@juspay/svelte-ui-components 2.126.0 → 2.127.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/AttachmentChipRow/AttachmentChipRow.svelte +354 -0
- package/dist/AttachmentChipRow/AttachmentChipRow.svelte.d.ts +4 -0
- package/dist/AttachmentChipRow/properties.d.ts +47 -0
- package/dist/AttachmentChipRow/properties.js +1 -0
- package/dist/ChatBubble/ChatBubble.svelte.d.ts +1 -1
- package/dist/ChatComposer/ChatComposer.svelte +96 -10
- package/dist/ChatComposer/properties.d.ts +76 -0
- package/dist/ChatSuggestions/ChatSuggestions.svelte +118 -11
- package/dist/ChatSuggestions/properties.d.ts +42 -0
- package/dist/HITL/HITL.svelte +576 -0
- package/dist/HITL/HITL.svelte.d.ts +4 -0
- package/dist/HITL/properties.d.ts +71 -0
- package/dist/HITL/properties.js +1 -0
- package/dist/HITL/timers.d.ts +6 -0
- package/dist/HITL/timers.js +7 -0
- package/dist/Pill/Pill.svelte +6 -0
- package/dist/ThinkingIndicator/ThinkingIndicator.svelte +217 -0
- package/dist/ThinkingIndicator/ThinkingIndicator.svelte.d.ts +4 -0
- package/dist/ThinkingIndicator/properties.d.ts +30 -0
- package/dist/ThinkingIndicator/properties.js +1 -0
- package/dist/Tooltip/tooltip-action.js +5 -0
- package/dist/TypewriterText/TypewriterText.svelte +110 -0
- package/dist/TypewriterText/TypewriterText.svelte.d.ts +4 -0
- package/dist/TypewriterText/properties.d.ts +21 -0
- package/dist/TypewriterText/properties.js +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { AttachmentChipFile, AttachmentChipImage, AttachmentChipVideo } from '../AttachmentChipRow/properties';
|
|
2
3
|
export type ChatComposerProperties = OptionalChatComposerProperties & ChatComposerEventProperties;
|
|
3
4
|
export type OptionalChatComposerProperties = {
|
|
4
5
|
value?: string;
|
|
@@ -9,6 +10,38 @@ export type OptionalChatComposerProperties = {
|
|
|
9
10
|
streaming?: boolean;
|
|
10
11
|
recording?: boolean;
|
|
11
12
|
attachments?: File[];
|
|
13
|
+
/**
|
|
14
|
+
* Replaces the built-in attachment pill strip above the input row — render a
|
|
15
|
+
* richer preview there (e.g. AttachmentChipRow) driven by your own attachment
|
|
16
|
+
* model. When your model lives outside `attachments`, pair with `sendable` so
|
|
17
|
+
* attachment-only sends stay possible.
|
|
18
|
+
*/
|
|
19
|
+
attachmentsPreview?: Snippet;
|
|
20
|
+
/**
|
|
21
|
+
* Processed image attachments for the built-in rich strip: when either rich
|
|
22
|
+
* list is non-empty the composer renders an AttachmentChipRow above the input
|
|
23
|
+
* row instead of the `attachments` pill strip, and counts the chips toward
|
|
24
|
+
* "can send". Opt-in — with both lists empty (the default) nothing changes.
|
|
25
|
+
* `attachmentsPreview` still replaces the strip entirely when provided.
|
|
26
|
+
*/
|
|
27
|
+
richImages?: AttachmentChipImage[];
|
|
28
|
+
/** Processed file attachments for the built-in rich strip — see `richImages`. */
|
|
29
|
+
richFiles?: AttachmentChipFile[];
|
|
30
|
+
/** Processed video attachments for the built-in rich strip — see `richImages`. */
|
|
31
|
+
richVideos?: AttachmentChipVideo[];
|
|
32
|
+
/** Tooltip text for a rich image chip. Nothing is shown when omitted or empty. */
|
|
33
|
+
richImageTooltip?: (image: AttachmentChipImage) => string;
|
|
34
|
+
/** Tooltip text for a rich video chip. Nothing is shown when omitted or empty. */
|
|
35
|
+
richVideoTooltip?: (video: AttachmentChipVideo) => string;
|
|
36
|
+
/** Glyph inside the rich chips' remove buttons. Falls back to the chip row's cross. */
|
|
37
|
+
richRemoveIcon?: Snippet;
|
|
38
|
+
/** Glyph on a rich file chip. Falls back to the chip row's document glyph. */
|
|
39
|
+
richFileIcon?: Snippet;
|
|
40
|
+
/**
|
|
41
|
+
* Overrides the internal "can send" calculation (text or `attachments`
|
|
42
|
+
* present). `null` keeps the default. `disabled` still wins.
|
|
43
|
+
*/
|
|
44
|
+
sendable?: boolean | null;
|
|
12
45
|
accept?: string;
|
|
13
46
|
multiple?: boolean;
|
|
14
47
|
sendLabel?: string;
|
|
@@ -19,15 +52,58 @@ export type OptionalChatComposerProperties = {
|
|
|
19
52
|
stopIcon?: Snippet;
|
|
20
53
|
voiceIcon?: Snippet;
|
|
21
54
|
attachIcon?: Snippet;
|
|
55
|
+
/** Glyph for the idle action button (see `onaction`). */
|
|
56
|
+
actionIcon?: Snippet;
|
|
57
|
+
actionLabel?: string;
|
|
22
58
|
leading?: Snippet;
|
|
23
59
|
testId?: string;
|
|
60
|
+
/** Test id emitted as `data-pw` on the textarea itself (none by default). */
|
|
61
|
+
inputTestId?: string;
|
|
62
|
+
/** Accessible name for the textarea. Defaults to the placeholder (or 'Message'). */
|
|
63
|
+
inputAriaLabel?: string;
|
|
64
|
+
/** Test ids for the control buttons (none by default). */
|
|
65
|
+
sendTestId?: string;
|
|
66
|
+
/** Test id on the send slot's wrapper — the same slot renders send, stop and action. */
|
|
67
|
+
sendSlotTestId?: string;
|
|
68
|
+
stopTestId?: string;
|
|
69
|
+
voiceTestId?: string;
|
|
70
|
+
attachTestId?: string;
|
|
71
|
+
actionTestId?: string;
|
|
24
72
|
classes?: string;
|
|
25
73
|
};
|
|
26
74
|
export type ChatComposerEventProperties = {
|
|
27
75
|
onsubmit?: (value: string, attachments: File[]) => void;
|
|
28
76
|
oninput?: (value: string, event: Event) => void;
|
|
29
77
|
onkeydown?: (event: KeyboardEvent) => void;
|
|
78
|
+
onpaste?: (event: ClipboardEvent) => void;
|
|
30
79
|
onstop?: () => void;
|
|
31
80
|
onvoice?: () => void;
|
|
32
81
|
onattach?: (files: File[]) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Intercepts the attach button: when provided, clicking it calls this instead of
|
|
84
|
+
* opening the built-in file picker — for apps with their own chooser (camera /
|
|
85
|
+
* gallery / files).
|
|
86
|
+
*/
|
|
87
|
+
onattachclick?: () => void;
|
|
88
|
+
/**
|
|
89
|
+
* Removal callbacks for the rich strip's chips. Omit them to render the rich
|
|
90
|
+
* chips read-only (no remove buttons), mirroring AttachmentChipRow.
|
|
91
|
+
*/
|
|
92
|
+
onremoverichimage?: (id: string) => void;
|
|
93
|
+
onremoverichfile?: (id: string) => void;
|
|
94
|
+
onremoverichvideo?: (id: string) => void;
|
|
95
|
+
/**
|
|
96
|
+
* Open/preview callbacks for the rich strip's chips — when provided, a chip's
|
|
97
|
+
* tile becomes a real button and clicking it fires with the attachment (e.g.
|
|
98
|
+
* open a lightbox, play the video).
|
|
99
|
+
*/
|
|
100
|
+
onopenrichimage?: (image: AttachmentChipImage) => void;
|
|
101
|
+
onopenrichvideo?: (video: AttachmentChipVideo) => void;
|
|
102
|
+
onopenrichfile?: (file: AttachmentChipFile) => void;
|
|
103
|
+
/**
|
|
104
|
+
* The composer's idle action (e.g. voice conversation mode). While the value is
|
|
105
|
+
* empty, nothing is sendable and no reply is streaming, the send button gives way
|
|
106
|
+
* to an action button that fires this.
|
|
107
|
+
*/
|
|
108
|
+
onaction?: () => void;
|
|
33
109
|
};
|
|
@@ -1,31 +1,100 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Pill from '../Pill/Pill.svelte';
|
|
3
|
+
import Img from '../Img/Img.svelte';
|
|
4
|
+
import Scroller from '../Scroller/Scroller.svelte';
|
|
3
5
|
import type { ChatSuggestion, ChatSuggestionsProperties } from './properties';
|
|
4
6
|
|
|
5
|
-
let {
|
|
7
|
+
let {
|
|
8
|
+
items,
|
|
9
|
+
disabled = false,
|
|
10
|
+
layout = 'wrap',
|
|
11
|
+
direction = 'horizontal',
|
|
12
|
+
maxVisible,
|
|
13
|
+
loading = false,
|
|
14
|
+
icon,
|
|
15
|
+
chipClasses,
|
|
16
|
+
onselect,
|
|
17
|
+
testId,
|
|
18
|
+
classes
|
|
19
|
+
}: ChatSuggestionsProperties = $props();
|
|
6
20
|
|
|
7
|
-
|
|
8
|
-
return typeof item === 'string' ? item : item.label;
|
|
9
|
-
}
|
|
21
|
+
const labelOf = (item: ChatSuggestion): string => (typeof item === 'string' ? item : item.label);
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
const valueOf = (item: ChatSuggestion): string => {
|
|
12
24
|
if (typeof item === 'string') {
|
|
13
25
|
return item;
|
|
14
26
|
}
|
|
15
27
|
return item.value ?? item.label;
|
|
16
|
-
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const iconOf = (item: ChatSuggestion): string | null =>
|
|
31
|
+
typeof item === 'string' ? null : (item.icon ?? null);
|
|
32
|
+
|
|
33
|
+
// The dispatched value is usually a longer phrasing of the label, so it doubles as
|
|
34
|
+
// the hover text. Suppressed when they are the same, to avoid a tooltip that just
|
|
35
|
+
// repeats the chip.
|
|
36
|
+
const hintOf = (item: ChatSuggestion): string | null => {
|
|
37
|
+
if (typeof item === 'string') {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (typeof item.hint === 'string') {
|
|
41
|
+
return item.hint;
|
|
42
|
+
}
|
|
43
|
+
return typeof item.value === 'string' && item.value !== item.label ? item.value : null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Clamped so a negative limit means "show nothing", not slice-from-the-end.
|
|
47
|
+
let shown = $derived(
|
|
48
|
+
typeof maxVisible === 'number' ? items.slice(0, Math.max(0, maxVisible)) : items
|
|
49
|
+
);
|
|
17
50
|
</script>
|
|
18
51
|
|
|
52
|
+
{#snippet chips()}
|
|
53
|
+
{#each shown as item, index (index)}
|
|
54
|
+
{@const resolvedIcon = iconOf(item)}
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
class="chip {chipClasses ?? ''}"
|
|
58
|
+
title={hintOf(item)}
|
|
59
|
+
{disabled}
|
|
60
|
+
onclick={() => onselect?.(valueOf(item), index)}
|
|
61
|
+
>
|
|
62
|
+
<Pill text={labelOf(item)} {disabled}>
|
|
63
|
+
{#snippet leadingIcon()}
|
|
64
|
+
{#if typeof icon === 'function'}
|
|
65
|
+
{@render icon(resolvedIcon, index)}
|
|
66
|
+
{:else if typeof resolvedIcon === 'string'}
|
|
67
|
+
<Img src={resolvedIcon} alt="" fallback="" inlineSvg={true} />
|
|
68
|
+
{/if}
|
|
69
|
+
{/snippet}
|
|
70
|
+
</Pill>
|
|
71
|
+
</button>
|
|
72
|
+
{/each}
|
|
73
|
+
{/snippet}
|
|
74
|
+
|
|
19
75
|
<div
|
|
20
76
|
class="chat-suggestions {classes ?? ''}"
|
|
77
|
+
class:vertical={direction === 'vertical'}
|
|
78
|
+
class:loading
|
|
21
79
|
data-pw={typeof testId === 'string' ? testId : null}
|
|
22
80
|
testID={typeof testId === 'string' ? testId : null}
|
|
23
81
|
>
|
|
24
|
-
{#
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
82
|
+
{#if !loading}
|
|
83
|
+
{#if layout === 'scroll'}
|
|
84
|
+
<Scroller
|
|
85
|
+
{direction}
|
|
86
|
+
showArrows={false}
|
|
87
|
+
showGradient={false}
|
|
88
|
+
hideScrollbar={true}
|
|
89
|
+
dragToScroll={true}
|
|
90
|
+
classes="chat-suggestions-scroller"
|
|
91
|
+
>
|
|
92
|
+
{@render chips()}
|
|
93
|
+
</Scroller>
|
|
94
|
+
{:else}
|
|
95
|
+
{@render chips()}
|
|
96
|
+
{/if}
|
|
97
|
+
{/if}
|
|
29
98
|
</div>
|
|
30
99
|
|
|
31
100
|
<style>
|
|
@@ -39,8 +108,46 @@
|
|
|
39
108
|
padding: var(--chat-suggestions-padding, 0);
|
|
40
109
|
}
|
|
41
110
|
|
|
111
|
+
/* A vertical stack is a menu, not a chip row: each entry takes the full width so
|
|
112
|
+
the labels align, which is what makes a long list scannable. */
|
|
113
|
+
.chat-suggestions.vertical {
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
align-items: var(--chat-suggestions-vertical-align-items, stretch);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.chat-suggestions.vertical .chip {
|
|
119
|
+
width: var(--chat-suggestions-vertical-chip-width, 100%);
|
|
120
|
+
--pill-width: 100%;
|
|
121
|
+
--pill-justify-content: flex-start;
|
|
122
|
+
--pill-text-align: left;
|
|
123
|
+
}
|
|
124
|
+
|
|
42
125
|
.chip {
|
|
126
|
+
appearance: none;
|
|
127
|
+
padding: 0;
|
|
128
|
+
border: 0;
|
|
129
|
+
background: none;
|
|
130
|
+
font: inherit;
|
|
131
|
+
color: inherit;
|
|
132
|
+
text-align: inherit;
|
|
133
|
+
cursor: pointer;
|
|
43
134
|
display: flex;
|
|
135
|
+
/* A chip must never be wider than the row that holds it. Pills are content-sized
|
|
136
|
+
and never wrap their label, so a long one spilled past the right edge —
|
|
137
|
+
measured, four chips overflowed an 80px container.
|
|
138
|
+
max-width alone is not enough: a flex item's min-width is auto, so it refuses
|
|
139
|
+
to shrink below its content. min-width:0 lets it, and capping --pill-max-width
|
|
140
|
+
hands the overflow to Pill's own ellipsis. */
|
|
141
|
+
min-width: 0;
|
|
142
|
+
max-width: 100%;
|
|
143
|
+
--pill-max-width: 100%;
|
|
144
|
+
--pill-flex-shrink: 1;
|
|
44
145
|
--pill-cursor: pointer;
|
|
45
146
|
}
|
|
147
|
+
|
|
148
|
+
/* The Pill is itself a flex item, and its own min-width:auto is the last thing
|
|
149
|
+
holding the label at full width. Zeroing it is what actually lets the ellipsis run. */
|
|
150
|
+
.chip > :global(.pill) {
|
|
151
|
+
min-width: 0;
|
|
152
|
+
}
|
|
46
153
|
</style>
|
|
@@ -1,15 +1,57 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* A suggestion chip.
|
|
4
|
+
*
|
|
5
|
+
* `label` is what the merchant reads; `value` is what gets dispatched. They are
|
|
6
|
+
* separate because a short call-to-action ("Refund trends") usually stands in for
|
|
7
|
+
* a much longer query, and sending the label would send the wrong thing.
|
|
8
|
+
*/
|
|
1
9
|
export type ChatSuggestion = string | {
|
|
2
10
|
label: string;
|
|
3
11
|
value?: string;
|
|
12
|
+
/** Leading mark for the chip — an image or SVG *URL* (rendered via Img with
|
|
13
|
+
* inlineSvg). For raw markup or custom rendering, use the `icon` snippet. */
|
|
14
|
+
icon?: string;
|
|
15
|
+
/** Hover/long-press text. Defaults to `value` when that differs from `label`. */
|
|
16
|
+
hint?: string;
|
|
4
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* How the chips are arranged.
|
|
20
|
+
*
|
|
21
|
+
* `wrap` flows them onto as many lines as needed — right for a roomy panel.
|
|
22
|
+
* `scroll` keeps them on one line inside a draggable scroller — right for a
|
|
23
|
+
* composer on a phone, where wrapping would push the input off-screen.
|
|
24
|
+
*/
|
|
25
|
+
export type ChatSuggestionsLayout = 'wrap' | 'scroll';
|
|
26
|
+
export type ChatSuggestionsDirection = 'horizontal' | 'vertical';
|
|
5
27
|
export type ChatSuggestionsProperties = OptionalChatSuggestionsProperties & ChatSuggestionsEventProperties & MandatoryChatSuggestionsProperties;
|
|
6
28
|
export type MandatoryChatSuggestionsProperties = {
|
|
7
29
|
items: ChatSuggestion[];
|
|
8
30
|
};
|
|
9
31
|
export type OptionalChatSuggestionsProperties = {
|
|
10
32
|
disabled?: boolean;
|
|
33
|
+
layout?: ChatSuggestionsLayout;
|
|
34
|
+
direction?: ChatSuggestionsDirection;
|
|
35
|
+
/** Render at most this many chips. Omit to render every item. */
|
|
36
|
+
maxVisible?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Suppresses the whole row without unmounting it — for the window between asking
|
|
39
|
+
* and answering, where stale suggestions would invite a second wrong question.
|
|
40
|
+
*/
|
|
41
|
+
loading?: boolean;
|
|
42
|
+
/** Custom mark per chip; receives the item's resolved icon string, or null. */
|
|
43
|
+
icon?: Snippet<[string | null, number]>;
|
|
11
44
|
testId?: string;
|
|
12
45
|
classes?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Class string placed on EVERY chip wrapper.
|
|
48
|
+
*
|
|
49
|
+
* A consuming app usually keeps its chip appearance in a central stylesheet keyed on
|
|
50
|
+
* its own class name. Without a hook the library's own wrapper is the only thing in
|
|
51
|
+
* the tree, those central rules never match, and the chips silently fall back to the
|
|
52
|
+
* library's neutral defaults — which is a regression, not a theme.
|
|
53
|
+
*/
|
|
54
|
+
chipClasses?: string;
|
|
13
55
|
};
|
|
14
56
|
export type ChatSuggestionsEventProperties = {
|
|
15
57
|
onselect?: (value: string, index: number) => void;
|