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