@odoo/o-spreadsheet 16.5.0-alpha.10 → 16.5.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 16.5.0-alpha.10
6
- * @date 2023-10-10T07:31:20.876Z
7
- * @hash 4dc340f
5
+ * @version 16.5.0-alpha.12
6
+ * @date 2023-10-20T15:12:28.296Z
7
+ * @hash 430f090
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -2059,6 +2059,7 @@
2059
2059
  CommandResult["InvalidDataValidationCriterionValue"] = "InvalidDataValidationCriterionValue";
2060
2060
  CommandResult["InvalidNumberOfCriterionValues"] = "InvalidNumberOfCriterionValues";
2061
2061
  CommandResult["BlockingValidationRule"] = "BlockingValidationRule";
2062
+ CommandResult["InvalidCopyPasteSelection"] = "InvalidCopyPasteSelection";
2062
2063
  })(exports.CommandResult || (exports.CommandResult = {}));
2063
2064
 
2064
2065
  const DEFAULT_LOCALES = [
@@ -4500,6 +4501,69 @@
4500
4501
  function toLowerCase(str) {
4501
4502
  return str ? str.toLowerCase() : "";
4502
4503
  }
4504
+ /**
4505
+ * Extract the fontSize from a context font string
4506
+ * @param font The (context) font string to parse
4507
+ * @returns The fontSize in pixels
4508
+ */
4509
+ const pxRegex = /([0-9\.]*)px/;
4510
+ function getContextFontSize(font) {
4511
+ return Number(font.match(pxRegex)?.[1]);
4512
+ }
4513
+ function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10 //This value is defined to get a good looking stroke
4514
+ ) {
4515
+ context.fillText(text, position.x, position.y);
4516
+ if (!underline && !strikethrough) {
4517
+ return;
4518
+ }
4519
+ const measure = context.measureText(text);
4520
+ const textWidth = measure.width;
4521
+ const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
4522
+ const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
4523
+ let { x, y } = position;
4524
+ let strikeY = y, underlineY = y;
4525
+ switch (context.textAlign) {
4526
+ case "center":
4527
+ x -= textWidth / 2;
4528
+ break;
4529
+ case "right":
4530
+ x -= textWidth;
4531
+ break;
4532
+ }
4533
+ switch (context.textBaseline) {
4534
+ case "top":
4535
+ underlineY += boxHeight - 2 * strokeWidth;
4536
+ strikeY += boxHeight - textHeight;
4537
+ break;
4538
+ case "middle":
4539
+ underlineY += boxHeight / 2 - strokeWidth;
4540
+ break;
4541
+ case "alphabetic":
4542
+ underlineY += 2 * strokeWidth;
4543
+ strikeY -= textHeight / 2 - strokeWidth / 2;
4544
+ break;
4545
+ case "bottom":
4546
+ underlineY = y;
4547
+ strikeY -= textHeight / 2 - strokeWidth / 2;
4548
+ break;
4549
+ }
4550
+ if (underline) {
4551
+ context.lineWidth = strokeWidth;
4552
+ context.strokeStyle = context.fillStyle;
4553
+ context.beginPath();
4554
+ context.moveTo(x, underlineY);
4555
+ context.lineTo(x + textWidth, underlineY);
4556
+ context.stroke();
4557
+ }
4558
+ if (strikethrough) {
4559
+ context.lineWidth = strokeWidth;
4560
+ context.strokeStyle = context.fillStyle;
4561
+ context.beginPath();
4562
+ context.moveTo(x, strikeY);
4563
+ context.lineTo(x + textWidth, strikeY);
4564
+ context.stroke();
4565
+ }
4566
+ }
4503
4567
 
4504
4568
  /*
4505
4569
  * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
@@ -4563,6 +4627,29 @@
4563
4627
  };
4564
4628
  }
4565
4629
 
4630
+ function transformZone(zone, executed) {
4631
+ if (executed.type === "REMOVE_COLUMNS_ROWS") {
4632
+ return reduceZoneOnDeletion(zone, executed.dimension === "COL" ? "left" : "top", executed.elements);
4633
+ }
4634
+ if (executed.type === "ADD_COLUMNS_ROWS") {
4635
+ return expandZoneOnInsertion(zone, executed.dimension === "COL" ? "left" : "top", executed.base, executed.position, executed.quantity);
4636
+ }
4637
+ return { ...zone };
4638
+ }
4639
+ function transformRangeData(range, executed) {
4640
+ const deletedSheet = executed.type === "DELETE_SHEET" && executed.sheetId;
4641
+ if ("sheetId" in executed && range._sheetId !== executed.sheetId) {
4642
+ return range;
4643
+ }
4644
+ else {
4645
+ const newZone = transformZone(range._zone, executed);
4646
+ if (newZone && deletedSheet !== range._sheetId) {
4647
+ return { ...range, _zone: newZone };
4648
+ }
4649
+ }
4650
+ return undefined;
4651
+ }
4652
+
4566
4653
  class ChartJsComponent extends owl.Component {
4567
4654
  static template = "o-spreadsheet-ChartJsComponent";
4568
4655
  canvas = owl.useRef("graphContainer");
@@ -4585,15 +4672,7 @@
4585
4672
  const runtime = this.chartRuntime;
4586
4673
  this.createChart(runtime.chartJsConfig);
4587
4674
  });
4588
- let previousRuntime = this.chartRuntime;
4589
- owl.onPatched(() => {
4590
- const chartRuntime = this.chartRuntime;
4591
- if (deepEquals(previousRuntime, chartRuntime)) {
4592
- return;
4593
- }
4594
- this.updateChartJs(chartRuntime);
4595
- previousRuntime = chartRuntime;
4596
- });
4675
+ owl.useEffect(() => this.updateChartJs(this.chartRuntime), () => [this.chartRuntime]);
4597
4676
  }
4598
4677
  createChart(chartData) {
4599
4678
  const canvas = this.canvas.el;
@@ -4630,145 +4709,606 @@
4630
4709
  };
4631
4710
 
4632
4711
  /**
4633
- * This file is largely inspired by owl 1.
4634
- * `css` tag has been removed from owl 2 without workaround to manage css.
4635
- * So, the solution was to import the behavior of owl 1 directly in our
4636
- * codebase, with one difference: the css is added to the sheet as soon as the
4637
- * css tag is executed. In owl 1, the css was added as soon as a Component was
4638
- * created for the first time.
4712
+ * AbstractChart is the class from which every Chart should inherit.
4713
+ * The role of this class is to maintain the state of each chart.
4639
4714
  */
4640
- const STYLESHEETS = {};
4641
- let nextId = 0;
4715
+ class AbstractChart {
4716
+ sheetId;
4717
+ title;
4718
+ getters;
4719
+ constructor(definition, sheetId, getters) {
4720
+ this.title = definition.title;
4721
+ this.sheetId = sheetId;
4722
+ this.getters = getters;
4723
+ }
4724
+ /**
4725
+ * Validate the chart definition given as arguments. This function will be
4726
+ * called from allowDispatch function
4727
+ */
4728
+ static validateChartDefinition(validator, definition) {
4729
+ throw new Error("This method should be implemented by sub class");
4730
+ }
4731
+ /**
4732
+ * Get a new chart definition transformed with the executed command. This
4733
+ * functions will be called during operational transform process
4734
+ */
4735
+ static transformDefinition(definition, executed) {
4736
+ throw new Error("This method should be implemented by sub class");
4737
+ }
4738
+ /**
4739
+ * Get an empty definition based on the given context
4740
+ */
4741
+ static getDefinitionFromContextCreation(context) {
4742
+ throw new Error("This method should be implemented by sub class");
4743
+ }
4744
+ }
4745
+
4642
4746
  /**
4643
- * CSS tag helper for defining inline stylesheets. With this, one can simply define
4644
- * an inline stylesheet with just the following code:
4645
- * ```js
4646
- * css`.component-a { color: red; }`;
4647
- * ```
4747
+ * This file contains helpers that are common to different charts (mainly
4748
+ * line, bar and pie charts)
4648
4749
  */
