@odoo/o-spreadsheet 19.5.0-alpha.1 → 19.5.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 19.5.0-alpha.1
6
- * @date 2026-07-01T05:05:42.239Z
7
- * @hash 3c78107
5
+ * @version 19.5.0-alpha.3
6
+ * @date 2026-07-14T10:17:06.949Z
7
+ * @hash c029184
8
8
  */
9
9
 
10
10
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
@@ -563,6 +563,7 @@ const DEFAULT_NUMBER_STYLE = {
563
563
  };
564
564
  const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
565
565
  const DEFAULT_WRAPPING_MODE = DEFAULT_STYLE.wrapping;
566
+ const DEFAULT_TEXT_HIGHLIGHT_PERCENT = .25;
566
567
  const DEFAULT_FONT_SIZE = DEFAULT_STYLE.fontSize;
567
568
  const DEFAULT_FONT = "'Roboto', arial, 'Liberation Sans'";
568
569
  const DEFAULT_BORDER_DESC = {
@@ -1247,6 +1248,12 @@ function defaultDict(def) {
1247
1248
  }
1248
1249
  };
1249
1250
  }
1251
+ function repeat(array, times) {
1252
+ const len = array.length;
1253
+ const result = new Array(len * times);
1254
+ for (let t = 0; t < times; t++) for (let i = 0; i < len; i++) result[t * len + i] = array[i];
1255
+ return result;
1256
+ }
1250
1257
 
1251
1258
  //#endregion
1252
1259
  //#region src/helpers/coordinates.ts
@@ -2600,7 +2607,7 @@ function unregisterChartJsExtensions() {
2600
2607
  *
2601
2608
  * The end goal is to eliminate all compatibility shims.
2602
2609
  */
2603
- 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: props$166, providePlugins, useScope, xml: xml$4 } = _odoo_owl;
2610
+ 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: props$167, providePlugins, useScope, xml: xml$4 } = _odoo_owl;
2604
2611
  const isOdooCompatLoaded = __ODOO_COMPATIBILITY_LAYER_ADDED__ === true;
2605
2612
  var _Component = class extends OwlComponent {
2606
2613
  static template = "";
@@ -2730,7 +2737,7 @@ var Portal = class extends OwlComponent {
2730
2737
  static template = xml$4`<t t-call-slot="default"/>`;
2731
2738
  constructor(node) {
2732
2739
  super(node);
2733
- this.props = props$166();
2740
+ this.props = props$167();
2734
2741
  this.__owl__ = node;
2735
2742
  }
2736
2743
  setup() {
@@ -8406,534 +8413,6 @@ function addOrigin(cell, origin) {
8406
8413
  return cell;
8407
8414
  }
8408
8415
 
8409
- //#endregion
8410
- //#region src/helpers/text_helper.ts
8411
- function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
8412
- return numberOfLines * (textLineHeight + 4) - 4;
8413
- }
8414
- function getCanvas(width = 100, height = 100) {
8415
- return new OffscreenCanvas(width, height).getContext("2d");
8416
- }
8417
- /**
8418
- * Get the default height of the cell given its style.
8419
- */
8420
- function getDefaultCellHeight(ctx, cell, locale, colSize) {
8421
- if (!cell || !cell.isFormula && !cell.content) return 23;
8422
- let content = "";
8423
- try {
8424
- if (!cell.isFormula) {
8425
- const localeFormat = {
8426
- format: cell.format,
8427
- locale
8428
- };
8429
- content = formatValue(parseLiteral(cell.content, locale), localeFormat);
8430
- }
8431
- } catch {
8432
- content = CellErrorType.GenericError;
8433
- }
8434
- return getCellContentHeight(ctx, content, cell.style, colSize);
8435
- }
8436
- function getCellContentHeight(ctx, content, style, colSize) {
8437
- return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
8438
- }
8439
- function getDefaultContextFont(fontSize, bold = false, italic = false) {
8440
- return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
8441
- }
8442
- function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
8443
- if (!textLines.length) return {
8444
- width: 0,
8445
- height: 0
8446
- };
8447
- const font = computeTextFont(style, fontUnit);
8448
- const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
8449
- const height = computeTextLinesHeight(sizes[0].height, textLines.length);
8450
- const width = Math.max(...sizes.map((size) => size.width));
8451
- if (!style.rotation) return {
8452
- height,
8453
- width
8454
- };
8455
- const cos = Math.abs(Math.cos(style.rotation));
8456
- const sin = Math.abs(Math.sin(style.rotation));
8457
- return {
8458
- width: width * cos + height * sin,
8459
- height: sin * width + cos * height
8460
- };
8461
- }
8462
- function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
8463
- return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
8464
- }
8465
- function computeCachedTextWidth(context, text, font, rotation) {
8466
- const size = computeCachedTextDimension(context, text, font);
8467
- if (!rotation) return size.width;
8468
- const cos = Math.abs(Math.cos(rotation));
8469
- const sin = Math.abs(Math.sin(rotation));
8470
- return size.width * cos + size.height * sin;
8471
- }
8472
- const textDimensionsCache = {};
8473
- function computeTextDimension(context, text, style, fontUnit = "pt") {
8474
- const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
8475
- if (!style.rotation) return size;
8476
- const cos = Math.abs(Math.cos(style.rotation));
8477
- const sin = Math.abs(Math.sin(style.rotation));
8478
- return {
8479
- width: size.width * cos + size.height * sin,
8480
- height: size.height * cos + size.width * sin
8481
- };
8482
- }
8483
- function computeCachedTextDimension(context, text, font) {
8484
- if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
8485
- if (textDimensionsCache[font][text] === void 0) {
8486
- context.save();
8487
- context.font = font;
8488
- const measure = context.measureText(text);
8489
- context.restore();
8490
- const width = measure.width;
8491
- const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
8492
- textDimensionsCache[font][text] = {
8493
- width,
8494
- height
8495
- };
8496
- }
8497
- return textDimensionsCache[font][text];
8498
- }
8499
- function fontSizeInPixels(fontSize) {
8500
- return Math.round(fontSize * 96 / 72);
8501
- }
8502
- function computeTextFont(style, fontUnit = "pt") {
8503
- return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
8504
- }
8505
- function computeTextFontSizeInPixels(style) {
8506
- return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
8507
- }
8508
- function splitWordToSpecificWidth(ctx, word, width, style) {
8509
- if (computeTextWidth(ctx, word, style) <= width) return [word];
8510
- const splitWord = [];
8511
- let wordPart = "";
8512
- for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
8513
- splitWord.push(wordPart);
8514
- wordPart = l;
8515
- } else wordPart += l;
8516
- splitWord.push(wordPart);
8517
- return splitWord;
8518
- }
8519
- /**
8520
- * Return the given text, split in multiple lines if needed. The text will be split in multiple
8521
- * line if it contains NEWLINE characters, or if it's longer than the given width.
8522
- */
8523
- function splitTextToWidth(ctx, text, style, width) {
8524
- if (!style) style = {};
8525
- if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
8526
- const brokenText = [];
8527
- const lines = text.includes("\n") ? text.split("\n") : [text];
8528
- for (const line of lines) {
8529
- const words = line.includes(" ") ? line.split(" ") : [line];
8530
- if (!width) {
8531
- brokenText.push(line);
8532
- continue;
8533
- }
8534
- let textLine = "";
8535
- let availableWidth = width;
8536
- for (const word of words) {
8537
- const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
8538
- const lastPart = splitWord.pop();
8539
- const lastPartWidth = computeTextWidth(ctx, lastPart, style);
8540
- if (splitWord.length) {
8541
- if (textLine !== "") {
8542
- brokenText.push(textLine);
8543
- textLine = "";
8544
- availableWidth = width;
8545
- }
8546
- splitWord.forEach((wordPart) => {
8547
- brokenText.push(wordPart);
8548
- });
8549
- textLine = lastPart;
8550
- availableWidth = width - lastPartWidth;
8551
- } else {
8552
- const _word = textLine === "" ? lastPart : " " + lastPart;
8553
- const wordWidth = computeTextWidth(ctx, _word, style);
8554
- if (wordWidth <= availableWidth) {
8555
- textLine += _word;
8556
- availableWidth -= wordWidth;
8557
- } else {
8558
- brokenText.push(textLine);
8559
- textLine = lastPart;
8560
- availableWidth = width - lastPartWidth;
8561
- }
8562
- }
8563
- }
8564
- if (textLine !== "") brokenText.push(textLine);
8565
- }
8566
- return brokenText;
8567
- }
8568
- /**
8569
- * Return the font size that makes the width of a text match the given line width.
8570
- * Minimum font size is 1.
8571
- *
8572
- * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
8573
- */
8574
- function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
8575
- let minFontSize = 1;
8576
- if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
8577
- if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
8578
- let fontSize = (minFontSize + maxFontSize) / 2;
8579
- let currentTextWidth = getTextWidth(fontSize);
8580
- let iterations = 0;
8581
- while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
8582
- if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
8583
- else minFontSize = (minFontSize + maxFontSize) / 2;
8584
- fontSize = (minFontSize + maxFontSize) / 2;
8585
- currentTextWidth = getTextWidth(fontSize);
8586
- iterations++;
8587
- }
8588
- return fontSize;
8589
- }
8590
- /** Transform a string to lowercase and removes whitespace from both ends of the string*/
8591
- function toTrimmedLowerCase(str) {
8592
- return str ? str.toLowerCase().trim() : "";
8593
- }
8594
- /**
8595
- * Extract the fontSize from a context font string
8596
- * @param font The (context) font string to parse
8597
- * @returns The fontSize in pixels
8598
- */
8599
- const pxRegex = /([0-9\.]*)px/;
8600
- function getContextFontSize(font) {
8601
- return Number(font.match(pxRegex)?.[1]);
8602
- }
8603
- function clipTextWithEllipsis(ctx, text, maxWidth) {
8604
- let width = computeCachedTextWidth(ctx, text, ctx.font);
8605
- if (width <= maxWidth) return text;
8606
- const ellipsis = "…";
8607
- const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
8608
- if (width <= ellipsisWidth) return text;
8609
- let len = text.length;
8610
- while (width >= maxWidth - ellipsisWidth && len-- > 0) {
8611
- text = text.substring(0, len);
8612
- width = computeCachedTextWidth(ctx, text, ctx.font);
8613
- }
8614
- return text + ellipsis;
8615
- }
8616
- function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10) {
8617
- context.fillText(text, position.x, position.y);
8618
- if (!underline && !strikethrough) return;
8619
- const measure = context.measureText(text);
8620
- const textWidth = measure.width;
8621
- const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
8622
- const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
8623
- let { x, y } = position;
8624
- let strikeY = y, underlineY = y;
8625
- switch (context.textAlign) {
8626
- case "center":
8627
- x -= textWidth / 2;
8628
- break;
8629
- case "right":
8630
- x -= textWidth;
8631
- break;
8632
- }
8633
- switch (context.textBaseline) {
8634
- case "top":
8635
- underlineY += boxHeight - 2 * strokeWidth;
8636
- strikeY += boxHeight / 2 - strokeWidth;
8637
- break;
8638
- case "middle":
8639
- underlineY += boxHeight / 2 - strokeWidth;
8640
- break;
8641
- case "alphabetic":
8642
- underlineY += 2 * strokeWidth;
8643
- strikeY -= 3 * strokeWidth;
8644
- break;
8645
- case "bottom":
8646
- underlineY = y;
8647
- strikeY -= textHeight / 2 - strokeWidth / 2;
8648
- break;
8649
- }
8650
- if (underline) {
8651
- context.lineWidth = strokeWidth;
8652
- context.strokeStyle = context.fillStyle;
8653
- context.beginPath();
8654
- context.moveTo(x, underlineY);
8655
- context.lineTo(x + textWidth, underlineY);
8656
- context.stroke();
8657
- }
8658
- if (strikethrough) {
8659
- context.lineWidth = strokeWidth;
8660
- context.strokeStyle = context.fillStyle;
8661
- context.beginPath();
8662
- context.moveTo(x, strikeY);
8663
- context.lineTo(x + textWidth, strikeY);
8664
- context.stroke();
8665
- }
8666
- }
8667
- function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
8668
- if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
8669
- const ellipsis = "...";
8670
- const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
8671
- if (ellipsisWidth >= width) return "";
8672
- let lowerBoundLen = 1;
8673
- let upperBoundLen = text.length;
8674
- let currentWidth;
8675
- while (lowerBoundLen <= upperBoundLen) {
8676
- const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
8677
- currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
8678
- if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
8679
- else lowerBoundLen = currentLen + 1;
8680
- }
8681
- const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
8682
- return slicedText ? slicedText + ellipsis : "";
8683
- }
8684
- /**
8685
- * Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
8686
- * with to original's text vertical and horizontal alignment.
8687
- */
8688
- function computeRotationPosition(rect, style) {
8689
- if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
8690
- let { x, y } = rect;
8691
- const cos = Math.cos(-style.rotation);
8692
- const sin = Math.sin(-style.rotation);
8693
- const width = rect.textWidth - 2 * 4;
8694
- const height = rect.textHeight;
8695
- const center = style.align === "center";
8696
- const rotateTowardCellCenter = style.align === "left" === sin < 0;
8697
- const sh = sin * height;
8698
- const sw = Math.abs(sin * width);
8699
- const ch = cos * height;
8700
- if (style.verticalAlign === "top") if (center) {
8701
- y += sw / 2;
8702
- x -= sh / 2;
8703
- } else if (rotateTowardCellCenter) x -= sh;
8704
- else y += sw;
8705
- else if (!style.verticalAlign || style.verticalAlign === "bottom") {
8706
- y += height - ch;
8707
- if (center) {
8708
- y -= sw / 2;
8709
- x -= sh / 2;
8710
- } else if (rotateTowardCellCenter) {
8711
- x -= sh;
8712
- y -= sw;
8713
- }
8714
- } else if (center) {
8715
- x -= sh / 2;
8716
- y -= height / 2;
8717
- if (rotateTowardCellCenter) y += sh;
8718
- else y -= sh;
8719
- } else if (rotateTowardCellCenter) {
8720
- x -= sh;
8721
- y -= sw / 2;
8722
- } else y += sw / 2 + ch / 4;
8723
- return {
8724
- x: cos * x - sin * y,
8725
- y: cos * y + sin * x
8726
- };
8727
- }
8728
-
8729
- //#endregion
8730
- //#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
8731
- /** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
8732
- const chartColorScalePlugin = {
8733
- id: "chartColorScalePlugin",
8734
- afterDatasetsDraw(chart, args, options) {
8735
- if (!options.position || options.position === "none" || !options.colorScale.length) return;
8736
- const ctx = chart.ctx;
8737
- ctx.save();
8738
- ctx.textAlign = "center";
8739
- ctx.textBaseline = "middle";
8740
- ctx.miterLimit = 1;
8741
- const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
8742
- const gradientWidth = 10;
8743
- const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
8744
- const gradientY = chart.chartArea.top;
8745
- const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
8746
- const step = 1 / (options.colorScale.length - 1);
8747
- options.colorScale.forEach((color, index) => {
8748
- gradient.addColorStop(index * step, color);
8749
- });
8750
- ctx.fillStyle = gradient;
8751
- ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
8752
- ctx.fillStyle = options.fontColor ?? "black";
8753
- ctx.font = getDefaultContextFont(12);
8754
- ctx.textAlign = "left";
8755
- let minValue = Math.round(options.minValue * 100) / 100;
8756
- let maxValue = Math.round(options.maxValue * 100) / 100;
8757
- if (options.minValue === options.maxValue) {
8758
- minValue -= 1;
8759
- maxValue += 1;
8760
- }
8761
- const formattedMaxValue = humanizeNumber({
8762
- value: maxValue,
8763
- format: void 0
8764
- }, options.locale);
8765
- const formattedMinValue = humanizeNumber({
8766
- value: minValue,
8767
- format: void 0
8768
- }, options.locale);
8769
- ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
8770
- ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
8771
- ctx.restore();
8772
- }
8773
- };
8774
-
8775
- //#endregion
8776
- //#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
8777
- function getFunnelChartController() {
8778
- if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
8779
- return class FunnelChartController extends globalThis.Chart.BarController {
8780
- static id = "funnel";
8781
- static defaults = {
8782
- ...globalThis.Chart?.BarController.defaults,
8783
- dataElementType: "funnel",
8784
- animation: { duration: (ctx) => {
8785
- if (ctx.type !== "data") return 1e3;
8786
- return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
8787
- } }
8788
- };
8789
- /** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
8790
- updateElements(rects, start, count, mode) {
8791
- super.updateElements(rects, start, count, mode);
8792
- for (let i = start; i < start + count; i++) {
8793
- const rect = rects[i];
8794
- this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
8795
- }
8796
- }
8797
- };
8798
- }
8799
- function getFunnelChartElement() {
8800
- if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
8801
- /**
8802
- * Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
8803
- * `width`, and the bottom is of width `nextElementWidth`.
8804
- */
8805
- return class FunnelChartElement extends globalThis.Chart.BarElement {
8806
- static id = "funnel";
8807
- /** Overwrite this to draw a trapezoid rather then a rectangle */
8808
- draw(ctx) {
8809
- ctx.save();
8810
- const { x, y, height, nextElement, base, options } = this.getProps([
8811
- "x",
8812
- "y",
8813
- "width",
8814
- "height",
8815
- "nextElement",
8816
- "base",
8817
- "options"
8818
- ]);
8819
- const width = getElementWidth(this);
8820
- const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
8821
- const startX = Math.min(x, base);
8822
- const startY = y - height / 2;
8823
- ctx.fillStyle = options.backgroundColor;
8824
- ctx.beginPath();
8825
- ctx.moveTo(startX, startY);
8826
- ctx.lineTo(startX + width, startY);
8827
- ctx.lineTo(startX + width - offset, startY + height);
8828
- ctx.lineTo(startX + offset, startY + height);
8829
- ctx.closePath();
8830
- ctx.fill();
8831
- if (options.borderWidth) {
8832
- ctx.strokeStyle = options.borderColor;
8833
- ctx.lineWidth = options.borderWidth;
8834
- ctx.stroke();
8835
- }
8836
- ctx.restore();
8837
- }
8838
- /** Check if the mouse is inside the trapezoid */
8839
- inRange(mouseX, mouseY) {
8840
- const { x, y, height, nextElement, base } = this.getProps([
8841
- "x",
8842
- "y",
8843
- "width",
8844
- "height",
8845
- "nextElement",
8846
- "base",
8847
- "options"
8848
- ]);
8849
- const width = getElementWidth(this);
8850
- const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
8851
- const startX = Math.min(x, base);
8852
- const startY = y - height / 2;
8853
- if (mouseY < startY || mouseY > startY + height) return false;
8854
- const offset = (width - nextElementWidth) / 2;
8855
- const left = startX + offset * (mouseY - startY) / height;
8856
- const right = startX + width - offset * (mouseY - startY) / height;
8857
- if (mouseX < left || mouseX > right) return false;
8858
- return true;
8859
- }
8860
- };
8861
- }
8862
- /**
8863
- * Get an element width.
8864
- *
8865
- * The property width is undefined during animations, we need to compute it manually.
8866
- */
8867
- function getElementWidth(element) {
8868
- const { x, base } = element.getProps(["x", "base"]);
8869
- return Math.max(x, base) - Math.min(x, base);
8870
- }
8871
- /**
8872
- * Position the tooltip inside the trapezoid.
8873
- * The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
8874
- */
8875
- const funnelTooltipPositioner = function(elements) {
8876
- if (!elements.length) return {
8877
- x: 0,
8878
- y: 0
8879
- };
8880
- const { x, y, base, width, height } = elements[0].element.getProps([
8881
- "x",
8882
- "y",
8883
- "width",
8884
- "height",
8885
- "base"
8886
- ]);
8887
- const startX = Math.min(x, base);
8888
- const startY = y - height / 2;
8889
- return {
8890
- x: startX + width * 2 / 3,
8891
- y: startY + height / 2
8892
- };
8893
- };
8894
-
8895
- //#endregion
8896
- //#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
8897
- const chartMinorGridPlugin = {
8898
- id: "o-spreadsheet-minor-gridlines",
8899
- beforeDatasetsDraw(chart) {
8900
- const ctx = chart.ctx;
8901
- const chartArea = chart.chartArea;
8902
- if (!chartArea) return;
8903
- for (const scaleId in chart.scales) {
8904
- const scale = chart.scales[scaleId];
8905
- const options = scale.options;
8906
- const minor = options?.grid?.minor;
8907
- if (!minor?.display) continue;
8908
- const showMajorGrid = options?.grid?.display;
8909
- const ticks = scale.ticks;
8910
- if (!ticks || ticks.length < 2) continue;
8911
- ctx.save();
8912
- ctx.lineWidth = 1;
8913
- ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
8914
- for (let i = 0; i < ticks.length - 1; i++) {
8915
- const start = scale.getPixelForTick(i);
8916
- const end = scale.getPixelForTick(i + 1);
8917
- if (!isFinite(start) || !isFinite(end)) continue;
8918
- for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
8919
- const ratio = j / 4;
8920
- const position = Math.round(start + (end - start) * ratio) + .5;
8921
- ctx.beginPath();
8922
- if (scale.isHorizontal()) {
8923
- ctx.moveTo(position, chartArea.top);
8924
- ctx.lineTo(position, chartArea.bottom);
8925
- } else {
8926
- ctx.moveTo(chartArea.left, position);
8927
- ctx.lineTo(chartArea.right, position);
8928
- }
8929
- ctx.stroke();
8930
- }
8931
- }
8932
- ctx.restore();
8933
- }
8934
- }
8935
- };
8936
-
8937
8416
  //#endregion
