@odoo/o-spreadsheet 19.5.0-alpha.0 → 19.5.0-alpha.2

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.0
6
- * @date 2026-06-29T09:01:26.331Z
7
- * @hash 2437c0b
5
+ * @version 19.5.0-alpha.2
6
+ * @date 2026-07-13T06:26:20.354Z
7
+ * @hash a4d2f90
8
8
  */
9
9
 
10
10
  import * as owl from "@odoo/owl";
@@ -562,6 +562,7 @@ const DEFAULT_NUMBER_STYLE = {
562
562
  };
563
563
  const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
564
564
  const DEFAULT_WRAPPING_MODE = DEFAULT_STYLE.wrapping;
565
+ const DEFAULT_TEXT_HIGHLIGHT_PERCENT = .25;
565
566
  const DEFAULT_FONT_SIZE = DEFAULT_STYLE.fontSize;
566
567
  const DEFAULT_FONT = "'Roboto', arial, 'Liberation Sans'";
567
568
  const DEFAULT_BORDER_DESC = {
@@ -8405,534 +8406,6 @@ function addOrigin(cell, origin) {
8405
8406
  return cell;
8406
8407
  }
8407
8408
 
8408
- //#endregion
8409
- //#region src/helpers/text_helper.ts
8410
- function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
8411
- return numberOfLines * (textLineHeight + 4) - 4;
8412
- }
8413
- function getCanvas(width = 100, height = 100) {
8414
- return new OffscreenCanvas(width, height).getContext("2d");
8415
- }
8416
- /**
8417
- * Get the default height of the cell given its style.
8418
- */
8419
- function getDefaultCellHeight(ctx, cell, locale, colSize) {
8420
- if (!cell || !cell.isFormula && !cell.content) return 23;
8421
- let content = "";
8422
- try {
8423
- if (!cell.isFormula) {
8424
- const localeFormat = {
8425
- format: cell.format,
8426
- locale
8427
- };
8428
- content = formatValue(parseLiteral(cell.content, locale), localeFormat);
8429
- }
8430
- } catch {
8431
- content = CellErrorType.GenericError;
8432
- }
8433
- return getCellContentHeight(ctx, content, cell.style, colSize);
8434
- }
8435
- function getCellContentHeight(ctx, content, style, colSize) {
8436
- return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
8437
- }
8438
- function getDefaultContextFont(fontSize, bold = false, italic = false) {
8439
- return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
8440
- }
8441
- function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
8442
- if (!textLines.length) return {
8443
- width: 0,
8444
- height: 0
8445
- };
8446
- const font = computeTextFont(style, fontUnit);
8447
- const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
8448
- const height = computeTextLinesHeight(sizes[0].height, textLines.length);
8449
- const width = Math.max(...sizes.map((size) => size.width));
8450
- if (!style.rotation) return {
8451
- height,
8452
- width
8453
- };
8454
- const cos = Math.abs(Math.cos(style.rotation));
8455
- const sin = Math.abs(Math.sin(style.rotation));
8456
- return {
8457
- width: width * cos + height * sin,
8458
- height: sin * width + cos * height
8459
- };
8460
- }
8461
- function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
8462
- return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
8463
- }
8464
- function computeCachedTextWidth(context, text, font, rotation) {
8465
- const size = computeCachedTextDimension(context, text, font);
8466
- if (!rotation) return size.width;
8467
- const cos = Math.abs(Math.cos(rotation));
8468
- const sin = Math.abs(Math.sin(rotation));
8469
- return size.width * cos + size.height * sin;
8470
- }
8471
- const textDimensionsCache = {};
8472
- function computeTextDimension(context, text, style, fontUnit = "pt") {
8473
- const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
8474
- if (!style.rotation) return size;
8475
- const cos = Math.abs(Math.cos(style.rotation));
8476
- const sin = Math.abs(Math.sin(style.rotation));
8477
- return {
8478
- width: size.width * cos + size.height * sin,
8479
- height: size.height * cos + size.width * sin
8480
- };
8481
- }
8482
- function computeCachedTextDimension(context, text, font) {
8483
- if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
8484
- if (textDimensionsCache[font][text] === void 0) {
8485
- context.save();
8486
- context.font = font;
8487
- const measure = context.measureText(text);
8488
- context.restore();
8489
- const width = measure.width;
8490
- const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
8491
- textDimensionsCache[font][text] = {
8492
- width,
8493
- height
8494
- };
8495
- }
8496
- return textDimensionsCache[font][text];
8497
- }
8498
- function fontSizeInPixels(fontSize) {
8499
- return Math.round(fontSize * 96 / 72);
8500
- }
8501
- function computeTextFont(style, fontUnit = "pt") {
8502
- return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
8503
- }
8504
- function computeTextFontSizeInPixels(style) {
8505
- return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
8506
- }
8507
- function splitWordToSpecificWidth(ctx, word, width, style) {
8508
- if (computeTextWidth(ctx, word, style) <= width) return [word];
8509
- const splitWord = [];
8510
- let wordPart = "";
8511
- for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
8512
- splitWord.push(wordPart);
8513
- wordPart = l;
8514
- } else wordPart += l;
8515
- splitWord.push(wordPart);
8516
- return splitWord;
8517
- }
8518
- /**
8519
- * Return the given text, split in multiple lines if needed. The text will be split in multiple
8520
- * line if it contains NEWLINE characters, or if it's longer than the given width.
8521
- */
8522
- function splitTextToWidth(ctx, text, style, width) {
8523
- if (!style) style = {};
8524
- if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
8525
- const brokenText = [];
8526
- const lines = text.includes("\n") ? text.split("\n") : [text];
8527
- for (const line of lines) {
8528
- const words = line.includes(" ") ? line.split(" ") : [line];
8529
- if (!width) {
8530
- brokenText.push(line);
8531
- continue;
8532
- }
8533
- let textLine = "";
8534
- let availableWidth = width;
8535
- for (const word of words) {
8536
- const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
8537
- const lastPart = splitWord.pop();
8538
- const lastPartWidth = computeTextWidth(ctx, lastPart, style);
8539
- if (splitWord.length) {
8540
- if (textLine !== "") {
8541
- brokenText.push(textLine);
8542
- textLine = "";
8543
- availableWidth = width;
8544
- }
8545
- splitWord.forEach((wordPart) => {
8546
- brokenText.push(wordPart);
8547
- });
8548
- textLine = lastPart;
8549
- availableWidth = width - lastPartWidth;
8550
- } else {
8551
- const _word = textLine === "" ? lastPart : " " + lastPart;
8552
- const wordWidth = computeTextWidth(ctx, _word, style);
8553
- if (wordWidth <= availableWidth) {
8554
- textLine += _word;
8555
- availableWidth -= wordWidth;
8556
- } else {
8557
- brokenText.push(textLine);
8558
- textLine = lastPart;
8559
- availableWidth = width - lastPartWidth;
8560
- }
8561
- }
8562
- }
8563
- if (textLine !== "") brokenText.push(textLine);
8564
- }
8565
- return brokenText;
8566
- }
8567
- /**
8568
- * Return the font size that makes the width of a text match the given line width.
8569
- * Minimum font size is 1.
8570
- *
8571
- * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
8572
- */
8573
- function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
8574
- let minFontSize = 1;
8575
- if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
8576
- if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
8577
- let fontSize = (minFontSize + maxFontSize) / 2;
8578
- let currentTextWidth = getTextWidth(fontSize);
8579
- let iterations = 0;
8580
- while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
8581
- if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
8582
- else minFontSize = (minFontSize + maxFontSize) / 2;
8583
- fontSize = (minFontSize + maxFontSize) / 2;
8584
- currentTextWidth = getTextWidth(fontSize);
8585
- iterations++;
8586
- }
8587
- return fontSize;
8588
- }
8589
- /** Transform a string to lowercase and removes whitespace from both ends of the string*/
8590
- function toTrimmedLowerCase(str) {
8591
- return str ? str.toLowerCase().trim() : "";
8592
- }
8593
- /**
8594
- * Extract the fontSize from a context font string
8595
- * @param font The (context) font string to parse
8596
- * @returns The fontSize in pixels
8597
- */
8598
- const pxRegex = /([0-9\.]*)px/;
8599
- function getContextFontSize(font) {
8600
- return Number(font.match(pxRegex)?.[1]);
8601
- }
8602
- function clipTextWithEllipsis(ctx, text, maxWidth) {
8603
- let width = computeCachedTextWidth(ctx, text, ctx.font);
8604
- if (width <= maxWidth) return text;
8605
- const ellipsis = "…";
8606
- const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
8607
- if (width <= ellipsisWidth) return text;
8608
- let len = text.length;
8609
- while (width >= maxWidth - ellipsisWidth && len-- > 0) {
8610
- text = text.substring(0, len);
8611
- width = computeCachedTextWidth(ctx, text, ctx.font);
8612
- }
8613
- return text + ellipsis;
8614
- }
8615
- function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10) {
8616
- context.fillText(text, position.x, position.y);
8617
- if (!underline && !strikethrough) return;
8618
- const measure = context.measureText(text);
8619
- const textWidth = measure.width;
8620
- const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
8621
- const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
8622
- let { x, y } = position;
8623
- let strikeY = y, underlineY = y;
8624
- switch (context.textAlign) {
8625
- case "center":
8626
- x -= textWidth / 2;
8627
- break;
8628
- case "right":
8629
- x -= textWidth;
8630
- break;
8631
- }
8632
- switch (context.textBaseline) {
8633
- case "top":
8634
- underlineY += boxHeight - 2 * strokeWidth;
8635
- strikeY += boxHeight / 2 - strokeWidth;
8636
- break;
8637
- case "middle":
8638
- underlineY += boxHeight / 2 - strokeWidth;
8639
- break;
8640
- case "alphabetic":
8641
- underlineY += 2 * strokeWidth;
8642
- strikeY -= 3 * strokeWidth;
8643
- break;
8644
- case "bottom":
8645
- underlineY = y;
8646
- strikeY -= textHeight / 2 - strokeWidth / 2;
8647
- break;
8648
- }
8649
- if (underline) {
8650
- context.lineWidth = strokeWidth;
8651
- context.strokeStyle = context.fillStyle;
8652
- context.beginPath();
8653
- context.moveTo(x, underlineY);
8654
- context.lineTo(x + textWidth, underlineY);
8655
- context.stroke();
8656
- }
8657
- if (strikethrough) {
8658
- context.lineWidth = strokeWidth;
8659
- context.strokeStyle = context.fillStyle;
8660
- context.beginPath();
8661
- context.moveTo(x, strikeY);
8662
- context.lineTo(x + textWidth, strikeY);
8663
- context.stroke();
8664
- }
8665
- }
8666
- function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
8667
- if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
8668
- const ellipsis = "...";
8669
- const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
8670
- if (ellipsisWidth >= width) return "";
8671
- let lowerBoundLen = 1;
8672
- let upperBoundLen = text.length;
8673
- let currentWidth;
8674
- while (lowerBoundLen <= upperBoundLen) {
8675
- const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
8676
- currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
8677
- if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
8678
- else lowerBoundLen = currentLen + 1;
8679
- }
8680
- const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
8681
- return slicedText ? slicedText + ellipsis : "";
8682
- }
8683
- /**
8684
- * Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
8685
- * with to original's text vertical and horizontal alignment.
8686
- */
8687
- function computeRotationPosition(rect, style) {
8688
- if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
8689
- let { x, y } = rect;
8690
- const cos = Math.cos(-style.rotation);
8691
- const sin = Math.sin(-style.rotation);
8692
- const width = rect.textWidth - 2 * 4;
8693
- const height = rect.textHeight;
8694
- const center = style.align === "center";
8695
- const rotateTowardCellCenter = style.align === "left" === sin < 0;
8696
- const sh = sin * height;
8697
- const sw = Math.abs(sin * width);
8698
- const ch = cos * height;
8699
- if (style.verticalAlign === "top") if (center) {
8700
- y += sw / 2;
8701
- x -= sh / 2;
8702
- } else if (rotateTowardCellCenter) x -= sh;
8703
- else y += sw;
8704
- else if (!style.verticalAlign || style.verticalAlign === "bottom") {
8705
- y += height - ch;
8706
- if (center) {
8707
- y -= sw / 2;
8708
- x -= sh / 2;
8709
- } else if (rotateTowardCellCenter) {
8710
- x -= sh;
8711
- y -= sw;
8712
- }
8713
- } else if (center) {
8714
- x -= sh / 2;
8715
- y -= height / 2;
8716
- if (rotateTowardCellCenter) y += sh;
8717
- else y -= sh;
8718
- } else if (rotateTowardCellCenter) {
8719
- x -= sh;
8720
- y -= sw / 2;
8721
- } else y += sw / 2 + ch / 4;
8722
- return {
8723
- x: cos * x - sin * y,
8724
- y: cos * y + sin * x
8725
- };
8726
- }
8727
-
8728
- //#endregion
8729
- //#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
8730
- /** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
8731
- const chartColorScalePlugin = {
8732
- id: "chartColorScalePlugin",
8733
- afterDatasetsDraw(chart, args, options) {
8734
- if (!options.position || options.position === "none" || !options.colorScale.length) return;
8735
- const ctx = chart.ctx;
8736
- ctx.save();
8737
- ctx.textAlign = "center";
8738
- ctx.textBaseline = "middle";
8739
- ctx.miterLimit = 1;
8740
- const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
8741
- const gradientWidth = 10;
8742
- const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
8743
- const gradientY = chart.chartArea.top;
8744
- const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
8745
- const step = 1 / (options.colorScale.length - 1);
8746
- options.colorScale.forEach((color, index) => {
8747
- gradient.addColorStop(index * step, color);
8748
- });
8749
- ctx.fillStyle = gradient;
8750
- ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
8751
- ctx.fillStyle = options.fontColor ?? "black";
8752
- ctx.font = getDefaultContextFont(12);
8753
- ctx.textAlign = "left";
8754
- let minValue = Math.round(options.minValue * 100) / 100;
8755
- let maxValue = Math.round(options.maxValue * 100) / 100;
8756
- if (options.minValue === options.maxValue) {
8757
- minValue -= 1;
8758
- maxValue += 1;
8759
- }
8760
- const formattedMaxValue = humanizeNumber({
8761
- value: maxValue,
8762
- format: void 0
8763
- }, options.locale);
8764
- const formattedMinValue = humanizeNumber({
8765
- value: minValue,
8766
- format: void 0
8767
- }, options.locale);
8768
- ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
8769
- ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
8770
- ctx.restore();
8771
- }
8772
- };
8773
-
8774
- //#endregion
8775
- //#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
8776
- function getFunnelChartController() {
8777
- if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
8778
- return class FunnelChartController extends globalThis.Chart.BarController {
8779
- static id = "funnel";
8780
- static defaults = {
8781
- ...globalThis.Chart?.BarController.defaults,
8782
- dataElementType: "funnel",
8783
- animation: { duration: (ctx) => {
8784
- if (ctx.type !== "data") return 1e3;
8785
- return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
8786
- } }
8787
- };
8788
- /** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
8789
- updateElements(rects, start, count, mode) {
8790
- super.updateElements(rects, start, count, mode);
8791
- for (let i = start; i < start + count; i++) {
8792
- const rect = rects[i];
8793
- this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
8794
- }
8795
- }
8796
- };
8797
- }
8798
- function getFunnelChartElement() {
8799
- if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
8800
- /**
8801
- * Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
8802
- * `width`, and the bottom is of width `nextElementWidth`.
8803
- */
8804
- return class FunnelChartElement extends globalThis.Chart.BarElement {
8805
- static id = "funnel";
8806
- /** Overwrite this to draw a trapezoid rather then a rectangle */
8807
- draw(ctx) {
8808
- ctx.save();
8809
- const { x, y, height, nextElement, base, options } = this.getProps([
8810
- "x",
8811
- "y",
8812
- "width",
8813
- "height",
8814
- "nextElement",
8815
- "base",
8816
- "options"
8817
- ]);
8818
- const width = getElementWidth(this);
8819
- const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
8820
- const startX = Math.min(x, base);
8821
- const startY = y - height / 2;
8822
- ctx.fillStyle = options.backgroundColor;
8823
- ctx.beginPath();
8824
- ctx.moveTo(startX, startY);
8825
- ctx.lineTo(startX + width, startY);
8826
- ctx.lineTo(startX + width - offset, startY + height);
8827
- ctx.lineTo(startX + offset, startY + height);
8828
- ctx.closePath();
8829
- ctx.fill();
8830
- if (options.borderWidth) {
8831
- ctx.strokeStyle = options.borderColor;
8832
- ctx.lineWidth = options.borderWidth;
8833
- ctx.stroke();
8834
- }
8835
- ctx.restore();
8836
- }
8837
- /** Check if the mouse is inside the trapezoid */
8838
- inRange(mouseX, mouseY) {
8839
- const { x, y, height, nextElement, base } = this.getProps([
8840
- "x",
8841
- "y",
8842
- "width",
8843
- "height",
8844
- "nextElement",
8845
- "base",
8846
- "options"
8847
- ]);
8848
- const width = getElementWidth(this);
8849
- const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
8850
- const startX = Math.min(x, base);
8851
- const startY = y - height / 2;
8852
- if (mouseY < startY || mouseY > startY + height) return false;
8853
- const offset = (width - nextElementWidth) / 2;
8854
- const left = startX + offset * (mouseY - startY) / height;
8855
- const right = startX + width - offset * (mouseY - startY) / height;
8856
- if (mouseX < left || mouseX > right) return false;
8857
- return true;
8858
- }
8859
- };
8860
- }
8861
- /**
8862
- * Get an element width.
8863
- *
8864
- * The property width is undefined during animations, we need to compute it manually.
8865
- */
8866
- function getElementWidth(element) {
8867
- const { x, base } = element.getProps(["x", "base"]);
8868
- return Math.max(x, base) - Math.min(x, base);
8869
- }
8870
- /**
8871
- * Position the tooltip inside the trapezoid.
8872
- * The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
8873
- */
8874
- const funnelTooltipPositioner = function(elements) {
8875
- if (!elements.length) return {
8876
- x: 0,
8877
- y: 0
8878
- };
8879
- const { x, y, base, width, height } = elements[0].element.getProps([
8880
- "x",
8881
- "y",
8882
- "width",
8883
- "height",
8884
- "base"
8885
- ]);
8886
- const startX = Math.min(x, base);
8887
- const startY = y - height / 2;
8888
- return {
8889
- x: startX + width * 2 / 3,
8890
- y: startY + height / 2
8891
- };
8892
- };
8893
-
8894
- //#endregion
8895
- //#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
8896
- const chartMinorGridPlugin = {
8897
- id: "o-spreadsheet-minor-gridlines",
8898
- beforeDatasetsDraw(chart) {
8899
- const ctx = chart.ctx;
8900
- const chartArea = chart.chartArea;
8901
- if (!chartArea) return;
8902
- for (const scaleId in chart.scales) {
8903
- const scale = chart.scales[scaleId];
8904
- const options = scale.options;
8905
- const minor = options?.grid?.minor;
8906
- if (!minor?.display) continue;
8907
- const showMajorGrid = options?.grid?.display;
8908
- const ticks = scale.ticks;
8909
- if (!ticks || ticks.length < 2) continue;
8910
- ctx.save();
8911
- ctx.lineWidth = 1;
8912
- ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
8913
- for (let i = 0; i < ticks.length - 1; i++) {
8914
- const start = scale.getPixelForTick(i);
8915
- const end = scale.getPixelForTick(i + 1);
8916
- if (!isFinite(start) || !isFinite(end)) continue;
8917
- for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
8918
- const ratio = j / 4;
8919
- const position = Math.round(start + (end - start) * ratio) + .5;
8920
- ctx.beginPath();
8921
- if (scale.isHorizontal()) {
8922
- ctx.moveTo(position, chartArea.top);
8923
- ctx.lineTo(position, chartArea.bottom);
8924
- } else {
8925
- ctx.moveTo(chartArea.left, position);
8926
- ctx.lineTo(chartArea.right, position);
8927
- }
8928
- ctx.stroke();
8929
- }
8930
- }
8931
- ctx.restore();
8932
- }
8933
- }
8934
- };
8935
-
8936
8409
  //#endregion