4649
- function css(strings, ...args) {
4650
- const name = `__sheet__${nextId++}`;
4651
- const value = String.raw(strings, ...args);
4652
- registerSheet(name, value);
4653
- activateSheet(name);
4654
- return name;
4655
- }
4656
- function processSheet(str) {
4657
- const tokens = str.split(/(\{|\}|;)/).map((s) => s.trim());
4658
- const selectorStack = [];
4659
- const parts = [];
4660
- let rules = [];
4661
- function generateSelector(stackIndex, parentSelector) {
4662
- const parts = [];
4663
- for (const selector of selectorStack[stackIndex]) {
4664
- let part = (parentSelector && parentSelector + " " + selector) || selector;
4665
- if (part.includes("&")) {
4666
- part = selector.replace(/&/g, parentSelector || "");
4667
- }
4668
- if (stackIndex < selectorStack.length - 1) {
4669
- part = generateSelector(stackIndex + 1, part);
4750
+ /**
4751
+ * Adapt ranges of a chart which support DataSet (dataSets and LabelRange).
4752
+ */
4753
+ function updateChartRangesWithDataSets(getters, applyChange, chartDataSets, chartLabelRange) {
4754
+ let isStale = false;
4755
+ const dataSetsWithUndefined = [];
4756
+ for (let index in chartDataSets) {
4757
+ let ds = chartDataSets[index];
4758
+ if (ds.labelCell) {
4759
+ const labelCell = adaptChartRange(ds.labelCell, applyChange);
4760
+ if (ds.labelCell !== labelCell) {
4761
+ isStale = true;
4762
+ ds = {
4763
+ ...ds,
4764
+ labelCell: labelCell,
4765
+ };
4670
4766
  }
4671
- parts.push(part);
4672
4767
  }
4673
- return parts.join(", ");
4674
- }
4675
- function generateRules() {
4676
- if (rules.length) {
4677
- parts.push(generateSelector(0) + " {");
4678
- parts.push(...rules);
4679
- parts.push("}");
4680
- rules = [];
4768
+ const dataRange = adaptChartRange(ds.dataRange, applyChange);
4769
+ if (dataRange === undefined ||
4770
+ getters.getRangeString(dataRange, dataRange.sheetId) === INCORRECT_RANGE_STRING) {
4771
+ isStale = true;
4772
+ ds = undefined;
4773
+ }
4774
+ else if (dataRange !== ds.dataRange) {
4775
+ isStale = true;
4776
+ ds = {
4777
+ ...ds,
4778
+ dataRange,
4779
+ };
4681
4780
  }
4781
+ dataSetsWithUndefined[index] = ds;
4682
4782
  }
4683
- while (tokens.length) {
4684
- let token = tokens.shift();
4685
- if (token === "}") {
4686
- generateRules();
4687
- selectorStack.pop();
4783
+ let labelRange = chartLabelRange;
4784
+ const range = adaptChartRange(labelRange, applyChange);
4785
+ if (range !== labelRange) {
4786
+ isStale = true;
4787
+ labelRange = range;
4788
+ }
4789
+ const dataSets = dataSetsWithUndefined.filter(isDefined$1);
4790
+ return {
4791
+ isStale,
4792
+ dataSets,
4793
+ labelRange,
4794
+ };
4795
+ }
4796
+ /**
4797
+ * Copy the dataSets given. All the ranges which are on sheetIdFrom will target
4798
+ * sheetIdTo.
4799
+ */
4800
+ function copyDataSetsWithNewSheetId(sheetIdFrom, sheetIdTo, dataSets) {
4801
+ return dataSets.map((ds) => {
4802
+ return {
4803
+ dataRange: copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, ds.dataRange),
4804
+ labelCell: ds.labelCell
4805
+ ? copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, ds.labelCell)
4806
+ : undefined,
4807
+ };
4808
+ });
4809
+ }
4810
+ /**
4811
+ * Copy a range. If the range is on the sheetIdFrom, the range will target
4812
+ * sheetIdTo.
4813
+ */
4814
+ function copyLabelRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
4815
+ return range ? copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) : undefined;
4816
+ }
4817
+ /**
4818
+ * Adapt a single range of a chart
4819
+ */
4820
+ function adaptChartRange(range, applyChange) {
4821
+ if (!range) {
4822
+ return undefined;
4823
+ }
4824
+ const change = applyChange(range);
4825
+ switch (change.changeType) {
4826
+ case "NONE":
4827
+ return range;
4828
+ case "REMOVE":
4829
+ return undefined;
4830
+ default:
4831
+ return change.range;
4832
+ }
4833
+ }
4834
+ /**
4835
+ * Create the dataSet objects from xcs
4836
+ */
4837
+ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
4838
+ const dataSets = [];
4839
+ for (const sheetXC of dataSetsString) {
4840
+ const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);
4841
+ const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName, invalidXc } = dataRange;
4842
+ if (invalidSheetName || invalidXc) {
4843
+ continue;
4688
4844
  }
4689
- else {
4690
- if (tokens[0] === "{") {
4691
- generateRules();
4692
- selectorStack.push(token.split(/\s*,\s*/));
4693
- tokens.shift();
4845
+ // It's a rectangle. We treat all columns (arbitrary) as different data series.
4846
+ if (zone.left !== zone.right && zone.top !== zone.bottom) {
4847
+ if (zone.right === undefined) {
4848
+ // Should never happens because of the allowDispatch of charts, but just making sure
4849
+ continue;
4694
4850
  }
4695
- if (tokens[0] === ";") {
4696
- rules.push(" " + token + ";");
4851
+ for (let column = zone.left; column <= zone.right; column++) {
4852
+ const columnZone = {
4853
+ ...zone,
4854
+ left: column,
4855
+ right: column,
4856
+ };
4857
+ dataSets.push(createDataSet(getters, dataSetSheetId, columnZone, dataSetsHaveTitle
4858
+ ? {
4859
+ top: columnZone.top,
4860
+ bottom: columnZone.top,
4861
+ left: columnZone.left,
4862
+ right: columnZone.left,
4863
+ }
4864
+ : undefined));
4697
4865
  }
4698
4866
  }
4867
+ else if (zone.left === zone.right && zone.top === zone.bottom) {
4868
+ // A single cell. If it's only the title, the dataset is not added.
4869
+ if (!dataSetsHaveTitle) {
4870
+ dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
4871
+ }
4872
+ }
4873
+ else {
4874
+ /* 1 row or 1 column */
4875
+ dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
4876
+ ? {
4877
+ top: zone.top,
4878
+ bottom: zone.top,
4879
+ left: zone.left,
4880
+ right: zone.left,
4881
+ }
4882
+ : undefined));
4883
+ }
4699
4884
  }
4700
- return parts.join("\n");
4885
+ return dataSets;
4701
4886
  }
4702
- function registerSheet(id, css) {
4703
- const sheet = document.createElement("style");
4704
- sheet.textContent = processSheet(css);
4705
- STYLESHEETS[id] = sheet;
4887
+ function createDataSet(getters, sheetId, fullZone, titleZone) {
4888
+ if (fullZone.left !== fullZone.right && fullZone.top !== fullZone.bottom) {
4889
+ throw new Error(`Zone should be a single column or row: ${zoneToXc(fullZone)}`);
4890
+ }
4891
+ if (titleZone) {
4892
+ const dataXC = zoneToXc(fullZone);
4893
+ const labelCellXC = zoneToXc(titleZone);
4894
+ return {
4895
+ labelCell: getters.getRangeFromSheetXC(sheetId, labelCellXC),
4896
+ dataRange: getters.getRangeFromSheetXC(sheetId, dataXC),
4897
+ };
4898
+ }
4899
+ else {
4900
+ return {
4901
+ labelCell: undefined,
4902
+ dataRange: getters.getRangeFromSheetXC(sheetId, zoneToXc(fullZone)),
4903
+ };
4904
+ }
4706
4905
  }
4707
- function activateSheet(id) {
4708
- const sheet = STYLESHEETS[id];
4709
- sheet.setAttribute("component", id);
4710
- document.head.appendChild(sheet);
4906
+ /**
4907
+ * Transform a dataSet to a ExcelDataSet
4908
+ */
4909
+ function toExcelDataset(getters, ds) {
4910
+ const labelZone = ds.labelCell?.zone;
4911
+ let dataZone = ds.dataRange.zone;
4912
+ if (labelZone) {
4913
+ const { numberOfRows, numberOfCols } = zoneToDimension(dataZone);
4914
+ if (numberOfRows === 1) {
4915
+ dataZone = { ...dataZone, left: dataZone.left + 1 };
4916
+ }
4917
+ else if (numberOfCols === 1) {
4918
+ dataZone = { ...dataZone, top: dataZone.top + 1 };
4919
+ }
4920
+ }
4921
+ const dataRange = ds.dataRange.clone({ zone: dataZone });
4922
+ return {
4923
+ label: ds.labelCell ? getters.getRangeString(ds.labelCell, "forceSheetReference") : undefined,
4924
+ range: getters.getRangeString(dataRange, "forceSheetReference"),
4925
+ };
4711
4926
  }
4712
- function getTextDecoration({ strikethrough, underline, }) {
4713
- if (!strikethrough && !underline) {
4714
- return "none";
4927
+ function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
4928
+ if (!labelRange)
4929
+ return undefined;
4930
+ let zone = {
4931
+ ...labelRange.zone,
4932
+ };
4933
+ if (shouldRemoveFirstLabel && labelRange.zone.bottom > labelRange.zone.top) {
4934
+ zone.top = zone.top + 1;
4715
4935
  }
4716
- return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`;
4936
+ const range = labelRange.clone({ zone });
4937
+ return getters.getRangeString(range, "forceSheetReference");
4717
4938
  }
4718
4939
  /**
4719
- * Convert the cell style to CSS properties.
4940
+ * Transform a chart definition which supports dataSets (dataSets and LabelRange)
4941
+ * with an executed command
4720
4942
  */
4721
- function cellStyleToCss(style) {
4722
- const attributes = cellTextStyleToCss(style);
4723
- if (!style)
4724
- return attributes;
4725
- if (style.fillColor) {
4726
- attributes["background"] = style.fillColor;
4943
+ function transformChartDefinitionWithDataSetsWithZone(definition, executed) {
4944
+ let labelRange;
4945
+ if (definition.labelRange) {
4946
+ const labelZone = transformZone(toUnboundedZone(definition.labelRange), executed);
4947
+ labelRange = labelZone ? zoneToXc(labelZone) : undefined;
4948
+ }
4949
+ const dataSets = definition.dataSets
4950
+ .map(toUnboundedZone)
4951
+ .map((zone) => transformZone(zone, executed))
4952
+ .filter(isDefined$1)
4953
+ .map(zoneToXc);
4954
+ return {
4955
+ ...definition,
4956
+ labelRange,
4957
+ dataSets,
4958
+ };
4959
+ }
4960
+ const GraphColors = [
4961
+ // the same colors as those used in odoo reporting
4962
+ "rgb(31,119,180)",
4963
+ "rgb(255,127,14)",
4964
+ "rgb(174,199,232)",
4965
+ "rgb(255,187,120)",
4966
+ "rgb(44,160,44)",
4967
+ "rgb(152,223,138)",
4968
+ "rgb(214,39,40)",
4969
+ "rgb(255,152,150)",
4970
+ "rgb(148,103,189)",
4971
+ "rgb(197,176,213)",
4972
+ "rgb(140,86,75)",
4973
+ "rgb(196,156,148)",
4974
+ "rgb(227,119,194)",
4975
+ "rgb(247,182,210)",
4976
+ "rgb(127,127,127)",
4977
+ "rgb(199,199,199)",
4978
+ "rgb(188,189,34)",
4979
+ "rgb(219,219,141)",
4980
+ "rgb(23,190,207)",
4981
+ "rgb(158,218,229)",
4982
+ ];
4983
+ class ChartColors {
4984
+ graphColorIndex = 0;
4985
+ next() {
4986
+ return GraphColors[this.graphColorIndex++ % GraphColors.length];
4727
4987
  }
4728
- return attributes;
4729
4988
  }
4730
4989
  /**
4731
- * Convert the cell text style to CSS properties.
4990
+ * Choose a font color based on a background color.
4991
+ * The font is white with a dark background.
4732
4992
  */
4733
- function cellTextStyleToCss(style) {
4734
- const attributes = {};
4735
- if (!style)
4736
- return attributes;
4737
- if (style.bold) {
4738
- attributes["font-weight"] = "bold";
4993
+ function chartFontColor(backgroundColor) {
4994
+ if (!backgroundColor) {
4995
+ return "#000000";
4739
4996
  }
4740
- if (style.italic) {
4741
- attributes["font-style"] = "italic";
4997
+ return relativeLuminance(backgroundColor) < 0.3 ? "#FFFFFF" : "#000000";
4998
+ }
4999
+ function checkDataset(definition) {
5000
+ if (definition.dataSets) {
5001
+ const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range)) !== undefined;
5002
+ if (invalidRanges) {
5003
+ return "InvalidDataSet" /* CommandResult.InvalidDataSet */;
5004
+ }
5005
+ const zones = definition.dataSets.map(toUnboundedZone);
5006
+ if (zones.some((zone) => zone.top !== zone.bottom && isFullRow(zone))) {
5007
+ return "InvalidDataSet" /* CommandResult.InvalidDataSet */;
5008
+ }
4742
5009
  }
4743
- if (style.strikethrough || style.underline) {
4744
- let decoration = style.strikethrough ? "line-through" : "";
4745
- decoration = style.underline ? decoration + " underline" : decoration;
4746
- attributes["text-decoration"] = decoration;
5010
+ return "Success" /* CommandResult.Success */;
5011
+ }
5012
+ function checkLabelRange(definition) {
5013
+ if (definition.labelRange) {
5014
+ const invalidLabels = !rangeReference.test(definition.labelRange || "");
5015
+ if (invalidLabels) {
5016
+ return "InvalidLabelRange" /* CommandResult.InvalidLabelRange */;
5017
+ }
4747
5018
  }
4748
- if (style.textColor) {
4749
- attributes["color"] = style.textColor;
5019
+ return "Success" /* CommandResult.Success */;
5020
+ }
5021
+ function shouldRemoveFirstLabel(labelRange, dataset, dataSetsHaveTitle) {
5022
+ if (!dataSetsHaveTitle)
5023
+ return false;
5024
+ if (!labelRange)
5025
+ return false;
5026
+ if (!dataset)
5027
+ return true;
5028
+ const datasetLength = getZoneArea(dataset.dataRange.zone);
5029
+ const labelLength = getZoneArea(labelRange.zone);
5030
+ if (labelLength < datasetLength) {
5031
+ return false;
4750
5032
  }
4751
- return attributes;
5033
+ return true;
4752
5034
  }
4753
- /**
4754
- * Transform CSS properties into a CSS string.
4755
- */
4756
- function cssPropertiesToCss(attributes) {
4757
- const str = Object.entries(attributes)
4758
- .filter(([attName, attValue]) => attValue !== undefined)
4759
- .map(([attName, attValue]) => `${attName}:${attValue};`)
4760
- .join(" ");
4761
- return str;
5035
+ // ---------------------------------------------------------------------------
5036
+ // Scorecard
5037
+ // ---------------------------------------------------------------------------
5038
+ function getBaselineText(baseline, keyValue, baselineMode, locale) {
5039
+ if (!baseline) {
5040
+ return "";
5041
+ }
5042
+ else if (baselineMode === "text" ||
5043
+ keyValue?.type !== CellValueType.number ||
5044
+ baseline.type !== CellValueType.number) {
5045
+ return baseline.formattedValue;
5046
+ }
5047
+ else {
5048
+ let diff = keyValue.value - baseline.value;
5049
+ if (baselineMode === "percentage" && diff !== 0) {
5050
+ diff = (diff / baseline.value) * 100;
5051
+ }
5052
+ if (baselineMode !== "percentage" && baseline.format) {
5053
+ return formatValue(diff, { format: baseline.format, locale });
5054
+ }
5055
+ const baselineStr = Math.abs(parseFloat(diff.toFixed(2))).toLocaleString();
5056
+ return baselineMode === "percentage" ? baselineStr + "%" : baselineStr;
5057
+ }
4762
5058
  }
4763
- function getElementMargins(el) {
4764
- const style = window.getComputedStyle(el);
4765
- const margins = {
4766
- top: parseInt(style.marginTop, 10) || 0,
4767
- bottom: parseInt(style.marginBottom, 10) || 0,
4768
- left: parseInt(style.marginLeft, 10) || 0,
4769
- right: parseInt(style.marginRight, 10) || 0,
5059
+ function getBaselineColor(baseline, baselineMode, keyValue, colorUp, colorDown) {
5060
+ if (baselineMode === "text" ||
5061
+ baseline?.type !== CellValueType.number ||
5062
+ keyValue?.type !== CellValueType.number) {
5063
+ return undefined;
5064
+ }
5065
+ const diff = keyValue.value - baseline.value;
5066
+ if (diff > 0) {
5067
+ return colorUp;
5068
+ }
5069
+ else if (diff < 0) {
5070
+ return colorDown;
5071
+ }
5072
+ return undefined;
5073
+ }
5074
+ function getBaselineArrowDirection(baseline, keyValue, baselineMode) {
5075
+ if (baselineMode === "text" ||
5076
+ baseline?.type !== CellValueType.number ||
5077
+ keyValue?.type !== CellValueType.number) {
5078
+ return "neutral";
5079
+ }
5080
+ const diff = keyValue.value - baseline.value;
5081
+ if (diff > 0) {
5082
+ return "up";
5083
+ }
5084
+ else if (diff < 0) {
5085
+ return "down";
5086
+ }
5087
+ return "neutral";
5088
+ }
5089
+ function getChartPositionAtCenterOfViewport(getters, chartSize) {
5090
+ const { x, y } = getters.getMainViewportCoordinates();
5091
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
5092
+ const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
5093
+ const position = {
5094
+ x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
5095
+ y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
5096
+ }; // Position at the center of the scrollable viewport
5097
+ return position;
5098
+ }
5099
+
5100
+ function checkKeyValue(definition) {
5101
+ return definition.keyValue && !rangeReference.test(definition.keyValue)
5102
+ ? "InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */
5103
+ : "Success" /* CommandResult.Success */;
5104
+ }
5105
+ function checkBaseline(definition) {
5106
+ return definition.baseline && !rangeReference.test(definition.baseline)
5107
+ ? "InvalidScorecardBaseline" /* CommandResult.InvalidScorecardBaseline */
5108
+ : "Success" /* CommandResult.Success */;
5109
+ }
5110
+ const arrowDownPath = new window.Path2D("M8.6 4.8a.5.5 0 0 1 0 .75l-3.9 3.9a.5 .5 0 0 1 -.75 0l-3.8 -3.9a.5 .5 0 0 1 0 -.75l.4-.4a.5.5 0 0 1 .75 0l2.3 2.4v-5.7c0-.25.25-.5.5-.5h.6c.25 0 .5.25.5.5v5.8l2.3 -2.4a.5.5 0 0 1 .75 0z");
5111
+ const arrowUpPath = new window.Path2D("M8.7 5.5a.5.5 0 0 0 0-.75l-3.8-4a.5.5 0 0 0-.75 0l-3.8 4a.5.5 0 0 0 0 .75l.4.4a.5.5 0 0 0 .75 0l2.3-2.4v5.8c0 .25.25.5.5.5h.6c.25 0 .5-.25.5-.5v-5.8l2.2 2.4a.5.5 0 0 0 .75 0z");
5112
+ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
5113
+ keyValue;
5114
+ baseline;
5115
+ baselineMode;
5116
+ baselineDescr;
5117
+ background;
5118
+ baselineColorUp;
5119
+ baselineColorDown;
5120
+ fontColor;
5121
+ type = "scorecard";
5122
+ constructor(definition, sheetId, getters) {
5123
+ super(definition, sheetId, getters);
5124
+ this.keyValue = createRange(getters, sheetId, definition.keyValue);
5125
+ this.baseline = createRange(getters, sheetId, definition.baseline);
5126
+ this.baselineMode = definition.baselineMode;
5127
+ this.baselineDescr = definition.baselineDescr;
5128
+ this.background = definition.background;
5129
+ this.baselineColorUp = definition.baselineColorUp;
5130
+ this.baselineColorDown = definition.baselineColorDown;
5131
+ }
5132
+ static validateChartDefinition(validator, definition) {
5133
+ return validator.checkValidations(definition, checkKeyValue, checkBaseline);
5134
+ }
5135
+ static getDefinitionFromContextCreation(context) {
5136
+ return {
5137
+ background: context.background,
5138
+ type: "scorecard",
5139
+ keyValue: context.range ? context.range[0] : undefined,
5140
+ title: context.title || "",
5141
+ baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
5142
+ baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
5143
+ baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
5144
+ baseline: context.auxiliaryRange || "",
5145
+ };
5146
+ }
5147
+ static transformDefinition(definition, executed) {
5148
+ let baselineZone;
5149
+ let keyValueZone;
5150
+ if (definition.baseline) {
5151
+ baselineZone = transformZone(toUnboundedZone(definition.baseline), executed);
5152
+ }
5153
+ if (definition.keyValue) {
5154
+ keyValueZone = transformZone(toUnboundedZone(definition.keyValue), executed);
5155
+ }
5156
+ return {
5157
+ ...definition,
5158
+ baseline: baselineZone ? zoneToXc(baselineZone) : undefined,
5159
+ keyValue: keyValueZone ? zoneToXc(keyValueZone) : undefined,
5160
+ };
5161
+ }
5162
+ copyForSheetId(sheetId) {
5163
+ const baseline = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.baseline);
5164
+ const keyValue = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.keyValue);
5165
+ const definition = this.getDefinitionWithSpecificRanges(baseline, keyValue, sheetId);
5166
+ return new ScorecardChart(definition, sheetId, this.getters);
5167
+ }
5168
+ copyInSheetId(sheetId) {
5169
+ const definition = this.getDefinitionWithSpecificRanges(this.baseline, this.keyValue, sheetId);
5170
+ return new ScorecardChart(definition, sheetId, this.getters);
5171
+ }
5172
+ getDefinition() {
5173
+ return this.getDefinitionWithSpecificRanges(this.baseline, this.keyValue);
5174
+ }
5175
+ getContextCreation() {
5176
+ return {
5177
+ background: this.background,
5178
+ title: this.title,
5179
+ range: this.keyValue ? [this.getters.getRangeString(this.keyValue, this.sheetId)] : undefined,
5180
+ auxiliaryRange: this.baseline
5181
+ ? this.getters.getRangeString(this.baseline, this.sheetId)
5182
+ : undefined,
5183
+ };
5184
+ }
5185
+ getDefinitionWithSpecificRanges(baseline, keyValue, targetSheetId) {
5186
+ return {
5187
+ baselineColorDown: this.baselineColorDown,
5188
+ baselineColorUp: this.baselineColorUp,
5189
+ baselineMode: this.baselineMode,
5190
+ title: this.title,
5191
+ type: "scorecard",
5192
+ background: this.background,
5193
+ baseline: baseline
5194
+ ? this.getters.getRangeString(baseline, targetSheetId || this.sheetId)
5195
+ : undefined,
5196
+ baselineDescr: this.baselineDescr,
5197
+ keyValue: keyValue
5198
+ ? this.getters.getRangeString(keyValue, targetSheetId || this.sheetId)
5199
+ : undefined,
5200
+ };
5201
+ }
5202
+ getDefinitionForExcel() {
5203
+ // This kind of graph is not exportable in Excel
5204
+ return undefined;
5205
+ }
5206
+ updateRanges(applyChange) {
5207
+ const baseline = adaptChartRange(this.baseline, applyChange);
5208
+ const keyValue = adaptChartRange(this.keyValue, applyChange);
5209
+ if (this.baseline === baseline && this.keyValue === keyValue) {
5210
+ return this;
5211
+ }
5212
+ const definition = this.getDefinitionWithSpecificRanges(baseline, keyValue);
5213
+ return new ScorecardChart(definition, this.sheetId, this.getters);
5214
+ }
5215
+ };
5216
+ function drawScoreChart(structure, canvas) {
5217
+ const ctx = canvas.getContext("2d");
5218
+ canvas.width = structure.canvas.width;
5219
+ canvas.height = structure.canvas.height;
5220
+ ctx.fillStyle = structure.canvas.backgroundColor;
5221
+ ctx.fillRect(0, 0, structure.canvas.width, structure.canvas.height);
5222
+ if (structure.title) {
5223
+ ctx.font = structure.title.style.font;
5224
+ ctx.fillStyle = structure.title.style.color;
5225
+ ctx.fillText(structure.title.text, structure.title.position.x, structure.title.position.y);
5226
+ }
5227
+ if (structure.baseline) {
5228
+ ctx.font = structure.baseline.style.font;
5229
+ ctx.fillStyle = structure.baseline.style.color;
5230
+ drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
5231
+ }
5232
+ if (structure.baselineArrow && structure.baselineArrow.style.size > 0) {
5233
+ ctx.save();
5234
+ ctx.fillStyle = structure.baselineArrow.style.color;
5235
+ ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
5236
+ // This ratio is computed according to the original svg size and the final size we want
5237
+ const ratio = structure.baselineArrow.style.size / 10;
5238
+ ctx.scale(ratio, ratio);
5239
+ switch (structure.baselineArrow.direction) {
5240
+ case "down": {
5241
+ ctx.fill(arrowDownPath);
5242
+ break;
5243
+ }
5244
+ case "up": {
5245
+ ctx.fill(arrowUpPath);
5246
+ break;
5247
+ }
5248
+ }
5249
+ ctx.restore();
5250
+ }
5251
+ if (structure.baselineDescr) {
5252
+ ctx.font = structure.baselineDescr.style.font;
5253
+ ctx.fillStyle = structure.baselineDescr.style.color;
5254
+ ctx.fillText(structure.baselineDescr.text, structure.baselineDescr.position.x, structure.baselineDescr.position.y);
5255
+ }
5256
+ if (structure.key) {
5257
+ ctx.font = structure.key.style.font;
5258
+ ctx.fillStyle = structure.key.style.color;
5259
+ drawDecoratedText(ctx, structure.key.text, structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
5260
+ }
5261
+ }
5262
+ function createScorecardChartRuntime(chart, getters) {
5263
+ let keyValue = "";
5264
+ let formattedKeyValue = "";
5265
+ let keyValueCell;
5266
+ if (chart.keyValue) {
5267
+ const keyValuePosition = {
5268
+ sheetId: chart.keyValue.sheetId,
5269
+ col: chart.keyValue.zone.left,
5270
+ row: chart.keyValue.zone.top,
5271
+ };
5272
+ keyValueCell = getters.getEvaluatedCell(keyValuePosition);
5273
+ keyValue = String(keyValueCell.value);
5274
+ formattedKeyValue = keyValueCell.formattedValue;
5275
+ }
5276
+ let baselineCell;
5277
+ const baseline = chart.baseline;
5278
+ if (baseline) {
5279
+ const baselinePosition = {
5280
+ sheetId: chart.baseline.sheetId,
5281
+ col: chart.baseline.zone.left,
5282
+ row: chart.baseline.zone.top,
5283
+ };
5284
+ baselineCell = getters.getEvaluatedCell(baselinePosition);
5285
+ }
5286
+ const background = getters.getBackgroundOfSingleCellChart(chart.background, chart.keyValue);
5287
+ const locale = getters.getLocale();
5288
+ return {
5289
+ title: _t(chart.title),
5290
+ keyValue: formattedKeyValue || keyValue,
5291
+ baselineDisplay: getBaselineText(baselineCell, keyValueCell, chart.baselineMode, locale),
5292
+ baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
5293
+ baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
5294
+ baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
5295
+ fontColor: chartFontColor(background),
5296
+ background,
5297
+ baselineStyle: chart.baselineMode !== "percentage" && baseline
5298
+ ? getters.getCellStyle({
5299
+ sheetId: baseline.sheetId,
5300
+ col: baseline.zone.left,
5301
+ row: baseline.zone.top,
5302
+ })
5303
+ : undefined,
5304
+ keyValueStyle: chart.keyValue
5305
+ ? getters.getCellStyle({
5306
+ sheetId: chart.keyValue.sheetId,
5307
+ col: chart.keyValue.zone.left,
5308
+ row: chart.keyValue.zone.top,
5309
+ })
5310
+ : undefined,
4770
5311
  };
4771
- return margins;
4772
5312
  }
4773
5313
 
4774
5314
  /* Sizes of boxes containing the texts, in percentage of the Chart size */
@@ -4784,64 +5324,96 @@
4784
5324
  * Having a line heigh =1em (=font size) don't work, the font will overflow.
4785
5325
  */
4786
5326
  const LINE_HEIGHT = 1.2;
4787
- css /* scss */ `
4788
- div.o-scorecard {
4789
- font-family: ${DEFAULT_FONT};
4790
- user-select: none;
4791
- background-color: white;
4792
- display: flex;
4793
- flex-direction: column;
4794
- box-sizing: border-box;
4795
-
4796
- .o-scorecard-content {
4797
- display: flex;
4798
- flex-direction: column;
4799
- height: 100%;
4800
- justify-content: center;
4801
- text-align: center;
4802
- }
4803
-
4804
- .o-title-text {
4805
- text-align: left;
4806
- height: ${LINE_HEIGHT + "em"};
4807
- line-height: ${LINE_HEIGHT + "em"};
4808
- overflow: hidden;
4809
- white-space: nowrap;
4810
- }
4811
-
4812
- .o-key-text {
4813
- line-height: ${LINE_HEIGHT + "em"};
4814
- height: ${LINE_HEIGHT + "em"};
4815
- overflow: hidden;
4816
- white-space: nowrap;
4817
- }
4818
-
4819
- .o-cf-icon {
4820
- display: inline-block;
4821
- width: 0.65em;
4822
- height: 1em;
4823
- line-height: 1em;
4824
- padding-bottom: 0.07em;
4825
- padding-right: 3px;
4826
- }
4827
-
4828
- .o-baseline-text {
4829
- line-height: ${LINE_HEIGHT + "em"};
4830
- height: ${LINE_HEIGHT + "em"};
4831
- overflow: hidden;
4832
- white-space: nowrap;
4833
-
4834
- .o-baseline-text-description {
4835
- white-space: pre;
4836
- }
4837
- }
5327
+ function formatBaselineDescr(baselineDescr, baseline) {
5328
+ const _baselineDescr = baselineDescr || "";
5329
+ return baseline && _baselineDescr ? " " + _baselineDescr : _baselineDescr;
4838
5330
  }
4839
- `;
4840
- let ScorecardChart$1 = class ScorecardChart extends owl.Component {
4841
- static template = "o-spreadsheet-ScorecardChart";
4842
- ctx = document.createElement("canvas").getContext("2d");
4843
- get runtime() {
4844
- return this.env.model.getters.getChartRuntime(this.props.figure.id);
5331
+ function getDefaultContextFont(fontSize, bold = false, italic = false) {
5332
+ const italicStr = italic ? "italic" : "";
5333
+ const weight = bold ? "bold" : "";
5334
+ return `${italicStr} ${weight} ${fontSize}px ${DEFAULT_FONT}`;
5335
+ }
5336
+ function getScorecardConfiguration({ width, height }, runtime) {
5337
+ const designer = new ScorecardChartConfigBuilder({ width, height }, runtime);
5338
+ return designer.computeDesign();
5339
+ }
5340
+ class ScorecardChartConfigBuilder {
5341
+ runtime;
5342
+ context;
5343
+ width;
5344
+ height;
5345
+ constructor({ width, height }, runtime) {
5346
+ this.runtime = runtime;
5347
+ const canvas = document.createElement("canvas");
5348
+ this.width = canvas.width = width;
5349
+ this.height = canvas.height = height;
5350
+ this.context = canvas.getContext("2d");
5351
+ }
5352
+ computeDesign() {
5353
+ const structure = {
5354
+ canvas: {
5355
+ width: this.width,
5356
+ height: this.height,
5357
+ backgroundColor: this.backgroundColor,
5358
+ },
5359
+ };
5360
+ const style = this.getTextStyles();
5361
+ const { height: titleHeight } = this.getTextDimensions(this.title, style.title.font);
5362
+ if (this.title) {
5363
+ structure.title = {
5364
+ text: this.title,
5365
+ style: style.title,
5366
+ position: {
5367
+ x: this.chartPadding,
5368
+ y: this.chartPadding + titleHeight,
5369
+ },
5370
+ };
5371
+ }
5372
+ const baselineArrowSize = style.baselineArrow?.size ?? 0;
5373
+ const { height: baselineHeight, width: baselineWidth } = this.getTextDimensions(this.baseline, style.baselineValue.font);
5374
+ const { width: baselineDescrWidth } = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font);
5375
+ structure.baseline = {
5376
+ text: this.baseline,
5377
+ style: style.baselineValue,
5378
+ position: {
5379
+ x: (this.width - baselineWidth - baselineDescrWidth + baselineArrowSize) / 2,
5380
+ y: this.keyValue
5381
+ ? this.height - 2 * this.chartPadding
5382
+ : this.height - (this.height - titleHeight - baselineHeight) / 2 - this.chartPadding,
5383
+ },
5384
+ };
5385
+ if (style.baselineArrow) {
5386
+ structure.baselineArrow = {
5387
+ direction: this.baselineArrow,
5388
+ style: style.baselineArrow,
5389
+ position: {
5390
+ x: structure.baseline.position.x - baselineArrowSize,
5391
+ y: structure.baseline.position.y - (baselineHeight + baselineArrowSize) / 2,
5392
+ },
5393
+ };
5394
+ }
5395
+ if (this.baselineDescr) {
5396
+ structure.baselineDescr = {
5397
+ text: this.baselineDescr,
5398
+ style: style.baselineDescr,
5399
+ position: {
5400
+ x: structure.baseline.position.x + baselineWidth,
5401
+ y: structure.baseline.position.y,
5402
+ },
5403
+ };
5404
+ }
5405
+ const { height: keyHeight, width: keyWidth } = this.getTextDimensions(this.keyValue, style.keyValue.font);
5406
+ if (this.keyValue) {
5407
+ structure.key = {
5408
+ text: this.keyValue,
5409
+ style: style.keyValue,
5410
+ position: {
5411
+ x: (this.width - keyWidth) / 2,
5412
+ y: (this.height - baselineHeight + titleHeight + keyHeight) / 2 - this.chartPadding,
5413
+ },
5414
+ };
5415
+ }
5416
+ return structure;
4845
5417
  }
4846
5418
  get title() {
4847
5419
  return this.runtime.title;
@@ -4853,87 +5425,73 @@
4853
5425
  return this.runtime.baselineDisplay;
4854
5426
  }
4855
5427
  get baselineDescr() {
4856
- const baselineDescr = this.runtime.baselineDescr || "";
4857
- return this.baseline && baselineDescr ? " " + baselineDescr : baselineDescr;
5428
+ return formatBaselineDescr(this.runtime.baselineDescr, this.baseline);
4858
5429
  }
4859
- get baselineArrowDirection() {
5430
+ get baselineArrow() {
4860
5431
  return this.runtime.baselineArrow;
4861
5432
  }
4862
5433
  get backgroundColor() {
4863
5434
  return this.runtime.background;
4864
5435
  }
4865
- get primaryFontColor() {
4866
- return this.runtime.fontColor;
4867
- }
4868
5436
  get secondaryFontColor() {
4869
5437
  return relativeLuminance(this.backgroundColor) > 0.3 ? "#525252" : "#C8C8C8";
4870
5438
  }
4871
- get figure() {
4872
- return this.props.figure;
4873
- }
4874
- get chartStyle() {
4875
- return cssPropertiesToCss({
4876
- padding: `${this.chartPadding}px`,
4877
- background: this.backgroundColor,
4878
- });
4879
- }
4880
- get chartContentStyle() {
4881
- return cssPropertiesToCss({ height: `${this.getDrawableHeight()}px` });
4882
- }
4883
5439
  get chartPadding() {
4884
- return this.props.figure.width * CHART_PADDING_RATIO;
5440
+ return this.width * CHART_PADDING_RATIO;
4885
5441
  }
4886
- translate(term) {
4887
- return _t(term);
5442
+ getTextDimensions(text, font) {
5443
+ this.context.font = font;
5444
+ const measure = this.context.measureText(text);
5445
+ return {
5446
+ width: measure.width,
5447
+ height: measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent,
5448
+ };
4888
5449
  }
4889
5450
  getTextStyles() {
4890
5451
  // If the widest text overflows horizontally, scale it down, and apply the same scaling factors to all the other fonts.
4891
- const maxLineWidth = this.props.figure.width * (1 - 2 * CHART_PADDING_RATIO);
5452
+ const maxLineWidth = this.width * (1 - 2 * CHART_PADDING_RATIO);
4892
5453
  const widestElement = this.getWidestElement();
4893
5454
  const baseFontSize = widestElement.getElementMaxFontSize(this.getDrawableHeight(), this);
4894
- const fontSizeMatchingWidth = getFontSizeMatchingWidth(maxLineWidth, baseFontSize, (fontSize) => widestElement.getElementWidth(fontSize, this.ctx, this));
5455
+ const fontSizeMatchingWidth = getFontSizeMatchingWidth(maxLineWidth, baseFontSize, (fontSize) => widestElement.getElementWidth(fontSize, this.context, this));
4895
5456
  let scalingFactor = fontSizeMatchingWidth / baseFontSize;
4896
5457
  // Fonts sizes in px
4897
5458
  const keyFontSize = new KeyValueElement(this.runtime.keyValueStyle).getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;
4898
5459
  const baselineFontSize = new BaselineElement(this.runtime.baselineStyle).getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;
4899
5460
  return {
4900
- titleStyle: this.getTextStyle({
4901
- fontSize: TITLE_FONT_SIZE,
5461
+ title: {
5462
+ font: getDefaultContextFont(TITLE_FONT_SIZE),
4902
5463
  color: this.secondaryFontColor,
4903
- }),
4904
- keyStyle: this.getTextStyle({
4905
- fontSize: keyFontSize,
4906
- cellStyle: this.runtime.keyValueStyle,
4907
- color: this.primaryFontColor,
4908
- }),
4909
- baselineStyle: this.getTextStyle({
4910
- fontSize: baselineFontSize,
4911
- }),
4912
- baselineValueStyle: this.getTextStyle({
4913
- fontSize: baselineFontSize,
4914
- cellStyle: this.runtime.baselineStyle,
4915
- color: this.runtime.baselineColor || this.secondaryFontColor,
4916
- }),
4917
- baselineDescrStyle: this.getTextStyle({
4918
- fontSize: baselineFontSize * BASELINE_DESCR_FONT_RATIO,
5464
+ },
5465
+ keyValue: {
5466
+ color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
5467
+ font: getDefaultContextFont(keyFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
5468
+ strikethrough: this.runtime.keyValueStyle?.strikethrough,
5469
+ underline: this.runtime.keyValueStyle?.underline,
5470
+ },
5471
+ baselineValue: {
5472
+ font: getDefaultContextFont(baselineFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
5473
+ strikethrough: this.runtime.baselineStyle?.strikethrough,
5474
+ underline: this.runtime.baselineStyle?.underline,
5475
+ color: this.runtime.baselineStyle?.textColor ||
5476
+ this.runtime.baselineColor ||
5477
+ this.secondaryFontColor,
5478
+ },
5479
+ baselineDescr: {
5480
+ font: getDefaultContextFont(baselineFontSize * BASELINE_DESCR_FONT_RATIO),
4919
5481
  color: this.secondaryFontColor,
4920
- }),
5482
+ },
5483
+ baselineArrow: this.baselineArrow === "neutral"
5484
+ ? undefined
5485
+ : {
5486
+ size: this.keyValue ? 0.8 * baselineFontSize : 0,
5487
+ color: this.runtime.baselineColor || this.secondaryFontColor,
5488
+ },
4921
5489
  };
4922
5490
  }
4923
- /** Return an CSS style string corresponding to the given arguments */
4924
- getTextStyle(args) {
4925
- const cssAttributes = cellTextStyleToCss(args.cellStyle);
4926
- cssAttributes["font-size"] = `${args.fontSize}px`;
4927
- cssAttributes["display"] = "inline-block";
4928
- if (!cssAttributes["color"] && args.color) {
4929
- cssAttributes["color"] = args.color;
4930
- }
4931
- return cssPropertiesToCss(cssAttributes);
4932
- }
4933
5491
  /** Get the height of the chart minus all the vertical paddings */
4934
5492
  getDrawableHeight() {
4935
5493
  const verticalPadding = 2 * this.chartPadding;
4936
- let availableHeight = this.props.figure.height - verticalPadding;
5494
+ let availableHeight = this.height - verticalPadding;
4937
5495
  availableHeight -= this.title ? TITLE_FONT_SIZE * LINE_HEIGHT : 0;
4938
5496
  return availableHeight;
4939
5497
  }
@@ -4941,39 +5499,39 @@
4941
5499
  getWidestElement() {
4942
5500
  const baseline = new BaselineElement(this.runtime.baselineStyle);
4943
5501
  const keyValue = new KeyValueElement(this.runtime.keyValueStyle);
4944
- return baseline.getElementWidth(BASELINE_BOX_HEIGHT_RATIO, this.ctx, this) >
4945
- keyValue.getElementWidth(KEY_BOX_HEIGHT_RATIO, this.ctx, this)
5502
+ return baseline.getElementWidth(BASELINE_BOX_HEIGHT_RATIO, this.context, this) >
5503
+ keyValue.getElementWidth(KEY_BOX_HEIGHT_RATIO, this.context, this)
4946
5504
  ? baseline
4947
5505
  : keyValue;
4948
5506
  }
4949
- };
5507
+ }
4950
5508
  class ScorecardScalableElement {
4951
5509
  style;
4952
5510
  constructor(style = {}) {
4953
5511
  this.style = style;
4954
5512
  }
4955
5513
  measureTextWidth(ctx, text, fontSize) {
4956
- const italic = this.style.italic ? "italic" : "";
4957
- const weight = this.style.bold ? "bold" : "";
4958
- ctx.font = `${italic} ${weight} ${fontSize}px ${DEFAULT_FONT}`;
5514
+ ctx.font = getDefaultContextFont(fontSize, this.style.bold, this.style.italic);
4959
5515
  return ctx.measureText(text).width;
4960
5516
  }
4961
5517
  }
4962
5518
  class BaselineElement extends ScorecardScalableElement {
4963
5519
  getElementWidth(fontSize, ctx, chart) {
4964
- if (!chart.runtime)
5520
+ if (!chart.runtime) {
4965
5521
  return 0;
5522
+ }
4966
5523
  const baselineStr = chart.baseline;
4967
5524
  // Put mock text to simulate the width of the up/down arrow
4968
- const largeText = chart.baselineArrowDirection !== "neutral" ? "A " + baselineStr : baselineStr;
5525
+ const largeText = chart.baselineArrow !== "neutral" ? "A " + baselineStr : baselineStr;
4969
5526
  let textWidth = this.measureTextWidth(ctx, largeText, fontSize);
4970
5527
  // Baseline descr font size should be smaller than baseline font size
4971
5528
  textWidth += this.measureTextWidth(ctx, chart.baselineDescr, fontSize * BASELINE_DESCR_FONT_RATIO);
4972
5529
  return textWidth;
4973
5530
  }
4974
5531
  getElementMaxFontSize(availableHeight, chart) {
4975
- if (!chart.runtime)
5532
+ if (!chart.runtime) {
4976
5533
  return 0;
5534
+ }
4977
5535
  const haveBaseline = chart.baseline !== "" || chart.baselineDescr;
4978
5536
  const maxHeight = haveBaseline ? BASELINE_BOX_HEIGHT_RATIO * availableHeight : 0;
4979
5537
  return maxHeight / LINE_HEIGHT;
@@ -4981,20 +5539,42 @@
4981
5539
  }
4982
5540
  class KeyValueElement extends ScorecardScalableElement {
4983
5541
  getElementWidth(fontSize, ctx, chart) {
4984
- if (!chart.runtime)
5542
+ if (!chart.runtime) {
4985
5543
  return 0;
5544
+ }
4986
5545
  const str = chart.keyValue || "";
4987
5546
  return this.measureTextWidth(ctx, str, fontSize);
4988
5547
  }
4989
5548
  getElementMaxFontSize(availableHeight, chart) {
4990
- if (!chart.runtime)
5549
+ if (!chart.runtime) {
4991
5550
  return 0;
5551
+ }
4992
5552
  const haveBaseline = chart.baseline !== "" || chart.baselineDescr;
4993
5553
  const maxHeight = haveBaseline ? KEY_BOX_HEIGHT_RATIO * availableHeight : availableHeight;
4994
5554
  return maxHeight / LINE_HEIGHT;
4995
5555
  }
4996
5556
  }
4997
- ScorecardChart$1.props = {
5557
+
5558
+ class ScorecardChart extends owl.Component {
5559
+ static template = "o-spreadsheet-ScorecardChart";
5560
+ canvas = owl.useRef("chartContainer");
5561
+ get runtime() {
5562
+ return this.env.model.getters.getChartRuntime(this.props.figure.id);
5563
+ }
5564
+ setup() {
5565
+ owl.useEffect(this.createChart.bind(this), () => {
5566
+ const canvas = this.canvas.el;
5567
+ const rect = canvas.getBoundingClientRect();
5568
+ return [rect.width, rect.height, this.runtime, this.canvas.el];
5569
+ });
5570
+ }
5571
+ createChart() {
5572
+ const canvas = this.canvas.el;
5573
+ const config = getScorecardConfiguration(canvas.getBoundingClientRect(), this.runtime);
5574
+ drawScoreChart(config, canvas);
5575
+ }
5576
+ }
5577
+ ScorecardChart.props = {
4998
5578
  figure: Object,
4999
5579
  };
5000
5580
 
@@ -5507,6 +6087,148 @@
5507
6087
  sequence: 40,
5508
6088
  });
5509
6089
 
6090
+ /**
6091
+ * This file is largely inspired by owl 1.
6092
+ * `css` tag has been removed from owl 2 without workaround to manage css.
6093
+ * So, the solution was to import the behavior of owl 1 directly in our
6094
+ * codebase, with one difference: the css is added to the sheet as soon as the
6095
+ * css tag is executed. In owl 1, the css was added as soon as a Component was
6096
+ * created for the first time.
6097
+ */
6098
+ const STYLESHEETS = {};
6099
+ let nextId = 0;
6100
+ /**
6101
+ * CSS tag helper for defining inline stylesheets. With this, one can simply define
6102
+ * an inline stylesheet with just the following code:
6103
+ * ```js
6104
+ * css`.component-a { color: red; }`;
6105
+ * ```
6106
+ */
6107
+ function css(strings, ...args) {
6108
+ const name = `__sheet__${nextId++}`;
6109
+ const value = String.raw(strings, ...args);
6110
+ registerSheet(name, value);
6111
+ activateSheet(name);
6112
+ return name;
6113
+ }
6114
+ function processSheet(str) {
6115
+ const tokens = str.split(/(\{|\}|;)/).map((s) => s.trim());
6116
+ const selectorStack = [];
6117
+ const parts = [];
6118
+ let rules = [];
6119
+ function generateSelector(stackIndex, parentSelector) {
6120
+ const parts = [];
6121
+ for (const selector of selectorStack[stackIndex]) {
6122
+ let part = (parentSelector && parentSelector + " " + selector) || selector;
6123
+ if (part.includes("&")) {
6124
+ part = selector.replace(/&/g, parentSelector || "");
6125
+ }
6126
+ if (stackIndex < selectorStack.length - 1) {
6127
+ part = generateSelector(stackIndex + 1, part);
6128
+ }
6129
+ parts.push(part);
6130
+ }
6131
+ return parts.join(", ");
6132
+ }
6133
+ function generateRules() {
6134
+ if (rules.length) {
6135
+ parts.push(generateSelector(0) + " {");
6136
+ parts.push(...rules);
6137
+ parts.push("}");
6138
+ rules = [];
6139
+ }
6140
+ }
6141
+ while (tokens.length) {
6142
+ let token = tokens.shift();
6143
+ if (token === "}") {
6144
+ generateRules();
6145
+ selectorStack.pop();
6146
+ }
6147
+ else {
6148
+ if (tokens[0] === "{") {
6149
+ generateRules();
6150
+ selectorStack.push(token.split(/\s*,\s*/));
6151
+ tokens.shift();
6152
+ }
6153
+ if (tokens[0] === ";") {
6154
+ rules.push(" " + token + ";");
6155
+ }
6156
+ }
6157
+ }
6158
+ return parts.join("\n");
6159
+ }
6160
+ function registerSheet(id, css) {
6161
+ const sheet = document.createElement("style");
6162
+ sheet.textContent = processSheet(css);
6163
+ STYLESHEETS[id] = sheet;
6164
+ }
6165
+ function activateSheet(id) {
6166
+ const sheet = STYLESHEETS[id];
6167
+ sheet.setAttribute("component", id);
6168
+ document.head.appendChild(sheet);
6169
+ }
6170
+ function getTextDecoration({ strikethrough, underline, }) {
6171
+ if (!strikethrough && !underline) {
6172
+ return "none";
6173
+ }
6174
+ return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`;
6175
+ }
6176
+ /**
6177
+ * Convert the cell style to CSS properties.
6178
+ */
6179
+ function cellStyleToCss(style) {
6180
+ const attributes = cellTextStyleToCss(style);
6181
+ if (!style)
6182
+ return attributes;
6183
+ if (style.fillColor) {
6184
+ attributes["background"] = style.fillColor;
6185
+ }
6186
+ return attributes;
6187
+ }
6188
+ /**
6189
+ * Convert the cell text style to CSS properties.
6190
+ */
6191
+ function cellTextStyleToCss(style) {
6192
+ const attributes = {};
6193
+ if (!style)
6194
+ return attributes;
6195
+ if (style.bold) {
6196
+ attributes["font-weight"] = "bold";
6197
+ }
6198
+ if (style.italic) {
6199
+ attributes["font-style"] = "italic";
6200
+ }
6201
+ if (style.strikethrough || style.underline) {
6202
+ let decoration = style.strikethrough ? "line-through" : "";
6203
+ decoration = style.underline ? decoration + " underline" : decoration;
6204
+ attributes["text-decoration"] = decoration;
6205
+ }
6206
+ if (style.textColor) {
6207
+ attributes["color"] = style.textColor;
6208
+ }
6209
+ return attributes;
6210
+ }
6211
+ /**
6212
+ * Transform CSS properties into a CSS string.
6213
+ */
6214
+ function cssPropertiesToCss(attributes) {
6215
+ const str = Object.entries(attributes)
6216
+ .filter(([attName, attValue]) => attValue !== undefined)
6217
+ .map(([attName, attValue]) => `${attName}:${attValue};`)
6218
+ .join(" ");
6219
+ return str;
6220
+ }
6221
+ function getElementMargins(el) {
6222
+ const style = window.getComputedStyle(el);
6223
+ const margins = {
6224
+ top: parseInt(style.marginTop, 10) || 0,
6225
+ bottom: parseInt(style.marginBottom, 10) || 0,
6226
+ left: parseInt(style.marginLeft, 10) || 0,
6227
+ right: parseInt(style.marginRight, 10) || 0,
6228
+ };
6229
+ return margins;
6230
+ }
6231
+
5510
6232
  const ERROR_TOOLTIP_MAX_HEIGHT = 80;
5511
6233
  const ERROR_TOOLTIP_WIDTH = 180;
5512
6234
  css /* scss */ `
@@ -6240,6 +6962,10 @@
6240
6962
  cursor: pointer;
6241
6963
  user-select: none;
6242
6964
 
6965
+ .o-menu-item-name {
6966
+ min-width: 40%;
6967
+ }
6968
+
6243
6969
  &.o-menu-root {
6244
6970
  display: flex;
6245
6971
  justify-content: space-between;
@@ -16375,410 +17101,11 @@
16375
17101
  return color;
16376
17102
  }
16377
17103
 
16378
- /**
16379
- * AbstractChart is the class from which every Chart should inherit.
16380
- * The role of this class is to maintain the state of each chart.
16381
- */
16382
- class AbstractChart {
16383
- sheetId;
16384
- title;
16385
- getters;
16386
- constructor(definition, sheetId, getters) {
16387
- this.title = definition.title;
16388
- this.sheetId = sheetId;
16389
- this.getters = getters;
16390
- }
16391
- /**
16392
- * Validate the chart definition given as arguments. This function will be
16393
- * called from allowDispatch function
16394
- */
16395
- static validateChartDefinition(validator, definition) {
16396
- throw new Error("This method should be implemented by sub class");
16397
- }
16398
- /**
16399
- * Get a new chart definition transformed with the executed command. This
16400
- * functions will be called during operational transform process
16401
- */
16402
- static transformDefinition(definition, executed) {
16403
- throw new Error("This method should be implemented by sub class");
16404
- }
16405
- /**
16406
- * Get an empty definition based on the given context
16407
- */
16408
- static getDefinitionFromContextCreation(context) {
16409
- throw new Error("This method should be implemented by sub class");
16410
- }
16411
- }
16412
-
16413
- function transformZone(zone, executed) {
16414
- if (executed.type === "REMOVE_COLUMNS_ROWS") {
16415
- return reduceZoneOnDeletion(zone, executed.dimension === "COL" ? "left" : "top", executed.elements);
16416
- }
16417
- if (executed.type === "ADD_COLUMNS_ROWS") {
16418
- return expandZoneOnInsertion(zone, executed.dimension === "COL" ? "left" : "top", executed.base, executed.position, executed.quantity);
16419
- }
16420
- return { ...zone };
16421
- }
16422
-
16423
- /**
16424
- * This file contains helpers that are common to different charts (mainly
16425
- * line, bar and pie charts)
16426
- */
16427
- /**
16428
- * Adapt ranges of a chart which support DataSet (dataSets and LabelRange).
16429
- */
16430
- function updateChartRangesWithDataSets(getters, applyChange, chartDataSets, chartLabelRange) {
16431
- let isStale = false;
16432
- const dataSetsWithUndefined = [];
16433
- for (let index in chartDataSets) {
16434
- let ds = chartDataSets[index];
16435
- if (ds.labelCell) {
16436
- const labelCell = adaptChartRange(ds.labelCell, applyChange);
16437
- if (ds.labelCell !== labelCell) {
16438
- isStale = true;
16439
- ds = {
16440
- ...ds,
16441
- labelCell: labelCell,
16442
- };
16443
- }
16444
- }
16445
- const dataRange = adaptChartRange(ds.dataRange, applyChange);
16446
- if (dataRange === undefined ||
16447
- getters.getRangeString(dataRange, dataRange.sheetId) === INCORRECT_RANGE_STRING) {
16448
- isStale = true;
16449
- ds = undefined;
16450
- }
16451
- else if (dataRange !== ds.dataRange) {
16452
- isStale = true;
16453
- ds = {
16454
- ...ds,
16455
- dataRange,
16456
- };
16457
- }
16458
- dataSetsWithUndefined[index] = ds;
16459
- }
16460
- let labelRange = chartLabelRange;
16461
- const range = adaptChartRange(labelRange, applyChange);
16462
- if (range !== labelRange) {
16463
- isStale = true;
16464
- labelRange = range;
16465
- }
16466
- const dataSets = dataSetsWithUndefined.filter(isDefined$1);
16467
- return {
16468
- isStale,
16469
- dataSets,
16470
- labelRange,
16471
- };
16472
- }
16473
- /**
16474
- * Copy the dataSets given. All the ranges which are on sheetIdFrom will target
16475
- * sheetIdTo.
16476
- */
16477
- function copyDataSetsWithNewSheetId(sheetIdFrom, sheetIdTo, dataSets) {
16478
- return dataSets.map((ds) => {
16479
- return {
16480
- dataRange: copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, ds.dataRange),
16481
- labelCell: ds.labelCell
16482
- ? copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, ds.labelCell)
16483
- : undefined,
16484
- };
16485
- });
16486
- }
16487
- /**
16488
- * Copy a range. If the range is on the sheetIdFrom, the range will target
16489
- * sheetIdTo.
16490
- */
16491
- function copyLabelRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
16492
- return range ? copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) : undefined;
16493
- }
16494
- /**
16495
- * Adapt a single range of a chart
16496
- */
16497
- function adaptChartRange(range, applyChange) {
16498
- if (!range) {
16499
- return undefined;
16500
- }
16501
- const change = applyChange(range);
16502
- switch (change.changeType) {
16503
- case "NONE":
16504
- return range;
16505
- case "REMOVE":
16506
- return undefined;
16507
- default:
16508
- return change.range;
16509
- }
16510
- }
16511
- /**
16512
- * Create the dataSet objects from xcs
16513
- */
16514
- function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
16515
- const dataSets = [];
16516
- for (const sheetXC of dataSetsString) {
16517
- const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);
16518
- const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName, invalidXc } = dataRange;
16519
- if (invalidSheetName || invalidXc) {
16520
- continue;
16521
- }
16522
- // It's a rectangle. We treat all columns (arbitrary) as different data series.
16523
- if (zone.left !== zone.right && zone.top !== zone.bottom) {
16524
- if (zone.right === undefined) {
16525
- // Should never happens because of the allowDispatch of charts, but just making sure
16526
- continue;
16527
- }
16528
- for (let column = zone.left; column <= zone.right; column++) {
16529
- const columnZone = {
16530
- ...zone,
16531
- left: column,
16532
- right: column,
16533
- };
16534
- dataSets.push(createDataSet(getters, dataSetSheetId, columnZone, dataSetsHaveTitle
16535
- ? {
16536
- top: columnZone.top,
16537
- bottom: columnZone.top,
16538
- left: columnZone.left,
16539
- right: columnZone.left,
16540
- }
16541
- : undefined));
16542
- }
16543
- }
16544
- else if (zone.left === zone.right && zone.top === zone.bottom) {
16545
- // A single cell. If it's only the title, the dataset is not added.
16546
- if (!dataSetsHaveTitle) {
16547
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
16548
- }
16549
- }
16550
- else {
16551
- /* 1 row or 1 column */
16552
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
16553
- ? {
16554
- top: zone.top,
16555
- bottom: zone.top,
16556
- left: zone.left,
16557
- right: zone.left,
16558
- }
16559
- : undefined));
16560
- }
16561
- }
16562
- return dataSets;
16563
- }
16564
- function createDataSet(getters, sheetId, fullZone, titleZone) {
16565
- if (fullZone.left !== fullZone.right && fullZone.top !== fullZone.bottom) {
16566
- throw new Error(`Zone should be a single column or row: ${zoneToXc(fullZone)}`);
16567
- }
16568
- if (titleZone) {
16569
- const dataXC = zoneToXc(fullZone);
16570
- const labelCellXC = zoneToXc(titleZone);
16571
- return {
16572
- labelCell: getters.getRangeFromSheetXC(sheetId, labelCellXC),
16573
- dataRange: getters.getRangeFromSheetXC(sheetId, dataXC),
16574
- };
16575
- }
16576
- else {
16577
- return {
16578
- labelCell: undefined,
16579
- dataRange: getters.getRangeFromSheetXC(sheetId, zoneToXc(fullZone)),
16580
- };
16581
- }
16582
- }
16583
- /**
16584
- * Transform a dataSet to a ExcelDataSet
16585
- */
16586
- function toExcelDataset(getters, ds) {
16587
- const labelZone = ds.labelCell?.zone;
16588
- let dataZone = ds.dataRange.zone;
16589
- if (labelZone) {
16590
- const { numberOfRows, numberOfCols } = zoneToDimension(dataZone);
16591
- if (numberOfRows === 1) {
16592
- dataZone = { ...dataZone, left: dataZone.left + 1 };
16593
- }
16594
- else if (numberOfCols === 1) {
16595
- dataZone = { ...dataZone, top: dataZone.top + 1 };
16596
- }
16597
- }
16598
- const dataRange = ds.dataRange.clone({ zone: dataZone });
16599
- return {
16600
- label: ds.labelCell ? getters.getRangeString(ds.labelCell, "forceSheetReference") : undefined,
16601
- range: getters.getRangeString(dataRange, "forceSheetReference"),
16602
- };
16603
- }
16604
- function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
16605
- if (!labelRange)
16606
- return undefined;
16607
- let zone = {
16608
- ...labelRange.zone,
16609
- };
16610
- if (shouldRemoveFirstLabel && labelRange.zone.bottom > labelRange.zone.top) {
16611
- zone.top = zone.top + 1;
16612
- }
16613
- const range = labelRange.clone({ zone });
16614
- return getters.getRangeString(range, "forceSheetReference");
16615
- }
16616
- /**
16617
- * Transform a chart definition which supports dataSets (dataSets and LabelRange)
16618
- * with an executed command
16619
- */
16620
- function transformChartDefinitionWithDataSetsWithZone(definition, executed) {
16621
- let labelRange;
16622
- if (definition.labelRange) {
16623
- const labelZone = transformZone(toUnboundedZone(definition.labelRange), executed);
16624
- labelRange = labelZone ? zoneToXc(labelZone) : undefined;
16625
- }
16626
- const dataSets = definition.dataSets
16627
- .map(toUnboundedZone)
16628
- .map((zone) => transformZone(zone, executed))
16629
- .filter(isDefined$1)
16630
- .map(zoneToXc);
16631
- return {
16632
- ...definition,
16633
- labelRange,
16634
- dataSets,
16635
- };
16636
- }
16637
- const GraphColors = [
16638
- // the same colors as those used in odoo reporting
16639
- "rgb(31,119,180)",
16640
- "rgb(255,127,14)",
16641
- "rgb(174,199,232)",
16642
- "rgb(255,187,120)",
16643
- "rgb(44,160,44)",
16644
- "rgb(152,223,138)",
16645
- "rgb(214,39,40)",
16646
- "rgb(255,152,150)",
16647
- "rgb(148,103,189)",
16648
- "rgb(197,176,213)",
16649
- "rgb(140,86,75)",
16650
- "rgb(196,156,148)",
16651
- "rgb(227,119,194)",
16652
- "rgb(247,182,210)",
16653
- "rgb(127,127,127)",
16654
- "rgb(199,199,199)",
16655
- "rgb(188,189,34)",
16656
- "rgb(219,219,141)",
16657
- "rgb(23,190,207)",
16658
- "rgb(158,218,229)",
16659
- ];
16660
- class ChartColors {
16661
- graphColorIndex = 0;
16662
- next() {
16663
- return GraphColors[this.graphColorIndex++ % GraphColors.length];
16664
- }
16665
- }
16666
- /**
16667
- * Choose a font color based on a background color.
16668
- * The font is white with a dark background.
16669
- */
16670
- function chartFontColor(backgroundColor) {
16671
- if (!backgroundColor) {
16672
- return "#000000";
16673
- }
16674
- return relativeLuminance(backgroundColor) < 0.3 ? "#FFFFFF" : "#000000";
16675
- }
16676
- function checkDataset(definition) {
16677
- if (definition.dataSets) {
16678
- const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range)) !== undefined;
16679
- if (invalidRanges) {
16680
- return "InvalidDataSet" /* CommandResult.InvalidDataSet */;
16681
- }
16682
- const zones = definition.dataSets.map(toUnboundedZone);
16683
- if (zones.some((zone) => zone.top !== zone.bottom && isFullRow(zone))) {
16684
- return "InvalidDataSet" /* CommandResult.InvalidDataSet */;
16685
- }
16686
- }
16687
- return "Success" /* CommandResult.Success */;
16688
- }
16689
- function checkLabelRange(definition) {
16690
- if (definition.labelRange) {
16691
- const invalidLabels = !rangeReference.test(definition.labelRange || "");
16692
- if (invalidLabels) {
16693
- return "InvalidLabelRange" /* CommandResult.InvalidLabelRange */;
16694
- }
16695
- }
16696
- return "Success" /* CommandResult.Success */;
16697
- }
16698
- function shouldRemoveFirstLabel(labelRange, dataset, dataSetsHaveTitle) {
16699
- if (!dataSetsHaveTitle)
16700
- return false;
16701
- if (!labelRange)
16702
- return false;
16703
- if (!dataset)
16704
- return true;
16705
- const datasetLength = getZoneArea(dataset.dataRange.zone);
16706
- const labelLength = getZoneArea(labelRange.zone);
16707
- if (labelLength < datasetLength) {
16708
- return false;
16709
- }
16710
- return true;
16711
- }
16712
- // ---------------------------------------------------------------------------
16713
- // Scorecard
16714
- // ---------------------------------------------------------------------------
16715
- function getBaselineText(baseline, keyValue, baselineMode, locale) {
16716
- if (!baseline) {
16717
- return "";
16718
- }
16719
- else if (baselineMode === "text" ||
16720
- keyValue?.type !== CellValueType.number ||
16721
- baseline.type !== CellValueType.number) {
16722
- return baseline.formattedValue;
16723
- }
16724
- else {
16725
- let diff = keyValue.value - baseline.value;
16726
- if (baselineMode === "percentage" && diff !== 0) {
16727
- diff = (diff / baseline.value) * 100;
16728
- }
16729
- if (baselineMode !== "percentage" && baseline.format) {
16730
- return formatValue(diff, { format: baseline.format, locale });
16731
- }
16732
- const baselineStr = Math.abs(parseFloat(diff.toFixed(2))).toLocaleString();
16733
- return baselineMode === "percentage" ? baselineStr + "%" : baselineStr;
16734
- }
16735
- }
16736
- function getBaselineColor(baseline, baselineMode, keyValue, colorUp, colorDown) {
16737
- if (baselineMode === "text" ||
16738
- baseline?.type !== CellValueType.number ||
16739
- keyValue?.type !== CellValueType.number) {
16740
- return undefined;
16741
- }
16742
- const diff = keyValue.value - baseline.value;
16743
- if (diff > 0) {
16744
- return colorUp;
16745
- }
16746
- else if (diff < 0) {
16747
- return colorDown;
16748
- }
16749
- return undefined;
16750
- }
16751
- function getBaselineArrowDirection(baseline, keyValue, baselineMode) {
16752
- if (baselineMode === "text" ||
16753
- baseline?.type !== CellValueType.number ||
16754
- keyValue?.type !== CellValueType.number) {
16755
- return "neutral";
16756
- }
16757
- const diff = keyValue.value - baseline.value;
16758
- if (diff > 0) {
16759
- return "up";
16760
- }
16761
- else if (diff < 0) {
16762
- return "down";
16763
- }
16764
- return "neutral";
16765
- }
16766
- function getChartPositionAtCenterOfViewport(getters, chartSize) {
16767
- const { x, y } = getters.getMainViewportCoordinates();
16768
- const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
16769
- const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
16770
- const position = {
16771
- x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
16772
- y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
16773
- }; // Position at the center of the scrollable viewport
16774
- return position;
16775
- }
16776
-
16777
17104
  const PasteInteractiveContent = {
16778
17105
  wrongPasteSelection: _t("This operation is not allowed with multiple selections."),
16779
17106
  willRemoveExistingMerge: _t("This operation is not possible due to a merge. Please remove the merges first than try again."),
16780
17107
  wrongFigurePasteOption: _t("Cannot do a special paste of a figure."),
16781
- frozenPaneOverlap: _t("Cannot paste merged cells over a frozen pane."),
17108
+ frozenPaneOverlap: _t("This operation is not allowed due to an overlapping frozen pane."),
16782
17109
  };
16783
17110
  function handlePasteResult(env, result) {
16784
17111
  if (!result.isSuccessful) {
@@ -17121,6 +17448,51 @@
17121
17448
  return index - 1;
17122
17449
  }
17123
17450
  }
17451
+ function chartToImage(runtime, figure, type) {
17452
+ // wrap the canvas in a div with a fixed size because chart.js would
17453
+ // fill the whole page otherwise
17454
+ const div = document.createElement("div");
17455
+ div.style.width = `${figure.width}px`;
17456
+ div.style.height = `${figure.height}px`;
17457
+ const canvas = document.createElement("canvas");
17458
+ div.append(canvas);
17459
+ canvas.setAttribute("width", figure.width.toString());
17460
+ canvas.setAttribute("height", figure.height.toString());
17461
+ // we have to add the canvas to the DOM otherwise it won't be rendered
17462
+ document.body.append(div);
17463
+ if ("chartJsConfig" in runtime) {
17464
+ runtime.chartJsConfig.plugins = [backgroundColorChartJSPlugin];
17465
+ // @ts-ignore
17466
+ const chart = new window.Chart(canvas, runtime.chartJsConfig);
17467
+ const imgContent = chart.toBase64Image();
17468
+ chart.destroy();
17469
+ div.remove();
17470
+ return imgContent;
17471
+ }
17472
+ else if (type === "scorecard") {
17473
+ const design = getScorecardConfiguration(figure, runtime);
17474
+ drawScoreChart(design, canvas);
17475
+ const imgContent = canvas.toDataURL();
17476
+ div.remove();
17477
+ return imgContent;
17478
+ }
17479
+ return "";
17480
+ }
17481
+ /**
17482
+ * Custom chart.js plugin to set the background color of the canvas
17483
+ * https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
17484
+ */
17485
+ const backgroundColorChartJSPlugin = {
17486
+ id: "customCanvasBackgroundColor",
17487
+ beforeDraw: (chart) => {
17488
+ const { ctx } = chart;
17489
+ ctx.save();
17490
+ ctx.globalCompositeOperation = "destination-over";
17491
+ ctx.fillStyle = "#ffffff";
17492
+ ctx.fillRect(0, 0, chart.width, chart.height);
17493
+ ctx.restore();
17494
+ },
17495
+ };
17124
17496
 
17125
17497
  class BarChart extends AbstractChart {
17126
17498
  dataSets;
@@ -18191,172 +18563,6 @@
18191
18563
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
18192
18564
  }
18193
18565
 
18194
- function checkKeyValue(definition) {
18195
- return definition.keyValue && !rangeReference.test(definition.keyValue)
18196
- ? "InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */
18197
- : "Success" /* CommandResult.Success */;
18198
- }
18199
- function checkBaseline(definition) {
18200
- return definition.baseline && !rangeReference.test(definition.baseline)
18201
- ? "InvalidScorecardBaseline" /* CommandResult.InvalidScorecardBaseline */
18202
- : "Success" /* CommandResult.Success */;
18203
- }
18204
- class ScorecardChart extends AbstractChart {
18205
- keyValue;
18206
- baseline;
18207
- baselineMode;
18208
- baselineDescr;
18209
- background;
18210
- baselineColorUp;
18211
- baselineColorDown;
18212
- fontColor;
18213
- type = "scorecard";
18214
- constructor(definition, sheetId, getters) {
18215
- super(definition, sheetId, getters);
18216
- this.keyValue = createRange(getters, sheetId, definition.keyValue);
18217
- this.baseline = createRange(getters, sheetId, definition.baseline);
18218
- this.baselineMode = definition.baselineMode;
18219
- this.baselineDescr = definition.baselineDescr;
18220
- this.background = definition.background;
18221
- this.baselineColorUp = definition.baselineColorUp;
18222
- this.baselineColorDown = definition.baselineColorDown;
18223
- }
18224
- static validateChartDefinition(validator, definition) {
18225
- return validator.checkValidations(definition, checkKeyValue, checkBaseline);
18226
- }
18227
- static getDefinitionFromContextCreation(context) {
18228
- return {
18229
- background: context.background,
18230
- type: "scorecard",
18231
- keyValue: context.range ? context.range[0] : undefined,
18232
- title: context.title || "",
18233
- baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
18234
- baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
18235
- baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
18236
- baseline: context.auxiliaryRange || "",
18237
- };
18238
- }
18239
- static transformDefinition(definition, executed) {
18240
- let baselineZone;
18241
- let keyValueZone;
18242
- if (definition.baseline) {
18243
- baselineZone = transformZone(toUnboundedZone(definition.baseline), executed);
18244
- }
18245
- if (definition.keyValue) {
18246
- keyValueZone = transformZone(toUnboundedZone(definition.keyValue), executed);
18247
- }
18248
- return {
18249
- ...definition,
18250
- baseline: baselineZone ? zoneToXc(baselineZone) : undefined,
18251
- keyValue: keyValueZone ? zoneToXc(keyValueZone) : undefined,
18252
- };
18253
- }
18254
- copyForSheetId(sheetId) {
18255
- const baseline = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.baseline);
18256
- const keyValue = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.keyValue);
18257
- const definition = this.getDefinitionWithSpecificRanges(baseline, keyValue, sheetId);
18258
- return new ScorecardChart(definition, sheetId, this.getters);
18259
- }
18260
- copyInSheetId(sheetId) {
18261
- const definition = this.getDefinitionWithSpecificRanges(this.baseline, this.keyValue, sheetId);
18262
- return new ScorecardChart(definition, sheetId, this.getters);
18263
- }
18264
- getDefinition() {
18265
- return this.getDefinitionWithSpecificRanges(this.baseline, this.keyValue);
18266
- }
18267
- getContextCreation() {
18268
- return {
18269
- background: this.background,
18270
- title: this.title,
18271
- range: this.keyValue ? [this.getters.getRangeString(this.keyValue, this.sheetId)] : undefined,
18272
- auxiliaryRange: this.baseline
18273
- ? this.getters.getRangeString(this.baseline, this.sheetId)
18274
- : undefined,
18275
- };
18276
- }
18277
- getDefinitionWithSpecificRanges(baseline, keyValue, targetSheetId) {
18278
- return {
18279
- baselineColorDown: this.baselineColorDown,
18280
- baselineColorUp: this.baselineColorUp,
18281
- baselineMode: this.baselineMode,
18282
- title: this.title,
18283
- type: "scorecard",
18284
- background: this.background,
18285
- baseline: baseline
18286
- ? this.getters.getRangeString(baseline, targetSheetId || this.sheetId)
18287
- : undefined,
18288
- baselineDescr: this.baselineDescr,
18289
- keyValue: keyValue
18290
- ? this.getters.getRangeString(keyValue, targetSheetId || this.sheetId)
18291
- : undefined,
18292
- };
18293
- }
18294
- getDefinitionForExcel() {
18295
- // This kind of graph is not exportable in Excel
18296
- return undefined;
18297
- }
18298
- updateRanges(applyChange) {
18299
- const baseline = adaptChartRange(this.baseline, applyChange);
18300
- const keyValue = adaptChartRange(this.keyValue, applyChange);
18301
- if (this.baseline === baseline && this.keyValue === keyValue) {
18302
- return this;
18303
- }
18304
- const definition = this.getDefinitionWithSpecificRanges(baseline, keyValue);
18305
- return new ScorecardChart(definition, this.sheetId, this.getters);
18306
- }
18307
- }
18308
- function createScorecardChartRuntime(chart, getters) {
18309
- let keyValue = "";
18310
- let formattedKeyValue = "";
18311
- let keyValueCell;
18312
- if (chart.keyValue) {
18313
- const keyValuePosition = {
18314
- sheetId: chart.keyValue.sheetId,
18315
- col: chart.keyValue.zone.left,
18316
- row: chart.keyValue.zone.top,
18317
- };
18318
- keyValueCell = getters.getEvaluatedCell(keyValuePosition);
18319
- keyValue = String(keyValueCell.value);
18320
- formattedKeyValue = keyValueCell.formattedValue;
18321
- }
18322
- let baselineCell;
18323
- const baseline = chart.baseline;
18324
- if (baseline) {
18325
- const baselinePosition = {
18326
- sheetId: chart.baseline.sheetId,
18327
- col: chart.baseline.zone.left,
18328
- row: chart.baseline.zone.top,
18329
- };
18330
- baselineCell = getters.getEvaluatedCell(baselinePosition);
18331
- }
18332
- const background = getters.getBackgroundOfSingleCellChart(chart.background, chart.keyValue);
18333
- const locale = getters.getLocale();
18334
- return {
18335
- title: _t(chart.title),
18336
- keyValue: formattedKeyValue || keyValue,
18337
- baselineDisplay: getBaselineText(baselineCell, keyValueCell, chart.baselineMode, locale),
18338
- baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
18339
- baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
18340
- baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
18341
- fontColor: chartFontColor(background),
18342
- background,
18343
- baselineStyle: chart.baselineMode !== "percentage" && baseline
18344
- ? getters.getCellStyle({
18345
- sheetId: baseline.sheetId,
18346
- col: baseline.zone.left,
18347
- row: baseline.zone.top,
18348
- })
18349
- : undefined,
18350
- keyValueStyle: chart.keyValue
18351
- ? getters.getCellStyle({
18352
- sheetId: chart.keyValue.sheetId,
18353
- col: chart.keyValue.zone.left,
18354
- row: chart.keyValue.zone.top,
18355
- })
18356
- : undefined,
18357
- };
18358
- }
18359
-
18360
18566
  /**
18361
18567
  * This registry is intended to map a cell content (raw string) to
18362
18568
  * an instance of a cell.
@@ -18394,11 +18600,11 @@
18394
18600
  });
18395
18601
  chartRegistry.add("scorecard", {
18396
18602
  match: (type) => type === "scorecard",
18397
- createChart: (definition, sheetId, getters) => new ScorecardChart(definition, sheetId, getters),
18603
+ createChart: (definition, sheetId, getters) => new ScorecardChart$1(definition, sheetId, getters),
18398
18604
  getChartRuntime: createScorecardChartRuntime,
18399
- validateChartDefinition: (validator, definition) => ScorecardChart.validateChartDefinition(validator, definition),
18400
- transformDefinition: (definition, executed) => ScorecardChart.transformDefinition(definition, executed),
18401
- getChartDefinitionFromContextCreation: (context) => ScorecardChart.getDefinitionFromContextCreation(context),
18605
+ validateChartDefinition: (validator, definition) => ScorecardChart$1.validateChartDefinition(validator, definition),
18606
+ transformDefinition: (definition, executed) => ScorecardChart$1.transformDefinition(definition, executed),
18607
+ getChartDefinitionFromContextCreation: (context) => ScorecardChart$1.getDefinitionFromContextCreation(context),
18402
18608
  name: _t("Scorecard"),
18403
18609
  sequence: 40,
18404
18610
  });
@@ -18417,7 +18623,7 @@
18417
18623
  chartComponentRegistry.add("bar", ChartJsComponent);
18418
18624
  chartComponentRegistry.add("pie", ChartJsComponent);
18419
18625
  chartComponentRegistry.add("gauge", ChartJsComponent);
18420
- chartComponentRegistry.add("scorecard", ScorecardChart$1);
18626
+ chartComponentRegistry.add("scorecard", ScorecardChart);
18421
18627
 
18422
18628
  /**
18423
18629
  * Registry intended to support usual currencies. It is mainly used to create
@@ -19159,6 +19365,7 @@
19159
19365
  // Helpers
19160
19366
  //------------------------------------------------------------------------------
19161
19367
  function setFormatter(env, format) {
19368
+ env.model.dispatch("CANCEL_EDITION");
19162
19369
  env.model.dispatch("SET_FORMATTING", {
19163
19370
  sheetId: env.model.getters.getActiveSheetId(),
19164
19371
  target: env.model.getters.getSelectedZones(),
@@ -20270,109 +20477,126 @@
20270
20477
  icon: "o-spreadsheet-Icon.SPLIT_TEXT",
20271
20478
  };
20272
20479
 
20480
+ /**
20481
+ * Create a format action specification for a given format.
20482
+ * The format can be dynamically computed from the environment.
20483
+ */
20484
+ function createFormatActionSpec({ name, format, descriptionValue, }) {
20485
+ const formatCallback = typeof format === "function" ? format : () => format;
20486
+ return {
20487
+ name,
20488
+ description: (env) => formatValue(descriptionValue, {
20489
+ format: formatCallback(env),
20490
+ locale: env.model.getters.getLocale(),
20491
+ }),
20492
+ execute: (env) => setFormatter(env, formatCallback(env)),
20493
+ isActive: (env) => isFormatSelected(env, formatCallback(env)),
20494
+ };
20495
+ }
20273
20496
  const formatNumberAutomatic = {
20274
20497
  name: _t("Automatic"),
20275
20498
  execute: (env) => setFormatter(env, ""),
20276
20499
  isActive: (env) => isAutomaticFormatSelected(env),
20277
20500
  };
20278
- const formatNumberNumber = {
20501
+ const formatNumberNumber = createFormatActionSpec({
20279
20502
  name: _t("Number"),
20280
- description: (env) => formatValue(1000.12, {
20281
- format: "#,##0.00",
20282
- locale: env.model.getters.getLocale(),
20283
- }),
20284
- execute: (env) => setFormatter(env, "#,##0.00"),
20285
- isActive: (env) => isFormatSelected(env, "#,##0.00"),
20286
- };
20503
+ descriptionValue: 1000.12,
20504
+ format: "#,##0.00",
20505
+ });
20287
20506
  const formatPercent = {
20288
20507
  name: _t("Format as percent"),
20289
20508
  execute: FORMAT_PERCENT_ACTION,
20290
20509
  icon: "o-spreadsheet-Icon.PERCENT",
20291
20510
  };
20292
- const formatNumberPercent = {
20511
+ const formatNumberPercent = createFormatActionSpec({
20293
20512
  name: _t("Percent"),
20294
- description: (env) => formatValue(0.1012, {
20295
- format: "0.00%",
20296
- locale: env.model.getters.getLocale(),
20297
- }),
20298
- execute: FORMAT_PERCENT_ACTION,
20299
- isActive: (env) => isFormatSelected(env, "0.00%"),
20300
- };
20301
- const formatNumberCurrency = {
20513
+ descriptionValue: 0.1012,
20514
+ format: "0.00%",
20515
+ });
20516
+ const formatNumberCurrency = createFormatActionSpec({
20302
20517
  name: _t("Currency"),
20303
- description: (env) => formatValue(1000.12, {
20304
- format: env.model.config.defaultCurrencyFormat,
20305
- locale: env.model.getters.getLocale(),
20306
- }),
20307
- execute: (env) => setFormatter(env, env.model.config.defaultCurrencyFormat),
20308
- isActive: (env) => isFormatSelected(env, env.model.config.defaultCurrencyFormat),
20309
- };
20518
+ descriptionValue: 1000.12,
20519
+ format: (env) => env.model.config.defaultCurrencyFormat,
20520
+ });
20310
20521
  const formatNumberCurrencyRounded = {
20311
- name: _t("Currency rounded"),
20312
- description: (env) => formatValue(1000, {
20313
- format: roundFormat(env.model.config.defaultCurrencyFormat),
20314
- locale: env.model.getters.getLocale(),
20522
+ ...createFormatActionSpec({
20523
+ name: _t("Currency rounded"),
20524
+ descriptionValue: 1000,
20525
+ format: (env) => roundFormat(env.model.config.defaultCurrencyFormat),
20315
20526
  }),
20316
- execute: (env) => setFormatter(env, roundFormat(env.model.config.defaultCurrencyFormat)),
20317
- isActive: (env) => isFormatSelected(env, roundFormat(env.model.config.defaultCurrencyFormat)),
20318
20527
  isVisible: (env) => {
20319
20528
  const currencyFormat = env.model.config.defaultCurrencyFormat;
20320
20529
  return currencyFormat !== roundFormat(currencyFormat);
20321
20530
  },
20322
20531
  };
20532
+ const EXAMPLE_DATE = parseLiteral("2023/09/26 10:43:00 PM", DEFAULT_LOCALE);
20323
20533
  const formatCustomCurrency = {
20324
20534
  name: _t("Custom currency"),
20325
20535
  isVisible: (env) => env.loadCurrencies !== undefined,
20326
20536
  execute: (env) => env.openSidePanel("CustomCurrency", {}),
20327
20537
  };
20328
- const formatNumberDate = {
20538
+ const formatNumberDate = createFormatActionSpec({
20329
20539
  name: _t("Date"),
20330
- description: (env) => {
20331
- const locale = env.model.getters.getLocale();
20332
- return formatValue(parseLiteral("9/26/2023", DEFAULT_LOCALE), {
20333
- format: locale.dateFormat,
20334
- locale,
20335
- });
20336
- },
20337
- execute: (env) => setFormatter(env, env.model.getters.getLocale().dateFormat),
20338
- isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().dateFormat),
20339
- };
20340
- const formatNumberTime = {
20540
+ descriptionValue: EXAMPLE_DATE,
20541
+ format: (env) => env.model.getters.getLocale().dateFormat,
20542
+ });
20543
+ const formatNumberTime = createFormatActionSpec({
20341
20544
  name: _t("Time"),
20342
- description: (env) => {
20343
- const locale = env.model.getters.getLocale();
20344
- return formatValue(parseLiteral("9/26/2023 10:43:00 PM", DEFAULT_LOCALE), {
20345
- format: locale.timeFormat,
20346
- locale,
20347
- });
20348
- },
20349
- execute: (env) => setFormatter(env, env.model.getters.getLocale().timeFormat),
20350
- isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().timeFormat),
20351
- };
20352
- const formatNumberDateTime = {
20545
+ descriptionValue: EXAMPLE_DATE,
20546
+ format: (env) => env.model.getters.getLocale().timeFormat,
20547
+ });
20548
+ const formatNumberDateTime = createFormatActionSpec({
20353
20549
  name: _t("Date time"),
20354
- description: (env) => {
20355
- const locale = env.model.getters.getLocale();
20356
- return formatValue(parseLiteral("9/26/2023 22:43:00", DEFAULT_LOCALE), {
20357
- format: locale.dateFormat + " " + locale.timeFormat,
20358
- locale,
20359
- });
20360
- },
20361
- execute: (env) => {
20362
- const locale = env.model.getters.getLocale();
20363
- setFormatter(env, locale.dateFormat + " " + locale.timeFormat);
20364
- },
20365
- isActive: (env) => {
20550
+ descriptionValue: EXAMPLE_DATE,
20551
+ format: (env) => {
20366
20552
  const locale = env.model.getters.getLocale();
20367
- return isFormatSelected(env, locale.dateFormat + " " + locale.timeFormat);
20553
+ return locale.dateFormat + " " + locale.timeFormat;
20368
20554
  },
20369
- };
20370
- const formatNumberDuration = {
20555
+ });
20556
+ const formatNumberDuration = createFormatActionSpec({
20371
20557
  name: _t("Duration"),
20372
- description: "27:51:38",
20373
- execute: (env) => setFormatter(env, "hhhh:mm:ss"),
20374
- isActive: (env) => isFormatSelected(env, "hhhh:mm:ss"),
20375
- };
20558
+ descriptionValue: "27:51:38",
20559
+ format: "hhhh:mm:ss",
20560
+ });
20561
+ const moreFormats = {
20562
+ name: _t("More date formats"),
20563
+ execute: (env) => env.openSidePanel("MoreFormats", {}),
20564
+ };
20565
+ const formatNumberFullDateTime = createFormatActionSpec({
20566
+ name: _t("Full date time"),
20567
+ format: "dddd d mmmm yyyy hh:mm:ss a",
20568
+ descriptionValue: EXAMPLE_DATE,
20569
+ });
20570
+ const formatNumberFullWeekDayAndMonth = createFormatActionSpec({
20571
+ name: _t("Full week day and month"),
20572
+ format: "dddd d mmmm yyyy",
20573
+ descriptionValue: EXAMPLE_DATE,
20574
+ });
20575
+ const formatNumberDayAndFullMonth = createFormatActionSpec({
20576
+ name: _t("Day and full month"),
20577
+ format: "d mmmm yyyy",
20578
+ descriptionValue: EXAMPLE_DATE,
20579
+ });
20580
+ const formatNumberShortWeekDay = createFormatActionSpec({
20581
+ name: _t("Short week day"),
20582
+ format: "ddd d mmm yyyy",
20583
+ descriptionValue: EXAMPLE_DATE,
20584
+ });
20585
+ const formatNumberDayAndShortMonth = createFormatActionSpec({
20586
+ name: _t("Day and short month"),
20587
+ format: "d mmm yyyy",
20588
+ descriptionValue: EXAMPLE_DATE,
20589
+ });
20590
+ const formatNumberFullMonth = createFormatActionSpec({
20591
+ name: _t("Full month"),
20592
+ format: "mmmm yyyy",
20593
+ descriptionValue: EXAMPLE_DATE,
20594
+ });
20595
+ const formatNumberShortMonth = createFormatActionSpec({
20596
+ name: _t("Short month"),
20597
+ format: "mmm yyyy",
20598
+ descriptionValue: EXAMPLE_DATE,
20599
+ });
20376
20600
  const incraseDecimalPlaces = {
20377
20601
  name: _t("Increase decimal places"),
20378
20602
  icon: "o-spreadsheet-Icon.INCREASE_DECIMAL",
@@ -20625,9 +20849,16 @@
20625
20849
  formatNumberCurrencyRounded: formatNumberCurrencyRounded,
20626
20850
  formatNumberDate: formatNumberDate,
20627
20851
  formatNumberDateTime: formatNumberDateTime,
20852
+ formatNumberDayAndFullMonth: formatNumberDayAndFullMonth,
20853
+ formatNumberDayAndShortMonth: formatNumberDayAndShortMonth,
20628
20854
  formatNumberDuration: formatNumberDuration,
20855
+ formatNumberFullDateTime: formatNumberFullDateTime,
20856
+ formatNumberFullMonth: formatNumberFullMonth,
20857
+ formatNumberFullWeekDayAndMonth: formatNumberFullWeekDayAndMonth,
20629
20858
  formatNumberNumber: formatNumberNumber,
20630
20859
  formatNumberPercent: formatNumberPercent,
20860
+ formatNumberShortMonth: formatNumberShortMonth,
20861
+ formatNumberShortWeekDay: formatNumberShortWeekDay,
20631
20862
  formatNumberTime: formatNumberTime,
20632
20863
  formatPercent: formatPercent,
20633
20864
  formatStrikethrough: formatStrikethrough,
@@ -20638,6 +20869,7 @@
20638
20869
  formatWrappingOverflow: formatWrappingOverflow,
20639
20870
  formatWrappingWrap: formatWrappingWrap,
20640
20871
  incraseDecimalPlaces: incraseDecimalPlaces,
20872
+ moreFormats: moreFormats,
20641
20873
  textColor: textColor
20642
20874
  });
20643
20875
 
@@ -21095,6 +21327,10 @@
21095
21327
  ...formatNumberDuration,
21096
21328
  sequence: 100,
21097
21329
  separator: true,
21330
+ })
21331
+ .add("more_formats", {
21332
+ ...moreFormats,
21333
+ sequence: 110,
21098
21334
  });
21099
21335
  const formatNumberMenuItemSpec = {
21100
21336
  name: _t("More formats"),
@@ -22996,7 +23232,7 @@
22996
23232
  });
22997
23233
  }
22998
23234
  canUpdateChart(figureId, updateDefinition) {
22999
- if (figureId !== this.figureId) {
23235
+ if (figureId !== this.figureId || !this.env.model.getters.isChartDefined(figureId)) {
23000
23236
  return;
23001
23237
  }
23002
23238
  const definition = {
@@ -24363,6 +24599,44 @@
24363
24599
  onCloseSidePanel: Function,
24364
24600
  };
24365
24601
 
24602
+ css /* scss */ `
24603
+ .o-more-formats-panel {
24604
+ .format-preview {
24605
+ height: 48px;
24606
+ background-color: white;
24607
+
24608
+ &:hover {
24609
+ background-color: rgba(0, 0, 0, 0.08);
24610
+ }
24611
+ }
24612
+ .check-icon {
24613
+ width: 24px;
24614
+ }
24615
+ }
24616
+ `;
24617
+ const DATE_FORMAT_ACTIONS = createActions([
24618
+ formatNumberFullDateTime,
24619
+ formatNumberFullWeekDayAndMonth,
24620
+ formatNumberDayAndFullMonth,
24621
+ formatNumberShortWeekDay,
24622
+ formatNumberDayAndShortMonth,
24623
+ formatNumberFullMonth,
24624
+ formatNumberShortMonth,
24625
+ formatNumberDate,
24626
+ formatNumberTime,
24627
+ formatNumberDateTime,
24628
+ formatNumberDuration,
24629
+ ]);
24630
+ class MoreFormatsPanel extends owl.Component {
24631
+ static template = "o-spreadsheet-MoreFormatsPanel";
24632
+ get dateFormatsActions() {
24633
+ return DATE_FORMAT_ACTIONS;
24634
+ }
24635
+ }
24636
+ MoreFormatsPanel.props = {
24637
+ onCloseSidePanel: Function,
24638
+ };
24639
+
24366
24640
  css /* scss */ `
24367
24641
  .o-checkbox-selection {
24368
24642
  height: 150px;
@@ -25820,6 +26094,10 @@
25820
26094
  title: _t("Data validation"),
25821
26095
  Body: DataValidationPanel,
25822
26096
  });
26097
+ sidePanelRegistry.add("MoreFormats", {
26098
+ title: _t("More date formats"),
26099
+ Body: MoreFormatsPanel,
26100
+ });
25823
26101
 
25824
26102
  class TopBarComponentRegistry extends Registry {
25825
26103
  mapping = {};
@@ -28765,7 +29043,14 @@
28765
29043
  return;
28766
29044
  }
28767
29045
  if (this.state.waitingForMove === true) {
28768
- this.startMovement(ev);
29046
+ if (!this.env.model.getters.isGridSelectionActive()) {
29047
+ this._selectElement(index, false);
29048
+ }
29049
+ else {
29050
+ // FIXME: Consider reintroducing this feature for all type of selection if we find
29051
+ // a way to have the grid selection follow the other selections evolution
29052
+ this.startMovement(ev);
29053
+ }
28769
29054
  return;
28770
29055
  }
28771
29056
  if (this.env.model.getters.getEditionMode() === "editing") {
@@ -29846,6 +30131,8 @@
29846
30131
  const position = this.env.model.getters.getActivePosition();
29847
30132
  this.env.model.selection.selectZone({ cell: position, zone: newZone });
29848
30133
  },
30134
+ "CTRL+D": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ABOVE"),
30135
+ "CTRL+R": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ON_LEFT"),
29849
30136
  "CTRL+SHIFT+E": () => this.setHorizontalAlign("center"),
29850
30137
  "CTRL+SHIFT+L": () => this.setHorizontalAlign("left"),
29851
30138
  "CTRL+SHIFT+R": () => this.setHorizontalAlign("right"),
@@ -32280,12 +32567,15 @@
32280
32567
  };
32281
32568
  }
32282
32569
  function xmlEscape(str) {
32283
- return String(str)
32570
+ return (String(str)
32284
32571
  .replace(/\&/g, "&amp;")
32285
32572
  .replace(/\</g, "&lt;")
32286
32573
  .replace(/\>/g, "&gt;")
32287
32574
  .replace(/\"/g, "&quot;")
32288
- .replace(/\'/g, "&apos;");
32575
+ .replace(/\'/g, "&apos;")
32576
+ // Delete all ASCII control characters except for TAB (\x09), LF (\x0A) and CR (\x0D)
32577
+ // They are not valid at all in XML 1.0 (even escaped)
32578
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, ""));
32289
32579
  }
32290
32580
  function formatAttributes(attrs) {
32291
32581
  return new XMLString(attrs.map(([key, val]) => `${key}="${xmlEscape(val)}"`).join(" "));
@@ -35395,7 +35685,7 @@
35395
35685
  export(data) {
35396
35686
  if (data.sheets) {
35397
35687
  for (let sheet of data.sheets) {
35398
- // TODO This code is false, if two plugins want ot insert figures on the sheet, it will crash !
35688
+ // TODO This code is false, if two plugins want to insert figures on the sheet, it will crash !
35399
35689
  const sheetFigures = this.getters.getFigures(sheet.id);
35400
35690
  const figures = [];
35401
35691
  for (let sheetFigure of sheetFigures) {
@@ -35415,24 +35705,6 @@
35415
35705
  }
35416
35706
  }
35417
35707
  }
35418
- exportForExcel(data) {
35419
- for (let sheet of data.sheets) {
35420
- const sheetFigures = this.getters.getFigures(sheet.id);
35421
- const figures = [];
35422
- for (let figure of sheetFigures) {
35423
- if (figure && figure.tag === "chart") {
35424
- const figureData = this.charts[figure.id]?.getDefinitionForExcel();
35425
- if (figureData) {
35426
- figures.push({
35427
- ...figure,
35428
- data: figureData,
35429
- });
35430
- }
35431
- }
35432
- }
35433
- sheet.charts = figures;
35434
- }
35435
- }
35436
35708
  // ---------------------------------------------------------------------------
35437
35709
  // Private
35438
35710
  // ---------------------------------------------------------------------------
@@ -36954,6 +37226,9 @@
36954
37226
  }
36955
37227
  exportForExcel(data) {
36956
37228
  for (const sheet of data.sheets) {
37229
+ if (!sheet.images) {
37230
+ sheet.images = [];
37231
+ }
36957
37232
  const figures = this.getters.getFigures(sheet.id);
36958
37233
  const images = [];
36959
37234
  for (const figure of figures) {
@@ -36967,7 +37242,7 @@
36967
37242
  }
36968
37243
  }
36969
37244
  }
36970
- sheet.images = images;
37245
+ sheet.images = [...sheet.images, ...images];
36971
37246
  }
36972
37247
  }
36973
37248
  getAllImages() {
@@ -39910,6 +40185,7 @@
39910
40185
  config;
39911
40186
  static getters = [
39912
40187
  "evaluateFormula",
40188
+ "getCorrespondingFormulaCell",
39913
40189
  "getRangeFormattedValues",
39914
40190
  "getRangeValues",
39915
40191
  "getRangeFormats",
@@ -40306,6 +40582,47 @@
40306
40582
  const style = this.getters.getCellComputedStyle({ sheetId, col, row });
40307
40583
  return style.fillColor || BACKGROUND_CHART_COLOR;
40308
40584
  }
40585
+ exportForExcel(data) {
40586
+ for (const sheet of data.sheets) {
40587
+ if (!sheet.images) {
40588
+ sheet.images = [];
40589
+ }
40590
+ const sheetFigures = this.getters.getFigures(sheet.id);
40591
+ const figures = [];
40592
+ for (const figure of sheetFigures) {
40593
+ if (!figure || figure.tag !== "chart") {
40594
+ continue;
40595
+ }
40596
+ const figureId = figure.id;
40597
+ const figureData = this.getters.getChart(figureId)?.getDefinitionForExcel();
40598
+ if (figureData) {
40599
+ figures.push({
40600
+ ...figure,
40601
+ data: figureData,
40602
+ });
40603
+ }
40604
+ else {
40605
+ const chart = this.getters.getChart(figureId);
40606
+ if (!chart) {
40607
+ continue;
40608
+ }
40609
+ const type = this.getters.getChartType(figureId);
40610
+ const runtime = this.getters.getChartRuntime(figureId);
40611
+ const img = chartToImage(runtime, figure, type);
40612
+ sheet.images.push({
40613
+ ...figure,
40614
+ tag: "image",
40615
+ data: {
40616
+ mimetype: "image/png",
40617
+ path: img,
40618
+ size: { width: figure.width, height: figure.height },
40619
+ },
40620
+ });
40621
+ }
40622
+ }
40623
+ sheet.charts = figures;
40624
+ }
40625
+ }
40309
40626
  }
40310
40627
 
40311
40628
  class EvaluationConditionalFormatPlugin extends UIPlugin {
@@ -41922,7 +42239,7 @@
41922
42239
  { match: isZoneDependent, fn: transformZoneDependentCommand },
41923
42240
  { match: isPositionDependent, fn: transformPosition },
41924
42241
  { match: isHeadersDependant, fn: transformHeaders },
41925
- { match: isRangeDependant, fn: transformRangeData },
42242
+ { match: isRangeDependant, fn: transformRangesDependentCommand },
41926
42243
  ];
41927
42244
  /**
41928
42245
  * Get the result of applying the operation transformations on the given command
@@ -42018,23 +42335,13 @@
42018
42335
  }
42019
42336
  return "IGNORE_COMMAND";
42020
42337
  }
42021
- function transformRangeData(toTransform, executed) {
42338
+ function transformRangesDependentCommand(toTransform, executed) {
42022
42339
  if (!("sheetId" in executed)) {
42023
42340
  return toTransform;
42024
42341
  }
42025
- const ranges = [];
42026
- const deletedSheet = executed.type === "DELETE_SHEET" && executed.sheetId;
42027
- for (const range of toTransform.ranges) {
42028
- if (range._sheetId !== executed.sheetId) {
42029
- ranges.push({ ...range, _zone: range._zone });
42030
- }
42031
- else {
42032
- const newZone = transformZone(range._zone, executed);
42033
- if (newZone && deletedSheet !== range._sheetId) {
42034
- ranges.push({ ...range, _zone: newZone });
42035
- }
42036
- }
42037
- }
42342
+ const ranges = toTransform.ranges
42343
+ .map((range) => transformRangeData(range, executed))
42344
+ .filter(isDefined$1);
42038
42345
  if (!ranges.length) {
42039
42346
  return "IGNORE_COMMAND";
42040
42347
  }
@@ -43179,6 +43486,10 @@
43179
43486
  if (!newRangesXC) {
43180
43487
  return;
43181
43488
  }
43489
+ if (newRangesXC.length === 0) {
43490
+ this.dispatch("REMOVE_CONDITIONAL_FORMAT", { id: cf.id, sheetId });
43491
+ return;
43492
+ }
43182
43493
  this.dispatch("ADD_CONDITIONAL_FORMAT", {
43183
43494
  cf: {
43184
43495
  id: cf.id,
@@ -44094,7 +44405,7 @@
44094
44405
  }
44095
44406
  }
44096
44407
  drawTexts(renderingContext) {
44097
- const { ctx, thinLineWidth } = renderingContext;
44408
+ const { ctx } = renderingContext;
44098
44409
  ctx.textBaseline = "top";
44099
44410
  let currentFont;
44100
44411
  for (let box of this.boxes) {
@@ -44139,23 +44450,7 @@
44139
44450
  // use the horizontal and the vertical start points to:
44140
44451
  // fill text / fill strikethrough / fill underline
44141
44452
  for (let brokenLine of box.content.textLines) {
44142
- ctx.fillText(brokenLine, Math.round(x), Math.round(y));
44143
- if (style.strikethrough || style.underline) {
44144
- const lineWidth = computeTextWidth(ctx, brokenLine, style);
44145
- let _x = x;
44146
- if (align === "right") {
44147
- _x -= lineWidth;
44148
- }
44149
- else if (align === "center") {
44150
- _x -= lineWidth / 2;
44151
- }
44152
- if (style.strikethrough) {
44153
- ctx.fillRect(_x, y + textLineHeight / 2, lineWidth, 2.6 * thinLineWidth);
44154
- }
44155
- if (style.underline) {
44156
- ctx.fillRect(_x, y + textLineHeight + 1, lineWidth, 1.3 * thinLineWidth);
44157
- }
44158
- }
44453
+ drawDecoratedText(ctx, brokenLine, { x: Math.round(x), y: Math.round(y) }, style.underline, style.strikethrough);
44159
44454
  y += MIN_CELL_TEXT_MARGIN + textLineHeight;
44160
44455
  }
44161
44456
  if (box.clipRect) {
@@ -46240,6 +46535,20 @@
46240
46535
  const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
46241
46536
  return state.isPasteAllowed(cmd.target, { pasteOption: cmd.pasteOption });
46242
46537
  }
46538
+ case "COPY_PASTE_CELLS_ABOVE": {
46539
+ const zones = this.getters.getSelectedZones();
46540
+ if (zones.length > 1 || (zones[0].top === 0 && zones[0].bottom === 0)) {
46541
+ return "InvalidCopyPasteSelection" /* CommandResult.InvalidCopyPasteSelection */;
46542
+ }
46543
+ break;
46544
+ }
46545
+ case "COPY_PASTE_CELLS_ON_LEFT": {
46546
+ const zones = this.getters.getSelectedZones();
46547
+ if (zones.length > 1 || (zones[0].left === 0 && zones[0].right === 0)) {
46548
+ return "InvalidCopyPasteSelection" /* CommandResult.InvalidCopyPasteSelection */;
46549
+ }
46550
+ break;
46551
+ }
46243
46552
  case "INSERT_CELL": {
46244
46553
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
46245
46554
  const state = this.getClipboardStateForCopyCells(cut, "CUT");
@@ -46279,6 +46588,40 @@
46279
46588
  this.status = "invisible";
46280
46589
  }
46281
46590
  break;
46591
+ case "COPY_PASTE_CELLS_ABOVE":
46592
+ {
46593
+ const zone = this.getters.getSelectedZone();
46594
+ const multipleRowsInSelection = zone.top !== zone.bottom;
46595
+ const copyTarget = {
46596
+ ...zone,
46597
+ bottom: multipleRowsInSelection ? zone.top : zone.top - 1,
46598
+ top: multipleRowsInSelection ? zone.top : zone.top - 1,
46599
+ };
46600
+ const state = this.getClipboardStateForCopyCells([copyTarget], "COPY");
46601
+ state.paste([zone], {
46602
+ pasteOption: undefined,
46603
+ shouldPasteCF: true,
46604
+ selectTarget: true,
46605
+ });
46606
+ }
46607
+ break;
46608
+ case "COPY_PASTE_CELLS_ON_LEFT":
46609
+ {
46610
+ const zone = this.getters.getSelectedZone();
46611
+ const multipleColsInSelection = zone.left !== zone.right;
46612
+ const copyTarget = {
46613
+ ...zone,
46614
+ right: multipleColsInSelection ? zone.left : zone.left - 1,
46615
+ left: multipleColsInSelection ? zone.left : zone.left - 1,
46616
+ };
46617
+ const state = this.getClipboardStateForCopyCells([copyTarget], "COPY");
46618
+ state.paste([zone], {
46619
+ pasteOption: undefined,
46620
+ shouldPasteCF: true,
46621
+ selectTarget: true,
46622
+ });
46623
+ }
46624
+ break;
46282
46625
  case "CLEAN_CLIPBOARD_HIGHLIGHT":
46283
46626
  this.status = "invisible";
46284
46627
  break;
@@ -46958,7 +47301,15 @@
46958
47301
  return cell?.content || "";
46959
47302
  case CellValueType.number:
46960
47303
  if (format && isDateTimeFormat(format)) {
46961
- return formattedValue;
47304
+ if (parseDateTime(formattedValue, locale) !== null) {
47305
+ // formatted string can be parsed again
47306
+ return formattedValue;
47307
+ }
47308
+ // display a simplified and parsable string otherwise
47309
+ const timeFormat = Number.isInteger(value)
47310
+ ? locale.dateFormat
47311
+ : locale.dateFormat + " " + locale.timeFormat;
47312
+ return formatValue(value, { locale, format: timeFormat });
46962
47313
  }
46963
47314
  return this.numberComposerContent(value, format, locale);
46964
47315
  }