8938
8417
  //#region src/helpers/color.ts
8939
8418
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -9527,113 +9006,646 @@ var AlternatingColorGenerator = class extends ColorGenerator {
9527
9006
  super(paletteSize, preferredColors);
9528
9007
  this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
9529
9008
  }
9530
- };
9531
- var AlternatingColorMap = class {
9532
- availableColors;
9533
- colors = {};
9534
- constructor(paletteSize = 12) {
9535
- this.availableColors = new AlternatingColorGenerator(paletteSize);
9009
+ };
9010
+ var AlternatingColorMap = class {
9011
+ availableColors;
9012
+ colors = {};
9013
+ constructor(paletteSize = 12) {
9014
+ this.availableColors = new AlternatingColorGenerator(paletteSize);
9015
+ }
9016
+ get(id) {
9017
+ if (!this.colors[id]) this.colors[id] = this.availableColors.next();
9018
+ return this.colors[id];
9019
+ }
9020
+ };
9021
+ const COLORSCHEMES = {
9022
+ greys: [
9023
+ "#ffffff",
9024
+ "#808080",
9025
+ "#000000"
9026
+ ],
9027
+ blues: [
9028
+ "#f7fbff",
9029
+ "#6aaed6",
9030
+ "#08306b"
9031
+ ],
9032
+ reds: [
9033
+ "#fff5f0",
9034
+ "#fb694a",
9035
+ "#67000d"
9036
+ ],
9037
+ greens: [
9038
+ "#f7fcf5",
9039
+ "#73c476",
9040
+ "#00441b"
9041
+ ],
9042
+ oranges: [
9043
+ "#fff5eb",
9044
+ "#fd8c3b",
9045
+ "#7f2704"
9046
+ ],
9047
+ purples: [
9048
+ "#fcfbfd",
9049
+ "#9e9ac8",
9050
+ "#3f007d"
9051
+ ],
9052
+ viridis: [
9053
+ "#440154",
9054
+ "#21918c",
9055
+ "#fde725"
9056
+ ],
9057
+ cividis: [
9058
+ "#00224e",
9059
+ "#7d7c78",
9060
+ "#fee838"
9061
+ ],
9062
+ rainbow: [
9063
+ "#B41DB4",
9064
+ "#FFFF00",
9065
+ "#00FFFF"
9066
+ ]
9067
+ };
9068
+ const COLORSCALES = Object.keys(COLORSCHEMES);
9069
+ /**
9070
+ * Returns a function that maps a value to a color using a color scale defined by the given
9071
+ * color/threshold values pairs.
9072
+ */
9073
+ function getColorScale(colorScalePoints) {
9074
+ if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
9075
+ const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
9076
+ const thresholds = [];
9077
+ for (let i = 1; i < sortedColorScalePoints.length; i++) {
9078
+ const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
9079
+ const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
9080
+ const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
9081
+ const maxColor = colorToNumber(sortedColorScalePoints[i].color);
9082
+ thresholds.push({
9083
+ min: sortedColorScalePoints[i - 1].value,
9084
+ max: sortedColorScalePoints[i].value,
9085
+ minColor,
9086
+ maxColor,
9087
+ minColorAlpha,
9088
+ maxColorAlpha,
9089
+ colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
9090
+ });
9091
+ }
9092
+ return (value) => {
9093
+ if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
9094
+ for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
9095
+ return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
9096
+ };
9097
+ }
9098
+ function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
9099
+ const deltaValue = maxValue - minValue;
9100
+ const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
9101
+ const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
9102
+ const deltaColorB = minColor % 256 - maxColor % 256;
9103
+ return [
9104
+ deltaColorR / deltaValue,
9105
+ deltaColorG / deltaValue,
9106
+ deltaColorB / deltaValue
9107
+ ];
9108
+ }
9109
+ function colorCell(value, minValue, minColor, colorDiffUnit) {
9110
+ const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
9111
+ const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
9112
+ const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
9113
+ const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
9114
+ return r << 16 | g << 8 | b;
9115
+ }
9116
+
9117
+ //#endregion
9118
+ //#region src/helpers/text_helper.ts
9119
+ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
9120
+ return numberOfLines * (textLineHeight + 4) - 4;
9121
+ }
9122
+ function getCanvas(width = 100, height = 100) {
9123
+ return new OffscreenCanvas(width, height).getContext("2d");
9124
+ }
9125
+ /**
9126
+ * Get the default height of the cell given its style.
9127
+ */
9128
+ function getDefaultCellHeight(ctx, cell, locale, colSize) {
9129
+ if (!cell || !cell.isFormula && !cell.content) return 23;
9130
+ let content = "";
9131
+ try {
9132
+ if (!cell.isFormula) {
9133
+ const localeFormat = {
9134
+ format: cell.format,
9135
+ locale
9136
+ };
9137
+ content = formatValue(parseLiteral(cell.content, locale), localeFormat);
9138
+ }
9139
+ } catch {
9140
+ content = CellErrorType.GenericError;
9141
+ }
9142
+ return getCellContentHeight(ctx, content, cell.style, colSize);
9143
+ }
9144
+ function getCellContentHeight(ctx, content, style, colSize) {
9145
+ return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
9146
+ }
9147
+ function getDefaultContextFont(fontSize, bold = false, italic = false) {
9148
+ return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
9149
+ }
9150
+ function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
9151
+ if (!textLines.length) return {
9152
+ width: 0,
9153
+ height: 0
9154
+ };
9155
+ const font = computeTextFont(style, fontUnit);
9156
+ const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
9157
+ const height = computeTextLinesHeight(sizes[0].height, textLines.length);
9158
+ const width = Math.max(...sizes.map((size) => size.width));
9159
+ if (!style.rotation) return {
9160
+ height,
9161
+ width
9162
+ };
9163
+ const cos = Math.abs(Math.cos(style.rotation));
9164
+ const sin = Math.abs(Math.sin(style.rotation));
9165
+ return {
9166
+ width: width * cos + height * sin,
9167
+ height: sin * width + cos * height
9168
+ };
9169
+ }
9170
+ function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
9171
+ return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
9172
+ }
9173
+ function computeCachedTextWidth(context, text, font, rotation) {
9174
+ const size = computeCachedTextDimension(context, text, font);
9175
+ if (!rotation) return size.width;
9176
+ const cos = Math.abs(Math.cos(rotation));
9177
+ const sin = Math.abs(Math.sin(rotation));
9178
+ return size.width * cos + size.height * sin;
9179
+ }
9180
+ const textDimensionsCache = {};
9181
+ function computeTextDimension(context, text, style, fontUnit = "pt") {
9182
+ const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
9183
+ if (!style.rotation) return size;
9184
+ const cos = Math.abs(Math.cos(style.rotation));
9185
+ const sin = Math.abs(Math.sin(style.rotation));
9186
+ return {
9187
+ width: size.width * cos + size.height * sin,
9188
+ height: size.height * cos + size.width * sin
9189
+ };
9190
+ }
9191
+ function computeCachedTextDimension(context, text, font) {
9192
+ if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
9193
+ if (textDimensionsCache[font][text] === void 0) {
9194
+ context.save();
9195
+ context.font = font;
9196
+ const measure = context.measureText(text);
9197
+ context.restore();
9198
+ const width = measure.width;
9199
+ const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
9200
+ textDimensionsCache[font][text] = {
9201
+ width,
9202
+ height
9203
+ };
9204
+ }
9205
+ return textDimensionsCache[font][text];
9206
+ }
9207
+ function fontSizeInPixels(fontSize) {
9208
+ return Math.round(fontSize * 96 / 72);
9209
+ }
9210
+ function computeTextFont(style, fontUnit = "pt") {
9211
+ return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
9212
+ }
9213
+ function computeTextFontSizeInPixels(style) {
9214
+ return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
9215
+ }
9216
+ function splitWordToSpecificWidth(ctx, word, width, style) {
9217
+ if (computeTextWidth(ctx, word, style) <= width) return [word];
9218
+ const splitWord = [];
9219
+ let wordPart = "";
9220
+ for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
9221
+ splitWord.push(wordPart);
9222
+ wordPart = l;
9223
+ } else wordPart += l;
9224
+ splitWord.push(wordPart);
9225
+ return splitWord;
9226
+ }
9227
+ /**
9228
+ * Return the given text, split in multiple lines if needed. The text will be split in multiple
9229
+ * line if it contains NEWLINE characters, or if it's longer than the given width.
9230
+ */
9231
+ function splitTextToWidth(ctx, text, style, width) {
9232
+ if (!style) style = {};
9233
+ if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
9234
+ const brokenText = [];
9235
+ const lines = text.includes("\n") ? text.split("\n") : [text];
9236
+ for (const line of lines) {
9237
+ const words = line.includes(" ") ? line.split(" ") : [line];
9238
+ if (!width) {
9239
+ brokenText.push(line);
9240
+ continue;
9241
+ }
9242
+ let textLine = "";
9243
+ let availableWidth = width;
9244
+ for (const word of words) {
9245
+ const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
9246
+ const lastPart = splitWord.pop();
9247
+ const lastPartWidth = computeTextWidth(ctx, lastPart, style);
9248
+ if (splitWord.length) {
9249
+ if (textLine !== "") {
9250
+ brokenText.push(textLine);
9251
+ textLine = "";
9252
+ availableWidth = width;
9253
+ }
9254
+ splitWord.forEach((wordPart) => {
9255
+ brokenText.push(wordPart);
9256
+ });
9257
+ textLine = lastPart;
9258
+ availableWidth = width - lastPartWidth;
9259
+ } else {
9260
+ const _word = textLine === "" ? lastPart : " " + lastPart;
9261
+ const wordWidth = computeTextWidth(ctx, _word, style);
9262
+ if (wordWidth <= availableWidth) {
9263
+ textLine += _word;
9264
+ availableWidth -= wordWidth;
9265
+ } else {
9266
+ brokenText.push(textLine);
9267
+ textLine = lastPart;
9268
+ availableWidth = width - lastPartWidth;
9269
+ }
9270
+ }
9271
+ }
9272
+ if (textLine !== "") brokenText.push(textLine);
9273
+ }
9274
+ return brokenText;
9275
+ }
9276
+ /**
9277
+ * Return the font size that makes the width of a text match the given line width.
9278
+ * Minimum font size is 1.
9279
+ *
9280
+ * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
9281
+ */
9282
+ function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
9283
+ let minFontSize = 1;
9284
+ if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
9285
+ if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
9286
+ let fontSize = (minFontSize + maxFontSize) / 2;
9287
+ let currentTextWidth = getTextWidth(fontSize);
9288
+ let iterations = 0;
9289
+ while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
9290
+ if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
9291
+ else minFontSize = (minFontSize + maxFontSize) / 2;
9292
+ fontSize = (minFontSize + maxFontSize) / 2;
9293
+ currentTextWidth = getTextWidth(fontSize);
9294
+ iterations++;
9295
+ }
9296
+ return fontSize;
9297
+ }
9298
+ /** Transform a string to lowercase and removes whitespace from both ends of the string*/
9299
+ function toTrimmedLowerCase(str) {
9300
+ return str ? str.toLowerCase().trim() : "";
9301
+ }
9302
+ /**
9303
+ * Extract the fontSize from a context font string
9304
+ * @param font The (context) font string to parse
9305
+ * @returns The fontSize in pixels
9306
+ */
9307
+ const pxRegex = /([0-9\.]*)px/;
9308
+ function getContextFontSize(font) {
9309
+ return Number(font.match(pxRegex)?.[1]);
9310
+ }
9311
+ function clipTextWithEllipsis(ctx, text, maxWidth) {
9312
+ let width = computeCachedTextWidth(ctx, text, ctx.font);
9313
+ if (width <= maxWidth) return text;
9314
+ const ellipsis = "…";
9315
+ const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
9316
+ if (width <= ellipsisWidth) return text;
9317
+ let len = text.length;
9318
+ while (width >= maxWidth - ellipsisWidth && len-- > 0) {
9319
+ text = text.substring(0, len);
9320
+ width = computeCachedTextWidth(ctx, text, ctx.font);
9321
+ }
9322
+ return text + ellipsis;
9323
+ }
9324
+ function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10, highlightText = false) {
9325
+ if (highlightText) {
9326
+ context.save();
9327
+ context.fillStyle = lightenColor(context.fillStyle, DEFAULT_TEXT_HIGHLIGHT_PERCENT);
9328
+ }
9329
+ context.fillText(text, position.x, position.y);
9330
+ if (!underline && !strikethrough) return;
9331
+ const measure = context.measureText(text);
9332
+ const textWidth = measure.width;
9333
+ const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
9334
+ const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
9335
+ let { x, y } = position;
9336
+ let strikeY = y, underlineY = y;
9337
+ switch (context.textAlign) {
9338
+ case "center":
9339
+ x -= textWidth / 2;
9340
+ break;
9341
+ case "right":
9342
+ x -= textWidth;
9343
+ break;
9344
+ }
9345
+ switch (context.textBaseline) {
9346
+ case "top":
9347
+ underlineY += boxHeight - 2 * strokeWidth;
9348
+ strikeY += boxHeight / 2 - strokeWidth;
9349
+ break;
9350
+ case "middle":
9351
+ underlineY += boxHeight / 2 - strokeWidth;
9352
+ break;
9353
+ case "alphabetic":
9354
+ underlineY += 2 * strokeWidth;
9355
+ strikeY -= 3 * strokeWidth;
9356
+ break;
9357
+ case "bottom":
9358
+ underlineY = y;
9359
+ strikeY -= textHeight / 2 - strokeWidth / 2;
9360
+ break;
9361
+ }
9362
+ if (underline) {
9363
+ context.lineWidth = strokeWidth;
9364
+ context.strokeStyle = context.fillStyle;
9365
+ context.beginPath();
9366
+ context.moveTo(x, underlineY);
9367
+ context.lineTo(x + textWidth, underlineY);
9368
+ context.stroke();
9536
9369
  }
9537
- get(id) {
9538
- if (!this.colors[id]) this.colors[id] = this.availableColors.next();
9539
- return this.colors[id];
9370
+ if (strikethrough) {
9371
+ context.lineWidth = strokeWidth;
9372
+ context.strokeStyle = context.fillStyle;
9373
+ context.beginPath();
9374
+ context.moveTo(x, strikeY);
9375
+ context.lineTo(x + textWidth, strikeY);
9376
+ context.stroke();
9540
9377
  }
9541
- };
9542
- const COLORSCHEMES = {
9543
- greys: [
9544
- "#ffffff",
9545
- "#808080",
9546
- "#000000"
9547
- ],
9548
- blues: [
9549
- "#f7fbff",
9550
- "#6aaed6",
9551
- "#08306b"
9552
- ],
9553
- reds: [
9554
- "#fff5f0",
9555
- "#fb694a",
9556
- "#67000d"
9557
- ],
9558
- greens: [
9559
- "#f7fcf5",
9560
- "#73c476",
9561
- "#00441b"
9562
- ],
9563
- oranges: [
9564
- "#fff5eb",
9565
- "#fd8c3b",
9566
- "#7f2704"
9567
- ],
9568
- purples: [
9569
- "#fcfbfd",
9570
- "#9e9ac8",
9571
- "#3f007d"
9572
- ],
9573
- viridis: [
9574
- "#440154",
9575
- "#21918c",
9576
- "#fde725"
9577
- ],
9578
- cividis: [
9579
- "#00224e",
9580
- "#7d7c78",
9581
- "#fee838"
9582
- ],
9583
- rainbow: [
9584
- "#B41DB4",
9585
- "#FFFF00",
9586
- "#00FFFF"
9587
- ]
9588
- };
9589
- const COLORSCALES = Object.keys(COLORSCHEMES);
9378
+ if (highlightText) context.restore();
9379
+ }
9380
+ function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
9381
+ if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
9382
+ const ellipsis = "...";
9383
+ const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
9384
+ if (ellipsisWidth >= width) return "";
9385
+ let lowerBoundLen = 1;
9386
+ let upperBoundLen = text.length;
9387
+ let currentWidth;
9388
+ while (lowerBoundLen <= upperBoundLen) {
9389
+ const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
9390
+ currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
9391
+ if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
9392
+ else lowerBoundLen = currentLen + 1;
9393
+ }
9394
+ const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
9395
+ return slicedText ? slicedText + ellipsis : "";
9396
+ }
9590
9397
  /**
9591
- * Returns a function that maps a value to a color using a color scale defined by the given
9592
- * color/threshold values pairs.
9398
+ * Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
9399
+ * with to original's text vertical and horizontal alignment.
9593
9400
  */
