@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
  'use strict';
@@ -994,6 +994,9 @@ function getUniqueText(text, texts, options = {}) {
994
994
  }
995
995
  return newText;
996
996
  }
997
+ function isFormula(content) {
998
+ return content.startsWith("=") || content.startsWith("+");
999
+ }
997
1000
 
998
1001
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
999
1002
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
@@ -4460,7 +4463,7 @@ function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueIn
4460
4463
  * @param reverseSearch if true, search in the array starting from the end.
4461
4464
 
4462
4465
  */
4463
- function linearSearch(data, target, mode, numberOfValues, getValueInData, reverseSearch = false) {
4466
+ function linearSearch(data, target, mode, numberOfValues, getValueInData, lookupCaches, reverseSearch = false) {
4464
4467
  if (target === undefined || target.value === null) {
4465
4468
  return -1;
4466
4469
  }
@@ -4469,17 +4472,48 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4469
4472
  }
4470
4473
  const _target = normalizeValue(target.value);
4471
4474
  const getValue = reverseSearch
4472
- ? (data, i) => getValueInData(data, numberOfValues - i - 1)
4473
- : getValueInData;
4475
+ ? (data, i) => normalizeValue(getValueInData(data, numberOfValues - i - 1))
4476
+ : (data, i) => normalizeValue(getValueInData(data, i));
4477
+ // first check if the target is in the cache
4478
+ const isNotWildcardTarget = mode !== "wildcard" ||
4479
+ typeof _target !== "string" ||
4480
+ !(_target.includes("*") || _target.includes("?"));
4481
+ if (lookupCaches && isNotWildcardTarget) {
4482
+ const searchMode = reverseSearch ? "reverseSearch" : "forwardSearch";
4483
+ let cache = lookupCaches[searchMode].get(data);
4484
+ if (cache === undefined) {
4485
+ // build the cache for all the values
4486
+ cache = new Map();
4487
+ for (let i = 0; i < numberOfValues; i++) {
4488
+ const value = getValue(data, i) ?? null;
4489
+ if (!cache.has(value)) {
4490
+ cache.set(value, i);
4491
+ }
4492
+ }
4493
+ lookupCaches[searchMode].set(data, cache);
4494
+ }
4495
+ if (cache.has(_target)) {
4496
+ const resultIndex = cache.get(_target);
4497
+ return reverseSearch ? numberOfValues - resultIndex - 1 : resultIndex;
4498
+ }
4499
+ if (mode === "strict") {
4500
+ return -1;
4501
+ }
4502
+ }
4503
+ // else perform the linear search
4504
+ const resultIndex = _linearSearch(data, _target, mode, numberOfValues, getValue);
4505
+ return reverseSearch && resultIndex !== -1 ? numberOfValues - resultIndex - 1 : resultIndex;
4506
+ }
4507
+ function _linearSearch(data, _target, mode, numberOfValues, getNormalizeValue) {
4474
4508
  let indexMatchTarget = (i) => {
4475
- return normalizeValue(getValue(data, i)) === _target;
4509
+ return getNormalizeValue(data, i) === _target;
4476
4510
  };
4477
4511
  if (mode === "wildcard" &&
4478
4512
  typeof _target === "string" &&
4479
4513
  (_target.includes("*") || _target.includes("?"))) {
4480
4514
  const regExp = wildcardToRegExp(_target);
4481
4515
  indexMatchTarget = (i) => {
4482
- const value = normalizeValue(getValue(data, i));
4516
+ const value = getNormalizeValue(data, i);
4483
4517
  if (typeof value === "string") {
4484
4518
  return regExp.test(value);
4485
4519
  }
@@ -4490,7 +4524,7 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4490
4524
  let closestMatchIndex = -1;
4491
4525
  if (mode === "nextSmaller") {
4492
4526
  indexMatchTarget = (i) => {
4493
- const value = normalizeValue(getValue(data, i));
4527
+ const value = getNormalizeValue(data, i);
4494
4528
  if ((!closestMatch && compareCellValues(_target, value) >= 0) ||
4495
4529
  (compareCellValues(_target, value) >= 0 && compareCellValues(value, closestMatch) > 0)) {
4496
4530
  closestMatch = value;
@@ -4501,7 +4535,7 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4501
4535
  }
4502
4536
  if (mode === "nextGreater") {
4503
4537
  indexMatchTarget = (i) => {
4504
- const value = normalizeValue(getValue(data, i));
4538
+ const value = getNormalizeValue(data, i);
4505
4539
  if ((!closestMatch && compareCellValues(_target, value) <= 0) ||
4506
4540
  (compareCellValues(_target, value) <= 0 && compareCellValues(value, closestMatch) < 0)) {
4507
4541
  closestMatch = value;
@@ -4512,12 +4546,10 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4512
4546
  }
4513
4547
  for (let i = 0; i < numberOfValues; i++) {
4514
4548
  if (indexMatchTarget(i)) {
4515
- return reverseSearch ? numberOfValues - i - 1 : i;
4549
+ return i;
4516
4550
  }
4517
4551
  }
4518
- return reverseSearch && closestMatchIndex !== -1
4519
- ? numberOfValues - closestMatchIndex - 1
4520
- : closestMatchIndex;
4552
+ return closestMatchIndex;
4521
4553
  }
4522
4554
  /**
4523
4555
  * Normalize a value.
@@ -4633,8 +4665,8 @@ function findMatchingSpec(url) {
4633
4665
  function urlRepresentation(link, getters) {
4634
4666
  return findMatchingSpec(link.url).urlRepresentation(link.url, getters);
4635
4667
  }
4636
- function openLink(link, env) {
4637
- findMatchingSpec(link.url).open(link.url, env);
4668
+ function openLink(link, env, isMiddleClick) {
4669
+ findMatchingSpec(link.url).open(link.url, env, isMiddleClick);
4638
4670
  }
4639
4671
  function detectLink(value) {
4640
4672
  if (typeof value !== "string") {
@@ -6491,10 +6523,11 @@ class UuidGenerator {
6491
6523
  *
6492
6524
  */
6493
6525
  smallUuid() {
6494
- //@ts-ignore
6495
- if (window.crypto && window.crypto.getRandomValues) {
6496
- //@ts-ignore
6497
- return ([1e7] + -1e3).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
6526
+ if (window.crypto) {
6527
+ return "10000000-1000".replace(/[01]/g, (c) => {
6528
+ const n = Number(c);
6529
+ return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
6530
+ });
6498
6531
  }
6499
6532
  else {
6500
6533
  // mainly for jest and other browsers that do not have the crypto functionality
@@ -6509,10 +6542,11 @@ class UuidGenerator {
6509
6542
  * This method should be used when you need to avoid collisions at all costs, like the id of a revision.
6510
6543
  */
6511
6544
  uuidv4() {
6512
- //@ts-ignore
6513
- if (window.crypto && window.crypto.getRandomValues) {
6514
- //@ts-ignore
6515
- return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
6545
+ if (window.crypto) {
6546
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => {
6547
+ const n = Number(c);
6548
+ return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
6549
+ });
6516
6550
  }
6517
6551
  else {
6518
6552
  // mainly for jest and other browsers that do not have the crypto functionality
@@ -6920,7 +6954,7 @@ function isValidLocale(locale) {
6920
6954
  * canonicalizeNumberContent("02/12/2012", FR_LOCALE) // "02/12/2012"
6921
6955
  */
6922
6956
  function canonicalizeNumberContent(content, locale) {
6923
- return content.startsWith("=")
6957
+ return isFormula(content)
6924
6958
  ? canonicalizeFormula$1(content, locale)
6925
6959
  : canonicalizeNumberLiteral(content, locale);
6926
6960
  }
@@ -6935,7 +6969,7 @@ function canonicalizeNumberContent(content, locale) {
6935
6969
  * canonicalizeContent("02-12-2012", FR_LOCALE) // "12/02/2012"
6936
6970
  */
6937
6971
  function canonicalizeContent(content, locale) {
6938
- return content.startsWith("=")
6972
+ return isFormula(content)
6939
6973
  ? canonicalizeFormula$1(content, locale)
6940
6974
  : canonicalizeLiteral(content, locale);
6941
6975
  }
@@ -6954,7 +6988,7 @@ function localizeContent(content, locale) {
6954
6988
  }
6955
6989
  /** Change a formula to its canonical form (en_US locale) */
6956
6990
  function canonicalizeFormula$1(formula, locale) {
6957
- return _localizeFormula$1(formula, locale, DEFAULT_LOCALE);
6991
+ return _localizeFormula$1(formula.startsWith("+") ? "=" + formula.slice(1) : formula, locale, DEFAULT_LOCALE);
6958
6992
  }
6959
6993
  /** Change a formula from the canonical form to the given locale */
6960
6994
  function localizeFormula(formula, locale) {
@@ -7117,13 +7151,13 @@ function getDateTimeFormat(locale) {
7117
7151
 
7118
7152
  /** Change a number string to its canonical form (en_US locale) */
7119
7153
  function canonicalizeNumberValue(content, locale) {
7120
- return content.startsWith("=")
7154
+ return isFormula(content)
7121
7155
  ? canonicalizeFormula(content, locale)
7122
7156
  : canonicalizeNumberLiteral(content, locale);
7123
7157
  }
7124
7158
  /** Change a formula to its canonical form (en_US locale) */
7125
7159
  function canonicalizeFormula(formula, locale) {
7126
- return _localizeFormula(formula, locale, DEFAULT_LOCALE);
7160
+ return _localizeFormula(formula.startsWith("+") ? "=" + formula.slice(1) : formula, locale, DEFAULT_LOCALE);
7127
7161
  }
7128
7162
  function _localizeFormula(formula, fromLocale, toLocale) {
7129
7163
  if (fromLocale.formulaArgSeparator === toLocale.formulaArgSeparator &&
@@ -10877,7 +10911,7 @@ const autoCompleteProviders = new Registry();
10877
10911
 
10878
10912
  autoCompleteProviders.add("dataValidation", {
10879
10913
  getProposals(tokenAtCursor, content) {
10880
- if (content.startsWith("=")) {
10914
+ if (isFormula(content)) {
10881
10915
  return [];
10882
10916
  }
10883
10917
  if (!this.composer.currentEditedCell) {
@@ -18686,7 +18720,7 @@ const HLOOKUP = {
18686
18720
  const _isSorted = toBoolean(isSorted.value);
18687
18721
  const colIndex = _isSorted
18688
18722
  ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range.length, getValueFromRange)
18689
- : linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange);
18723
+ : linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange, this.lookupCaches);
18690
18724
  const col = _range[colIndex];
18691
18725
  if (col === undefined) {
18692
18726
  return valueNotAvailable(searchKey);
@@ -18841,7 +18875,7 @@ const MATCH = {
18841
18875
  index = dichotomicSearch(_range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
18842
18876
  break;
18843
18877
  case 0:
18844
- index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement);
18878
+ index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement, this.lookupCaches);
18845
18879
  break;
18846
18880
  case -1:
18847
18881
  index = dichotomicSearch(_range, searchKey, "nextGreater", "desc", rangeLen, getElement);
@@ -18909,7 +18943,7 @@ const VLOOKUP = {
18909
18943
  const _isSorted = toBoolean(isSorted.value);
18910
18944
  const rowIndex = _isSorted
18911
18945
  ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range[0].length, getValueFromRange)
18912
- : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange);
18946
+ : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange, this.lookupCaches);
18913
18947
  const value = _range[_index - 1][rowIndex];
18914
18948
  if (value === undefined) {
18915
18949
  return valueNotAvailable(searchKey);
@@ -18965,7 +18999,7 @@ const XLOOKUP = {
18965
18999
  const reverseSearch = _searchMode === -1;
18966
19000
  const index = _searchMode === 2 || _searchMode === -2
18967
19001
  ? dichotomicSearch(_lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
18968
- : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
19002
+ : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, this.lookupCaches, reverseSearch);
18969
19003
  if (index !== -1) {
18970
19004
  return lookupDirection === "col"
18971
19005
  ? _returnRange.map((col) => [col[index]])
@@ -20423,7 +20457,7 @@ autoCompleteProviders.add("functions", {
20423
20457
  return [];
20424
20458
  }
20425
20459
  const searchTerm = tokenAtCursor.value;
20426
- if (!this.composer.currentContent.startsWith("=")) {
20460
+ if (!isFormula(this.composer.currentContent)) {
20427
20461
  return [];
20428
20462
  }
20429
20463
  const values = Object.entries(functionRegistry.content)
@@ -20831,7 +20865,7 @@ class AbstractComposerStore extends SpreadsheetStore {
20831
20865
  return;
20832
20866
  }
20833
20867
  if (content) {
20834
- if (content.startsWith("=")) {
20868
+ if (isFormula(content)) {
20835
20869
  const left = this.currentTokens.filter((t) => t.type === "LEFT_PAREN").length;
20836
20870
  const right = this.currentTokens.filter((t) => t.type === "RIGHT_PAREN").length;
20837
20871
  const missing = left - right;
@@ -20885,7 +20919,7 @@ class AbstractComposerStore extends SpreadsheetStore {
20885
20919
  }
20886
20920
  if (isNewCurrentContent || this.editionMode !== "inactive") {
20887
20921
  const locale = this.getters.getLocale();
20888
- this.currentTokens = text.startsWith("=") ? composerTokenize(text, locale) : [];
20922
+ this.currentTokens = isFormula(text) ? composerTokenize(text, locale) : [];
20889
20923
  if (this.currentTokens.length > 100) {
20890
20924
  if (raise) {
20891
20925
  this.notificationStore.raiseError(_t("This formula has over 100 parts. It can't be processed properly, consider splitting it into multiple cells"));
@@ -21085,7 +21119,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21085
21119
  }
21086
21120
  }
21087
21121
  updateRangeColor() {
21088
- if (!this._currentContent.startsWith("=") || this.editionMode === "inactive") {
21122
+ if (!isFormula(this._currentContent) || this.editionMode === "inactive") {
21089
21123
  return;
21090
21124
  }
21091
21125
  const editionSheetId = this.sheetId;
@@ -21114,7 +21148,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21114
21148
  * Highlight all ranges that can be found in the composer content.
21115
21149
  */
21116
21150
  get highlights() {
21117
- if (!this.currentContent.startsWith("=") || this.editionMode === "inactive") {
21151
+ if (!isFormula(this.currentContent) || this.editionMode === "inactive") {
21118
21152
  return [];
21119
21153
  }
21120
21154
  const editionSheetId = this.sheetId;
@@ -21148,7 +21182,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21148
21182
  }
21149
21183
  get autocompleteProvider() {
21150
21184
  const content = this.currentContent;
21151
- const tokenAtCursor = content.startsWith("=")
21185
+ const tokenAtCursor = isFormula(content)
21152
21186
  ? this.tokenAtCursor
21153
21187
  : { type: "STRING", value: content };
21154
21188
  if (this.editionMode === "inactive" ||
@@ -21205,7 +21239,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21205
21239
  * - Previous and next tokens can be separated by spaces
21206
21240
  */
21207
21241
  canStartComposerRangeSelection() {
21208
- if (this._currentContent.startsWith("=")) {
21242
+ if (isFormula(this._currentContent)) {
21209
21243
  const tokenAtCursor = this.tokenAtCursor;
21210
21244
  if (!tokenAtCursor) {
21211
21245
  return false;
@@ -28304,7 +28338,7 @@ function getBarChartData(definition, dataSets, labelRange, getters) {
28304
28338
  }
28305
28339
  function getPyramidChartData(definition, dataSets, labelRange, getters) {
28306
28340
  const barChartData = getBarChartData(definition, dataSets, labelRange, getters);
28307
- const barDataset = barChartData.dataSetsValues;
28341
+ const barDataset = barChartData.dataSetsValues.filter((ds) => !ds.hidden);
28308
28342
  const pyramidDatasetValues = [];
28309
28343
  if (barDataset[0]) {
28310
28344
  const pyramidData = barDataset[0].data.map((value) => (value > 0 ? value : 0));
@@ -28781,10 +28815,8 @@ function getChartDatasetFormat(getters, allDataSets, axis) {
28781
28815
  function getChartDatasetValues(getters, dataSets) {
28782
28816
  const datasetValues = [];
28783
28817
  for (const [dsIndex, ds] of Object.entries(dataSets)) {
28784
- if (getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left)) {
28785
- continue;
28786
- }
28787
28818
  let label;
28819
+ let hidden = getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left);
28788
28820
  if (ds.labelCell) {
28789
28821
  const labelRange = ds.labelCell;
28790
28822
  const cell = labelRange
@@ -28811,9 +28843,9 @@ function getChartDatasetValues(getters, dataSets) {
28811
28843
  data.fill(1);
28812
28844
  }
28813
28845
  else if (data.every((cell) => cell === undefined || cell === null || !isNumber(cell.toString(), DEFAULT_LOCALE))) {
28814
- continue;
28846
+ hidden = true;
28815
28847
  }
28816
- datasetValues.push({ data, label });
28848
+ datasetValues.push({ data, label, hidden });
28817
28849
  }
28818
28850
  return datasetValues;
28819
28851
  }
@@ -28824,12 +28856,13 @@ function getBarChartDatasets(definition, args) {
28824
28856
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28825
28857
  const trendDatasets = [];
28826
28858
  for (const index in dataSetsValues) {
28827
- let { label, data } = dataSetsValues[index];
28859
+ let { label, data, hidden } = dataSetsValues[index];
28828
28860
  label = definition.dataSets?.[index].label || label;
28829
28861
  const backgroundColor = colors.next();
28830
28862
  const dataset = {
28831
28863
  label,
28832
28864
  data,
28865
+ hidden,
28833
28866
  borderColor: definition.background || BACKGROUND_CHART_COLOR,
28834
28867
  borderWidth: definition.stacked ? 1 : 0,
28835
28868
  backgroundColor,
@@ -28862,6 +28895,9 @@ function getWaterfallDatasetAndLabels(definition, args) {
28862
28895
  const labelsWithSubTotals = [];
28863
28896
  let lastValue = 0;
28864
28897
  for (const dataSetsValue of dataSetsValues) {
28898
+ if (dataSetsValue.hidden) {
28899
+ continue;
28900
+ }
28865
28901
  for (let i = 0; i < dataSetsValue.data.length; i++) {
28866
28902
  const data = dataSetsValue.data[i];
28867
28903
  labelsWithSubTotals.push(labels[i]);
@@ -28897,7 +28933,7 @@ function getLineChartDatasets(definition, args) {
28897
28933
  const trendDatasets = [];
28898
28934
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28899
28935
  for (let index = 0; index < dataSetsValues.length; index++) {
28900
- let { label, data } = dataSetsValues[index];
28936
+ let { label, data, hidden } = dataSetsValues[index];
28901
28937
  label = definition.dataSets?.[index].label || label;
28902
28938
  const color = colors.next();
28903
28939
  if (axisType && ["linear", "time"].includes(axisType)) {
@@ -28907,6 +28943,7 @@ function getLineChartDatasets(definition, args) {
28907
28943
  const dataset = {
28908
28944
  label,
28909
28945
  data,
28946
+ hidden,
28910
28947
  tension: 0, // 0 -> render straight lines, which is much faster
28911
28948
  borderColor: color,
28912
28949
  backgroundColor: areaChart ? setColorAlpha(color, LINE_FILL_TRANSPARENCY) : color,
@@ -28939,7 +28976,9 @@ function getPieChartDatasets(definition, args) {
28939
28976
  const dataSets = [];
28940
28977
  const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
28941
28978
  const backgroundColor = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
28942
- for (const { label, data } of dataSetsValues) {
28979
+ for (const { label, data, hidden } of dataSetsValues) {
28980
+ if (hidden)
28981
+ continue;
28943
28982
  const dataset = {
28944
28983
  label,
28945
28984
  data,
@@ -28957,7 +28996,7 @@ function getComboChartDatasets(definition, args) {
28957
28996
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28958
28997
  const trendDatasets = [];
28959
28998
  for (let index = 0; index < dataSetsValues.length; index++) {
28960
- let { label, data } = dataSetsValues[index];
28999
+ let { label, data, hidden } = dataSetsValues[index];
28961
29000
  label = definition.dataSets?.[index].label || label;
28962
29001
  const design = definition.dataSets?.[index];
28963
29002
  const color = colors.next();
@@ -28965,6 +29004,7 @@ function getComboChartDatasets(definition, args) {
28965
29004
  const dataset = {
28966
29005
  label: label,
28967
29006
  data,
29007
+ hidden,
28968
29008
  borderColor: color,
28969
29009
  backgroundColor: color,
28970
29010
  yAxisID: definition.dataSets?.[index].yAxisId || "y",
@@ -28989,7 +29029,7 @@ function getRadarChartDatasets(definition, args) {
28989
29029
  const fill = definition.fillArea ?? false;
28990
29030
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28991
29031
  for (let i = 0; i < dataSetsValues.length; i++) {
28992
- let { label, data } = dataSetsValues[i];
29032
+ let { label, data, hidden } = dataSetsValues[i];
28993
29033
  if (definition.dataSets?.[i]?.label) {
28994
29034
  label = definition.dataSets[i].label;
28995
29035
  }
@@ -28997,6 +29037,7 @@ function getRadarChartDatasets(definition, args) {
28997
29037
  const dataset = {
28998
29038
  label,
28999
29039
  data,
29040
+ hidden,
29000
29041
  borderColor,
29001
29042
  backgroundColor: borderColor,
29002
29043
  };
@@ -29142,6 +29183,11 @@ function getPieChartLegend(definition, args) {
29142
29183
  hidden: false,
29143
29184
  lineWidth: 2,
29144
29185
  })),
29186
+ filter: (legendItem, data) => {
29187
+ return "datasetIndex" in legendItem
29188
+ ? !data.datasets[legendItem.datasetIndex].hidden
29189
+ : true;
29190
+ },
29145
29191
  },
29146
29192
  };
29147
29193
  }
@@ -29203,6 +29249,11 @@ function getWaterfallChartLegend(definition, args) {
29203
29249
  }
29204
29250
  return legendValues;
29205
29251
  },
29252
+ filter: (legendItem, data) => {
29253
+ return "datasetIndex" in legendItem
29254
+ ? !data.datasets[legendItem.datasetIndex].hidden
29255
+ : true;
29256
+ },
29206
29257
  },
29207
29258
  onClick: () => { }, // Disables click interaction with the waterfall chart legend items
29208
29259
  };
@@ -29286,6 +29337,11 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
29286
29337
  ...legendLabelConfig,
29287
29338
  };
29288
29339
  }),
29340
+ filter: (legendItem, data) => {
29341
+ return "datasetIndex" in legendItem
29342
+ ? !data.datasets[legendItem.datasetIndex].hidden
29343
+ : true;
29344
+ },
29289
29345
  },
29290
29346
  };
29291
29347
  }
@@ -29619,7 +29675,7 @@ const templates = /* xml */ `
29619
29675
  <div
29620
29676
  class="o-chart-custom-tooltip border rounded px-2 py-1 pe-none mw-100 position-absolute text-nowrap shadow opacity-100">
29621
29677
  <table class="overflow-hidden m-0">
29622
- <thead>
29678
+ <thead t-if="title">
29623
29679
  <tr>
29624
29680
  <th class="o-tooltip-title align-baseline border-0 text-truncate" t-esc="title" t-attf-style="max-width: {{ labelsMaxWidth }}"/>
29625
29681
  </tr>
@@ -29680,8 +29736,8 @@ function getBarChartTooltip(definition, args) {
29680
29736
  ? undefined
29681
29737
  : "";
29682
29738
  },
29739
+ beforeLabel: (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label,
29683
29740
  label: function (tooltipItem) {
29684
- const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
29685
29741
  const horizontalChart = definition.horizontal;
29686
29742
  let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
29687
29743
  if (yLabel === undefined || yLabel === null) {
@@ -29689,7 +29745,7 @@ function getBarChartTooltip(definition, args) {
29689
29745
  }
29690
29746
  const axisId = horizontalChart ? tooltipItem.dataset.xAxisID : tooltipItem.dataset.yAxisID;
29691
29747
  const yLabelStr = formatChartDatasetValue(args.axisFormats, args.locale)(yLabel, axisId);
29692
- return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
29748
+ return yLabelStr;
29693
29749
  },
29694
29750
  },
29695
29751
  };
@@ -29714,21 +29770,18 @@ function getLineChartTooltip(definition, args) {
29714
29770
  const formattedX = formatValue(label, { locale, format: labelFormat });
29715
29771
  const axisId = tooltipItem.dataset.yAxisID || "y";
29716
29772
  const formattedY = formatValue(dataSetPoint, { locale, format: axisFormats?.[axisId] });
29717
- const dataSetTitle = tooltipItem.dataset.label;
29718
- return formattedX
29719
- ? `${dataSetTitle}: (${formattedX}, ${formattedY})`
29720
- : `${dataSetTitle}: ${formattedY}`;
29773
+ return formattedX ? `(${formattedX}, ${formattedY})` : `${formattedY}`;
29721
29774
  };
29722
29775
  }
29723
29776
  else {
29724
29777
  tooltip.callbacks.label = function (tooltipItem) {
29725
- const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
29726
29778
  const yLabel = tooltipItem.parsed.y;
29727
29779
  const axisId = tooltipItem.dataset.yAxisID;
29728
29780
  const yLabelStr = formatChartDatasetValue(axisFormats, locale)(yLabel, axisId);
29729
- return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
29781
+ return yLabelStr;
29730
29782
  };
29731
29783
  }
29784
+ tooltip.callbacks.beforeLabel = (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label;
29732
29785
  tooltip.callbacks.title = function (tooltipItems) {
29733
29786
  const displayTooltipTitle = axisType !== "linear" &&
29734
29787
  tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
@@ -29746,17 +29799,15 @@ function getPieChartTooltip(definition, args) {
29746
29799
  title: function (tooltipItems) {
29747
29800
  return tooltipItems[0].dataset.label;
29748
29801
  },
29802
+ beforeLabel: (tooltipItem) => tooltipItem.label || tooltipItem.dataset.label,
29749
29803
  label: function (tooltipItem) {
29750
29804
  const data = tooltipItem.dataset.data;
29751
29805
  const dataIndex = tooltipItem.dataIndex;
29752
29806
  const percentage = calculatePercentage(data, dataIndex);
29753
- const xLabel = tooltipItem.label || tooltipItem.dataset.label;
29754
29807
  const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
29755
29808
  const toolTipFormat = !format && yLabel >= 1000 ? "#,##" : format;
29756
29809
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29757
- return xLabel
29758
- ? `${xLabel}: ${yLabelStr} (${percentage}%)`
29759
- : `${yLabelStr} (${percentage}%)`;
29810
+ return `${yLabelStr} (${percentage}%)`;
29760
29811
  },
29761
29812
  },
29762
29813
  };
@@ -29769,16 +29820,17 @@ function getWaterfallChartTooltip(definition, args) {
29769
29820
  enabled: false,
29770
29821
  external: customTooltipHandler,
29771
29822
  callbacks: {
29772
- label: function (tooltipItem) {
29773
- const [lastValue, currentValue] = tooltipItem.raw;
29774
- const yLabel = currentValue - lastValue;
29823
+ beforeLabel: function (tooltipItem) {
29775
29824
  const dataSeriesIndex = labels.length
29776
29825
  ? Math.floor(tooltipItem.dataIndex / labels.length)
29777
29826
  : 0;
29778
- const dataSeriesLabel = dataSeriesLabels[dataSeriesIndex];
29827
+ return dataSeriesLabels[dataSeriesIndex];
29828
+ },
29829
+ label: function (tooltipItem) {
29830
+ const [lastValue, currentValue] = tooltipItem.raw;
29831
+ const yLabel = currentValue - lastValue;
29779
29832
  const toolTipFormat = !format && Math.abs(yLabel) > 1000 ? "#,##" : format;
29780
- const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29781
- return dataSeriesLabel ? `${dataSeriesLabel}: ${yLabelStr}` : yLabelStr;
29833
+ return formatValue(yLabel, { format: toolTipFormat, locale });
29782
29834
  },
29783
29835
  },
29784
29836
  };
@@ -29802,11 +29854,10 @@ function getRadarChartTooltip(definition, args) {
29802
29854
  enabled: false,
29803
29855
  external: customTooltipHandler,
29804
29856
  callbacks: {
29857
+ beforeLabel: (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label,
29805
29858
  label: function (tooltipItem) {
29806
- const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
29807
29859
  const yLabel = tooltipItem.parsed.r;
29808
- const formattedY = formatValue(yLabel, { format: axisFormats?.r, locale });
29809
- return xLabel ? `${xLabel}: ${formattedY}` : formattedY;
29860
+ return formatValue(yLabel, { format: axisFormats?.r, locale });
29810
29861
  },
29811
29862
  },
29812
29863
  };
@@ -29821,13 +29872,12 @@ function getGeoChartTooltip(definition, args) {
29821
29872
  return tooltipItem.raw.value !== undefined;
29822
29873
  },
29823
29874
  callbacks: {
29875
+ beforeLabel: (tooltipItem) => tooltipItem.raw.feature.properties.name,
29824
29876
  label: function (tooltipItem) {
29825
29877
  const rawItem = tooltipItem.raw;
29826
- const xLabel = rawItem.feature.properties.name;
29827
29878
  const yLabel = rawItem.value;
29828
29879
  const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
29829
- const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29830
- return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
29880
+ return formatValue(yLabel, { format: toolTipFormat, locale });
29831
29881
  },
29832
29882
  },
29833
29883
  };
@@ -29847,7 +29897,8 @@ function customTooltipHandler({ chart, tooltip }) {
29847
29897
  return;
29848
29898
  }
29849
29899
  const tooltipItems = tooltip.body.map((body, index) => {
29850
- let [label, value] = body.lines[0].split(":").map((str) => str.trim());
29900
+ let label = body.before[0];
29901
+ let value = body.lines[0];
29851
29902
  if (!value) {
29852
29903
  value = label;
29853
29904
  label = "";
@@ -33008,6 +33059,187 @@ const FilterMenuPopoverBuilder = {
33008
33059
  },
33009
33060
  };
33010
33061
 
33062
+ const macRegex = /Mac/i;
33063
+ const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
33064
+ /**
33065
+ * Return true if the event was triggered from
33066
+ * a child element.
33067
+ */
33068
+ function isChildEvent(parent, ev) {
33069
+ if (!parent)
33070
+ return false;
33071
+ return !!ev.target && parent.contains(ev.target);
33072
+ }
33073
+ function gridOverlayPosition() {
33074
+ const spreadsheetElement = document.querySelector(".o-grid-overlay");
33075
+ if (spreadsheetElement) {
33076
+ const { top, left } = spreadsheetElement?.getBoundingClientRect();
33077
+ return { top, left };
33078
+ }
33079
+ throw new Error("Can't find spreadsheet position");
33080
+ }
33081
+ function getBoundingRectAsPOJO(el) {
33082
+ const rect = el.getBoundingClientRect();
33083
+ return {
33084
+ x: rect.x,
33085
+ y: rect.y,
33086
+ width: rect.width,
33087
+ height: rect.height,
33088
+ };
33089
+ }
33090
+ /**
33091
+ * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
33092
+ */
33093
+ function* iterateChildren(el) {
33094
+ yield el;
33095
+ if (el.hasChildNodes()) {
33096
+ for (let child of el.childNodes) {
33097
+ yield* iterateChildren(child);
33098
+ }
33099
+ }
33100
+ }
33101
+ function getOpenedMenus() {
33102
+ return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
33103
+ }
33104
+ function getCurrentSelection(el) {
33105
+ let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
33106
+ let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
33107
+ let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
33108
+ return {
33109
+ start: startSizeBefore,
33110
+ end: endSizeBefore,
33111
+ };
33112
+ }
33113
+ function getStartAndEndSelection(el) {
33114
+ const selection = document.getSelection();
33115
+ return {
33116
+ startElement: selection.anchorNode || el,
33117
+ startSelectionOffset: selection.anchorOffset,
33118
+ endElement: selection.focusNode || el,
33119
+ endSelectionOffset: selection.focusOffset,
33120
+ };
33121
+ }
33122
+ /**
33123
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
33124
+ * The selected node is either a Text node or an Element node.
33125
+ *
33126
+ * case 1 -Text node:
33127
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
33128
+ * content length of all previous nodes.
33129
+ *
33130
+ * case 2 - Element node:
33131
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
33132
+ * all the nodes prior to the selected node as well as the content of the child node before the offset.
33133
+ *
33134
+ * See the MDN documentation for more details.
33135
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
33136
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
33137
+ *
33138
+ */
33139
+ function findSelectionIndex(el, nodeToFind, nodeOffset) {
33140
+ let usedCharacters = 0;
33141
+ let it = iterateChildren(el);
33142
+ let current = it.next();
33143
+ let isFirstParagraph = true;
33144
+ while (!current.done && current.value !== nodeToFind) {
33145
+ if (!current.value.hasChildNodes()) {
33146
+ if (current.value.textContent) {
33147
+ usedCharacters += current.value.textContent.length;
33148
+ }
33149
+ }
33150
+ // One new paragraph = one new line character, except for the first paragraph
33151
+ if (current.value.nodeName === "P" ||
33152
+ (current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
33153
+ ) {
33154
+ if (isFirstParagraph) {
33155
+ isFirstParagraph = false;
33156
+ }
33157
+ else {
33158
+ usedCharacters++;
33159
+ }
33160
+ }
33161
+ current = it.next();
33162
+ }
33163
+ if (current.value !== nodeToFind) {
33164
+ /** This situation can happen if the code is called while the selection is not currently on the element.
33165
+ * In this case, we return 0 because we don't know the size of the text before the selection.
33166
+ *
33167
+ * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
33168
+ */
33169
+ return 0;
33170
+ }
33171
+ else {
33172
+ if (!current.value.hasChildNodes()) {
33173
+ usedCharacters += nodeOffset;
33174
+ }
33175
+ else {
33176
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
33177
+ usedCharacters += children.reduce((acc, child, index) => {
33178
+ if (child.textContent !== null) {
33179
+ // need to account for paragraph nodes that implicitly add a new line
33180
+ // except for the last paragraph
33181
+ let chars = child.textContent.length;
33182
+ if (child.nodeName === "P" && index !== children.length - 1) {
33183
+ chars++;
33184
+ }
33185
+ return acc + chars;
33186
+ }
33187
+ else {
33188
+ return acc;
33189
+ }
33190
+ }, 0);
33191
+ }
33192
+ }
33193
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
33194
+ usedCharacters++;
33195
+ }
33196
+ return usedCharacters;
33197
+ }
33198
+ const letterRegex = /^[a-zA-Z]$/;
33199
+ /**
33200
+ * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
33201
+ *
33202
+ * @argument ev - The keyboard event to transform
33203
+ * @argument mode - Use either ev.key of ev.code to get the string shortcut
33204
+ *
33205
+ * @example
33206
+ * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
33207
+ * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
33208
+ */
33209
+ function keyboardEventToShortcutString(ev, mode = "key") {
33210
+ let keyDownString = "";
33211
+ if (!MODIFIER_KEYS.includes(ev.key)) {
33212
+ if (isCtrlKey(ev))
33213
+ keyDownString += "Ctrl+";
33214
+ if (ev.altKey)
33215
+ keyDownString += "Alt+";
33216
+ if (ev.shiftKey)
33217
+ keyDownString += "Shift+";
33218
+ }
33219
+ const key = mode === "key" ? ev.key : ev.code;
33220
+ keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
33221
+ return keyDownString;
33222
+ }
33223
+ function isMacOS() {
33224
+ return Boolean(macRegex.test(navigator.userAgent));
33225
+ }
33226
+ /**
33227
+ * @param {KeyboardEvent | MouseEvent} ev
33228
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
33229
+ * On Mac, this is the "meta" or "command" key.
33230
+ */
33231
+ function isCtrlKey(ev) {
33232
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
33233
+ }
33234
+ /**
33235
+ * @param {MouseEvent} ev - The mouse event.
33236
+ * @returns {boolean} Returns true if the event was triggered by a middle-click
33237
+ * or a Ctrl + Click (Cmd + Click on Mac).
33238
+ */
33239
+ function isMiddleClickOrCtrlClick(ev) {
33240
+ return ev.button === 1 || (isCtrlKey(ev) && ev.button === 0);
33241
+ }
33242
+
33011
33243
  const LINK_TOOLTIP_HEIGHT = 32;
33012
33244
  const LINK_TOOLTIP_WIDTH = 220;
33013
33245
  css /* scss */ `
@@ -33082,8 +33314,8 @@ class LinkDisplay extends owl.Component {
33082
33314
  getUrlRepresentation(link) {
33083
33315
  return urlRepresentation(link, this.env.model.getters);
33084
33316
  }
33085
- openLink() {
33086
- openLink(this.link, this.env);
33317
+ openLink(ev) {
33318
+ openLink(this.link, this.env, isMiddleClickOrCtrlClick(ev));
33087
33319
  }
33088
33320
  edit() {
33089
33321
  const { col, row } = this.props.cellPosition;
@@ -33212,179 +33444,6 @@ linkMenuRegistry.add("sheet", {
33212
33444
  sequence: 10,
33213
33445
  });
33214
33446
 
33215
- const macRegex = /Mac/i;
33216
- const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
33217
- /**
33218
- * Return true if the event was triggered from
33219
- * a child element.
33220
- */
33221
- function isChildEvent(parent, ev) {
33222
- if (!parent)
33223
- return false;
33224
- return !!ev.target && parent.contains(ev.target);
33225
- }
33226
- function gridOverlayPosition() {
33227
- const spreadsheetElement = document.querySelector(".o-grid-overlay");
33228
- if (spreadsheetElement) {
33229
- const { top, left } = spreadsheetElement?.getBoundingClientRect();
33230
- return { top, left };
33231
- }
33232
- throw new Error("Can't find spreadsheet position");
33233
- }
33234
- function getBoundingRectAsPOJO(el) {
33235
- const rect = el.getBoundingClientRect();
33236
- return {
33237
- x: rect.x,
33238
- y: rect.y,
33239
- width: rect.width,
33240
- height: rect.height,
33241
- };
33242
- }
33243
- /**
33244
- * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
33245
- */
33246
- function* iterateChildren(el) {
33247
- yield el;
33248
- if (el.hasChildNodes()) {
33249
- for (let child of el.childNodes) {
33250
- yield* iterateChildren(child);
33251
- }
33252
- }
33253
- }
33254
- function getOpenedMenus() {
33255
- return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
33256
- }
33257
- function getCurrentSelection(el) {
33258
- let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
33259
- let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
33260
- let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
33261
- return {
33262
- start: startSizeBefore,
33263
- end: endSizeBefore,
33264
- };
33265
- }
33266
- function getStartAndEndSelection(el) {
33267
- const selection = document.getSelection();
33268
- return {
33269
- startElement: selection.anchorNode || el,
33270
- startSelectionOffset: selection.anchorOffset,
33271
- endElement: selection.focusNode || el,
33272
- endSelectionOffset: selection.focusOffset,
33273
- };
33274
- }
33275
- /**
33276
- * Computes the text 'index' inside this.el based on the currently selected node and its offset.
33277
- * The selected node is either a Text node or an Element node.
33278
- *
33279
- * case 1 -Text node:
33280
- * the offset is the number of characters from the start of the node. We have to add this offset to the
33281
- * content length of all previous nodes.
33282
- *
33283
- * case 2 - Element node:
33284
- * the offset is the number of child nodes before the selected node. We have to add the content length of
33285
- * all the nodes prior to the selected node as well as the content of the child node before the offset.
33286
- *
33287
- * See the MDN documentation for more details.
33288
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
33289
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
33290
- *
33291
- */
33292
- function findSelectionIndex(el, nodeToFind, nodeOffset) {
33293
- let usedCharacters = 0;
33294
- let it = iterateChildren(el);
33295
- let current = it.next();
33296
- let isFirstParagraph = true;
33297
- while (!current.done && current.value !== nodeToFind) {
33298
- if (!current.value.hasChildNodes()) {
33299
- if (current.value.textContent) {
33300
- usedCharacters += current.value.textContent.length;
33301
- }
33302
- }
33303
- // One new paragraph = one new line character, except for the first paragraph
33304
- if (current.value.nodeName === "P" ||
33305
- (current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
33306
- ) {
33307
- if (isFirstParagraph) {
33308
- isFirstParagraph = false;
33309
- }
33310
- else {
33311
- usedCharacters++;
33312
- }
33313
- }
33314
- current = it.next();
33315
- }
33316
- if (current.value !== nodeToFind) {
33317
- /** This situation can happen if the code is called while the selection is not currently on the element.
33318
- * In this case, we return 0 because we don't know the size of the text before the selection.
33319
- *
33320
- * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
33321
- */
33322
- return 0;
33323
- }
33324
- else {
33325
- if (!current.value.hasChildNodes()) {
33326
- usedCharacters += nodeOffset;
33327
- }
33328
- else {
33329
- const children = [...current.value.childNodes].slice(0, nodeOffset);
33330
- usedCharacters += children.reduce((acc, child, index) => {
33331
- if (child.textContent !== null) {
33332
- // need to account for paragraph nodes that implicitly add a new line
33333
- // except for the last paragraph
33334
- let chars = child.textContent.length;
33335
- if (child.nodeName === "P" && index !== children.length - 1) {
33336
- chars++;
33337
- }
33338
- return acc + chars;
33339
- }
33340
- else {
33341
- return acc;
33342
- }
33343
- }, 0);
33344
- }
33345
- }
33346
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
33347
- usedCharacters++;
33348
- }
33349
- return usedCharacters;
33350
- }
33351
- const letterRegex = /^[a-zA-Z]$/;
33352
- /**
33353
- * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
33354
- *
33355
- * @argument ev - The keyboard event to transform
33356
- * @argument mode - Use either ev.key of ev.code to get the string shortcut
33357
- *
33358
- * @example
33359
- * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
33360
- * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
33361
- */
33362
- function keyboardEventToShortcutString(ev, mode = "key") {
33363
- let keyDownString = "";
33364
- if (!MODIFIER_KEYS.includes(ev.key)) {
33365
- if (isCtrlKey(ev))
33366
- keyDownString += "Ctrl+";
33367
- if (ev.altKey)
33368
- keyDownString += "Alt+";
33369
- if (ev.shiftKey)
33370
- keyDownString += "Shift+";
33371
- }
33372
- const key = mode === "key" ? ev.key : ev.code;
33373
- keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
33374
- return keyDownString;
33375
- }
33376
- function isMacOS() {
33377
- return Boolean(macRegex.test(navigator.userAgent));
33378
- }
33379
- /**
33380
- * @param {KeyboardEvent | MouseEvent} ev
33381
- * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
33382
- * On Mac, this is the "meta" or "command" key.
33383
- */
33384
- function isCtrlKey(ev) {
33385
- return isMacOS() ? ev.metaKey : ev.ctrlKey;
33386
- }
33387
-
33388
33447
  /**
33389
33448
  * Repeatedly calls a callback function with a time delay between calls.
33390
33449
  */
@@ -33579,8 +33638,8 @@ class Menu extends owl.Component {
33579
33638
  getIconColor(menu) {
33580
33639
  return cssPropertiesToCss({ color: menu.iconColor });
33581
33640
  }
33582
- async activateMenu(menu) {
33583
- const result = await menu.execute?.(this.env);
33641
+ async activateMenu(menu, isMiddleClick) {
33642
+ const result = await menu.execute?.(this.env, isMiddleClick);
33584
33643
  this.close();
33585
33644
  this.props.onMenuClicked?.({ detail: result });
33586
33645
  }
@@ -33643,13 +33702,14 @@ class Menu extends owl.Component {
33643
33702
  this.subMenu.parentMenu = undefined;
33644
33703
  }
33645
33704
  onClickMenu(menu, ev) {
33646
- if (this.isEnabled(menu)) {
33647
- if (this.isRoot(menu)) {
33648
- this.openSubMenu(menu, ev.currentTarget);
33649
- }
33650
- else {
33651
- this.activateMenu(menu);
33652
- }
33705
+ if (!this.isEnabled(menu)) {
33706
+ return;
33707
+ }
33708
+ if (this.isRoot(menu)) {
33709
+ this.openSubMenu(menu, ev.currentTarget);
33710
+ }
33711
+ else {
33712
+ this.activateMenu(menu, isMiddleClickOrCtrlClick(ev));
33653
33713
  }
33654
33714
  }
33655
33715
  onMouseOver(menu, ev) {
@@ -40277,7 +40337,7 @@ class Composer extends owl.Component {
40277
40337
  const content = this.props.composerStore.currentContent;
40278
40338
  if (this.props.focus === "cellFocus" &&
40279
40339
  !this.autoCompleteState.provider &&
40280
- !content.startsWith("=")) {
40340
+ !isFormula(content)) {
40281
40341
  this.props.composerStore.stopEdition();
40282
40342
  return;
40283
40343
  }
@@ -40494,7 +40554,7 @@ class Composer extends owl.Component {
40494
40554
  return;
40495
40555
  }
40496
40556
  const composerContent = this.props.composerStore.currentContent;
40497
- const isValidFormula = composerContent.startsWith("=");
40557
+ const isValidFormula = isFormula(composerContent);
40498
40558
  if (isValidFormula) {
40499
40559
  const tokens = this.props.composerStore.currentTokens;
40500
40560
  const currentSelection = this.contentHelper.getCurrentSelection();
@@ -40551,7 +40611,7 @@ class Composer extends owl.Component {
40551
40611
  */
40552
40612
  getContentLines() {
40553
40613
  let value = this.props.composerStore.currentContent;
40554
- const isValidFormula = value.startsWith("=");
40614
+ const isValidFormula = isFormula(value);
40555
40615
  if (value === "") {
40556
40616
  return [];
40557
40617
  }
@@ -40640,7 +40700,7 @@ class Composer extends owl.Component {
40640
40700
  this.autoCompleteState.useProvider(autoCompleteProvider);
40641
40701
  }
40642
40702
  const token = this.props.composerStore.tokenAtCursor;
40643
- if (content.startsWith("=") && token && token.type !== "SYMBOL") {
40703
+ if (isFormula(content) && token && token.type !== "SYMBOL") {
40644
40704
  const tokenContext = token.functionContext;
40645
40705
  const parentFunction = tokenContext?.parent.toUpperCase();
40646
40706
  if (tokenContext &&
@@ -48698,7 +48758,7 @@ class CellComposerStore extends AbstractComposerStore {
48698
48758
  }
48699
48759
  break;
48700
48760
  case "ACTIVATE_SHEET":
48701
- if (!this._currentContent.startsWith("=")) {
48761
+ if (!isFormula(this._currentContent)) {
48702
48762
  this._cancelEdition();
48703
48763
  this.resetContent();
48704
48764
  }
@@ -48774,7 +48834,7 @@ class CellComposerStore extends AbstractComposerStore {
48774
48834
  if (content) {
48775
48835
  const sheetId = this.getters.getActiveSheetId();
48776
48836
  const cell = this.getters.getEvaluatedCell({ sheetId, col: this.col, row: this.row });
48777
- if (cell.link && !content.startsWith("=")) {
48837
+ if (cell.link && !isFormula(content)) {
48778
48838
  content = markdownLink(content, cell.link.url);
48779
48839
  }
48780
48840
  this.addHeadersForSpreadingFormula(content);
@@ -48834,7 +48894,7 @@ class CellComposerStore extends AbstractComposerStore {
48834
48894
  }
48835
48895
  /** Add headers at the end of the sheet so the formula in the composer has enough space to spread */
48836
48896
  addHeadersForSpreadingFormula(content) {
48837
- if (!content.startsWith("=")) {
48897
+ if (!isFormula(content)) {
48838
48898
  return;
48839
48899
  }
48840
48900
  const evaluated = this.getters.evaluateFormula(this.sheetId, content);
@@ -48867,7 +48927,7 @@ class CellComposerStore extends AbstractComposerStore {
48867
48927
  checkDataValidation() {
48868
48928
  const cellPosition = { sheetId: this.sheetId, col: this.col, row: this.row };
48869
48929
  const content = this.getCurrentCanonicalContent();
48870
- const cellValue = content.startsWith("=")
48930
+ const cellValue = isFormula(content)
48871
48931
  ? this.getters.evaluateFormula(this.sheetId, content)
48872
48932
  : parseLiteral(content, this.getters.getLocale());
48873
48933
  if (isMatrix(cellValue)) {
@@ -48989,23 +49049,23 @@ class GridComposer extends owl.Component {
48989
49049
  if (this.composerStore.editionMode === "inactive") {
48990
49050
  return `z-index: -1000;`;
48991
49051
  }
48992
- const isFormula = this.composerStore.currentContent.startsWith("=");
49052
+ const _isFormula = isFormula(this.composerStore.currentContent);
48993
49053
  const cell = this.env.model.getters.getActiveCell();
48994
49054
  const position = this.env.model.getters.getActivePosition();
48995
49055
  const style = this.env.model.getters.getCellComputedStyle(position);
48996
49056
  // position style
48997
49057
  const { x: left, y: top, width, height } = this.rect;
48998
49058
  // color style
48999
- const background = (!isFormula && style.fillColor) || "#ffffff";
49000
- const color = (!isFormula && style.textColor) || "#000000";
49059
+ const background = (!_isFormula && style.fillColor) || "#ffffff";
49060
+ const color = (!_isFormula && style.textColor) || "#000000";
49001
49061
  // font style
49002
- const fontSize = (!isFormula && style.fontSize) || 10;
49003
- const fontWeight = !isFormula && style.bold ? "bold" : undefined;
49004
- const fontStyle = !isFormula && style.italic ? "italic" : "normal";
49005
- const textDecoration = !isFormula ? getTextDecoration(style) : "none";
49062
+ const fontSize = (!_isFormula && style.fontSize) || 10;
49063
+ const fontWeight = !_isFormula && style.bold ? "bold" : undefined;
49064
+ const fontStyle = !_isFormula && style.italic ? "italic" : "normal";
49065
+ const textDecoration = !_isFormula ? getTextDecoration(style) : "none";
49006
49066
  // align style
49007
49067
  let textAlign = "left";
49008
- if (!isFormula) {
49068
+ if (!_isFormula) {
49009
49069
  textAlign = style.align || cell.defaultAlign;
49010
49070
  }
49011
49071
  const maxHeight = this.props.gridDims.height - this.rect.y;
@@ -51639,8 +51699,8 @@ class Border extends owl.Component {
51639
51699
  css /* scss */ `
51640
51700
  .o-corner {
51641
51701
  position: absolute;
51642
- height: 6px;
51643
- width: 6px;
51702
+ height: 8px;
51703
+ width: 8px;
51644
51704
  border: 1px solid white;
51645
51705
  }
51646
51706
  .o-corner-nw,
@@ -53205,7 +53265,7 @@ class BordersPlugin extends CorePlugin {
53205
53265
  this.history.update("borders", sheetId, col, row, "left", border?.left);
53206
53266
  if (border?.left &&
53207
53267
  col > 0 &&
53208
- !deepEquals(this.getCellBorder({ sheetId, col: col - 1, row })?.right, border?.left)) {
53268
+ !deepEquals(this.borders[sheetId]?.[col - 1]?.[row]?.right, border?.left)) {
53209
53269
  this.history.update("borders", sheetId, col - 1, row, "right", undefined);
53210
53270
  }
53211
53271
  }
@@ -53213,7 +53273,7 @@ class BordersPlugin extends CorePlugin {
53213
53273
  this.history.update("borders", sheetId, col, row, "top", border?.top);
53214
53274
  if (border?.top &&
53215
53275
  row > 0 &&
53216
- !deepEquals(this.getCellBorder({ sheetId, col, row: row - 1 })?.bottom, border?.top)) {
53276
+ !deepEquals(this.borders[sheetId]?.[col]?.[row - 1]?.bottom, border?.top)) {
53217
53277
  this.history.update("borders", sheetId, col, row - 1, "bottom", undefined);
53218
53278
  }
53219
53279
  }
@@ -53221,7 +53281,7 @@ class BordersPlugin extends CorePlugin {
53221
53281
  this.history.update("borders", sheetId, col, row, "right", border?.right);
53222
53282
  if (border?.right &&
53223
53283
  col < maxCol &&
53224
- !deepEquals(this.getCellBorder({ sheetId, col: col + 1, row })?.left, border?.right)) {
53284
+ !deepEquals(this.borders[sheetId]?.[col + 1]?.[row]?.left, border?.right)) {
53225
53285
  this.history.update("borders", sheetId, col + 1, row, "left", undefined);
53226
53286
  }
53227
53287
  }
@@ -53229,7 +53289,7 @@ class BordersPlugin extends CorePlugin {
53229
53289
  this.history.update("borders", sheetId, col, row, "bottom", border?.bottom);
53230
53290
  if (border?.bottom &&
53231
53291
  row < maxRow &&
53232
- !deepEquals(this.getCellBorder({ sheetId, col, row: row + 1 })?.top, border?.bottom)) {
53292
+ !deepEquals(this.borders[sheetId]?.[col]?.[row + 1]?.top, border?.bottom)) {
53233
53293
  this.history.update("borders", sheetId, col, row + 1, "top", undefined);
53234
53294
  }
53235
53295
  }
@@ -60001,6 +60061,10 @@ class Evaluator {
60001
60061
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
60002
60062
  this.compilationParams.evalContext.updateDependencies = this.updateDependencies.bind(this);
60003
60063
  this.compilationParams.evalContext.addDependencies = this.addDependencies.bind(this);
60064
+ this.compilationParams.evalContext.lookupCaches = {
60065
+ forwardSearch: new Map(),
60066
+ reverseSearch: new Map(),
60067
+ };
60004
60068
  }
60005
60069
  createEmptyPositionSet() {
60006
60070
  const sheetSizes = {};
@@ -68617,7 +68681,7 @@ clickableCellRegistry.add("link", {
68617
68681
  condition: (position, getters) => {
68618
68682
  return !!getters.getEvaluatedCell(position).link;
68619
68683
  },
68620
- execute: (position, env) => openLink(env.model.getters.getEvaluatedCell(position).link, env),
68684
+ execute: (position, env, isMiddleClick) => openLink(env.model.getters.getEvaluatedCell(position).link, env, isMiddleClick),
68621
68685
  sequence: 5,
68622
68686
  });
68623
68687
 
@@ -69602,9 +69666,9 @@ class SpreadsheetDashboard extends owl.Component {
69602
69666
  getClickableCells() {
69603
69667
  return owl.toRaw(this.clickableCellsStore.clickableCells);
69604
69668
  }
69605
- selectClickableCell(clickableCell) {
69669
+ selectClickableCell(ev, clickableCell) {
69606
69670
  const { position, action } = clickableCell;
69607
- action(position, this.env);
69671
+ action(position, this.env, isMiddleClickOrCtrlClick(ev));
69608
69672
  }
69609
69673
  onClosePopover() {
69610
69674
  this.cellPopovers.close();
@@ -75611,6 +75675,6 @@ exports.tokenColors = tokenColors;
75611
75675
  exports.tokenize = tokenize;
75612
75676
 
75613
75677
 
75614
- __info__.version = "18.2.0";
75615
- __info__.date = "2025-02-18T08:27:07.101Z";
75616
- __info__.hash = "d708714";
75678
+ __info__.version = "18.3.0-alpha.1";
75679
+ __info__.date = "2025-02-25T06:00:14.885Z";
75680
+ __info__.hash = "be4d957";