@odoo/o-spreadsheet 19.5.0-alpha.1 → 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.1
6
- * @date 2026-07-01T05:05:42.239Z
7
- * @hash 3c78107
5
+ * @version 19.5.0-alpha.2
6
+ * @date 2026-07-13T06:26:20.354Z
7
+ * @hash a4d2f90
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 = {
@@ -2600,7 +2601,7 @@ function unregisterChartJsExtensions() {
2600
2601
  *
2601
2602
  * The end goal is to eliminate all compatibility shims.
2602
2603
  */
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;
2604
+ 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
2605
  const isOdooCompatLoaded = __ODOO_COMPATIBILITY_LAYER_ADDED__ === true;
2605
2606
  var _Component = class extends OwlComponent {
2606
2607
  static template = "";
@@ -2730,7 +2731,7 @@ var Portal = class extends OwlComponent {
2730
2731
  static template = xml$4`<t t-call-slot="default"/>`;
2731
2732
  constructor(node) {
2732
2733
  super(node);
2733
- this.props = props$166();
2734
+ this.props = props$167();
2734
2735
  this.__owl__ = node;
2735
2736
  }
2736
2737
  setup() {
@@ -8406,534 +8407,6 @@ function addOrigin(cell, origin) {
8406
8407
  return cell;
8407
8408
  }
8408
8409
 
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
8410
  //#endregion
8938
8411
  //#region src/helpers/color.ts
8939
8412
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -9498,142 +8971,675 @@ const ALTERNATING_COLORS_XL = [
9498
8971
  function getNthColor(index, palette) {
9499
8972
  return palette[index % palette.length];
9500
8973
  }
9501
- function getColorsPalette(quantity) {
9502
- if (quantity <= 6) return COLORS_SM;
9503
- else if (quantity <= 12) return COLORS_MD;
9504
- else if (quantity <= 24) return COLORS_LG;
9505
- else return COLORS_XL;
8974
+ function getColorsPalette(quantity) {
8975
+ if (quantity <= 6) return COLORS_SM;
8976
+ else if (quantity <= 12) return COLORS_MD;
8977
+ else if (quantity <= 24) return COLORS_LG;
8978
+ else return COLORS_XL;
8979
+ }
8980
+ function getAlternatingColorsPalette(quantity) {
8981
+ if (quantity <= 6) return COLORS_SM;
8982
+ else if (quantity <= 12) return ALTERNATING_COLORS_MD;
8983
+ else if (quantity <= 24) return ALTERNATING_COLORS_LG;
8984
+ else return ALTERNATING_COLORS_XL;
8985
+ }
8986
+ var ColorGenerator = class {
8987
+ preferredColors;
8988
+ currentColorIndex = 0;
8989
+ palette;
8990
+ constructor(paletteSize, preferredColors = []) {
8991
+ this.preferredColors = preferredColors;
8992
+ this.palette = getColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
8993
+ }
8994
+ next() {
8995
+ return this.preferredColors?.[this.currentColorIndex] ? this.preferredColors[this.currentColorIndex++] : getNthColor(this.currentColorIndex++, this.palette);
8996
+ }
8997
+ };
8998
+ var AlternatingColorGenerator = class extends ColorGenerator {
8999
+ constructor(paletteSize, preferredColors = []) {
9000
+ super(paletteSize, preferredColors);
9001
+ this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
9002
+ }
9003
+ };
9004
+ var AlternatingColorMap = class {
9005
+ availableColors;
9006
+ colors = {};
9007
+ constructor(paletteSize = 12) {
9008
+ this.availableColors = new AlternatingColorGenerator(paletteSize);
9009
+ }
9010
+ get(id) {
9011
+ if (!this.colors[id]) this.colors[id] = this.availableColors.next();
9012
+ return this.colors[id];
9013
+ }
9014
+ };
9015
+ const COLORSCHEMES = {
9016
+ greys: [
9017
+ "#ffffff",
9018
+ "#808080",
9019
+ "#000000"
9020
+ ],
9021
+ blues: [
9022
+ "#f7fbff",
9023
+ "#6aaed6",
9024
+ "#08306b"
9025
+ ],
9026
+ reds: [
9027
+ "#fff5f0",
9028
+ "#fb694a",
9029
+ "#67000d"
9030
+ ],
9031
+ greens: [
9032
+ "#f7fcf5",
9033
+ "#73c476",
9034
+ "#00441b"
9035
+ ],
9036
+ oranges: [
9037
+ "#fff5eb",
9038
+ "#fd8c3b",
9039
+ "#7f2704"
9040
+ ],
9041
+ purples: [
9042
+ "#fcfbfd",
9043
+ "#9e9ac8",
9044
+ "#3f007d"
9045
+ ],
9046
+ viridis: [
9047
+ "#440154",
9048
+ "#21918c",
9049
+ "#fde725"
9050
+ ],
9051
+ cividis: [
9052
+ "#00224e",
9053
+ "#7d7c78",
9054
+ "#fee838"
9055
+ ],
9056
+ rainbow: [
9057
+ "#B41DB4",
9058
+ "#FFFF00",
9059
+ "#00FFFF"
9060
+ ]
9061
+ };
9062
+ const COLORSCALES = Object.keys(COLORSCHEMES);
9063
+ /**
9064
+ * Returns a function that maps a value to a color using a color scale defined by the given
9065
+ * color/threshold values pairs.
9066
+ */
9067
+ function getColorScale(colorScalePoints) {
9068
+ if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
9069
+ const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
9070
+ const thresholds = [];
9071
+ for (let i = 1; i < sortedColorScalePoints.length; i++) {
9072
+ const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
9073
+ const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
9074
+ const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
9075
+ const maxColor = colorToNumber(sortedColorScalePoints[i].color);
9076
+ thresholds.push({
9077
+ min: sortedColorScalePoints[i - 1].value,
9078
+ max: sortedColorScalePoints[i].value,
9079
+ minColor,
9080
+ maxColor,
9081
+ minColorAlpha,
9082
+ maxColorAlpha,
9083
+ colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
9084
+ });
9085
+ }
9086
+ return (value) => {
9087
+ if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
9088
+ for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
9089
+ return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
9090
+ };
9091
+ }
9092
+ function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
9093
+ const deltaValue = maxValue - minValue;
9094
+ const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
9095
+ const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
9096
+ const deltaColorB = minColor % 256 - maxColor % 256;
9097
+ return [
9098
+ deltaColorR / deltaValue,
9099
+ deltaColorG / deltaValue,
9100
+ deltaColorB / deltaValue
9101
+ ];
9102
+ }
9103
+ function colorCell(value, minValue, minColor, colorDiffUnit) {
9104
+ const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
9105
+ const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
9106
+ const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
9107
+ const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
9108
+ return r << 16 | g << 8 | b;
9109
+ }
9110
+
9111
+ //#endregion
9112
+ //#region src/helpers/text_helper.ts
9113
+ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
9114
+ return numberOfLines * (textLineHeight + 4) - 4;
9115
+ }
9116
+ function getCanvas(width = 100, height = 100) {
9117
+ return new OffscreenCanvas(width, height).getContext("2d");
9118
+ }
9119
+ /**
9120
+ * Get the default height of the cell given its style.
9121
+ */
9122
+ function getDefaultCellHeight(ctx, cell, locale, colSize) {
9123
+ if (!cell || !cell.isFormula && !cell.content) return 23;
9124
+ let content = "";
9125
+ try {
9126
+ if (!cell.isFormula) {
9127
+ const localeFormat = {
9128
+ format: cell.format,
9129
+ locale
9130
+ };
9131
+ content = formatValue(parseLiteral(cell.content, locale), localeFormat);
9132
+ }
9133
+ } catch {
9134
+ content = CellErrorType.GenericError;
9135
+ }
9136
+ return getCellContentHeight(ctx, content, cell.style, colSize);
9137
+ }
9138
+ function getCellContentHeight(ctx, content, style, colSize) {
9139
+ return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
9140
+ }
9141
+ function getDefaultContextFont(fontSize, bold = false, italic = false) {
9142
+ return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
9143
+ }
9144
+ function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
9145
+ if (!textLines.length) return {
9146
+ width: 0,
9147
+ height: 0
9148
+ };
9149
+ const font = computeTextFont(style, fontUnit);
9150
+ const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
9151
+ const height = computeTextLinesHeight(sizes[0].height, textLines.length);
9152
+ const width = Math.max(...sizes.map((size) => size.width));
9153
+ if (!style.rotation) return {
9154
+ height,
9155
+ width
9156
+ };
9157
+ const cos = Math.abs(Math.cos(style.rotation));
9158
+ const sin = Math.abs(Math.sin(style.rotation));
9159
+ return {
9160
+ width: width * cos + height * sin,
9161
+ height: sin * width + cos * height
9162
+ };
9163
+ }
9164
+ function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
9165
+ return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
9166
+ }
9167
+ function computeCachedTextWidth(context, text, font, rotation) {
9168
+ const size = computeCachedTextDimension(context, text, font);
9169
+ if (!rotation) return size.width;
9170
+ const cos = Math.abs(Math.cos(rotation));
9171
+ const sin = Math.abs(Math.sin(rotation));
9172
+ return size.width * cos + size.height * sin;
9173
+ }
9174
+ const textDimensionsCache = {};
9175
+ function computeTextDimension(context, text, style, fontUnit = "pt") {
9176
+ const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
9177
+ if (!style.rotation) return size;
9178
+ const cos = Math.abs(Math.cos(style.rotation));
9179
+ const sin = Math.abs(Math.sin(style.rotation));
9180
+ return {
9181
+ width: size.width * cos + size.height * sin,
9182
+ height: size.height * cos + size.width * sin
9183
+ };
9184
+ }
9185
+ function computeCachedTextDimension(context, text, font) {
9186
+ if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
9187
+ if (textDimensionsCache[font][text] === void 0) {
9188
+ context.save();
9189
+ context.font = font;
9190
+ const measure = context.measureText(text);
9191
+ context.restore();
9192
+ const width = measure.width;
9193
+ const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
9194
+ textDimensionsCache[font][text] = {
9195
+ width,
9196
+ height
9197
+ };
9198
+ }
9199
+ return textDimensionsCache[font][text];
9200
+ }
9201
+ function fontSizeInPixels(fontSize) {
9202
+ return Math.round(fontSize * 96 / 72);
9203
+ }
9204
+ function computeTextFont(style, fontUnit = "pt") {
9205
+ return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
9206
+ }
9207
+ function computeTextFontSizeInPixels(style) {
9208
+ return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
9209
+ }
9210
+ function splitWordToSpecificWidth(ctx, word, width, style) {
9211
+ if (computeTextWidth(ctx, word, style) <= width) return [word];
9212
+ const splitWord = [];
9213
+ let wordPart = "";
9214
+ for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
9215
+ splitWord.push(wordPart);
9216
+ wordPart = l;
9217
+ } else wordPart += l;
9218
+ splitWord.push(wordPart);
9219
+ return splitWord;
9220
+ }
9221
+ /**
9222
+ * Return the given text, split in multiple lines if needed. The text will be split in multiple
9223
+ * line if it contains NEWLINE characters, or if it's longer than the given width.
9224
+ */
9225
+ function splitTextToWidth(ctx, text, style, width) {
9226
+ if (!style) style = {};
9227
+ if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
9228
+ const brokenText = [];
9229
+ const lines = text.includes("\n") ? text.split("\n") : [text];
9230
+ for (const line of lines) {
9231
+ const words = line.includes(" ") ? line.split(" ") : [line];
9232
+ if (!width) {
9233
+ brokenText.push(line);
9234
+ continue;
9235
+ }
9236
+ let textLine = "";
9237
+ let availableWidth = width;
9238
+ for (const word of words) {
9239
+ const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
9240
+ const lastPart = splitWord.pop();
9241
+ const lastPartWidth = computeTextWidth(ctx, lastPart, style);
9242
+ if (splitWord.length) {
9243
+ if (textLine !== "") {
9244
+ brokenText.push(textLine);
9245
+ textLine = "";
9246
+ availableWidth = width;
9247
+ }
9248
+ splitWord.forEach((wordPart) => {
9249
+ brokenText.push(wordPart);
9250
+ });
9251
+ textLine = lastPart;
9252
+ availableWidth = width - lastPartWidth;
9253
+ } else {
9254
+ const _word = textLine === "" ? lastPart : " " + lastPart;
9255
+ const wordWidth = computeTextWidth(ctx, _word, style);
9256
+ if (wordWidth <= availableWidth) {
9257
+ textLine += _word;
9258
+ availableWidth -= wordWidth;
9259
+ } else {
9260
+ brokenText.push(textLine);
9261
+ textLine = lastPart;
9262
+ availableWidth = width - lastPartWidth;
9263
+ }
9264
+ }
9265
+ }
9266
+ if (textLine !== "") brokenText.push(textLine);
9267
+ }
9268
+ return brokenText;
9269
+ }
9270
+ /**
9271
+ * Return the font size that makes the width of a text match the given line width.
9272
+ * Minimum font size is 1.
9273
+ *
9274
+ * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
9275
+ */
9276
+ function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
9277
+ let minFontSize = 1;
9278
+ if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
9279
+ if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
9280
+ let fontSize = (minFontSize + maxFontSize) / 2;
9281
+ let currentTextWidth = getTextWidth(fontSize);
9282
+ let iterations = 0;
9283
+ while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
9284
+ if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
9285
+ else minFontSize = (minFontSize + maxFontSize) / 2;
9286
+ fontSize = (minFontSize + maxFontSize) / 2;
9287
+ currentTextWidth = getTextWidth(fontSize);
9288
+ iterations++;
9289
+ }
9290
+ return fontSize;
9291
+ }
9292
+ /** Transform a string to lowercase and removes whitespace from both ends of the string*/
9293
+ function toTrimmedLowerCase(str) {
9294
+ return str ? str.toLowerCase().trim() : "";
9506
9295
  }
