@odoo/o-spreadsheet 18.1.1 → 18.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.1.1
6
- * @date 2025-01-14T11:43:19.116Z
7
- * @hash d0fa5de
5
+ * @version 18.2.0-alpha.1
6
+ * @date 2025-01-14T11:35:51.135Z
7
+ * @hash 702f816
8
8
  */
9
9
 
10
10
  'use strict';
@@ -357,6 +357,7 @@ var ComponentsImportance;
357
357
  ComponentsImportance[ComponentsImportance["Popover"] = 35] = "Popover";
358
358
  ComponentsImportance[ComponentsImportance["FigureAnchor"] = 1000] = "FigureAnchor";
359
359
  ComponentsImportance[ComponentsImportance["FigureSnapLine"] = 1001] = "FigureSnapLine";
360
+ ComponentsImportance[ComponentsImportance["FigureTooltip"] = 1002] = "FigureTooltip";
360
361
  })(ComponentsImportance || (ComponentsImportance = {}));
361
362
  let DEFAULT_SHEETVIEW_SIZE = 0;
362
363
  function getDefaultSheetViewSize() {
@@ -976,6 +977,16 @@ function transpose2dPOJO(pojo) {
976
977
  }
977
978
  return result;
978
979
  }
980
+ function getUniqueText(text, texts, options = {}) {
981
+ const compute = options.compute ?? ((text, i) => `${text} (${i})`);
982
+ const computeFirstOne = options.computeFirstOne ?? false;
983
+ let i = options.start ?? 1;
984
+ let newText = computeFirstOne ? compute(text, i) : text;
985
+ while (texts.includes(newText)) {
986
+ newText = compute(text, i++);
987
+ }
988
+ return newText;
989
+ }
979
990
 
980
991
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
981
992
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
@@ -6029,10 +6040,10 @@ class RangeImpl {
6029
6040
  }
6030
6041
  }
6031
6042
  /**
6032
- * Copy a range. If the range is on the sheetIdFrom, the range will target
6043
+ * Duplicate a range. If the range is on the sheetIdFrom, the range will target
6033
6044
  * sheetIdTo.
6034
6045
  */
6035
- function copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
6046
+ function duplicateRangeInDuplicatedSheet(sheetIdFrom, sheetIdTo, range) {
6036
6047
  const sheetId = range.sheetId === sheetIdFrom ? sheetIdTo : range.sheetId;
6037
6048
  return range.clone({ sheetId });
6038
6049
  }
@@ -9465,6 +9476,150 @@ class ComposerFocusStore extends SpreadsheetStore {
9465
9476
  }
9466
9477
  }
9467
9478
 