9594
- function getColorScale(colorScalePoints) {
9595
- if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
9596
- const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
9597
- const thresholds = [];
9598
- for (let i = 1; i < sortedColorScalePoints.length; i++) {
9599
- const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
9600
- const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
9601
- const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
9602
- const maxColor = colorToNumber(sortedColorScalePoints[i].color);
9603
- thresholds.push({
9604
- min: sortedColorScalePoints[i - 1].value,
9605
- max: sortedColorScalePoints[i].value,
9606
- minColor,
9607
- maxColor,
9608
- minColorAlpha,
9609
- maxColorAlpha,
9610
- colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
9401
+ function computeRotationPosition(rect, style) {
9402
+ if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
9403
+ let { x, y } = rect;
9404
+ const cos = Math.cos(-style.rotation);
9405
+ const sin = Math.sin(-style.rotation);
9406
+ const width = rect.textWidth - 2 * 4;
9407
+ const height = rect.textHeight;
9408
+ const center = style.align === "center";
9409
+ const rotateTowardCellCenter = style.align === "left" === sin < 0;
9410
+ const sh = sin * height;
9411
+ const sw = Math.abs(sin * width);
9412
+ const ch = cos * height;
9413
+ if (style.verticalAlign === "top") if (center) {
9414
+ y += sw / 2;
9415
+ x -= sh / 2;
9416
+ } else if (rotateTowardCellCenter) x -= sh;
9417
+ else y += sw;
9418
+ else if (!style.verticalAlign || style.verticalAlign === "bottom") {
9419
+ y += height - ch;
9420
+ if (center) {
9421
+ y -= sw / 2;
9422
+ x -= sh / 2;
9423
+ } else if (rotateTowardCellCenter) {
9424
+ x -= sh;
9425
+ y -= sw;
9426
+ }
9427
+ } else if (center) {
9428
+ x -= sh / 2;
9429
+ y -= height / 2;
9430
+ if (rotateTowardCellCenter) y += sh;
9431
+ else y -= sh;
9432
+ } else if (rotateTowardCellCenter) {
9433
+ x -= sh;
9434
+ y -= sw / 2;
9435
+ } else y += sw / 2 + ch / 4;
9436
+ return {
9437
+ x: cos * x - sin * y,
9438
+ y: cos * y + sin * x
9439
+ };
9440
+ }
9441
+
9442
+ //#endregion
9443
+ //#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
9444
+ /** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
9445
+ const chartColorScalePlugin = {
9446
+ id: "chartColorScalePlugin",
9447
+ afterDatasetsDraw(chart, args, options) {
9448
+ if (!options.position || options.position === "none" || !options.colorScale.length) return;
9449
+ const ctx = chart.ctx;
9450
+ ctx.save();
9451
+ ctx.textAlign = "center";
9452
+ ctx.textBaseline = "middle";
9453
+ ctx.miterLimit = 1;
9454
+ const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
9455
+ const gradientWidth = 10;
9456
+ const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
9457
+ const gradientY = chart.chartArea.top;
9458
+ const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
9459
+ const step = 1 / (options.colorScale.length - 1);
9460
+ options.colorScale.forEach((color, index) => {
9461
+ gradient.addColorStop(index * step, color);
9611
9462
  });
9463
+ ctx.fillStyle = gradient;
9464
+ ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
9465
+ ctx.fillStyle = options.fontColor ?? "black";
9466
+ ctx.font = getDefaultContextFont(12);
9467
+ ctx.textAlign = "left";
9468
+ let minValue = Math.round(options.minValue * 100) / 100;
9469
+ let maxValue = Math.round(options.maxValue * 100) / 100;
9470
+ if (options.minValue === options.maxValue) {
9471
+ minValue -= 1;
9472
+ maxValue += 1;
9473
+ }
9474
+ const formattedMaxValue = humanizeNumber({
9475
+ value: maxValue,
9476
+ format: void 0
9477
+ }, options.locale);
9478
+ const formattedMinValue = humanizeNumber({
9479
+ value: minValue,
9480
+ format: void 0
9481
+ }, options.locale);
9482
+ ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
9483
+ ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
9484
+ ctx.restore();
9612
9485
  }
9613
- return (value) => {
9614
- if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
9615
- for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
9616
- return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
9486
+ };
9487
+
9488
+ //#endregion
9489
+ //#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
9490
+ function getFunnelChartController() {
9491
+ if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
9492
+ return class FunnelChartController extends globalThis.Chart.BarController {
9493
+ static id = "funnel";
9494
+ static defaults = {
9495
+ ...globalThis.Chart?.BarController.defaults,
9496
+ dataElementType: "funnel",
9497
+ animation: { duration: (ctx) => {
9498
+ if (ctx.type !== "data") return 1e3;
9499
+ return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
9500
+ } }
9501
+ };
9502
+ /** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
9503
+ updateElements(rects, start, count, mode) {
9504
+ super.updateElements(rects, start, count, mode);
9505
+ for (let i = start; i < start + count; i++) {
9506
+ const rect = rects[i];
9507
+ this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
9508
+ }
9509
+ }
9617
9510
  };
9618
9511
  }
9619
- function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
9620
- const deltaValue = maxValue - minValue;
9621
- const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
9622
- const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
9623
- const deltaColorB = minColor % 256 - maxColor % 256;
9624
- return [
9625
- deltaColorR / deltaValue,
9626
- deltaColorG / deltaValue,
9627
- deltaColorB / deltaValue
9628
- ];
9512
+ function getFunnelChartElement() {
9513
+ if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
9514
+ /**
9515
+ * Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
9516
+ * `width`, and the bottom is of width `nextElementWidth`.
9517
+ */
9518
+ return class FunnelChartElement extends globalThis.Chart.BarElement {
9519
+ static id = "funnel";
9520
+ /** Overwrite this to draw a trapezoid rather then a rectangle */
9521
+ draw(ctx) {
9522
+ ctx.save();
9523
+ const { x, y, height, nextElement, base, options } = this.getProps([
9524
+ "x",
9525
+ "y",
9526
+ "width",
9527
+ "height",
9528
+ "nextElement",
9529
+ "base",
9530
+ "options"
9531
+ ]);
9532
+ const width = getElementWidth(this);
9533
+ const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
9534
+ const startX = Math.min(x, base);
9535
+ const startY = y - height / 2;
9536
+ ctx.fillStyle = options.backgroundColor;
9537
+ ctx.beginPath();
9538
+ ctx.moveTo(startX, startY);
9539
+ ctx.lineTo(startX + width, startY);
9540
+ ctx.lineTo(startX + width - offset, startY + height);
9541
+ ctx.lineTo(startX + offset, startY + height);
9542
+ ctx.closePath();
9543
+ ctx.fill();
9544
+ if (options.borderWidth) {
9545
+ ctx.strokeStyle = options.borderColor;
9546
+ ctx.lineWidth = options.borderWidth;
9547
+ ctx.stroke();
9548
+ }
9549
+ ctx.restore();
9550
+ }
9551
+ /** Check if the mouse is inside the trapezoid */
9552
+ inRange(mouseX, mouseY) {
9553
+ const { x, y, height, nextElement, base } = this.getProps([
9554
+ "x",
9555
+ "y",
9556
+ "width",
9557
+ "height",
9558
+ "nextElement",
9559
+ "base",
9560
+ "options"
9561
+ ]);
9562
+ const width = getElementWidth(this);
9563
+ const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
9564
+ const startX = Math.min(x, base);
9565
+ const startY = y - height / 2;
9566
+ if (mouseY < startY || mouseY > startY + height) return false;
9567
+ const offset = (width - nextElementWidth) / 2;
9568
+ const left = startX + offset * (mouseY - startY) / height;
9569
+ const right = startX + width - offset * (mouseY - startY) / height;
9570
+ if (mouseX < left || mouseX > right) return false;
9571
+ return true;
9572
+ }
9573
+ };
9629
9574
  }
9630
- function colorCell(value, minValue, minColor, colorDiffUnit) {
9631
- const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
9632
- const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
9633
- const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
9634
- const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
9635
- return r << 16 | g << 8 | b;
9575
+ /**
9576
+ * Get an element width.
9577
+ *
9578
+ * The property width is undefined during animations, we need to compute it manually.
9579
+ */
9580
+ function getElementWidth(element) {
9581
+ const { x, base } = element.getProps(["x", "base"]);
9582
+ return Math.max(x, base) - Math.min(x, base);
9636
9583
  }
9584
+ /**
9585
+ * Position the tooltip inside the trapezoid.
9586
+ * The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
9587
+ */
9588
+ const funnelTooltipPositioner = function(elements) {
9589
+ if (!elements.length) return {
9590
+ x: 0,
9591
+ y: 0
9592
+ };
9593
+ const { x, y, base, width, height } = elements[0].element.getProps([
9594
+ "x",
9595
+ "y",
9596
+ "width",
9597
+ "height",
9598
+ "base"
9599
+ ]);
9600
+ const startX = Math.min(x, base);
9601
+ const startY = y - height / 2;
9602
+ return {
9603
+ x: startX + width * 2 / 3,
9604
+ y: startY + height / 2
9605
+ };
9606
+ };
9607
+
9608
+ //#endregion
9609
+ //#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
9610
+ const chartMinorGridPlugin = {
9611
+ id: "o-spreadsheet-minor-gridlines",
9612
+ beforeDatasetsDraw(chart) {
9613
+ const ctx = chart.ctx;
9614
+ const chartArea = chart.chartArea;
9615
+ if (!chartArea) return;
9616
+ for (const scaleId in chart.scales) {
9617
+ const scale = chart.scales[scaleId];
9618
+ const options = scale.options;
9619
+ const minor = options?.grid?.minor;
9620
+ if (!minor?.display) continue;
9621
+ const showMajorGrid = options?.grid?.display;
9622
+ const ticks = scale.ticks;
9623
+ if (!ticks || ticks.length < 2) continue;
9624
+ ctx.save();
9625
+ ctx.lineWidth = 1;
9626
+ ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
9627
+ for (let i = 0; i < ticks.length - 1; i++) {
9628
+ const start = scale.getPixelForTick(i);
9629
+ const end = scale.getPixelForTick(i + 1);
9630
+ if (!isFinite(start) || !isFinite(end)) continue;
9631
+ for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
9632
+ const ratio = j / 4;
9633
+ const position = Math.round(start + (end - start) * ratio) + .5;
9634
+ ctx.beginPath();
9635
+ if (scale.isHorizontal()) {
9636
+ ctx.moveTo(position, chartArea.top);
9637
+ ctx.lineTo(position, chartArea.bottom);
9638
+ } else {
9639
+ ctx.moveTo(chartArea.left, position);
9640
+ ctx.lineTo(chartArea.right, position);
9641
+ }
9642
+ ctx.stroke();
9643
+ }
9644
+ }
9645
+ ctx.restore();
9646
+ }
9647
+ }
9648
+ };
9637
9649
 
9638
9650
  //#endregion
9639
9651
  //#region src/xlsx/constants.ts
@@ -10127,6 +10139,7 @@ function getChartBackgroundColor({ background }, getters) {
10127
10139
  //#endregion
10128
10140
  //#region src/components/figures/chart/chartJs/chartjs_show_values_plugin.ts
10129
10141
  const MINIMAL_VERTICAL_DISTANCE = 13;
10142
+ const HORIZONTAL_PADDING = 3;
10130
10143
  function isLineOverlayOnBarChart(options, dataset) {
10131
10144
  return options.type === "bar" && dataset.type === "line";
10132
10145
  }
@@ -10136,175 +10149,276 @@ const chartShowValuesPlugin = {
10136
10149
  afterDatasetsDraw(chart, args, options) {
10137
10150
  if (!options.showValues) return;
10138
10151
  if (!chart._metasets?.[0]?.data) return;
10139
- const ctx = chart.ctx;
10140
- ctx.save();
10141
- ctx.textAlign = "center";
10142
- ctx.textBaseline = "middle";
10143
- ctx.miterLimit = 1;
10144
10152
  switch (options.type) {
10145
10153
  case "pie":
10146
- drawPieChartValues(chart, options, ctx);
10154
+ drawPieValues(chart, options);
10147
10155
  break;
10148
10156
  case "line":
10149
10157
  case "scatter":
10158
+ drawLineValues(chart, options);
10159
+ break;
10150
10160
  case "combo":
10151
10161
  case "waterfall":
10162
+ drawComboValues(chart, options);
10163
+ break;
10152
10164
  case "radar":
10153
- drawLineOrBarOrRadarChartValues(chart, options, ctx);
10165
+ drawRadarValues(chart, options);
10154
10166
  break;
10155
10167
  case "bar":
10156
- options.horizontal ? drawHorizontalBarChartValues(chart, options, ctx) : drawLineOrBarOrRadarChartValues(chart, options, ctx);
10168
+ options.horizontal ? drawHorizontalBarValues(chart, options) : drawVerticalBarValues(chart, options);
10157
10169
  break;
10158
10170
  case "pyramid":
10159
- drawHorizontalBarChartValues(chart, options, ctx);
10171
+ drawHorizontalBarValues(chart, options);
10160
10172
  break;
10161
10173
  case "calendar":
10162
- drawBarChartValues(chart, options, ctx);
10174
+ drawCalendarValues(chart, options);
10163
10175
  break;
10164
10176
  case "bubble":
10165
- drawBubbleChartValues(chart, options, ctx);
10177
+ drawBubbleValues(chart, options);
10166
10178
  break;
10167
10179
  case "funnel":
10168
- drawHorizontalBarChartValues(chart, options, ctx);
10180
+ drawFunnelValues(chart, options);
10169
10181
  break;
10170
10182
  }
10171
- ctx.restore();
10172
10183
  }
10173
10184
  };
10174
- function drawTextWithBackground(text, x, y, ctx) {
10175
- ctx.lineWidth = 3;
10176
- ctx.strokeText(text, x, y);
10177
- ctx.lineWidth = 1;
10178
- ctx.fillText(text, x, y);
10179
- }
10180
- function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10185
+ function drawValues(args) {
10186
+ const { chart, options, getNumberValue, direction } = args;
10187
+ const ctx = chart.ctx;
10188
+ ctx.save();
10189
+ ctx.textAlign = "center";
10190
+ ctx.textBaseline = "middle";
10191
+ ctx.miterLimit = 1;
10181
10192
  const textsPositions = {};
10182
10193
  for (const dataset of chart._metasets) {
10183
- if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden || isLineOverlayOnBarChart(options, dataset)) continue;
10184
- const yAxisScale = chart.scales[dataset.yAxisID];
10194
+ if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) continue;
10185
10195
  for (let i = 0; i < dataset._parsed.length; i++) {
10186
- const parsedValue = dataset._parsed[i];
10187
- const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
10188
- if (isNaN(value)) continue;
10189
- const point = dataset.data[i];
10190
- const xPosition = point.x;
10191
- let yPosition = 0;
10192
- if (chart.config.type === "line" || chart.config.type === "radar") yPosition = value < 0 ? point.y + 10 : point.y - 10;
10193
- else if (chart.config.type === "bubble") yPosition = point.y;
10194
- else {
10195
- const yZeroLine = yAxisScale.getPixelForValue(0);
10196
- const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
10197
- const textHeight = globalThis.Chart?.defaults.font.size ?? 12;
10198
- if (distanceFromAxisOrigin < textHeight) yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
10199
- else yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10200
- }
10201
- if (!textsPositions[xPosition]) textsPositions[xPosition] = [];
10202
- for (const otherPosition of textsPositions[xPosition] || []) if (Math.abs(otherPosition - yPosition) < MINIMAL_VERTICAL_DISTANCE) yPosition = otherPosition + MINIMAL_VERTICAL_DISTANCE * (value < 0 ? 1 : -1);
10203
- textsPositions[xPosition].push(yPosition);
10204
- ctx.fillStyle = point.options.backgroundColor;
10205
- ctx.strokeStyle = options.background(Number(value), dataset, i) || "#ffffff";
10206
- drawTextWithBackground(options.callback(Number(value), dataset, i), xPosition, yPosition, ctx);
10196
+ const numberValue = getNumberValue(dataset, i);
10197
+ if (numberValue === void 0 || isNaN(numberValue)) continue;
10198
+ const chartElement = dataset.data[i];
10199
+ const elementColor = chartElement.options.backgroundColor;
10200
+ if (!elementColor || colorToRGBA(elementColor).a !== 1) continue;
10201
+ const valueToDisplay = options.callback(numberValue, dataset, i);
10202
+ const textSize = getTextDimensions(valueToDisplay, ctx);
10203
+ const callbackArgs = {
10204
+ dataset,
10205
+ chartElement,
10206
+ numberValue,
10207
+ valueIndex: i,
10208
+ textSize,
10209
+ options,
10210
+ chart
10211
+ };
10212
+ const position = args.getValuePosition(callbackArgs);
10213
+ if (args.shouldSkipValue?.(callbackArgs)) continue;
10214
+ if (direction === "vertical") {
10215
+ const key = Math.round(position.x);
10216
+ if (!textsPositions[key]) textsPositions[key] = [];
10217
+ for (const otherPosition of textsPositions[key] || []) if (Math.abs(otherPosition - position.y) < MINIMAL_VERTICAL_DISTANCE) position.y = otherPosition + MINIMAL_VERTICAL_DISTANCE * (numberValue < 0 ? 1 : -1);
10218
+ textsPositions[key].push(position.y);
10219
+ } else {
10220
+ const key = Math.round(position.y);
10221
+ if (!textsPositions[key]) textsPositions[key] = [];
10222
+ for (const otherPosition of textsPositions[key]) if (Math.abs(otherPosition - position.x) < textSize.width) position.x = numberValue < 0 ? otherPosition - textSize.width - HORIZONTAL_PADDING : otherPosition + textSize.width + HORIZONTAL_PADDING;
10223
+ textsPositions[key].push(position.x);
10224
+ }
10225
+ const { strokeColor, textColor } = args.getTextColors(callbackArgs);
10226
+ if (!!strokeColor) {
10227
+ ctx.strokeStyle = strokeColor;
10228
+ ctx.lineWidth = 3;
10229
+ ctx.strokeText(valueToDisplay, position.x, position.y);
10230
+ }
10231
+ ctx.fillStyle = textColor;
10232
+ ctx.lineWidth = 1;
10233
+ ctx.fillText(valueToDisplay, position.x, position.y);
10207
10234
  }
10208
10235
  }
10236
+ ctx.restore();
10209
10237
  }
10210
- function drawBarChartValues(chart, options, ctx) {
10211
- const yMax = chart.chartArea.bottom;
10212
- const yMin = chart.chartArea.top;
10213
- for (const dataset of chart._metasets) {
10214
- if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) continue;
10215
- const yAxisScale = chart.scales[dataset.yAxisID];
10216
- for (let i = 0; i < dataset._parsed.length; i++) {
10217
- const parsedValue = dataset._parsed[i];
10218
- const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
10219
- if (isNaN(value)) continue;
10220
- const point = dataset.data[i];
10221
- const xPosition = point.x;
10222
- let yPosition = 0;
10223
- const yZeroLine = yAxisScale.getPixelForValue(0);
10224
- const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
10225
- const textHeight = globalThis.Chart?.defaults.font.size ?? 12;
10226
- if (distanceFromAxisOrigin < textHeight) yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
10227
- else yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10228
- yPosition = Math.min(yPosition, yMax);
10229
- yPosition = Math.max(yPosition, yMin);
10230
- ctx.strokeStyle = point.options.backgroundColor;
10231
- ctx.fillStyle = options.background(Number(value), dataset, i) || "#ffffff";
10232
- const valueToDisplay = options.callback(Number(value), dataset, i);
10233
- const measures = ctx.measureText(valueToDisplay);
10234
- if (measures.actualBoundingBoxAscent + measures.actualBoundingBoxDescent + 2 > Math.abs(point.height) - 2) continue;
10235
- drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
10236
- }
10237
- }
10238
- }
10239
- function drawBubbleChartValues(chart, options, ctx) {
10240
- const yMax = chart.chartArea.bottom;
10241
- const yMin = chart.chartArea.top;
10242
- const textsPositions = {};
10243
- for (const dataset of chart._metasets) for (let i = 0; i < dataset._parsed.length; i++) {
10244
- const value = dataset._parsed[i].y;
10245
- if (isNaN(value)) continue;
10246
- const point = dataset.data[i];
10247
- const xPosition = point.x;
10248
- let yPosition = Math.max(Math.min(point.y, yMax), yMin);
10249
- if (!textsPositions[xPosition]) textsPositions[xPosition] = [];
10250
- for (const otherPosition of textsPositions[xPosition] || []) if (Math.abs(otherPosition - yPosition) < MINIMAL_VERTICAL_DISTANCE) yPosition = otherPosition + MINIMAL_VERTICAL_DISTANCE * (value < 0 ? 1 : -1);
10251
- textsPositions[xPosition].push(yPosition);
10252
- const color = point.options.backgroundColor ?? "#ffffff";
10253
- if (hexToHSLA(toHex(color)).a === 1) ctx.fillStyle = chartFontColor(color);
10254
- else ctx.fillStyle = "#000000";
10255
- const valueToDisplay = options.callback(Number(value), dataset, i);
10256
- ctx.fillText(valueToDisplay, xPosition, yPosition);
10257
- }
10258
- }
10259
- function drawHorizontalBarChartValues(chart, options, ctx) {
10260
- const textsPositions = {};
10261
- for (const dataset of chart._metasets) {
10262
- if (isTrendLineAxis(dataset.xAxisID) || isLineOverlayOnBarChart(options, dataset)) continue;
10263
- const xZeroLine = chart.scales[dataset.xAxisID].getPixelForValue(0);
10264
- for (let i = 0; i < dataset._parsed.length; i++) {
10265
- const value = Number(dataset._parsed[i].x);
10266
- if (isNaN(value)) continue;
10267
- const displayValue = options.callback(value, dataset, i);
10268
- const point = dataset.data[i];
10269
- const yPosition = point.y;
10270
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: globalThis.Chart?.defaults.font.size ?? 12 }, "px");
10271
- const distanceFromAxisOrigin = Math.abs(point.x - xZeroLine);
10272
- const PADDING = 3;
10238
+ function drawVerticalBarValues(chart, options) {
10239
+ drawValues({
10240
+ chart,
10241
+ options,
10242
+ direction: "vertical",
10243
+ getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
10244
+ getValuePosition: getVerticalBarValuePosition,
10245
+ shouldSkipValue: ({ dataset }) => isLineOverlayOnBarChart(options, dataset),
10246
+ getTextColors: chartBackgroundColoredTextWithElementColoredHalo
10247
+ });
10248
+ }
10249
+ function drawRadarValues(chart, options) {
10250
+ drawValues({
10251
+ chart,
10252
+ options,
10253
+ direction: "vertical",
10254
+ getNumberValue: (dataset, i) => Number(dataset._parsed[i].r),
10255
+ getValuePosition: ({ chartElement }) => ({
10256
+ x: chartElement.x,
10257
+ y: chartElement.y - 10
10258
+ }),
10259
+ shouldSkipValue: () => false,
10260
+ getTextColors: chartElementColoredTextWithChartBackgroundHalo
10261
+ });
10262
+ }
10263
+ function drawLineValues(chart, options) {
10264
+ drawValues({
10265
+ chart,
10266
+ options,
10267
+ direction: "vertical",
10268
+ getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
10269
+ getValuePosition: ({ chartElement }) => ({
10270
+ x: chartElement.x,
10271
+ y: chartElement.y - 10
10272
+ }),
10273
+ shouldSkipValue: () => false,
10274
+ getTextColors: chartElementColoredTextWithChartBackgroundHalo
10275
+ });
10276
+ }
10277
+ function drawComboValues(chart, options) {
10278
+ drawValues({
10279
+ chart,
10280
+ options,
10281
+ direction: "vertical",
10282
+ getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
10283
+ getValuePosition: (args) => args.dataset.type === "line" ? {
10284
+ x: args.chartElement.x,
10285
+ y: args.chartElement.y - 10
10286
+ } : getVerticalBarValuePosition(args),
10287
+ shouldSkipValue: () => false,
10288
+ getTextColors: (args) => args.dataset.type === "line" ? chartElementColoredTextWithChartBackgroundHalo(args) : chartBackgroundColoredTextWithElementColoredHalo(args)
10289
+ });
10290
+ }
10291
+ function drawCalendarValues(chart, options) {
10292
+ drawValues({
10293
+ chart,
10294
+ options,
10295
+ direction: "vertical",
10296
+ getNumberValue: (dataset, i) => dataset._parsed[i].y,
10297
+ getValuePosition: ({ chartElement }) => ({
10298
+ x: chartElement.x,
10299
+ y: chartElement.y + chartElement.height / 2
10300
+ }),
10301
+ shouldSkipValue: ({ chartElement, textSize }) => textSize.height + 2 > Math.abs(chartElement.height) - 2,
10302
+ getTextColors: ({ chartElement }) => ({
10303
+ strokeColor: void 0,
10304
+ textColor: chartFontColor(chartElement.options.backgroundColor)
10305
+ })
10306
+ });
10307
+ }
10308
+ function drawBubbleValues(chart, options) {
10309
+ const canDrawTextInsideBubble = (chartElement, textSize) => {
10310
+ const radius = chartElement.options.radius ?? globalThis.Chart?.defaults.elements.point.radius ?? 3;
10311
+ return textSize.height < radius * 2;
10312
+ };
10313
+ drawValues({
10314
+ chart,
10315
+ options,
10316
+ direction: "vertical",
10317
+ getNumberValue: (dataset, i) => dataset._parsed[i].y,
10318
+ getValuePosition: ({ chartElement, textSize }) => ({
10319
+ x: chartElement.x,
10320
+ y: canDrawTextInsideBubble(chartElement, textSize) ? chartElement.y : chartElement.y - 10
10321
+ }),
10322
+ shouldSkipValue: () => false,
10323
+ getTextColors: (args) => {
10324
+ return canDrawTextInsideBubble(args.chartElement, args.textSize) ? chartBackgroundColoredTextWithElementColoredHalo(args) : chartElementColoredTextWithChartBackgroundHalo(args);
10325
+ }
10326
+ });
10327
+ }
10328
+ function drawHorizontalBarValues(chart, options) {
10329
+ drawValues({
10330
+ chart,
10331
+ options,
10332
+ direction: "horizontal",
10333
+ getNumberValue: (dataset, i) => dataset._parsed[i].x,
10334
+ getValuePosition: ({ chartElement, numberValue, textSize, dataset }) => {
10335
+ const xZeroLine = chart.scales[dataset.xAxisID].getPixelForValue(0);
10336
+ const distanceFromAxisOrigin = Math.abs(chartElement.x - xZeroLine);
10273
10337
  let xPosition;
10274
- if (distanceFromAxisOrigin < textWidth) xPosition = value < 0 ? xZeroLine - textWidth / 2 - PADDING : xZeroLine + textWidth / 2 + PADDING;
10275
- else xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10276
- if (!textsPositions[yPosition]) textsPositions[yPosition] = [];
10277
- for (const otherPosition of textsPositions[yPosition]) if (Math.abs(otherPosition - xPosition) < textWidth) xPosition = value < 0 ? otherPosition - textWidth - PADDING : otherPosition + textWidth + PADDING;
10278
- textsPositions[yPosition].push(xPosition);
10279
- ctx.strokeStyle = point.options.backgroundColor;
10280
- ctx.fillStyle = options.background(Number(value), dataset, i) || "#ffffff";
10281
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
10282
- }
10283
- }
10284
- }
10285
- function drawPieChartValues(chart, options, ctx) {
10286
- for (const dataset of chart._metasets) for (let i = 0; i < dataset._parsed.length; i++) {
10287
- const value = Number(dataset._parsed[i]);
10288
- if (isNaN(value) || value === 0) continue;
10289
- const bar = dataset.data[i];
10290
- const { startAngle, endAngle, innerRadius, outerRadius } = bar;
10291
- const midAngle = (startAngle + endAngle) / 2;
10292
- const midRadius = (innerRadius + outerRadius) / 2;
10293
- const x = bar.x + midRadius * Math.cos(midAngle);
10294
- const y = bar.y + midRadius * Math.sin(midAngle);
10295
- const displayValue = options.callback(value, dataset, i);
10296
- const textHeight = globalThis.Chart?.defaults.font.size ?? 12;
10297
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10298
- const radius = outerRadius - innerRadius;
10299
- if (textWidth >= radius || radius < textHeight) continue;
10300
- const sliceAngle = endAngle - startAngle;
10301
- const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10302
- if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) continue;
10303
- const background = options.background(Number(value), dataset, i);
10304
- ctx.fillStyle = chartFontColor(background);
10305
- ctx.strokeStyle = background || "#ffffff";
10306
- drawTextWithBackground(displayValue, x, y, ctx);
10307
- }
10338
+ const sign = numberValue < 0 ? -1 : 1;
10339
+ if (distanceFromAxisOrigin < textSize.width) xPosition = xZeroLine + sign * (textSize.width / 2 + HORIZONTAL_PADDING);
10340
+ else xPosition = chartElement.x - sign * (chartElement.width / 2);
10341
+ return {
10342
+ x: xPosition,
10343
+ y: chartElement.y
10344
+ };
10345
+ },
10346
+ shouldSkipValue: ({ dataset }) => isLineOverlayOnBarChart(options, dataset),
10347
+ getTextColors: chartBackgroundColoredTextWithElementColoredHalo
10348
+ });
10349
+ }
10350
+ function drawFunnelValues(chart, options) {
10351
+ drawValues({
10352
+ chart,
10353
+ options,
10354
+ direction: "horizontal",
10355
+ getNumberValue: (dataset, i) => dataset._parsed[i].x,
10356
+ getValuePosition: ({ chartElement }) => ({
10357
+ x: chartElement.x - chartElement.width / 2,
10358
+ y: chartElement.y
10359
+ }),
10360
+ shouldSkipValue: () => false,
10361
+ getTextColors: chartBackgroundColoredTextWithElementColoredHalo
10362
+ });
10363
+ }
10364
+ function drawPieValues(chart, options) {
10365
+ const getSliceDimensions = (chartElement) => {
10366
+ const { startAngle, endAngle, innerRadius, outerRadius } = chartElement;
10367
+ return {
10368
+ midAngle: (startAngle + endAngle) / 2,
10369
+ midRadius: (innerRadius + outerRadius) / 2
10370
+ };
10371
+ };
10372
+ drawValues({
10373
+ chart,
10374
+ options,
10375
+ direction: "vertical",
10376
+ getNumberValue: (dataset, i) => Number(dataset._parsed[i]),
10377
+ getValuePosition: ({ chartElement }) => {
10378
+ const { midAngle, midRadius } = getSliceDimensions(chartElement);
10379
+ return {
10380
+ x: chartElement.x + midRadius * Math.cos(midAngle),
10381
+ y: chartElement.y + midRadius * Math.sin(midAngle)
10382
+ };
10383
+ },
10384
+ shouldSkipValue: ({ chartElement, textSize }) => {
10385
+ const { midRadius } = getSliceDimensions(chartElement);
10386
+ const sliceAngle = chartElement.endAngle - chartElement.startAngle;
10387
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10388
+ return sliceAngle < Math.PI / 2 && (textSize.width >= midWidth || midWidth < textSize.height);
10389
+ },
10390
+ getTextColors: chartBackgroundColoredTextWithElementColoredHalo
10391
+ });
10392
+ }
10393
+ function getTextDimensions(text, ctx) {
10394
+ return computeCachedTextDimension(ctx, text, computeTextFont({ fontSize: globalThis.Chart?.defaults.font.size ?? 12 }, "px"));
10395
+ }
10396
+ function chartBackgroundColoredTextWithElementColoredHalo(args) {
10397
+ const { dataset, numberValue, valueIndex, chartElement, options } = args;
10398
+ return {
10399
+ strokeColor: chartElement.options.backgroundColor,
10400
+ textColor: options.background(numberValue, dataset, valueIndex) || "#ffffff"
10401
+ };
10402
+ }
10403
+ function chartElementColoredTextWithChartBackgroundHalo(args) {
10404
+ const { dataset, numberValue, valueIndex, chartElement, options } = args;
10405
+ return {
10406
+ strokeColor: options.background(numberValue, dataset, valueIndex) || "#ffffff",
10407
+ textColor: chartElement.options.backgroundColor
10408
+ };
10409
+ }
10410
+ function getVerticalBarValuePosition(args) {
10411
+ const { chartElement, numberValue, dataset, textSize, chart } = args;
10412
+ const yZeroLine = chart.scales[dataset.yAxisID].getPixelForValue(0);
10413
+ const distanceFromAxisOrigin = Math.abs(yZeroLine - chartElement.y);
10414
+ const sign = numberValue < 0 ? -1 : 1;
10415
+ let yPosition = 0;
10416
+ if (distanceFromAxisOrigin < textSize.height) yPosition = yZeroLine - sign * (textSize.height / 2);
10417
+ else yPosition = chartElement.y + sign * (chartElement.height / 2);
10418
+ return {
10419
+ x: chartElement.x,
10420
+ y: yPosition
10421
+ };
10308
10422
  }