9507
- function getAlternatingColorsPalette(quantity) {
9508
- if (quantity <= 6) return COLORS_SM;
9509
- else if (quantity <= 12) return ALTERNATING_COLORS_MD;
9510
- else if (quantity <= 24) return ALTERNATING_COLORS_LG;
9511
- else return ALTERNATING_COLORS_XL;
9296
+ /**
9297
+ * Extract the fontSize from a context font string
9298
+ * @param font The (context) font string to parse
9299
+ * @returns The fontSize in pixels
9300
+ */
9301
+ const pxRegex = /([0-9\.]*)px/;
9302
+ function getContextFontSize(font) {
9303
+ return Number(font.match(pxRegex)?.[1]);
9512
9304
  }
9513
- var ColorGenerator = class {
9514
- preferredColors;
9515
- currentColorIndex = 0;
9516
- palette;
9517
- constructor(paletteSize, preferredColors = []) {
9518
- this.preferredColors = preferredColors;
9519
- this.palette = getColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
9305
+ function clipTextWithEllipsis(ctx, text, maxWidth) {
9306
+ let width = computeCachedTextWidth(ctx, text, ctx.font);
9307
+ if (width <= maxWidth) return text;
9308
+ const ellipsis = "…";
9309
+ const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
9310
+ if (width <= ellipsisWidth) return text;
9311
+ let len = text.length;
9312
+ while (width >= maxWidth - ellipsisWidth && len-- > 0) {
9313
+ text = text.substring(0, len);
9314
+ width = computeCachedTextWidth(ctx, text, ctx.font);
9520
9315
  }
9521
- next() {
9522
- return this.preferredColors?.[this.currentColorIndex] ? this.preferredColors[this.currentColorIndex++] : getNthColor(this.currentColorIndex++, this.palette);
9316
+ return text + ellipsis;
9317
+ }
9318
+ function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10, highlightText = false) {
9319
+ if (highlightText) {
9320
+ context.save();
9321
+ context.fillStyle = lightenColor(context.fillStyle, DEFAULT_TEXT_HIGHLIGHT_PERCENT);
9523
9322
  }
9524
- };
9525
- var AlternatingColorGenerator = class extends ColorGenerator {
9526
- constructor(paletteSize, preferredColors = []) {
9527
- super(paletteSize, preferredColors);
9528
- this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
9323
+ context.fillText(text, position.x, position.y);
9324
+ if (!underline && !strikethrough) return;
9325
+ const measure = context.measureText(text);
9326
+ const textWidth = measure.width;
9327
+ const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
9328
+ const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
9329
+ let { x, y } = position;
9330
+ let strikeY = y, underlineY = y;
9331
+ switch (context.textAlign) {
9332
+ case "center":
9333
+ x -= textWidth / 2;
9334
+ break;
9335
+ case "right":
9336
+ x -= textWidth;
9337
+ break;
9529
9338
  }
9530
- };
9531
- var AlternatingColorMap = class {
9532
- availableColors;
9533
- colors = {};
9534
- constructor(paletteSize = 12) {
9535
- this.availableColors = new AlternatingColorGenerator(paletteSize);
9339
+ switch (context.textBaseline) {
9340
+ case "top":
9341
+ underlineY += boxHeight - 2 * strokeWidth;
9342
+ strikeY += boxHeight / 2 - strokeWidth;
9343
+ break;
9344
+ case "middle":
9345
+ underlineY += boxHeight / 2 - strokeWidth;
9346
+ break;
9347
+ case "alphabetic":
9348
+ underlineY += 2 * strokeWidth;
9349
+ strikeY -= 3 * strokeWidth;
9350
+ break;
9351
+ case "bottom":
9352
+ underlineY = y;
9353
+ strikeY -= textHeight / 2 - strokeWidth / 2;
9354
+ break;
9536
9355
  }
9537
- get(id) {
9538
- if (!this.colors[id]) this.colors[id] = this.availableColors.next();
9539
- return this.colors[id];
9356
+ if (underline) {
9357
+ context.lineWidth = strokeWidth;
9358
+ context.strokeStyle = context.fillStyle;
9359
+ context.beginPath();
9360
+ context.moveTo(x, underlineY);
9361
+ context.lineTo(x + textWidth, underlineY);
9362
+ context.stroke();
9540
9363
  }
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);
9364
+ if (strikethrough) {
9365
+ context.lineWidth = strokeWidth;
9366
+ context.strokeStyle = context.fillStyle;
9367
+ context.beginPath();
9368
+ context.moveTo(x, strikeY);
9369
+ context.lineTo(x + textWidth, strikeY);
9370
+ context.stroke();
9371
+ }
9372
+ if (highlightText) context.restore();
9373
+ }
9374
+ function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
9375
+ if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
9376
+ const ellipsis = "...";
9377
+ const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
9378
+ if (ellipsisWidth >= width) return "";
9379
+ let lowerBoundLen = 1;
9380
+ let upperBoundLen = text.length;
9381
+ let currentWidth;
9382
+ while (lowerBoundLen <= upperBoundLen) {
9383
+ const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
9384
+ currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
9385
+ if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
9386
+ else lowerBoundLen = currentLen + 1;
9387
+ }
9388
+ const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
9389
+ return slicedText ? slicedText + ellipsis : "";
9390
+ }
9590
9391
  /**
9591
- * Returns a function that maps a value to a color using a color scale defined by the given
9592
- * color/threshold values pairs.
9392
+ * Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
9393
+ * with to original's text vertical and horizontal alignment.
9593
9394
  */
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)
9395
+ function computeRotationPosition(rect, style) {
9396
+ if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
9397
+ let { x, y } = rect;
9398
+ const cos = Math.cos(-style.rotation);
9399
+ const sin = Math.sin(-style.rotation);
9400
+ const width = rect.textWidth - 2 * 4;
9401
+ const height = rect.textHeight;
9402
+ const center = style.align === "center";
9403
+ const rotateTowardCellCenter = style.align === "left" === sin < 0;
9404
+ const sh = sin * height;
9405
+ const sw = Math.abs(sin * width);
9406
+ const ch = cos * height;
9407
+ if (style.verticalAlign === "top") if (center) {
9408
+ y += sw / 2;
9409
+ x -= sh / 2;
9410
+ } else if (rotateTowardCellCenter) x -= sh;
9411
+ else y += sw;
9412
+ else if (!style.verticalAlign || style.verticalAlign === "bottom") {
9413
+ y += height - ch;
9414
+ if (center) {
9415
+ y -= sw / 2;
9416
+ x -= sh / 2;
9417
+ } else if (rotateTowardCellCenter) {
9418
+ x -= sh;
9419
+ y -= sw;
9420
+ }
9421
+ } else if (center) {
9422
+ x -= sh / 2;
9423
+ y -= height / 2;
9424
+ if (rotateTowardCellCenter) y += sh;
9425
+ else y -= sh;
9426
+ } else if (rotateTowardCellCenter) {
9427
+ x -= sh;
9428
+ y -= sw / 2;
9429
+ } else y += sw / 2 + ch / 4;
9430
+ return {
9431
+ x: cos * x - sin * y,
9432
+ y: cos * y + sin * x
9433
+ };
9434
+ }
9435
+
9436
+ //#endregion
9437
+ //#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
9438
+ /** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
9439
+ const chartColorScalePlugin = {
9440
+ id: "chartColorScalePlugin",
9441
+ afterDatasetsDraw(chart, args, options) {
9442
+ if (!options.position || options.position === "none" || !options.colorScale.length) return;
9443
+ const ctx = chart.ctx;
9444
+ ctx.save();
9445
+ ctx.textAlign = "center";
9446
+ ctx.textBaseline = "middle";
9447
+ ctx.miterLimit = 1;
9448
+ const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
9449
+ const gradientWidth = 10;
9450
+ const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
9451
+ const gradientY = chart.chartArea.top;
9452
+ const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
9453
+ const step = 1 / (options.colorScale.length - 1);
9454
+ options.colorScale.forEach((color, index) => {
9455
+ gradient.addColorStop(index * step, color);
9611
9456
  });
9457
+ ctx.fillStyle = gradient;
9458
+ ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
9459
+ ctx.fillStyle = options.fontColor ?? "black";
9460
+ ctx.font = getDefaultContextFont(12);
9461
+ ctx.textAlign = "left";
9462
+ let minValue = Math.round(options.minValue * 100) / 100;
9463
+ let maxValue = Math.round(options.maxValue * 100) / 100;
9464
+ if (options.minValue === options.maxValue) {
9465
+ minValue -= 1;
9466
+ maxValue += 1;
9467
+ }
9468
+ const formattedMaxValue = humanizeNumber({
9469
+ value: maxValue,
9470
+ format: void 0
9471
+ }, options.locale);
9472
+ const formattedMinValue = humanizeNumber({
9473
+ value: minValue,
9474
+ format: void 0
9475
+ }, options.locale);
9476
+ ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
9477
+ ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
9478
+ ctx.restore();
9612
9479
  }
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);
9480
+ };
9481
+
9482
+ //#endregion
9483
+ //#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
9484
+ function getFunnelChartController() {
9485
+ if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
9486
+ return class FunnelChartController extends globalThis.Chart.BarController {
9487
+ static id = "funnel";
9488
+ static defaults = {
9489
+ ...globalThis.Chart?.BarController.defaults,
9490
+ dataElementType: "funnel",
9491
+ animation: { duration: (ctx) => {
9492
+ if (ctx.type !== "data") return 1e3;
9493
+ return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
9494
+ } }
9495
+ };
9496
+ /** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
9497
+ updateElements(rects, start, count, mode) {
9498
+ super.updateElements(rects, start, count, mode);
9499
+ for (let i = start; i < start + count; i++) {
9500
+ const rect = rects[i];
9501
+ this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
9502
+ }
9503
+ }
9617
9504
  };
9618
9505
  }
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
- ];
9506
+ function getFunnelChartElement() {
9507
+ if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
9508
+ /**
9509
+ * Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
9510
+ * `width`, and the bottom is of width `nextElementWidth`.
9511
+ */
9512
+ return class FunnelChartElement extends globalThis.Chart.BarElement {
9513
+ static id = "funnel";
9514
+ /** Overwrite this to draw a trapezoid rather then a rectangle */
9515
+ draw(ctx) {
9516
+ ctx.save();
9517
+ const { x, y, height, nextElement, base, options } = this.getProps([
9518
+ "x",
9519
+ "y",
9520
+ "width",
9521
+ "height",
9522
+ "nextElement",
9523
+ "base",
9524
+ "options"
9525
+ ]);
9526
+ const width = getElementWidth(this);
9527
+ const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
9528
+ const startX = Math.min(x, base);
9529
+ const startY = y - height / 2;
9530
+ ctx.fillStyle = options.backgroundColor;
9531
+ ctx.beginPath();
9532
+ ctx.moveTo(startX, startY);
9533
+ ctx.lineTo(startX + width, startY);
9534
+ ctx.lineTo(startX + width - offset, startY + height);
9535
+ ctx.lineTo(startX + offset, startY + height);
9536
+ ctx.closePath();
9537
+ ctx.fill();
9538
+ if (options.borderWidth) {
9539
+ ctx.strokeStyle = options.borderColor;
9540
+ ctx.lineWidth = options.borderWidth;
9541
+ ctx.stroke();
9542
+ }
9543
+ ctx.restore();
9544
+ }
9545
+ /** Check if the mouse is inside the trapezoid */
9546
+ inRange(mouseX, mouseY) {
9547
+ const { x, y, height, nextElement, base } = this.getProps([
9548
+ "x",
9549
+ "y",
9550
+ "width",
9551
+ "height",
9552
+ "nextElement",
9553
+ "base",
9554
+ "options"
9555
+ ]);
9556
+ const width = getElementWidth(this);
9557
+ const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
9558
+ const startX = Math.min(x, base);
9559
+ const startY = y - height / 2;
9560
+ if (mouseY < startY || mouseY > startY + height) return false;
9561
+ const offset = (width - nextElementWidth) / 2;
9562
+ const left = startX + offset * (mouseY - startY) / height;
9563
+ const right = startX + width - offset * (mouseY - startY) / height;
9564
+ if (mouseX < left || mouseX > right) return false;
9565
+ return true;
9566
+ }
9567
+ };
9629
9568
  }
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;
9569
+ /**
9570
+ * Get an element width.
9571
+ *
9572
+ * The property width is undefined during animations, we need to compute it manually.
9573
+ */
9574
+ function getElementWidth(element) {
9575
+ const { x, base } = element.getProps(["x", "base"]);
9576
+ return Math.max(x, base) - Math.min(x, base);
9636
9577
  }
9578
+ /**
9579
+ * Position the tooltip inside the trapezoid.
9580
+ * The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
9581
+ */
9582
+ const funnelTooltipPositioner = function(elements) {
9583
+ if (!elements.length) return {
9584
+ x: 0,
9585
+ y: 0
9586
+ };
9587
+ const { x, y, base, width, height } = elements[0].element.getProps([
9588
+ "x",
9589
+ "y",
9590
+ "width",
9591
+ "height",
9592
+ "base"
9593
+ ]);
9594
+ const startX = Math.min(x, base);
9595
+ const startY = y - height / 2;
9596
+ return {
9597
+ x: startX + width * 2 / 3,
9598
+ y: startY + height / 2
9599
+ };
9600
+ };
9601
+
9602
+ //#endregion
9603
+ //#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
9604
+ const chartMinorGridPlugin = {
9605
+ id: "o-spreadsheet-minor-gridlines",
9606
+ beforeDatasetsDraw(chart) {
9607
+ const ctx = chart.ctx;
9608
+ const chartArea = chart.chartArea;
9609
+ if (!chartArea) return;
9610
+ for (const scaleId in chart.scales) {
9611
+ const scale = chart.scales[scaleId];
9612
+ const options = scale.options;
9613
+ const minor = options?.grid?.minor;
9614
+ if (!minor?.display) continue;
9615
+ const showMajorGrid = options?.grid?.display;
9616
+ const ticks = scale.ticks;
9617
+ if (!ticks || ticks.length < 2) continue;
9618
+ ctx.save();
9619
+ ctx.lineWidth = 1;
9620
+ ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
9621
+ for (let i = 0; i < ticks.length - 1; i++) {
9622
+ const start = scale.getPixelForTick(i);
9623
+ const end = scale.getPixelForTick(i + 1);
9624
+ if (!isFinite(start) || !isFinite(end)) continue;
9625
+ for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
9626
+ const ratio = j / 4;
9627
+ const position = Math.round(start + (end - start) * ratio) + .5;
9628
+ ctx.beginPath();
9629
+ if (scale.isHorizontal()) {
9630
+ ctx.moveTo(position, chartArea.top);
9631
+ ctx.lineTo(position, chartArea.bottom);
9632
+ } else {
9633
+ ctx.moveTo(chartArea.left, position);
9634
+ ctx.lineTo(chartArea.right, position);
9635
+ }
9636
+ ctx.stroke();
9637
+ }
9638
+ }
9639
+ ctx.restore();
9640
+ }
9641
+ }
9642
+ };
9637
9643
 
9638
9644
  //#endregion
9639
9645
  //#region src/xlsx/constants.ts
@@ -12073,11 +12079,11 @@ function drawScoreChart(structure, canvas, zoom = 1) {
12073
12079
  if (structure.baseline) {
12074
12080
  ctx.font = structure.baseline.style.font;
12075
12081
  ctx.fillStyle = structure.baseline.style.color;
12076
- drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
12082
+ drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough, void 0, structure.baseline.style.highlightText);
12077
12083
  }
12078
12084
  if (structure.baselineArrow && structure.baselineArrow.style.size > 0 && Path2DConstructor) {
12079
12085
  ctx.save();
12080
- ctx.fillStyle = structure.baselineArrow.style.color;
12086
+ ctx.fillStyle = structure.baselineArrow.style.highlight ? lightenColor(structure.baselineArrow.style.color, DEFAULT_TEXT_HIGHLIGHT_PERCENT) : structure.baselineArrow.style.color;
12081
12087
  ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
12082
12088
  const ratio = structure.baselineArrow.style.size / 10;
12083
12089
  ctx.scale(ratio, ratio);
@@ -12095,18 +12101,18 @@ function drawScoreChart(structure, canvas, zoom = 1) {
12095
12101
  const descr = structure.baselineDescr;
12096
12102
  ctx.font = descr.style.font;
12097
12103
  ctx.fillStyle = descr.style.color;
12098
- ctx.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
12104
+ drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.baseline?.style.highlightText);
12099
12105
  }
12100
12106
  if (structure.key) {
12101
12107
  ctx.font = structure.key.style.font;
12102
12108
  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);
12109
+ 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
12110
  }
