@odoo/o-spreadsheet 18.2.0 → 18.3.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.2.0
6
- * @date 2025-02-18T08:27:07.101Z
7
- * @hash d708714
5
+ * @version 18.3.0-alpha.1
6
+ * @date 2025-02-25T06:00:14.885Z
7
+ * @hash be4d957
8
8
  */
9
9
 
10
10
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -992,6 +992,9 @@ function getUniqueText(text, texts, options = {}) {
992
992
  }
993
993
  return newText;
994
994
  }
995
+ function isFormula(content) {
996
+ return content.startsWith("=") || content.startsWith("+");
997
+ }
995
998
 
996
999
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
997
1000
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
@@ -4458,7 +4461,7 @@ function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueIn
4458
4461
  * @param reverseSearch if true, search in the array starting from the end.
4459
4462
 
4460
4463
  */
4461
- function linearSearch(data, target, mode, numberOfValues, getValueInData, reverseSearch = false) {
4464
+ function linearSearch(data, target, mode, numberOfValues, getValueInData, lookupCaches, reverseSearch = false) {
4462
4465
  if (target === undefined || target.value === null) {
4463
4466
  return -1;
4464
4467
  }
@@ -4467,17 +4470,48 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4467
4470
  }
4468
4471
  const _target = normalizeValue(target.value);
4469
4472
  const getValue = reverseSearch
4470
- ? (data, i) => getValueInData(data, numberOfValues - i - 1)
4471
- : getValueInData;
4473
+ ? (data, i) => normalizeValue(getValueInData(data, numberOfValues - i - 1))
4474
+ : (data, i) => normalizeValue(getValueInData(data, i));
4475
+ // first check if the target is in the cache
4476
+ const isNotWildcardTarget = mode !== "wildcard" ||
4477
+ typeof _target !== "string" ||
4478
+ !(_target.includes("*") || _target.includes("?"));
4479
+ if (lookupCaches && isNotWildcardTarget) {
4480
+ const searchMode = reverseSearch ? "reverseSearch" : "forwardSearch";
4481
+ let cache = lookupCaches[searchMode].get(data);
4482
+ if (cache === undefined) {
4483
+ // build the cache for all the values
4484
+ cache = new Map();
4485
+ for (let i = 0; i < numberOfValues; i++) {
4486
+ const value = getValue(data, i) ?? null;
4487
+ if (!cache.has(value)) {
4488
+ cache.set(value, i);
4489
+ }
4490
+ }
4491
+ lookupCaches[searchMode].set(data, cache);
4492
+ }
4493
+ if (cache.has(_target)) {
4494
+ const resultIndex = cache.get(_target);
4495
+ return reverseSearch ? numberOfValues - resultIndex - 1 : resultIndex;
4496
+ }
4497
+ if (mode === "strict") {
4498
+ return -1;
4499
+ }
4500
+ }
4501
+ // else perform the linear search
4502
+ const resultIndex = _linearSearch(data, _target, mode, numberOfValues, getValue);
4503
+ return reverseSearch && resultIndex !== -1 ? numberOfValues - resultIndex - 1 : resultIndex;
4504
+ }
4505
+ function _linearSearch(data, _target, mode, numberOfValues, getNormalizeValue) {
4472
4506
  let indexMatchTarget = (i) => {
4473
- return normalizeValue(getValue(data, i)) === _target;
4507
+ return getNormalizeValue(data, i) === _target;
4474
4508
  };
4475
4509
  if (mode === "wildcard" &&
4476
4510
  typeof _target === "string" &&
4477
4511
  (_target.includes("*") || _target.includes("?"))) {
4478
4512
  const regExp = wildcardToRegExp(_target);
4479
4513
  indexMatchTarget = (i) => {
4480
- const value = normalizeValue(getValue(data, i));
4514
+ const value = getNormalizeValue(data, i);
4481
4515
  if (typeof value === "string") {
4482
4516
  return regExp.test(value);
4483
4517
  }
@@ -4488,7 +4522,7 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4488
4522
  let closestMatchIndex = -1;
4489
4523
  if (mode === "nextSmaller") {
4490
4524
  indexMatchTarget = (i) => {
4491
- const value = normalizeValue(getValue(data, i));
4525
+ const value = getNormalizeValue(data, i);
4492
4526
  if ((!closestMatch && compareCellValues(_target, value) >= 0) ||
4493
4527
  (compareCellValues(_target, value) >= 0 && compareCellValues(value, closestMatch) > 0)) {
4494
4528
  closestMatch = value;
@@ -4499,7 +4533,7 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4499
4533
  }
4500
4534
  if (mode === "nextGreater") {
4501
4535
  indexMatchTarget = (i) => {
4502
- const value = normalizeValue(getValue(data, i));
4536
+ const value = getNormalizeValue(data, i);
4503
4537
  if ((!closestMatch && compareCellValues(_target, value) <= 0) ||
4504
4538
  (compareCellValues(_target, value) <= 0 && compareCellValues(value, closestMatch) < 0)) {
4505
4539
  closestMatch = value;
@@ -4510,12 +4544,10 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4510
4544
  }
4511
4545
  for (let i = 0; i < numberOfValues; i++) {
4512
4546
  if (indexMatchTarget(i)) {
4513
- return reverseSearch ? numberOfValues - i - 1 : i;
4547
+ return i;
4514
4548
  }
4515
4549
  }
4516
- return reverseSearch && closestMatchIndex !== -1
4517
- ? numberOfValues - closestMatchIndex - 1
4518
- : closestMatchIndex;
4550
+ return closestMatchIndex;
4519
4551
  }
4520
4552
  /**
4521
4553
  * Normalize a value.
@@ -4631,8 +4663,8 @@ function findMatchingSpec(url) {
4631
4663
  function urlRepresentation(link, getters) {
4632
4664
  return findMatchingSpec(link.url).urlRepresentation(link.url, getters);
4633
4665
  }
4634
- function openLink(link, env) {
4635
- findMatchingSpec(link.url).open(link.url, env);
4666
+ function openLink(link, env, isMiddleClick) {
4667
+ findMatchingSpec(link.url).open(link.url, env, isMiddleClick);
4636
4668
  }
4637
4669
  function detectLink(value) {
4638
4670
  if (typeof value !== "string") {
@@ -6489,10 +6521,11 @@ class UuidGenerator {
6489
6521
  *
6490
6522
  */
6491
6523
  smallUuid() {
6492
- //@ts-ignore
6493
- if (window.crypto && window.crypto.getRandomValues) {
6494
- //@ts-ignore
6495
- return ([1e7] + -1e3).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
6524
+ if (window.crypto) {
6525
+ return "10000000-1000".replace(/[01]/g, (c) => {
6526
+ const n = Number(c);
6527
+ return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
6528
+ });
6496
6529
  }
6497
6530
  else {
6498
6531
  // mainly for jest and other browsers that do not have the crypto functionality
@@ -6507,10 +6540,11 @@ class UuidGenerator {
6507
6540
  * This method should be used when you need to avoid collisions at all costs, like the id of a revision.
6508
6541
  */
6509
6542
  uuidv4() {
6510
- //@ts-ignore
6511
- if (window.crypto && window.crypto.getRandomValues) {
6512
- //@ts-ignore
6513
- return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
6543
+ if (window.crypto) {
6544
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => {
6545
+ const n = Number(c);
6546
+ return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
6547
+ });
6514
6548
  }
6515
6549
  else {
6516
6550
  // mainly for jest and other browsers that do not have the crypto functionality
@@ -6918,7 +6952,7 @@ function isValidLocale(locale) {
6918
6952
  * canonicalizeNumberContent("02/12/2012", FR_LOCALE) // "02/12/2012"
6919
6953
  */
6920
6954
  function canonicalizeNumberContent(content, locale) {
6921
- return content.startsWith("=")
6955
+ return isFormula(content)
6922
6956
  ? canonicalizeFormula$1(content, locale)
6923
6957
  : canonicalizeNumberLiteral(content, locale);
6924
6958
  }
@@ -6933,7 +6967,7 @@ function canonicalizeNumberContent(content, locale) {
6933
6967
  * canonicalizeContent("02-12-2012", FR_LOCALE) // "12/02/2012"
6934
6968
  */
6935
6969
  function canonicalizeContent(content, locale) {
6936
- return content.startsWith("=")
6970
+ return isFormula(content)
6937
6971
  ? canonicalizeFormula$1(content, locale)
6938
6972
  : canonicalizeLiteral(content, locale);
6939
6973
  }
@@ -6952,7 +6986,7 @@ function localizeContent(content, locale) {
6952
6986
  }
6953
6987
  /** Change a formula to its canonical form (en_US locale) */
6954
6988
  function canonicalizeFormula$1(formula, locale) {
6955
- return _localizeFormula$1(formula, locale, DEFAULT_LOCALE);
6989
+ return _localizeFormula$1(formula.startsWith("+") ? "=" + formula.slice(1) : formula, locale, DEFAULT_LOCALE);
6956
6990
  }
6957
6991
  /** Change a formula from the canonical form to the given locale */
6958
6992
  function localizeFormula(formula, locale) {
@@ -7115,13 +7149,13 @@ function getDateTimeFormat(locale) {
7115
7149
 
7116
7150
  /** Change a number string to its canonical form (en_US locale) */
7117
7151
  function canonicalizeNumberValue(content, locale) {
7118
- return content.startsWith("=")
7152
+ return isFormula(content)
7119
7153
  ? canonicalizeFormula(content, locale)
7120
7154
  : canonicalizeNumberLiteral(content, locale);
7121
7155
  }
7122
7156
  /** Change a formula to its canonical form (en_US locale) */
7123
7157
  function canonicalizeFormula(formula, locale) {
7124
- return _localizeFormula(formula, locale, DEFAULT_LOCALE);
7158
+ return _localizeFormula(formula.startsWith("+") ? "=" + formula.slice(1) : formula, locale, DEFAULT_LOCALE);
7125
7159
  }
7126
7160
  function _localizeFormula(formula, fromLocale, toLocale) {
7127
7161
  if (fromLocale.formulaArgSeparator === toLocale.formulaArgSeparator &&
@@ -10875,7 +10909,7 @@ const autoCompleteProviders = new Registry();
10875
10909
 
10876
10910
  autoCompleteProviders.add("dataValidation", {
10877
10911
  getProposals(tokenAtCursor, content) {
10878
- if (content.startsWith("=")) {
10912
+ if (isFormula(content)) {
10879
10913
  return [];
10880
10914
  }
10881
10915
  if (!this.composer.currentEditedCell) {
@@ -18684,7 +18718,7 @@ const HLOOKUP = {
18684
18718
  const _isSorted = toBoolean(isSorted.value);
18685
18719
  const colIndex = _isSorted
18686
18720
  ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range.length, getValueFromRange)
18687
- : linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange);
18721
+ : linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange, this.lookupCaches);
18688
18722
  const col = _range[colIndex];
18689
18723
  if (col === undefined) {
18690
18724
  return valueNotAvailable(searchKey);
@@ -18839,7 +18873,7 @@ const MATCH = {
18839
18873
  index = dichotomicSearch(_range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
18840
18874
  break;
18841
18875
  case 0:
18842
- index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement);
18876
+ index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement, this.lookupCaches);
18843
18877
  break;
18844
18878
  case -1:
18845
18879
  index = dichotomicSearch(_range, searchKey, "nextGreater", "desc", rangeLen, getElement);
@@ -18907,7 +18941,7 @@ const VLOOKUP = {
18907
18941
  const _isSorted = toBoolean(isSorted.value);
18908
18942
  const rowIndex = _isSorted
18909
18943
  ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range[0].length, getValueFromRange)
18910
- : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange);
18944
+ : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange, this.lookupCaches);
18911
18945
  const value = _range[_index - 1][rowIndex];
18912
18946
  if (value === undefined) {
18913
18947
  return valueNotAvailable(searchKey);
@@ -18963,7 +18997,7 @@ const XLOOKUP = {
18963
18997
  const reverseSearch = _searchMode === -1;
18964
18998
  const index = _searchMode === 2 || _searchMode === -2
18965
18999
  ? dichotomicSearch(_lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
18966
- : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
19000
+ : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, this.lookupCaches, reverseSearch);
18967
19001
  if (index !== -1) {
18968
19002
  return lookupDirection === "col"
18969
19003
  ? _returnRange.map((col) => [col[index]])
@@ -20421,7 +20455,7 @@ autoCompleteProviders.add("functions", {
20421
20455
  return [];
20422
20456
  }
20423
20457
  const searchTerm = tokenAtCursor.value;
20424
- if (!this.composer.currentContent.startsWith("=")) {
20458
+ if (!isFormula(this.composer.currentContent)) {
20425
20459
  return [];
20426
20460
  }
20427
20461
  const values = Object.entries(functionRegistry.content)
@@ -20829,7 +20863,7 @@ class AbstractComposerStore extends SpreadsheetStore {
20829
20863
  return;
20830
20864
  }
20831
20865
  if (content) {
20832
- if (content.startsWith("=")) {
20866
+ if (isFormula(content)) {
20833
20867
  const left = this.currentTokens.filter((t) => t.type === "LEFT_PAREN").length;
20834
20868
  const right = this.currentTokens.filter((t) => t.type === "RIGHT_PAREN").length;
20835
20869
  const missing = left - right;
@@ -20883,7 +20917,7 @@ class AbstractComposerStore extends SpreadsheetStore {
20883
20917
  }
20884
20918
  if (isNewCurrentContent || this.editionMode !== "inactive") {
20885
20919
  const locale = this.getters.getLocale();
20886
- this.currentTokens = text.startsWith("=") ? composerTokenize(text, locale) : [];
20920
+ this.currentTokens = isFormula(text) ? composerTokenize(text, locale) : [];
20887
20921
  if (this.currentTokens.length > 100) {
20888
20922
  if (raise) {
20889
20923
  this.notificationStore.raiseError(_t("This formula has over 100 parts. It can't be processed properly, consider splitting it into multiple cells"));
@@ -21083,7 +21117,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21083
21117
  }
21084
21118
  }
21085
21119
  updateRangeColor() {
21086
- if (!this._currentContent.startsWith("=") || this.editionMode === "inactive") {
21120
+ if (!isFormula(this._currentContent) || this.editionMode === "inactive") {
21087
21121
  return;
21088
21122
  }
21089
21123
  const editionSheetId = this.sheetId;
@@ -21112,7 +21146,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21112
21146
  * Highlight all ranges that can be found in the composer content.
21113
21147
  */
21114
21148
  get highlights() {
21115
- if (!this.currentContent.startsWith("=") || this.editionMode === "inactive") {
21149
+ if (!isFormula(this.currentContent) || this.editionMode === "inactive") {
21116
21150
  return [];
21117
21151
  }
21118
21152
  const editionSheetId = this.sheetId;
@@ -21146,7 +21180,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21146
21180
  }
21147
21181
  get autocompleteProvider() {
21148
21182
  const content = this.currentContent;
21149
- const tokenAtCursor = content.startsWith("=")
21183
+ const tokenAtCursor = isFormula(content)
21150
21184
  ? this.tokenAtCursor
21151
21185
  : { type: "STRING", value: content };
21152
21186
  if (this.editionMode === "inactive" ||
@@ -21203,7 +21237,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21203
21237
  * - Previous and next tokens can be separated by spaces
21204
21238
  */
21205
21239
  canStartComposerRangeSelection() {
21206
- if (this._currentContent.startsWith("=")) {
21240
+ if (isFormula(this._currentContent)) {
21207
21241
  const tokenAtCursor = this.tokenAtCursor;
21208
21242
  if (!tokenAtCursor) {
21209
21243
  return false;
@@ -28302,7 +28336,7 @@ function getBarChartData(definition, dataSets, labelRange, getters) {
28302
28336
  }
28303
28337
  function getPyramidChartData(definition, dataSets, labelRange, getters) {
28304
28338
  const barChartData = getBarChartData(definition, dataSets, labelRange, getters);
28305
- const barDataset = barChartData.dataSetsValues;
28339
+ const barDataset = barChartData.dataSetsValues.filter((ds) => !ds.hidden);
28306
28340
  const pyramidDatasetValues = [];
28307
28341
  if (barDataset[0]) {
28308
28342
  const pyramidData = barDataset[0].data.map((value) => (value > 0 ? value : 0));
@@ -28779,10 +28813,8 @@ function getChartDatasetFormat(getters, allDataSets, axis) {
28779
28813
  function getChartDatasetValues(getters, dataSets) {
28780
28814
  const datasetValues = [];
28781
28815
  for (const [dsIndex, ds] of Object.entries(dataSets)) {
28782
- if (getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left)) {
28783
- continue;
28784
- }
28785
28816
  let label;
28817
+ let hidden = getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left);
28786
28818
  if (ds.labelCell) {
28787
28819
  const labelRange = ds.labelCell;
28788
28820
  const cell = labelRange
@@ -28809,9 +28841,9 @@ function getChartDatasetValues(getters, dataSets) {
28809
28841
  data.fill(1);
28810
28842
  }
28811
28843
  else if (data.every((cell) => cell === undefined || cell === null || !isNumber(cell.toString(), DEFAULT_LOCALE))) {
28812
- continue;
28844
+ hidden = true;
28813
28845
  }
28814
- datasetValues.push({ data, label });
28846
+ datasetValues.push({ data, label, hidden });
28815
28847
  }
28816
28848
  return datasetValues;
28817
28849
  }
@@ -28822,12 +28854,13 @@ function getBarChartDatasets(definition, args) {
28822
28854
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28823
28855
  const trendDatasets = [];
28824
28856
  for (const index in dataSetsValues) {
28825
- let { label, data } = dataSetsValues[index];
28857
+ let { label, data, hidden } = dataSetsValues[index];
28826
28858
  label = definition.dataSets?.[index].label || label;
28827
28859
  const backgroundColor = colors.next();
28828
28860
  const dataset = {
28829
28861
  label,
28830
28862
  data,
28863
+ hidden,
28831
28864
  borderColor: definition.background || BACKGROUND_CHART_COLOR,
28832
28865
  borderWidth: definition.stacked ? 1 : 0,
28833
28866
  backgroundColor,
@@ -28860,6 +28893,9 @@ function getWaterfallDatasetAndLabels(definition, args) {
28860
28893
  const labelsWithSubTotals = [];
28861
28894
  let lastValue = 0;
28862
28895
  for (const dataSetsValue of dataSetsValues) {
28896
+ if (dataSetsValue.hidden) {
28897
+ continue;
28898
+ }
28863
28899
  for (let i = 0; i < dataSetsValue.data.length; i++) {
28864
28900
  const data = dataSetsValue.data[i];
28865
28901
  labelsWithSubTotals.push(labels[i]);
@@ -28895,7 +28931,7 @@ function getLineChartDatasets(definition, args) {
28895
28931
  const trendDatasets = [];
28896
28932
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28897
28933
  for (let index = 0; index < dataSetsValues.length; index++) {
28898
- let { label, data } = dataSetsValues[index];
28934
+ let { label, data, hidden } = dataSetsValues[index];
28899
28935
  label = definition.dataSets?.[index].label || label;
28900
28936
  const color = colors.next();
28901
28937
  if (axisType && ["linear", "time"].includes(axisType)) {
@@ -28905,6 +28941,7 @@ function getLineChartDatasets(definition, args) {
28905
28941
  const dataset = {
28906
28942
  label,
28907
28943
  data,
28944
+ hidden,
28908
28945
  tension: 0, // 0 -> render straight lines, which is much faster
28909
28946
  borderColor: color,
28910
28947
  backgroundColor: areaChart ? setColorAlpha(color, LINE_FILL_TRANSPARENCY) : color,
@@ -28937,7 +28974,9 @@ function getPieChartDatasets(definition, args) {
28937
28974
  const dataSets = [];
28938
28975
  const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
28939
28976
  const backgroundColor = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
28940
- for (const { label, data } of dataSetsValues) {
28977
+ for (const { label, data, hidden } of dataSetsValues) {
28978
+ if (hidden)
28979
+ continue;
28941
28980
  const dataset = {
28942
28981
  label,
28943
28982
  data,
@@ -28955,7 +28994,7 @@ function getComboChartDatasets(definition, args) {
28955
28994
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28956
28995
  const trendDatasets = [];
28957
28996
  for (let index = 0; index < dataSetsValues.length; index++) {
28958
- let { label, data } = dataSetsValues[index];
28997
+ let { label, data, hidden } = dataSetsValues[index];
28959
28998
  label = definition.dataSets?.[index].label || label;
28960
28999
  const design = definition.dataSets?.[index];
28961
29000
  const color = colors.next();
@@ -28963,6 +29002,7 @@ function getComboChartDatasets(definition, args) {
28963
29002
  const dataset = {
28964
29003
  label: label,
28965
29004
  data,
29005
+ hidden,
28966
29006
  borderColor: color,
28967
29007
  backgroundColor: color,
28968
29008
  yAxisID: definition.dataSets?.[index].yAxisId || "y",
@@ -28987,7 +29027,7 @@ function getRadarChartDatasets(definition, args) {
28987
29027
  const fill = definition.fillArea ?? false;
28988
29028
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28989
29029
  for (let i = 0; i < dataSetsValues.length; i++) {
28990
- let { label, data } = dataSetsValues[i];
29030
+ let { label, data, hidden } = dataSetsValues[i];
28991
29031
  if (definition.dataSets?.[i]?.label) {
28992
29032
  label = definition.dataSets[i].label;
28993
29033
  }
@@ -28995,6 +29035,7 @@ function getRadarChartDatasets(definition, args) {
28995
29035
  const dataset = {
28996
29036
  label,
28997
29037
  data,
29038
+ hidden,
28998
29039
  borderColor,
28999
29040
  backgroundColor: borderColor,
29000
29041
  };
@@ -29140,6 +29181,11 @@ function getPieChartLegend(definition, args) {
29140
29181
  hidden: false,
29141
29182
  lineWidth: 2,
29142
29183
  })),
29184
+ filter: (legendItem, data) => {
29185
+ return "datasetIndex" in legendItem
29186
+ ? !data.datasets[legendItem.datasetIndex].hidden
29187
+ : true;
29188
+ },
29143
29189
  },
29144
29190
  };
29145
29191
  }
@@ -29201,6 +29247,11 @@ function getWaterfallChartLegend(definition, args) {
29201
29247
  }
29202
29248
  return legendValues;
29203
29249
  },
29250
+ filter: (legendItem, data) => {
29251
+ return "datasetIndex" in legendItem
29252
+ ? !data.datasets[legendItem.datasetIndex].hidden
29253
+ : true;
29254
+ },
29204
29255
  },
29205
29256
  onClick: () => { }, // Disables click interaction with the waterfall chart legend items
29206
29257
  };
@@ -29284,6 +29335,11 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
29284
29335
  ...legendLabelConfig,
29285
29336
  };
29286
29337
  }),
29338
+ filter: (legendItem, data) => {
29339
+ return "datasetIndex" in legendItem
29340
+ ? !data.datasets[legendItem.datasetIndex].hidden
29341
+ : true;
29342
+ },
29287
29343
  },
29288
29344
  };
29289
29345
  }
@@ -29617,7 +29673,7 @@ const templates = /* xml */ `
29617
29673
  <div
29618
29674
  class="o-chart-custom-tooltip border rounded px-2 py-1 pe-none mw-100 position-absolute text-nowrap shadow opacity-100">
29619
29675
  <table class="overflow-hidden m-0">
29620
- <thead>
29676
+ <thead t-if="title">
29621
29677
  <tr>
29622
29678
  <th class="o-tooltip-title align-baseline border-0 text-truncate" t-esc="title" t-attf-style="max-width: {{ labelsMaxWidth }}"/>
29623
29679
  </tr>
@@ -29678,8 +29734,8 @@ function getBarChartTooltip(definition, args) {
29678
29734
  ? undefined
29679
29735
  : "";
29680
29736
  },
29737
+ beforeLabel: (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label,
29681
29738
  label: function (tooltipItem) {
29682
- const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
29683
29739
  const horizontalChart = definition.horizontal;
29684
29740
  let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
29685
29741
  if (yLabel === undefined || yLabel === null) {
@@ -29687,7 +29743,7 @@ function getBarChartTooltip(definition, args) {
29687
29743
  }
29688
29744
  const axisId = horizontalChart ? tooltipItem.dataset.xAxisID : tooltipItem.dataset.yAxisID;
29689
29745
  const yLabelStr = formatChartDatasetValue(args.axisFormats, args.locale)(yLabel, axisId);
29690
- return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
29746
+ return yLabelStr;
29691
29747
  },
29692
29748
  },
29693
29749
  };
@@ -29712,21 +29768,18 @@ function getLineChartTooltip(definition, args) {
29712
29768
  const formattedX = formatValue(label, { locale, format: labelFormat });
29713
29769
  const axisId = tooltipItem.dataset.yAxisID || "y";
29714
29770
  const formattedY = formatValue(dataSetPoint, { locale, format: axisFormats?.[axisId] });
29715
- const dataSetTitle = tooltipItem.dataset.label;
29716
- return formattedX
29717
- ? `${dataSetTitle}: (${formattedX}, ${formattedY})`
29718
- : `${dataSetTitle}: ${formattedY}`;
29771
+ return formattedX ? `(${formattedX}, ${formattedY})` : `${formattedY}`;
29719
29772
  };
29720
29773
  }
29721
29774
  else {
29722
29775
  tooltip.callbacks.label = function (tooltipItem) {
29723
- const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
29724
29776
  const yLabel = tooltipItem.parsed.y;
29725
29777
  const axisId = tooltipItem.dataset.yAxisID;
29726
29778
  const yLabelStr = formatChartDatasetValue(axisFormats, locale)(yLabel, axisId);
29727
- return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
29779
+ return yLabelStr;
29728
29780
  };
29729
29781
  }
29782
+ tooltip.callbacks.beforeLabel = (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label;
29730
29783
  tooltip.callbacks.title = function (tooltipItems) {
29731
29784
  const displayTooltipTitle = axisType !== "linear" &&
29732
29785
  tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
@@ -29744,17 +29797,15 @@ function getPieChartTooltip(definition, args) {
29744
29797
  title: function (tooltipItems) {
29745
29798
  return tooltipItems[0].dataset.label;
29746
29799
  },
29800
+ beforeLabel: (tooltipItem) => tooltipItem.label || tooltipItem.dataset.label,
29747
29801
  label: function (tooltipItem) {
29748
29802
  const data = tooltipItem.dataset.data;
29749
29803
  const dataIndex = tooltipItem.dataIndex;
29750
29804
  const percentage = calculatePercentage(data, dataIndex);
29751
- const xLabel = tooltipItem.label || tooltipItem.dataset.label;
29752
29805
  const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
29753
29806
  const toolTipFormat = !format && yLabel >= 1000 ? "#,##" : format;
29754
29807
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29755
- return xLabel
29756
- ? `${xLabel}: ${yLabelStr} (${percentage}%)`
29757
- : `${yLabelStr} (${percentage}%)`;
29808
+ return `${yLabelStr} (${percentage}%)`;
29758
29809
  },
29759
29810
  },
29760
29811
  };
@@ -29767,16 +29818,17 @@ function getWaterfallChartTooltip(definition, args) {
29767
29818
  enabled: false,
29768
29819
  external: customTooltipHandler,
29769
29820
  callbacks: {
29770
- label: function (tooltipItem) {
29771
- const [lastValue, currentValue] = tooltipItem.raw;
29772
- const yLabel = currentValue - lastValue;
29821
+ beforeLabel: function (tooltipItem) {
29773
29822
  const dataSeriesIndex = labels.length
29774
29823
  ? Math.floor(tooltipItem.dataIndex / labels.length)
29775
29824
  : 0;
29776
- const dataSeriesLabel = dataSeriesLabels[dataSeriesIndex];
29825
+ return dataSeriesLabels[dataSeriesIndex];
29826
+ },
29827
+ label: function (tooltipItem) {
29828
+ const [lastValue, currentValue] = tooltipItem.raw;
29829
+ const yLabel = currentValue - lastValue;
29777
29830
  const toolTipFormat = !format && Math.abs(yLabel) > 1000 ? "#,##" : format;
29778
- const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29779
- return dataSeriesLabel ? `${dataSeriesLabel}: ${yLabelStr}` : yLabelStr;
29831
+ return formatValue(yLabel, { format: toolTipFormat, locale });
29780
29832
  },
29781
29833
  },
29782
29834
  };
@@ -29800,11 +29852,10 @@ function getRadarChartTooltip(definition, args) {
29800
29852
  enabled: false,
29801
29853
  external: customTooltipHandler,
29802
29854
  callbacks: {
29855
+ beforeLabel: (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label,
29803
29856
  label: function (tooltipItem) {
29804
- const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
29805
29857
  const yLabel = tooltipItem.parsed.r;
29806
- const formattedY = formatValue(yLabel, { format: axisFormats?.r, locale });
29807
- return xLabel ? `${xLabel}: ${formattedY}` : formattedY;
29858
+ return formatValue(yLabel, { format: axisFormats?.r, locale });
29808
29859
  },
29809
29860
  },
29810
29861
  };
@@ -29819,13 +29870,12 @@ function getGeoChartTooltip(definition, args) {
29819
29870
  return tooltipItem.raw.value !== undefined;
29820
29871
  },
29821
29872
  callbacks: {
29873
+ beforeLabel: (tooltipItem) => tooltipItem.raw.feature.properties.name,
29822
29874
  label: function (tooltipItem) {
29823
29875
  const rawItem = tooltipItem.raw;
29824
- const xLabel = rawItem.feature.properties.name;
29825
29876
  const yLabel = rawItem.value;
29826
29877
  const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
29827
- const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29828
- return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
29878
+ return formatValue(yLabel, { format: toolTipFormat, locale });
29829
29879
  },
29830
29880
  },
29831
29881
  };
@@ -29845,7 +29895,8 @@ function customTooltipHandler({ chart, tooltip }) {
29845
29895
  return;
29846
29896
  }
29847
29897
  const tooltipItems = tooltip.body.map((body, index) => {
29848
- let [label, value] = body.lines[0].split(":").map((str) => str.trim());
29898
+ let label = body.before[0];
29899
+ let value = body.lines[0];
29849
29900
  if (!value) {
29850
29901
  value = label;
29851
29902
  label = "";
@@ -33006,6 +33057,187 @@ const FilterMenuPopoverBuilder = {
33006
33057
  },
33007
33058
  };
33008
33059
 
33060
+ const macRegex = /Mac/i;
33061
+ const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
33062
+ /**
33063
+ * Return true if the event was triggered from
33064
+ * a child element.
33065
+ */
33066
+ function isChildEvent(parent, ev) {
33067
+ if (!parent)
33068
+ return false;
33069
+ return !!ev.target && parent.contains(ev.target);
33070
+ }
33071
+ function gridOverlayPosition() {
33072
+ const spreadsheetElement = document.querySelector(".o-grid-overlay");
33073
+ if (spreadsheetElement) {
33074
+ const { top, left } = spreadsheetElement?.getBoundingClientRect();
33075
+ return { top, left };
33076
+ }
33077
+ throw new Error("Can't find spreadsheet position");
33078
+ }
33079
+ function getBoundingRectAsPOJO(el) {
33080
+ const rect = el.getBoundingClientRect();
33081
+ return {
33082
+ x: rect.x,
33083
+ y: rect.y,
33084
+ width: rect.width,
33085
+ height: rect.height,
33086
+ };
33087
+ }
33088
+ /**
33089
+ * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
33090
+ */
33091
+ function* iterateChildren(el) {
33092
+ yield el;
33093
+ if (el.hasChildNodes()) {
33094
+ for (let child of el.childNodes) {
33095
+ yield* iterateChildren(child);
33096
+ }
33097
+ }
33098
+ }
33099
+ function getOpenedMenus() {
33100
+ return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
33101
+ }
33102
+ function getCurrentSelection(el) {
33103
+ let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
33104
+ let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
33105
+ let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
33106
+ return {
33107
+ start: startSizeBefore,
33108
+ end: endSizeBefore,
33109
+ };
33110
+ }
33111
+ function getStartAndEndSelection(el) {
33112
+ const selection = document.getSelection();
33113
+ return {
33114
+ startElement: selection.anchorNode || el,
33115
+ startSelectionOffset: selection.anchorOffset,
33116
+ endElement: selection.focusNode || el,
33117
+ endSelectionOffset: selection.focusOffset,
33118
+ };
33119
+ }
33120
+ /**
33121
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
33122
+ * The selected node is either a Text node or an Element node.
33123
+ *
33124
+ * case 1 -Text node:
33125
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
33126
+ * content length of all previous nodes.
33127
+ *
33128
+ * case 2 - Element node:
33129
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
33130
+ * all the nodes prior to the selected node as well as the content of the child node before the offset.
33131
+ *
33132
+ * See the MDN documentation for more details.
33133
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
33134
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
33135
+ *
33136
+ */
33137
+ function findSelectionIndex(el, nodeToFind, nodeOffset) {
33138
+ let usedCharacters = 0;
33139
+ let it = iterateChildren(el);
33140
+ let current = it.next();
33141
+ let isFirstParagraph = true;
33142
+ while (!current.done && current.value !== nodeToFind) {
33143
+ if (!current.value.hasChildNodes()) {
33144
+ if (current.value.textContent) {
33145
+ usedCharacters += current.value.textContent.length;
33146
+ }
33147
+ }
33148
+ // One new paragraph = one new line character, except for the first paragraph
33149
+ if (current.value.nodeName === "P" ||
33150
+ (current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
33151
+ ) {
33152
+ if (isFirstParagraph) {
33153
+ isFirstParagraph = false;
33154
+ }
33155
+ else {
33156
+ usedCharacters++;
33157
+ }
33158
+ }
33159
+ current = it.next();
33160
+ }
33161
+ if (current.value !== nodeToFind) {
33162
+ /** This situation can happen if the code is called while the selection is not currently on the element.
33163
+ * In this case, we return 0 because we don't know the size of the text before the selection.
33164
+ *
33165
+ * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
33166
+ */
33167
+ return 0;
33168
+ }
33169
+ else {
33170
+ if (!current.value.hasChildNodes()) {
33171
+ usedCharacters += nodeOffset;
33172
+ }
33173
+ else {
33174
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
33175
+ usedCharacters += children.reduce((acc, child, index) => {
33176
+ if (child.textContent !== null) {
33177
+ // need to account for paragraph nodes that implicitly add a new line
33178
+ // except for the last paragraph
33179
+ let chars = child.textContent.length;
33180
+ if (child.nodeName === "P" && index !== children.length - 1) {
33181
+ chars++;
33182
+ }
33183
+ return acc + chars;
33184
+ }
33185
+ else {
33186
+ return acc;
33187
+ }
33188
+ }, 0);
33189
+ }
33190
+ }
33191
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
33192
+ usedCharacters++;
33193
+ }
33194
+ return usedCharacters;
33195
+ }
33196
+ const letterRegex = /^[a-zA-Z]$/;
33197
+ /**
33198
+ * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
33199
+ *
33200
+ * @argument ev - The keyboard event to transform
33201
+ * @argument mode - Use either ev.key of ev.code to get the string shortcut
33202
+ *
33203
+ * @example
33204
+ * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
33205
+ * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
33206
+ */
33207
+ function keyboardEventToShortcutString(ev, mode = "key") {
33208
+ let keyDownString = "";
33209
+ if (!MODIFIER_KEYS.includes(ev.key)) {
33210
+ if (isCtrlKey(ev))
33211
+ keyDownString += "Ctrl+";
33212
+ if (ev.altKey)
33213
+ keyDownString += "Alt+";
33214
+ if (ev.shiftKey)
33215
+ keyDownString += "Shift+";
33216
+ }
33217
+ const key = mode === "key" ? ev.key : ev.code;
33218
+ keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
33219
+ return keyDownString;
33220
+ }
33221
+ function isMacOS() {
33222
+ return Boolean(macRegex.test(navigator.userAgent));
33223
+ }
33224
+ /**
33225
+ * @param {KeyboardEvent | MouseEvent} ev
33226
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
33227
+ * On Mac, this is the "meta" or "command" key.
33228
+ */
33229
+ function isCtrlKey(ev) {
33230
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
33231
+ }
33232
+ /**
33233
+ * @param {MouseEvent} ev - The mouse event.
33234
+ * @returns {boolean} Returns true if the event was triggered by a middle-click
33235
+ * or a Ctrl + Click (Cmd + Click on Mac).
33236
+ */
33237
+ function isMiddleClickOrCtrlClick(ev) {
33238
+ return ev.button === 1 || (isCtrlKey(ev) && ev.button === 0);
33239
+ }
33240
+
33009
33241
  const LINK_TOOLTIP_HEIGHT = 32;
33010
33242
  const LINK_TOOLTIP_WIDTH = 220;
33011
33243
  css /* scss */ `
@@ -33080,8 +33312,8 @@ class LinkDisplay extends Component {
33080
33312
  getUrlRepresentation(link) {
33081
33313
  return urlRepresentation(link, this.env.model.getters);
33082
33314
  }
33083
- openLink() {
33084
- openLink(this.link, this.env);
33315
+ openLink(ev) {
33316
+ openLink(this.link, this.env, isMiddleClickOrCtrlClick(ev));
33085
33317
  }
33086
33318
  edit() {
33087
33319
  const { col, row } = this.props.cellPosition;
@@ -33210,179 +33442,6 @@ linkMenuRegistry.add("sheet", {
33210
33442
  sequence: 10,
33211
33443
  });
33212
33444
 
33213
- const macRegex = /Mac/i;
33214
- const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
33215
- /**
33216
- * Return true if the event was triggered from
33217
- * a child element.
33218
- */
33219
- function isChildEvent(parent, ev) {
33220
- if (!parent)
33221
- return false;
33222
- return !!ev.target && parent.contains(ev.target);
33223
- }
33224
- function gridOverlayPosition() {
33225
- const spreadsheetElement = document.querySelector(".o-grid-overlay");
33226
- if (spreadsheetElement) {
33227
- const { top, left } = spreadsheetElement?.getBoundingClientRect();
33228
- return { top, left };
33229
- }
33230
- throw new Error("Can't find spreadsheet position");
33231
- }
33232
- function getBoundingRectAsPOJO(el) {
33233
- const rect = el.getBoundingClientRect();
33234
- return {
33235
- x: rect.x,
33236
- y: rect.y,
33237
- width: rect.width,
33238
- height: rect.height,
33239
- };
33240
- }
33241
- /**
33242
- * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
33243
- */
33244
- function* iterateChildren(el) {
33245
- yield el;
33246
- if (el.hasChildNodes()) {
33247
- for (let child of el.childNodes) {
33248
- yield* iterateChildren(child);
33249
- }
33250
- }
33251
- }
33252
- function getOpenedMenus() {
33253
- return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
33254
- }
33255
- function getCurrentSelection(el) {
33256
- let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
33257
- let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
33258
- let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
33259
- return {
33260
- start: startSizeBefore,
33261
- end: endSizeBefore,
33262
- };
33263
- }
33264
- function getStartAndEndSelection(el) {
33265
- const selection = document.getSelection();
33266
- return {
33267
- startElement: selection.anchorNode || el,
33268
- startSelectionOffset: selection.anchorOffset,
33269
- endElement: selection.focusNode || el,
33270
- endSelectionOffset: selection.focusOffset,
33271
- };
33272
- }
33273
- /**
33274
- * Computes the text 'index' inside this.el based on the currently selected node and its offset.
33275
- * The selected node is either a Text node or an Element node.
33276
- *
33277
- * case 1 -Text node:
33278
- * the offset is the number of characters from the start of the node. We have to add this offset to the
33279
- * content length of all previous nodes.
33280
- *
33281
- * case 2 - Element node:
33282
- * the offset is the number of child nodes before the selected node. We have to add the content length of
33283
- * all the nodes prior to the selected node as well as the content of the child node before the offset.
33284
- *
33285
- * See the MDN documentation for more details.
33286
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
33287
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
33288
- *
33289
- */
33290
- function findSelectionIndex(el, nodeToFind, nodeOffset) {
33291
- let usedCharacters = 0;
33292
- let it = iterateChildren(el);
33293
- let current = it.next();
33294
- let isFirstParagraph = true;
33295
- while (!current.done && current.value !== nodeToFind) {
33296
- if (!current.value.hasChildNodes()) {
33297
- if (current.value.textContent) {
33298
- usedCharacters += current.value.textContent.length;
33299
- }
33300
- }
33301
- // One new paragraph = one new line character, except for the first paragraph
33302
- if (current.value.nodeName === "P" ||
33303
- (current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
33304
- ) {
33305
- if (isFirstParagraph) {
33306
- isFirstParagraph = false;
33307
- }
33308
- else {
33309
- usedCharacters++;
33310
- }
33311
- }
33312
- current = it.next();
33313
- }
33314
- if (current.value !== nodeToFind) {
33315
- /** This situation can happen if the code is called while the selection is not currently on the element.
33316
- * In this case, we return 0 because we don't know the size of the text before the selection.
33317
- *
33318
- * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
33319
- */
33320
- return 0;
33321
- }
33322
- else {
33323
- if (!current.value.hasChildNodes()) {
33324
- usedCharacters += nodeOffset;
33325
- }
33326
- else {
33327
- const children = [...current.value.childNodes].slice(0, nodeOffset);
33328
- usedCharacters += children.reduce((acc, child, index) => {
33329
- if (child.textContent !== null) {
33330
- // need to account for paragraph nodes that implicitly add a new line
33331
- // except for the last paragraph
33332
- let chars = child.textContent.length;
33333
- if (child.nodeName === "P" && index !== children.length - 1) {
33334
- chars++;
33335
- }
33336
- return acc + chars;
33337
- }
33338
- else {
33339
- return acc;
33340
- }
33341
- }, 0);
33342
- }
33343
- }
33344
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
33345
- usedCharacters++;
33346
- }
33347
- return usedCharacters;
33348
- }
33349
- const letterRegex = /^[a-zA-Z]$/;
33350
- /**
33351
- * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
33352
- *
33353
- * @argument ev - The keyboard event to transform
33354
- * @argument mode - Use either ev.key of ev.code to get the string shortcut
33355
- *
33356
- * @example
33357
- * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
33358
- * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
33359
- */
33360
- function keyboardEventToShortcutString(ev, mode = "key") {
33361
- let keyDownString = "";
33362
- if (!MODIFIER_KEYS.includes(ev.key)) {
33363
- if (isCtrlKey(ev))
33364
- keyDownString += "Ctrl+";
33365
- if (ev.altKey)
33366
- keyDownString += "Alt+";
33367
- if (ev.shiftKey)
33368
- keyDownString += "Shift+";
33369
- }
33370
- const key = mode === "key" ? ev.key : ev.code;
33371
- keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
33372
- return keyDownString;
33373
- }
33374
- function isMacOS() {
33375
- return Boolean(macRegex.test(navigator.userAgent));
33376
- }
33377
- /**
33378
- * @param {KeyboardEvent | MouseEvent} ev
33379
- * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
33380
- * On Mac, this is the "meta" or "command" key.
33381
- */
33382
- function isCtrlKey(ev) {
33383
- return isMacOS() ? ev.metaKey : ev.ctrlKey;
33384
- }
33385
-
33386
33445
  /**
33387
33446
  * Repeatedly calls a callback function with a time delay between calls.
33388
33447
  */
@@ -33577,8 +33636,8 @@ class Menu extends Component {
33577
33636
  getIconColor(menu) {
33578
33637
  return cssPropertiesToCss({ color: menu.iconColor });
33579
33638
  }
33580
- async activateMenu(menu) {
33581
- const result = await menu.execute?.(this.env);
33639
+ async activateMenu(menu, isMiddleClick) {
33640
+ const result = await menu.execute?.(this.env, isMiddleClick);
33582
33641
  this.close();
33583
33642
  this.props.onMenuClicked?.({ detail: result });
33584
33643
  }
@@ -33641,13 +33700,14 @@ class Menu extends Component {
33641
33700
  this.subMenu.parentMenu = undefined;
33642
33701
  }
33643
33702
  onClickMenu(menu, ev) {
33644
- if (this.isEnabled(menu)) {
33645
- if (this.isRoot(menu)) {
33646
- this.openSubMenu(menu, ev.currentTarget);
33647
- }
33648
- else {
33649
- this.activateMenu(menu);
33650
- }
33703
+ if (!this.isEnabled(menu)) {
33704
+ return;
33705
+ }
33706
+ if (this.isRoot(menu)) {
33707
+ this.openSubMenu(menu, ev.currentTarget);
33708
+ }
33709
+ else {
33710
+ this.activateMenu(menu, isMiddleClickOrCtrlClick(ev));
33651
33711
  }
33652
33712
  }
33653
33713
  onMouseOver(menu, ev) {
@@ -40275,7 +40335,7 @@ class Composer extends Component {
40275
40335
  const content = this.props.composerStore.currentContent;
40276
40336
  if (this.props.focus === "cellFocus" &&
40277
40337
  !this.autoCompleteState.provider &&
40278
- !content.startsWith("=")) {
40338
+ !isFormula(content)) {
40279
40339
  this.props.composerStore.stopEdition();
40280
40340
  return;
40281
40341
  }
@@ -40492,7 +40552,7 @@ class Composer extends Component {
40492
40552
  return;
40493
40553
  }
40494
40554
  const composerContent = this.props.composerStore.currentContent;
40495
- const isValidFormula = composerContent.startsWith("=");
40555
+ const isValidFormula = isFormula(composerContent);
40496
40556
  if (isValidFormula) {
40497
40557
  const tokens = this.props.composerStore.currentTokens;
40498
40558
  const currentSelection = this.contentHelper.getCurrentSelection();
@@ -40549,7 +40609,7 @@ class Composer extends Component {
40549
40609
  */
40550
40610
  getContentLines() {
40551
40611
  let value = this.props.composerStore.currentContent;
40552
- const isValidFormula = value.startsWith("=");
40612
+ const isValidFormula = isFormula(value);
40553
40613
  if (value === "") {
40554
40614
  return [];
40555
40615
  }
@@ -40638,7 +40698,7 @@ class Composer extends Component {
40638
40698
  this.autoCompleteState.useProvider(autoCompleteProvider);
40639
40699
  }
40640
40700
  const token = this.props.composerStore.tokenAtCursor;
40641
- if (content.startsWith("=") && token && token.type !== "SYMBOL") {
40701
+ if (isFormula(content) && token && token.type !== "SYMBOL") {
40642
40702
  const tokenContext = token.functionContext;
40643
40703
  const parentFunction = tokenContext?.parent.toUpperCase();
40644
40704
  if (tokenContext &&
@@ -48696,7 +48756,7 @@ class CellComposerStore extends AbstractComposerStore {
48696
48756
  }
48697
48757
  break;
48698
48758
  case "ACTIVATE_SHEET":
48699
- if (!this._currentContent.startsWith("=")) {
48759
+ if (!isFormula(this._currentContent)) {
48700
48760
  this._cancelEdition();
48701
48761
  this.resetContent();
48702
48762
  }
@@ -48772,7 +48832,7 @@ class CellComposerStore extends AbstractComposerStore {
48772
48832
  if (content) {
48773
48833
  const sheetId = this.getters.getActiveSheetId();
48774
48834
  const cell = this.getters.getEvaluatedCell({ sheetId, col: this.col, row: this.row });
48775
- if (cell.link && !content.startsWith("=")) {
48835
+ if (cell.link && !isFormula(content)) {
48776
48836
  content = markdownLink(content, cell.link.url);
48777
48837
  }
48778
48838
  this.addHeadersForSpreadingFormula(content);
@@ -48832,7 +48892,7 @@ class CellComposerStore extends AbstractComposerStore {
48832
48892
  }
48833
48893
  /** Add headers at the end of the sheet so the formula in the composer has enough space to spread */
48834
48894
  addHeadersForSpreadingFormula(content) {
48835
- if (!content.startsWith("=")) {
48895
+ if (!isFormula(content)) {
48836
48896
  return;
48837
48897
  }
48838
48898
  const evaluated = this.getters.evaluateFormula(this.sheetId, content);
@@ -48865,7 +48925,7 @@ class CellComposerStore extends AbstractComposerStore {
48865
48925
  checkDataValidation() {
48866
48926
  const cellPosition = { sheetId: this.sheetId, col: this.col, row: this.row };
48867
48927
  const content = this.getCurrentCanonicalContent();
48868
- const cellValue = content.startsWith("=")
48928
+ const cellValue = isFormula(content)
48869
48929
  ? this.getters.evaluateFormula(this.sheetId, content)
48870
48930
  : parseLiteral(content, this.getters.getLocale());
48871
48931
  if (isMatrix(cellValue)) {
@@ -48987,23 +49047,23 @@ class GridComposer extends Component {
48987
49047
  if (this.composerStore.editionMode === "inactive") {
48988
49048
  return `z-index: -1000;`;
48989
49049
  }
48990
- const isFormula = this.composerStore.currentContent.startsWith("=");
49050
+ const _isFormula = isFormula(this.composerStore.currentContent);
48991
49051
  const cell = this.env.model.getters.getActiveCell();
48992
49052
  const position = this.env.model.getters.getActivePosition();
48993
49053
  const style = this.env.model.getters.getCellComputedStyle(position);
48994
49054
  // position style
48995
49055
  const { x: left, y: top, width, height } = this.rect;
48996
49056
  // color style
48997
- const background = (!isFormula && style.fillColor) || "#ffffff";
48998
- const color = (!isFormula && style.textColor) || "#000000";
49057
+ const background = (!_isFormula && style.fillColor) || "#ffffff";
49058
+ const color = (!_isFormula && style.textColor) || "#000000";
48999
49059
  // font style
49000
- const fontSize = (!isFormula && style.fontSize) || 10;
49001
- const fontWeight = !isFormula && style.bold ? "bold" : undefined;
49002
- const fontStyle = !isFormula && style.italic ? "italic" : "normal";
49003
- const textDecoration = !isFormula ? getTextDecoration(style) : "none";
49060
+ const fontSize = (!_isFormula && style.fontSize) || 10;
49061
+ const fontWeight = !_isFormula && style.bold ? "bold" : undefined;
49062
+ const fontStyle = !_isFormula && style.italic ? "italic" : "normal";
49063
+ const textDecoration = !_isFormula ? getTextDecoration(style) : "none";
49004
49064
  // align style
49005
49065
  let textAlign = "left";
49006
- if (!isFormula) {
49066
+ if (!_isFormula) {
49007
49067
  textAlign = style.align || cell.defaultAlign;
49008
49068
  }
49009
49069
  const maxHeight = this.props.gridDims.height - this.rect.y;
@@ -51637,8 +51697,8 @@ class Border extends Component {
51637
51697
  css /* scss */ `
51638
51698
  .o-corner {
51639
51699
  position: absolute;
51640
- height: 6px;
51641
- width: 6px;
51700
+ height: 8px;
51701
+ width: 8px;
51642
51702
  border: 1px solid white;
51643
51703
  }
51644
51704
  .o-corner-nw,
@@ -53203,7 +53263,7 @@ class BordersPlugin extends CorePlugin {
53203
53263
  this.history.update("borders", sheetId, col, row, "left", border?.left);
53204
53264
  if (border?.left &&
53205
53265
  col > 0 &&
53206
- !deepEquals(this.getCellBorder({ sheetId, col: col - 1, row })?.right, border?.left)) {
53266
+ !deepEquals(this.borders[sheetId]?.[col - 1]?.[row]?.right, border?.left)) {
53207
53267
  this.history.update("borders", sheetId, col - 1, row, "right", undefined);
53208
53268
  }
53209
53269
  }
@@ -53211,7 +53271,7 @@ class BordersPlugin extends CorePlugin {
53211
53271
  this.history.update("borders", sheetId, col, row, "top", border?.top);
53212
53272
  if (border?.top &&
53213
53273
  row > 0 &&
53214
- !deepEquals(this.getCellBorder({ sheetId, col, row: row - 1 })?.bottom, border?.top)) {
53274
+ !deepEquals(this.borders[sheetId]?.[col]?.[row - 1]?.bottom, border?.top)) {
53215
53275
  this.history.update("borders", sheetId, col, row - 1, "bottom", undefined);
53216
53276
  }
53217
53277
  }
@@ -53219,7 +53279,7 @@ class BordersPlugin extends CorePlugin {
53219
53279
  this.history.update("borders", sheetId, col, row, "right", border?.right);
53220
53280
  if (border?.right &&
53221
53281
  col < maxCol &&
53222
- !deepEquals(this.getCellBorder({ sheetId, col: col + 1, row })?.left, border?.right)) {
53282
+ !deepEquals(this.borders[sheetId]?.[col + 1]?.[row]?.left, border?.right)) {
53223
53283
  this.history.update("borders", sheetId, col + 1, row, "left", undefined);
53224
53284
  }
53225
53285
  }
@@ -53227,7 +53287,7 @@ class BordersPlugin extends CorePlugin {
53227
53287
  this.history.update("borders", sheetId, col, row, "bottom", border?.bottom);
53228
53288
  if (border?.bottom &&
53229
53289
  row < maxRow &&
53230
- !deepEquals(this.getCellBorder({ sheetId, col, row: row + 1 })?.top, border?.bottom)) {
53290
+ !deepEquals(this.borders[sheetId]?.[col]?.[row + 1]?.top, border?.bottom)) {
53231
53291
  this.history.update("borders", sheetId, col, row + 1, "top", undefined);
53232
53292
  }
53233
53293
  }
@@ -59999,6 +60059,10 @@ class Evaluator {
59999
60059
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
60000
60060
  this.compilationParams.evalContext.updateDependencies = this.updateDependencies.bind(this);
60001
60061
  this.compilationParams.evalContext.addDependencies = this.addDependencies.bind(this);
60062
+ this.compilationParams.evalContext.lookupCaches = {
60063
+ forwardSearch: new Map(),
60064
+ reverseSearch: new Map(),
60065
+ };
60002
60066
  }
60003
60067
  createEmptyPositionSet() {
60004
60068
  const sheetSizes = {};
@@ -68615,7 +68679,7 @@ clickableCellRegistry.add("link", {
68615
68679
  condition: (position, getters) => {
68616
68680
  return !!getters.getEvaluatedCell(position).link;
68617
68681
  },
68618
- execute: (position, env) => openLink(env.model.getters.getEvaluatedCell(position).link, env),
68682
+ execute: (position, env, isMiddleClick) => openLink(env.model.getters.getEvaluatedCell(position).link, env, isMiddleClick),
68619
68683
  sequence: 5,
68620
68684
  });
68621
68685
 
@@ -69600,9 +69664,9 @@ class SpreadsheetDashboard extends Component {
69600
69664
  getClickableCells() {
69601
69665
  return toRaw(this.clickableCellsStore.clickableCells);
69602
69666
  }
69603
- selectClickableCell(clickableCell) {
69667
+ selectClickableCell(ev, clickableCell) {
69604
69668
  const { position, action } = clickableCell;
69605
- action(position, this.env);
69669
+ action(position, this.env, isMiddleClickOrCtrlClick(ev));
69606
69670
  }
69607
69671
  onClosePopover() {
69608
69672
  this.cellPopovers.close();
@@ -75564,6 +75628,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
75564
75628
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
75565
75629
 
75566
75630
 
75567
- __info__.version = "18.2.0";
75568
- __info__.date = "2025-02-18T08:27:07.101Z";
75569
- __info__.hash = "d708714";
75631
+ __info__.version = "18.3.0-alpha.1";
75632
+ __info__.date = "2025-02-25T06:00:14.885Z";
75633
+ __info__.hash = "be4d957";