@gooddata/sdk-ui-vis-commons 11.39.0-alpha.1 → 11.39.0-alpha.2
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.
- package/esm/customTooltip/composeSectionHtml.d.ts +10 -0
- package/esm/customTooltip/composeSectionHtml.js +16 -0
- package/esm/customTooltip/markdownToHtml.js +20 -6
- package/esm/customTooltip/tooltipExecution.d.ts +43 -0
- package/esm/customTooltip/tooltipExecution.js +152 -0
- package/esm/customTooltip/tooltipKey.d.ts +20 -0
- package/esm/customTooltip/tooltipKey.js +25 -0
- package/esm/customTooltip/tooltipLookup.d.ts +21 -0
- package/esm/customTooltip/tooltipLookup.js +83 -0
- package/esm/customTooltip/types.d.ts +9 -1
- package/esm/customTooltip/useTooltipLookupExecutions.d.ts +43 -0
- package/esm/customTooltip/useTooltipLookupExecutions.js +63 -0
- package/esm/index.d.ts +5 -0
- package/esm/index.js +5 -0
- package/esm/sdk-ui-vis-commons.d.ts +151 -1
- package/package.json +11 -11
- package/styles/scss/customTooltip.scss +22 -18
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type IResolvedReferenceValues } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Merge order matters: in-chart values override external ones, because the
|
|
4
|
+
* in-chart value is what the user already sees on the rendered point/feature.
|
|
5
|
+
* If a tooltip references the same id from both, showing the external (fetched)
|
|
6
|
+
* value would let it drift from what the chart pixel displays.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare function composeCustomTooltipSectionHtml(content: string, inChartValues: IResolvedReferenceValues, externalValues: IResolvedReferenceValues, fallbackText: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { markdownToHtml } from "./markdownToHtml.js";
|
|
3
|
+
import { resolveReferences } from "./referenceResolver.js";
|
|
4
|
+
/**
|
|
5
|
+
* Merge order matters: in-chart values override external ones, because the
|
|
6
|
+
* in-chart value is what the user already sees on the rendered point/feature.
|
|
7
|
+
* If a tooltip references the same id from both, showing the external (fetched)
|
|
8
|
+
* value would let it drift from what the chart pixel displays.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export function composeCustomTooltipSectionHtml(content, inChartValues, externalValues, fallbackText) {
|
|
13
|
+
const merged = { ...externalValues, ...inChartValues };
|
|
14
|
+
const resolved = resolveReferences(content, merged, fallbackText);
|
|
15
|
+
return `<div class="gd-viz-tooltip-custom-section">${markdownToHtml(resolved)}</div>`;
|
|
16
|
+
}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* - Unordered lists (- item or * item)
|
|
10
10
|
* - Ordered lists (1. item)
|
|
11
11
|
* - Images ()
|
|
12
|
-
* - Links ([text](url)) — rendered as
|
|
12
|
+
* - Links ([text](url)) — rendered as clickable anchors (target=_blank, rel=noopener noreferrer)
|
|
13
|
+
* for `http(s):` URLs; unsafe URLs (javascript:, data:text/...) fall back to plain text
|
|
13
14
|
* - Horizontal rules (--- or ***)
|
|
14
15
|
* - Line breaks
|
|
15
16
|
* - Backslash escapes (\*, \_, \[, \!, etc.) — render the metachar as literal text.
|
|
@@ -25,9 +26,16 @@
|
|
|
25
26
|
function escapeHtml(text) {
|
|
26
27
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
// http(s) navigation is the only safe scheme for clickable links. Data URLs are
|
|
30
|
+
// fine inline for <img> sources, but allowing them on <a href> would let an
|
|
31
|
+
// `[link](data:image/svg+xml,...)` target an SVG that may execute scripts on
|
|
32
|
+
// navigation.
|
|
33
|
+
function isSafeImageUrl(url) {
|
|
29
34
|
return /^https?:\/\//i.test(url) || /^data:image\//i.test(url);
|
|
30
35
|
}
|
|
36
|
+
function isSafeLinkUrl(url) {
|
|
37
|
+
return /^https?:\/\//i.test(url);
|
|
38
|
+
}
|
|
31
39
|
// URL pattern allowing one level of balanced parens, e.g.
|
|
32
40
|
// https://en.wikipedia.org/wiki/Page_(name)
|
|
33
41
|
const URL_PATTERN = "(?:[^()\\s]|\\([^)]*\\))+";
|
|
@@ -41,14 +49,19 @@ function processInlineMarkdown(text) {
|
|
|
41
49
|
let result = escapeHtml(text);
|
|
42
50
|
// Inline style as fallback since the tooltip renders outside the normal DOM tree.
|
|
43
51
|
result = result.replace(IMAGE_REGEX, (_match, alt, url) => {
|
|
44
|
-
if (!
|
|
52
|
+
if (!isSafeImageUrl(url)) {
|
|
45
53
|
return `${alt}`;
|
|
46
54
|
}
|
|
47
55
|
return `<img src="${url}" alt="${alt}" style="max-width: 100%; display: block; margin: 4px 0;" />`;
|
|
48
56
|
});
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
// Always emit a `target="_blank"` anchor for http(s) URLs; whether the user
|
|
58
|
+
// can practically reach it depends on the tooltip mode's lifecycle (the
|
|
59
|
+
// tooltip needs to stay open long enough to mouse over the link).
|
|
60
|
+
result = result.replace(LINK_REGEX, (_match, linkText, url) => {
|
|
61
|
+
if (!isSafeLinkUrl(url)) {
|
|
62
|
+
return linkText;
|
|
63
|
+
}
|
|
64
|
+
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${linkText}</a>`;
|
|
52
65
|
});
|
|
53
66
|
// Bold-italic: ***text*** — must run before bold and italic so the triple
|
|
54
67
|
// asterisks are consumed as a unit instead of being split across patterns.
|
|
@@ -138,6 +151,7 @@ function parseMarkdown(markdown) {
|
|
|
138
151
|
const trimmed = line.trim();
|
|
139
152
|
if (!trimmed) {
|
|
140
153
|
closeList();
|
|
154
|
+
htmlParts.push("<br/>");
|
|
141
155
|
continue;
|
|
142
156
|
}
|
|
143
157
|
// Horizontal rule: --- or *** or ___
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type IExecutionFactory, type IPreparedExecution } from "@gooddata/sdk-backend-spi";
|
|
2
|
+
import { type IExecutionDefinition } from "@gooddata/sdk-model";
|
|
3
|
+
/**
|
|
4
|
+
* Maps used by `buildLookupTable` to interpret the execution result.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export interface ITooltipExecutionMeta {
|
|
9
|
+
/** value localId → count localId, for "Multiple items" detection. */
|
|
10
|
+
labelCountMap: Record<string, string>;
|
|
11
|
+
/** tooltip metric localId → LDM measure identifier. */
|
|
12
|
+
measureIdMap: Record<string, string>;
|
|
13
|
+
/** label value localId → LDM label identifier. */
|
|
14
|
+
labelIdMap: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Prepared tooltip execution paired with the meta needed to interpret its result.
|
|
18
|
+
* Carry them together — meta from one call mis-interprets results from another.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export interface ITooltipExecutionBundle {
|
|
23
|
+
execution: IPreparedExecution;
|
|
24
|
+
meta: ITooltipExecutionMeta;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export interface IBuildTooltipExecutionOptions {
|
|
30
|
+
/**
|
|
31
|
+
* LocalIdentifiers from `definition.attributes` to use as the row dimension,
|
|
32
|
+
* in the desired order. Omit to use all attributes in definition order
|
|
33
|
+
* (Highcharts default). Geo passes an explicit list to drop position attrs.
|
|
34
|
+
*/
|
|
35
|
+
slicingAttributeLocalIds?: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns `null` when the content has no references or all references are
|
|
39
|
+
* already in the chart (resolvable from drill data without a secondary call).
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildTooltipExecution(executionFactory: IExecutionFactory, chartDefinition: IExecutionDefinition, tooltipContent: string, options?: IBuildTooltipExecutionOptions): ITooltipExecutionBundle | null;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { MeasureGroupIdentifier, filterMeasureRef, idRef, isIdentifierRef, isLocalIdRef, isSimpleMeasure, measureItem, measureLocalId, newAttribute, newMeasure, newTwoDimensional, } from "@gooddata/sdk-model";
|
|
3
|
+
import { REFERENCE_REGEX_MATCH } from "@gooddata/sdk-ui-kit";
|
|
4
|
+
function parseReferences(content) {
|
|
5
|
+
const refs = [];
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
// REFERENCE_REGEX_MATCH lives in sdk-ui-kit; match[3] is the kind
|
|
8
|
+
// ("label" | "metric"), match[4] is the id.
|
|
9
|
+
for (const match of content.matchAll(REFERENCE_REGEX_MATCH)) {
|
|
10
|
+
const rawType = match[3].toLowerCase();
|
|
11
|
+
// Synonyms like `measure` / `displayForm` aren't canonical here; let
|
|
12
|
+
// them fall through so the rest of the pipeline doesn't silently treat
|
|
13
|
+
// a `measure` ref as a metric.
|
|
14
|
+
if (rawType !== "metric" && rawType !== "label") {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const id = match[4];
|
|
18
|
+
const key = `${rawType}/${id}`;
|
|
19
|
+
if (!seen.has(key)) {
|
|
20
|
+
seen.add(key);
|
|
21
|
+
refs.push({ type: rawType, id });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return refs;
|
|
25
|
+
}
|
|
26
|
+
function getChartMetricIds(definition) {
|
|
27
|
+
const ids = new Set();
|
|
28
|
+
for (const measure of definition.measures) {
|
|
29
|
+
if (isSimpleMeasure(measure)) {
|
|
30
|
+
const ref = measureItem(measure);
|
|
31
|
+
if (ref && isIdentifierRef(ref)) {
|
|
32
|
+
ids.add(ref.identifier);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return ids;
|
|
37
|
+
}
|
|
38
|
+
function getChartLabelIds(definition) {
|
|
39
|
+
// Only display-form identifier refs match. Refs in user content using a
|
|
40
|
+
// URI ref or a parent attribute id miss the set, fall into the secondary
|
|
41
|
+
// execution, and fail backend-side (one bad ref drops the whole call).
|
|
42
|
+
const ids = new Set();
|
|
43
|
+
for (const attr of definition.attributes) {
|
|
44
|
+
const ref = attr.attribute.displayForm;
|
|
45
|
+
if (isIdentifierRef(ref)) {
|
|
46
|
+
ids.add(ref.identifier);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return ids;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Chart measures that MVF/ranking filters depend on. Must be included in the
|
|
53
|
+
* tooltip execution with their original localIds so the filter refs resolve and
|
|
54
|
+
* tooltip values stay filter-consistent with the chart.
|
|
55
|
+
*/
|
|
56
|
+
function getFilterDependencyMeasures(filters, chartMeasures) {
|
|
57
|
+
const neededLocalIds = new Set();
|
|
58
|
+
for (const filter of filters) {
|
|
59
|
+
const measureRef = filterMeasureRef(filter);
|
|
60
|
+
if (measureRef && isLocalIdRef(measureRef)) {
|
|
61
|
+
neededLocalIds.add(measureRef.localIdentifier);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (neededLocalIds.size === 0) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
return chartMeasures.filter((m) => neededLocalIds.has(measureLocalId(m)));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build tooltip-only measures for refs not already in the chart.
|
|
71
|
+
* Labels get a max+count pair (mirrors the RichText widget pattern) so the
|
|
72
|
+
* lookup can render "(Multiple items)" when a label resolves to >1 value per row.
|
|
73
|
+
*
|
|
74
|
+
* LocalId prefixes `tt_m_`, `tt_lv_`, `tt_lc_` are reserved — collision with
|
|
75
|
+
* chart-side measure localIds would break filter-dependency reuse.
|
|
76
|
+
*/
|
|
77
|
+
function buildTooltipItems(refs, chartMetricIds, chartLabelIds) {
|
|
78
|
+
const measures = [];
|
|
79
|
+
const labelCountMap = {};
|
|
80
|
+
const labelIdMap = {};
|
|
81
|
+
const measureIdMap = {};
|
|
82
|
+
let idx = 0;
|
|
83
|
+
for (const ref of refs) {
|
|
84
|
+
if (ref.type === "metric" && !chartMetricIds.has(ref.id)) {
|
|
85
|
+
const localId = `tt_m_${idx++}`;
|
|
86
|
+
measures.push(newMeasure(idRef(ref.id, "measure"), (m) => m.localId(localId)));
|
|
87
|
+
measureIdMap[localId] = ref.id;
|
|
88
|
+
}
|
|
89
|
+
else if (ref.type === "label" && !chartLabelIds.has(ref.id)) {
|
|
90
|
+
const valueLocalId = `tt_lv_${idx}`;
|
|
91
|
+
const countLocalId = `tt_lc_${idx}`;
|
|
92
|
+
idx++;
|
|
93
|
+
measures.push(newMeasure(idRef(ref.id, "displayForm"), (m) => m.localId(valueLocalId).aggregation("max")));
|
|
94
|
+
measures.push(newMeasure(idRef(ref.id, "displayForm"), (m) => m.localId(countLocalId).aggregation("count")));
|
|
95
|
+
labelCountMap[valueLocalId] = countLocalId;
|
|
96
|
+
labelIdMap[valueLocalId] = ref.id;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { measures, labelCountMap, labelIdMap, measureIdMap };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Slicing attributes for the tooltip execution's row dimension. When
|
|
103
|
+
* `slicingAttributeLocalIds` is passed, attributes are filtered AND reordered
|
|
104
|
+
* to match the caller's list (geo layers exclude position attributes and rely
|
|
105
|
+
* on the order being theirs).
|
|
106
|
+
*/
|
|
107
|
+
function getSlicingAttributes(definition, slicingAttributeLocalIds) {
|
|
108
|
+
if (!slicingAttributeLocalIds) {
|
|
109
|
+
return definition.attributes.map((attr) => newAttribute(attr.attribute.displayForm, (a) => a.localId(attr.attribute.localIdentifier)));
|
|
110
|
+
}
|
|
111
|
+
const byLocalId = new Map(definition.attributes.map((a) => [a.attribute.localIdentifier, a]));
|
|
112
|
+
const out = [];
|
|
113
|
+
for (const localId of slicingAttributeLocalIds) {
|
|
114
|
+
const attr = byLocalId.get(localId);
|
|
115
|
+
if (attr) {
|
|
116
|
+
out.push(newAttribute(attr.attribute.displayForm, (a) => a.localId(localId)));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Returns `null` when the content has no references or all references are
|
|
123
|
+
* already in the chart (resolvable from drill data without a secondary call).
|
|
124
|
+
*
|
|
125
|
+
* @internal
|
|
126
|
+
*/
|
|
127
|
+
export function buildTooltipExecution(executionFactory, chartDefinition, tooltipContent, options) {
|
|
128
|
+
const refs = parseReferences(tooltipContent);
|
|
129
|
+
if (refs.length === 0) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const chartMetricIds = getChartMetricIds(chartDefinition);
|
|
133
|
+
const chartLabelIds = getChartLabelIds(chartDefinition);
|
|
134
|
+
const { measures, labelCountMap, labelIdMap, measureIdMap } = buildTooltipItems(refs, chartMetricIds, chartLabelIds);
|
|
135
|
+
if (measures.length === 0) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const chartAttrs = getSlicingAttributes(chartDefinition, options?.slicingAttributeLocalIds);
|
|
139
|
+
const filterDepMeasures = getFilterDependencyMeasures(chartDefinition.filters, chartDefinition.measures);
|
|
140
|
+
const allItems = [...chartAttrs, ...measures, ...filterDepMeasures];
|
|
141
|
+
const attrLocalIds = chartAttrs.map((a) => a.attribute.localIdentifier);
|
|
142
|
+
let execution = executionFactory
|
|
143
|
+
.forItems(allItems, chartDefinition.filters)
|
|
144
|
+
.withDimensions(...newTwoDimensional(attrLocalIds, [MeasureGroupIdentifier]));
|
|
145
|
+
if (chartDefinition.executionConfig) {
|
|
146
|
+
execution = execution.withExecConfig(chartDefinition.executionConfig);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
execution,
|
|
150
|
+
meta: { labelCountMap, measureIdMap, labelIdMap },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lookup-key helpers shared by `buildLookupTable` and chart-family hover-time
|
|
3
|
+
* key builders. Both sides must produce identical strings.
|
|
4
|
+
*
|
|
5
|
+
* Format: `${displayFormId}:${uri}` per attribute, joined by `|` after
|
|
6
|
+
* lexicographic sort. The `displayFormId` is always the idRef identifier —
|
|
7
|
+
* uriRef-backed display forms are skipped at the tooltip-execution-planning
|
|
8
|
+
* step (in each chart family's adapter), so they never reach this key builder.
|
|
9
|
+
* Name is omitted: the backend substitutes null/empty names with localized
|
|
10
|
+
* strings only on the display side, so including them here causes
|
|
11
|
+
* lookup-vs-hover mismatches on null/empty rows.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildKeySegment(displayFormId: string, uri: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function joinKeySegments(parts: readonly string[]): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
/**
|
|
3
|
+
* Lookup-key helpers shared by `buildLookupTable` and chart-family hover-time
|
|
4
|
+
* key builders. Both sides must produce identical strings.
|
|
5
|
+
*
|
|
6
|
+
* Format: `${displayFormId}:${uri}` per attribute, joined by `|` after
|
|
7
|
+
* lexicographic sort. The `displayFormId` is always the idRef identifier —
|
|
8
|
+
* uriRef-backed display forms are skipped at the tooltip-execution-planning
|
|
9
|
+
* step (in each chart family's adapter), so they never reach this key builder.
|
|
10
|
+
* Name is omitted: the backend substitutes null/empty names with localized
|
|
11
|
+
* strings only on the display side, so including them here causes
|
|
12
|
+
* lookup-vs-hover mismatches on null/empty rows.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export function buildKeySegment(displayFormId, uri) {
|
|
18
|
+
return `${displayFormId}:${uri}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export function joinKeySegments(parts) {
|
|
24
|
+
return [...parts].sort().join("|");
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type IDataView } from "@gooddata/sdk-backend-spi";
|
|
2
|
+
import { type ISeparators } from "@gooddata/sdk-model";
|
|
3
|
+
import { type ITooltipExecutionMeta } from "./tooltipExecution.js";
|
|
4
|
+
import { type IResolvedReferenceValues } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Localized placeholders for unresolved reference values. Mirrors the RichText
|
|
7
|
+
* widget's `richText.no_data` / `richText.multiple_data` messages.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export interface ITooltipLookupLocalizedStrings {
|
|
12
|
+
noData: string;
|
|
13
|
+
multipleItems: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a per-data-point lookup keyed by `${displayFormId}:${uri}` segments
|
|
17
|
+
* (joined by `|`, sorted). Iteration is orientation-agnostic via slices/series.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildLookupTable(dataView: IDataView, meta: ITooltipExecutionMeta, separators?: ISeparators, localizedStrings?: ITooltipLookupLocalizedStrings): Map<string, IResolvedReferenceValues>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { ClientFormatterFacade } from "@gooddata/number-formatter";
|
|
3
|
+
import { isResultAttributeHeader } from "@gooddata/sdk-model";
|
|
4
|
+
import { DataViewFacade } from "@gooddata/sdk-ui";
|
|
5
|
+
import { buildKeySegment, joinKeySegments } from "./tooltipKey.js";
|
|
6
|
+
const DEFAULT_LOCALIZED_STRINGS = {
|
|
7
|
+
noData: "(No data)",
|
|
8
|
+
multipleItems: "(Multiple items)",
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Build a per-data-point lookup keyed by `${displayFormId}:${uri}` segments
|
|
12
|
+
* (joined by `|`, sorted). Iteration is orientation-agnostic via slices/series.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export function buildLookupTable(dataView, meta, separators, localizedStrings = DEFAULT_LOCALIZED_STRINGS) {
|
|
17
|
+
const lookup = new Map();
|
|
18
|
+
const facade = DataViewFacade.for(dataView);
|
|
19
|
+
const slices = facade.data().slices().toArray();
|
|
20
|
+
const seriesArray = facade.data().series().toArray();
|
|
21
|
+
const dimDescriptors = facade.meta().dimensionItemDescriptors(0);
|
|
22
|
+
const countLocalIds = new Set(Object.values(meta.labelCountMap));
|
|
23
|
+
const seriesByLocalId = new Map(seriesArray.map((s) => [s.descriptor.measureDescriptor.measureHeaderItem.localIdentifier, s]));
|
|
24
|
+
for (let sliceIdx = 0; sliceIdx < slices.length; sliceIdx++) {
|
|
25
|
+
const slice = slices[sliceIdx];
|
|
26
|
+
const sliceHeaders = slice.descriptor.headers;
|
|
27
|
+
const keyParts = [];
|
|
28
|
+
for (let i = 0; i < sliceHeaders.length; i++) {
|
|
29
|
+
const header = sliceHeaders[i];
|
|
30
|
+
const descriptor = dimDescriptors[i];
|
|
31
|
+
if (header && isResultAttributeHeader(header) && descriptor && "attributeHeader" in descriptor) {
|
|
32
|
+
const dfId = descriptor.attributeHeader.identifier;
|
|
33
|
+
const uri = header.attributeHeaderItem.uri ?? "";
|
|
34
|
+
keyParts.push(buildKeySegment(dfId, uri));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const pointKey = joinKeySegments(keyParts);
|
|
38
|
+
const values = {};
|
|
39
|
+
for (const series of seriesArray) {
|
|
40
|
+
const measureDesc = series.descriptor.measureDescriptor;
|
|
41
|
+
const localId = measureDesc.measureHeaderItem.localIdentifier;
|
|
42
|
+
const dataPoint = series.dataPoints()[sliceIdx];
|
|
43
|
+
const rawValue = dataPoint?.rawValue;
|
|
44
|
+
if (countLocalIds.has(localId)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const labelId = meta.labelIdMap[localId];
|
|
48
|
+
if (labelId) {
|
|
49
|
+
const countLocalId = meta.labelCountMap[localId];
|
|
50
|
+
const countSeries = countLocalId ? seriesByLocalId.get(countLocalId) : undefined;
|
|
51
|
+
const countValue = countSeries
|
|
52
|
+
? Number(countSeries.dataPoints()[sliceIdx]?.rawValue ?? 0)
|
|
53
|
+
: 1;
|
|
54
|
+
if (countValue > 1) {
|
|
55
|
+
values[`label/${labelId}`] = localizedStrings.multipleItems;
|
|
56
|
+
}
|
|
57
|
+
else if (rawValue == null || rawValue === "") {
|
|
58
|
+
values[`label/${labelId}`] = localizedStrings.noData;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
values[`label/${labelId}`] = String(rawValue);
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const metricId = meta.measureIdMap[localId];
|
|
66
|
+
if (metricId) {
|
|
67
|
+
const format = measureDesc.measureHeaderItem.format;
|
|
68
|
+
if (rawValue == null) {
|
|
69
|
+
values[`metric/${metricId}`] = localizedStrings.noData;
|
|
70
|
+
}
|
|
71
|
+
else if (format) {
|
|
72
|
+
const { formattedValue } = ClientFormatterFacade.formatValue(Number(rawValue), format, separators);
|
|
73
|
+
values[`metric/${metricId}`] = formattedValue;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
values[`metric/${metricId}`] = String(rawValue);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
lookup.set(pointKey, values);
|
|
81
|
+
}
|
|
82
|
+
return lookup;
|
|
83
|
+
}
|
|
@@ -28,7 +28,9 @@ export interface ICustomTooltipConfig {
|
|
|
28
28
|
* - Bold (`**text**`), italic (`*text*`)
|
|
29
29
|
* - Unordered lists (`- item`) and ordered lists (`1. item`) — not nested
|
|
30
30
|
* - Images (``) — `https:`, `http:`, and `data:image/...` URLs only
|
|
31
|
-
* - Links (`[text](url)`) —
|
|
31
|
+
* - Links (`[text](url)`) — `http(s)` URLs only; rendered as anchors opening in
|
|
32
|
+
* a new tab. End-users can only reach them when the tooltip stays open long
|
|
33
|
+
* enough to interact with (accessible/sticky Highcharts tooltips, geo popups).
|
|
32
34
|
* - Horizontal rules (`---`)
|
|
33
35
|
* - Backslash escapes (`\*`, `\_`, `\[`, `\!`, etc.) to render a metacharacter
|
|
34
36
|
* as literal text instead of formatting
|
|
@@ -40,6 +42,12 @@ export interface ICustomTooltipConfig {
|
|
|
40
42
|
* automatically backslash-escaped, so data containing markdown metacharacters
|
|
41
43
|
* renders as literal text — no manual escaping is required.
|
|
42
44
|
*
|
|
45
|
+
* Use display-form identifiers (NOT parent attribute identifiers) inside
|
|
46
|
+
* `{label/id}`. An attribute id renders correctly for attributes that are
|
|
47
|
+
* already in the chart, but it cannot be fetched as a label for external
|
|
48
|
+
* attributes — and a single such ref causes the secondary tooltip fetch
|
|
49
|
+
* to fail backend-side, dropping every other external ref alongside it.
|
|
50
|
+
*
|
|
43
51
|
* @see https://www.gooddata.com/docs/cloud/create-visualizations/custom-tooltips/
|
|
44
52
|
*/
|
|
45
53
|
content?: string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type IPreparedExecution } from "@gooddata/sdk-backend-spi";
|
|
2
|
+
import { type ISeparators } from "@gooddata/sdk-model";
|
|
3
|
+
import { type ITooltipExecutionBundle, type ITooltipExecutionMeta } from "./tooltipExecution.js";
|
|
4
|
+
import { type ITooltipLookupLocalizedStrings } from "./tooltipLookup.js";
|
|
5
|
+
import { type IResolvedReferenceValues } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* One prepared tooltip execution paired with a caller-owned key and the
|
|
8
|
+
* context that travels with the built lookup.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export interface ITooltipLookupExecutionEntry<TContext> {
|
|
13
|
+
key: string;
|
|
14
|
+
execution: IPreparedExecution;
|
|
15
|
+
meta: ITooltipExecutionMeta;
|
|
16
|
+
context: TContext;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Built lookup for one tooltip execution entry.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export interface ITooltipLookupExecutionResult<TContext> {
|
|
24
|
+
lookup: Map<string, IResolvedReferenceValues>;
|
|
25
|
+
context: TContext;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Single-bundle variant for chart families that have one tooltip execution per
|
|
29
|
+
* chart (e.g. Highcharts). Returns `undefined` while no bundle is provided or
|
|
30
|
+
* before the first result lands; consumers handle that as "no external values".
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export declare function useTooltipLookup(bundle: ITooltipExecutionBundle | undefined, separators?: ISeparators, localizedStrings?: ITooltipLookupLocalizedStrings): Map<string, IResolvedReferenceValues> | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Multi-bundle variant for chart families that key tooltip executions per
|
|
37
|
+
* sub-target (e.g. geo per-layer). `context` is required so the produced
|
|
38
|
+
* lookup carries whatever the caller needs to interpret the result —
|
|
39
|
+
* downstream code does not have to defensively check for missing context.
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export declare function useTooltipLookupExecutions<TContext>(entries: readonly ITooltipLookupExecutionEntry<TContext>[], separators?: ISeparators, localizedStrings?: ITooltipLookupLocalizedStrings): Map<string, ITooltipLookupExecutionResult<TContext>>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useCancelablePromise } from "@gooddata/sdk-ui";
|
|
4
|
+
import { buildLookupTable } from "./tooltipLookup.js";
|
|
5
|
+
const EMPTY_LOOKUPS = new Map();
|
|
6
|
+
async function executeOne(execution) {
|
|
7
|
+
const result = await execution.execute();
|
|
8
|
+
return result.readAll();
|
|
9
|
+
}
|
|
10
|
+
function getEntriesFingerprint(entries) {
|
|
11
|
+
return entries.map((entry) => `${entry.key}::${entry.execution.fingerprint()}`).join("||");
|
|
12
|
+
}
|
|
13
|
+
async function executeAll(entries) {
|
|
14
|
+
const settled = await Promise.allSettled(entries.map(async (entry) => ({ ...entry, dataView: await executeOne(entry.execution) })));
|
|
15
|
+
// Failed entries drop out silently; callers fall back when a lookup is missing.
|
|
16
|
+
return settled.flatMap((result) => (result.status === "fulfilled" ? [result.value] : []));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Single-bundle variant for chart families that have one tooltip execution per
|
|
20
|
+
* chart (e.g. Highcharts). Returns `undefined` while no bundle is provided or
|
|
21
|
+
* before the first result lands; consumers handle that as "no external values".
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export function useTooltipLookup(bundle, separators, localizedStrings) {
|
|
26
|
+
const fingerprint = bundle?.execution.fingerprint();
|
|
27
|
+
const { result } = useCancelablePromise({
|
|
28
|
+
promise: bundle ? () => executeOne(bundle.execution) : undefined,
|
|
29
|
+
}, [fingerprint]);
|
|
30
|
+
return useMemo(() => {
|
|
31
|
+
if (!result || !bundle) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return buildLookupTable(result, bundle.meta, separators, localizedStrings);
|
|
35
|
+
}, [result, bundle, separators, localizedStrings]);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Multi-bundle variant for chart families that key tooltip executions per
|
|
39
|
+
* sub-target (e.g. geo per-layer). `context` is required so the produced
|
|
40
|
+
* lookup carries whatever the caller needs to interpret the result —
|
|
41
|
+
* downstream code does not have to defensively check for missing context.
|
|
42
|
+
*
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export function useTooltipLookupExecutions(entries, separators, localizedStrings) {
|
|
46
|
+
const fingerprint = getEntriesFingerprint(entries);
|
|
47
|
+
const { result } = useCancelablePromise({
|
|
48
|
+
promise: entries.length > 0 ? () => executeAll(entries) : undefined,
|
|
49
|
+
}, [fingerprint]);
|
|
50
|
+
return useMemo(() => {
|
|
51
|
+
if (!result) {
|
|
52
|
+
return EMPTY_LOOKUPS;
|
|
53
|
+
}
|
|
54
|
+
const lookups = new Map();
|
|
55
|
+
for (const entry of result) {
|
|
56
|
+
lookups.set(entry.key, {
|
|
57
|
+
lookup: buildLookupTable(entry.dataView, entry.meta, separators, localizedStrings),
|
|
58
|
+
context: entry.context,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return lookups;
|
|
62
|
+
}, [result, separators, localizedStrings]);
|
|
63
|
+
}
|
package/esm/index.d.ts
CHANGED
|
@@ -32,3 +32,8 @@ export { type ICustomTooltipConfig, type CustomTooltipPlacement, type IResolvedR
|
|
|
32
32
|
export { markdownToHtml } from "./customTooltip/markdownToHtml.js";
|
|
33
33
|
export { resolveReferences } from "./customTooltip/referenceResolver.js";
|
|
34
34
|
export { resolveMeasureLdmIdentifier } from "./customTooltip/measureLdmIdentifier.js";
|
|
35
|
+
export { buildTooltipExecution, type IBuildTooltipExecutionOptions, type ITooltipExecutionBundle, type ITooltipExecutionMeta, } from "./customTooltip/tooltipExecution.js";
|
|
36
|
+
export { buildLookupTable, type ITooltipLookupLocalizedStrings } from "./customTooltip/tooltipLookup.js";
|
|
37
|
+
export { buildKeySegment, joinKeySegments } from "./customTooltip/tooltipKey.js";
|
|
38
|
+
export { composeCustomTooltipSectionHtml } from "./customTooltip/composeSectionHtml.js";
|
|
39
|
+
export { useTooltipLookup, useTooltipLookupExecutions, type ITooltipLookupExecutionEntry, type ITooltipLookupExecutionResult, } from "./customTooltip/useTooltipLookupExecutions.js";
|
package/esm/index.js
CHANGED
|
@@ -32,3 +32,8 @@ export { PatternFill } from "./coloring/PatternFill.js";
|
|
|
32
32
|
export { markdownToHtml } from "./customTooltip/markdownToHtml.js";
|
|
33
33
|
export { resolveReferences } from "./customTooltip/referenceResolver.js";
|
|
34
34
|
export { resolveMeasureLdmIdentifier } from "./customTooltip/measureLdmIdentifier.js";
|
|
35
|
+
export { buildTooltipExecution, } from "./customTooltip/tooltipExecution.js";
|
|
36
|
+
export { buildLookupTable } from "./customTooltip/tooltipLookup.js";
|
|
37
|
+
export { buildKeySegment, joinKeySegments } from "./customTooltip/tooltipKey.js";
|
|
38
|
+
export { composeCustomTooltipSectionHtml } from "./customTooltip/composeSectionHtml.js";
|
|
39
|
+
export { useTooltipLookup, useTooltipLookupExecutions, } from "./customTooltip/useTooltipLookupExecutions.js";
|
|
@@ -16,10 +16,14 @@ import { IColorAssignment } from '@gooddata/sdk-ui';
|
|
|
16
16
|
import { IColorPalette } from '@gooddata/sdk-model';
|
|
17
17
|
import { IColorPaletteItem } from '@gooddata/sdk-model';
|
|
18
18
|
import { IDataView } from '@gooddata/sdk-backend-spi';
|
|
19
|
+
import { IExecutionDefinition } from '@gooddata/sdk-model';
|
|
20
|
+
import { IExecutionFactory } from '@gooddata/sdk-backend-spi';
|
|
19
21
|
import { IHeaderPredicate } from '@gooddata/sdk-ui';
|
|
20
22
|
import { IMappingHeader } from '@gooddata/sdk-ui';
|
|
21
23
|
import { IMeasure } from '@gooddata/sdk-model';
|
|
24
|
+
import { IPreparedExecution } from '@gooddata/sdk-backend-spi';
|
|
22
25
|
import { IRgbColorValue } from '@gooddata/sdk-model';
|
|
26
|
+
import { ISeparators } from '@gooddata/sdk-model';
|
|
23
27
|
import { ITheme } from '@gooddata/sdk-model';
|
|
24
28
|
import { JSX } from 'react/jsx-runtime';
|
|
25
29
|
import { NamedExoticComponent } from 'react';
|
|
@@ -34,6 +38,39 @@ export declare class AttributeColorStrategy extends ColorStrategy {
|
|
|
34
38
|
protected createColorAssignment(colorPalette: IColorPalette, colorMapping: IColorMapping[] | undefined, viewByAttribute: any, stackByAttribute: any, dv: DataViewFacade): ICreateColorAssignmentReturnValue;
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Lookup-key helpers shared by `buildLookupTable` and chart-family hover-time
|
|
43
|
+
* key builders. Both sides must produce identical strings.
|
|
44
|
+
*
|
|
45
|
+
* Format: `${displayFormId}:${uri}` per attribute, joined by `|` after
|
|
46
|
+
* lexicographic sort. The `displayFormId` is always the idRef identifier —
|
|
47
|
+
* uriRef-backed display forms are skipped at the tooltip-execution-planning
|
|
48
|
+
* step (in each chart family's adapter), so they never reach this key builder.
|
|
49
|
+
* Name is omitted: the backend substitutes null/empty names with localized
|
|
50
|
+
* strings only on the display side, so including them here causes
|
|
51
|
+
* lookup-vs-hover mismatches on null/empty rows.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildKeySegment(displayFormId: string, uri: string): string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build a per-data-point lookup keyed by `${displayFormId}:${uri}` segments
|
|
60
|
+
* (joined by `|`, sorted). Iteration is orientation-agnostic via slices/series.
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildLookupTable(dataView: IDataView, meta: ITooltipExecutionMeta, separators?: ISeparators, localizedStrings?: ITooltipLookupLocalizedStrings): Map<string, IResolvedReferenceValues>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns `null` when the content has no references or all references are
|
|
68
|
+
* already in the chart (resolvable from drill data without a secondary call).
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildTooltipExecution(executionFactory: IExecutionFactory, chartDefinition: IExecutionDefinition, tooltipContent: string, options?: IBuildTooltipExecutionOptions): ITooltipExecutionBundle | null;
|
|
73
|
+
|
|
37
74
|
/**
|
|
38
75
|
* @internal
|
|
39
76
|
*/
|
|
@@ -84,6 +121,16 @@ export declare const ColorUtils: {
|
|
|
84
121
|
getColorMappingPredicate: typeof getColorMappingPredicate;
|
|
85
122
|
};
|
|
86
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Merge order matters: in-chart values override external ones, because the
|
|
126
|
+
* in-chart value is what the user already sees on the rendered point/feature.
|
|
127
|
+
* If a tooltip references the same id from both, showing the external (fetched)
|
|
128
|
+
* value would let it drift from what the chart pixel displays.
|
|
129
|
+
*
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
export declare function composeCustomTooltipSectionHtml(content: string, inChartValues: IResolvedReferenceValues, externalValues: IResolvedReferenceValues, fallbackText: string): string;
|
|
133
|
+
|
|
87
134
|
/**
|
|
88
135
|
* Placement of the custom tooltip section relative to the default tooltip content.
|
|
89
136
|
*
|
|
@@ -250,6 +297,18 @@ export declare function HeadlinePagination({ renderSecondaryItem, renderTertiary
|
|
|
250
297
|
*/
|
|
251
298
|
export declare const HeatmapLegend: NamedExoticComponent<IHeatmapLegendProps>;
|
|
252
299
|
|
|
300
|
+
/**
|
|
301
|
+
* @internal
|
|
302
|
+
*/
|
|
303
|
+
export declare interface IBuildTooltipExecutionOptions {
|
|
304
|
+
/**
|
|
305
|
+
* LocalIdentifiers from `definition.attributes` to use as the row dimension,
|
|
306
|
+
* in the desired order. Omit to use all attributes in definition order
|
|
307
|
+
* (Highcharts default). Geo passes an explicit list to drop position attrs.
|
|
308
|
+
*/
|
|
309
|
+
slicingAttributeLocalIds?: readonly string[];
|
|
310
|
+
}
|
|
311
|
+
|
|
253
312
|
/**
|
|
254
313
|
* Chart fill config is used to customize the chart fill.
|
|
255
314
|
*
|
|
@@ -366,7 +425,9 @@ export declare interface ICustomTooltipConfig {
|
|
|
366
425
|
* - Bold (`**text**`), italic (`*text*`)
|
|
367
426
|
* - Unordered lists (`- item`) and ordered lists (`1. item`) — not nested
|
|
368
427
|
* - Images (``) — `https:`, `http:`, and `data:image/...` URLs only
|
|
369
|
-
* - Links (`[text](url)`) —
|
|
428
|
+
* - Links (`[text](url)`) — `http(s)` URLs only; rendered as anchors opening in
|
|
429
|
+
* a new tab. End-users can only reach them when the tooltip stays open long
|
|
430
|
+
* enough to interact with (accessible/sticky Highcharts tooltips, geo popups).
|
|
370
431
|
* - Horizontal rules (`---`)
|
|
371
432
|
* - Backslash escapes (`\*`, `\_`, `\[`, `\!`, etc.) to render a metacharacter
|
|
372
433
|
* as literal text instead of formatting
|
|
@@ -378,6 +439,12 @@ export declare interface ICustomTooltipConfig {
|
|
|
378
439
|
* automatically backslash-escaped, so data containing markdown metacharacters
|
|
379
440
|
* renders as literal text — no manual escaping is required.
|
|
380
441
|
*
|
|
442
|
+
* Use display-form identifiers (NOT parent attribute identifiers) inside
|
|
443
|
+
* `{label/id}`. An attribute id renders correctly for attributes that are
|
|
444
|
+
* already in the chart, but it cannot be fetched as a label for external
|
|
445
|
+
* attributes — and a single such ref causes the secondary tooltip fetch
|
|
446
|
+
* to fail backend-side, dropping every other external ref alongside it.
|
|
447
|
+
*
|
|
381
448
|
* @see https://www.gooddata.com/docs/cloud/create-visualizations/custom-tooltips/
|
|
382
449
|
*/
|
|
383
450
|
content?: string;
|
|
@@ -717,6 +784,70 @@ export declare function isValidMappedColor(colorItem: IColor | null | undefined,
|
|
|
717
784
|
*/
|
|
718
785
|
export declare type ItemBorderRadiusPredicate = (item: any) => boolean;
|
|
719
786
|
|
|
787
|
+
/**
|
|
788
|
+
* Prepared tooltip execution paired with the meta needed to interpret its result.
|
|
789
|
+
* Carry them together — meta from one call mis-interprets results from another.
|
|
790
|
+
*
|
|
791
|
+
* @internal
|
|
792
|
+
*/
|
|
793
|
+
export declare interface ITooltipExecutionBundle {
|
|
794
|
+
execution: IPreparedExecution;
|
|
795
|
+
meta: ITooltipExecutionMeta;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Maps used by `buildLookupTable` to interpret the execution result.
|
|
800
|
+
*
|
|
801
|
+
* @internal
|
|
802
|
+
*/
|
|
803
|
+
export declare interface ITooltipExecutionMeta {
|
|
804
|
+
/** value localId → count localId, for "Multiple items" detection. */
|
|
805
|
+
labelCountMap: Record<string, string>;
|
|
806
|
+
/** tooltip metric localId → LDM measure identifier. */
|
|
807
|
+
measureIdMap: Record<string, string>;
|
|
808
|
+
/** label value localId → LDM label identifier. */
|
|
809
|
+
labelIdMap: Record<string, string>;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* One prepared tooltip execution paired with a caller-owned key and the
|
|
814
|
+
* context that travels with the built lookup.
|
|
815
|
+
*
|
|
816
|
+
* @internal
|
|
817
|
+
*/
|
|
818
|
+
export declare interface ITooltipLookupExecutionEntry<TContext> {
|
|
819
|
+
key: string;
|
|
820
|
+
execution: IPreparedExecution;
|
|
821
|
+
meta: ITooltipExecutionMeta;
|
|
822
|
+
context: TContext;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Built lookup for one tooltip execution entry.
|
|
827
|
+
*
|
|
828
|
+
* @internal
|
|
829
|
+
*/
|
|
830
|
+
export declare interface ITooltipLookupExecutionResult<TContext> {
|
|
831
|
+
lookup: Map<string, IResolvedReferenceValues>;
|
|
832
|
+
context: TContext;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Localized placeholders for unresolved reference values. Mirrors the RichText
|
|
837
|
+
* widget's `richText.no_data` / `richText.multiple_data` messages.
|
|
838
|
+
*
|
|
839
|
+
* @internal
|
|
840
|
+
*/
|
|
841
|
+
export declare interface ITooltipLookupLocalizedStrings {
|
|
842
|
+
noData: string;
|
|
843
|
+
multipleItems: string;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* @internal
|
|
848
|
+
*/
|
|
849
|
+
export declare function joinKeySegments(parts: readonly string[]): string;
|
|
850
|
+
|
|
720
851
|
/**
|
|
721
852
|
* @internal
|
|
722
853
|
*/
|
|
@@ -867,6 +998,25 @@ export declare const StaticLegend: NamedExoticComponent<IStaticLegendProps>;
|
|
|
867
998
|
*/
|
|
868
999
|
export declare const SupportedLegendPositions: PositionType[];
|
|
869
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* Single-bundle variant for chart families that have one tooltip execution per
|
|
1003
|
+
* chart (e.g. Highcharts). Returns `undefined` while no bundle is provided or
|
|
1004
|
+
* before the first result lands; consumers handle that as "no external values".
|
|
1005
|
+
*
|
|
1006
|
+
* @internal
|
|
1007
|
+
*/
|
|
1008
|
+
export declare function useTooltipLookup(bundle: ITooltipExecutionBundle | undefined, separators?: ISeparators, localizedStrings?: ITooltipLookupLocalizedStrings): Map<string, IResolvedReferenceValues> | undefined;
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Multi-bundle variant for chart families that key tooltip executions per
|
|
1012
|
+
* sub-target (e.g. geo per-layer). `context` is required so the produced
|
|
1013
|
+
* lookup carries whatever the caller needs to interpret the result —
|
|
1014
|
+
* downstream code does not have to defensively check for missing context.
|
|
1015
|
+
*
|
|
1016
|
+
* @internal
|
|
1017
|
+
*/
|
|
1018
|
+
export declare function useTooltipLookupExecutions<TContext>(entries: readonly ITooltipLookupExecutionEntry<TContext>[], separators?: ISeparators, localizedStrings?: ITooltipLookupLocalizedStrings): Map<string, ITooltipLookupExecutionResult<TContext>>;
|
|
1019
|
+
|
|
870
1020
|
/**
|
|
871
1021
|
* Returns the value if it is non-empty or a fallback text.
|
|
872
1022
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-vis-commons",
|
|
3
|
-
"version": "11.39.0-alpha.
|
|
3
|
+
"version": "11.39.0-alpha.2",
|
|
4
4
|
"description": "GoodData.UI SDK - common functionality for different types of visualizations",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "GoodData Corporation",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"react-intl": "7.1.11",
|
|
37
37
|
"react-measure": "^2.5.2",
|
|
38
38
|
"tslib": "2.8.1",
|
|
39
|
-
"@gooddata/sdk-
|
|
40
|
-
"@gooddata/sdk-
|
|
41
|
-
"@gooddata/sdk-
|
|
42
|
-
"@gooddata/sdk-ui-
|
|
43
|
-
"@gooddata/sdk-ui-
|
|
39
|
+
"@gooddata/sdk-model": "11.39.0-alpha.2",
|
|
40
|
+
"@gooddata/sdk-ui": "11.39.0-alpha.2",
|
|
41
|
+
"@gooddata/sdk-backend-spi": "11.39.0-alpha.2",
|
|
42
|
+
"@gooddata/sdk-ui-kit": "11.39.0-alpha.2",
|
|
43
|
+
"@gooddata/sdk-ui-theme-provider": "11.39.0-alpha.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@microsoft/api-documenter": "^7.17.0",
|
|
@@ -81,11 +81,11 @@
|
|
|
81
81
|
"typescript": "5.9.3",
|
|
82
82
|
"vitest": "4.1.0",
|
|
83
83
|
"vitest-dom": "0.1.1",
|
|
84
|
-
"@gooddata/
|
|
85
|
-
"@gooddata/
|
|
86
|
-
"@gooddata/reference-workspace": "11.39.0-alpha.
|
|
87
|
-
"@gooddata/
|
|
88
|
-
"@gooddata/
|
|
84
|
+
"@gooddata/oxlint-config": "11.39.0-alpha.2",
|
|
85
|
+
"@gooddata/eslint-config": "11.39.0-alpha.2",
|
|
86
|
+
"@gooddata/reference-workspace": "11.39.0-alpha.2",
|
|
87
|
+
"@gooddata/sdk-backend-mockingbird": "11.39.0-alpha.2",
|
|
88
|
+
"@gooddata/stylelint-config": "11.39.0-alpha.2"
|
|
89
89
|
},
|
|
90
90
|
"peerDependencies": {
|
|
91
91
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -4,62 +4,66 @@
|
|
|
4
4
|
// containers across chart families (Highcharts, geo, etc.).
|
|
5
5
|
|
|
6
6
|
.gd-viz-tooltip-custom-section {
|
|
7
|
-
font-size:
|
|
8
|
-
line-height: 1.
|
|
7
|
+
font-size: 14px;
|
|
8
|
+
line-height: 1.2;
|
|
9
9
|
text-align: left;
|
|
10
10
|
white-space: normal;
|
|
11
11
|
word-wrap: break-word;
|
|
12
12
|
min-width: 200px;
|
|
13
|
+
color: var(--gd-palette-complementary-7);
|
|
13
14
|
|
|
14
15
|
h1,
|
|
15
16
|
h2,
|
|
16
17
|
h3,
|
|
17
18
|
h4 {
|
|
18
|
-
margin: 0
|
|
19
|
+
margin: 0;
|
|
19
20
|
font-weight: 700;
|
|
20
|
-
color: var(--gd-
|
|
21
|
+
color: var(--gd-palette-complementary-9);
|
|
21
22
|
font-size: 14px;
|
|
22
|
-
line-height: 1.
|
|
23
|
+
line-height: 1.2;
|
|
24
|
+
padding: 5px 0;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
p {
|
|
26
|
-
margin: 0
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding-block-end: 5px;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
strong {
|
|
30
33
|
font-weight: 700;
|
|
31
|
-
color: var(--gd-chart-tooltip-valueColor);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
ul,
|
|
35
37
|
ol {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
li {
|
|
41
|
-
margin-bottom: 1px;
|
|
38
|
+
padding: 0 0 0 18px;
|
|
39
|
+
margin: 0;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
hr {
|
|
45
43
|
border: 0;
|
|
46
44
|
border-top: 1px solid var(--gd-palette-complementary-3);
|
|
47
|
-
|
|
45
|
+
padding: 4px 0;
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
img {
|
|
51
49
|
max-width: 100%;
|
|
52
50
|
display: block;
|
|
53
|
-
|
|
51
|
+
padding: 3px 0;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
a {
|
|
57
55
|
text-decoration: underline;
|
|
58
|
-
color:
|
|
56
|
+
color: inherit;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
pointer-events: auto;
|
|
59
|
+
|
|
60
|
+
&:hover {
|
|
61
|
+
color: var(--gd-palette-primary-base);
|
|
62
|
+
}
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
.gd-viz-tooltip-custom-separator {
|
|
63
67
|
border-top: 1px solid var(--gd-palette-complementary-3);
|
|
64
|
-
|
|
68
|
+
padding: 7px 0;
|
|
65
69
|
}
|