9479
+ /**
9480
+ * This file is largely inspired by owl 1.
9481
+ * `css` tag has been removed from owl 2 without workaround to manage css.
9482
+ * So, the solution was to import the behavior of owl 1 directly in our
9483
+ * codebase, with one difference: the css is added to the sheet as soon as the
9484
+ * css tag is executed. In owl 1, the css was added as soon as a Component was
9485
+ * created for the first time.
9486
+ */
9487
+ const STYLESHEETS = {};
9488
+ let nextId = 0;
9489
+ /**
9490
+ * CSS tag helper for defining inline stylesheets. With this, one can simply define
9491
+ * an inline stylesheet with just the following code:
9492
+ * ```js
9493
+ * css`.component-a { color: red; }`;
9494
+ * ```
9495
+ */
9496
+ function css(strings, ...args) {
9497
+ const name = `__sheet__${nextId++}`;
9498
+ const value = String.raw(strings, ...args);
9499
+ registerSheet(name, value);
9500
+ activateSheet(name);
9501
+ return name;
9502
+ }
9503
+ function processSheet(str) {
9504
+ const tokens = str.split(/(\{|\}|;)/).map((s) => s.trim());
9505
+ const selectorStack = [];
9506
+ const parts = [];
9507
+ let rules = [];
9508
+ function generateSelector(stackIndex, parentSelector) {
9509
+ const parts = [];
9510
+ for (const selector of selectorStack[stackIndex]) {
9511
+ let part = (parentSelector && parentSelector + " " + selector) || selector;
9512
+ if (part.includes("&")) {
9513
+ part = selector.replace(/&/g, parentSelector || "");
9514
+ }
9515
+ if (stackIndex < selectorStack.length - 1) {
9516
+ part = generateSelector(stackIndex + 1, part);
9517
+ }
9518
+ parts.push(part);
9519
+ }
9520
+ return parts.join(", ");
9521
+ }
9522
+ function generateRules() {
9523
+ if (rules.length) {
9524
+ parts.push(generateSelector(0) + " {");
9525
+ parts.push(...rules);
9526
+ parts.push("}");
9527
+ rules = [];
9528
+ }
9529
+ }
9530
+ while (tokens.length) {
9531
+ let token = tokens.shift();
9532
+ if (token === "}") {
9533
+ generateRules();
9534
+ selectorStack.pop();
9535
+ }
9536
+ else {
9537
+ if (tokens[0] === "{") {
9538
+ generateRules();
9539
+ selectorStack.push(token.split(/\s*,\s*/));
9540
+ tokens.shift();
9541
+ }
9542
+ if (tokens[0] === ";") {
9543
+ rules.push(" " + token + ";");
9544
+ }
9545
+ }
9546
+ }
9547
+ return parts.join("\n");
9548
+ }
9549
+ function registerSheet(id, css) {
9550
+ const sheet = document.createElement("style");
9551
+ sheet.textContent = processSheet(css);
9552
+ STYLESHEETS[id] = sheet;
9553
+ }
9554
+ function activateSheet(id) {
9555
+ const sheet = STYLESHEETS[id];
9556
+ sheet.setAttribute("component", id);
9557
+ document.head.appendChild(sheet);
9558
+ }
9559
+ function getTextDecoration({ strikethrough, underline, }) {
9560
+ if (!strikethrough && !underline) {
9561
+ return "none";
9562
+ }
9563
+ return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`;
9564
+ }
9565
+ /**
9566
+ * Convert the cell style to CSS properties.
9567
+ */
9568
+ function cellStyleToCss(style) {
9569
+ const attributes = cellTextStyleToCss(style);
9570
+ if (!style)
9571
+ return attributes;
9572
+ if (style.fillColor) {
9573
+ attributes["background"] = style.fillColor;
9574
+ }
9575
+ return attributes;
9576
+ }
9577
+ /**
9578
+ * Convert the cell text style to CSS properties.
9579
+ */
9580
+ function cellTextStyleToCss(style) {
9581
+ const attributes = {};
9582
+ if (!style)
9583
+ return attributes;
9584
+ if (style.bold) {
9585
+ attributes["font-weight"] = "bold";
9586
+ }
9587
+ if (style.italic) {
9588
+ attributes["font-style"] = "italic";
9589
+ }
9590
+ if (style.strikethrough || style.underline) {
9591
+ let decoration = style.strikethrough ? "line-through" : "";
9592
+ decoration = style.underline ? decoration + " underline" : decoration;
9593
+ attributes["text-decoration"] = decoration;
9594
+ }
9595
+ if (style.textColor) {
9596
+ attributes["color"] = style.textColor;
9597
+ }
9598
+ return attributes;
9599
+ }
9600
+ /**
9601
+ * Transform CSS properties into a CSS string.
9602
+ */
9603
+ function cssPropertiesToCss(attributes) {
9604
+ let styleStr = "";
9605
+ for (const attName in attributes) {
9606
+ if (!attributes[attName]) {
9607
+ continue;
9608
+ }
9609
+ styleStr += `${attName}:${attributes[attName]}; `;
9610
+ }
9611
+ return styleStr;
9612
+ }
9613
+ function getElementMargins(el) {
9614
+ const style = window.getComputedStyle(el);
9615
+ return {
9616
+ top: parseInt(style.marginTop, 10) || 0,
9617
+ bottom: parseInt(style.marginBottom, 10) || 0,
9618
+ left: parseInt(style.marginLeft, 10) || 0,
9619
+ right: parseInt(style.marginRight, 10) || 0,
9620
+ };
9621
+ }
9622
+
9468
9623
  const TREND_LINE_XAXIS_ID = "x1";
9469
9624
  /**
9470
9625
  * This file contains helpers that are common to different charts (mainly
@@ -9517,25 +9672,25 @@ function updateChartRangesWithDataSets(getters, applyChange, chartDataSets, char
9517
9672
  };
9518
9673
  }
9519
9674
  /**
9520
- * Copy the dataSets given. All the ranges which are on sheetIdFrom will target
9675
+ * Duplicate the dataSets. All ranges on sheetIdFrom are adapted to target
9521
9676
  * sheetIdTo.
9522
9677
  */
9523
- function copyDataSetsWithNewSheetId(sheetIdFrom, sheetIdTo, dataSets) {
9678
+ function duplicateDataSetsInDuplicatedSheet(sheetIdFrom, sheetIdTo, dataSets) {
9524
9679
  return dataSets.map((ds) => {
9525
9680
  return {
9526
- dataRange: copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, ds.dataRange),
9681
+ dataRange: duplicateRangeInDuplicatedSheet(sheetIdFrom, sheetIdTo, ds.dataRange),
9527
9682
  labelCell: ds.labelCell
9528
- ? copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, ds.labelCell)
9683
+ ? duplicateRangeInDuplicatedSheet(sheetIdFrom, sheetIdTo, ds.labelCell)
9529
9684
  : undefined,
9530
9685
  };
9531
9686
  });
9532
9687
  }
9533
9688
  /**
9534
- * Copy a range. If the range is on the sheetIdFrom, the range will target
9689
+ * Duplicate a range. If the range is on the sheetIdFrom, the range will target
9535
9690
  * sheetIdTo.
9536
9691
  */
9537
- function copyLabelRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
9538
- return range ? copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) : undefined;
9692
+ function duplicateLabelRangeInDuplicatedSheet(sheetIdFrom, sheetIdTo, range) {
9693
+ return range ? duplicateRangeInDuplicatedSheet(sheetIdFrom, sheetIdTo, range) : undefined;
9539
9694
  }
9540
9695
  /**
9541
9696
  * Adapt a single range of a chart
@@ -9788,10 +9943,11 @@ function formatTickValue(localeFormat) {
9788
9943
  if (isNaN(value))
9789
9944
  return value;
9790
9945
  const { locale, format } = localeFormat;
9791
- return formatValue(value, {
9946
+ const formattedValue = formatValue(value, {
9792
9947
  locale,
9793
9948
  format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
9794
9949
  });
9950
+ return truncateLabel(formattedValue);
9795
9951
  };
9796
9952
  }
9797
9953
  const CHART_AXIS_CHOICES = [
@@ -9806,6 +9962,15 @@ function getPieColors(colors, dataSetsValues) {
9806
9962
  }
9807
9963
  return pieColors;
9808
9964
  }
9965
+ function truncateLabel(label) {
9966
+ if (!label) {
9967
+ return "";
9968
+ }
9969
+ if (label.length > MAX_CHAR_LABEL) {
9970
+ return label.substring(0, MAX_CHAR_LABEL) + "…";
9971
+ }
9972
+ return label;
9973
+ }
9809
9974
 
9810
9975
  /** This is a chartJS plugin that will draw the values of each data next to the point/bar/pie slice */
9811
9976
  const chartShowValuesPlugin = {
@@ -10001,6 +10166,18 @@ function getNextNonEmptyBar(bars, startIndex) {
10001
10166
 
10002
10167
  window.Chart?.register(waterfallLinesPlugin);
10003
10168
  window.Chart?.register(chartShowValuesPlugin);
10169
+ css /* scss */ `
10170
+ .o-spreadsheet {
10171
+ .o-chart-custom-tooltip {
10172
+ font-size: 12px;
10173
+ background-color: #fff;
10174
+ z-index: ${ComponentsImportance.FigureTooltip};
10175
+ table td span {
10176
+ box-sizing: border-box;
10177
+ }
10178
+ }
10179
+ }
10180
+ `;
10004
10181
  class ChartJsComponent extends owl.Component {
10005
10182
  static template = "o-spreadsheet-ChartJsComponent";
10006
10183
  static props = {
@@ -10239,11 +10416,11 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10239
10416
  keyValue: keyValueZone ? zoneToXc(keyValueZone) : undefined,
10240
10417
  };
10241
10418
  }
10242
- copyForSheetId(sheetId) {
10243
- const baseline = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.baseline);
10244
- const keyValue = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.keyValue);
10245
- const definition = this.getDefinitionWithSpecificRanges(baseline, keyValue, sheetId);
10246
- return new ScorecardChart(definition, sheetId, this.getters);
10419
+ duplicateInDuplicatedSheet(newSheetId) {
10420
+ const baseline = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.baseline);
10421
+ const keyValue = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.keyValue);
10422
+ const definition = this.getDefinitionWithSpecificRanges(baseline, keyValue, newSheetId);
10423
+ return new ScorecardChart(definition, newSheetId, this.getters);
10247
10424
  }
10248
10425
  copyInSheetId(sheetId) {
10249
10426
  const definition = this.getDefinitionWithSpecificRanges(this.baseline, this.keyValue, sheetId);
@@ -22570,15 +22747,6 @@ const CHART_COMMON_OPTIONS = {
22570
22747
  },
22571
22748
  animation: false,
22572
22749
  };
22573
- function truncateLabel(label) {
22574
- if (!label) {
22575
- return "";
22576
- }
22577
- if (label.length > MAX_CHAR_LABEL) {
22578
- return label.substring(0, MAX_CHAR_LABEL) + "…";
22579
- }
22580
- return label;
22581
- }
22582
22750
  function chartToImage(runtime, figure, type) {
22583
22751
  // wrap the canvas in a div with a fixed size because chart.js would
22584
22752
  // fill the whole page otherwise
@@ -22900,150 +23068,6 @@ const CONTENT_TYPES_FILE = "[Content_Types].xml";
22900
23068
  */
22901
23069
  const iconsOnCellRegistry = new Registry();
22902
23070
 
22903
- /**
22904
- * This file is largely inspired by owl 1.
22905
- * `css` tag has been removed from owl 2 without workaround to manage css.
22906
- * So, the solution was to import the behavior of owl 1 directly in our
22907
- * codebase, with one difference: the css is added to the sheet as soon as the
22908
- * css tag is executed. In owl 1, the css was added as soon as a Component was
22909
- * created for the first time.
22910
- */
22911
- const STYLESHEETS = {};
22912
- let nextId = 0;
22913
- /**
22914
- * CSS tag helper for defining inline stylesheets. With this, one can simply define
22915
- * an inline stylesheet with just the following code:
22916
- * ```js
22917
- * css`.component-a { color: red; }`;
22918
- * ```
22919
- */
22920
- function css(strings, ...args) {
22921
- const name = `__sheet__${nextId++}`;
22922
- const value = String.raw(strings, ...args);
22923
- registerSheet(name, value);
22924
- activateSheet(name);
22925
- return name;
22926
- }
22927
- function processSheet(str) {
22928
- const tokens = str.split(/(\{|\}|;)/).map((s) => s.trim());
22929
- const selectorStack = [];
22930
- const parts = [];
22931
- let rules = [];
22932
- function generateSelector(stackIndex, parentSelector) {
22933
- const parts = [];
22934
- for (const selector of selectorStack[stackIndex]) {
22935
- let part = (parentSelector && parentSelector + " " + selector) || selector;
22936
- if (part.includes("&")) {
22937
- part = selector.replace(/&/g, parentSelector || "");
22938
- }
22939
- if (stackIndex < selectorStack.length - 1) {
22940
- part = generateSelector(stackIndex + 1, part);
22941
- }
22942
- parts.push(part);
22943
- }
22944
- return parts.join(", ");
22945
- }
22946
- function generateRules() {
22947
- if (rules.length) {
22948
- parts.push(generateSelector(0) + " {");
22949
- parts.push(...rules);
22950
- parts.push("}");
22951
- rules = [];
22952
- }
22953
- }
22954
- while (tokens.length) {
22955
- let token = tokens.shift();
22956
- if (token === "}") {
22957
- generateRules();
22958
- selectorStack.pop();
22959
- }
22960
- else {
22961
- if (tokens[0] === "{") {
22962
- generateRules();
22963
- selectorStack.push(token.split(/\s*,\s*/));
22964
- tokens.shift();
22965
- }
22966
- if (tokens[0] === ";") {
22967
- rules.push(" " + token + ";");
22968
- }
22969
- }
22970
- }
22971
- return parts.join("\n");
22972
- }
22973
- function registerSheet(id, css) {
22974
- const sheet = document.createElement("style");
22975
- sheet.textContent = processSheet(css);
22976
- STYLESHEETS[id] = sheet;
22977
- }
22978
- function activateSheet(id) {
22979
- const sheet = STYLESHEETS[id];
22980
- sheet.setAttribute("component", id);
22981
- document.head.appendChild(sheet);
22982
- }
22983
- function getTextDecoration({ strikethrough, underline, }) {
22984
- if (!strikethrough && !underline) {
22985
- return "none";
22986
- }
22987
- return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`;
22988
- }
22989
- /**
22990
- * Convert the cell style to CSS properties.
22991
- */
22992
- function cellStyleToCss(style) {
22993
- const attributes = cellTextStyleToCss(style);
22994
- if (!style)
22995
- return attributes;
22996
- if (style.fillColor) {
22997
- attributes["background"] = style.fillColor;
22998
- }
22999
- return attributes;
23000
- }
23001
- /**
23002
- * Convert the cell text style to CSS properties.
23003
- */
23004
- function cellTextStyleToCss(style) {
23005
- const attributes = {};
23006
- if (!style)
23007
- return attributes;
23008
- if (style.bold) {
23009
- attributes["font-weight"] = "bold";
23010
- }
23011
- if (style.italic) {
23012
- attributes["font-style"] = "italic";
23013
- }
23014
- if (style.strikethrough || style.underline) {
23015
- let decoration = style.strikethrough ? "line-through" : "";
23016
- decoration = style.underline ? decoration + " underline" : decoration;
23017
- attributes["text-decoration"] = decoration;
23018
- }
23019
- if (style.textColor) {
23020
- attributes["color"] = style.textColor;
23021
- }
23022
- return attributes;
23023
- }
23024
- /**
23025
- * Transform CSS properties into a CSS string.
23026
- */
23027
- function cssPropertiesToCss(attributes) {
23028
- let styleStr = "";
23029
- for (const attName in attributes) {
23030
- if (!attributes[attName]) {
23031
- continue;
23032
- }
23033
- styleStr += `${attName}:${attributes[attName]}; `;
23034
- }
23035
- return styleStr;
23036
- }
23037
- function getElementMargins(el) {
23038
- const style = window.getComputedStyle(el);
23039
- return {
23040
- top: parseInt(style.marginTop, 10) || 0,
23041
- bottom: parseInt(style.marginBottom, 10) || 0,
23042
- left: parseInt(style.marginLeft, 10) || 0,
23043
- right: parseInt(style.marginRight, 10) || 0,
23044
- };
23045
- }
23046
-
23047
23071
  css /* scss */ `
23048
23072
  .o-spreadsheet {
23049
23073
  .o-icon {
@@ -27163,12 +27187,9 @@ migrationStepRegistry
27163
27187
  }
27164
27188
  const oldName = sheet.name;
27165
27189
  const escapedName = sanitizeSheetName(oldName, "_");
27166
- let i = 1;
27167
- let newName = escapedName;
27168
- while (namesTaken.includes(newName)) {
27169
- newName = `${escapedName}${i}`;
27170
- i++;
27171
- }
27190
+ const newName = getUniqueText(escapedName, namesTaken, {
27191
+ compute: (name, i) => `${name}${i}`,
27192
+ });
27172
27193
  sheet.name = newName;
27173
27194
  namesTaken.push(newName);
27174
27195
  const replaceName = (str) => {
@@ -28701,7 +28722,7 @@ function getChartDatasetValues(getters, dataSets) {
28701
28722
  : undefined;
28702
28723
  label =
28703
28724
  cell && labelRange
28704
- ? truncateLabel(cell.formattedValue)
28725
+ ? cell.formattedValue
28705
28726
  : (label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`);