8937
8410
  //#region src/helpers/color.ts
8938
8411
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -9497,142 +8970,675 @@ const ALTERNATING_COLORS_XL = [
9497
8970
  function getNthColor(index, palette) {
9498
8971
  return palette[index % palette.length];
9499
8972
  }
9500
- function getColorsPalette(quantity) {
9501
- if (quantity <= 6) return COLORS_SM;
9502
- else if (quantity <= 12) return COLORS_MD;
9503
- else if (quantity <= 24) return COLORS_LG;
9504
- else return COLORS_XL;
8973
+ function getColorsPalette(quantity) {
8974
+ if (quantity <= 6) return COLORS_SM;
8975
+ else if (quantity <= 12) return COLORS_MD;
8976
+ else if (quantity <= 24) return COLORS_LG;
8977
+ else return COLORS_XL;
8978
+ }
8979
+ function getAlternatingColorsPalette(quantity) {
8980
+ if (quantity <= 6) return COLORS_SM;
8981
+ else if (quantity <= 12) return ALTERNATING_COLORS_MD;
8982
+ else if (quantity <= 24) return ALTERNATING_COLORS_LG;
8983
+ else return ALTERNATING_COLORS_XL;
8984
+ }
8985
+ var ColorGenerator = class {
8986
+ preferredColors;
8987
+ currentColorIndex = 0;
8988
+ palette;
8989
+ constructor(paletteSize, preferredColors = []) {
8990
+ this.preferredColors = preferredColors;
8991
+ this.palette = getColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
8992
+ }
8993
+ next() {
8994
+ return this.preferredColors?.[this.currentColorIndex] ? this.preferredColors[this.currentColorIndex++] : getNthColor(this.currentColorIndex++, this.palette);
8995
+ }
8996
+ };
8997
+ var AlternatingColorGenerator = class extends ColorGenerator {
8998
+ constructor(paletteSize, preferredColors = []) {
8999
+ super(paletteSize, preferredColors);
9000
+ this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
9001
+ }
9002
+ };
9003
+ var AlternatingColorMap = class {
9004
+ availableColors;
9005
+ colors = {};
9006
+ constructor(paletteSize = 12) {
9007
+ this.availableColors = new AlternatingColorGenerator(paletteSize);
9008
+ }
9009
+ get(id) {
9010
+ if (!this.colors[id]) this.colors[id] = this.availableColors.next();
9011
+ return this.colors[id];
9012
+ }
9013
+ };
9014
+ const COLORSCHEMES = {
9015
+ greys: [
9016
+ "#ffffff",
9017
+ "#808080",
9018
+ "#000000"
9019
+ ],
9020
+ blues: [
9021
+ "#f7fbff",
9022
+ "#6aaed6",
9023
+ "#08306b"
9024
+ ],
9025
+ reds: [
9026
+ "#fff5f0",
9027
+ "#fb694a",
9028
+ "#67000d"
9029
+ ],
9030
+ greens: [
9031
+ "#f7fcf5",
9032
+ "#73c476",
9033
+ "#00441b"
9034
+ ],
9035
+ oranges: [
9036
+ "#fff5eb",
9037
+ "#fd8c3b",
9038
+ "#7f2704"
9039
+ ],
9040
+ purples: [
9041
+ "#fcfbfd",
9042
+ "#9e9ac8",
9043
+ "#3f007d"
9044
+ ],
9045
+ viridis: [
9046
+ "#440154",
9047
+ "#21918c",
9048
+ "#fde725"
9049
+ ],
9050
+ cividis: [
9051
+ "#00224e",
9052
+ "#7d7c78",
9053
+ "#fee838"
9054
+ ],
9055
+ rainbow: [
9056
+ "#B41DB4",
9057
+ "#FFFF00",
9058
+ "#00FFFF"
9059
+ ]
9060
+ };
9061
+ const COLORSCALES = Object.keys(COLORSCHEMES);
9062
+ /**
9063
+ * Returns a function that maps a value to a color using a color scale defined by the given
9064
+ * color/threshold values pairs.
9065
+ */
9066
+ function getColorScale(colorScalePoints) {
9067
+ if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
9068
+ const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
9069
+ const thresholds = [];
9070
+ for (let i = 1; i < sortedColorScalePoints.length; i++) {
9071
+ const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
9072
+ const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
9073
+ const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
9074
+ const maxColor = colorToNumber(sortedColorScalePoints[i].color);
9075
+ thresholds.push({
9076
+ min: sortedColorScalePoints[i - 1].value,
9077
+ max: sortedColorScalePoints[i].value,
9078
+ minColor,
9079
+ maxColor,
9080
+ minColorAlpha,
9081
+ maxColorAlpha,
9082
+ colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
9083
+ });
9084
+ }
9085
+ return (value) => {
9086
+ if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
9087
+ for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
9088
+ return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
9089
+ };
9090
+ }
9091
+ function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
9092
+ const deltaValue = maxValue - minValue;
9093
+ const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
9094
+ const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
9095
+ const deltaColorB = minColor % 256 - maxColor % 256;
9096
+ return [
9097
+ deltaColorR / deltaValue,
9098
+ deltaColorG / deltaValue,
9099
+ deltaColorB / deltaValue
9100
+ ];
9101
+ }
9102
+ function colorCell(value, minValue, minColor, colorDiffUnit) {
9103
+ const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
9104
+ const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
9105
+ const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
9106
+ const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
9107
+ return r << 16 | g << 8 | b;
9108
+ }
9109
+
9110
+ //#endregion
9111
+ //#region src/helpers/text_helper.ts
9112
+ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
9113
+ return numberOfLines * (textLineHeight + 4) - 4;
9114
+ }
9115
+ function getCanvas(width = 100, height = 100) {
9116
+ return new OffscreenCanvas(width, height).getContext("2d");
9117
+ }
9118
+ /**
9119
+ * Get the default height of the cell given its style.
9120
+ */
9121
+ function getDefaultCellHeight(ctx, cell, locale, colSize) {
9122
+ if (!cell || !cell.isFormula && !cell.content) return 23;
9123
+ let content = "";
9124
+ try {
9125
+ if (!cell.isFormula) {
9126
+ const localeFormat = {
9127
+ format: cell.format,
9128
+ locale
9129
+ };
9130
+ content = formatValue(parseLiteral(cell.content, locale), localeFormat);
9131
+ }
9132
+ } catch {
9133
+ content = CellErrorType.GenericError;
9134
+ }
9135
+ return getCellContentHeight(ctx, content, cell.style, colSize);
9136
+ }
9137
+ function getCellContentHeight(ctx, content, style, colSize) {
9138
+ return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
9139
+ }
9140
+ function getDefaultContextFont(fontSize, bold = false, italic = false) {
9141
+ return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
9142
+ }
9143
+ function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
9144
+ if (!textLines.length) return {
9145
+ width: 0,
9146
+ height: 0
9147
+ };
9148
+ const font = computeTextFont(style, fontUnit);
9149
+ const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
9150
+ const height = computeTextLinesHeight(sizes[0].height, textLines.length);
9151
+ const width = Math.max(...sizes.map((size) => size.width));
9152
+ if (!style.rotation) return {
9153
+ height,
9154
+ width
9155
+ };
9156
+ const cos = Math.abs(Math.cos(style.rotation));
9157
+ const sin = Math.abs(Math.sin(style.rotation));
9158
+ return {
9159
+ width: width * cos + height * sin,
9160
+ height: sin * width + cos * height
9161
+ };
9162
+ }
9163
+ function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
9164
+ return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
9165
+ }
9166
+ function computeCachedTextWidth(context, text, font, rotation) {
9167
+ const size = computeCachedTextDimension(context, text, font);
9168
+ if (!rotation) return size.width;
9169
+ const cos = Math.abs(Math.cos(rotation));
9170
+ const sin = Math.abs(Math.sin(rotation));
9171
+ return size.width * cos + size.height * sin;
9172
+ }
9173
+ const textDimensionsCache = {};
9174
+ function computeTextDimension(context, text, style, fontUnit = "pt") {
9175
+ const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
9176
+ if (!style.rotation) return size;
9177
+ const cos = Math.abs(Math.cos(style.rotation));
9178
+ const sin = Math.abs(Math.sin(style.rotation));
9179
+ return {
9180
+ width: size.width * cos + size.height * sin,
9181
+ height: size.height * cos + size.width * sin
9182
+ };
9183
+ }
9184
+ function computeCachedTextDimension(context, text, font) {
9185
+ if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
9186
+ if (textDimensionsCache[font][text] === void 0) {
9187
+ context.save();
9188
+ context.font = font;
9189
+ const measure = context.measureText(text);
9190
+ context.restore();
9191
+ const width = measure.width;
9192
+ const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
9193
+ textDimensionsCache[font][text] = {
9194
+ width,
9195
+ height
9196
+ };
9197
+ }
9198
+ return textDimensionsCache[font][text];
9199
+ }
9200
+ function fontSizeInPixels(fontSize) {
9201
+ return Math.round(fontSize * 96 / 72);
9202
+ }
9203
+ function computeTextFont(style, fontUnit = "pt") {
9204
+ return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
9205
+ }
9206
+ function computeTextFontSizeInPixels(style) {
9207
+ return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
9208
+ }
9209
+ function splitWordToSpecificWidth(ctx, word, width, style) {
9210
+ if (computeTextWidth(ctx, word, style) <= width) return [word];
9211
+ const splitWord = [];
9212
+ let wordPart = "";
9213
+ for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
9214
+ splitWord.push(wordPart);
9215
+ wordPart = l;
9216
+ } else wordPart += l;
9217
+ splitWord.push(wordPart);
9218
+ return splitWord;
9219
+ }
9220
+ /**
9221
+ * Return the given text, split in multiple lines if needed. The text will be split in multiple
9222
+ * line if it contains NEWLINE characters, or if it's longer than the given width.
9223
+ */
9224
+ function splitTextToWidth(ctx, text, style, width) {
9225
+ if (!style) style = {};
9226
+ if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
9227
+ const brokenText = [];
9228
+ const lines = text.includes("\n") ? text.split("\n") : [text];
9229
+ for (const line of lines) {
9230
+ const words = line.includes(" ") ? line.split(" ") : [line];
9231
+ if (!width) {
9232
+ brokenText.push(line);
9233
+ continue;
9234
+ }
9235
+ let textLine = "";
9236
+ let availableWidth = width;
9237
+ for (const word of words) {
9238
+ const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
9239
+ const lastPart = splitWord.pop();
9240
+ const lastPartWidth = computeTextWidth(ctx, lastPart, style);
9241
+ if (splitWord.length) {
9242
+ if (textLine !== "") {
9243
+ brokenText.push(textLine);
9244
+ textLine = "";
9245
+ availableWidth = width;
9246
+ }
9247
+ splitWord.forEach((wordPart) => {
9248
+ brokenText.push(wordPart);
9249
+ });
9250
+ textLine = lastPart;
9251
+ availableWidth = width - lastPartWidth;
9252
+ } else {
9253
+ const _word = textLine === "" ? lastPart : " " + lastPart;
9254
+ const wordWidth = computeTextWidth(ctx, _word, style);
9255
+ if (wordWidth <= availableWidth) {
9256
+ textLine += _word;
9257
+ availableWidth -= wordWidth;
9258
+ } else {
9259
+ brokenText.push(textLine);
9260
+ textLine = lastPart;
9261
+ availableWidth = width - lastPartWidth;
9262
+ }
9263
+ }
9264
+ }
9265
+ if (textLine !== "") brokenText.push(textLine);
9266
+ }
9267
+ return brokenText;
9268
+ }
9269
+ /**
9270
+ * Return the font size that makes the width of a text match the given line width.
9271
+ * Minimum font size is 1.
9272
+ *
9273
+ * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
9274
+ */
9275
+ function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
9276
+ let minFontSize = 1;
9277
+ if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
9278
+ if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
9279
+ let fontSize = (minFontSize + maxFontSize) / 2;
9280
+ let currentTextWidth = getTextWidth(fontSize);
9281
+ let iterations = 0;
9282
+ while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
9283
+ if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
9284
+ else minFontSize = (minFontSize + maxFontSize) / 2;
9285
+ fontSize = (minFontSize + maxFontSize) / 2;
9286
+ currentTextWidth = getTextWidth(fontSize);
9287
+ iterations++;
9288
+ }
9289
+ return fontSize;
9290
+ }
9291
+ /** Transform a string to lowercase and removes whitespace from both ends of the string*/
9292
+ function toTrimmedLowerCase(str) {
9293
+ return str ? str.toLowerCase().trim() : "";
9505
9294
  }