@@ -47513,6 +47864,7 @@
47513
47864
  "isSingleColSelected",
47514
47865
  "getElementsFromSelection",
47515
47866
  "tryGetActiveSheetId",
47867
+ "isGridSelectionActive",
47516
47868
  ];
47517
47869
  gridSelection = {
47518
47870
  anchor: {
@@ -47722,6 +48074,9 @@
47722
48074
  // ---------------------------------------------------------------------------
47723
48075
  // Getters
47724
48076
  // ---------------------------------------------------------------------------
48077
+ isGridSelectionActive() {
48078
+ return this.selection.isListening(this);
48079
+ }
47725
48080
  getActiveSheet() {
47726
48081
  return this.activeSheet;
47727
48082
  }
@@ -47806,11 +48161,19 @@
47806
48161
  }
47807
48162
  }
47808
48163
  getStatisticFnResults() {
47809
- // get deduplicated cells in zones
47810
- const cells = new Set(this.gridSelection.zones
47811
- .map((zone) => this.getters.getEvaluatedCellsInZone(this.getters.getActiveSheetId(), zone))
47812
- .flat()
47813
- .filter((cell) => cell.type !== CellValueType.empty));
48164
+ const sheetId = this.getters.getActiveSheetId();
48165
+ const cells = new Set();
48166
+ for (const zone of this.gridSelection.zones) {
48167
+ for (const { col, row } of positions(zone)) {
48168
+ if (this.getters.isRowHidden(sheetId, row) || this.getters.isColHidden(sheetId, col)) {
48169
+ continue; // Skip hidden cells
48170
+ }
48171
+ const evaluatedCell = this.getters.getEvaluatedCell({ sheetId, col, row });
48172
+ if (evaluatedCell.type !== CellValueType.empty) {
48173
+ cells.add(evaluatedCell);
48174
+ }
48175
+ }
48176
+ }
47814
48177
  let cellsTypes = new Set();
47815
48178
  let cellsValues = [];
47816
48179
  for (let cell of cells) {
@@ -52673,6 +53036,9 @@
52673
53036
  },
52674
53037
  });