10309
10423
 
10310
10424
  //#endregion
@@ -11161,6 +11275,7 @@ function getRadarChartDatasets(definition, args) {
11161
11275
  hidden,
11162
11276
  borderColor,
11163
11277
  backgroundColor: borderColor,
11278
+ pointBackgroundColor: borderColor,
11164
11279
  pointRadius: definition.hideDataMarkers ? 0 : 3
11165
11280
  };
11166
11281
  if (fill) {
@@ -12073,11 +12188,11 @@ function drawScoreChart(structure, canvas, zoom = 1) {
12073
12188
  if (structure.baseline) {
12074
12189
  ctx.font = structure.baseline.style.font;
12075
12190
  ctx.fillStyle = structure.baseline.style.color;
12076
- drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
12191
+ drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough, void 0, structure.baseline.style.highlightText);
12077
12192
  }
12078
12193
  if (structure.baselineArrow && structure.baselineArrow.style.size > 0 && Path2DConstructor) {
12079
12194
  ctx.save();
12080
- ctx.fillStyle = structure.baselineArrow.style.color;
12195
+ ctx.fillStyle = structure.baselineArrow.style.highlight ? lightenColor(structure.baselineArrow.style.color, DEFAULT_TEXT_HIGHLIGHT_PERCENT) : structure.baselineArrow.style.color;
12081
12196
  ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
12082
12197
  const ratio = structure.baselineArrow.style.size / 10;
12083
12198
  ctx.scale(ratio, ratio);
@@ -12095,18 +12210,18 @@ function drawScoreChart(structure, canvas, zoom = 1) {
12095
12210
  const descr = structure.baselineDescr;
12096
12211
  ctx.font = descr.style.font;
12097
12212
  ctx.fillStyle = descr.style.color;
12098
- ctx.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
12213
+ drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.baseline?.style.highlightText);
12099
12214
  }
12100
12215
  if (structure.key) {
12101
12216
  ctx.font = structure.key.style.font;
12102
12217
  ctx.fillStyle = structure.key.style.color;
12103
- drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
12218
+ drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough, void 0, structure.key.style.highlightText);
12104
12219
  }