9506
- function getAlternatingColorsPalette(quantity) {
9507
- if (quantity <= 6) return COLORS_SM;
9508
- else if (quantity <= 12) return ALTERNATING_COLORS_MD;
9509
- else if (quantity <= 24) return ALTERNATING_COLORS_LG;
9510
- else return ALTERNATING_COLORS_XL;
9295
+ /**
9296
+ * Extract the fontSize from a context font string
9297
+ * @param font The (context) font string to parse
9298
+ * @returns The fontSize in pixels
9299
+ */
9300
+ const pxRegex = /([0-9\.]*)px/;
9301
+ function getContextFontSize(font) {
9302
+ return Number(font.match(pxRegex)?.[1]);
9511
9303
  }
9512
- var ColorGenerator = class {
9513
- preferredColors;
9514
- currentColorIndex = 0;
9515
- palette;
9516
- constructor(paletteSize, preferredColors = []) {
9517
- this.preferredColors = preferredColors;
9518
- this.palette = getColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
9304
+ function clipTextWithEllipsis(ctx, text, maxWidth) {
9305
+ let width = computeCachedTextWidth(ctx, text, ctx.font);
9306
+ if (width <= maxWidth) return text;
9307
+ const ellipsis = "…";
9308
+ const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
9309
+ if (width <= ellipsisWidth) return text;
9310
+ let len = text.length;
9311
+ while (width >= maxWidth - ellipsisWidth && len-- > 0) {
9312
+ text = text.substring(0, len);
9313
+ width = computeCachedTextWidth(ctx, text, ctx.font);
9519
9314
  }
9520
- next() {
9521
- return this.preferredColors?.[this.currentColorIndex] ? this.preferredColors[this.currentColorIndex++] : getNthColor(this.currentColorIndex++, this.palette);
9315
+ return text + ellipsis;
9316
+ }
9317
+ function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10, highlightText = false) {
9318
+ if (highlightText) {
9319
+ context.save();
9320
+ context.fillStyle = lightenColor(context.fillStyle, DEFAULT_TEXT_HIGHLIGHT_PERCENT);
9522
9321
  }
9523
- };
9524
- var AlternatingColorGenerator = class extends ColorGenerator {
9525
- constructor(paletteSize, preferredColors = []) {
9526
- super(paletteSize, preferredColors);
9527
- this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
9322
+ context.fillText(text, position.x, position.y);
9323
+ if (!underline && !strikethrough) return;
9324
+ const measure = context.measureText(text);
9325
+ const textWidth = measure.width;
9326
+ const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
9327
+ const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
9328
+ let { x, y } = position;
9329
+ let strikeY = y, underlineY = y;
9330
+ switch (context.textAlign) {
9331
+ case "center":
9332
+ x -= textWidth / 2;
9333
+ break;
9334
+ case "right":
9335
+ x -= textWidth;
9336
+ break;
9528
9337
  }
9529
- };
9530
- var AlternatingColorMap = class {
9531
- availableColors;
9532
- colors = {};
9533
- constructor(paletteSize = 12) {
9534
- this.availableColors = new AlternatingColorGenerator(paletteSize);
9338
+ switch (context.textBaseline) {
9339
+ case "top":
9340
+ underlineY += boxHeight - 2 * strokeWidth;
9341
+ strikeY += boxHeight / 2 - strokeWidth;
9342
+ break;
9343
+ case "middle":
9344
+ underlineY += boxHeight / 2 - strokeWidth;
9345
+ break;
9346
+ case "alphabetic":
9347
+ underlineY += 2 * strokeWidth;
9348
+ strikeY -= 3 * strokeWidth;
9349
+ break;
9350
+ case "bottom":
9351
+ underlineY = y;
9352
+ strikeY -= textHeight / 2 - strokeWidth / 2;
9353
+ break;
9535
9354
  }
9536
- get(id) {
9537
- if (!this.colors[id]) this.colors[id] = this.availableColors.next();
9538
- return this.colors[id];
9355
+ if (underline) {
9356
+ context.lineWidth = strokeWidth;
9357
+ context.strokeStyle = context.fillStyle;
9358
+ context.beginPath();
9359
+ context.moveTo(x, underlineY);
9360
+ context.lineTo(x + textWidth, underlineY);
9361
+ context.stroke();
9539
9362
  }
9540
- };
9541
- const COLORSCHEMES = {
9542
- greys: [
9543
- "#ffffff",
9544
- "#808080",
9545
- "#000000"
9546
- ],
9547
- blues: [
9548
- "#f7fbff",
9549
- "#6aaed6",
9550
- "#08306b"
9551
- ],
9552
- reds: [
9553
- "#fff5f0",
9554
- "#fb694a",
9555
- "#67000d"
9556
- ],
9557
- greens: [
9558
- "#f7fcf5",
9559
- "#73c476",
9560
- "#00441b"
9561
- ],
9562
- oranges: [
9563
- "#fff5eb",
9564
- "#fd8c3b",
9565
- "#7f2704"
9566
- ],
9567
- purples: [
9568
- "#fcfbfd",
9569
- "#9e9ac8",
9570
- "#3f007d"
9571
- ],
9572
- viridis: [
9573
- "#440154",
9574
- "#21918c",
9575
- "#fde725"
9576
- ],
9577
- cividis: [
9578
- "#00224e",
9579
- "#7d7c78",
9580
- "#fee838"
9581
- ],
9582
- rainbow: [
9583
- "#B41DB4",
9584
- "#FFFF00",
9585
- "#00FFFF"
9586
- ]
9587
- };
9588
- const COLORSCALES = Object.keys(COLORSCHEMES);
9363
+ if (strikethrough) {
9364
+ context.lineWidth = strokeWidth;
9365
+ context.strokeStyle = context.fillStyle;
9366
+ context.beginPath();
9367
+ context.moveTo(x, strikeY);
9368
+ context.lineTo(x + textWidth, strikeY);
9369
+ context.stroke();
9370
+ }
9371
+ if (highlightText) context.restore();
9372
+ }
9373
+ function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
9374
+ if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
9375
+ const ellipsis = "...";
9376
+ const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
9377
+ if (ellipsisWidth >= width) return "";
9378
+ let lowerBoundLen = 1;
9379
+ let upperBoundLen = text.length;
9380
+ let currentWidth;
9381
+ while (lowerBoundLen <= upperBoundLen) {
9382
+ const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
9383
+ currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
9384
+ if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
9385
+ else lowerBoundLen = currentLen + 1;
9386
+ }
9387
+ const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
9388
+ return slicedText ? slicedText + ellipsis : "";
9389
+ }
9589
9390
  /**
9590
- * Returns a function that maps a value to a color using a color scale defined by the given
9591
- * color/threshold values pairs.
9391
+ * Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
9392
+ * with to original's text vertical and horizontal alignment.
9592
9393
  */
9593
- function getColorScale(colorScalePoints) {
9594
- if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
9595
- const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
9596
- const thresholds = [];
9597
- for (let i = 1; i < sortedColorScalePoints.length; i++) {
9598
- const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
9599
- const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
9600
- const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
9601
- const maxColor = colorToNumber(sortedColorScalePoints[i].color);
9602
- thresholds.push({
9603
- min: sortedColorScalePoints[i - 1].value,
9604
- max: sortedColorScalePoints[i].value,
9605
- minColor,
9606
- maxColor,
9607
- minColorAlpha,
9608
- maxColorAlpha,
9609
- colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
9394
+ function computeRotationPosition(rect, style) {
9395
+ if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
9396
+ let { x, y } = rect;
9397
+ const cos = Math.cos(-style.rotation);
9398
+ const sin = Math.sin(-style.rotation);
9399
+ const width = rect.textWidth - 2 * 4;
9400
+ const height = rect.textHeight;
9401
+ const center = style.align === "center";
9402
+ const rotateTowardCellCenter = style.align === "left" === sin < 0;
9403
+ const sh = sin * height;
9404
+ const sw = Math.abs(sin * width);
9405
+ const ch = cos * height;
9406
+ if (style.verticalAlign === "top") if (center) {
9407
+ y += sw / 2;
9408
+ x -= sh / 2;
9409
+ } else if (rotateTowardCellCenter) x -= sh;
9410
+ else y += sw;
9411
+ else if (!style.verticalAlign || style.verticalAlign === "bottom") {
9412
+ y += height - ch;
9413
+ if (center) {
9414
+ y -= sw / 2;
9415
+ x -= sh / 2;
9416
+ } else if (rotateTowardCellCenter) {
9417
+ x -= sh;
9418
+ y -= sw;
9419
+ }
9420
+ } else if (center) {
9421
+ x -= sh / 2;
9422
+ y -= height / 2;
9423
+ if (rotateTowardCellCenter) y += sh;
9424
+ else y -= sh;
9425
+ } else if (rotateTowardCellCenter) {
9426
+ x -= sh;
9427
+ y -= sw / 2;
9428
+ } else y += sw / 2 + ch / 4;
9429
+ return {
9430
+ x: cos * x - sin * y,
9431
+ y: cos * y + sin * x
9432
+ };
9433
+ }
9434
+
9435
+ //#endregion
9436
+ //#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
9437
+ /** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
9438
+ const chartColorScalePlugin = {
9439
+ id: "chartColorScalePlugin",
9440
+ afterDatasetsDraw(chart, args, options) {
9441
+ if (!options.position || options.position === "none" || !options.colorScale.length) return;
9442
+ const ctx = chart.ctx;
9443
+ ctx.save();
9444
+ ctx.textAlign = "center";
9445
+ ctx.textBaseline = "middle";
9446
+ ctx.miterLimit = 1;
9447
+ const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
9448
+ const gradientWidth = 10;
9449
+ const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
9450
+ const gradientY = chart.chartArea.top;
9451
+ const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
9452
+ const step = 1 / (options.colorScale.length - 1);
9453
+ options.colorScale.forEach((color, index) => {
9454
+ gradient.addColorStop(index * step, color);
9610
9455
  });
9456
+ ctx.fillStyle = gradient;
9457
+ ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
9458
+ ctx.fillStyle = options.fontColor ?? "black";
9459
+ ctx.font = getDefaultContextFont(12);
9460
+ ctx.textAlign = "left";
9461
+ let minValue = Math.round(options.minValue * 100) / 100;
9462
+ let maxValue = Math.round(options.maxValue * 100) / 100;
9463
+ if (options.minValue === options.maxValue) {
9464
+ minValue -= 1;
9465
+ maxValue += 1;
9466
+ }
9467
+ const formattedMaxValue = humanizeNumber({
9468
+ value: maxValue,
9469
+ format: void 0
9470
+ }, options.locale);
9471
+ const formattedMinValue = humanizeNumber({
9472
+ value: minValue,
9473
+ format: void 0
9474
+ }, options.locale);
9475
+ ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
9476
+ ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
9477
+ ctx.restore();
9611
9478
  }
9612
- return (value) => {
9613
- if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
9614
- for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
9615
- return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
9479
+ };
9480
+
9481
+ //#endregion
9482
+ //#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
9483
+ function getFunnelChartController() {
9484
+ if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
9485
+ return class FunnelChartController extends globalThis.Chart.BarController {
9486
+ static id = "funnel";
9487
+ static defaults = {
9488
+ ...globalThis.Chart?.BarController.defaults,
9489
+ dataElementType: "funnel",
9490
+ animation: { duration: (ctx) => {
9491
+ if (ctx.type !== "data") return 1e3;
9492
+ return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
9493
+ } }
9494
+ };
9495
+ /** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
9496
+ updateElements(rects, start, count, mode) {
9497
+ super.updateElements(rects, start, count, mode);
9498
+ for (let i = start; i < start + count; i++) {
9499
+ const rect = rects[i];
9500
+ this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
9501
+ }
9502
+ }
9616
9503
  };
9617
9504
  }
9618
- function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
9619
- const deltaValue = maxValue - minValue;
9620
- const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
9621
- const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
9622
- const deltaColorB = minColor % 256 - maxColor % 256;
9623
- return [
9624
- deltaColorR / deltaValue,
9625
- deltaColorG / deltaValue,
9626
- deltaColorB / deltaValue
9627
- ];
9505
+ function getFunnelChartElement() {
9506
+ if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
9507
+ /**
9508
+ * Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
9509
+ * `width`, and the bottom is of width `nextElementWidth`.
9510
+ */
9511
+ return class FunnelChartElement extends globalThis.Chart.BarElement {
9512
+ static id = "funnel";
9513
+ /** Overwrite this to draw a trapezoid rather then a rectangle */
9514
+ draw(ctx) {
9515
+ ctx.save();
9516
+ const { x, y, height, nextElement, base, options } = this.getProps([
9517
+ "x",
9518
+ "y",
9519
+ "width",
9520
+ "height",
9521
+ "nextElement",
9522
+ "base",
9523
+ "options"
9524
+ ]);
9525
+ const width = getElementWidth(this);
9526
+ const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
9527
+ const startX = Math.min(x, base);
9528
+ const startY = y - height / 2;
9529
+ ctx.fillStyle = options.backgroundColor;
9530
+ ctx.beginPath();
9531
+ ctx.moveTo(startX, startY);
9532
+ ctx.lineTo(startX + width, startY);
9533
+ ctx.lineTo(startX + width - offset, startY + height);
9534
+ ctx.lineTo(startX + offset, startY + height);
9535
+ ctx.closePath();
9536
+ ctx.fill();
9537
+ if (options.borderWidth) {
9538
+ ctx.strokeStyle = options.borderColor;
9539
+ ctx.lineWidth = options.borderWidth;
9540
+ ctx.stroke();
9541
+ }
9542
+ ctx.restore();
9543
+ }
9544
+ /** Check if the mouse is inside the trapezoid */
9545
+ inRange(mouseX, mouseY) {
9546
+ const { x, y, height, nextElement, base } = this.getProps([
9547
+ "x",
9548
+ "y",
9549
+ "width",
9550
+ "height",
9551
+ "nextElement",
9552
+ "base",
9553
+ "options"
9554
+ ]);
9555
+ const width = getElementWidth(this);
9556
+ const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
9557
+ const startX = Math.min(x, base);
9558
+ const startY = y - height / 2;
9559
+ if (mouseY < startY || mouseY > startY + height) return false;
9560
+ const offset = (width - nextElementWidth) / 2;
9561
+ const left = startX + offset * (mouseY - startY) / height;
9562
+ const right = startX + width - offset * (mouseY - startY) / height;
9563
+ if (mouseX < left || mouseX > right) return false;
9564
+ return true;
9565
+ }
9566
+ };
9628
9567
  }
9629
- function colorCell(value, minValue, minColor, colorDiffUnit) {
9630
- const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
9631
- const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
9632
- const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
9633
- const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
9634
- return r << 16 | g << 8 | b;
9568
+ /**
9569
+ * Get an element width.
9570
+ *
9571
+ * The property width is undefined during animations, we need to compute it manually.
9572
+ */
9573
+ function getElementWidth(element) {
9574
+ const { x, base } = element.getProps(["x", "base"]);
9575
+ return Math.max(x, base) - Math.min(x, base);
9635
9576
  }
9577
+ /**
9578
+ * Position the tooltip inside the trapezoid.
9579
+ * The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
9580
+ */
9581
+ const funnelTooltipPositioner = function(elements) {
9582
+ if (!elements.length) return {
9583
+ x: 0,
9584
+ y: 0
9585
+ };
9586
+ const { x, y, base, width, height } = elements[0].element.getProps([
9587
+ "x",
9588
+ "y",
9589
+ "width",
9590
+ "height",
9591
+ "base"
9592
+ ]);
9593
+ const startX = Math.min(x, base);
9594
+ const startY = y - height / 2;
9595
+ return {
9596
+ x: startX + width * 2 / 3,
9597
+ y: startY + height / 2
9598
+ };
9599
+ };
9600
+
9601
+ //#endregion
9602
+ //#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
9603
+ const chartMinorGridPlugin = {
9604
+ id: "o-spreadsheet-minor-gridlines",
9605
+ beforeDatasetsDraw(chart) {
9606
+ const ctx = chart.ctx;
9607
+ const chartArea = chart.chartArea;
9608
+ if (!chartArea) return;
9609
+ for (const scaleId in chart.scales) {
9610
+ const scale = chart.scales[scaleId];
9611
+ const options = scale.options;
9612
+ const minor = options?.grid?.minor;
9613
+ if (!minor?.display) continue;
9614
+ const showMajorGrid = options?.grid?.display;
9615
+ const ticks = scale.ticks;
9616
+ if (!ticks || ticks.length < 2) continue;
9617
+ ctx.save();
9618
+ ctx.lineWidth = 1;
9619
+ ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
9620
+ for (let i = 0; i < ticks.length - 1; i++) {
9621
+ const start = scale.getPixelForTick(i);
9622
+ const end = scale.getPixelForTick(i + 1);
9623
+ if (!isFinite(start) || !isFinite(end)) continue;
9624
+ for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
9625
+ const ratio = j / 4;
9626
+ const position = Math.round(start + (end - start) * ratio) + .5;
9627
+ ctx.beginPath();
9628
+ if (scale.isHorizontal()) {
9629
+ ctx.moveTo(position, chartArea.top);
9630
+ ctx.lineTo(position, chartArea.bottom);
9631
+ } else {
9632
+ ctx.moveTo(chartArea.left, position);
9633
+ ctx.lineTo(chartArea.right, position);
9634
+ }
9635
+ ctx.stroke();
9636
+ }
9637
+ }
9638
+ ctx.restore();
9639
+ }
9640
+ }
9641
+ };
9636
9642
 
9637
9643
  //#endregion
9638
9644
  //#region src/xlsx/constants.ts
@@ -12072,11 +12078,11 @@ function drawScoreChart(structure, canvas, zoom = 1) {
12072
12078
  if (structure.baseline) {
12073
12079
  ctx.font = structure.baseline.style.font;
12074
12080
  ctx.fillStyle = structure.baseline.style.color;
12075
- drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
12081
+ drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough, void 0, structure.baseline.style.highlightText);
12076
12082
  }
12077
12083
  if (structure.baselineArrow && structure.baselineArrow.style.size > 0 && Path2DConstructor) {
12078
12084
  ctx.save();
12079
- ctx.fillStyle = structure.baselineArrow.style.color;
12085
+ ctx.fillStyle = structure.baselineArrow.style.highlight ? lightenColor(structure.baselineArrow.style.color, DEFAULT_TEXT_HIGHLIGHT_PERCENT) : structure.baselineArrow.style.color;
12080
12086
  ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
12081
12087
  const ratio = structure.baselineArrow.style.size / 10;
12082
12088
  ctx.scale(ratio, ratio);
@@ -12094,18 +12100,18 @@ function drawScoreChart(structure, canvas, zoom = 1) {
12094
12100
  const descr = structure.baselineDescr;
12095
12101
  ctx.font = descr.style.font;
12096
12102
  ctx.fillStyle = descr.style.color;
12097
- ctx.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
12103
+ drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.baseline?.style.highlightText);
12098
12104
  }
12099
12105
  if (structure.key) {
12100
12106
  ctx.font = structure.key.style.font;
12101
12107
  ctx.fillStyle = structure.key.style.color;
12102
- drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
12108
+ 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);
12103
12109
  }
12104
12110
  if (structure.keyDescr) {
12105
12111
  const descr = structure.keyDescr;
12106
12112
  ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
12107
12113
  ctx.fillStyle = descr.style.color;
12108
- ctx.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
12114
+ drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.key?.style.highlightText);
12109
12115
  }
12110
12116
  if (structure.progressBar) {
12111
12117
  ctx.fillStyle = structure.progressBar.style.backgroundColor;
@@ -12329,29 +12335,34 @@ var ScorecardChartConfigBuilder = class {
12329
12335
  color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
12330
12336
  font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
12331
12337
  strikethrough: this.runtime.keyValueStyle?.strikethrough,
12332
- underline: this.runtime.keyValueStyle?.underline
12338
+ underline: this.runtime.keyValueStyle?.underline,
12339
+ highlightText: this.runtime.keyHighlight
12333
12340
  },
12334
12341
  keyDescr: {
12335
12342
  color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
12336
12343
  font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
12337
12344
  strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
12338
- underline: this.runtime.keyValueDescrStyle?.underline
12345
+ underline: this.runtime.keyValueDescrStyle?.underline,
12346
+ highlightText: this.runtime.keyHighlight
12339
12347
  },
12340
12348
  baselineValue: {
12341
12349
  font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
12342
12350
  strikethrough: this.runtime.baselineStyle?.strikethrough,
12343
12351
  underline: this.runtime.baselineStyle?.underline,
12344
- color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
12352
+ color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
12353
+ highlightText: this.runtime.baselineHighlight
12345
12354
  },
12346
12355
  baselineDescr: {
12347
12356
  font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
12348
12357
  strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
12349
12358
  underline: this.runtime.baselineDescrStyle?.underline,
12350
- color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor
12359
+ color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
12360
+ highlightText: this.runtime.baselineHighlight
12351
12361
  },
12352
12362
  baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar ? void 0 : {
12353
12363
  size: this.keyValue ? .8 * baselineValueFontSize : 0,
12354
- color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
12364
+ color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
12365
+ highlight: this.runtime.baselineHighlight
12355
12366
  }
12356
12367
  };
12357
12368
  }
@@ -12447,11 +12458,14 @@ var ScorecardChart = class extends Component {
12447
12458
  });
12448
12459
  onWillUnmount(() => resizeObserver.disconnect());
12449
12460
  }
12461
+ config(canvasRect, zoom) {
12462
+ return getScorecardConfiguration(getZoomedRect(1 / zoom, canvasRect), this.runtime);
12463
+ }
12450
12464
  createChart() {
12451
12465
  const canvas = this.canvas();
12452
12466
  if (!canvas) return;
12453
12467
  const zoom = this.env.model.getters.getViewportZoomLevel();
12454
- drawScoreChart(getScorecardConfiguration(getZoomedRect(1 / zoom, canvas.getBoundingClientRect()), this.runtime), canvas, zoom);
12468
+ drawScoreChart(this.config(canvas.getBoundingClientRect(), zoom), canvas, zoom);
12455
12469
  }
12456
12470
  };
12457
12471
 
@@ -21791,10 +21805,10 @@ function toNormalizedPivotValue(dimension, groupValue) {
21791
21805
  type: dimension.type
21792
21806
  }));
