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