@juspay/svelte-ui-components 4.27.6 → 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.
- package/README.md +140 -36
- package/dist/Animations/ModalAnimation.svelte +15 -1
- package/dist/Animations/OverlayAnimation.svelte +7 -1
- package/dist/AreaChart/AreaChart.svelte +366 -85
- package/dist/AspectRatio/AspectRatio.svelte +51 -0
- package/dist/AspectRatio/AspectRatio.svelte.d.ts +3 -0
- package/dist/AspectRatio/properties.d.ts +27 -0
- package/dist/AspectRatio/properties.js +1 -0
- package/dist/Banner/Banner.svelte +10 -4
- package/dist/Banner/properties.d.ts +8 -0
- package/dist/BarChart/BarChart.svelte +156 -49
- package/dist/Book/Book.svelte +14 -1
- package/dist/BrandLoader/BrandLoader.svelte +76 -12
- package/dist/Button/Button.svelte +56 -11
- package/dist/Calendar/Calendar.svelte +88 -61
- package/dist/Card/Card.svelte +1 -1
- package/dist/Carousel/Carousel.svelte +6 -3
- package/dist/ChatComposer/ChatComposer.svelte +54 -4
- package/dist/ChatComposer/dictationState.d.ts +15 -0
- package/dist/ChatComposer/dictationState.js +9 -0
- package/dist/ChatComposer/properties.d.ts +28 -1
- package/dist/ChatMessage/ChatMessage.svelte +27 -1
- package/dist/CheckListItem/CheckListItem.svelte +3 -3
- package/dist/Checkbox/Checkbox.svelte +84 -3
- package/dist/Checkbox/properties.d.ts +45 -13
- package/dist/ChipInput/ChipInput.svelte +70 -3
- package/dist/ChipInput/properties.d.ts +16 -0
- package/dist/Choicebox/Choicebox.svelte +156 -1
- package/dist/Choicebox/group.d.ts +63 -0
- package/dist/Choicebox/group.js +127 -0
- package/dist/Choicebox/properties.d.ts +46 -0
- package/dist/ColorPicker/ColorPicker.svelte +169 -20
- package/dist/ColorPicker/properties.d.ts +16 -0
- package/dist/Combobox/Combobox.svelte +87 -25
- package/dist/Combobox/properties.d.ts +23 -0
- package/dist/CommandMenu/CommandMenu.svelte +27 -11
- package/dist/ContextMenu/ContextMenu.svelte +31 -37
- package/dist/DateRangePicker/DateRangePicker.svelte +171 -73
- package/dist/DeltaIndicator/DeltaIndicator.svelte +3 -3
- package/dist/Draggable/Draggable.svelte +12 -7
- package/dist/DualAxisBarChart/DualAxisBarChart.svelte +11 -4
- package/dist/EmptyState/EmptyState.svelte +1 -1
- package/dist/FileDropzoneTrigger/FileDropzoneTrigger.svelte +1 -1
- package/dist/FileInput/FileInput.svelte +50 -1
- package/dist/FileInput/properties.d.ts +7 -0
- package/dist/FunnelChart/FunnelChart.svelte +40 -12
- package/dist/FunnelChart/properties.d.ts +7 -1
- package/dist/Gallery/Gallery.svelte +20 -13
- package/dist/Gallery/properties.d.ts +8 -0
- package/dist/GridItem/GridItem.svelte +31 -1
- package/dist/HITL/HITL.svelte +35 -9
- package/dist/Icon/Icon.svelte +19 -3
- package/dist/Input/Input.svelte +24 -10
- package/dist/Input/properties.d.ts +37 -1
- package/dist/InputButton/InputButton.svelte +86 -11
- package/dist/InputButton/properties.d.ts +10 -1
- package/dist/Label/Label.svelte +54 -0
- package/dist/Label/Label.svelte.d.ts +3 -0
- package/dist/Label/properties.d.ts +26 -0
- package/dist/Label/properties.js +1 -0
- package/dist/LineChart/LineChart.svelte +385 -94
- package/dist/ListItem/ListItem.svelte +121 -15
- package/dist/Loader/Loader.svelte +22 -2
- package/dist/LoadingDots/LoadingDots.svelte +21 -0
- package/dist/MediaUpload/MediaUpload.svelte +13 -3
- package/dist/Menu/Menu.svelte +56 -28
- package/dist/Modal/Modal.svelte +77 -42
- package/dist/Pagination/Pagination.svelte +9 -2
- package/dist/Phone/Phone.svelte +4 -0
- package/dist/PieChart/PieChart.svelte +282 -38
- package/dist/PieChart/properties.d.ts +9 -0
- package/dist/Pill/Pill.svelte +14 -5
- package/dist/Progress/Progress.svelte +20 -0
- package/dist/Radio/Radio.svelte +58 -1
- package/dist/Radio/properties.d.ts +23 -0
- package/dist/RatingGroup/RatingGroup.svelte +283 -0
- package/dist/RatingGroup/RatingGroup.svelte.d.ts +4 -0
- package/dist/RatingGroup/properties.d.ts +73 -0
- package/dist/RatingGroup/properties.js +1 -0
- package/dist/RelativeTime/RelativeTime.svelte +1 -1
- package/dist/SankeyChart/SankeyChart.svelte +272 -50
- package/dist/SankeyChart/properties.d.ts +9 -0
- package/dist/Scroller/Scroller.svelte +37 -2
- package/dist/Select/Select.svelte +166 -46
- package/dist/Select/properties.d.ts +7 -0
- package/dist/Separator/Separator.svelte +48 -0
- package/dist/Separator/Separator.svelte.d.ts +3 -0
- package/dist/Separator/properties.d.ts +22 -0
- package/dist/Separator/properties.js +1 -0
- package/dist/Sheet/Sheet.svelte +60 -21
- package/dist/Shimmer/Shimmer.svelte +15 -0
- package/dist/Slider/Slider.svelte +87 -6
- package/dist/Slider/properties.d.ts +26 -0
- package/dist/Snippet/Snippet.svelte +4 -0
- package/dist/Snippet/copyState.svelte.d.ts +13 -1
- package/dist/Snippet/copyState.svelte.js +27 -1
- package/dist/Snippet/properties.d.ts +7 -0
- package/dist/SoundKit/SoundKit.js +9 -8
- package/dist/SplitInput/SplitInput.svelte +68 -2
- package/dist/SplitInput/properties.d.ts +23 -0
- package/dist/StatCard/StatCard.svelte +6 -6
- package/dist/Status/Status.svelte +2 -2
- package/dist/Stepper/Step.svelte +19 -6
- package/dist/Stepper/Stepper.svelte +108 -35
- package/dist/Table/BuiltinCell.svelte +2 -2
- package/dist/Table/Table.svelte +373 -55
- package/dist/Table/properties.d.ts +134 -3
- package/dist/Tabs/Tabs.svelte +263 -65
- package/dist/Tabs/properties.d.ts +23 -0
- package/dist/TaskList/TaskList.svelte +14 -5
- package/dist/ThemeSwitcher/ThemeSwitcher.svelte +23 -0
- package/dist/ThinkingIndicator/ThinkingIndicator.svelte +13 -5
- package/dist/Toast/Toast.svelte +27 -2
- package/dist/Toggle/Toggle.svelte +79 -7
- package/dist/Toggle/Toggle.svelte.d.ts +1 -1
- package/dist/Toggle/properties.d.ts +30 -7
- package/dist/ToolCallLog/ToolCallLog.svelte +59 -21
- package/dist/ToolCallLog/properties.d.ts +10 -0
- package/dist/Toolbar/Toolbar.svelte +1 -1
- package/dist/Tooltip/Tooltip.svelte +20 -2
- package/dist/Tooltip/tooltip-action.js +39 -4
- package/dist/TypewriterText/TypewriterText.svelte +34 -2
- package/dist/TypewriterText/properties.d.ts +18 -5
- package/dist/_chart/ChartContainer.svelte +12 -0
- package/dist/_chart/ChartTooltip.svelte +18 -4
- package/dist/_chart/Legend.svelte +21 -0
- package/dist/_chart/Legend.svelte.d.ts +15 -0
- package/dist/_chart/geometry.d.ts +54 -1
- package/dist/_chart/geometry.js +122 -14
- package/dist/_chart/interactions.d.ts +5 -1
- package/dist/_chart/interactions.js +9 -5
- package/dist/_chart/scales.js +16 -0
- package/dist/_chart/temporal.d.ts +45 -0
- package/dist/_chart/temporal.js +116 -0
- package/dist/_field/description.d.ts +50 -0
- package/dist/_field/description.js +51 -0
- package/dist/_interaction/dismissal.d.ts +67 -0
- package/dist/_interaction/dismissal.js +137 -0
- package/dist/_interaction/focus.d.ts +73 -0
- package/dist/_interaction/focus.js +143 -0
- package/dist/assets/star.svg +5 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +21 -1
- package/dist/reduced-motion.svelte.d.ts +37 -0
- package/dist/reduced-motion.svelte.js +44 -0
- package/dist/styles/theme-dark.css +778 -0
- package/dist/types.d.ts +4 -2
- package/dist/utils.d.ts +62 -1
- package/dist/utils.js +80 -21
- package/dist-codemod/{bin.js → codemod/bin.js} +13 -2
- package/dist-codemod/{cli.js → codemod/cli.js} +48 -3
- package/dist-codemod/codemod/migrate-audit.js +395 -0
- package/dist-codemod/codemod/migration-assets/legacy-display.css +120 -0
- package/dist-codemod/codemod/migration-assets/legacy-palette.css +106 -0
- package/dist-codemod/codemod/migration-assets/wc-display.json +492 -0
- package/dist-codemod/migrate/analyze.js +804 -0
- package/dist-codemod/migrate/chart-tooltip-selector.js +301 -0
- package/dist-wc/index.js +13992 -4323
- package/package.json +13 -6
- package/dist/Modal/focus-trap.d.ts +0 -42
- package/dist/Modal/focus-trap.js +0 -57
- /package/dist-codemod/{legacy-pairs.js → codemod/legacy-pairs.js} +0 -0
- /package/dist-codemod/{transform.js → codemod/transform.js} +0 -0
- /package/dist-codemod/{wc-children.js → codemod/wc-children.js} +0 -0
|
@@ -140,19 +140,19 @@
|
|
|
140
140
|
|
|
141
141
|
.variant-secondary {
|
|
142
142
|
--_btn-color: transparent;
|
|
143
|
-
--_btn-text-color: #3a4550;
|
|
144
|
-
--_btn-border: 1px solid #cbd5e1;
|
|
145
|
-
--_btn-hover-color: #f1f5f9;
|
|
146
|
-
--_btn-hover-text-color: #3a4550;
|
|
147
|
-
--_btn-hover-border: 1px solid #cbd5e1;
|
|
143
|
+
--_btn-text-color: var(--button-secondary-text-color, #3a4550);
|
|
144
|
+
--_btn-border: 1px solid var(--button-secondary-border-color, #cbd5e1);
|
|
145
|
+
--_btn-hover-color: var(--button-secondary-hover-background, #f1f5f9);
|
|
146
|
+
--_btn-hover-text-color: var(--button-secondary-text-color, #3a4550);
|
|
147
|
+
--_btn-hover-border: 1px solid var(--button-secondary-border-color, #cbd5e1);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
.variant-ghost {
|
|
151
151
|
--_btn-color: transparent;
|
|
152
|
-
--_btn-text-color: #3a4550;
|
|
152
|
+
--_btn-text-color: var(--button-ghost-text-color, #3a4550);
|
|
153
153
|
--_btn-border: none;
|
|
154
|
-
--_btn-hover-color: #f1f5f9;
|
|
155
|
-
--_btn-hover-text-color: #3a4550;
|
|
154
|
+
--_btn-hover-color: var(--button-ghost-hover-background, #f1f5f9);
|
|
155
|
+
--_btn-hover-text-color: var(--button-ghost-text-color, #3a4550);
|
|
156
156
|
--_btn-hover-border: none;
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -224,7 +224,10 @@
|
|
|
224
224
|
would reset a consumer's own background-image/position/size layers
|
|
225
225
|
whenever the hook is unset. Gradients go here; solids stay on --button-color. */
|
|
226
226
|
background-image: var(--button-background, none);
|
|
227
|
-
color: var(
|
|
227
|
+
color: var(
|
|
228
|
+
--button-text-color,
|
|
229
|
+
var(--_btn-text-color, var(--button-neutral-text-color, white))
|
|
230
|
+
);
|
|
228
231
|
height: var(--button-height, fit-content);
|
|
229
232
|
padding: var(--button-padding, var(--_btn-padding, 16px));
|
|
230
233
|
margin: var(--button-margin);
|
|
@@ -256,8 +259,8 @@
|
|
|
256
259
|
cursor: var(--disabled-cursor, not-allowed);
|
|
257
260
|
opacity: var(--disabled-opacity, 0.4);
|
|
258
261
|
color: var(--disabled-text-color, var(--button-text-color, var(--_btn-text-color, white)));
|
|
259
|
-
font-size: var(--disabled-font-size);
|
|
260
|
-
font-weight: var(--disabled-font-weight);
|
|
262
|
+
font-size: var(--disabled-font-size, inherit);
|
|
263
|
+
font-weight: var(--disabled-font-weight, inherit);
|
|
261
264
|
text-decoration: var(--button-disabled-text-decoration, var(--button-text-decoration, none));
|
|
262
265
|
/* Preserve the variant border when disabled so secondary (bordered) stays distinct from ghost. */
|
|
263
266
|
border: var(--disabled-border, var(--button-border, var(--_btn-border, none)));
|
|
@@ -342,4 +345,46 @@
|
|
|
342
345
|
width: 100%;
|
|
343
346
|
}
|
|
344
347
|
}
|
|
348
|
+
|
|
349
|
+
/* This bar is not indefinite like a spinner -- its width IS the meaning:
|
|
350
|
+
"how much of `--button-progress-loader-duration` has elapsed", and
|
|
351
|
+
`showProgressBar` keeps the button disabled for that whole span. A bare
|
|
352
|
+
`animation: none` would freeze it at the base rule above (`width: 100%`),
|
|
353
|
+
showing a FULL, "finished" bar for up to 8s while the button is actually
|
|
354
|
+
still counting down and still disabled -- a frame that means something
|
|
355
|
+
else is worse than the motion it removed. Freezing at 0% instead would
|
|
356
|
+
lie the other way once the real duration elapses and the bar should read
|
|
357
|
+
complete. Neither static width is honest for the whole span, so motion is
|
|
358
|
+
thinned rather than removed: `steps()` swaps the continuous ease for
|
|
359
|
+
discrete jumps that hold the PRIOR interval's value until each jump (the
|
|
360
|
+
`jump-end` default), so the displayed width can only ever under-report
|
|
361
|
+
elapsed time -- never claim to be further along than it really is -- while
|
|
362
|
+
still losing the smooth per-frame slide prefers-reduced-motion targets.
|
|
363
|
+
The step count is a LITERAL, deliberately not a token. `steps()` is invalid
|
|
364
|
+
for a count below 1, and an invalid value does not fall back to a safer
|
|
365
|
+
stepped default -- the whole declaration is dropped and the property takes
|
|
366
|
+
the base rule's `ease`, restoring exactly the continuous slide this block
|
|
367
|
+
exists to remove. A consumer passing `0` to "just freeze it" would silently
|
|
368
|
+
switch the guard off for every user who asked for reduced motion. A guard
|
|
369
|
+
that fails OPEN, quietly, is worse than no guard, and one tunable is not
|
|
370
|
+
worth that.
|
|
371
|
+
This block lives in the component's own <style> because that is the only
|
|
372
|
+
stylesheet that reaches inside the shadow root a custom-element consumer
|
|
373
|
+
gets. */
|
|
374
|
+
@media (prefers-reduced-motion: reduce) {
|
|
375
|
+
.button-progress-bar {
|
|
376
|
+
animation-timing-function: steps(5, jump-end);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* The loader is not the only motion here. `--button-transition` defaults to
|
|
380
|
+
`none`, so this rule does nothing until a consumer opts in -- and the
|
|
381
|
+
documented way to opt in is exactly a hover/press animation, which is
|
|
382
|
+
what a user asking for reduced motion is asking not to have. The sweep
|
|
383
|
+
that covered every transition in ThemeSwitcher stopped at the one
|
|
384
|
+
animation the brief named in this file. */
|
|
385
|
+
.button-el,
|
|
386
|
+
.button-container {
|
|
387
|
+
transition: none;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
345
390
|
</style>
|
|
@@ -148,6 +148,21 @@
|
|
|
148
148
|
return days;
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
// A calendar grid is one tab stop, the same roving-tabindex pattern Tabs uses: the
|
|
152
|
+
// day being actively navigated while inside the grid, else the selected day, else
|
|
153
|
+
// today if it's in view and enabled, else the first enabled day -- so a month with
|
|
154
|
+
// no selection, or whose selection has scrolled out of view, is still reachable by
|
|
155
|
+
// Tab. A fully disabled month in view resolves to null, and the grid container
|
|
156
|
+
// becomes the fallback tab stop so the calendar can still be tabbed to.
|
|
157
|
+
const tabStopDay: number | null = $derived(
|
|
158
|
+
focusedDay ??
|
|
159
|
+
calendarDays.find((d) => d.isCurrentMonth && (d.isSelected || d.isRangeStart || d.isRangeEnd))
|
|
160
|
+
?.day ??
|
|
161
|
+
calendarDays.find((d) => d.isCurrentMonth && d.isToday && !d.isDisabled)?.day ??
|
|
162
|
+
calendarDays.find((d) => d.isCurrentMonth && !d.isDisabled)?.day ??
|
|
163
|
+
null
|
|
164
|
+
);
|
|
165
|
+
|
|
151
166
|
function normalizeDate(d: Date): Date {
|
|
152
167
|
const n = new SvelteDate(d);
|
|
153
168
|
n.setHours(0, 0, 0, 0);
|
|
@@ -222,79 +237,79 @@
|
|
|
222
237
|
}
|
|
223
238
|
}
|
|
224
239
|
|
|
225
|
-
function
|
|
226
|
-
|
|
240
|
+
function dateAtDay(year: number, month: number, day: number): Date {
|
|
241
|
+
const date = new SvelteDate(year, month, day);
|
|
242
|
+
date.setHours(0, 0, 0, 0);
|
|
243
|
+
return date;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// disabledDates can be a predicate that disables every day it's asked about, so the
|
|
247
|
+
// walk is capped rather than open-ended -- ~10 years of daily steps is far past any
|
|
248
|
+
// realistic disabled range but still guarantees the keyboard handler terminates
|
|
249
|
+
// instead of hanging when a whole month (or more) is disabled.
|
|
250
|
+
const MAX_DAY_SKIP = 3660;
|
|
251
|
+
|
|
252
|
+
function findEnabledDate(from: Date, stepDays: number): Date | null {
|
|
253
|
+
const candidate = new SvelteDate(from);
|
|
254
|
+
for (let i = 0; i < MAX_DAY_SKIP; i++) {
|
|
255
|
+
candidate.setDate(candidate.getDate() + stepDays);
|
|
256
|
+
candidate.setHours(0, 0, 0, 0);
|
|
257
|
+
if (!isDateDisabled(candidate)) {
|
|
258
|
+
return candidate;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Moves the roving tab stop to `date`, crossing into a different displayed month
|
|
265
|
+
// (by any number of months, since findEnabledDate can skip past several) when the
|
|
266
|
+
// target lands outside the currently visible one.
|
|
267
|
+
function moveFocusTo(date: Date): void {
|
|
268
|
+
const changesMonth =
|
|
269
|
+
date.getFullYear() !== displayDate.getFullYear() ||
|
|
270
|
+
date.getMonth() !== displayDate.getMonth();
|
|
271
|
+
if (changesMonth) {
|
|
272
|
+
displayDate.setFullYear(date.getFullYear(), date.getMonth(), 1);
|
|
273
|
+
onmonthchange?.({ year: displayDate.getFullYear(), month: displayDate.getMonth() });
|
|
274
|
+
}
|
|
275
|
+
focusedDay = date.getDate();
|
|
276
|
+
focusDayCell();
|
|
227
277
|
}
|
|
228
278
|
|
|
229
279
|
function handleKeyDown(event: KeyboardEvent): void {
|
|
230
280
|
const year = displayDate.getFullYear();
|
|
231
281
|
const month = displayDate.getMonth();
|
|
232
|
-
const totalDays = daysInMonth(year, month);
|
|
233
|
-
|
|
234
|
-
if (focusedDay === null) {
|
|
235
|
-
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
|
|
236
|
-
event.preventDefault();
|
|
237
|
-
focusedDay = 1;
|
|
238
|
-
focusDayCell();
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
282
|
|
|
243
283
|
switch (event.key) {
|
|
244
284
|
case 'ArrowRight':
|
|
245
|
-
event.preventDefault();
|
|
246
|
-
if (focusedDay !== null && focusedDay < totalDays) {
|
|
247
|
-
focusedDay++;
|
|
248
|
-
} else if (focusedDay === totalDays) {
|
|
249
|
-
navigateMonth(1);
|
|
250
|
-
focusedDay = 1;
|
|
251
|
-
}
|
|
252
|
-
focusDayCell();
|
|
253
|
-
break;
|
|
254
285
|
case 'ArrowLeft':
|
|
255
|
-
event.preventDefault();
|
|
256
|
-
if (focusedDay !== null && focusedDay > 1) {
|
|
257
|
-
focusedDay--;
|
|
258
|
-
} else if (focusedDay === 1) {
|
|
259
|
-
navigateMonth(-1);
|
|
260
|
-
focusedDay = daysInMonth(displayDate.getFullYear(), displayDate.getMonth());
|
|
261
|
-
}
|
|
262
|
-
focusDayCell();
|
|
263
|
-
break;
|
|
264
286
|
case 'ArrowDown':
|
|
287
|
+
case 'ArrowUp': {
|
|
265
288
|
event.preventDefault();
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const overflow = focusedDay + 7 - totalDays;
|
|
271
|
-
navigateMonth(1);
|
|
272
|
-
focusedDay = overflow;
|
|
273
|
-
}
|
|
289
|
+
// Nothing is focusable in the grid (a fully disabled month with no
|
|
290
|
+
// selection or today in view) -- there's nowhere to move from.
|
|
291
|
+
if (tabStopDay === null) {
|
|
292
|
+
break;
|
|
274
293
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
focusedDay = prevTotal + (currentDay - 7);
|
|
287
|
-
}
|
|
294
|
+
const stepDays =
|
|
295
|
+
event.key === 'ArrowRight'
|
|
296
|
+
? 1
|
|
297
|
+
: event.key === 'ArrowLeft'
|
|
298
|
+
? -1
|
|
299
|
+
: event.key === 'ArrowDown'
|
|
300
|
+
? 7
|
|
301
|
+
: -7;
|
|
302
|
+
const target = findEnabledDate(dateAtDay(year, month, tabStopDay), stepDays);
|
|
303
|
+
if (target !== null) {
|
|
304
|
+
moveFocusTo(target);
|
|
288
305
|
}
|
|
289
|
-
focusDayCell();
|
|
290
306
|
break;
|
|
307
|
+
}
|
|
291
308
|
case 'Enter':
|
|
292
309
|
case ' ':
|
|
293
310
|
event.preventDefault();
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
date.setHours(0, 0, 0, 0);
|
|
297
|
-
selectDate(date);
|
|
311
|
+
if (tabStopDay !== null) {
|
|
312
|
+
selectDate(dateAtDay(year, month, tabStopDay));
|
|
298
313
|
}
|
|
299
314
|
break;
|
|
300
315
|
}
|
|
@@ -338,7 +353,13 @@
|
|
|
338
353
|
{/each}
|
|
339
354
|
</div>
|
|
340
355
|
|
|
341
|
-
<div
|
|
356
|
+
<div
|
|
357
|
+
class="grid"
|
|
358
|
+
bind:this={gridRef}
|
|
359
|
+
tabindex={tabStopDay === null ? 0 : -1}
|
|
360
|
+
role="grid"
|
|
361
|
+
onkeydown={handleKeyDown}
|
|
362
|
+
>
|
|
342
363
|
{#each calendarDays as dayInfo (dayInfo.date.getTime())}
|
|
343
364
|
{#if dayInfo.isCurrentMonth}
|
|
344
365
|
<button
|
|
@@ -352,7 +373,7 @@
|
|
|
352
373
|
class:disabled={dayInfo.isDisabled}
|
|
353
374
|
data-day={dayInfo.day}
|
|
354
375
|
disabled={dayInfo.isDisabled}
|
|
355
|
-
tabindex={
|
|
376
|
+
tabindex={tabStopDay === dayInfo.day ? 0 : -1}
|
|
356
377
|
aria-label={dayInfo.date.toLocaleDateString(locale)}
|
|
357
378
|
onclick={() => selectDate(dayInfo.date)}
|
|
358
379
|
>
|
|
@@ -422,7 +443,7 @@
|
|
|
422
443
|
.day-name {
|
|
423
444
|
font-size: var(--calendar-day-name-font-size, 12px);
|
|
424
445
|
font-weight: var(--calendar-day-name-font-weight, 600);
|
|
425
|
-
color: var(--calendar-day-name-color, #
|
|
446
|
+
color: var(--calendar-day-name-color, #595959);
|
|
426
447
|
padding: var(--calendar-day-name-padding, 4px 0);
|
|
427
448
|
}
|
|
428
449
|
|
|
@@ -490,13 +511,19 @@
|
|
|
490
511
|
border-radius: 0;
|
|
491
512
|
}
|
|
492
513
|
|
|
514
|
+
/* #cccccc (1.61:1 on white) read as blank space, not as a disabled digit --
|
|
515
|
+
disabled controls are contrast-exempt under WCAG 1.4.3, but the demo's own
|
|
516
|
+
disabledDates run (10th-15th) is information the user must be able to read,
|
|
517
|
+
not just infer. #949494 lands near the 3:1 floor WCAG 1.4.11 uses elsewhere
|
|
518
|
+
for "must stay perceivable" content, while staying well short of the 4.5:1
|
|
519
|
+
enabled-text weight so disabled still reads as visually recessive. */
|
|
493
520
|
.disabled {
|
|
494
|
-
color: var(--calendar-disabled-color, #
|
|
521
|
+
color: var(--calendar-disabled-color, #949494);
|
|
495
522
|
cursor: var(--calendar-disabled-cursor, not-allowed);
|
|
496
523
|
}
|
|
497
524
|
|
|
498
525
|
.outside-month {
|
|
499
|
-
color: var(--calendar-outside-month-color, #
|
|
526
|
+
color: var(--calendar-outside-month-color, #6b7280);
|
|
500
527
|
cursor: default;
|
|
501
528
|
}
|
|
502
529
|
</style>
|
package/dist/Card/Card.svelte
CHANGED
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
.card-description {
|
|
232
232
|
font-size: var(--card-description-font-size, 14px);
|
|
233
233
|
color: var(--card-description-color, inherit);
|
|
234
|
-
opacity: var(--card-description-opacity, 0.
|
|
234
|
+
opacity: var(--card-description-opacity, 0.8);
|
|
235
235
|
margin-top: var(--card-description-margin-top, 4px);
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -230,7 +230,8 @@
|
|
|
230
230
|
.slidesDiv {
|
|
231
231
|
display: flex;
|
|
232
232
|
transform: translateX(0);
|
|
233
|
-
transition: transform 0.5s
|
|
233
|
+
transition: transform var(--carousel-track-transition-duration, var(--motion-duration, 0.5s))
|
|
234
|
+
var(--carousel-track-transition-easing, var(--motion-easing, ease-in-out));
|
|
234
235
|
}
|
|
235
236
|
.dots-wrapper {
|
|
236
237
|
gap: var(--dot-gap, 10px);
|
|
@@ -244,7 +245,8 @@
|
|
|
244
245
|
border-radius: 50%;
|
|
245
246
|
background: var(--carousel-dot-color, #c4c4c4);
|
|
246
247
|
cursor: pointer;
|
|
247
|
-
transition: 0.3s
|
|
248
|
+
transition: all var(--carousel-dot-transition-duration, var(--motion-duration, 0.3s))
|
|
249
|
+
var(--carousel-dot-transition-easing, var(--motion-easing, ease));
|
|
248
250
|
}
|
|
249
251
|
|
|
250
252
|
.active-dot {
|
|
@@ -253,7 +255,8 @@
|
|
|
253
255
|
border-radius: 50%;
|
|
254
256
|
cursor: pointer;
|
|
255
257
|
background: var(--carousel-dot-active-color, #000000);
|
|
256
|
-
transition: 0.3s
|
|
258
|
+
transition: all var(--carousel-dot-transition-duration, var(--motion-duration, 0.3s))
|
|
259
|
+
var(--carousel-dot-transition-easing, var(--motion-easing, ease));
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
.dot:focus-visible,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import type { ChatComposerProperties } from './properties';
|
|
11
11
|
import { resolveControlDisabled } from './controlDisabled';
|
|
12
12
|
import { shouldApplyClear } from './submitResult';
|
|
13
|
+
import { normalizeDictationState } from './dictationState';
|
|
13
14
|
|
|
14
15
|
let {
|
|
15
16
|
value = $bindable(''),
|
|
@@ -51,12 +52,15 @@
|
|
|
51
52
|
actionIcon,
|
|
52
53
|
actionLabel = 'Voice conversation',
|
|
53
54
|
leading,
|
|
55
|
+
statusText,
|
|
56
|
+
statusTestId,
|
|
54
57
|
onsubmit,
|
|
55
58
|
oninput,
|
|
56
59
|
onkeydown,
|
|
57
60
|
onpaste,
|
|
58
61
|
onstop,
|
|
59
62
|
onvoice,
|
|
63
|
+
oncanceldictation,
|
|
60
64
|
onattach,
|
|
61
65
|
onattachclick,
|
|
62
66
|
onaction,
|
|
@@ -84,7 +88,14 @@
|
|
|
84
88
|
// unset, so a caller who never passes the new props keeps the original
|
|
85
89
|
// single-`disabled`-gates-everything behaviour exactly.
|
|
86
90
|
let resolvedTextDisabled = $derived(resolveControlDisabled(textDisabled, disabled));
|
|
87
|
-
|
|
91
|
+
// 'busy' (transcribing) is not cosmetic -- the control must not accept
|
|
92
|
+
// a new press, so busy forces the voice button off regardless of what the
|
|
93
|
+
// caller passed for `voiceDisabled`/`disabled`, the same way `disabled`
|
|
94
|
+
// itself is not something a specific flag can override back on.
|
|
95
|
+
let dictationState = $derived(normalizeDictationState(recording));
|
|
96
|
+
let resolvedVoiceDisabled = $derived(
|
|
97
|
+
resolveControlDisabled(voiceDisabled, disabled) || dictationState === 'busy'
|
|
98
|
+
);
|
|
88
99
|
let resolvedSendDisabled = $derived(resolveControlDisabled(sendDisabled, disabled));
|
|
89
100
|
|
|
90
101
|
let canSend = $derived(
|
|
@@ -131,6 +142,13 @@
|
|
|
131
142
|
event.preventDefault();
|
|
132
143
|
submit();
|
|
133
144
|
}
|
|
145
|
+
// Escape cancels dictation when recording, and only then -- not
|
|
146
|
+
// while idle (nothing to cancel) and not while busy (transcribing is
|
|
147
|
+
// already past the point Escape can interrupt).
|
|
148
|
+
if (event.key === 'Escape' && dictationState === 'recording') {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
oncanceldictation?.();
|
|
151
|
+
}
|
|
134
152
|
onkeydown?.(event);
|
|
135
153
|
}
|
|
136
154
|
|
|
@@ -167,6 +185,13 @@
|
|
|
167
185
|
data-pw={typeof testId === 'string' ? testId : null}
|
|
168
186
|
testID={typeof testId === 'string' ? testId : null}
|
|
169
187
|
>
|
|
188
|
+
{#if typeof statusText === 'string'}
|
|
189
|
+
<!-- Generic region, role and politeness only -- the sentence is
|
|
190
|
+
whatever the caller passed in `statusText`, never hardcoded here. -->
|
|
191
|
+
<div class="sr-only" role="status" aria-live="polite" data-pw={statusTestId ?? null}>
|
|
192
|
+
{statusText}
|
|
193
|
+
</div>
|
|
194
|
+
{/if}
|
|
170
195
|
{#if typeof attachmentsPreview === 'function'}
|
|
171
196
|
{@render attachmentsPreview()}
|
|
172
197
|
{:else if hasRichAttachments}
|
|
@@ -250,7 +275,11 @@
|
|
|
250
275
|
></textarea>
|
|
251
276
|
|
|
252
277
|
{#if showVoice}
|
|
253
|
-
<div
|
|
278
|
+
<div
|
|
279
|
+
class="control voice"
|
|
280
|
+
class:recording={dictationState === 'recording'}
|
|
281
|
+
class:busy={dictationState === 'busy'}
|
|
282
|
+
>
|
|
254
283
|
<Button
|
|
255
284
|
onclick={() => onvoice?.()}
|
|
256
285
|
disabled={resolvedVoiceDisabled}
|
|
@@ -278,7 +307,7 @@
|
|
|
278
307
|
{/if}
|
|
279
308
|
</Button>
|
|
280
309
|
</div>
|
|
281
|
-
{:else if typeof onaction === 'function' && !canSend &&
|
|
310
|
+
{:else if typeof onaction === 'function' && !canSend && dictationState === 'idle'}
|
|
282
311
|
<div class="control send action" data-pw={sendSlotTestId ?? null}>
|
|
283
312
|
<Button
|
|
284
313
|
onclick={() => onaction()}
|
|
@@ -368,7 +397,7 @@
|
|
|
368
397
|
}
|
|
369
398
|
|
|
370
399
|
.input::placeholder {
|
|
371
|
-
color: var(--chat-composer-placeholder-color, #
|
|
400
|
+
color: var(--chat-composer-placeholder-color, #52525b);
|
|
372
401
|
}
|
|
373
402
|
|
|
374
403
|
.control {
|
|
@@ -398,6 +427,13 @@
|
|
|
398
427
|
--button-text-color: var(--chat-composer-voice-recording-color, #dc2626);
|
|
399
428
|
}
|
|
400
429
|
|
|
430
|
+
/* No dedicated busy palette: the disabled `Button` already dims itself
|
|
431
|
+
(`--disabled-opacity`), which is enough to distinguish "busy" from the
|
|
432
|
+
active "recording" red without inventing new color tokens that would
|
|
433
|
+
need a matching dark-theme entry outside this component's scope. The
|
|
434
|
+
`.busy` class is still applied to the wrapper (see markup) so a caller
|
|
435
|
+
can target it with their own CSS if they want a distinct look. */
|
|
436
|
+
|
|
401
437
|
.send {
|
|
402
438
|
--button-width: var(--chat-composer-send-size, 40px);
|
|
403
439
|
--button-height: var(--chat-composer-send-size, 40px);
|
|
@@ -413,4 +449,18 @@
|
|
|
413
449
|
--button-color: var(--chat-composer-stop-background-color, #18181b);
|
|
414
450
|
--button-text-color: var(--chat-composer-stop-color, #ffffff);
|
|
415
451
|
}
|
|
452
|
+
|
|
453
|
+
/* Visually hidden but still read by assistive technology -- same pattern
|
|
454
|
+
Table's caption and Label's required marker use. */
|
|
455
|
+
.sr-only {
|
|
456
|
+
position: absolute;
|
|
457
|
+
width: 1px;
|
|
458
|
+
height: 1px;
|
|
459
|
+
padding: 0;
|
|
460
|
+
overflow: hidden;
|
|
461
|
+
clip: rect(0, 0, 0, 0);
|
|
462
|
+
clip-path: inset(50%);
|
|
463
|
+
white-space: nowrap;
|
|
464
|
+
border-width: 0;
|
|
465
|
+
}
|
|
416
466
|
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loom's composer models dictation as a tri-state
|
|
3
|
+
* `'idle' | 'recording' | 'busy'` against this library's boolean `recording`.
|
|
4
|
+
* The third state is not cosmetic -- while transcribing, the control must not
|
|
5
|
+
* accept a new press, so "recording" and "busy" have to be states a consumer
|
|
6
|
+
* can actually branch on, not one flag with two meanings.
|
|
7
|
+
*
|
|
8
|
+
* `recording` widens to accept the tri-state directly. The boolean is kept
|
|
9
|
+
* working, unchanged, as an alias for one minor cycle: `true` reads as
|
|
10
|
+
* 'recording' and `false` (the default) reads as 'idle' -- the only two
|
|
11
|
+
* states a boolean caller could ever express -- so a caller who never touches
|
|
12
|
+
* this keeps exactly today's behaviour.
|
|
13
|
+
*/
|
|
14
|
+
export type ChatComposerDictationState = 'idle' | 'recording' | 'busy';
|
|
15
|
+
export declare function normalizeDictationState(recording: boolean | ChatComposerDictationState): ChatComposerDictationState;
|
|
@@ -24,7 +24,16 @@ export type OptionalChatComposerProperties = {
|
|
|
24
24
|
submitOnEnter?: boolean;
|
|
25
25
|
maxLength?: number;
|
|
26
26
|
streaming?: boolean;
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Voice/dictation state. Accepts the tri-state `'idle' | 'recording' |
|
|
29
|
+
* 'busy'` -- 'busy' means the control is transcribing: it must not accept a
|
|
30
|
+
* new press, so the voice button disables itself while busy regardless of
|
|
31
|
+
* `voiceDisabled`/`disabled`.
|
|
32
|
+
*
|
|
33
|
+
* `boolean` is kept working, unchanged, as an alias for one minor cycle:
|
|
34
|
+
* `true` reads as 'recording', `false` (the default) reads as 'idle'.
|
|
35
|
+
*/
|
|
36
|
+
recording?: boolean | 'idle' | 'recording' | 'busy';
|
|
28
37
|
attachments?: File[];
|
|
29
38
|
/**
|
|
30
39
|
* Replaces the built-in attachment pill strip above the input row — render a
|
|
@@ -72,6 +81,17 @@ export type OptionalChatComposerProperties = {
|
|
|
72
81
|
actionIcon?: Snippet;
|
|
73
82
|
actionLabel?: string;
|
|
74
83
|
leading?: Snippet;
|
|
84
|
+
/**
|
|
85
|
+
* Opt-in sr-only status region (`role="status" aria-live="polite"`) for
|
|
86
|
+
* narrating composer state changes -- e.g. "Recording. Press Escape to stop
|
|
87
|
+
* and transcribe." Rendered only when set (an empty string still renders an
|
|
88
|
+
* emptied region, so a caller can clear it without unmounting). The library
|
|
89
|
+
* owns only the region, its role and its politeness; the sentences are
|
|
90
|
+
* product voice and must come from the caller -- never hardcoded here.
|
|
91
|
+
*/
|
|
92
|
+
statusText?: string;
|
|
93
|
+
/** Test id on the status region (see `statusText`). */
|
|
94
|
+
statusTestId?: string;
|
|
75
95
|
testId?: string;
|
|
76
96
|
/** Test id emitted as `data-pw` on the textarea itself (none by default). */
|
|
77
97
|
inputTestId?: string;
|
|
@@ -100,6 +120,13 @@ export type ChatComposerEventProperties = {
|
|
|
100
120
|
onpaste?: (event: ClipboardEvent) => void;
|
|
101
121
|
onstop?: () => void;
|
|
102
122
|
onvoice?: () => void;
|
|
123
|
+
/**
|
|
124
|
+
* Fires when Escape is pressed while `recording` is 'recording' (or the
|
|
125
|
+
* legacy `true`), and only then -- Escape has no effect at any other
|
|
126
|
+
* dictation state. The library owns no microphone or media APIs, so this is
|
|
127
|
+
* the seam a caller wires to actually stop dictation.
|
|
128
|
+
*/
|
|
129
|
+
oncanceldictation?: () => void;
|
|
103
130
|
onattach?: (files: File[]) => void;
|
|
104
131
|
/**
|
|
105
132
|
* Intercepts the attach button: when provided, clicking it calls this instead of
|
|
@@ -455,6 +455,23 @@
|
|
|
455
455
|
--button-hover-color: var(--chat-message-action-hover-background-color, #f4f4f5);
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
/* The sender bubble is a dark fill in light mode, so the neutral action grey
|
|
459
|
+
above lands at 1.81:1 on it. Actions inside that bubble take their own ink. */
|
|
460
|
+
.chat-message.party-sender .actions {
|
|
461
|
+
--button-text-color: var(--chat-message-sender-action-color, #d4d4d8);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* The sender bubble is a dark fill in light mode and a light one in dark, so
|
|
465
|
+
anything rendered inside it -- links and inline buttons included -- has to
|
|
466
|
+
invert with it rather than take one fixed colour. */
|
|
467
|
+
.chat-message.party-sender .bubble :global(a) {
|
|
468
|
+
color: var(--chat-message-sender-link-color, light-dark(#a5b4fc, #4338ca));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.chat-message.party-sender .bubble :global(.button-text) {
|
|
472
|
+
color: var(--chat-message-sender-action-color, light-dark(#d4d4d8, #27272a));
|
|
473
|
+
}
|
|
474
|
+
|
|
458
475
|
.chat-message:hover .actions,
|
|
459
476
|
.chat-message:focus-within .actions {
|
|
460
477
|
opacity: 1;
|
|
@@ -499,7 +516,7 @@
|
|
|
499
516
|
}
|
|
500
517
|
|
|
501
518
|
.body :global(a) {
|
|
502
|
-
color: var(--chat-message-link-color, #6d28d9);
|
|
519
|
+
color: var(--chat-message-link-color, light-dark(#6d28d9, #a5b4fc));
|
|
503
520
|
text-decoration: underline;
|
|
504
521
|
text-underline-offset: 2px;
|
|
505
522
|
}
|
|
@@ -638,4 +655,13 @@
|
|
|
638
655
|
border-top: 1px solid var(--chat-message-hr-color, rgba(0, 0, 0, 0.12));
|
|
639
656
|
margin: 0.8em 0;
|
|
640
657
|
}
|
|
658
|
+
/* The actions row is revealed on hover by fading opacity 0 -> 1. Under a
|
|
659
|
+
reduced-motion preference it appears at once instead: the row is a set of
|
|
660
|
+
controls the user is reaching for, so arriving instantly is the better
|
|
661
|
+
outcome, not a degraded one. */
|
|
662
|
+
@media (prefers-reduced-motion: reduce) {
|
|
663
|
+
.actions {
|
|
664
|
+
transition: none;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
641
667
|
</style>
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
|
|
59
59
|
.text {
|
|
60
60
|
font-size: var(--check-list-item-text-size, 14px);
|
|
61
|
-
color: var(--check-list-item-text-color);
|
|
61
|
+
color: var(--check-list-item-text-color, inherit);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
.text.checked {
|
|
65
|
-
color: var(--check-list-item-checked-text-color);
|
|
66
|
-
font-weight: var(--check-list-item-checked-font-weight);
|
|
65
|
+
color: var(--check-list-item-checked-text-color, inherit);
|
|
66
|
+
font-weight: var(--check-list-item-checked-font-weight, inherit);
|
|
67
67
|
}
|
|
68
68
|
</style>
|