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

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