@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.
@@ -0,0 +1,217 @@
1
+ <script lang="ts">
2
+ import Accordion from '../Accordion/Accordion.svelte';
3
+ import Button from '../Button/Button.svelte';
4
+ import Loader from '../Loader/Loader.svelte';
5
+ import type { ThinkingIndicatorProperties } from './properties';
6
+
7
+ let {
8
+ label,
9
+ detail,
10
+ expanded = $bindable(false),
11
+ variant = 'default',
12
+ onToggle,
13
+ avatar,
14
+ toggleIcon,
15
+ testId,
16
+ toggleTestId,
17
+ detailTestId,
18
+ labelTestId,
19
+ classes
20
+ }: ThinkingIndicatorProperties = $props();
21
+
22
+ // A detail string is what makes the indicator expandable. Without one there is
23
+ // nothing to reveal, so it renders as a plain live status line instead of a
24
+ // disclosure control. `bare` overrides that entirely.
25
+ const isExpandable = $derived(
26
+ variant !== 'bare' && typeof detail === 'string' && detail.length > 0
27
+ );
28
+
29
+ const handleToggle = (): void => {
30
+ expanded = !expanded;
31
+ onToggle?.();
32
+ };
33
+ </script>
34
+
35
+ {#if isExpandable}
36
+ <div
37
+ class="thinking-indicator expandable {classes ?? ''}"
38
+ data-pw={typeof testId === 'string' ? testId : null}
39
+ testID={typeof testId === 'string' ? testId : null}
40
+ >
41
+ <div class="toggle">
42
+ <Button
43
+ onclick={handleToggle}
44
+ ariaExpanded={expanded}
45
+ testId={toggleTestId ?? (testId && `${testId}-toggle`)}
46
+ >
47
+ <span class="status-row">
48
+ <span class="avatar">
49
+ {#if avatar}{@render avatar()}{:else}<Loader />{/if}
50
+ </span>
51
+ <span class="status-label static-label" data-pw={labelTestId ?? null}>{label}</span>
52
+ </span>
53
+ <span class="arrow" class:expanded aria-hidden="true">
54
+ {#if toggleIcon}
55
+ {@render toggleIcon()}
56
+ {:else}
57
+ <svg viewBox="0 0 16 16" fill="none">
58
+ <path
59
+ d="M4 6l4 4 4-4"
60
+ stroke="currentColor"
61
+ stroke-width="1.5"
62
+ stroke-linecap="round"
63
+ stroke-linejoin="round"
64
+ />
65
+ </svg>
66
+ {/if}
67
+ </span>
68
+ </Button>
69
+ </div>
70
+ <Accordion expand={expanded}>
71
+ <p class="detail" data-pw={detailTestId ?? (testId && `${testId}-detail`) ?? null}>
72
+ {detail}
73
+ </p>
74
+ </Accordion>
75
+ </div>
76
+ {:else if variant === 'bare'}
77
+ <span
78
+ class="status-label {classes ?? ''}"
79
+ data-pw={typeof testId === 'string' ? testId : null}
80
+ testID={typeof testId === 'string' ? testId : null}
81
+ >{#if typeof labelTestId === 'string'}<span data-pw={labelTestId} testID={labelTestId}
82
+ >{label}</span
83
+ >{:else}{label}{/if}</span
84
+ >
85
+ {:else}
86
+ <div
87
+ class="thinking-indicator status-host {classes ?? ''}"
88
+ data-pw={typeof testId === 'string' ? testId : null}
89
+ testID={typeof testId === 'string' ? testId : null}
90
+ >
91
+ <span class="status-row">
92
+ <span class="avatar">
93
+ {#if avatar}{@render avatar()}{:else}<Loader />{/if}
94
+ </span>
95
+ <span class="status-label" data-pw={labelTestId ?? null}>{label}</span>
96
+ </span>
97
+ </div>
98
+ {/if}
99
+
100
+ <style>
101
+ .thinking-indicator {
102
+ box-sizing: border-box;
103
+ width: 100%;
104
+ }
105
+
106
+ .expandable {
107
+ border-bottom: var(--thinking-indicator-border-bottom, 1px solid #e4e4e7);
108
+ padding-block: var(--thinking-indicator-padding-block, 0.5rem);
109
+ margin-bottom: var(--thinking-indicator-margin-bottom, 1rem);
110
+ }
111
+
112
+ .toggle {
113
+ width: 100%;
114
+ --button-color: transparent;
115
+ --button-border: none;
116
+ --button-box-shadow: none;
117
+ --button-hover-box-shadow: none;
118
+ --button-active-box-shadow: none;
119
+ --button-width: 100%;
120
+ --button-padding: 0;
121
+ --button-justify-content: flex-start;
122
+ --button-text-color: inherit;
123
+ }
124
+
125
+ /* A flex ROW host makes the inner status-row hug its content, so the shimmer
126
+ gradient maps to the text's own width rather than the full container. */
127
+ .status-host {
128
+ display: flex;
129
+ align-items: center;
130
+ }
131
+
132
+ .status-row {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: var(--thinking-indicator-gap, 0.25rem);
136
+ min-width: 0;
137
+ }
138
+
139
+ .avatar {
140
+ display: inline-flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ flex-shrink: 0;
144
+ width: var(--thinking-indicator-avatar-size, 1.5rem);
145
+ height: var(--thinking-indicator-avatar-size, 1.5rem);
146
+ --loader-width: var(--thinking-indicator-avatar-loader-size, 1rem);
147
+ --loader-height: var(--thinking-indicator-avatar-loader-size, 1rem);
148
+ }
149
+
150
+ .status-label {
151
+ flex: 1;
152
+ min-width: 0;
153
+ overflow: hidden;
154
+ white-space: nowrap;
155
+ text-overflow: ellipsis;
156
+ text-align: left;
157
+ font-size: var(--thinking-indicator-font-size, 0.75rem);
158
+ line-height: var(--thinking-indicator-line-height, 1.25rem);
159
+ color: var(--thinking-indicator-label-color, #858585);
160
+ background: var(
161
+ --thinking-indicator-shimmer-gradient,
162
+ linear-gradient(90deg, #858585 0%, #bebebe 50%, #858585 100%)
163
+ );
164
+ background-size: 200% 100%;
165
+ background-clip: text;
166
+ -webkit-text-fill-color: transparent;
167
+ animation: thinking-indicator-shimmer var(--thinking-indicator-shimmer-duration, 2s) linear
168
+ infinite;
169
+ }
170
+
171
+ /* The expandable summary holds still; only live status lines shimmer. */
172
+ .static-label {
173
+ animation: none;
174
+ -webkit-text-fill-color: var(--thinking-indicator-label-color, #858585);
175
+ }
176
+
177
+ .arrow {
178
+ display: inline-flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ flex-shrink: 0;
182
+ width: var(--thinking-indicator-arrow-size, 1rem);
183
+ height: var(--thinking-indicator-arrow-size, 1rem);
184
+ margin-left: var(--thinking-indicator-arrow-margin-left, 0.25rem);
185
+ color: var(--thinking-indicator-arrow-color, #7a7a7a);
186
+ transform: rotate(-90deg);
187
+ transition: var(--thinking-indicator-arrow-transition, transform 0.2s ease-in-out);
188
+ }
189
+
190
+ .arrow svg {
191
+ width: 100%;
192
+ height: 100%;
193
+ }
194
+
195
+ .arrow.expanded {
196
+ transform: rotate(0deg);
197
+ }
198
+
199
+ .detail {
200
+ margin: 0;
201
+ padding-top: var(--thinking-indicator-detail-padding-top, 0.5rem);
202
+ font-size: var(--thinking-indicator-detail-font-size, 0.875rem);
203
+ line-height: var(--thinking-indicator-detail-line-height, 1.5);
204
+ color: var(--thinking-indicator-detail-color, #bebebe);
205
+ text-align: left;
206
+ }
207
+
208
+ @keyframes thinking-indicator-shimmer {
209
+ 0% {
210
+ background-position: 200% 0;
211
+ }
212
+
213
+ 100% {
214
+ background-position: -200% 0;
215
+ }
216
+ }
217
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { ThinkingIndicatorProperties } from './properties';
2
+ declare const ThinkingIndicator: import("svelte").Component<ThinkingIndicatorProperties, {}, "expanded">;
3
+ type ThinkingIndicator = ReturnType<typeof ThinkingIndicator>;
4
+ export default ThinkingIndicator;
@@ -0,0 +1,30 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type ThinkingIndicatorVariant = 'default' | 'bare';
3
+ export type ThinkingIndicatorProperties = OptionalThinkingIndicatorProperties & MandatoryThinkingIndicatorProperties;
4
+ export type MandatoryThinkingIndicatorProperties = {
5
+ label: string;
6
+ };
7
+ export type OptionalThinkingIndicatorProperties = {
8
+ /** Reasoning/steps text. Providing one makes the indicator an expandable disclosure. */
9
+ detail?: string;
10
+ /** Bindable disclosure state — meaningful only when `detail` is set. */
11
+ expanded?: boolean;
12
+ /**
13
+ * `bare` renders only the shimmering label — for chat bubbles where the surrounding
14
+ * UI already supplies the avatar and layout. It never becomes expandable.
15
+ */
16
+ variant?: ThinkingIndicatorVariant;
17
+ onToggle?: () => void;
18
+ /** Leading indicator. Falls back to the built-in spinner. */
19
+ avatar?: Snippet;
20
+ /** Disclosure chevron. Falls back to a built-in chevron that rotates on expand. */
21
+ toggleIcon?: Snippet;
22
+ testId?: string;
23
+ /** Override the toggle button's test id (default: `<testId>-toggle`). */
24
+ toggleTestId?: string;
25
+ /** Override the detail text's test id (default: `<testId>-detail`). */
26
+ detailTestId?: string;
27
+ /** Test id for the status label itself (none by default). */
28
+ labelTestId?: string;
29
+ classes?: string;
30
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -174,6 +174,11 @@ export const tooltip = (node, options) => {
174
174
  if (bubbleEl !== null || delayTimer !== null) {
175
175
  return;
176
176
  }
177
+ // An empty text renders a degenerate stub bubble (arrow + sliver) that reads as a
178
+ // clipped artifact — consumers legitimately pass '' to mean "no tooltip here".
179
+ if (currentOptions.text.trim().length === 0) {
180
+ return;
181
+ }
177
182
  const doShow = () => {
178
183
  // Re-check after the delay: hide() may have been called while the timer was pending.
179
184
  if (bubbleEl !== null) {
@@ -0,0 +1,110 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy, untrack } from 'svelte';
3
+ import type { TypewriterTextProperties } from './properties';
4
+
5
+ let {
6
+ text,
7
+ speed = 15,
8
+ isStreaming = false,
9
+ renderText,
10
+ testId,
11
+ classes
12
+ }: TypewriterTextProperties = $props();
13
+
14
+ let displayedText = $state('');
15
+ let currentIndex = $state(0);
16
+ let timeoutId = $state<ReturnType<typeof setTimeout> | null>(null);
17
+
18
+ // Where typing left off, so newly streamed-in text continues rather than restarts.
19
+ let previousTextLength = $state(0);
20
+
21
+ const typeNextCharacter = (): void => {
22
+ if (currentIndex < text.length) {
23
+ displayedText = text.substring(0, currentIndex + 1);
24
+ currentIndex++;
25
+ timeoutId = setTimeout(typeNextCharacter, speed);
26
+ }
27
+ };
28
+
29
+ // A reactive timer cannot be a $derived — same escape the chart components use.
30
+ // Internal typing state is read untracked so the effect re-runs only when the
31
+ // `text` prop itself changes, never on our own per-character writes.
32
+ // eslint-disable-next-line no-restricted-syntax
33
+ $effect(() => {
34
+ const nextText = text;
35
+ untrack(() => {
36
+ // Streamed text CONTINUES what is already displayed. Anything else — a
37
+ // shorter string, or one that no longer starts with what was typed — is a
38
+ // replacement: stop the timer and retype from the start, instead of
39
+ // leaving stale characters on screen.
40
+ const isContinuation =
41
+ nextText.length >= previousTextLength && nextText.startsWith(displayedText);
42
+ if (!isContinuation) {
43
+ if (timeoutId !== null) {
44
+ clearTimeout(timeoutId);
45
+ timeoutId = null;
46
+ }
47
+ displayedText = '';
48
+ currentIndex = 0;
49
+ previousTextLength = 0;
50
+ }
51
+ if (nextText.length > 0 && nextText.length > previousTextLength) {
52
+ if (currentIndex >= previousTextLength) {
53
+ if (timeoutId !== null) {
54
+ clearTimeout(timeoutId);
55
+ }
56
+ typeNextCharacter();
57
+ }
58
+ previousTextLength = nextText.length;
59
+ }
60
+ });
61
+ });
62
+
63
+ // eslint-disable-next-line no-restricted-syntax
64
+ $effect(() => {
65
+ if (!isStreaming && text.length > 0) {
66
+ if (timeoutId !== null) {
67
+ clearTimeout(timeoutId);
68
+ }
69
+ displayedText = text;
70
+ currentIndex = text.length;
71
+ }
72
+ });
73
+
74
+ onMount(() => {
75
+ if (text.length > 0) {
76
+ previousTextLength = text.length;
77
+ typeNextCharacter();
78
+ }
79
+ });
80
+
81
+ onDestroy(() => {
82
+ if (timeoutId !== null) {
83
+ clearTimeout(timeoutId);
84
+ }
85
+ });
86
+ </script>
87
+
88
+ <div
89
+ class="typewriter-text {classes ?? ''}"
90
+ class:plain={typeof renderText !== 'function'}
91
+ data-pw={typeof testId === 'string' ? testId : null}
92
+ testID={typeof testId === 'string' ? testId : null}
93
+ >
94
+ {#if typeof renderText === 'function'}
95
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
96
+ {@html renderText(displayedText)}
97
+ {:else}
98
+ {displayedText}
99
+ {/if}
100
+ </div>
101
+
102
+ <style>
103
+ .typewriter-text {
104
+ max-width: var(--typewriter-text-max-width, 100%);
105
+ }
106
+
107
+ .plain {
108
+ white-space: var(--typewriter-text-white-space, pre-wrap);
109
+ }
110
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { TypewriterTextProperties } from './properties';
2
+ declare const TypewriterText: import("svelte").Component<TypewriterTextProperties, {}, "">;
3
+ type TypewriterText = ReturnType<typeof TypewriterText>;
4
+ export default TypewriterText;
@@ -0,0 +1,21 @@
1
+ export type TypewriterTextProperties = OptionalTypewriterTextProperties & MandatoryTypewriterTextProperties;
2
+ export type MandatoryTypewriterTextProperties = {
3
+ text: string;
4
+ };
5
+ export type OptionalTypewriterTextProperties = {
6
+ /** Milliseconds between characters. */
7
+ speed?: number;
8
+ /**
9
+ * While `true`, text revealed so far stays and new text keeps typing as `text` grows.
10
+ * The moment it turns `false`, all remaining text is shown at once.
11
+ */
12
+ isStreaming?: boolean;
13
+ /**
14
+ * Renders the revealed text as HTML — pass a markdown renderer to type rich text.
15
+ * The component trusts the returned string, so sanitise inside the renderer if the
16
+ * text can contain untrusted input. When omitted, the text renders as plain text.
17
+ */
18
+ renderText?: (text: string) => string;
19
+ testId?: string;
20
+ classes?: string;
21
+ };
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -34,6 +34,7 @@ export { default as Tabs } from './Tabs/Tabs.svelte';
34
34
  export { default as Choicebox } from './Choicebox/Choicebox.svelte';
35
35
  export { default as Slider } from './Slider/Slider.svelte';
36
36
  export { default as Tooltip } from './Tooltip/Tooltip.svelte';
37
+ export { default as TypewriterText } from './TypewriterText/TypewriterText.svelte';
37
38
  export { tooltip } from './Tooltip/tooltip-action';
38
39
  export { default as Shimmer } from './Shimmer/Shimmer.svelte';
39
40
  export { default as Progress } from './Progress/Progress.svelte';
@@ -52,6 +53,7 @@ export { default as ContextMenu } from './ContextMenu/ContextMenu.svelte';
52
53
  export { default as Calendar } from './Calendar/Calendar.svelte';
53
54
  export { default as RelativeTime } from './RelativeTime/RelativeTime.svelte';
54
55
  export { default as ThemeSwitcher } from './ThemeSwitcher/ThemeSwitcher.svelte';
56
+ export { default as ThinkingIndicator } from './ThinkingIndicator/ThinkingIndicator.svelte';
55
57
  export { default as Book } from './Book/Book.svelte';
56
58
  export { default as Browser } from './Browser/Browser.svelte';
57
59
  export { default as Phone } from './Phone/Phone.svelte';
@@ -66,6 +68,7 @@ export { default as DateRangePicker } from './DateRangePicker/DateRangePicker.sv
66
68
  export { default as Breadcrumb } from './Breadcrumb/Breadcrumb.svelte';
67
69
  export { default as LineChart } from './LineChart/LineChart.svelte';
68
70
  export { default as AreaChart } from './AreaChart/AreaChart.svelte';
71
+ export { default as AttachmentChipRow } from './AttachmentChipRow/AttachmentChipRow.svelte';
69
72
  export { default as BarChart } from './BarChart/BarChart.svelte';
70
73
  export { default as PieChart } from './PieChart/PieChart.svelte';
71
74
  export { default as SankeyChart } from './SankeyChart/SankeyChart.svelte';
@@ -85,6 +88,8 @@ export { default as ChatComposer } from './ChatComposer/ChatComposer.svelte';
85
88
  export { default as ChatSuggestions } from './ChatSuggestions/ChatSuggestions.svelte';
86
89
  export { default as ChatToolStatus } from './ChatToolStatus/ChatToolStatus.svelte';
87
90
  export { default as ChatBubble } from './ChatBubble/ChatBubble.svelte';
91
+ export { default as HITL } from './HITL/HITL.svelte';
92
+ export { pauseAllConfirmationTimers } from './HITL/timers';
88
93
  export { default as Resizable } from './Resizable/Resizable.svelte';
89
94
  export { ChatController } from './Chat/controller.svelte';
90
95
  export { partyOf } from './Chat/roles';
@@ -118,6 +123,7 @@ export type * from './Tabs/properties';
118
123
  export type * from './Choicebox/properties';
119
124
  export type * from './Slider/properties';
120
125
  export type * from './Tooltip/properties';
126
+ export type * from './TypewriterText/properties';
121
127
  export type * from './Shimmer/properties';
122
128
  export type * from './Progress/properties';
123
129
  export type * from './Pill/properties';
@@ -135,6 +141,7 @@ export type * from './ContextMenu/properties';
135
141
  export type * from './Calendar/properties';
136
142
  export type * from './RelativeTime/properties';
137
143
  export type * from './ThemeSwitcher/properties';
144
+ export type * from './ThinkingIndicator/properties';
138
145
  export type * from './Book/properties';
139
146
  export type * from './Browser/properties';
140
147
  export type * from './Phone/properties';
@@ -150,6 +157,7 @@ export type * from './DateRangePicker/properties';
150
157
  export type * from './Breadcrumb/properties';
151
158
  export type * from './LineChart/properties';
152
159
  export type * from './AreaChart/properties';
160
+ export type * from './AttachmentChipRow/properties';
153
161
  export type * from './BarChart/properties';
154
162
  export type * from './PieChart/properties';
155
163
  export type * from './SankeyChart/properties';
@@ -171,6 +179,7 @@ export type * from './ChatComposer/properties';
171
179
  export type * from './ChatSuggestions/properties';
172
180
  export type * from './ChatToolStatus/properties';
173
181
  export type * from './ChatBubble/properties';
182
+ export type * from './HITL/properties';
174
183
  export type * from './Resizable/properties';
175
184
  export { validateInput } from './utils';
176
185
  export { formatNumberIndian } from './_chart/format';
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ export { default as Tabs } from './Tabs/Tabs.svelte';
34
34
  export { default as Choicebox } from './Choicebox/Choicebox.svelte';
35
35
  export { default as Slider } from './Slider/Slider.svelte';
36
36
  export { default as Tooltip } from './Tooltip/Tooltip.svelte';
37
+ export { default as TypewriterText } from './TypewriterText/TypewriterText.svelte';
37
38
  export { tooltip } from './Tooltip/tooltip-action';
38
39
  export { default as Shimmer } from './Shimmer/Shimmer.svelte';
39
40
  export { default as Progress } from './Progress/Progress.svelte';
@@ -52,6 +53,7 @@ export { default as ContextMenu } from './ContextMenu/ContextMenu.svelte';
52
53
  export { default as Calendar } from './Calendar/Calendar.svelte';
53
54
  export { default as RelativeTime } from './RelativeTime/RelativeTime.svelte';
54
55
  export { default as ThemeSwitcher } from './ThemeSwitcher/ThemeSwitcher.svelte';
56
+ export { default as ThinkingIndicator } from './ThinkingIndicator/ThinkingIndicator.svelte';
55
57
  export { default as Book } from './Book/Book.svelte';
56
58
  export { default as Browser } from './Browser/Browser.svelte';
57
59
  export { default as Phone } from './Phone/Phone.svelte';
@@ -66,6 +68,7 @@ export { default as DateRangePicker } from './DateRangePicker/DateRangePicker.sv
66
68
  export { default as Breadcrumb } from './Breadcrumb/Breadcrumb.svelte';
67
69
  export { default as LineChart } from './LineChart/LineChart.svelte';
68
70
  export { default as AreaChart } from './AreaChart/AreaChart.svelte';
71
+ export { default as AttachmentChipRow } from './AttachmentChipRow/AttachmentChipRow.svelte';
69
72
  export { default as BarChart } from './BarChart/BarChart.svelte';
70
73
  export { default as PieChart } from './PieChart/PieChart.svelte';
71
74
  export { default as SankeyChart } from './SankeyChart/SankeyChart.svelte';
@@ -85,6 +88,8 @@ export { default as ChatComposer } from './ChatComposer/ChatComposer.svelte';
85
88
  export { default as ChatSuggestions } from './ChatSuggestions/ChatSuggestions.svelte';
86
89
  export { default as ChatToolStatus } from './ChatToolStatus/ChatToolStatus.svelte';
87
90
  export { default as ChatBubble } from './ChatBubble/ChatBubble.svelte';
91
+ export { default as HITL } from './HITL/HITL.svelte';
92
+ export { pauseAllConfirmationTimers } from './HITL/timers';
88
93
  export { default as Resizable } from './Resizable/Resizable.svelte';
89
94
  export { ChatController } from './Chat/controller.svelte';
90
95
  export { partyOf } from './Chat/roles';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/svelte-ui-components",
3
- "version": "2.126.0",
3
+ "version": "2.127.0",
4
4
  "description": "A themeable Svelte 5 UI component library with CSS custom property driven styling",
5
5
  "keywords": [
6
6
  "svelte",