28706
28727
  }
28707
28728
  else {
@@ -28791,7 +28812,7 @@ function getWaterfallDatasetAndLabels(definition, args) {
28791
28812
  }
28792
28813
  return {
28793
28814
  datasets: [dataset],
28794
- labels: labelsWithSubTotals.map(truncateLabel),
28815
+ labels: labelsWithSubTotals,
28795
28816
  };
28796
28817
  }
28797
28818
  function getLineChartDatasets(definition, args) {
@@ -29040,7 +29061,7 @@ function getPieChartLegend(definition, args) {
29040
29061
  generateLabels: (c) =>
29041
29062
  //@ts-ignore
29042
29063
  c.data.labels.map((label, index) => ({
29043
- text: label,
29064
+ text: truncateLabel(String(label)),
29044
29065
  strokeStyle: colors[index],
29045
29066
  fillStyle: colors[index],
29046
29067
  pointStyle: "rect",
@@ -29171,7 +29192,7 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
29171
29192
  generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
29172
29193
  if (dataset["xAxisID"] === TREND_LINE_XAXIS_ID) {
29173
29194
  return {
29174
- text: dataset.label ?? "",
29195
+ text: truncateLabel(dataset.label),
29175
29196
  fontColor,
29176
29197
  strokeStyle: dataset.borderColor,
29177
29198
  hidden: !chart.isDatasetVisible(index),
@@ -29181,7 +29202,7 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
29181
29202
  };
29182
29203
  }
29183
29204
  return {
29184
- text: dataset.label ?? "",
29205
+ text: truncateLabel(dataset.label),
29185
29206
  fontColor,
29186
29207
  strokeStyle: dataset.borderColor,
29187
29208
  fillStyle: dataset.backgroundColor,
@@ -29328,7 +29349,10 @@ function getRadarChartScales(definition, args) {
29328
29349
  callback: formatTickValue({ format: axisFormats?.r, locale }),
29329
29350
  backdropColor: definition.background || "#FFFFFF",
29330
29351
  },
29331
- pointLabels: { color: chartFontColor(definition.background) },
29352
+ pointLabels: {
29353
+ color: chartFontColor(definition.background),
29354
+ callback: truncateLabel,
29355
+ },
29332
29356
  suggestedMin: minValue < 0 ? minValue - 1 : 0,
29333
29357
  },
29334
29358
  };
@@ -29425,6 +29449,11 @@ function getChartAxis(definition, position, type, options) {
29425
29449
  ticks: {
29426
29450
  padding: 5,
29427
29451
  color: fontColor,
29452
+ callback: function (tickValue) {
29453
+ // Category axis callback's internal tick value is the index of the label
29454
+ // https://www.chartjs.org/docs/latest/axes/labelling.html#creating-custom-tick-formats
29455
+ return truncateLabel(this.getLabelForValue(tickValue));
29456
+ },
29428
29457
  },
29429
29458
  grid: {
29430
29459
  display: false,
@@ -29504,8 +29533,70 @@ function getChartTitle(definition) {
29504
29533
  };
29505
29534
  }
29506
29535
 
29536
+ /**
29537
+ * Custom tooltip for the charts. Mostly copied from Odoo's custom tooltip, with some slight changes to make it work
29538
+ * with o-spreadsheet chart data and CSS.
29539
+ *
29540
+ * https://github.com/odoo/odoo/blob/18.0/addons/web/static/src/views/graph/graph_renderer.xml
29541
+ */
29542
+ const templates = /* xml */ `
29543
+ <templates>
29544
+ <t t-name="o-spreadsheet-CustomTooltip">
29545
+ <div
29546
+ class="o-chart-custom-tooltip border rounded px-2 py-1 pe-none mw-100 position-absolute text-nowrap shadow opacity-100">
29547
+ <table class="overflow-hidden m-0">
29548
+ <thead>
29549
+ <tr>
29550
+ <th class="o-tooltip-title align-baseline border-0 text-truncate" t-esc="title" t-attf-style="max-width: {{ labelsMaxWidth }}"/>
29551
+ </tr>
29552
+ </thead>
29553
+ <tbody>
29554
+ <tr t-foreach="tooltipItems" t-as="tooltipItem" t-key="tooltipItem_index">
29555
+ <td>
29556
+ <span
29557
+ class="badge ps-2 py-2 rounded-0 align-middle"
29558
+ t-attf-style="background-color: {{ tooltipItem.boxColor }}"
29559
+ > </span>
29560
+ <small
29561
+ t-if="tooltipItem.label"
29562
+ class="o-tooltip-label d-inline-block text-truncate align-middle smaller ms-2"
29563
+ t-esc="tooltipItem.label"
29564
+ t-attf-style="max-width: {{ labelsMaxWidth }}"
29565
+ />
29566
+ </td>
29567
+ <td class="o-tooltip-value ps-2 fw-bolder text-end">
29568
+ <small class="smaller d-inline-block text-truncate align-middle" t-attf-style="max-width: {{ valuesMaxWidth }}">
29569
+ <t t-esc="tooltipItem.value"/>
29570
+ <t t-if="tooltipItem.percentage">
29571
+ (
29572
+ <t t-esc="tooltipItem.percentage"/>
29573
+ %)
29574
+ </t>
29575
+ </small>
29576
+ </td>
29577
+ </tr>
29578
+ </tbody>
29579
+ </table>
29580
+ </div>
29581
+ </t>
29582
+ </templates>
29583
+ `;
29584
+ const app = new owl.App(owl.Component, { templates, translateFn: _t });
29585
+ function renderToString(templateName, context = {}) {
29586
+ return render(templateName, context).innerHTML;
29587
+ }
29588
+ function render(templateName, context = {}) {
29589
+ const templateFn = app.getTemplate(templateName);
29590
+ const bdom = templateFn(context, {});
29591
+ const div = document.createElement("div");
29592
+ owl.blockDom.mount(bdom, div);
29593
+ return div;
29594
+ }
29595
+
29507
29596
  function getBarChartTooltip(definition, args) {
29508
29597
  return {
29598
+ enabled: false,
29599
+ external: customTooltipHandler,
29509
29600
  callbacks: {
29510
29601
  title: function (tooltipItems) {
29511
29602
  return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
@@ -29529,7 +29620,11 @@ function getBarChartTooltip(definition, args) {
29529
29620
  function getLineChartTooltip(definition, args) {
29530
29621
  const { axisType, locale, axisFormats } = args;
29531
29622
  const labelFormat = axisFormats?.x;
29532
- const tooltip = { callbacks: {} };
29623
+ const tooltip = {
29624
+ enabled: false,
29625
+ external: customTooltipHandler,
29626
+ callbacks: {},
29627
+ };
29533
29628
  if (axisType === "linear") {
29534
29629
  tooltip.callbacks.label = (tooltipItem) => {
29535
29630
  const dataSetPoint = tooltipItem.parsed.y;
@@ -29568,6 +29663,8 @@ function getPieChartTooltip(definition, args) {
29568
29663
  const { locale, axisFormats } = args;
29569
29664
  const format = axisFormats?.y || axisFormats?.y1;
29570
29665
  return {
29666
+ enabled: false,
29667
+ external: customTooltipHandler,
29571
29668
  callbacks: {
29572
29669
  title: function (tooltipItems) {
29573
29670
  return tooltipItems[0].dataset.label;
@@ -29592,6 +29689,8 @@ function getWaterfallChartTooltip(definition, args) {
29592
29689
  const format = axisFormats?.y || axisFormats?.y1;
29593
29690
  const dataSeriesLabels = dataSetsValues.map((dataSet) => dataSet.label);
29594
29691
  return {
29692
+ enabled: false,
29693
+ external: customTooltipHandler,
29595
29694
  callbacks: {
29596
29695
  label: function (tooltipItem) {
29597
29696
  const [lastValue, currentValue] = tooltipItem.raw;
@@ -29623,6 +29722,8 @@ function getPyramidChartTooltip(definition, args) {
29623
29722
  function getRadarChartTooltip(definition, args) {
29624
29723
  const { locale, axisFormats } = args;
29625
29724
  return {
29725
+ enabled: false,
29726
+ external: customTooltipHandler,
29626
29727
  callbacks: {
29627
29728
  label: function (tooltipItem) {
29628
29729
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
@@ -29661,6 +29762,48 @@ function calculatePercentage(dataset, dataIndex) {
29661
29762
  const percentage = (dataset[dataIndex] / total) * 100;
29662
29763
  return percentage.toFixed(2);
29663
29764
  }
29765
+ function customTooltipHandler({ chart, tooltip }) {
29766
+ chart.canvas.parentNode.querySelector("div.o-chart-custom-tooltip")?.remove();
29767
+ if (tooltip.opacity === 0 || tooltip.dataPoints.length === 0) {
29768
+ return;
29769
+ }
29770
+ const tooltipItems = tooltip.body.map((body, index) => {
29771
+ let [label, value] = body.lines[0].split(":").map((str) => str.trim());
29772
+ if (!value) {
29773
+ value = label;
29774
+ label = "";
29775
+ }
29776
+ const color = tooltip.labelColors[index].backgroundColor;
29777
+ return {
29778
+ label,
29779
+ value,
29780
+ boxColor: typeof color === "string" ? setColorAlpha(color, 1) : color,
29781
+ };
29782
+ });
29783
+ const innerHTML = renderToString("o-spreadsheet-CustomTooltip", {
29784
+ labelsMaxWidth: Math.floor(chart.canvas.clientWidth * 0.5) + "px",
29785
+ valuesMaxWidth: Math.floor(chart.canvas.clientWidth * 0.25) + "px",
29786
+ title: tooltip.title[0],
29787
+ tooltipItems,
29788
+ });
29789
+ const template = Object.assign(document.createElement("template"), { innerHTML });
29790
+ const newTooltipEl = template.content.firstChild;
29791
+ chart.canvas.parentNode?.appendChild(newTooltipEl);
29792
+ Object.assign(newTooltipEl.style, {
29793
+ left: getTooltipLeftPosition(chart, tooltip, newTooltipEl.clientWidth) + "px",
29794
+ top: Math.floor(tooltip.caretY - newTooltipEl.clientHeight / 2) + "px",
29795
+ });
29796
+ }
29797
+ /**
29798
+ * Get the left position for the tooltip, making sure it doesn't go out of the chart area.
29799
+ */
29800
+ function getTooltipLeftPosition(chart, tooltip, tooltipWidth) {
29801
+ const x = tooltip.caretX;
29802
+ if (x + tooltipWidth > chart.chartArea.right) {
29803
+ return Math.max(0, x - tooltipWidth);
29804
+ }
29805
+ return x;
29806
+ }
29664
29807
 
29665
29808
  var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
29666
29809
  __proto__: null,
@@ -29774,11 +29917,11 @@ class BarChart extends AbstractChart {
29774
29917
  : undefined,
29775
29918
  };
29776
29919
  }
29777
- copyForSheetId(sheetId) {
29778
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
29779
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
29780
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
29781
- return new BarChart(definition, sheetId, this.getters);
29920
+ duplicateInDuplicatedSheet(newSheetId) {
29921
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
29922
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
29923
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
29924
+ return new BarChart(definition, newSheetId, this.getters);
29782
29925
  }
29783
29926
  copyInSheetId(sheetId) {
29784
29927
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -29845,7 +29988,7 @@ function createBarChartRuntime(chart, getters) {
29845
29988
  const config = {
29846
29989
  type: "bar",
29847
29990
  data: {
29848
- labels: chartData.labels.map(truncateLabel),
29991
+ labels: chartData.labels,
29849
29992
  datasets: getBarChartDatasets(definition, chartData),
29850
29993
  },
29851
29994
  options: {
@@ -29981,11 +30124,11 @@ class ComboChart extends AbstractChart {
29981
30124
  showValues: context.showValues,
29982
30125
  };
29983
30126
  }
29984
- copyForSheetId(sheetId) {
29985
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
29986
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
29987
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
29988
- return new ComboChart(definition, sheetId, this.getters);
30127
+ duplicateInDuplicatedSheet(newSheetId) {
30128
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
30129
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
30130
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
30131
+ return new ComboChart(definition, newSheetId, this.getters);
29989
30132
  }
29990
30133
  copyInSheetId(sheetId) {
29991
30134
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -29998,7 +30141,7 @@ function createComboChartRuntime(chart, getters) {
29998
30141
  const config = {
29999
30142
  type: "bar",
30000
30143
  data: {
30001
- labels: chartData.labels.map(truncateLabel),
30144
+ labels: chartData.labels,
30002
30145
  datasets: getComboChartDatasets(definition, chartData),
30003
30146
  },
30004
30147
  options: {
@@ -30132,10 +30275,10 @@ class GaugeChart extends AbstractChart {
30132
30275
  },
30133
30276
  };
30134
30277
  }
30135
- copyForSheetId(sheetId) {
30136
- const dataRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.dataRange);
30137
- const definition = this.getDefinitionWithSpecificRanges(dataRange, sheetId);
30138
- return new GaugeChart(definition, sheetId, this.getters);
30278
+ duplicateInDuplicatedSheet(newSheetId) {
30279
+ const dataRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.dataRange);
30280
+ const definition = this.getDefinitionWithSpecificRanges(dataRange, newSheetId);
30281
+ return new GaugeChart(definition, newSheetId, this.getters);
30139
30282
  }
30140
30283
  copyInSheetId(sheetId) {
30141
30284
  const definition = this.getDefinitionWithSpecificRanges(this.dataRange, sheetId);
@@ -30316,11 +30459,11 @@ class GeoChart extends AbstractChart {
30316
30459
  : undefined,
30317
30460
  };
30318
30461
  }
30319
- copyForSheetId(sheetId) {
30320
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30321
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30322
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30323
- return new GeoChart(definition, sheetId, this.getters);
30462
+ duplicateInDuplicatedSheet(newSheetId) {
30463
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
30464
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
30465
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
30466
+ return new GeoChart(definition, newSheetId, this.getters);
30324
30467
  }
30325
30468
  copyInSheetId(sheetId) {
30326
30469
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -30513,11 +30656,11 @@ class LineChart extends AbstractChart {
30513
30656
  verticalAxis: getDefinedAxis(definition),
30514
30657
  };
30515
30658
  }
30516
- copyForSheetId(sheetId) {
30517
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30518
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30519
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30520
- return new LineChart(definition, sheetId, this.getters);
30659
+ duplicateInDuplicatedSheet(newSheetId) {
30660
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
30661
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
30662
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
30663
+ return new LineChart(definition, newSheetId, this.getters);
30521
30664
  }
30522
30665
  copyInSheetId(sheetId) {
30523
30666
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -30530,7 +30673,7 @@ function createLineChartRuntime(chart, getters) {
30530
30673
  const config = {
30531
30674
  type: "line",
30532
30675
  data: {
30533
- labels: chartData.axisType !== "time" ? chartData.labels.map(truncateLabel) : chartData.labels,
30676
+ labels: chartData.labels,
30534
30677
  datasets: getLineChartDatasets(definition, chartData),
30535
30678
  },
30536
30679
  options: {
@@ -30624,11 +30767,11 @@ class PieChart extends AbstractChart {
30624
30767
  showValues: this.showValues,
30625
30768
  };
30626
30769
  }
30627
- copyForSheetId(sheetId) {
30628
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30629
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30630
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30631
- return new PieChart(definition, sheetId, this.getters);
30770
+ duplicateInDuplicatedSheet(newSheetId) {
30771
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
30772
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
30773
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
30774
+ return new PieChart(definition, newSheetId, this.getters);
30632
30775
  }
30633
30776
  copyInSheetId(sheetId) {
30634
30777
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -30665,7 +30808,7 @@ function createPieChartRuntime(chart, getters) {
30665
30808
  const config = {
30666
30809
  type: chart.isDoughnut ? "doughnut" : "pie",
30667
30810
  data: {
30668
- labels: chartData.labels.map(truncateLabel),
30811
+ labels: chartData.labels,
30669
30812
  datasets: getPieChartDatasets(definition, chartData),
30670
30813
  },
30671
30814
  options: {
@@ -30745,11 +30888,11 @@ class PyramidChart extends AbstractChart {
30745
30888
  : undefined,
30746
30889
  };
30747
30890
  }
30748
- copyForSheetId(sheetId) {
30749
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30750
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30751
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30752
- return new PyramidChart(definition, sheetId, this.getters);
30891
+ duplicateInDuplicatedSheet(newSheetId) {
30892
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
30893
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
30894
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
30895
+ return new PyramidChart(definition, newSheetId, this.getters);
30753
30896
  }
30754
30897
  copyInSheetId(sheetId) {
30755
30898
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -30801,7 +30944,7 @@ function createPyramidChartRuntime(chart, getters) {
30801
30944
  const config = {
30802
30945
  type: "bar",
30803
30946
  data: {
30804
- labels: chartData.labels.map(truncateLabel),
30947
+ labels: chartData.labels,
30805
30948
  datasets: getBarChartDatasets(definition, chartData),
30806
30949
  },
30807
30950
  options: {
@@ -30882,11 +31025,11 @@ class RadarChart extends AbstractChart {
30882
31025
  : undefined,
30883
31026
  };
30884
31027
  }
30885
- copyForSheetId(sheetId) {
30886
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30887
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30888
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30889
- return new RadarChart(definition, sheetId, this.getters);
31028
+ duplicateInDuplicatedSheet(newSheetId) {
31029
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
31030
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
31031
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
31032
+ return new RadarChart(definition, newSheetId, this.getters);
30890
31033
  }
30891
31034
  copyInSheetId(sheetId) {
30892
31035
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -30951,7 +31094,7 @@ function createRadarChartRuntime(chart, getters) {
30951
31094
  const config = {
30952
31095
  type: "radar",
30953
31096
  data: {
30954
- labels: chartData.labels.map(truncateLabel),
31097
+ labels: chartData.labels,
30955
31098
  datasets: getRadarChartDatasets(definition, chartData),
30956
31099
  },
30957
31100
  options: {
@@ -31085,11 +31228,11 @@ class ScatterChart extends AbstractChart {
31085
31228
  verticalAxis: getDefinedAxis(definition),
31086
31229
  };
31087
31230
  }
31088
- copyForSheetId(sheetId) {
31089
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
31090
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
31091
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
31092
- return new ScatterChart(definition, sheetId, this.getters);
31231
+ duplicateInDuplicatedSheet(newSheetId) {
31232
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
31233
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
31234
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
31235
+ return new ScatterChart(definition, newSheetId, this.getters);
31093
31236
  }
31094
31237
  copyInSheetId(sheetId) {
31095
31238
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -31104,7 +31247,7 @@ function createScatterChartRuntime(chart, getters) {
31104
31247
  // have less options than the line chart (it only works with linear labels)
31105
31248
  type: "line",
31106
31249
  data: {
31107
- labels: chartData.axisType !== "time" ? chartData.labels.map(truncateLabel) : chartData.labels,
31250
+ labels: chartData.labels,
31108
31251
  datasets: getScatterChartDatasets(definition, chartData),
31109
31252
  },
31110
31253
  options: {
@@ -31202,11 +31345,11 @@ class WaterfallChart extends AbstractChart {
31202
31345
  : undefined,
31203
31346
  };
31204
31347
  }
31205
- copyForSheetId(sheetId) {
31206
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
31207
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
31208
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
31209
- return new WaterfallChart(definition, sheetId, this.getters);
31348
+ duplicateInDuplicatedSheet(newSheetId) {
31349
+ const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
31350
+ const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
31351
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
31352
+ return new WaterfallChart(definition, newSheetId, this.getters);
31210
31353
  }
31211
31354
  copyInSheetId(sheetId) {
31212
31355
  const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
@@ -33633,8 +33776,6 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
33633
33776
  chartToImage: chartToImage,
33634
33777
  checkDataset: checkDataset,
33635
33778
  checkLabelRange: checkLabelRange,
33636
- copyDataSetsWithNewSheetId: copyDataSetsWithNewSheetId,
33637
- copyLabelRangeWithNewSheetId: copyLabelRangeWithNewSheetId,
33638
33779
  createBarChartRuntime: createBarChartRuntime,
33639
33780
  createDataSets: createDataSets,
33640
33781
  createGaugeChartRuntime: createGaugeChartRuntime,
@@ -33643,6 +33784,8 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
33643
33784
  createScorecardChartRuntime: createScorecardChartRuntime,
33644
33785
  createWaterfallChartRuntime: createWaterfallChartRuntime,
33645
33786
  drawScoreChart: drawScoreChart,
33787
+ duplicateDataSetsInDuplicatedSheet: duplicateDataSetsInDuplicatedSheet,
33788
+ duplicateLabelRangeInDuplicatedSheet: duplicateLabelRangeInDuplicatedSheet,
33646
33789
  formatChartDatasetValue: formatChartDatasetValue,
33647
33790
  formatTickValue: formatTickValue,
33648
33791
  getChartPositionAtCenterOfViewport: getChartPositionAtCenterOfViewport,
@@ -46075,12 +46218,10 @@ class SpreadsheetPivot {
46075
46218
  * Take cares of double names
46076
46219
  */
46077
46220
  findName(name, fields) {
46078
- let increment = 1;
46079
- const initialName = name;
46080
- while (name in fields) {
46081
- name = `${initialName}${++increment}`;
46082
- }
46083
- return name;
46221
+ return getUniqueText(name, Object.keys(fields), {
46222
+ compute: (name, i) => `${name}${i}`,
46223
+ start: 2,
46224
+ });
46084
46225
  }
46085
46226
  extractDataEntriesFromRange(range) {
46086
46227
  const dataEntries = [];
@@ -46322,13 +46463,10 @@ class PivotSidePanelStore extends SpreadsheetStore {
46322
46463
  }
46323
46464
  }
46324
46465
  isDynamicPivotInViewport() {
46325
- const sheetId = this.getters.getActiveSheetId();
46326
- for (const col of this.getters.getSheetViewVisibleCols()) {
46327
- for (const row of this.getters.getSheetViewVisibleRows()) {
46328
- const isDynamicPivot = this.getters.isSpillPivotFormula({ sheetId, col, row });
46329
- if (isDynamicPivot) {
46330
- return true;
46331
- }
46466
+ for (const position of this.getters.getVisibleCellPositions()) {
46467
+ const isDynamicPivot = this.getters.isSpillPivotFormula(position);
46468
+ if (isDynamicPivot) {
46469
+ return true;
46332
46470
  }
46333
46471
  }
46334
46472
  return false;
@@ -52322,15 +52460,11 @@ class Grid extends owl.Component {
52322
52460
  class BasePlugin {
52323
52461
  static getters = [];
52324
52462
  history;
52325
- dispatch;
52326
- canDispatch;
52327
- constructor(stateObserver, dispatch, canDispatch) {
52463
+ constructor(stateObserver) {
52328
52464
  this.history = Object.assign(Object.create(stateObserver), {
52329
52465
  update: stateObserver.addChange.bind(stateObserver, this),
52330
52466
  selectCell: () => { },
52331
52467
  });
52332
- this.dispatch = dispatch;
52333
- this.canDispatch = canDispatch;
52334
52468
  }
52335
52469
  /**
52336
52470
  * Export for excel should be available for all plugins, even for the UI.
@@ -52408,10 +52542,14 @@ class BasePlugin {
52408
52542
  */
52409
52543
  class CorePlugin extends BasePlugin {
52410
52544
  getters;
52545
+ dispatch;
52546
+ canDispatch;
52411
52547
  constructor({ getters, stateObserver, range, dispatch, canDispatch }) {
52412
- super(stateObserver, dispatch, canDispatch);
52548
+ super(stateObserver);
52413
52549
  range.addRangeProvider(this.adaptRanges.bind(this));
52414
52550
  this.getters = getters;
52551
+ this.dispatch = dispatch;
52552
+ this.canDispatch = canDispatch;
52415
52553
  }
52416
52554
  // ---------------------------------------------------------------------------
52417
52555
  // Import/Export
@@ -53648,7 +53786,7 @@ class ChartPlugin extends CorePlugin {
53648
53786
  if (fig.tag === "chart") {
53649
53787
  const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();
53650
53788
  const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
53651
- const chart = this.charts[fig.id]?.copyForSheetId(cmd.sheetIdTo);
53789
+ const chart = this.charts[fig.id]?.duplicateInDuplicatedSheet(cmd.sheetIdTo);
53652
53790
  if (chart) {
53653
53791
  this.dispatch("CREATE_CHART", {
53654
53792
  id: duplicatedFigureId,
@@ -54292,7 +54430,7 @@ class DataValidationPlugin extends CorePlugin {
54292
54430
  case "DUPLICATE_SHEET": {
54293
54431
  const rules = deepCopy(this.rules[cmd.sheetId]).map((rule) => ({
54294
54432
  ...rule,
54295
- ranges: rule.ranges.map((range) => copyRangeWithNewSheetId(cmd.sheetId, cmd.sheetIdTo, range)),
54433
+ ranges: rule.ranges.map((range) => duplicateRangeInDuplicatedSheet(cmd.sheetId, cmd.sheetIdTo, range)),
54296
54434
  }));
54297
54435
  this.history.update("rules", cmd.sheetIdTo, rules);
54298
54436
  break;
@@ -56341,14 +56479,11 @@ class SheetPlugin extends CorePlugin {
56341
56479
  return dimension === "COL" ? this.getNumberCols(sheetId) : this.getNumberRows(sheetId);
56342
56480
  }
56343
56481
  getNextSheetName(baseName = "Sheet") {
56344
- let i = 1;
56345
56482
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
56346
- let name = `${baseName}${i}`;
56347
- while (names.includes(name)) {
56348
- name = `${baseName}${i}`;
56349
- i++;
56350
- }
56351
- return name;
56483
+ return getUniqueText(baseName, names, {
56484
+ compute: (name, i) => `${name}${i}`,
56485
+ computeFirstOne: true,
56486
+ });
56352
56487
  }
56353
56488
  getSheetSize(sheetId) {
56354
56489
  return {
@@ -56628,15 +56763,9 @@ class SheetPlugin extends CorePlugin {
56628
56763
  this.history.update("sheetIdsMapName", sheetIdsMapName);
56629
56764
  }
56630
56765
  getDuplicateSheetName(sheetName) {
56631
- let i = 1;
56632
56766
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
56633
56767
  const baseName = _t("Copy of %s", sheetName);
56634
- let name = baseName.toString();
56635
- while (names.includes(name)) {
56636
- name = `${baseName} (${i})`;
56637
- i++;
56638
- }
56639
- return name;
56768
+ return getUniqueText(baseName.toString(), names);
56640
56769
  }
56641
56770
  deleteSheet(sheet) {
56642
56771
  const name = sheet.name;
@@ -58166,15 +58295,8 @@ class TableStylePlugin extends CorePlugin {
58166
58295
  }
58167
58296
  getNewCustomTableStyleName() {
58168
58297
  let name = _t("Custom Table Style");
58169
- const styleNames = new Set(Object.values(this.styles).map((style) => style.displayName));
58170
- if (!styleNames.has(name)) {
58171
- return name;
58172
- }
58173
- let i = 2;
58174
- while (styleNames.has(`${name} ${i}`)) {
58175
- i++;
58176
- }
58177
- return `${name} ${i}`;
58298
+ const styleNames = Object.values(this.styles).map((style) => style.displayName);
58299
+ return getUniqueText(name, styleNames, { compute: (name, i) => `${name} ${i}`, start: 2 });
58178
58300
  }
58179
58301
  isTableStyleEditable(styleId) {
58180
58302
  return !TABLE_PRESETS[styleId];
@@ -58204,24 +58326,15 @@ class TableStylePlugin extends CorePlugin {
58204
58326
  }
58205
58327
 
58206
58328
  /**
58207
- * UI plugins handle any transient data required to display a spreadsheet.
58208
- * They can draw on the grid canvas.
58329
+ * Core view plugins handle any data derived from core date (i.e. evaluation).
58330
+ * They cannot impact the model data (i.e. cannot dispatch commands).
58209
58331
  */
58210
- class UIPlugin extends BasePlugin {
58211
- static layers = [];
58332
+ class CoreViewPlugin extends BasePlugin {
58212
58333
  getters;
58213
- ui;
58214
- selection;
58215
- constructor({ getters, stateObserver, dispatch, canDispatch, uiActions, selection, }) {
58216
- super(stateObserver, dispatch, canDispatch);
58334
+ constructor({ getters, stateObserver }) {
58335
+ super(stateObserver);
58217
58336
  this.getters = getters;
58218
- this.ui = uiActions;
58219
- this.selection = selection;
58220
58337
  }
58221
- // ---------------------------------------------------------------------------
58222
- // Grid rendering
58223
- // ---------------------------------------------------------------------------
58224
- drawLayer(ctx, layer) { }
58225
58338
  }
58226
58339
 
58227
58340
  /**
@@ -59916,7 +60029,7 @@ function updateEvalContextAndExecute(compiledFormula, compilationParams, sheetId
59916
60029
  // as necessary in several iterations, where evaluated cells can trigger the evaluation
59917
60030
  // of other cells depending on it, at the next iteration.
59918
60031
  //#endregion
59919
- class EvaluationPlugin extends UIPlugin {
60032
+ class EvaluationPlugin extends CoreViewPlugin {
59920
60033
  static getters = [
59921
60034
  "evaluateFormula",
59922
60035
  "evaluateFormulaResult",
@@ -60183,7 +60296,7 @@ function colorDistance(color1, color2) {
60183
60296
  * This plugins aims to compute and keep to custom colors used in the
60184
60297
  * current spreadsheet
60185
60298
  */
60186
- class CustomColorsPlugin extends UIPlugin {
60299
+ class CustomColorsPlugin extends CoreViewPlugin {
60187
60300
  customColors = {};
60188
60301
  shouldUpdateColors = true;
60189
60302
  static getters = ["getCustomColors"];
@@ -60319,7 +60432,7 @@ class CustomColorsPlugin extends UIPlugin {
60319
60432
  }
60320
60433
  }
60321
60434
 
60322
- class EvaluationChartPlugin extends UIPlugin {
60435
+ class EvaluationChartPlugin extends CoreViewPlugin {
60323
60436
  static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
60324
60437
  charts = {};
60325
60438
  createRuntimeChart = chartRuntimeFactory(this.getters);
@@ -60423,7 +60536,7 @@ class EvaluationChartPlugin extends UIPlugin {
60423
60536
  }
60424
60537
  }
60425
60538
 
60426
- class EvaluationConditionalFormatPlugin extends UIPlugin {
60539
+ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
60427
60540
  static getters = [
60428
60541
  "getConditionalIcon",
60429
60542
  "getCellConditionalFormatStyle",
@@ -60741,7 +60854,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
60741
60854
  }
60742
60855
 
60743
60856
  const VALID_RESULT = { isValid: true };
60744
- class EvaluationDataValidationPlugin extends UIPlugin {
60857
+ class EvaluationDataValidationPlugin extends CoreViewPlugin {
60745
60858
  static getters = [
60746
60859
  "getDataValidationInvalidCriterionValueMessage",
60747
60860
  "getInvalidDataValidationMessage",
@@ -60872,7 +60985,7 @@ class EvaluationDataValidationPlugin extends UIPlugin {
60872
60985
  }
60873
60986
  }
60874
60987
 
60875
- class DynamicTablesPlugin extends UIPlugin {
60988
+ class DynamicTablesPlugin extends CoreViewPlugin {
60876
60989
  static getters = [
60877
60990
  "canCreateDynamicTableOnZones",
60878
60991
  "doesZonesContainFilter",
@@ -61046,7 +61159,7 @@ class DynamicTablesPlugin extends UIPlugin {
61046
61159
  }
61047
61160
  }
61048
61161
 
61049
- class HeaderSizeUIPlugin extends UIPlugin {
61162
+ class HeaderSizeUIPlugin extends CoreViewPlugin {
61050
61163
  static getters = ["getRowSize", "getHeaderSize"];
61051
61164
  tallestCellInRow = {};
61052
61165
  ctx = document.createElement("canvas").getContext("2d");
@@ -61752,7 +61865,7 @@ const UNDO_REDO_PIVOT_COMMANDS = ["ADD_PIVOT", "UPDATE_PIVOT"];
61752
61865
  function isPivotCommand(cmd) {
61753
61866
  return UNDO_REDO_PIVOT_COMMANDS.includes(cmd.type);
61754
61867
  }
61755
- class PivotUIPlugin extends UIPlugin {
61868
+ class PivotUIPlugin extends CoreViewPlugin {
61756
61869
  static getters = [
61757
61870
  "getPivot",
61758
61871
  "getFirstPivotFunction",
@@ -61956,13 +62069,9 @@ class PivotUIPlugin extends UIPlugin {
61956
62069
  }
61957
62070
  generateNewCalculatedMeasureName(measures) {
61958
62071
  const existingMeasures = measures.map((m) => m.fieldName);
61959
- let i = 1;
61960
- let name = _t("Calculated measure %s", i);
61961
- while (existingMeasures.includes(name)) {
61962
- i++;
61963
- name = _t("Calculated measure %s", i);
61964
- }
61965
- return name;
62072
+ return getUniqueText(_t("Calculated measure 1"), existingMeasures, {
62073
+ compute: (name, i) => _t("Calculated measure %s", i),
62074
+ });
61966
62075
  }
61967
62076
  getPivot(pivotId) {
61968
62077
  if (!this.getters.isExistingPivot(pivotId)) {
@@ -62016,6 +62125,31 @@ class PivotUIPlugin extends UIPlugin {
62016
62125
  }
62017
62126
  }
62018
62127
 
62128
+ /**
62129
+ * UI plugins handle any transient data required to display a spreadsheet.
62130
+ * They can draw on the grid canvas.
62131
+ */
62132
+ class UIPlugin extends BasePlugin {
62133
+ static layers = [];
62134
+ getters;
62135
+ ui;
62136
+ selection;
62137
+ dispatch;
62138
+ canDispatch;
62139
+ constructor({ getters, stateObserver, dispatch, canDispatch, uiActions, selection, }) {
62140
+ super(stateObserver);
62141
+ this.getters = getters;
62142
+ this.ui = uiActions;
62143
+ this.selection = selection;
62144
+ this.dispatch = dispatch;
62145
+ this.canDispatch = canDispatch;
62146
+ }
62147
+ // ---------------------------------------------------------------------------
62148
+ // Grid rendering
62149
+ // ---------------------------------------------------------------------------
62150
+ drawLayer(ctx, layer) { }
62151
+ }
62152
+
62019
62153
  /**
62020
62154
  * This plugin manage the autofill.
62021
62155
  *
@@ -64131,15 +64265,9 @@ class InsertPivotPlugin extends UIPlugin {
64131
64265
  }
64132
64266
  }
64133
64267
  getPivotDuplicateSheetName(pivotName) {
64134
- let i = 1;
64135
64268
  const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
64136
64269
  const sanitizedName = sanitizeSheetName(pivotName);
64137
- let name = sanitizedName;
64138
- while (names.includes(name)) {
64139
- name = `${sanitizedName} (${i})`;
64140
- i++;
64141
- }
64142
- return name;
64270
+ return getUniqueText(sanitizedName, names);
64143
64271
  }
64144
64272
  insertPivotWithTable(sheetId, col, row, pivotId, table, mode) {
64145
64273
  const { cols, rows, measures, fieldsType } = table;
@@ -66246,13 +66374,10 @@ class FilterEvaluationPlugin extends UIPlugin {
66246
66374
  if (!colName) {
66247
66375
  colName = `Column${colIndex}`;
66248
66376
  }
66249
- let currentColName = colName;
66250
- let i = 2;
66251
- while (usedColNames.includes(currentColName)) {
66252
- currentColName = colName + String(i);
66253
- i++;
66254
- }
66255
- return currentColName;
66377
+ return getUniqueText(colName, usedColNames, {
66378
+ compute: (name, i) => colName + String(i),
66379
+ start: 2,
66380
+ });
66256
66381
  }
66257
66382
  }
66258
66383
 
@@ -68904,23 +69029,17 @@ class ClickableCellsStore extends SpreadsheetStore {
68904
69029
  const cells = [];
68905
69030
  const getters = this.getters;
68906
69031
  const sheetId = getters.getActiveSheetId();
68907
- for (const col of getters.getSheetViewVisibleCols()) {
68908
- for (const row of getters.getSheetViewVisibleRows()) {
68909
- const position = { sheetId, col, row };
68910
- if (!getters.isMainCellPosition(position)) {
68911
- continue;
68912
- }
68913
- const action = this.getClickableAction(position);
68914
- if (!action) {
68915
- continue;
68916
- }
68917
- const zone = getters.expandZone(sheetId, positionToZone(position));
68918
- cells.push({
68919
- coordinates: getters.getVisibleRect(zone),
68920
- position,
68921
- action,
68922
- });
69032
+ for (const position of this.getters.getVisibleCellPositions()) {
69033
+ const action = this.getClickableAction(position);
69034
+ if (!action) {
69035
+ continue;
68923
69036
  }
69037
+ const zone = getters.expandZone(sheetId, positionToZone(position));
69038
+ cells.push({
69039
+ coordinates: getters.getVisibleRect(zone),
69040
+ position,
69041
+ action,
69042
+ });
68924
69043
  }
68925
69044
  return cells;
68926
69045
  }
@@ -74171,14 +74290,13 @@ function createRelRoot() {
74171
74290
  */
74172
74291
  function fixLengthySheetNames(data) {
74173
74292
  const nameMapping = {};
74174
- const newNames = new Set();
74293
+ const newNames = [];
74175
74294
  for (const sheet of data.sheets) {
74176
74295
  let newName = sheet.name.slice(0, 31);
74177
- let i = 1;
74178
- while (newNames.has(newName)) {
74179
- newName = newName.slice(0, 31 - String(i).length) + i++;
74180
- }
74181
- newNames.add(newName);
74296
+ newName = getUniqueText(newName, newNames, {
74297
+ compute: (name, i) => name.slice(0, 31 - String(i).length) + i,
74298
+ });
74299
+ newNames.push(newName);
74182
74300
  if (newName !== sheet.name) {
74183
74301
  nameMapping[sheet.name] = newName;
74184
74302
  sheet.name = newName;
@@ -74243,6 +74361,7 @@ class Model extends EventBus {
74243
74361
  */
74244
74362
  config;
74245
74363
  corePluginConfig;
74364
+ coreViewPluginConfig;
74246
74365
  uiPluginConfig;
74247
74366
  state;
74248
74367
  selection;
@@ -74295,6 +74414,7 @@ class Model extends EventBus {
74295
74414
  this.coreHandlers.push(this.range);
74296
74415
  this.handlers.push(this.range);
74297
74416
  this.corePluginConfig = this.setupCorePluginConfig();
74417
+ this.coreViewPluginConfig = this.setupCoreViewPluginConfig();
74298
74418
  this.uiPluginConfig = this.setupUiPluginConfig();
74299
74419
  // registering plugins
74300
74420
  for (let Plugin of corePluginRegistry.getAll()) {
@@ -74303,7 +74423,7 @@ class Model extends EventBus {
74303
74423
  Object.assign(this.getters, this.coreGetters);
74304
74424
  this.session.loadInitialMessages(stateUpdateMessages);
74305
74425
  for (let Plugin of coreViewsPluginRegistry.getAll()) {
74306
- const plugin = this.setupUiPlugin(Plugin);
74426
+ const plugin = this.setupCoreViewPlugin(Plugin);
74307
74427
  this.handlers.push(plugin);
74308
74428
  this.uiHandlers.push(plugin);
74309
74429
  this.coreHandlers.push(plugin);
@@ -74369,6 +74489,19 @@ class Model extends EventBus {
74369
74489
  }
74370
74490
  return plugin;
74371
74491
  }
74492
+ setupCoreViewPlugin(Plugin) {
74493
+ const plugin = new Plugin(this.coreViewPluginConfig);
74494
+ for (let name of Plugin.getters) {
74495
+ if (!(name in plugin)) {
74496
+ throw new Error(`Invalid getter name: ${name} for plugin ${plugin.constructor}`);
74497
+ }
74498
+ if (name in this.getters) {
74499
+ throw new Error(`Getter "${name}" is already defined.`);
74500
+ }
74501
+ this.getters[name] = plugin[name].bind(plugin);
74502
+ }
74503
+ return plugin;
74504
+ }
74372
74505
  /**
74373
74506
  * Initialize and properly configure a plugin.
74374
74507
  *
@@ -74470,6 +74603,20 @@ class Model extends EventBus {
74470
74603
  external: this.config.external,
74471
74604
  };
74472
74605
  }
74606
+ setupCoreViewPluginConfig() {
74607
+ return {
74608
+ getters: this.getters,
74609
+ stateObserver: this.state,
74610
+ selection: this.selection,
74611
+ moveClient: this.session.move.bind(this.session),
74612
+ custom: this.config.custom,
74613
+ uiActions: this.config,
74614
+ session: this.session,
74615
+ defaultCurrency: this.config.defaultCurrency,
74616
+ customColors: this.config.customColors || [],
74617
+ external: this.config.external,
74618
+ };
74619
+ }
74473
74620
  setupUiPluginConfig() {
74474
74621
  return {
74475
74622
  getters: this.getters,
@@ -74817,6 +74964,7 @@ const helpers = {
74817
74964
  areDomainArgsFieldsValid,
74818
74965
  splitReference,
74819
74966
  sanitizeSheetName,
74967
+ getUniqueText,
74820
74968
  isNumber,
74821
74969
  isDateTime,
74822
74970
  };
@@ -74915,6 +75063,7 @@ exports.AbstractChart = AbstractChart;
74915
75063
  exports.AbstractFigureClipboardHandler = AbstractFigureClipboardHandler;
74916
75064
  exports.CellErrorType = CellErrorType;
74917
75065
  exports.CorePlugin = CorePlugin;
75066
+ exports.CoreViewPlugin = CoreViewPlugin;
74918
75067
  exports.DispatchResult = DispatchResult;
74919
75068
  exports.EvaluationError = EvaluationError;
74920
75069
  exports.Model = Model;
@@ -74957,6 +75106,6 @@ exports.tokenColors = tokenColors;
74957
75106
  exports.tokenize = tokenize;
74958
75107
 
74959
75108
 
74960
- __info__.version = "18.1.1";
74961
- __info__.date = "2025-01-14T11:43:19.116Z";
74962
- __info__.hash = "d0fa5de";
75109
+ __info__.version = "18.2.0-alpha.1";
75110
+ __info__.date = "2025-01-14T11:35:51.135Z";
75111
+ __info__.hash = "702f816";