@juspay/svelte-ui-components 4.20.0 → 4.22.0
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/dist/AreaChart/AreaChart.svelte +10 -1
- package/dist/AreaChart/properties.d.ts +15 -0
- package/dist/BarChart/BarChart.svelte +15 -2
- package/dist/BarChart/properties.d.ts +16 -0
- package/dist/ChatMessage/ChatMessage.svelte +39 -0
- package/dist/DualAxisBarChart/DualAxisBarChart.svelte +12 -1
- package/dist/DualAxisBarChart/properties.d.ts +15 -0
- package/dist/LineChart/LineChart.svelte +7 -1
- package/dist/LineChart/properties.d.ts +14 -0
- package/dist/MarkdownText/MarkdownText.svelte +4 -1
- package/dist/MarkdownText/markdown.js +24 -4
- package/dist/MarkdownText/properties.d.ts +11 -0
- package/dist/Table/Table.svelte +12 -7
- package/dist/Table/properties.d.ts +32 -0
- package/dist/_chart/Legend.svelte +41 -13
- package/dist/_chart/aggregate.d.ts +35 -0
- package/dist/_chart/aggregate.js +68 -0
- package/dist/_chart/narrationHighlighter.d.ts +83 -0
- package/dist/_chart/narrationHighlighter.js +113 -0
- package/dist/_chart/types.d.ts +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist-wc/index.js +359 -302
- package/package.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ChartHighlightAPI } from './highlight';
|
|
2
|
+
/**
|
|
3
|
+
* Drives a chart's highlight from a growing narration transcript.
|
|
4
|
+
*
|
|
5
|
+
* `LineChart`, `BarChart` and `PieChart` hand back a `ChartHighlightAPI` from
|
|
6
|
+
* `onChartReady`, described there as being "for imperative point highlighting
|
|
7
|
+
* from external orchestrators (e.g. voice narration sync, step-through
|
|
8
|
+
* animations)". The socket shipped; the plug did not, so the obvious consumer —
|
|
9
|
+
* an assistant speaking over a chart — hand-rolled it, together with a registry
|
|
10
|
+
* of charts and a store of pending highlights.
|
|
11
|
+
*
|
|
12
|
+
* None of that is needed here. `getCategories()` is already the label list a
|
|
13
|
+
* registry would have duplicated, and `highlight(null)` is already the clear
|
|
14
|
+
* path, so this holds one `ChartHighlightAPI` and nothing else. Passing the API
|
|
15
|
+
* straight in also removes a race the registry version had to work around: a
|
|
16
|
+
* registry keyed by chart identity has to tolerate `onChartReady` firing before
|
|
17
|
+
* the chart is registered, which is a window that does not exist when the
|
|
18
|
+
* caller simply hands over the object it was given.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* let highlighter: NarrationHighlighter | null = null;
|
|
22
|
+
*
|
|
23
|
+
* <LineChart {data} onChartReady={(chart) => {
|
|
24
|
+
* highlighter = createNarrationHighlighter({ chart });
|
|
25
|
+
* }} />
|
|
26
|
+
*
|
|
27
|
+
* // as each transcript chunk arrives
|
|
28
|
+
* highlighter?.processText(transcriptSoFar);
|
|
29
|
+
* // when the assistant starts a new answer
|
|
30
|
+
* highlighter?.reset();
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export type NarrationHighlighterOptions = {
|
|
34
|
+
/** The API handed to `onChartReady` by the chart being narrated. */
|
|
35
|
+
chart: ChartHighlightAPI;
|
|
36
|
+
/** Milliseconds a highlight stays up before clearing. Defaults to `2000`. */
|
|
37
|
+
autoClearMs?: number;
|
|
38
|
+
/** Match category labels case-sensitively. Defaults to `false`. */
|
|
39
|
+
caseSensitive?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Replaces the built-in matching rule. Receives the narration and one
|
|
42
|
+
* category label, already lower-cased unless `caseSensitive` is set.
|
|
43
|
+
*
|
|
44
|
+
* The default matches a label only as a whole word. Substring matching is
|
|
45
|
+
* available through this hook — `(narration, category) =>
|
|
46
|
+
* narration.includes(category)` — but is a poor default: category labels are
|
|
47
|
+
* frequently ordinary words like `Other`, `All` or `None`, especially when
|
|
48
|
+
* they come from a model's tool output rather than a curated list, and a
|
|
49
|
+
* substring rule fires `Other` on `otherwise` and `others`.
|
|
50
|
+
*
|
|
51
|
+
* Whole-word matching narrows that surface; it does not close it. In "on the
|
|
52
|
+
* other hand", `other` is genuinely a standalone word, so the label still
|
|
53
|
+
* matches and no lexical rule could decide otherwise without reading the
|
|
54
|
+
* sentence. A caller who cannot curate labels should require something
|
|
55
|
+
* stronger here, and should `reset()` between turns so a mis-fire cannot
|
|
56
|
+
* outlive one answer.
|
|
57
|
+
*/
|
|
58
|
+
match?: (narration: string, category: string) => boolean;
|
|
59
|
+
};
|
|
60
|
+
export type NarrationHighlighter = {
|
|
61
|
+
/**
|
|
62
|
+
* Reads the transcript so far and highlights the first not-yet-seen category
|
|
63
|
+
* it names. Returns that category's index, or `null` when the narration named
|
|
64
|
+
* nothing new.
|
|
65
|
+
*
|
|
66
|
+
* Safe to call on every chunk: the transcript is expected to grow, and a
|
|
67
|
+
* category already highlighted this turn is not highlighted again.
|
|
68
|
+
*/
|
|
69
|
+
processText: (narration: string) => number | null;
|
|
70
|
+
/**
|
|
71
|
+
* Starts a new turn: clears the highlight and forgets which categories have
|
|
72
|
+
* fired, so they can fire again for the next answer.
|
|
73
|
+
*/
|
|
74
|
+
reset: () => void;
|
|
75
|
+
/**
|
|
76
|
+
* Cancels a pending auto-clear. Call from the owner's teardown.
|
|
77
|
+
*
|
|
78
|
+
* Deliberately does not call into the chart: teardown ordering is the
|
|
79
|
+
* caller's, and the chart may already be gone.
|
|
80
|
+
*/
|
|
81
|
+
destroy: () => void;
|
|
82
|
+
};
|
|
83
|
+
export declare const createNarrationHighlighter: (options: NarrationHighlighterOptions) => NarrationHighlighter;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const DEFAULT_AUTO_CLEAR_MS = 2000;
|
|
2
|
+
/** Letters, digits and underscore, so a label is matched only as a whole word. */
|
|
3
|
+
const WORD_CHARACTER = /[\p{L}\p{N}_]/u;
|
|
4
|
+
/**
|
|
5
|
+
* `charAt` rather than indexing: out of range it returns `''`, which is not a
|
|
6
|
+
* word character, so the string's edges need no special case.
|
|
7
|
+
*/
|
|
8
|
+
const isWordCharacter = (character) => WORD_CHARACTER.test(character);
|
|
9
|
+
/**
|
|
10
|
+
* True when `needle` appears in `haystack` bounded by non-word characters.
|
|
11
|
+
*
|
|
12
|
+
* Written as a scan rather than a `RegExp` for two reasons: a category label is
|
|
13
|
+
* caller data and would have to be escaped, and `\b` is defined over ASCII word
|
|
14
|
+
* characters, so it mismatches labels like `Café` or `北区`. Lookbehind would
|
|
15
|
+
* solve the second but is not available everywhere this library runs.
|
|
16
|
+
*/
|
|
17
|
+
const occursAsWord = (haystack, needle) => {
|
|
18
|
+
for (let from = 0; from <= haystack.length - needle.length;) {
|
|
19
|
+
const at = haystack.indexOf(needle, from);
|
|
20
|
+
if (at === -1) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const before = at === 0 ? '' : haystack.charAt(at - 1);
|
|
24
|
+
const after = haystack.charAt(at + needle.length);
|
|
25
|
+
if (!isWordCharacter(before) && !isWordCharacter(after)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
from = at + 1;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The type says `number`, but a plain-JS or web-component caller can pass
|
|
34
|
+
* anything. `NaN` reaches `setTimeout` as an immediate fire, clearing the
|
|
35
|
+
* highlight before it has been seen, so an unusable delay degrades to the
|
|
36
|
+
* default rather than breaking the affordance it controls.
|
|
37
|
+
*/
|
|
38
|
+
const resolveAutoClearMs = (value) => {
|
|
39
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
40
|
+
return DEFAULT_AUTO_CLEAR_MS;
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
/** `getCategories` is the chart's, and a torn-down chart can throw or return junk. */
|
|
45
|
+
const readCategories = (chart) => {
|
|
46
|
+
try {
|
|
47
|
+
const categories = chart.getCategories();
|
|
48
|
+
if (!Array.isArray(categories)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return categories.flatMap((category, sourceIndex) => typeof category === 'string' ? [{ label: category, sourceIndex }] : []);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export const createNarrationHighlighter = (options) => {
|
|
58
|
+
const { chart, caseSensitive = false, match } = options;
|
|
59
|
+
const autoClearMs = resolveAutoClearMs(options.autoClearMs ?? null);
|
|
60
|
+
const fired = new Set();
|
|
61
|
+
let clearTimer = null;
|
|
62
|
+
const cancelPendingClear = () => {
|
|
63
|
+
if (clearTimer !== null) {
|
|
64
|
+
clearTimeout(clearTimer);
|
|
65
|
+
clearTimer = null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const matches = (narration, category) => match ? match(narration, category) : occursAsWord(narration, category);
|
|
69
|
+
return {
|
|
70
|
+
processText: (narration) => {
|
|
71
|
+
if (typeof narration !== 'string' || narration.length === 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const haystack = caseSensitive ? narration : narration.toLowerCase();
|
|
75
|
+
const categories = readCategories(chart);
|
|
76
|
+
/*
|
|
77
|
+
* Longest label wins. `Q1` is a whole-word match inside `Q1 Europe`, so
|
|
78
|
+
* taking the first hit would highlight the broader slice while the
|
|
79
|
+
* narration is naming the narrower one. Ties keep the earlier index,
|
|
80
|
+
* which makes the choice stable across calls.
|
|
81
|
+
*/
|
|
82
|
+
let bestIndex = null;
|
|
83
|
+
let bestLength = 0;
|
|
84
|
+
categories.forEach(({ label, sourceIndex }) => {
|
|
85
|
+
if (label.length === 0 || fired.has(sourceIndex) || label.length <= bestLength) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const needle = caseSensitive ? label : label.toLowerCase();
|
|
89
|
+
if (matches(haystack, needle)) {
|
|
90
|
+
bestIndex = sourceIndex;
|
|
91
|
+
bestLength = label.length;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
if (bestIndex === null) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
fired.add(bestIndex);
|
|
98
|
+
cancelPendingClear();
|
|
99
|
+
chart.highlight(bestIndex);
|
|
100
|
+
clearTimer = setTimeout(() => {
|
|
101
|
+
clearTimer = null;
|
|
102
|
+
chart.highlight(null);
|
|
103
|
+
}, autoClearMs);
|
|
104
|
+
return bestIndex;
|
|
105
|
+
},
|
|
106
|
+
reset: () => {
|
|
107
|
+
cancelPendingClear();
|
|
108
|
+
fired.clear();
|
|
109
|
+
chart.highlight(null);
|
|
110
|
+
},
|
|
111
|
+
destroy: cancelPendingClear
|
|
112
|
+
};
|
|
113
|
+
};
|
package/dist/_chart/types.d.ts
CHANGED
|
@@ -126,6 +126,14 @@ export type LegendItem = {
|
|
|
126
126
|
color: string;
|
|
127
127
|
/** True when the series is toggled off via an interactive legend. */
|
|
128
128
|
hidden?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Formatted per-series aggregate (e.g. `"$1.2K"`, `"45%"`) shown beside the
|
|
131
|
+
* label. `null`/omitted renders nothing -- both the "no `aggregate` was
|
|
132
|
+
* configured for this series" and "the series had no data to aggregate"
|
|
133
|
+
* cases resolve here, via `computeSeriesAggregate`/`formatSeriesAggregate`
|
|
134
|
+
* in `_chart/aggregate.ts`.
|
|
135
|
+
*/
|
|
136
|
+
aggregateLabel?: string | null;
|
|
129
137
|
};
|
|
130
138
|
/** Data-space anchor for point/category-anchored tooltips (coords are container px). */
|
|
131
139
|
export type TooltipAnchor = {
|
package/dist/index.d.ts
CHANGED
|
@@ -185,6 +185,7 @@ export type * from './ProportionBar/properties';
|
|
|
185
185
|
export type * from './ChipInput/properties';
|
|
186
186
|
export type * from './FileDropzoneTrigger/properties';
|
|
187
187
|
export type * from './_chart/highlight';
|
|
188
|
+
export type * from './_chart/narrationHighlighter';
|
|
188
189
|
export type * from './Chat/properties';
|
|
189
190
|
export type * from './Chat/types';
|
|
190
191
|
export type * from './SpeechToText/types';
|
|
@@ -205,3 +206,4 @@ export type * from './Gallery/properties';
|
|
|
205
206
|
export { createSoundKit } from './SoundKit/SoundKit';
|
|
206
207
|
export { validateInput, lockBodyScroll, unlockBodyScroll } from './utils';
|
|
207
208
|
export { formatNumberIndian } from './_chart/format';
|
|
209
|
+
export { createNarrationHighlighter } from './_chart/narrationHighlighter';
|
package/dist/index.js
CHANGED
|
@@ -106,3 +106,4 @@ export { createCopyState } from './Snippet/copyState.svelte';
|
|
|
106
106
|
export { createSoundKit } from './SoundKit/SoundKit';
|
|
107
107
|
export { validateInput, lockBodyScroll, unlockBodyScroll } from './utils';
|
|
108
108
|
export { formatNumberIndian } from './_chart/format';
|
|
109
|
+
export { createNarrationHighlighter } from './_chart/narrationHighlighter';
|