12105
12111
  if (structure.keyDescr) {
12106
12112
  const descr = structure.keyDescr;
12107
12113
  ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
12108
12114
  ctx.fillStyle = descr.style.color;
12109
- ctx.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
12115
+ drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.key?.style.highlightText);
12110
12116
  }
12111
12117
  if (structure.progressBar) {
12112
12118
  ctx.fillStyle = structure.progressBar.style.backgroundColor;
@@ -12330,29 +12336,34 @@ var ScorecardChartConfigBuilder = class {
12330
12336
  color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
12331
12337
  font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
12332
12338
  strikethrough: this.runtime.keyValueStyle?.strikethrough,
12333
- underline: this.runtime.keyValueStyle?.underline
12339
+ underline: this.runtime.keyValueStyle?.underline,
12340
+ highlightText: this.runtime.keyHighlight
12334
12341
  },
12335
12342
  keyDescr: {
12336
12343
  color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
12337
12344
  font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
12338
12345
  strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
12339
- underline: this.runtime.keyValueDescrStyle?.underline
12346
+ underline: this.runtime.keyValueDescrStyle?.underline,
12347
+ highlightText: this.runtime.keyHighlight
12340
12348
  },
12341
12349
  baselineValue: {
12342
12350
  font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
12343
12351
  strikethrough: this.runtime.baselineStyle?.strikethrough,
12344
12352
  underline: this.runtime.baselineStyle?.underline,
12345
- color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
12353
+ color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
12354
+ highlightText: this.runtime.baselineHighlight
12346
12355
  },
12347
12356
  baselineDescr: {
12348
12357
  font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
12349
12358
  strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
12350
12359
  underline: this.runtime.baselineDescrStyle?.underline,
12351
- color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor
12360
+ color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
12361
+ highlightText: this.runtime.baselineHighlight
12352
12362
  },
12353
12363
  baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar ? void 0 : {
12354
12364
  size: this.keyValue ? .8 * baselineValueFontSize : 0,
12355
- color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
12365
+ color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
12366
+ highlight: this.runtime.baselineHighlight
12356
12367
  }
12357
12368
  };
