@juspay/svelte-ui-components 4.29.1 → 4.31.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 +15 -14
- package/dist/Accordion/Accordion.svelte +18 -0
- package/dist/AnimatedNumber/AnimatedNumber.svelte +776 -0
- package/dist/AnimatedNumber/AnimatedNumber.svelte.d.ts +4 -0
- package/dist/AnimatedNumber/motion.d.ts +47 -0
- package/dist/AnimatedNumber/motion.js +190 -0
- package/dist/AnimatedNumber/properties.d.ts +58 -0
- package/dist/AnimatedNumber/properties.js +1 -0
- package/dist/Badge/Badge.svelte +20 -3
- package/dist/Badge/properties.d.ts +9 -0
- package/dist/Book/Book.svelte +9 -0
- package/dist/Calendar/Calendar.svelte +34 -2
- package/dist/Carousel/Carousel.svelte +17 -2
- package/dist/ChatBubble/ChatBubble.svelte +12 -0
- package/dist/ChatComposer/ChatComposer.svelte +9 -0
- package/dist/CommandMenu/CommandMenu.svelte +13 -0
- package/dist/DateRangePicker/DateRangePicker.svelte +77 -20
- package/dist/DeltaIndicator/DeltaIndicator.svelte +12 -1
- package/dist/DeltaIndicator/properties.d.ts +9 -0
- package/dist/Gallery/Gallery.svelte +16 -0
- package/dist/Gauge/Gauge.svelte +21 -2
- package/dist/Gauge/properties.d.ts +13 -0
- package/dist/Input/Input.svelte +34 -26
- package/dist/KeyboardInput/KeyboardInput.svelte +1 -1
- package/dist/ListItem/ListItem.svelte +27 -2
- package/dist/ListItem/label.d.ts +11 -0
- package/dist/ListItem/label.js +40 -0
- package/dist/ListItem/properties.d.ts +52 -0
- package/dist/MediaUpload/MediaUpload.svelte +12 -1
- package/dist/MediaUpload/properties.d.ts +9 -0
- package/dist/Menu/Menu.svelte +11 -2
- package/dist/Modal/Modal.svelte +14 -4
- package/dist/PieChart/PieChart.svelte +16 -1
- package/dist/PieChart/properties.d.ts +13 -0
- package/dist/Progress/Progress.svelte +21 -1
- package/dist/Progress/properties.d.ts +12 -0
- package/dist/ProportionBar/ProportionBar.svelte +7 -1
- package/dist/ProportionBar/ProportionBar.svelte.d.ts +0 -19
- package/dist/ProportionBar/properties.d.ts +10 -0
- package/dist/Select/Select.svelte +23 -8
- package/dist/SplitButton/SplitButton.svelte +4 -1
- package/dist/SplitInput/SplitInput.svelte +1 -1
- package/dist/StatCard/StatCard.svelte +129 -5
- package/dist/StatCard/metric.d.ts +22 -0
- package/dist/StatCard/metric.js +74 -0
- package/dist/StatCard/properties.d.ts +44 -0
- package/dist/Table/BuiltinCell.svelte +19 -2
- package/dist/Table/cellData.js +3 -0
- package/dist/Table/properties.d.ts +15 -0
- package/dist/Tabs/Tabs.svelte +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/styles/theme-dark.css +27 -3
- package/dist-codemod/codemod/migration-assets/legacy-display.css +2 -1
- package/dist-codemod/codemod/migration-assets/legacy-palette.css +4 -1
- package/dist-codemod/codemod/migration-assets/wc-display.json +5 -0
- package/dist-wc/index.js +1435 -747
- package/package.json +4 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The odometer's motion arithmetic, kept out of the component so it can be
|
|
3
|
+
* tested as plain functions -- jsdom has no `Element.animate`, no `CSS` global
|
|
4
|
+
* and no running transitions, so none of this is observable through a rendered
|
|
5
|
+
* component there.
|
|
6
|
+
*
|
|
7
|
+
* The mechanism, which is worth stating once because it is not the obvious one:
|
|
8
|
+
* a glyph's position is never itself animated. Each of the ten glyphs in a
|
|
9
|
+
* column resolves its own offset as a pure function of one scalar, and only that
|
|
10
|
+
* scalar moves. Animating each glyph's own `transform` instead interpolates the
|
|
11
|
+
* RESOLVED before and after values, so a glyph parked off-screen above whose
|
|
12
|
+
* offset wrapped to below is tweened straight THROUGH the visible window -- a
|
|
13
|
+
* full-size digit sliding across the number. Moving the scalar and recomputing
|
|
14
|
+
* the offsets every frame cannot produce that, because every glyph's position
|
|
15
|
+
* stays a continuous function of one moving value.
|
|
16
|
+
*
|
|
17
|
+
* The scalar is `--_animated-number-digit + --_animated-number-delta`. The digit
|
|
18
|
+
* jumps to its new value immediately and the delta animates from `-delta` to 0,
|
|
19
|
+
* so their sum travels from the old digit to the new one without anything
|
|
20
|
+
* needing to remember where it started.
|
|
21
|
+
*/
|
|
22
|
+
/** Digits per column. Every column carries a full 0-9 wheel. */
|
|
23
|
+
export declare const WHEEL_LENGTH = 10;
|
|
24
|
+
/** Registered so it INTERPOLATES: an unregistered custom property animates discretely. */
|
|
25
|
+
export declare const DELTA_PROPERTY = "--_animated-number-delta";
|
|
26
|
+
/**
|
|
27
|
+
* How far this column's wheel turns, signed, in digits.
|
|
28
|
+
*
|
|
29
|
+
* `trend` is the direction the number as a whole moved, and passing it is the
|
|
30
|
+
* single thing that makes 99 -> 100 roll each 9 forward by one step. Judged per
|
|
31
|
+
* column instead, a 9 -> 0 column sees a difference of -9 and takes nine steps
|
|
32
|
+
* backwards while its neighbours step forwards. Zero means "no opinion", which
|
|
33
|
+
* falls back to each column's own direction.
|
|
34
|
+
*/
|
|
35
|
+
export declare const wheelDelta: (digit: number, previous: number, trend: number) => number;
|
|
36
|
+
/**
|
|
37
|
+
* Which way the whole number moved: 1 up, -1 down, 0 unchanged.
|
|
38
|
+
*
|
|
39
|
+
* Numbers are compared as numbers. A pre-formatted string has no numeric value
|
|
40
|
+
* to compare, so its digit runs are compared instead -- longer wins, then
|
|
41
|
+
* lexicographically, which is exactly numeric order for two runs of digits and
|
|
42
|
+
* stays correct past `Number.MAX_SAFE_INTEGER`, where subtracting would not.
|
|
43
|
+
*/
|
|
44
|
+
export declare const trendBetween: (previous: number | string, next: number | string, digitOf?: (character: string) => number) => number;
|
|
45
|
+
/** A CSS `<time>` in milliseconds. Anything unparseable reads as 0, i.e. no motion. */
|
|
46
|
+
export declare const durationMs: (value: string) => number;
|
|
47
|
+
export declare const canSpin: () => boolean;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The odometer's motion arithmetic, kept out of the component so it can be
|
|
3
|
+
* tested as plain functions -- jsdom has no `Element.animate`, no `CSS` global
|
|
4
|
+
* and no running transitions, so none of this is observable through a rendered
|
|
5
|
+
* component there.
|
|
6
|
+
*
|
|
7
|
+
* The mechanism, which is worth stating once because it is not the obvious one:
|
|
8
|
+
* a glyph's position is never itself animated. Each of the ten glyphs in a
|
|
9
|
+
* column resolves its own offset as a pure function of one scalar, and only that
|
|
10
|
+
* scalar moves. Animating each glyph's own `transform` instead interpolates the
|
|
11
|
+
* RESOLVED before and after values, so a glyph parked off-screen above whose
|
|
12
|
+
* offset wrapped to below is tweened straight THROUGH the visible window -- a
|
|
13
|
+
* full-size digit sliding across the number. Moving the scalar and recomputing
|
|
14
|
+
* the offsets every frame cannot produce that, because every glyph's position
|
|
15
|
+
* stays a continuous function of one moving value.
|
|
16
|
+
*
|
|
17
|
+
* The scalar is `--_animated-number-digit + --_animated-number-delta`. The digit
|
|
18
|
+
* jumps to its new value immediately and the delta animates from `-delta` to 0,
|
|
19
|
+
* so their sum travels from the old digit to the new one without anything
|
|
20
|
+
* needing to remember where it started.
|
|
21
|
+
*/
|
|
22
|
+
/** Digits per column. Every column carries a full 0-9 wheel. */
|
|
23
|
+
export const WHEEL_LENGTH = 10;
|
|
24
|
+
/** Registered so it INTERPOLATES: an unregistered custom property animates discretely. */
|
|
25
|
+
export const DELTA_PROPERTY = '--_animated-number-delta';
|
|
26
|
+
/**
|
|
27
|
+
* How far this column's wheel turns, signed, in digits.
|
|
28
|
+
*
|
|
29
|
+
* `trend` is the direction the number as a whole moved, and passing it is the
|
|
30
|
+
* single thing that makes 99 -> 100 roll each 9 forward by one step. Judged per
|
|
31
|
+
* column instead, a 9 -> 0 column sees a difference of -9 and takes nine steps
|
|
32
|
+
* backwards while its neighbours step forwards. Zero means "no opinion", which
|
|
33
|
+
* falls back to each column's own direction.
|
|
34
|
+
*/
|
|
35
|
+
export const wheelDelta = (digit, previous, trend) => {
|
|
36
|
+
const difference = digit - previous;
|
|
37
|
+
const direction = trend === 0 ? Math.sign(difference) : trend;
|
|
38
|
+
if (direction < 0 && digit > previous) {
|
|
39
|
+
return digit - WHEEL_LENGTH - previous;
|
|
40
|
+
}
|
|
41
|
+
if (direction > 0 && digit < previous) {
|
|
42
|
+
return WHEEL_LENGTH - previous + digit;
|
|
43
|
+
}
|
|
44
|
+
return difference;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The digits of a string, rewritten as ASCII so two runs can be compared.
|
|
48
|
+
*
|
|
49
|
+
* `digitOf` is the caller's own glyph lookup, so a numbering system whose digits
|
|
50
|
+
* are neither ASCII nor contiguous still reduces to something comparable. The
|
|
51
|
+
* previous `replace(/\D/g, '')` was ASCII-only without the `u` flag, so every
|
|
52
|
+
* Arabic-Indic, Devanagari or Bengali numeral was stripped and BOTH runs came
|
|
53
|
+
* back empty -- the trend then read 0, and a 9 -> 0 column with no direction to
|
|
54
|
+
* follow rolls nine positions backwards. Rewriting to ASCII rather than keeping
|
|
55
|
+
* the original glyphs also keeps the comparison below honest for a
|
|
56
|
+
* non-contiguous system, where code-point order is not value order.
|
|
57
|
+
*/
|
|
58
|
+
const digitRun = (text, digitOf) => {
|
|
59
|
+
let run = '';
|
|
60
|
+
for (const character of text) {
|
|
61
|
+
const digit = digitOf(character);
|
|
62
|
+
if (digit >= 0) {
|
|
63
|
+
run += String(digit);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return run;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Fallback lookup for a caller with no numbering system of its own to offer.
|
|
70
|
+
*
|
|
71
|
+
* Unicode assigns the General_Category Nd only to decimal digits, and each such
|
|
72
|
+
* block is exactly ten consecutive code points beginning at its own zero. So the
|
|
73
|
+
* value of a digit is how far it sits above the first Nd code point in its run,
|
|
74
|
+
* found by walking down at most nine places. That covers every numbering system
|
|
75
|
+
* whose digits ARE decimal digits -- Arabic-Indic, Devanagari, Bengali, the
|
|
76
|
+
* supplementary-plane ones -- without a table.
|
|
77
|
+
*
|
|
78
|
+
* It returns -1 for `hanidec`, whose Han numerals are not Nd at all. Nothing can
|
|
79
|
+
* infer those from the character alone, which is exactly why the component hands
|
|
80
|
+
* in its own map rather than relying on this.
|
|
81
|
+
*/
|
|
82
|
+
const unicodeDigitOf = (character) => {
|
|
83
|
+
const code = character.codePointAt(0);
|
|
84
|
+
if (!Number.isInteger(code) || !/\p{Nd}/u.test(character)) {
|
|
85
|
+
return -1;
|
|
86
|
+
}
|
|
87
|
+
const value = Number(code);
|
|
88
|
+
let zero = value;
|
|
89
|
+
while (value - zero < 9 && /\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
|
|
90
|
+
zero -= 1;
|
|
91
|
+
}
|
|
92
|
+
return value - zero;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Which way the whole number moved: 1 up, -1 down, 0 unchanged.
|
|
96
|
+
*
|
|
97
|
+
* Numbers are compared as numbers. A pre-formatted string has no numeric value
|
|
98
|
+
* to compare, so its digit runs are compared instead -- longer wins, then
|
|
99
|
+
* lexicographically, which is exactly numeric order for two runs of digits and
|
|
100
|
+
* stays correct past `Number.MAX_SAFE_INTEGER`, where subtracting would not.
|
|
101
|
+
*/
|
|
102
|
+
export const trendBetween = (previous, next, digitOf = unicodeDigitOf) => {
|
|
103
|
+
if (typeof previous === 'number' && typeof next === 'number') {
|
|
104
|
+
// `|| 0` folds away both the signed zero `Math.sign` returns for `0 -> -0`
|
|
105
|
+
// and the NaN it returns when either side is non-finite: neither is a
|
|
106
|
+
// direction, and both would otherwise be passed on as one.
|
|
107
|
+
return Math.sign(next - previous) || 0;
|
|
108
|
+
}
|
|
109
|
+
const before = digitRun(String(previous), digitOf);
|
|
110
|
+
const after = digitRun(String(next), digitOf);
|
|
111
|
+
if (before.length !== after.length) {
|
|
112
|
+
return Math.sign(after.length - before.length);
|
|
113
|
+
}
|
|
114
|
+
return before < after ? 1 : before > after ? -1 : 0;
|
|
115
|
+
};
|
|
116
|
+
/** A CSS `<time>` in milliseconds. Anything unparseable reads as 0, i.e. no motion. */
|
|
117
|
+
export const durationMs = (value) => {
|
|
118
|
+
const text = value.trim();
|
|
119
|
+
const amount = Number.parseFloat(text);
|
|
120
|
+
if (!Number.isFinite(amount)) {
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
return text.endsWith('ms') ? amount : text.endsWith('s') ? amount * 1000 : 0;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Registers the delta property on the document, returning whether it is
|
|
127
|
+
* registered afterwards.
|
|
128
|
+
*
|
|
129
|
+
* It has to be done from script rather than by an `@property` rule in the
|
|
130
|
+
* component's own stylesheet: an `@property` inside a shadow root does not
|
|
131
|
+
* register (measured in both Chromium and WebKit -- the probe's initial value
|
|
132
|
+
* never applied). That stylesheet is the only one reaching inside the shadow
|
|
133
|
+
* root a custom-element consumer gets, so relying on the at-rule would have left
|
|
134
|
+
* `<sui-animated-number>` silently un-animated while the Svelte component moved.
|
|
135
|
+
* `CSS.registerProperty` registers per document, so one call covers both.
|
|
136
|
+
*/
|
|
137
|
+
const registerDelta = () => {
|
|
138
|
+
try {
|
|
139
|
+
CSS.registerProperty({
|
|
140
|
+
name: DELTA_PROPERTY,
|
|
141
|
+
syntax: '<number>',
|
|
142
|
+
inherits: true,
|
|
143
|
+
initialValue: '0'
|
|
144
|
+
});
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
// Registering the same name twice throws, and so does a malformed
|
|
149
|
+
// descriptor. Only the first means the property is there -- which happens
|
|
150
|
+
// whenever a second copy of this library shares the page.
|
|
151
|
+
return error instanceof DOMException && error.name === 'InvalidModificationError';
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Whether this engine can run the roll at all.
|
|
156
|
+
*
|
|
157
|
+
* `mod()` and `round()` place the glyphs, `linear()` carries the spring easing,
|
|
158
|
+
* and the registration above is what lets the delta interpolate rather than jump
|
|
159
|
+
* at the halfway point. Without all four the component still renders correctly
|
|
160
|
+
* -- `transform` falls back to `none`, which is where the current glyph already
|
|
161
|
+
* sits, and the other nine stay `display: none` because nothing ever adds the
|
|
162
|
+
* spinning class -- it just changes instantly.
|
|
163
|
+
*/
|
|
164
|
+
let supported = null;
|
|
165
|
+
export const canSpin = () => {
|
|
166
|
+
if (supported !== null) {
|
|
167
|
+
return supported;
|
|
168
|
+
}
|
|
169
|
+
if (typeof CSS === 'undefined' || typeof CSS.supports !== 'function') {
|
|
170
|
+
supported = false;
|
|
171
|
+
return supported;
|
|
172
|
+
}
|
|
173
|
+
// Probed by attempting it rather than through CSS.supports, which reports
|
|
174
|
+
// false positives for `linear()` against what `animate()` will actually take.
|
|
175
|
+
const linear = (() => {
|
|
176
|
+
try {
|
|
177
|
+
document.createElement('div').animate({ opacity: 0 }, { easing: 'linear(0, 1)' });
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
})();
|
|
184
|
+
supported =
|
|
185
|
+
CSS.supports('line-height', 'mod(1,1)') &&
|
|
186
|
+
CSS.supports('line-height', 'round(down,1,1)') &&
|
|
187
|
+
linear &&
|
|
188
|
+
registerDelta();
|
|
189
|
+
return supported;
|
|
190
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** How value changes are exposed to assistive technology. */
|
|
2
|
+
export type AnimatedNumberLive = 'off' | 'polite' | 'assertive';
|
|
3
|
+
export type AnimatedNumberProperties = MandatoryAnimatedNumberProperties & OptionalAnimatedNumberProperties;
|
|
4
|
+
export type MandatoryAnimatedNumberProperties = {
|
|
5
|
+
/**
|
|
6
|
+
* The value to display. A `number` is formatted through `Intl.NumberFormat`
|
|
7
|
+
* and decomposed semantically, so the component knows a group separator from
|
|
8
|
+
* a currency symbol. A `string` is taken as already-formatted and diffed
|
|
9
|
+
* against the previous string instead -- which is what lets a consumer's own
|
|
10
|
+
* formatter, or a pre-formatted prop like `StatCard.value`, animate without
|
|
11
|
+
* anyone having to surface the underlying number.
|
|
12
|
+
*/
|
|
13
|
+
value: number | string;
|
|
14
|
+
};
|
|
15
|
+
export type OptionalAnimatedNumberProperties = {
|
|
16
|
+
/**
|
|
17
|
+
* BCP 47 locale used to format a numeric `value`. Defaults to a fixed
|
|
18
|
+
* `'en-US'` rather than the runtime's ambient default: `Intl.NumberFormat`
|
|
19
|
+
* with an undefined locale resolves against whatever the executing
|
|
20
|
+
* environment reports, which differs between the process that prerenders a
|
|
21
|
+
* page and the browser that hydrates it. Pinning it keeps the server and the
|
|
22
|
+
* first client frame byte-identical.
|
|
23
|
+
* @default 'en-US'
|
|
24
|
+
*/
|
|
25
|
+
locale?: string;
|
|
26
|
+
/** `Intl.NumberFormat` options applied when `value` is a number. */
|
|
27
|
+
format?: Intl.NumberFormatOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Whether value changes are announced. Left `'off'`, the accessible name
|
|
30
|
+
* still updates -- it is simply not interrupted into. Reserve `'polite'` for
|
|
31
|
+
* values that change infrequently; a rapidly changing value queues one
|
|
32
|
+
* announcement per change, with no coalescing.
|
|
33
|
+
* @default 'off'
|
|
34
|
+
*/
|
|
35
|
+
live?: AnimatedNumberLive;
|
|
36
|
+
/**
|
|
37
|
+
* Rolls every column up from zero when the number first appears, instead of
|
|
38
|
+
* painting it outright.
|
|
39
|
+
*
|
|
40
|
+
* Off by default, because a number that was always going to be on screen
|
|
41
|
+
* should not make the reader wait for it. Turn it on where the figure ARRIVES
|
|
42
|
+
* -- a dashboard whose metrics land after a fetch, a total that appears at the
|
|
43
|
+
* end of a flow -- and especially where the value then never changes again:
|
|
44
|
+
* without this, such a number is static for its whole life and the odometer
|
|
45
|
+
* does nothing at all.
|
|
46
|
+
*
|
|
47
|
+
* Independent of the rolls that follow. A later change animates either way,
|
|
48
|
+
* and this only governs the first paint.
|
|
49
|
+
* @default false
|
|
50
|
+
*/
|
|
51
|
+
animateOnMount?: boolean;
|
|
52
|
+
/** Overrides the accessible name, which otherwise mirrors the visible text. */
|
|
53
|
+
ariaLabel?: string;
|
|
54
|
+
/** Value for the `data-pw` attribute on the root element for E2E selection. */
|
|
55
|
+
testId?: string;
|
|
56
|
+
/** Extra CSS classes applied to the root element for consumer theming. */
|
|
57
|
+
classes?: string;
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/Badge/Badge.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import AnimatedNumber from '../AnimatedNumber/AnimatedNumber.svelte';
|
|
2
3
|
import type { BadgeProperties } from './properties';
|
|
3
4
|
|
|
4
5
|
let {
|
|
@@ -9,7 +10,8 @@
|
|
|
9
10
|
hidden = false,
|
|
10
11
|
ariaLabel,
|
|
11
12
|
testId,
|
|
12
|
-
classes
|
|
13
|
+
classes,
|
|
14
|
+
animateValue = false
|
|
13
15
|
}: BadgeProperties = $props();
|
|
14
16
|
|
|
15
17
|
let showImage = $derived(typeof image === 'string' && image.length > 0);
|
|
@@ -20,6 +22,11 @@
|
|
|
20
22
|
let standaloneAriaLabel = $derived(
|
|
21
23
|
isDot ? null : (ariaLabel ?? (typeof value === 'string' ? value : null))
|
|
22
24
|
);
|
|
25
|
+
|
|
26
|
+
// `value` is a general-purpose display string ('99+' included), so the dot
|
|
27
|
+
// check gates AnimatedNumber the same way it already gates the plain text --
|
|
28
|
+
// dot mode never had a value to animate in the first place.
|
|
29
|
+
let animateThisValue = $derived(animateValue && !isDot);
|
|
23
30
|
</script>
|
|
24
31
|
|
|
25
32
|
{#if showImage}
|
|
@@ -31,7 +38,13 @@
|
|
|
31
38
|
<div class="badge-wrap">
|
|
32
39
|
<img class="icon-img" src={image} {alt} />
|
|
33
40
|
{#if showBadge}
|
|
34
|
-
<div class="badge" class:badge-dot={isDot}>
|
|
41
|
+
<div class="badge" class:badge-dot={isDot}>
|
|
42
|
+
{#if animateThisValue}
|
|
43
|
+
<AnimatedNumber value={value ?? ''} />
|
|
44
|
+
{:else}
|
|
45
|
+
{isDot ? '' : (value ?? '')}
|
|
46
|
+
{/if}
|
|
47
|
+
</div>
|
|
35
48
|
{/if}
|
|
36
49
|
</div>
|
|
37
50
|
</div>
|
|
@@ -44,7 +57,11 @@
|
|
|
44
57
|
data-pw={typeof testId === 'string' ? testId : null}
|
|
45
58
|
testID={typeof testId === 'string' ? testId : null}
|
|
46
59
|
>
|
|
47
|
-
{
|
|
60
|
+
{#if animateThisValue}
|
|
61
|
+
<AnimatedNumber value={value ?? ''} />
|
|
62
|
+
{:else}
|
|
63
|
+
{isDot ? '' : (value ?? '')}
|
|
64
|
+
{/if}
|
|
48
65
|
</div>
|
|
49
66
|
{/if}
|
|
50
67
|
|
|
@@ -10,5 +10,14 @@ export type OptionalBadgeProperties = {
|
|
|
10
10
|
ariaLabel?: string;
|
|
11
11
|
testId?: string;
|
|
12
12
|
classes?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Rolls `value` through AnimatedNumber instead of swapping it in place.
|
|
15
|
+
* `value` stays the same string prop -- AnimatedNumber diffs it by
|
|
16
|
+
* character position, so '99+' rolls the two digits and leaves the '+'
|
|
17
|
+
* alone, with no separate numeric prop required here. No effect in
|
|
18
|
+
* `mode="dot"`, which never renders `value` at all.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
animateValue?: boolean;
|
|
13
22
|
};
|
|
14
23
|
export type BadgeEventProperties = Record<never, never>;
|
package/dist/Book/Book.svelte
CHANGED
|
@@ -185,6 +185,15 @@
|
|
|
185
185
|
outline: none;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/* role="region" tabindex="0" above makes this a real Tab stop (arrow keys
|
|
189
|
+
then page it via handleKeyDown), so removing its outline unconditionally
|
|
190
|
+
left a keyboard user with no visible sign it was focused. :focus-visible
|
|
191
|
+
so tapping/clicking into the book to swipe doesn't show a ring. */
|
|
192
|
+
.book:focus-visible {
|
|
193
|
+
outline: var(--book-focus-outline, 2px solid #2563eb);
|
|
194
|
+
outline-offset: var(--book-focus-outline-offset, 2px);
|
|
195
|
+
}
|
|
196
|
+
|
|
188
197
|
.book-viewport {
|
|
189
198
|
position: relative;
|
|
190
199
|
display: flex;
|
|
@@ -202,7 +202,31 @@
|
|
|
202
202
|
return false;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// Bounds the prev/next month CONTROLS, distinct from isDateDisabled's per-day check
|
|
206
|
+
// above: a month is only unreachable when every one of its days would fail that same
|
|
207
|
+
// check, i.e. the whole month falls outside [minDate, maxDate] -- not merely when
|
|
208
|
+
// today's selection or the currently displayed month does. Comparing the previous
|
|
209
|
+
// month's LAST day against minDate (and the next month's FIRST day against maxDate)
|
|
210
|
+
// is the correct boundary: any later/earlier day in that month is enabled already.
|
|
211
|
+
const isPrevMonthDisabled = $derived(
|
|
212
|
+
minDate !== null &&
|
|
213
|
+
new SvelteDate(displayDate.getFullYear(), displayDate.getMonth(), 0).getTime() <
|
|
214
|
+
normalizeDate(minDate).getTime()
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const isNextMonthDisabled = $derived(
|
|
218
|
+
maxDate !== null &&
|
|
219
|
+
new SvelteDate(displayDate.getFullYear(), displayDate.getMonth() + 1, 1).getTime() >
|
|
220
|
+
normalizeDate(maxDate).getTime()
|
|
221
|
+
);
|
|
222
|
+
|
|
205
223
|
function navigateMonth(delta: number): void {
|
|
224
|
+
if (delta < 0 && isPrevMonthDisabled) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (delta > 0 && isNextMonthDisabled) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
206
230
|
displayDate.setMonth(displayDate.getMonth() + delta);
|
|
207
231
|
focusedDay = null;
|
|
208
232
|
onmonthchange?.({ year: displayDate.getFullYear(), month: displayDate.getMonth() });
|
|
@@ -325,7 +349,11 @@
|
|
|
325
349
|
>
|
|
326
350
|
<div class="header">
|
|
327
351
|
<div class="nav-button nav-prev">
|
|
328
|
-
<Button
|
|
352
|
+
<Button
|
|
353
|
+
onclick={() => navigateMonth(-1)}
|
|
354
|
+
ariaLabel="Previous month"
|
|
355
|
+
disabled={isPrevMonthDisabled}
|
|
356
|
+
>
|
|
329
357
|
{#if typeof previousMonthIcon === 'function'}
|
|
330
358
|
{@render previousMonthIcon()}
|
|
331
359
|
{:else}
|
|
@@ -336,7 +364,11 @@
|
|
|
336
364
|
</div>
|
|
337
365
|
<span class="header-label">{headerLabel}</span>
|
|
338
366
|
<div class="nav-button nav-next">
|
|
339
|
-
<Button
|
|
367
|
+
<Button
|
|
368
|
+
onclick={() => navigateMonth(1)}
|
|
369
|
+
ariaLabel="Next month"
|
|
370
|
+
disabled={isNextMonthDisabled}
|
|
371
|
+
>
|
|
340
372
|
{#if typeof nextMonthIcon === 'function'}
|
|
341
373
|
{@render nextMonthIcon()}
|
|
342
374
|
{:else}
|
|
@@ -233,6 +233,17 @@
|
|
|
233
233
|
transition: transform var(--carousel-track-transition-duration, var(--motion-duration, 0.5s))
|
|
234
234
|
var(--carousel-track-transition-easing, var(--motion-easing, ease-in-out));
|
|
235
235
|
}
|
|
236
|
+
/* The track slide is the component's motion. The dots below transition only
|
|
237
|
+
`background`, which is a colour change rather than movement, so they are
|
|
238
|
+
deliberately left alone -- silencing them would remove feedback the
|
|
239
|
+
preference never asked to remove. Finite, so dropping it lands the track on
|
|
240
|
+
the slide it was moving to. */
|
|
241
|
+
@media (prefers-reduced-motion: reduce) {
|
|
242
|
+
.slidesDiv {
|
|
243
|
+
transition: none;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
236
247
|
.dots-wrapper {
|
|
237
248
|
gap: var(--dot-gap, 10px);
|
|
238
249
|
padding-top: var(--dot-padding-top, 10px);
|
|
@@ -245,7 +256,10 @@
|
|
|
245
256
|
border-radius: 50%;
|
|
246
257
|
background: var(--carousel-dot-color, #c4c4c4);
|
|
247
258
|
cursor: pointer;
|
|
248
|
-
|
|
259
|
+
/* Only background actually differs between .dot and .active-dot, so only
|
|
260
|
+
background is transitioned. `transition: all` used to catch `outline`
|
|
261
|
+
too, animating the keyboard focus ring in on every Tab. */
|
|
262
|
+
transition: background var(--carousel-dot-transition-duration, var(--motion-duration, 0.3s))
|
|
249
263
|
var(--carousel-dot-transition-easing, var(--motion-easing, ease));
|
|
250
264
|
}
|
|
251
265
|
|
|
@@ -255,7 +269,8 @@
|
|
|
255
269
|
border-radius: 50%;
|
|
256
270
|
cursor: pointer;
|
|
257
271
|
background: var(--carousel-dot-active-color, #000000);
|
|
258
|
-
|
|
272
|
+
/* See .dot above: scoped to background so outline is never transitioned. */
|
|
273
|
+
transition: background var(--carousel-dot-transition-duration, var(--motion-duration, 0.3s))
|
|
259
274
|
var(--carousel-dot-transition-easing, var(--motion-easing, ease));
|
|
260
275
|
}
|
|
261
276
|
|
|
@@ -385,6 +385,18 @@
|
|
|
385
385
|
outline: none;
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
+
/* setOpen() (see script) calls panel.focus() directly on open, and the panel
|
|
389
|
+
-- not a child -- is the Tab-trap's own boundary, so removing its outline
|
|
390
|
+
unconditionally left a keyboard user with no visible sign focus had moved
|
|
391
|
+
into the launcher's panel at all. :focus-visible restores a ring only for
|
|
392
|
+
keyboard/programmatic focus, so the launcher click that opens the panel
|
|
393
|
+
shows nothing new. Matches Sheet's sheet-panel fix for the identical
|
|
394
|
+
shape of problem. */
|
|
395
|
+
.panel:focus-visible {
|
|
396
|
+
outline: var(--chat-bubble-panel-focus-outline, 2px solid #2563eb);
|
|
397
|
+
outline-offset: var(--chat-bubble-panel-focus-outline-offset, -2px);
|
|
398
|
+
}
|
|
399
|
+
|
|
388
400
|
.launcher {
|
|
389
401
|
--button-width: var(--chat-bubble-width, var(--chat-bubble-size, 56px));
|
|
390
402
|
--button-height: var(--chat-bubble-height, var(--chat-bubble-size, 56px));
|
|
@@ -356,6 +356,15 @@
|
|
|
356
356
|
opacity: var(--chat-composer-disabled-opacity, 0.6);
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
/* .input below sets outline: none with nothing standing in for it, so the
|
|
360
|
+
visible replacement lives here on the surrounding surface instead --
|
|
361
|
+
:focus-within lights up whenever the textarea inside is focused, the same
|
|
362
|
+
pattern Select's .select-trigger uses for its nested search input. */
|
|
363
|
+
.chat-composer:focus-within {
|
|
364
|
+
border-color: var(--chat-composer-focus-border-color, #2563eb);
|
|
365
|
+
box-shadow: var(--chat-composer-focus-shadow, 0 0 0 2px rgba(37, 99, 235, 0.2));
|
|
366
|
+
}
|
|
367
|
+
|
|
359
368
|
.attachments-rich {
|
|
360
369
|
padding: var(--chat-composer-attachments-padding, 2px 4px 0);
|
|
361
370
|
}
|
|
@@ -431,6 +431,19 @@
|
|
|
431
431
|
caret-color: var(--command-menu-input-caret-color, #3b82f6);
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
/* The input is the only real Tab stop the overlay currently renders (list
|
|
435
|
+
items are a roving aria-activedescendant selection, not DOM focus -- see
|
|
436
|
+
.active below), so it is also the only element here that WCAG 2.4.7
|
|
437
|
+
applies to. A real text input, so :focus rather than :focus-visible: the
|
|
438
|
+
ring must stay visible regardless of input modality, matching Input.svelte's
|
|
439
|
+
own textarea/input convention. Positive offset (not Sheet/Modal's inset
|
|
440
|
+
-2px) because the input sits inside .command-menu-input-wrapper's own
|
|
441
|
+
padding, not flush against the dialog's edge. */
|
|
442
|
+
.command-menu-input:focus {
|
|
443
|
+
outline: var(--command-menu-input-focus-outline, 2px solid #2563eb);
|
|
444
|
+
outline-offset: var(--command-menu-input-focus-outline-offset, 2px);
|
|
445
|
+
}
|
|
446
|
+
|
|
434
447
|
.command-menu-input::placeholder {
|
|
435
448
|
color: var(--command-menu-input-placeholder-color, #94a3b8);
|
|
436
449
|
}
|