@juspay/svelte-ui-components 4.27.5 → 4.28.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.
Files changed (164) hide show
  1. package/README.md +140 -36
  2. package/dist/Animations/ModalAnimation.svelte +15 -1
  3. package/dist/Animations/OverlayAnimation.svelte +7 -1
  4. package/dist/AreaChart/AreaChart.svelte +366 -85
  5. package/dist/AspectRatio/AspectRatio.svelte +51 -0
  6. package/dist/AspectRatio/AspectRatio.svelte.d.ts +3 -0
  7. package/dist/AspectRatio/properties.d.ts +27 -0
  8. package/dist/AspectRatio/properties.js +1 -0
  9. package/dist/Banner/Banner.svelte +10 -4
  10. package/dist/Banner/properties.d.ts +8 -0
  11. package/dist/BarChart/BarChart.svelte +156 -49
  12. package/dist/Book/Book.svelte +14 -1
  13. package/dist/BrandLoader/BrandLoader.svelte +76 -12
  14. package/dist/Button/Button.svelte +56 -11
  15. package/dist/Calendar/Calendar.svelte +88 -61
  16. package/dist/Card/Card.svelte +1 -1
  17. package/dist/Carousel/Carousel.svelte +6 -3
  18. package/dist/ChatComposer/ChatComposer.svelte +54 -4
  19. package/dist/ChatComposer/dictationState.d.ts +15 -0
  20. package/dist/ChatComposer/dictationState.js +9 -0
  21. package/dist/ChatComposer/properties.d.ts +28 -1
  22. package/dist/ChatMessage/ChatMessage.svelte +27 -1
  23. package/dist/CheckListItem/CheckListItem.svelte +3 -3
  24. package/dist/Checkbox/Checkbox.svelte +84 -3
  25. package/dist/Checkbox/properties.d.ts +45 -13
  26. package/dist/ChipInput/ChipInput.svelte +70 -3
  27. package/dist/ChipInput/properties.d.ts +16 -0
  28. package/dist/Choicebox/Choicebox.svelte +156 -1
  29. package/dist/Choicebox/group.d.ts +63 -0
  30. package/dist/Choicebox/group.js +127 -0
  31. package/dist/Choicebox/properties.d.ts +46 -0
  32. package/dist/ColorPicker/ColorPicker.svelte +169 -20
  33. package/dist/ColorPicker/properties.d.ts +16 -0
  34. package/dist/Combobox/Combobox.svelte +87 -25
  35. package/dist/Combobox/properties.d.ts +23 -0
  36. package/dist/CommandMenu/CommandMenu.svelte +27 -11
  37. package/dist/ContextMenu/ContextMenu.svelte +31 -37
  38. package/dist/DateRangePicker/DateRangePicker.svelte +171 -73
  39. package/dist/DeltaIndicator/DeltaIndicator.svelte +3 -3
  40. package/dist/Draggable/Draggable.svelte +12 -7
  41. package/dist/DualAxisBarChart/DualAxisBarChart.svelte +11 -4
  42. package/dist/EmptyState/EmptyState.svelte +1 -1
  43. package/dist/FileDropzoneTrigger/FileDropzoneTrigger.svelte +1 -1
  44. package/dist/FileInput/FileInput.svelte +50 -1
  45. package/dist/FileInput/properties.d.ts +7 -0
  46. package/dist/FunnelChart/FunnelChart.svelte +40 -12
  47. package/dist/FunnelChart/properties.d.ts +7 -1
  48. package/dist/Gallery/Gallery.svelte +20 -13
  49. package/dist/Gallery/properties.d.ts +8 -0
  50. package/dist/GridItem/GridItem.svelte +31 -1
  51. package/dist/HITL/HITL.svelte +35 -9
  52. package/dist/Icon/Icon.svelte +19 -3
  53. package/dist/Input/Input.svelte +24 -10
  54. package/dist/Input/properties.d.ts +37 -1
  55. package/dist/InputButton/InputButton.svelte +86 -11
  56. package/dist/InputButton/properties.d.ts +10 -1
  57. package/dist/Label/Label.svelte +54 -0
  58. package/dist/Label/Label.svelte.d.ts +3 -0
  59. package/dist/Label/properties.d.ts +26 -0
  60. package/dist/Label/properties.js +1 -0
  61. package/dist/LineChart/LineChart.svelte +385 -94
  62. package/dist/ListItem/ListItem.svelte +121 -15
  63. package/dist/Loader/Loader.svelte +22 -2
  64. package/dist/LoadingDots/LoadingDots.svelte +21 -0
  65. package/dist/MediaUpload/MediaUpload.svelte +13 -3
  66. package/dist/Menu/Menu.svelte +56 -28
  67. package/dist/Modal/Modal.svelte +77 -42
  68. package/dist/Pagination/Pagination.svelte +9 -2
  69. package/dist/Phone/Phone.svelte +4 -0
  70. package/dist/PieChart/PieChart.svelte +282 -38
  71. package/dist/PieChart/properties.d.ts +9 -0
  72. package/dist/Pill/Pill.svelte +14 -5
  73. package/dist/Progress/Progress.svelte +20 -0
  74. package/dist/Radio/Radio.svelte +58 -1
  75. package/dist/Radio/properties.d.ts +23 -0
  76. package/dist/RatingGroup/RatingGroup.svelte +283 -0
  77. package/dist/RatingGroup/RatingGroup.svelte.d.ts +4 -0
  78. package/dist/RatingGroup/properties.d.ts +73 -0
  79. package/dist/RatingGroup/properties.js +1 -0
  80. package/dist/RelativeTime/RelativeTime.svelte +1 -1
  81. package/dist/SankeyChart/SankeyChart.svelte +272 -50
  82. package/dist/SankeyChart/properties.d.ts +9 -0
  83. package/dist/Scroller/Scroller.svelte +37 -2
  84. package/dist/Select/Select.svelte +166 -46
  85. package/dist/Select/properties.d.ts +7 -0
  86. package/dist/Separator/Separator.svelte +48 -0
  87. package/dist/Separator/Separator.svelte.d.ts +3 -0
  88. package/dist/Separator/properties.d.ts +22 -0
  89. package/dist/Separator/properties.js +1 -0
  90. package/dist/Sheet/Sheet.svelte +60 -21
  91. package/dist/Shimmer/Shimmer.svelte +15 -0
  92. package/dist/Slider/Slider.svelte +87 -6
  93. package/dist/Slider/properties.d.ts +26 -0
  94. package/dist/Snippet/Snippet.svelte +4 -0
  95. package/dist/Snippet/copyState.svelte.d.ts +13 -1
  96. package/dist/Snippet/copyState.svelte.js +27 -1
  97. package/dist/Snippet/properties.d.ts +7 -0
  98. package/dist/SoundKit/SoundKit.js +9 -8
  99. package/dist/SplitInput/SplitInput.svelte +68 -2
  100. package/dist/SplitInput/properties.d.ts +23 -0
  101. package/dist/StatCard/StatCard.svelte +6 -6
  102. package/dist/Status/Status.svelte +2 -2
  103. package/dist/Stepper/Step.svelte +19 -6
  104. package/dist/Stepper/Stepper.svelte +108 -35
  105. package/dist/Table/BuiltinCell.svelte +2 -2
  106. package/dist/Table/Table.svelte +373 -55
  107. package/dist/Table/properties.d.ts +134 -3
  108. package/dist/Tabs/Tabs.svelte +263 -65
  109. package/dist/Tabs/properties.d.ts +23 -0
  110. package/dist/TaskList/TaskList.svelte +14 -5
  111. package/dist/ThemeSwitcher/ThemeSwitcher.svelte +23 -0
  112. package/dist/ThinkingIndicator/ThinkingIndicator.svelte +13 -5
  113. package/dist/Toast/Toast.svelte +27 -2
  114. package/dist/Toggle/Toggle.svelte +79 -7
  115. package/dist/Toggle/Toggle.svelte.d.ts +1 -1
  116. package/dist/Toggle/properties.d.ts +30 -7
  117. package/dist/ToolCallLog/ToolCallLog.svelte +59 -21
  118. package/dist/ToolCallLog/properties.d.ts +10 -0
  119. package/dist/Toolbar/Toolbar.svelte +1 -1
  120. package/dist/Tooltip/Tooltip.svelte +20 -2
  121. package/dist/Tooltip/tooltip-action.js +39 -4
  122. package/dist/TypewriterText/TypewriterText.svelte +34 -2
  123. package/dist/TypewriterText/properties.d.ts +18 -5
  124. package/dist/_chart/ChartContainer.svelte +12 -0
  125. package/dist/_chart/ChartTooltip.svelte +18 -4
  126. package/dist/_chart/Legend.svelte +21 -0
  127. package/dist/_chart/Legend.svelte.d.ts +15 -0
  128. package/dist/_chart/geometry.d.ts +54 -1
  129. package/dist/_chart/geometry.js +122 -14
  130. package/dist/_chart/interactions.d.ts +5 -1
  131. package/dist/_chart/interactions.js +9 -5
  132. package/dist/_chart/scales.js +16 -0
  133. package/dist/_chart/temporal.d.ts +45 -0
  134. package/dist/_chart/temporal.js +116 -0
  135. package/dist/_field/description.d.ts +50 -0
  136. package/dist/_field/description.js +51 -0
  137. package/dist/_interaction/dismissal.d.ts +67 -0
  138. package/dist/_interaction/dismissal.js +137 -0
  139. package/dist/_interaction/focus.d.ts +73 -0
  140. package/dist/_interaction/focus.js +143 -0
  141. package/dist/assets/star.svg +5 -0
  142. package/dist/index.d.ts +13 -1
  143. package/dist/index.js +21 -1
  144. package/dist/reduced-motion.svelte.d.ts +37 -0
  145. package/dist/reduced-motion.svelte.js +44 -0
  146. package/dist/styles/theme-dark.css +778 -0
  147. package/dist/types.d.ts +4 -2
  148. package/dist/utils.d.ts +62 -1
  149. package/dist/utils.js +80 -21
  150. package/dist-codemod/{bin.js → codemod/bin.js} +13 -2
  151. package/dist-codemod/{cli.js → codemod/cli.js} +48 -3
  152. package/dist-codemod/codemod/migrate-audit.js +395 -0
  153. package/dist-codemod/codemod/migration-assets/legacy-display.css +120 -0
  154. package/dist-codemod/codemod/migration-assets/legacy-palette.css +106 -0
  155. package/dist-codemod/codemod/migration-assets/wc-display.json +492 -0
  156. package/dist-codemod/migrate/analyze.js +804 -0
  157. package/dist-codemod/migrate/chart-tooltip-selector.js +301 -0
  158. package/dist-wc/index.js +13992 -4323
  159. package/package.json +13 -6
  160. package/dist/Modal/focus-trap.d.ts +0 -42
  161. package/dist/Modal/focus-trap.js +0 -57
  162. /package/dist-codemod/{legacy-pairs.js → codemod/legacy-pairs.js} +0 -0
  163. /package/dist-codemod/{transform.js → codemod/transform.js} +0 -0
  164. /package/dist-codemod/{wc-children.js → codemod/wc-children.js} +0 -0