21793
21807
  if (groupValueString.toLowerCase() === "false") return false;
21794
- return pivotNormalizationValueRegistry.get(dimension.type)(groupValueString, dimension.granularity);
21808
+ return pivotNormalizationValueRegistry.get(dimension.type)(groupValueString, dimension);
21795
21809
  }
21796
- function normalizeDateTime(value, granularity) {
21797
- return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
21810
+ function normalizeDateTime(value, dimension) {
21811
+ return pivotTimeAdapter(dimension.granularity ?? "month").normalizeFunctionValue(value);
21798
21812
  }
21799
21813
  function toFunctionPivotValue(value, dimension) {
21800
21814
  if (value === null) return `"null"`;
@@ -28055,7 +28069,7 @@ function filterInvalidCalendarDataPoints(labels, datasets) {
28055
28069
  */
28056
28070
  function filterInvalidHierarchicalPoints(values, hierarchy) {
28057
28071
  const numberOfDataPoints = Math.max(values.length, ...hierarchy.map((dataset) => dataset.data?.length || 0));
28058
- const isEmpty = (value) => value === null || value === "";
28072
+ const isEmpty = (value) => value === void 0 || value === null || value === "";
28059
28073
  const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
28060
28074
  const groups = hierarchy.map((dataset) => dataset.data?.[dataPointIndex]);
28061
28075
  if (isEmpty(groups[0]?.value)) return false;
@@ -34536,6 +34550,35 @@ var CellIsRuleEditor = class extends Component {
34536
34550
  }
34537
34551
  };
34538
34552
 
34553
+ //#endregion
34554
+ //#region src/components/side_panel/criterion_form/calendar_button/calendar_button.ts
34555
+ var CalendarButton = class extends Component {
34556
+ static template = "o-spreadsheet-CalendarButton";
34557
+ props = props({
34558
+ value: types$1.string().optional(""),
34559
+ onChange: types$1.function()
34560
+ });
34561
+ datePickerRef = signal.ref(HTMLInputElement);
34562
+ openCalendar() {
34563
+ this.datePickerRef()?.showPicker();
34564
+ }
34565
+ formatDateForInput(value) {
34566
+ const dateValue = parseDateTime(value, DEFAULT_LOCALE);
34567
+ return dateValue ? formatValue(dateValue.value, {
34568
+ format: "yyyy-mm-dd",
34569
+ locale: DEFAULT_LOCALE
34570
+ }) : "";
34571
+ }
34572
+ onDateInputValueChanged(value) {
34573
+ const dateValue = parseDateTime(value, DEFAULT_LOCALE);
34574
+ const formattedValue = dateValue ? formatValue(dateValue.value, {
34575
+ format: DEFAULT_LOCALE.dateFormat,
34576
+ locale: DEFAULT_LOCALE
34577
+ }) : "";
34578
+ this.props.onChange(formattedValue);
34579
+ }
34580
+ };
34581
+
34539
34582
  //#endregion
34540
34583
  //#region src/components/side_panel/criterion_form/criterion_form.ts
34541
34584
  var CriterionForm = class extends Component {
@@ -35224,7 +35267,8 @@ var DateCriterionForm = class extends CriterionForm {
35224
35267
  static template = "o-spreadsheet-DataValidationDateCriterion";
35225
35268
  static components = {
35226
35269
  CriterionInput,
35227
- Select
35270
+ Select,
35271
+ CalendarButton
35228
35272
  };
35229
35273
  get currentDateValue() {
35230
35274
  return this.props.criterion.dateValue || "exactDate";
@@ -35250,7 +35294,10 @@ var DateCriterionForm = class extends CriterionForm {
35250
35294
  //#region src/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.ts
35251
35295
  var DoubleInputCriterionForm = class extends CriterionForm {
35252
35296
  static template = "o-spreadsheet-DoubleInputCriterionForm";
35253
- static components = { CriterionInput };
35297
+ static components = {
35298
+ CriterionInput,
35299
+ CalendarButton
35300
+ };
35254
35301
  onFirstValueChanged(value) {
35255
35302
  const values = this.props.criterion.values;
35256
35303
  this.updateCriterion({ values: [value, values[1] || ""] });
@@ -35259,6 +35306,9 @@ var DoubleInputCriterionForm = class extends CriterionForm {
35259
35306
  const values = this.props.criterion.values;
35260
35307
  this.updateCriterion({ values: [values[0] || "", value] });
35261
35308
  }
35309
+ get isDateType() {
35310
+ return ["dateIsBetween", "dateIsNotBetween"].includes(this.props.criterion.type);
35311
+ }
35262
35312
  };
35263
35313
 
35264
35314
  //#endregion
@@ -46605,7 +46655,7 @@ var FiguresContainer = class extends Component {
46605
46655
  horizontalSnap: void 0,
46606
46656
  verticalSnap: void 0,
46607
46657
  cancelDnd: void 0,
46608
- overlappingCarousel: void 0
46658
+ overlappingChartOrCarousel: void 0
46609
46659
  });
46610
46660
  setup() {
46611
46661
  onMounted(() => {
@@ -46621,7 +46671,7 @@ var FiguresContainer = class extends Component {
46621
46671
  this.dnd.selectedRect = void 0;
46622
46672
  this.dnd.horizontalSnap = void 0;
46623
46673
  this.dnd.verticalSnap = void 0;
46624
- this.dnd.overlappingCarousel = void 0;
46674
+ this.dnd.overlappingChartOrCarousel = void 0;
46625
46675
  this.dnd.cancelDnd = void 0;
46626
46676
  }
46627
46677
  });
@@ -46759,11 +46809,11 @@ var FiguresContainer = class extends Component {
46759
46809
  hasStartedDnd = true;
46760
46810
  const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
46761
46811
  const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
46762
- let overlappingCarousel = void 0;
46812
+ let overlappingChartOrCarousel = void 0;
46763
46813
  const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
46764
- if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingCarousel = this.getCarouselOverlappingChart(draggedFigure, otherFigures);
46765
- this.dnd.overlappingCarousel = overlappingCarousel;
46766
- if (!overlappingCarousel) {
46814
+ if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
46815
+ this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
46816
+ if (!overlappingChartOrCarousel) {
46767
46817
  const snapReturn = snapForMove(getters, selectedFigures, otherFigures);
46768
46818
  this.dnd.selectedFigures = snapReturn.snappedFigures;
46769
46819
  this.dnd.selectedRect = this.getDndFigureRect();
@@ -46784,7 +46834,7 @@ var FiguresContainer = class extends Component {
46784
46834
  else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
46785
46835
  return;
46786
46836
  }
46787
- if (!this.dnd.overlappingCarousel) {
46837
+ if (!this.dnd.overlappingChartOrCarousel) {
46788
46838
  const payloads = this.dnd.selectedFigures?.map((f) => {
46789
46839
  return {
46790
46840
  sheetId,
@@ -46794,20 +46844,25 @@ var FiguresContainer = class extends Component {
46794
46844
  }) || [];
46795
46845
  this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
46796
46846
  } else {
46797
- const carouselFigureId = this.dnd.overlappingCarousel.id;
46847
+ const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
46798
46848
  const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
46799
- this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
46849
+ if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
46800
46850
  sheetId,
46801
- carouselFigureId,
46851
+ carouselFigureId: overlappingFigureId,
46802
46852
  chartFigureIds
46803
46853
  });
46854
+ else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
46855
+ sheetId,
46856
+ baseFigureId: overlappingFigureId,
46857
+ chartFigureIds: [overlappingFigureId, ...chartFigureIds]
46858
+ });
46804
46859
  }
46805
46860
  this.dnd.draggedFigure = void 0;
46806
46861
  this.dnd.selectedFigures = void 0;
46807
46862
  this.dnd.selectedRect = void 0;
46808
46863
  this.dnd.horizontalSnap = void 0;
46809
46864
  this.dnd.verticalSnap = void 0;
46810
- this.dnd.overlappingCarousel = void 0;
46865
+ this.dnd.overlappingChartOrCarousel = void 0;
46811
46866
  };
46812
46867
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
46813
46868
  }
@@ -46863,7 +46918,7 @@ var FiguresContainer = class extends Component {
46863
46918
  this.dnd.selectedRect = void 0;
46864
46919
  this.dnd.horizontalSnap = void 0;
46865
46920
  this.dnd.verticalSnap = void 0;
46866
- this.dnd.overlappingCarousel = void 0;
46921
+ this.dnd.overlappingChartOrCarousel = void 0;
46867
46922
  };
46868
46923
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
46869
46924
  }
@@ -46873,12 +46928,12 @@ var FiguresContainer = class extends Component {
46873
46928
  getFigureStyle(figureUI) {
46874
46929
  if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
46875
46930
  return cssPropertiesToCss({
46876
- opacity: this.dnd.overlappingCarousel?.id ? "0.6" : "0.9",
46931
+ opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
46877
46932
  cursor: "grabbing"
46878
46933
  });
46879
46934
  }
46880
46935
  getFigureClass(figureUI) {
46881
- if (figureUI.id !== this.dnd.overlappingCarousel?.id) return "";
46936
+ if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
46882
46937
  return "o-add-to-carousel";
46883
46938
  }
46884
46939
  getSnap(snapLine) {
@@ -46921,18 +46976,18 @@ var FiguresContainer = class extends Component {
46921
46976
  height: `100%`
46922
46977
  });
46923
46978
  }
46924
- getCarouselOverlappingChart(figureUI, otherFigures) {
46979
+ getOverlappingFigure(figureUI, otherFigures) {
46925
46980
  if (figureUI.tag !== "chart") return;
46926
46981
  const figureCenterX = figureUI.x + figureUI.width / 2;
46927
46982
  const figureCenterY = figureUI.y + figureUI.height / 2;
46928
46983
  let bestMatch;
46929
46984
  let smallestDistance = Infinity;
46930
46985
  for (const figure of otherFigures) {
46931
- if (figure.tag !== "carousel") continue;
46932
- const carouselCenterX = figure.x + figure.width / 2;
46933
- const carouselCenterY = figure.y + figure.height / 2;
46934
- const distanceX = Math.abs(figureCenterX - carouselCenterX);
46935
- const distanceY = Math.abs(figureCenterY - carouselCenterY);
46986
+ if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
46987
+ const targetCenterX = figure.x + figure.width / 2;
46988
+ const targetCenterY = figure.y + figure.height / 2;
46989
+ const distanceX = Math.abs(figureCenterX - targetCenterX);
46990
+ const distanceY = Math.abs(figureCenterY - targetCenterY);
46936
46991
  const squaredDistance = distanceX ** 2 + distanceY ** 2;
46937
46992
  if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
46938
46993
  smallestDistance = squaredDistance;
@@ -58719,13 +58774,13 @@ var EvaluationConditionalFormatPlugin = class extends CoreViewPlugin {
58719
58774
  const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
58720
58775
  const midValue = rule.midpoint ? this.parsePoint(sheetId, range, rule.midpoint) : null;
58721
58776
  const maxValue = this.parsePoint(sheetId, range, rule.maximum, "max");
58722
- if (minValue === null || maxValue === null || minValue >= maxValue || midValue && (minValue >= midValue || midValue >= maxValue)) return;
58777
+ if (minValue === null || maxValue === null || minValue >= maxValue || midValue !== null && (minValue >= midValue || midValue >= maxValue)) return;
58723
58778
  const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
58724
58779
  const colorThresholds = [{
58725
58780
  value: minValue,
58726
58781
  color: rule.minimum.color
58727
58782
  }];
58728
- if (rule.midpoint && midValue) colorThresholds.push({
58783
+ if (rule.midpoint && midValue !== null) colorThresholds.push({
58729
58784
  value: midValue,
58730
58785
  color: rule.midpoint.color
58731
58786
  });
@@ -66184,7 +66239,9 @@ var GridSelectionPlugin = class extends UIPlugin {
66184
66239
  ctx.lineWidth = 1.5 * thinLineWidth;
66185
66240
  const isDarkMode = this.getters.isDarkMode();
66186
66241
  for (const zone of zones) {
66242
+ if (!viewports.isZoneVisibleInViewport(sheetId, zone)) continue;
66187
66243
  const { x, y, width, height } = viewports.getVisibleRect(sheetId, zone);
66244
+ const currentLineWidth = ctx.lineWidth;
66188
66245
  if (!isDarkMode) ctx.globalCompositeOperation = "multiply";
66189
66246
  if (height === 0 || width === 0) ctx.lineWidth = 3 * thinLineWidth;
66190
66247
  if (height === 0 && width === 0) {
@@ -66198,6 +66255,7 @@ var GridSelectionPlugin = class extends UIPlugin {
66198
66255
  ctx.globalCompositeOperation = "source-over";
66199
66256
  ctx.strokeRect(x, y, width, height);
66200
66257
  }
66258
+ ctx.lineWidth = currentLineWidth;
66201
66259
  }
66202
66260
  ctx.globalCompositeOperation = "source-over";
66203
66261
  const position = renderingContext.activePosition;
@@ -66344,6 +66402,9 @@ var InternalViewport = class {
66344
66402
  this.adjustViewportZoneX();
66345
66403
  this.adjustViewportZoneY();
66346
66404
  }
66405
+ isZoneVisible(zone) {
66406
+ return intersection(zone, this) !== void 0;
66407
+ }
66347
66408
  /**
66348
66409
  *
66349
66410
  * Computes the visible coordinates & dimensions of a given zone inside the viewport
@@ -66624,6 +66685,9 @@ var ViewportCollection = class {
66624
66685
  isVisibleInViewport({ sheetId, col, row }) {
66625
66686
  return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
66626
66687
  }
66688
+ isZoneVisibleInViewport(sheetId, zone) {
66689
+ return this.getSubViewports(sheetId).some((pane) => pane.isZoneVisible(zone));
66690
+ }
66627
66691
  getScrollBarWidth() {
66628
66692
  return 15 / this.zoomLevel;
66629
66693
  }
@@ -70697,18 +70761,28 @@ var NamedRangeSelector = class extends Component {
70697
70761
  return;
70698
70762
  }
70699
70763
  const activeSheetId = this.env.model.getters.getActiveSheetId();
70700
- if (activeSheetId !== sheetId) this.env.model.dispatch("ACTIVATE_SHEET", {
70701
- sheetIdFrom: activeSheetId,
70702
- sheetIdTo: sheetId
70703
- });
70704
- this.env.model.selection.selectCell(zone.right, zone.bottom);
70764
+ if (activeSheetId !== sheetId) {
70765
+ if (!this.env.model.getters.getSheet(sheetId).isVisible) {
70766
+ this.env.notifyUser({
70767
+ text: _t("The sheet on which the range is defined is hidden."),
70768
+ type: "info",
70769
+ sticky: false
70770
+ });
70771
+ return;
70772
+ }
70773
+ this.env.model.dispatch("ACTIVATE_SHEET", {
70774
+ sheetIdFrom: activeSheetId,
70775
+ sheetIdTo: sheetId
70776
+ });
70777
+ }
70778
+ this.env.model.selection.selectCell(zone.right, zone.bottom, { allowsHiddenSelection: true });
70705
70779
  this.env.model.selection.selectZone({
70706
70780
  cell: {
70707
70781
  col: zone.left,
70708
70782
  row: zone.top
70709
70783
  },
70710
70784
  zone
70711
- });
70785
+ }, { allowsHiddenSelection: true });
70712
70786
  }
70713
70787
  get selectionKey() {
70714
70788
  return `${this.env.model.getters.getActiveSheetId()}-${zoneToXc(this.selectedZone)}`;
@@ -87333,7 +87407,9 @@ const helpers = {
87333
87407
  collapseHierarchicalDisplayName,
87334
87408
  getCanonicalSymbolName,
87335
87409
  fuzzyLookup,
87336
- replaceSymbolInFormula
87410
+ replaceSymbolInFormula,
87411
+ isSingleCellReference,
87412
+ computeCachedTextDimension
87337
87413
  };
87338
87414
  const links = {
87339
87415
  isMarkdownLink,
@@ -87403,7 +87479,8 @@ const components = {
87403
87479
  FullScreenFigure,
87404
87480
  NumberInput,
87405
87481
  TopBar,
87406
- Composer
87482
+ Composer,
87483
+ CalendarButton
87407
87484
  };
87408
87485
  const hooks = {
87409
87486
  useDragAndDropListItems,
@@ -87463,6 +87540,6 @@ const chartHelpers = {
87463
87540
  //#endregion
87464
87541
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, BadExpressionError, CHART_TYPES, CellErrorType, CellValueType, CircularDependencyError, ClientDisconnectedError, ClipboardMIMEType, CommandResult, CompiledFormula, CorePlugin, CoreViewPlugin, DEFAULT_LOCALE, DEFAULT_LOCALES, DEFAULT_LOCALE_DIGIT_GROUPING, DIRECTION, DispatchResult, DivisionByZeroError, EvaluationError, InvalidReferenceError, LocalTransportService, Model, NEXT_VALUE, NotAvailableError, NumberTooLargeError, OrderedLayers, PREVIOUS_VALUE, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, SplillBlockedError, Spreadsheet, SpreadsheetPivotTable, UIPlugin, UnknownFunctionError, __info__, addFunction, addRenderingLayer, astToFormula, availableConditionalFormatOperators, availableDataValidationOperators, availableFiltersOperators, borderPositions, borderStyles, canExecuteInReadonly, categories, chartHelpers, compatibility, components, composerFocusTypes, constants, convertAstNodes, coreTypes, createAutocompleteArgumentsProvider, errorTypes, filterDateCriterionOperators, filterNumberCriterionOperators, filterTextCriterionOperators, findCellInNewZone, functionCache, getCaretDownSvg, getCaretUpSvg, helpers, hooks, invalidSubtotalFormulasCommands, invalidateBordersCommands, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, isCoreCommand, isHeadersDependant, isMatrix, isPositionDependent, isRangeDependant, isSheetDependent, isTargetDependent, isZoneDependent, iterateAstNodes, links, load, lockedSheetAllowedCommands, parse, parseTokens, readonlyAllowedCommands, registries, schemeToColorScale, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
87465
87542
 
87466
- __info__.version = "19.5.0-alpha.0";
87467
- __info__.date = "2026-06-29T09:01:26.331Z";
87468
- __info__.hash = "2437c0b";
87543
+ __info__.version = "19.5.0-alpha.2";
87544
+ __info__.date = "2026-07-13T06:26:20.354Z";
87545
+ __info__.hash = "a4d2f90";