52675
53038
  }
53039
+ isListening(owner) {
53040
+ return this.stream.isListening(owner);
53041
+ }
52676
53042
  /**
52677
53043
  * Process a new anchor selection event. If the new anchor is inside
52678
53044
  * the sheet boundaries, the event is pushed to the event stream to
@@ -55061,8 +55427,10 @@
55061
55427
  toString,
55062
55428
  toXC,
55063
55429
  toZone,
55430
+ toUnboundedZone,
55064
55431
  toCartesian,
55065
55432
  numberToLetters,
55433
+ lettersToNumber,
55066
55434
  UuidGenerator,
55067
55435
  formatValue,
55068
55436
  createCurrencyFormat,
@@ -55084,6 +55452,7 @@
55084
55452
  genericRepeat,
55085
55453
  createAction,
55086
55454
  createActions,
55455
+ transformRangeData,
55087
55456
  };
55088
55457
  const links = {
55089
55458
  isMarkdownLink,
@@ -55098,7 +55467,7 @@
55098
55467
  ChartJsComponent,
55099
55468
  Grid,
55100
55469
  GridOverlay,
55101
- ScorecardChart: ScorecardChart$1,
55470
+ ScorecardChart,
55102
55471
  LineConfigPanel,
55103
55472
  LineBarPieDesignPanel,
55104
55473
  BarConfigPanel,
@@ -55109,6 +55478,7 @@
55109
55478
  ScorecardChartDesignPanel,
55110
55479
  FigureComponent,
55111
55480
  Menu,
55481
+ SelectionInput,
55112
55482
  };
55113
55483
  const hooks = {
55114
55484
  useDragAndDropListItems,
@@ -55159,9 +55529,9 @@
55159
55529
  exports.tokenize = tokenize;
55160
55530
 
55161
55531
 
55162
- __info__.version = "16.5.0-alpha.10";
55163
- __info__.date = "2023-10-10T07:31:20.876Z";
55164
- __info__.hash = "4dc340f";
55532
+ __info__.version = "16.5.0-alpha.12";
55533
+ __info__.date = "2023-10-20T15:12:28.296Z";
55534
+ __info__.hash = "430f090";
55165
55535
 
55166
55536
 
55167
55537
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);