12358
12369
  }
@@ -12448,11 +12459,14 @@ var ScorecardChart = class extends Component {
12448
12459
  });
12449
12460
  (0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
12450
12461
  }
12462
+ config(canvasRect, zoom) {
12463
+ return getScorecardConfiguration(getZoomedRect(1 / zoom, canvasRect), this.runtime);
12464
+ }
12451
12465
  createChart() {
12452
12466
  const canvas = this.canvas();
12453
12467
  if (!canvas) return;
12454
12468
  const zoom = this.env.model.getters.getViewportZoomLevel();
12455
- drawScoreChart(getScorecardConfiguration(getZoomedRect(1 / zoom, canvas.getBoundingClientRect()), this.runtime), canvas, zoom);
12469
+ drawScoreChart(this.config(canvas.getBoundingClientRect(), zoom), canvas, zoom);
12456
12470
  }
12457
12471
  };
12458
12472
 
@@ -28056,7 +28070,7 @@ function filterInvalidCalendarDataPoints(labels, datasets) {
28056
28070
  */
28057
28071
  function filterInvalidHierarchicalPoints(values, hierarchy) {
28058
28072
  const numberOfDataPoints = Math.max(values.length, ...hierarchy.map((dataset) => dataset.data?.length || 0));
28059
- const isEmpty = (value) => value === null || value === "";
28073
+ const isEmpty = (value) => value === void 0 || value === null || value === "";
28060
28074
  const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
28061
28075
  const groups = hierarchy.map((dataset) => dataset.data?.[dataPointIndex]);
28062
28076
  if (isEmpty(groups[0]?.value)) return false;
@@ -34537,6 +34551,35 @@ var CellIsRuleEditor = class extends Component {
34537
34551
  }
34538
34552
  };
34539
34553
 
34554
+ //#endregion
34555
+ //#region src/components/side_panel/criterion_form/calendar_button/calendar_button.ts
34556
+ var CalendarButton = class extends Component {
34557
+ static template = "o-spreadsheet-CalendarButton";
34558
+ props = (0, _odoo_owl.props)({
34559
+ value: types.string().optional(""),
34560
+ onChange: types.function()
34561
+ });
34562
+ datePickerRef = _odoo_owl.signal.ref(HTMLInputElement);
34563
+ openCalendar() {
34564
+ this.datePickerRef()?.showPicker();
34565
+ }
34566
+ formatDateForInput(value) {
34567
+ const dateValue = parseDateTime(value, DEFAULT_LOCALE);
34568
+ return dateValue ? formatValue(dateValue.value, {
34569
+ format: "yyyy-mm-dd",
34570
+ locale: DEFAULT_LOCALE
34571
+ }) : "";
34572
+ }
34573
+ onDateInputValueChanged(value) {
34574
+ const dateValue = parseDateTime(value, DEFAULT_LOCALE);
34575
+ const formattedValue = dateValue ? formatValue(dateValue.value, {
34576
+ format: DEFAULT_LOCALE.dateFormat,
34577
+ locale: DEFAULT_LOCALE
34578
+ }) : "";
34579
+ this.props.onChange(formattedValue);
34580
+ }
34581
+ };
34582
+
34540
34583
  //#endregion
34541
34584
  //#region src/components/side_panel/criterion_form/criterion_form.ts
34542
34585
  var CriterionForm = class extends Component {
@@ -35225,7 +35268,8 @@ var DateCriterionForm = class extends CriterionForm {
35225
35268
  static template = "o-spreadsheet-DataValidationDateCriterion";
35226
35269
  static components = {
35227
35270
  CriterionInput,
35228
- Select
35271
+ Select,
35272
+ CalendarButton
35229
35273
  };
35230
35274
  get currentDateValue() {
35231
35275
  return this.props.criterion.dateValue || "exactDate";
@@ -35251,7 +35295,10 @@ var DateCriterionForm = class extends CriterionForm {
35251
35295
  //#region src/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.ts
35252
35296
  var DoubleInputCriterionForm = class extends CriterionForm {
35253
35297
  static template = "o-spreadsheet-DoubleInputCriterionForm";
35254
- static components = { CriterionInput };
35298
+ static components = {
35299
+ CriterionInput,
35300
+ CalendarButton
35301
+ };
35255
35302
  onFirstValueChanged(value) {
35256
35303
  const values = this.props.criterion.values;
35257
35304
  this.updateCriterion({ values: [value, values[1] || ""] });
@@ -35260,6 +35307,9 @@ var DoubleInputCriterionForm = class extends CriterionForm {
35260
35307
  const values = this.props.criterion.values;
35261
35308
  this.updateCriterion({ values: [values[0] || "", value] });
35262
35309
  }
35310
+ get isDateType() {
35311
+ return ["dateIsBetween", "dateIsNotBetween"].includes(this.props.criterion.type);
35312
+ }
35263
35313
  };
35264
35314
 
35265
35315
  //#endregion
@@ -46606,7 +46656,7 @@ var FiguresContainer = class extends Component {
46606
46656
  horizontalSnap: void 0,
46607
46657
  verticalSnap: void 0,
46608
46658
  cancelDnd: void 0,
46609
- overlappingCarousel: void 0
46659
+ overlappingChartOrCarousel: void 0
46610
46660
  });
46611
46661
  setup() {
46612
46662
  (0, _odoo_owl.onMounted)(() => {
@@ -46622,7 +46672,7 @@ var FiguresContainer = class extends Component {
46622
46672
  this.dnd.selectedRect = void 0;
46623
46673
  this.dnd.horizontalSnap = void 0;
46624
46674
  this.dnd.verticalSnap = void 0;
46625
- this.dnd.overlappingCarousel = void 0;
46675
+ this.dnd.overlappingChartOrCarousel = void 0;
46626
46676
  this.dnd.cancelDnd = void 0;
46627
46677
  }
46628
46678
  });
@@ -46760,11 +46810,11 @@ var FiguresContainer = class extends Component {
46760
46810
  hasStartedDnd = true;
46761
46811
  const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
46762
46812
  const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
46763
- let overlappingCarousel = void 0;
46813
+ let overlappingChartOrCarousel = void 0;
46764
46814
  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) {
46815
+ if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
46816
+ this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
46817
+ if (!overlappingChartOrCarousel) {
46768
46818
  const snapReturn = snapForMove(getters, selectedFigures, otherFigures);
46769
46819
  this.dnd.selectedFigures = snapReturn.snappedFigures;
46770
46820
  this.dnd.selectedRect = this.getDndFigureRect();
@@ -46785,7 +46835,7 @@ var FiguresContainer = class extends Component {
46785
46835
  else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
46786
46836
  return;
46787
46837
  }
46788
- if (!this.dnd.overlappingCarousel) {
46838
+ if (!this.dnd.overlappingChartOrCarousel) {
46789
46839
  const payloads = this.dnd.selectedFigures?.map((f) => {
46790
46840
  return {
46791
46841
  sheetId,
@@ -46795,20 +46845,25 @@ var FiguresContainer = class extends Component {
46795
46845
  }) || [];
46796
46846
  this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
46797
46847
  } else {
46798
- const carouselFigureId = this.dnd.overlappingCarousel.id;
46848
+ const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
46799
46849
  const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
46800
- this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
46850
+ if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
46801
46851
  sheetId,
46802
- carouselFigureId,
46852
+ carouselFigureId: overlappingFigureId,
46803
46853
  chartFigureIds
46804
46854
  });
46855
+ else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
46856
+ sheetId,
46857
+ baseFigureId: overlappingFigureId,
46858
+ chartFigureIds: [overlappingFigureId, ...chartFigureIds]
46859
+ });
46805
46860
  }
46806
46861
  this.dnd.draggedFigure = void 0;
46807
46862
  this.dnd.selectedFigures = void 0;
46808
46863
  this.dnd.selectedRect = void 0;
46809
46864
  this.dnd.horizontalSnap = void 0;
46810
46865
  this.dnd.verticalSnap = void 0;
46811
- this.dnd.overlappingCarousel = void 0;
46866
+ this.dnd.overlappingChartOrCarousel = void 0;
46812
46867
  };
46813
46868
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
46814
46869
  }
@@ -46864,7 +46919,7 @@ var FiguresContainer = class extends Component {
46864
46919
  this.dnd.selectedRect = void 0;
46865
46920
  this.dnd.horizontalSnap = void 0;
46866
46921
  this.dnd.verticalSnap = void 0;
46867
- this.dnd.overlappingCarousel = void 0;
46922
+ this.dnd.overlappingChartOrCarousel = void 0;
46868
46923
  };
46869
46924
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
46870
46925
  }
@@ -46874,12 +46929,12 @@ var FiguresContainer = class extends Component {
46874
46929
  getFigureStyle(figureUI) {
46875
46930
  if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
46876
46931
  return cssPropertiesToCss({
46877
- opacity: this.dnd.overlappingCarousel?.id ? "0.6" : "0.9",
46932
+ opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
46878
46933
  cursor: "grabbing"
46879
46934
  });
46880
46935
  }
46881
46936
  getFigureClass(figureUI) {
46882
- if (figureUI.id !== this.dnd.overlappingCarousel?.id) return "";
46937
+ if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
46883
46938
  return "o-add-to-carousel";
46884
46939
  }
46885
46940
  getSnap(snapLine) {
@@ -46922,18 +46977,18 @@ var FiguresContainer = class extends Component {
46922
46977
  height: `100%`
46923
46978
  });
46924
46979
  }
46925
- getCarouselOverlappingChart(figureUI, otherFigures) {
46980
+ getOverlappingFigure(figureUI, otherFigures) {
46926
46981
  if (figureUI.tag !== "chart") return;
46927
46982
  const figureCenterX = figureUI.x + figureUI.width / 2;
46928
46983
  const figureCenterY = figureUI.y + figureUI.height / 2;
46929
46984
  let bestMatch;
46930
46985
  let smallestDistance = Infinity;
46931
46986
  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);
46987
+ if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
46988
+ const targetCenterX = figure.x + figure.width / 2;
46989
+ const targetCenterY = figure.y + figure.height / 2;
46990
+ const distanceX = Math.abs(figureCenterX - targetCenterX);
46991
+ const distanceY = Math.abs(figureCenterY - targetCenterY);
46937
46992
  const squaredDistance = distanceX ** 2 + distanceY ** 2;
46938
46993
  if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
46939
46994
  smallestDistance = squaredDistance;
@@ -58904,13 +58959,13 @@ var EvaluationConditionalFormatPlugin = class extends CoreViewPlugin {
58904
58959
  const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
58905
58960
  const midValue = rule.midpoint ? this.parsePoint(sheetId, range, rule.midpoint) : null;
58906
58961
  const maxValue = this.parsePoint(sheetId, range, rule.maximum, "max");
58907
- if (minValue === null || maxValue === null || minValue >= maxValue || midValue && (minValue >= midValue || midValue >= maxValue)) return;
58962
+ if (minValue === null || maxValue === null || minValue >= maxValue || midValue !== null && (minValue >= midValue || midValue >= maxValue)) return;
58908
58963
  const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
58909
58964
  const colorThresholds = [{
58910
58965
  value: minValue,
58911
58966
  color: rule.minimum.color
58912
58967
  }];
58913
- if (rule.midpoint && midValue) colorThresholds.push({
58968
+ if (rule.midpoint && midValue !== null) colorThresholds.push({
58914
58969
  value: midValue,
58915
58970
  color: rule.midpoint.color
58916
58971
  });
@@ -66369,7 +66424,9 @@ var GridSelectionPlugin = class extends UIPlugin {
66369
66424
  ctx.lineWidth = 1.5 * thinLineWidth;
66370
66425
  const isDarkMode = this.getters.isDarkMode();
66371
66426
  for (const zone of zones) {
66427
+ if (!viewports.isZoneVisibleInViewport(sheetId, zone)) continue;
66372
66428
  const { x, y, width, height } = viewports.getVisibleRect(sheetId, zone);
66429
+ const currentLineWidth = ctx.lineWidth;
66373
66430
  if (!isDarkMode) ctx.globalCompositeOperation = "multiply";
66374
66431
  if (height === 0 || width === 0) ctx.lineWidth = 3 * thinLineWidth;
66375
66432
  if (height === 0 && width === 0) {
@@ -66383,6 +66440,7 @@ var GridSelectionPlugin = class extends UIPlugin {
66383
66440
  ctx.globalCompositeOperation = "source-over";
66384
66441
  ctx.strokeRect(x, y, width, height);
66385
66442
  }
66443
+ ctx.lineWidth = currentLineWidth;
66386
66444
  }
66387
66445
  ctx.globalCompositeOperation = "source-over";
66388
66446
  const position = renderingContext.activePosition;
@@ -66529,6 +66587,9 @@ var InternalViewport = class {
66529
66587
  this.adjustViewportZoneX();
66530
66588
  this.adjustViewportZoneY();
66531
66589
  }
66590
+ isZoneVisible(zone) {
66591
+ return intersection(zone, this) !== void 0;
66592
+ }
66532
66593
  /**
66533
66594
  *
66534
66595
  * Computes the visible coordinates & dimensions of a given zone inside the viewport
@@ -66809,6 +66870,9 @@ var ViewportCollection = class {
66809
66870
  isVisibleInViewport({ sheetId, col, row }) {
66810
66871
  return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
66811
66872
  }
66873
+ isZoneVisibleInViewport(sheetId, zone) {
66874
+ return this.getSubViewports(sheetId).some((pane) => pane.isZoneVisible(zone));
66875
+ }
66812
66876
  getScrollBarWidth() {
66813
66877
  return 15 / this.zoomLevel;
66814
66878
  }
@@ -70882,18 +70946,28 @@ var NamedRangeSelector = class extends Component {
70882
70946
  return;
70883
70947
  }
70884
70948
  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);
70949
+ if (activeSheetId !== sheetId) {
70950
+ if (!this.env.model.getters.getSheet(sheetId).isVisible) {
70951
+ this.env.notifyUser({
70952
+ text: _t("The sheet on which the range is defined is hidden."),
70953
+ type: "info",
70954
+ sticky: false
70955
+ });
70956
+ return;
70957
+ }
70958
+ this.env.model.dispatch("ACTIVATE_SHEET", {
70959
+ sheetIdFrom: activeSheetId,
70960
+ sheetIdTo: sheetId
70961
+ });
70962
+ }
70963
+ this.env.model.selection.selectCell(zone.right, zone.bottom, { allowsHiddenSelection: true });
70890
70964
  this.env.model.selection.selectZone({
70891
70965
  cell: {
70892
70966
  col: zone.left,
70893
70967
  row: zone.top
70894
70968
  },
70895
70969
  zone
70896
- });
70970
+ }, { allowsHiddenSelection: true });
70897
70971
  }
70898
70972
  get selectionKey() {
70899
70973
  return `${this.env.model.getters.getActiveSheetId()}-${zoneToXc(this.selectedZone)}`;
@@ -87518,7 +87592,9 @@ const helpers = {
87518
87592
  collapseHierarchicalDisplayName,
87519
87593
  getCanonicalSymbolName,
87520
87594
  fuzzyLookup,
87521
- replaceSymbolInFormula
87595
+ replaceSymbolInFormula,
87596
+ isSingleCellReference,
87597
+ computeCachedTextDimension
87522
87598
  };
87523
87599
  const links = {
87524
87600
  isMarkdownLink,
@@ -87588,7 +87664,8 @@ const components = {
87588
87664
  FullScreenFigure,
87589
87665
  NumberInput,
87590
87666
  TopBar,
87591
- Composer
87667
+ Composer,
87668
+ CalendarButton
87592
87669
  };
87593
87670
  const hooks = {
87594
87671
  useDragAndDropListItems,
@@ -87742,6 +87819,6 @@ exports.stores = stores;
87742
87819
  exports.tokenColors = tokenColors;
87743
87820
  exports.tokenize = tokenize;
87744
87821
 
87745
- __info__.version = "19.5.0-alpha.1";
87746
- __info__.date = "2026-07-01T05:05:42.239Z";
87747
- __info__.hash = "3c78107";
87822
+ __info__.version = "19.5.0-alpha.2";
87823
+ __info__.date = "2026-07-13T06:26:20.354Z";
87824
+ __info__.hash = "a4d2f90";