@@ -143,7 +143,12 @@
143
143
  display: flex;
144
144
  align-items: baseline;
145
145
  gap: var(--task-list-row-inline-gap, 10px);
146
- animation: task-list-fade-up 320ms var(--task-list-ease, cubic-bezier(0.23, 1, 0.32, 1)) both;
146
+ /* --task-list-ease was an easing-only token (DESIGN_PRINCIPLES.md motion
147
+ convention) — kept working unchanged, now paired with a duration token
148
+ and both fall back through the library-wide --motion-* tokens. */
149
+ animation: task-list-fade-up
150
+ var(--task-list-row-animation-duration, var(--motion-duration, 320ms))
151
+ var(--task-list-ease, var(--motion-easing, cubic-bezier(0.23, 1, 0.32, 1))) both;
147
152
  }
148
153
 
149
154
  .row-glyph {
@@ -160,7 +165,9 @@
160
165
  display: inline-flex;
161
166
  align-items: center;
162
167
  justify-content: center;
163
- animation: task-list-glyph-fade 150ms ease both;
168
+ animation: task-list-glyph-fade
169
+ var(--task-list-glyph-animation-duration, var(--motion-duration, 150ms))
170
+ var(--task-list-glyph-animation-easing, var(--motion-easing, ease)) both;
164
171
  }