12105
12220
  if (structure.keyDescr) {
12106
12221
  const descr = structure.keyDescr;
12107
12222
  ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
12108
12223
  ctx.fillStyle = descr.style.color;
12109
- ctx.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
12224
+ drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.key?.style.highlightText);
12110
12225
  }
12111
12226
  if (structure.progressBar) {
12112
12227
  ctx.fillStyle = structure.progressBar.style.backgroundColor;
@@ -12330,29 +12445,34 @@ var ScorecardChartConfigBuilder = class {
12330
12445
  color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
12331
12446
  font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
12332
12447
  strikethrough: this.runtime.keyValueStyle?.strikethrough,
12333
- underline: this.runtime.keyValueStyle?.underline
12448
+ underline: this.runtime.keyValueStyle?.underline,
12449
+ highlightText: this.runtime.keyHighlight
12334
12450
  },
12335
12451
  keyDescr: {
12336
12452
  color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
12337
12453
  font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
12338
12454
  strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
12339
- underline: this.runtime.keyValueDescrStyle?.underline
12455
+ underline: this.runtime.keyValueDescrStyle?.underline,
12456
+ highlightText: this.runtime.keyHighlight
12340
12457
  },
12341
12458
  baselineValue: {
12342
12459
  font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
12343
12460
  strikethrough: this.runtime.baselineStyle?.strikethrough,
12344
12461
  underline: this.runtime.baselineStyle?.underline,
12345
- color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
12462
+ color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
12463
+ highlightText: this.runtime.baselineHighlight
12346
12464
  },
12347
12465
  baselineDescr: {
12348
12466
  font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
12349
12467
  strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
12350
12468
  underline: this.runtime.baselineDescrStyle?.underline,
12351
- color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor
12469
+ color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
12470
+ highlightText: this.runtime.baselineHighlight
12352
12471
  },
12353
12472
  baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar ? void 0 : {
12354
12473
  size: this.keyValue ? .8 * baselineValueFontSize : 0,
12355
- color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
12474
+ color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
12475
+ highlight: this.runtime.baselineHighlight
12356
12476
  }
12357
12477
  };
