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