165
172
 
166
173
  .glyph-dot {
@@ -197,7 +204,9 @@
197
204
  }
198
205
  .glyph-check {
199
206
  color: var(--task-list-done-color, #6b6b6b);
200
- animation: task-list-check-pop 200ms var(--task-list-ease, cubic-bezier(0.23, 1, 0.32, 1)) both;
207
+ animation: task-list-check-pop
208
+ var(--task-list-check-pop-animation-duration, var(--motion-duration, 200ms))
209
+ var(--task-list-ease, var(--motion-easing, cubic-bezier(0.23, 1, 0.32, 1))) both;
201
210
  }
202
211
  .glyph-x {
203
212
  color: var(--task-list-error-color, #c93f38);
@@ -211,7 +220,7 @@
211
220
  gap: var(--task-list-text-gap, 8px);
212
221
  }
213
222
  .task-row.pending .row-text {
214
- opacity: var(--task-list-pending-opacity, 0.55);
223
+ opacity: var(--task-list-pending-opacity, 0.85);
215
224
  }
216
225
 
217
226
  .row-label {
@@ -225,7 +234,7 @@
225
234
 
226
235
  .row-secondary {
227
236
  font-size: var(--task-list-secondary-font-size, 0.75rem);
228
- color: var(--task-list-secondary-color, #9a9a9a);
237
+ color: var(--task-list-secondary-color, #555b65);
229
238
  white-space: nowrap;
230
239
  }
231
240
  .row-secondary.mono {
@@ -143,6 +143,7 @@
143
143
  class:selected={currentValue === option.value}
144
144
  onclick={() => applyValue(option.value)}
145
145
  aria-label={option.label ?? option.value}
146
+ aria-pressed={currentValue === option.value}
146
147
  >
147
148
  <span class="icon">
148
149
  {#if typeof option.icon === 'function'}
@@ -255,4 +256,26 @@
255
256
  .segment-button.selected {
256
257
  color: var(--theme-switcher-icon-color-active, #1f2937);
257
258
  }
259
+
260
+ /* Unlike the two progress-bar loaders elsewhere in this library, every
261
+ transition here moves between two states that are each fully meaningful
262
+ on their own (a background tint, an icon's shown/hidden pose, the
263
+ indicator's resting position, a label's active color) -- there is no
264
+ in-between frame that could be misread as a different state. Dropping the
265
+ transition just makes the swap instant instead of animated; nothing is
266
+ stranded mid-motion the way a freeze mid-fill would be. Four separate
267
+ rules declare `transition` in this file (toggle background, icon
268
+ opacity/transform, segment indicator left/width, segment label color) --
269
+ all four are covered here, not just the two the icon/indicator case calls
270
+ out, so no motion in this component is left unguarded. Lives in the
271
+ component's own <style> because that is the only stylesheet that reaches
272
+ inside the shadow root a custom-element consumer gets. */
273
+ @media (prefers-reduced-motion: reduce) {
274
+ .toggle-button,
275
+ .icon,
276
+ .segment-indicator,
277
+ .segment-button {
278
+ transition: none;
279
+ }
280
+ }
258
281
  </style>
@@ -666,14 +666,18 @@
666
666
  padding-top: var(--thinking-indicator-detail-padding-top, 0.5rem);
667
667
  font-size: var(--thinking-indicator-detail-font-size, 0.875rem);
668
668
  line-height: var(--thinking-indicator-detail-line-height, 1.5);
669
- color: var(--thinking-indicator-detail-color, #bebebe);
669
+ /* Resting text, not the shimmer highlight — needs its own readable default
670
+ rather than reusing the shimmer's light sweep color. Matches
671
+ --thinking-indicator-trace-prose-color, the same prose-at-rest ink used
672
+ for the reasoning trace's paragraph rows. */
673
+ color: var(--thinking-indicator-detail-color, #6b6b6b);
670
674
  text-align: left;
671
675
  }
672
676
 
673
677
  .elapsed {
674
678
  flex-shrink: 0;
675
679
  font-variant-numeric: tabular-nums;
676
- color: var(--thinking-indicator-elapsed-color, #9a9a9a);
680
+ color: var(--thinking-indicator-elapsed-color, #4b5563);
677
681
  font-size: var(--thinking-indicator-elapsed-font-size, 0.75rem);
678
682
  }
679
683
 
@@ -753,7 +757,9 @@
753
757
  margin: -3px -6px;
754
758
  border-radius: var(--thinking-indicator-trace-row-radius, 6px);
755
759
  cursor: pointer;
756
- transition: background 150ms ease;
760
+ transition: background
761
+ var(--thinking-indicator-trace-row-transition-duration, var(--motion-duration, 150ms))
762
+ var(--thinking-indicator-trace-row-transition-easing, var(--motion-easing, ease));
757
763
  width: calc(100% + 12px);
758
764
  }
759
765
 
@@ -777,7 +783,7 @@
777
783
  }
778
784
 
779
785
  .row-secondary {
780
- color: var(--thinking-indicator-trace-secondary-color, #9a9a9a);
786
+ color: var(--thinking-indicator-trace-secondary-color, #4b5563);
781
787
  font-size: var(--thinking-indicator-trace-secondary-font-size, 0.75rem);
782
788
  white-space: nowrap;
783
789
  }
@@ -861,7 +867,9 @@
861
867
  font-size: var(--thinking-indicator-trace-more-font-size, 0.75rem);
862
868
  color: var(--thinking-indicator-trace-more-color, #9a9a9a);
863
869
  padding: 6px 0 0 22px;
864
- animation: thinking-indicator-fade-in 300ms ease both;
870
+ animation: thinking-indicator-fade-in
871
+ var(--thinking-indicator-trace-more-animation-duration, var(--motion-duration, 300ms))
872
+ var(--thinking-indicator-trace-more-animation-easing, var(--motion-easing, ease)) both;
865
873
  }
866
874
 
867
875
  @media (prefers-reduced-motion: reduce) {
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
3
  import { fly } from 'svelte/transition';
4
+ import { prefersReducedMotion } from '../utils';
4
5
  import type { ToastDirection, ToastProperties } from './properties';
5
6
  import type { FlyAnimationConfig } from '../types';
6
7
  import Img from '../Img/Img.svelte';
@@ -72,6 +73,20 @@
72
73
  break;
73
74
  }
74
75
 
76
+ // Reduced motion zeroes the OFFSETS as well as the durations, and the offsets
77
+ // are the part that matters. `fly` interpolates from x/y to 0, so a duration of
78
+ // 0 with x still at 500 paints the toast 500px away on its first frame and then
79
+ // snaps -- a stranded frame that reads as a glitch rather than as no animation.
80
+ // Zeroing both makes it simply appear in place, which is what the preference
81
+ // asks for. A `@media` block cannot reach any of this: these values are consumed
82
+ // by `in:fly={...}` / `out:fly={...}`, so the guard has to live here.
83
+ if (prefersReducedMotion()) {
84
+ return {
85
+ in: { x: 0, y: 0, duration: 0 },
86
+ out: { x: 0, y: 0, duration: 0 }
87
+ };
88
+ }
89
+
75
90
  return {
76
91
  in: {
77
92
  x: inX,
@@ -108,12 +123,22 @@
108
123
  </script>
109
124
 
110
125
  {#if showToast}
126
+ <!-- `showToast` starts `true` (see the `$state(true)` above), so this
127
+ `{#if}` is already satisfied on the component's own first render -- a
128
+ *local* transition only plays when its enclosing block flips true as a
129
+ later reactive update, not when the block is created already-true,
130
+ which is what happens every time a caller mounts Toast fresh (e.g.
131
+ `{#if visible}<Toast .../>{/if}` in the caller). `in:fly|global` plays
132
+ the intro on creation regardless, matching Sheet's panel transition
133
+ (Sheet.svelte). `out:fly` needs no such modifier: `hideToast()` always
134
+ flips an already-mounted block from true to false, a genuine local
135
+ transition that already fires correctly. -->
111
136
  <div
112
137
  class={rootClass}
113
138
  class:no-page-overlap={!overlapPage}
114
139
  role="alert"
115
140
  aria-live="assertive"
116
- in:fly={animationConfig.in}
141
+ in:fly|global={animationConfig.in}
117
142
  out:fly={animationConfig.out}
118
143
  onoutroend={handleAnimationEnd}
119
144
  data-pw={testId}
@@ -162,7 +187,7 @@
162
187
  padding: var(--toast-padding, 10px);
163
188
  font-size: var(--toast-font-size, 14px);
164
189
  font-family: var(--toast-font-family, inherit);
165
- font-weight: var(--toast-font-weight);
190
+ font-weight: var(--toast-font-weight, inherit);
166
191
  height: var(--toast-height, fit-content);
167
192
  border-radius: var(--toast-border-radius, var(--radius, 4px));
168
193
  border: var(--toast-border, none);
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">
2
+ import { describeField } from '../_field/description';
2
3
  import type { ToggleProperties } from './properties';
3
4
 
4
5
  let {
5
- checked = false,
6
+ checked = $bindable(false),
6
7
  text = '',
7
8
  disabled = false,
8
9
  testId,
@@ -12,11 +13,26 @@
12
13
  ariaLabelledby,
13
14
  onclick,
14
15
  name,
15
- value
16
+ value,
17
+ required = false,
18
+ form,
19
+ errorMessage,
20
+ infoMessage,
21
+ invalid = false
16
22
  }: ToggleProperties = $props();
17
23
 
18
24
  // Stable across hydration so the hidden input stays named and text remains clickable.
19
25
  const generatedId = $props.id();
26
+
27
+ /* The control and the text that explains it were never linked: a screen-reader
28
+ user reaching this control heard its name and nothing about why it was
29
+ rejected. `describeField` composes the reference from the messages that are
30
+ actually rendered, so aria-describedby never points at an id that is not in
31
+ the DOM -- which passes an attribute assertion and resolves to nothing in a
32
+ real reader. */
33
+ const field = $derived(
34
+ describeField(generatedId, { error: errorMessage, info: infoMessage, invalid })
35
+ );
20
36
  const inputId = $derived(id?.trim() || `toggle-${generatedId}`);
21
37
 
22
38
  const handleCheckboxClick = (e: MouseEvent): void => {
@@ -40,10 +56,16 @@
40
56
  id={inputId}
41
57
  class="input-checkbox"
42
58
  type="checkbox"
59
+ aria-describedby={field.describedBy}
60
+ aria-invalid={field.ariaInvalid}
43
61
  {checked}
44
62
  {disabled}
45
- name={name ?? null}
46
63
  value={value ?? null}
64
+ {required}
65
+ name={typeof name === 'string' ? name : null}
66
+ form={typeof form === 'string' ? form : null}
67
+ data-state={checked ? 'checked' : 'unchecked'}
68
+ data-disabled={disabled ? '' : null}
47
69
  aria-label={ariaLabel?.trim() || null}
48
70
  aria-labelledby={ariaLabelledby?.trim() || null}
49
71
  onclick={handleCheckboxClick}
@@ -52,6 +74,28 @@
52
74
  </label>
53
75
  </div>
54
76
 
77
+ {#if field.showsError}
78
+ <div
79
+ id={field.errorId}
80
+ role="alert"
81
+ class="field-error"
82
+ data-pw={typeof testId === 'string' ? `${testId}-error-message` : null}
83
+ testID={typeof testId === 'string' ? `${testId}-error-message` : null}
84
+ >
85
+ {errorMessage}
86
+ </div>
87
+ {/if}
88
+ {#if field.showsInfo}
89
+ <div
90
+ id={field.infoId}
91
+ class="field-info"
92
+ data-pw={typeof testId === 'string' ? `${testId}-info-message` : null}
93
+ testID={typeof testId === 'string' ? `${testId}-info-message` : null}
94
+ >
95
+ {infoMessage}
96
+ </div>
97
+ {/if}
98
+
55
99
  <style>
56
100
  .container {
57
101
  display: var(--toggle-container-display, flex);
@@ -94,8 +138,16 @@
94
138
  right: var(--toggle-slider-right, 0);
95
139
  bottom: var(--toggle-slider-bottom, 0);
96
140
  background-color: var(--slider-unchecked-color, #ccc);
97
- -webkit-transition: var(--toggle-slider-transition, 0.4s);
98
- transition: var(--toggle-slider-transition, 0.4s);
141
+ -webkit-transition: var(
142
+ --toggle-slider-transition,
143
+ all var(--toggle-slider-transition-duration, var(--motion-duration, 0.4s))
144
+ var(--toggle-slider-transition-easing, var(--motion-easing, ease))
145
+ );
146
+ transition: var(
147
+ --toggle-slider-transition,
148
+ all var(--toggle-slider-transition-duration, var(--motion-duration, 0.4s))
149
+ var(--toggle-slider-transition-easing, var(--motion-easing, ease))
150
+ );
99
151
  }
100
152
 
101
153
  .slider:before {
@@ -107,8 +159,16 @@
107
159
  bottom: var(--toggle-slider-before-bottom, 1px);
108
160
  top: var(--toggle-slider-before-top, 1px);
109
161
  background-color: var(--toggle-slider-before-background-color, white);
110
- -webkit-transition: var(--toggle-slider-transition, 0.4s);
111
- transition: var(--toggle-slider-transition, 0.4s);
162
+ -webkit-transition: var(
163
+ --toggle-slider-transition,
164
+ all var(--toggle-slider-transition-duration, var(--motion-duration, 0.4s))
165
+ var(--toggle-slider-transition-easing, var(--motion-easing, ease))
166
+ );
167
+ transition: var(
168
+ --toggle-slider-transition,
169
+ all var(--toggle-slider-transition-duration, var(--motion-duration, 0.4s))
170
+ var(--toggle-slider-transition-easing, var(--motion-easing, ease))
171
+ );
112
172
  }
113
173
 
114
174
  .input-checkbox:checked + .slider {
@@ -132,4 +192,16 @@
132
192
  .slider.round:before {
133
193
  border-radius: var(--slider-border-radius-before, 50%);
134
194
  }
195
+
196
+ .field-error {
197
+ color: var(--field-error-color, #c5120a);
198
+ font-size: var(--field-error-font-size, 12px);
199
+ margin: var(--field-error-margin, 4px 0 0 0);
200
+ }
201
+
202
+ .field-info {
203
+ color: var(--field-info-color, #6b7280);
204
+ font-size: var(--field-info-font-size, 12px);
205
+ margin: var(--field-info-margin, 4px 0 0 0);
206
+ }
135
207
  </style>
@@ -1,4 +1,4 @@
1
1
  import type { ToggleProperties } from './properties';
2
- declare const Toggle: import("svelte").Component<ToggleProperties, {}, "">;
2
+ declare const Toggle: import("svelte").Component<ToggleProperties, {}, "checked">;
3
3
  type Toggle = ReturnType<typeof Toggle>;
4
4
  export default Toggle;
@@ -1,6 +1,23 @@
1
1
  export type ToggleProperties = OptionalToggleProperties & ToggleEventProperties;
2
2
  export type OptionalToggleProperties = {
3
+ /**
4
+ * Text shown, and announced, when the control is in error. Linked to the
5
+ * control through `aria-describedby`, and sets `aria-invalid` while present.
6
+ */
7
+ errorMessage?: string | null;
8
+ /**
9
+ * Persistent helper text describing the control. Linked through
10
+ * `aria-describedby` too, so it is read before the user trips an error rather
11
+ * than only after.
12
+ */
13
+ infoMessage?: string | null;
14
+ /**
15
+ * Marks the control invalid without supplying a message, for a consumer
16
+ * driving validity from a server or its own rules.
17
+ */
18
+ invalid?: boolean;
3
19
  text?: string;
20
+ /** Whether the switch is on. Bindable, like `Checkbox.checked` and `Slider.value`. */
4
21
  checked?: boolean;
5
22
  disabled?: boolean;
6
23
  testId?: string;
@@ -19,15 +36,21 @@ export type OptionalToggleProperties = {
19
36
  * `text` or `inputAriaLabel` for labels outside the shadow root.
20
37
  */
21
38
  ariaLabelledby?: string;
22
- /** Native `name` on the underlying `<input type="checkbox">`, so the switch
23
- * participates in a surrounding `<form>`'s submission. Omitted by default:
24
- * the input stays nameless and outside `FormData`, exactly as before this
25
- * prop existed. */
39
+ /** Submits the switch under this name, so it participates in a surrounding
40
+ * `<form>`'s submission. Omitted by default: with no `name` the input stays
41
+ * nameless and outside `FormData`, exactly as before this prop existed. Omitted
42
+ * from submission too when off or disabled, matching the native checkbox the
43
+ * switch is built from. */
26
44
  name?: string;
27
- /** Native `value` submitted when on. Defaults to `"on"` — the native
28
- * checkbox default — when omitted. An off toggle never appears in
29
- * `FormData`, matching native behaviour. */
45
+ /** Value submitted while on. Left unset, the attribute is omitted and the native
46
+ * checkbox's own `"on"` default is what submits, so setting `name` alone is enough and
47
+ * an existing consumer's DOM is unchanged. An off switch never appears in `FormData`. */
30
48
  value?: string;
49
+ /** Blocks submission while the switch is off. */
50
+ required?: boolean;
51
+ /** `id` of a form elsewhere in the same document. Unavailable through
52
+ * `<sui-toggle>`, whose control sits in a shadow root. */
53
+ form?: string;
31
54
  };
32
55
  export type ToggleEventProperties = {
33
56
  onclick?: (checked: boolean) => void;
@@ -4,9 +4,10 @@
4
4
  import Loader from '../Loader/Loader.svelte';
5
5
  import Pill from '../Pill/Pill.svelte';
6
6
  import { computeMenuDropdownPosition } from '../Menu/dropdownPosition';
7
+ import { eventHitsInside } from '../_interaction/dismissal';
7
8
  import type { ToolCallChip, ToolCallLogProperties } from './properties';
8
9
 
9
- let { chips, onchipclick, testId, classes }: ToolCallLogProperties = $props();
10
+ let { chips, onchipclick, testId, classes, usePortal = true }: ToolCallLogProperties = $props();
10
11
 
11
12
  // Exactly one popover open at a time, component-local.
12
13
  let openIndex = $state<number | null>(null);
@@ -97,16 +98,37 @@
97
98
  });
98
99
 
99
100
  /**
100
- * Svelte action for the popover element: relocates it to `document.body`
101
+ * Where a portalled node is allowed to land. Svelte scopes a custom element's
102
+ * CSS to its shadow root, so `document.body` is the one place the popover must
103
+ * never go from inside `<sui-tool-call-log>`: it keeps its `svelte-*` scoping
104
+ * class and loses every rule behind it, plus the host's custom properties.
105
+ * Measured in Chromium -- position fell back to `static`, background to
106
+ * transparent, border and shadow to none, so the detail text landed as a bare
107
+ * paragraph at the end of the page. Its own root holds that stylesheet and
108
+ * still sits above every overflow/scroll ancestor between the two.
109
+ */
110
+ const portalTarget = (node: Node): Node => {
111
+ const root = node.getRootNode();
112
+ return root instanceof ShadowRoot ? root : document.body;
113
+ };
114
+
115
+ /**
116
+ * Svelte action for the popover element: relocates it out to its root
101
117
  * (mirroring Menu's `usePortal`) so an `overflow: hidden` or scrolling
102
118
  * ancestor — a chat bubble, a card — can never clip it, and keeps
103
119
  * `popoverTick` bumping (rAF-coalesced) while it is mounted so `popoverStyle`
104
120
  * re-derives on scroll/resize. The popover only exists in the DOM while open
105
121
  * (see the `{#if}` below), so the listeners live exactly as long as it does;
106
122
  * `use:` actions never run during SSR.
123
+ *
124
+ * The listeners are wired even when `usePortal` is false: the popover is
125
+ * `position: fixed` in both modes, so its coordinates go stale on scroll
126
+ * whether or not it was moved.
107
127
  */
108
128
  const popoverPortal: Action<HTMLElement> = (node) => {
109
- document.body.appendChild(node);
129
+ if (usePortal) {
130
+ portalTarget(node).appendChild(node);
131
+ }
110
132
  let frame: number | null = null;
111
133
  const bump = (): void => {
112
134
  if (frame !== null) {
@@ -126,7 +148,11 @@
126
148
  if (frame !== null) {
127
149
  cancelAnimationFrame(frame);
128
150
  }
129
- node.remove();
151
+ // Only a node this action moved is this action's to take back out; an
152
+ // un-portalled one is still Svelte's, and it removes it on its own.
153
+ if (usePortal) {
154
+ node.remove();
155
+ }
130
156
  }
131
157
  };
132
158
  };
@@ -138,14 +164,21 @@
138
164
  * `openIndex`, and closing here too would race it: this listener runs after
139
165
  * the chip's own `onclick` on the same bubbling click event, so without the
140
166
  * guard it would immediately re-close a popover a click just opened.
167
+ *
168
+ * "Inside" is asked of the composed path, not of `contains(event.target)`.
169
+ * This listener is on the document, which sees the target retargeted to the
170
+ * shadow host, so `chipEl.contains(target)` was false for a click on the chip
171
+ * itself and the guard above never held: through `<sui-tool-call-log>` the
172
+ * popover opened and closed on the same click, every time. Observed under
173
+ * a MutationObserver — one insert, one removal, `aria-expanded` back to
174
+ * "false" — while chasing the portal fix this file also carries.
141
175
  */
142
176
  const handleDocumentClick = (event: MouseEvent): void => {
143
- const target = event.target;
144
- if (openIndex === null || !(target instanceof Node)) {
177
+ if (openIndex === null) {
145
178
  return;
146
179
  }
147
- const clickedInsideChip = chipEls.some((chipEl) => chipEl !== null && chipEl.contains(target));
148
- const clickedInsidePopover = popoverEl !== null && popoverEl.contains(target);
180
+ const clickedInsideChip = chipEls.some((chipEl) => eventHitsInside(chipEl, event));
181
+ const clickedInsidePopover = eventHitsInside(popoverEl, event);
149
182
  if (!clickedInsideChip && !clickedInsidePopover) {
150
183
  openIndex = null;
151
184
  }
@@ -274,11 +307,14 @@
274
307
  color: var(--tool-call-log-label-color, #2b2b2b);
275
308
  cursor: var(--tool-call-log-chip-cursor, pointer);
276
309
  max-width: 100%;
277
- animation: tool-call-log-fade-up 320ms var(--tool-call-log-ease, cubic-bezier(0.23, 1, 0.32, 1))
278
- both;
310
+ animation: tool-call-log-fade-up
311
+ var(--tool-call-log-chip-animation-duration, var(--motion-duration, 320ms))
312
+ var(--tool-call-log-ease, var(--motion-easing, cubic-bezier(0.23, 1, 0.32, 1))) both;
279
313
  transition:
280
- background 150ms ease,
281
- border-color 150ms ease;
314
+ background var(--tool-call-log-chip-transition-duration, var(--motion-duration, 150ms))
315
+ var(--tool-call-log-chip-transition-easing, var(--motion-easing, ease)),
316
+ border-color var(--tool-call-log-chip-transition-duration, var(--motion-duration, 150ms))
317
+ var(--tool-call-log-chip-transition-easing, var(--motion-easing, ease));
282
318
  }
283
319
  .chip:hover {
284
320
  background: var(--tool-call-log-chip-hover-background, #f1f1f1);
@@ -288,7 +324,7 @@
288
324
  }
289
325
 
290
326
  .chip.error {
291
- color: var(--tool-call-log-error-color, #c93f38);
327
+ color: var(--tool-call-log-error-color, #9f2d27);
292
328
  border-color: var(--tool-call-log-error-border-color, #f2b8b5);
293
329
  background: var(--tool-call-log-error-background, #fdf1f0);
294
330
  }
@@ -304,7 +340,7 @@
304
340
  }
305
341
 
306
342
  .chip-meta {
307
- color: var(--tool-call-log-meta-color, #9a9a9a);
343
+ color: var(--tool-call-log-meta-color, #666666);
308
344
  font-size: var(--tool-call-log-meta-font-size, 0.75rem);
309
345
  white-space: nowrap;
310
346
  overflow: hidden;
@@ -366,13 +402,14 @@
366
402
  }
367
403
  .diffstat-removed {
368
404
  --pill-background: var(--tool-call-log-removed-background, #fbeceb);
369
- --pill-color: var(--tool-call-log-removed-color, #c93f38);
405
+ --pill-color: var(--tool-call-log-removed-color, #a8302a);
370
406
  }
371
407
 
372
- /* Always portaled to document.body (see `popoverPortal`), so `position:
373
- fixed` is the baseline rather than a portal-only override — left/top/
374
- z-index come from the inline `popoverStyle`, computed against the live
375
- anchor rect. */
408
+ /* `position: fixed` is the baseline rather than a portal-only override —
409
+ left/top/z-index come from the inline `popoverStyle`, computed against the
410
+ live anchor rect, and that is viewport-relative in both portal modes. So
411
+ `usePortal={false}` (see `popoverPortal`) changes only which ancestors can
412
+ clip the popover, never how it is placed. */
376
413
  .chip-popover {
377
414
  position: fixed;
378
415
  z-index: var(--tool-call-log-popover-z-index, 1000);
@@ -386,8 +423,9 @@
386
423
  border: var(--tool-call-log-popover-border, 1px solid #e4e4e7);
387
424
  border-radius: var(--tool-call-log-popover-radius, 10px);
388
425
  box-shadow: var(--tool-call-log-popover-shadow, 0 10px 30px rgba(0, 0, 0, 0.12));
389
- animation: tool-call-log-fade-up 200ms var(--tool-call-log-ease, cubic-bezier(0.23, 1, 0.32, 1))
390
- both;
426
+ animation: tool-call-log-fade-up
427
+ var(--tool-call-log-popover-animation-duration, var(--motion-duration, 200ms))
428
+ var(--tool-call-log-ease, var(--motion-easing, cubic-bezier(0.23, 1, 0.32, 1))) both;
391
429
  }
392
430
 
393
431
  .popover-detail {
@@ -24,4 +24,14 @@ export type OptionalToolCallLogProperties = {
24
24
  onchipclick?: (index: number, chip: ToolCallChip) => void;
25
25
  testId?: string;
26
26
  classes?: string;
27
+ /**
28
+ * Move the open detail popover out to the root of the tree it lives in, so an
29
+ * `overflow: hidden` or scrolling ancestor between the two — a chat bubble, a
30
+ * card — cannot clip it. Defaults to true, which is what this component did
31
+ * unconditionally before the prop existed. Placement is identical either way
32
+ * (the popover is `position: fixed` in both modes), so turning it off costs
33
+ * only the clipping escape — worth it when the popover must stay inside a
34
+ * container that is itself being transformed or scrolled as a unit.
35
+ */
36
+ usePortal?: boolean;
27
37
  };
@@ -211,7 +211,7 @@
211
211
  font-weight: var(--toolbar-text-font-weight, normal);
212
212
  padding: var(--toolbar-text-padding, 0px);
213
213
  margin: var(--toolbar-text-margin, 0px);
214
- color: var(--toolbar-text-color);
214
+ color: var(--toolbar-text-color, inherit);
215
215
  flex: var(--toolbar-text-flex, 1);
216
216
  }
217
217
  </style>
@@ -93,6 +93,24 @@
93
93
  /** Reference to the trigger wrapper element, used for `getBoundingClientRect` in portal mode. */
94
94
  let containerEl: HTMLDivElement | null = $state(null);
95
95
 
96
+ /**
97
+ * Where the portalled bubble is allowed to land, asked of the TRIGGER rather
98
+ * than the bubble: a freshly created, still-detached element is its own root,
99
+ * so it can answer nothing useful about where the component lives.
100
+ *
101
+ * The bubble's own box survives `document.body` -- every rule of it is inline
102
+ * (see `createPortalBubble`) -- but two things it carries do not. The
103
+ * `--tooltip-*` custom properties those inline rules read inherit from the
104
+ * shadow host, not from `<body>`, so a themed `<sui-tooltip>` reverts to the
105
+ * literal fallbacks; and a `content` snippet is mounted into the bubble with
106
+ * the consumer's own `svelte-*` scoping classes, whose stylesheet is in the
107
+ * shadow root the bubble just left. Its own root keeps both.
108
+ */
109
+ const portalTarget = (node: Node): Node => {
110
+ const root = node.getRootNode();
111
+ return root instanceof ShadowRoot ? root : document.body;
112
+ };
113
+
96
114
  /**
97
115
  * Imperatively managed portal bubble element.
98
116
  * Created in `showTooltip` and removed in `hideTooltip` when `usePortal=true`.
@@ -303,7 +321,7 @@
303
321
  }
304
322
  portalBubbleEl = createPortalBubble(rect, pos);
305
323
  if (portalBubbleEl !== null) {
306
- document.body.appendChild(portalBubbleEl);
324
+ portalTarget(containerEl).appendChild(portalBubbleEl);
307
325
  const effectivePos = flipPortalBubbleIfNeeded(portalBubbleEl, rect, pos);
308
326
  clampPortalBubble(portalBubbleEl, effectivePos);
309
327
  }
@@ -405,7 +423,7 @@
405
423
  color: var(--tooltip-color, #ffffff);
406
424
  font-size: var(--tooltip-font-size, 12px);
407
425
  font-weight: var(--tooltip-font-weight, 400);
408
- font-family: var(--tooltip-font-family);
426
+ font-family: var(--tooltip-font-family, inherit);
409
427
  padding: var(--tooltip-padding, 6px 10px);
410
428
  border-radius: var(--tooltip-border-radius, var(--radius, 4px));
411
429
  border: var(--tooltip-border, none);