12358
12478
  }
@@ -12448,11 +12568,14 @@ var ScorecardChart = class extends Component {
12448
12568
  });
12449
12569
  (0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
12450
12570
  }
12571
+ config(canvasRect, zoom) {
12572
+ return getScorecardConfiguration(getZoomedRect(1 / zoom, canvasRect), this.runtime);
12573
+ }
12451
12574
  createChart() {
12452
12575
  const canvas = this.canvas();
12453
12576
  if (!canvas) return;
12454
12577
  const zoom = this.env.model.getters.getViewportZoomLevel();
12455
- drawScoreChart(getScorecardConfiguration(getZoomedRect(1 / zoom, canvas.getBoundingClientRect()), this.runtime), canvas, zoom);
12578
+ drawScoreChart(this.config(canvas.getBoundingClientRect(), zoom), canvas, zoom);
12456
12579
  }
12457
12580
  };
12458
12581
 
@@ -28056,7 +28179,7 @@ function filterInvalidCalendarDataPoints(labels, datasets) {
28056
28179
  */
28057
28180
  function filterInvalidHierarchicalPoints(values, hierarchy) {
28058
28181
  const numberOfDataPoints = Math.max(values.length, ...hierarchy.map((dataset) => dataset.data?.length || 0));
28059
- const isEmpty = (value) => value === null || value === "";
28182
+ const isEmpty = (value) => value === void 0 || value === null || value === "";
28060
28183
  const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
28061
28184
  const groups = hierarchy.map((dataset) => dataset.data?.[dataPointIndex]);
28062
28185
  if (isEmpty(groups[0]?.value)) return false;
@@ -28620,23 +28743,11 @@ function getChartShowValues(definition, args) {
28620
28743
  }
28621
28744
  function getCalendarChartShowValues(definition, args) {
28622
28745
  const { locale, axisFormats } = args;
28623
- let background = (_value, dataset, index) => definition.background;
28624
- const values = args.dataSetsValues.flat().flatMap((dsv) => dsv?.data.filter(isNumberResult)).map((cell) => cell.value);
28625
- if (values.length) {
28626
- const min = Math.min(...values);
28627
- const max = Math.max(...values);
28628
- const colorScale = getRuntimeColorScale(definition.colorScale ?? schemeToColorScale("oranges"), min, max);
28629
- background = (_value, dataset, index) => {
28630
- const value = dataset._dataset.values[index];
28631
- if (value === void 0) return definition.background;
28632
- return chartFontColor(colorScale(value));
28633
- };
28634
- }
28635
28746
  return {
28636
28747
  type: "calendar",
28637
28748
  horizontal: false,
28638
28749
  showValues: "showValues" in definition ? !!definition.showValues : false,
28639
- background,
28750
+ background: () => definition.background,
28640
28751
  callback: (_value, dataset, index) => {
28641
28752
  const value = dataset._dataset.values[index];
28642
28753
  return value === void 0 ? "" : humanizeNumber({
@@ -34537,6 +34648,35 @@ var CellIsRuleEditor = class extends Component {
34537
34648
  }
34538
34649
  };
34539
34650
 
34651
+ //#endregion
34652
+ //#region src/components/side_panel/criterion_form/calendar_button/calendar_button.ts
34653
+ var CalendarButton = class extends Component {
34654
+ static template = "o-spreadsheet-CalendarButton";
34655
+ props = (0, _odoo_owl.props)({
34656
+ value: types.string().optional(""),
34657
+ onChange: types.function()
34658
+ });
34659
+ datePickerRef = _odoo_owl.signal.ref(HTMLInputElement);
34660
+ openCalendar() {
34661
+ this.datePickerRef()?.showPicker();
34662
+ }
34663
+ formatDateForInput(value) {
34664
+ const dateValue = parseDateTime(value, DEFAULT_LOCALE);
34665
+ return dateValue ? formatValue(dateValue.value, {
34666
+ format: "yyyy-mm-dd",
34667
+ locale: DEFAULT_LOCALE
34668
+ }) : "";
34669
+ }
34670
+ onDateInputValueChanged(value) {
34671
+ const dateValue = parseDateTime(value, DEFAULT_LOCALE);
34672
+ const formattedValue = dateValue ? formatValue(dateValue.value, {
34673
+ format: DEFAULT_LOCALE.dateFormat,
34674
+ locale: DEFAULT_LOCALE
34675
+ }) : "";
34676
+ this.props.onChange(formattedValue);
34677
+ }
34678
+ };
34679
+
34540
34680
  //#endregion
34541
34681
  //#region src/components/side_panel/criterion_form/criterion_form.ts
34542
34682
  var CriterionForm = class extends Component {
@@ -35225,7 +35365,8 @@ var DateCriterionForm = class extends CriterionForm {
35225
35365
  static template = "o-spreadsheet-DataValidationDateCriterion";
35226
35366
  static components = {
35227
35367
  CriterionInput,
35228
- Select
35368
+ Select,
35369
+ CalendarButton
35229
35370
  };
35230
35371
  get currentDateValue() {
35231
35372
  return this.props.criterion.dateValue || "exactDate";
@@ -35251,7 +35392,10 @@ var DateCriterionForm = class extends CriterionForm {
35251
35392
  //#region src/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.ts
35252
35393
  var DoubleInputCriterionForm = class extends CriterionForm {
35253
35394
  static template = "o-spreadsheet-DoubleInputCriterionForm";
35254
- static components = { CriterionInput };
35395
+ static components = {
35396
+ CriterionInput,
35397
+ CalendarButton
35398
+ };
35255
35399
  onFirstValueChanged(value) {
35256
35400
  const values = this.props.criterion.values;
35257
35401
  this.updateCriterion({ values: [value, values[1] || ""] });
@@ -35260,6 +35404,9 @@ var DoubleInputCriterionForm = class extends CriterionForm {
35260
35404
  const values = this.props.criterion.values;
35261
35405
  this.updateCriterion({ values: [values[0] || "", value] });
35262
35406
  }
35407
+ get isDateType() {
35408
+ return ["dateIsBetween", "dateIsNotBetween"].includes(this.props.criterion.type);
35409
+ }
35263
35410
  };
35264
35411
 
35265
35412
  //#endregion
@@ -46606,7 +46753,7 @@ var FiguresContainer = class extends Component {
46606
46753
  horizontalSnap: void 0,
46607
46754
  verticalSnap: void 0,
46608
46755
  cancelDnd: void 0,
46609
- overlappingCarousel: void 0
46756
+ overlappingChartOrCarousel: void 0
46610
46757
  });
46611
46758
  setup() {
46612
46759
  (0, _odoo_owl.onMounted)(() => {
@@ -46622,7 +46769,7 @@ var FiguresContainer = class extends Component {
46622
46769
  this.dnd.selectedRect = void 0;
46623
46770
  this.dnd.horizontalSnap = void 0;
46624
46771
  this.dnd.verticalSnap = void 0;
46625
- this.dnd.overlappingCarousel = void 0;
46772
+ this.dnd.overlappingChartOrCarousel = void 0;
46626
46773
  this.dnd.cancelDnd = void 0;
46627
46774
  }
46628
46775
  });
@@ -46760,11 +46907,11 @@ var FiguresContainer = class extends Component {
46760
46907
  hasStartedDnd = true;
46761
46908
  const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
46762
46909
  const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
46763
- let overlappingCarousel = void 0;
46910
+ let overlappingChartOrCarousel = void 0;
46764
46911
  const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
46765
- if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingCarousel = this.getCarouselOverlappingChart(draggedFigure, otherFigures);
46766
- this.dnd.overlappingCarousel = overlappingCarousel;
46767
- if (!overlappingCarousel) {
46912
+ if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
46913
+ this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
46914
+ if (!overlappingChartOrCarousel) {
46768
46915
  const snapReturn = snapForMove(getters, selectedFigures, otherFigures);
46769
46916
  this.dnd.selectedFigures = snapReturn.snappedFigures;
46770
46917
  this.dnd.selectedRect = this.getDndFigureRect();
@@ -46785,7 +46932,7 @@ var FiguresContainer = class extends Component {
46785
46932
  else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
46786
46933
  return;
46787
46934
  }
46788
- if (!this.dnd.overlappingCarousel) {
46935
+ if (!this.dnd.overlappingChartOrCarousel) {
46789
46936
  const payloads = this.dnd.selectedFigures?.map((f) => {
46790
46937
  return {
46791
46938
  sheetId,
@@ -46795,20 +46942,25 @@ var FiguresContainer = class extends Component {
46795
46942
  }) || [];
46796
46943
  this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
46797
46944
  } else {
46798
- const carouselFigureId = this.dnd.overlappingCarousel.id;
46945
+ const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
46799
46946
  const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
46800
- this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
46947
+ if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
46801
46948
  sheetId,
46802
- carouselFigureId,
46949
+ carouselFigureId: overlappingFigureId,
46803
46950
  chartFigureIds
46804
46951
  });
46952
+ else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
46953
+ sheetId,
46954
+ baseFigureId: overlappingFigureId,
46955
+ chartFigureIds: [overlappingFigureId, ...chartFigureIds]
46956
+ });
46805
46957
  }
46806
46958
  this.dnd.draggedFigure = void 0;
46807
46959
  this.dnd.selectedFigures = void 0;
46808
46960
  this.dnd.selectedRect = void 0;
46809
46961
  this.dnd.horizontalSnap = void 0;
46810
46962
  this.dnd.verticalSnap = void 0;
46811
- this.dnd.overlappingCarousel = void 0;
46963
+ this.dnd.overlappingChartOrCarousel = void 0;
46812
46964
  };
46813
46965
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
46814
46966
  }
@@ -46864,7 +47016,7 @@ var FiguresContainer = class extends Component {
46864
47016
  this.dnd.selectedRect = void 0;
46865
47017
  this.dnd.horizontalSnap = void 0;
46866
47018
  this.dnd.verticalSnap = void 0;
46867
- this.dnd.overlappingCarousel = void 0;
47019
+ this.dnd.overlappingChartOrCarousel = void 0;
46868
47020
  };
46869
47021
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
46870
47022
  }
@@ -46874,12 +47026,12 @@ var FiguresContainer = class extends Component {
46874
47026
  getFigureStyle(figureUI) {
46875
47027
  if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
46876
47028
  return cssPropertiesToCss({
46877
- opacity: this.dnd.overlappingCarousel?.id ? "0.6" : "0.9",
47029
+ opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
46878
47030
  cursor: "grabbing"
46879
47031
  });
46880
47032
  }
46881
47033
  getFigureClass(figureUI) {
46882
- if (figureUI.id !== this.dnd.overlappingCarousel?.id) return "";
47034
+ if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
46883
47035
  return "o-add-to-carousel";
46884
47036
  }
46885
47037
  getSnap(snapLine) {
@@ -46922,18 +47074,18 @@ var FiguresContainer = class extends Component {
46922
47074
  height: `100%`
46923
47075
  });
46924
47076
  }
46925
- getCarouselOverlappingChart(figureUI, otherFigures) {
47077
+ getOverlappingFigure(figureUI, otherFigures) {
46926
47078
  if (figureUI.tag !== "chart") return;
46927
47079
  const figureCenterX = figureUI.x + figureUI.width / 2;
46928
47080
  const figureCenterY = figureUI.y + figureUI.height / 2;
46929
47081
  let bestMatch;
46930
47082
  let smallestDistance = Infinity;
46931
47083
  for (const figure of otherFigures) {
46932
- if (figure.tag !== "carousel") continue;
46933
- const carouselCenterX = figure.x + figure.width / 2;
46934
- const carouselCenterY = figure.y + figure.height / 2;
46935
- const distanceX = Math.abs(figureCenterX - carouselCenterX);
46936
- const distanceY = Math.abs(figureCenterY - carouselCenterY);
47084
+ if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
47085
+ const targetCenterX = figure.x + figure.width / 2;
47086
+ const targetCenterY = figure.y + figure.height / 2;
47087
+ const distanceX = Math.abs(figureCenterX - targetCenterX);
47088
+ const distanceY = Math.abs(figureCenterY - targetCenterY);
46937
47089
  const squaredDistance = distanceX ** 2 + distanceY ** 2;
46938
47090
  if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
46939
47091
  smallestDistance = squaredDistance;
@@ -53026,11 +53178,16 @@ var DefaultPlugin = class extends CorePlugin {
53026
53178
  const deltaStyle = {};
53027
53179
  let hasDelta = false;
53028
53180
  const styleSheet = this.style[position.sheetId];
53029
- if (!styleSheet) continue;
53030
53181
  for (const key in newDefaultStyle) {
53031
53182
  if (key in cellStyle) continue;
53032
- const defaults = styleSheet[key];
53033
- if (!defaults) continue;
53183
+ const defaults = styleSheet?.[key];
53184
+ if (!defaults) {
53185
+ if (newDefaultStyle[key] !== DEFAULT_STYLE[key]) {
53186
+ deltaStyle[key] = DEFAULT_STYLE[key];
53187
+ hasDelta = true;
53188
+ }
53189
+ continue;
53190
+ }
53034
53191
  const rowDefault = defaults.rowDefault?.[position.row];
53035
53192
  if (rowDefault !== void 0) {
53036
53193
  if (priorities.shouldUseDefaultRow) {
@@ -53072,7 +53229,8 @@ var DefaultPlugin = class extends CorePlugin {
53072
53229
  for (const key in styleSheet) if (!(key in style)) {
53073
53230
  const defaults = styleSheet[key];
53074
53231
  if (!defaults) continue;
53075
- style[key] = defaults.rowDefault?.[position.row] ?? defaults.colDefault?.[position.col] ?? defaults.sheetDefault;
53232
+ const styleValue = defaults.rowDefault?.[position.row] ?? defaults.colDefault?.[position.col] ?? defaults.sheetDefault;
53233
+ if (styleValue !== void 0) style[key] = styleValue;
53076
53234
  }
53077
53235
  return style;
53078
53236
  }
@@ -58904,13 +59062,13 @@ var EvaluationConditionalFormatPlugin = class extends CoreViewPlugin {
58904
59062
  const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
58905
59063
  const midValue = rule.midpoint ? this.parsePoint(sheetId, range, rule.midpoint) : null;
58906
59064
  const maxValue = this.parsePoint(sheetId, range, rule.maximum, "max");
58907
- if (minValue === null || maxValue === null || minValue >= maxValue || midValue && (minValue >= midValue || midValue >= maxValue)) return;
59065
+ if (minValue === null || maxValue === null || minValue >= maxValue || midValue !== null && (minValue >= midValue || midValue >= maxValue)) return;
58908
59066
  const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
58909
59067
  const colorThresholds = [{
58910
59068
  value: minValue,
58911
59069
  color: rule.minimum.color
58912
59070
  }];
58913
- if (rule.midpoint && midValue) colorThresholds.push({
59071
+ if (rule.midpoint && midValue !== null) colorThresholds.push({
58914
59072
  value: midValue,
58915
59073
  color: rule.midpoint.color
58916
59074
  });
@@ -66369,7 +66527,9 @@ var GridSelectionPlugin = class extends UIPlugin {
66369
66527
  ctx.lineWidth = 1.5 * thinLineWidth;
66370
66528
  const isDarkMode = this.getters.isDarkMode();
66371
66529
  for (const zone of zones) {
66530
+ if (!viewports.isZoneVisibleInViewport(sheetId, zone)) continue;
66372
66531
  const { x, y, width, height } = viewports.getVisibleRect(sheetId, zone);
66532
+ const currentLineWidth = ctx.lineWidth;
66373
66533
  if (!isDarkMode) ctx.globalCompositeOperation = "multiply";
66374
66534
  if (height === 0 || width === 0) ctx.lineWidth = 3 * thinLineWidth;
66375
66535
  if (height === 0 && width === 0) {
@@ -66383,6 +66543,7 @@ var GridSelectionPlugin = class extends UIPlugin {
66383
66543
  ctx.globalCompositeOperation = "source-over";
66384
66544
  ctx.strokeRect(x, y, width, height);
66385
66545
  }
66546
+ ctx.lineWidth = currentLineWidth;
66386
66547
  }
66387
66548
  ctx.globalCompositeOperation = "source-over";
66388
66549
  const position = renderingContext.activePosition;
@@ -66529,6 +66690,9 @@ var InternalViewport = class {
66529
66690
  this.adjustViewportZoneX();
66530
66691
  this.adjustViewportZoneY();
66531
66692
  }
66693
+ isZoneVisible(zone) {
66694
+ return intersection(zone, this) !== void 0;
66695
+ }
66532
66696
  /**
66533
66697
  *
66534
66698
  * Computes the visible coordinates & dimensions of a given zone inside the viewport
@@ -66809,6 +66973,9 @@ var ViewportCollection = class {
66809
66973
  isVisibleInViewport({ sheetId, col, row }) {
66810
66974
  return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
66811
66975
  }
66976
+ isZoneVisibleInViewport(sheetId, zone) {
66977
+ return this.getSubViewports(sheetId).some((pane) => pane.isZoneVisible(zone));
66978
+ }
66812
66979
  getScrollBarWidth() {
66813
66980
  return 15 / this.zoomLevel;
66814
66981
  }
@@ -70882,18 +71049,28 @@ var NamedRangeSelector = class extends Component {
70882
71049
  return;
70883
71050
  }
70884
71051
  const activeSheetId = this.env.model.getters.getActiveSheetId();
70885
- if (activeSheetId !== sheetId) this.env.model.dispatch("ACTIVATE_SHEET", {
70886
- sheetIdFrom: activeSheetId,
70887
- sheetIdTo: sheetId
70888
- });
70889
- this.env.model.selection.selectCell(zone.right, zone.bottom);
71052
+ if (activeSheetId !== sheetId) {
71053
+ if (!this.env.model.getters.getSheet(sheetId).isVisible) {
71054
+ this.env.notifyUser({
71055
+ text: _t("The sheet on which the range is defined is hidden."),
71056
+ type: "info",
71057
+ sticky: false
71058
+ });
71059
+ return;
71060
+ }
71061
+ this.env.model.dispatch("ACTIVATE_SHEET", {
71062
+ sheetIdFrom: activeSheetId,
71063
+ sheetIdTo: sheetId
71064
+ });
71065
+ }
71066
+ this.env.model.selection.selectCell(zone.right, zone.bottom, { allowsHiddenSelection: true });
70890
71067
  this.env.model.selection.selectZone({
70891
71068
  cell: {
70892
71069
  col: zone.left,
70893
71070
  row: zone.top
70894
71071
  },
70895
71072
  zone
70896
- });
71073
+ }, { allowsHiddenSelection: true });
70897
71074
  }
70898
71075
  get selectionKey() {
70899
71076
  return `${this.env.model.getters.getActiveSheetId()}-${zoneToXc(this.selectedZone)}`;
@@ -77172,8 +77349,8 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
77172
77349
  for (const key in DEFAULT_STYLE) {
77173
77350
  content.style[key] = {
77174
77351
  sheetDefault: this.getters.getDefaultStyle(data.sheetId, key, "SHEET", void 0) ?? DEFAULT_STYLE[key],
77175
- colDefault: {},
77176
- rowDefault: {}
77352
+ colDefault: [],
77353
+ rowDefault: []
77177
77354
  };
77178
77355
  let colIndex = 0;
77179
77356
  for (const col of data.columnsIndexes) {
@@ -77190,20 +77367,41 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
77190
77367
  }
77191
77368
  return content;
77192
77369
  }
77193
- clearStyleFormat(sheetId, zone) {
77194
- this.dispatch("SET_FORMATTING", {
77195
- sheetId,
77196
- target: [zone],
77197
- format: ""
77198
- });
77370
+ adaptContentToZone(zone, content) {
77371
+ const colRepetition = Math.max(Math.floor((zone.right - zone.left + 1) / content.width), 1);
77372
+ const rowRepetition = Math.max(Math.floor((zone.bottom - zone.top + 1) / content.height), 1);
77373
+ if (colRepetition === 1 && rowRepetition === 1) return content;
77374
+ const newContent = deepCopy(content);
77375
+ newContent.height *= rowRepetition;
77376
+ newContent.width *= colRepetition;
77377
+ if (rowRepetition > 1 && newContent.format.rowDefault) {
77378
+ newContent.format.rowDefault.length = content.height;
77379
+ newContent.format.rowDefault = repeat(newContent.format.rowDefault, rowRepetition);
77380
+ }
77381
+ if (colRepetition > 1 && newContent.format.colDefault) {
77382
+ newContent.format.colDefault.length = content.width;
77383
+ newContent.format.colDefault = repeat(newContent.format.colDefault, colRepetition);
77384
+ }
77385
+ for (const key in content.style) {
77386
+ if (rowRepetition > 1 && newContent.style[key].rowDefault) {
77387
+ newContent.style[key].rowDefault.length = content.height;
77388
+ newContent.style[key].rowDefault = repeat(newContent.style[key].rowDefault, rowRepetition);
77389
+ }
77390
+ if (colRepetition > 1 && newContent.style[key].colDefault) {
77391
+ newContent.style[key].colDefault.length = content.width;
77392
+ newContent.style[key].colDefault = repeat(newContent.style[key].colDefault, colRepetition);
77393
+ }
77394
+ }
77395
+ return newContent;
77199
77396
  }
77200
77397
  paste(target, content, options) {
77201
77398
  const sheetId = target.sheetId;
77202
77399
  if (options.pasteOption === "asValue") return;
77203
77400
  const zones = target.zones;
77204
- if (!options.isCutOperation) for (const zone of zones) for (const pasteZone of splitZoneForPaste(zone, content.width, content.height)) {
77205
- this.pasteStyle(sheetId, pasteZone.left, pasteZone.top, content.width, content.height, content.style);
77206
- this.pasteFormat(sheetId, pasteZone.left, pasteZone.top, content.width, content.height, content.format);
77401
+ if (!options.isCutOperation) for (const zone of zones) {
77402
+ const newContent = this.adaptContentToZone(zone, content);
77403
+ this.pasteStyle(sheetId, zone.left, zone.top, newContent.width, newContent.height, newContent.style);
77404
+ this.pasteFormat(sheetId, zone.left, zone.top, newContent.width, newContent.height, newContent.format);
77207
77405
  }
77208
77406
  else {
77209
77407
  this.clearClippedZones(content);
@@ -77316,7 +77514,7 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
77316
77514
  const commands = [];
77317
77515
  for (const [zoneStr, [zone, priority]] of Object.entries(zones)) {
77318
77516
  const format = updateCells[zoneStr];
77319
- commands.push([
77517
+ if (format !== void 0) commands.push([
77320
77518
  priority,
77321
77519
  zone,
77322
77520
  format
@@ -87518,7 +87716,9 @@ const helpers = {
87518
87716
  collapseHierarchicalDisplayName,
87519
87717
  getCanonicalSymbolName,
87520
87718
  fuzzyLookup,
87521
- replaceSymbolInFormula
87719
+ replaceSymbolInFormula,
87720
+ isSingleCellReference,
87721
+ computeCachedTextDimension
87522
87722
  };
87523
87723
  const links = {
87524
87724
  isMarkdownLink,
@@ -87588,7 +87788,8 @@ const components = {
87588
87788
  FullScreenFigure,
87589
87789
  NumberInput,
87590
87790
  TopBar,
87591
- Composer
87791
+ Composer,
87792
+ CalendarButton
87592
87793
  };
87593
87794
  const hooks = {
87594
87795
  useDragAndDropListItems,
@@ -87742,6 +87943,6 @@ exports.stores = stores;
87742
87943
  exports.tokenColors = tokenColors;
87743
87944
  exports.tokenize = tokenize;
87744
87945
 
87745
- __info__.version = "19.5.0-alpha.1";
87746
- __info__.date = "2026-07-01T05:05:42.239Z";
87747
- __info__.hash = "3c78107";
87946
+ __info__.version = "19.5.0-alpha.3";
87947
+ __info__.date = "2026-07-14T10:17:06.949Z";
87948
+ __info__.hash = "c029184";