@refraction-ui/astro 0.17.0 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accordion/accordion.ts +155 -0
- package/dist/accordion/index.ts +9 -1
- package/dist/analytics/analytics-manager.ts +7 -2
- package/dist/analytics/http-sink.ts +3 -1
- package/dist/analytics/types.ts +6 -0
- package/dist/astro-location-selector/LocationSelector.astro +56 -0
- package/dist/astro-location-selector/LocationSelector.stories.ts +15 -0
- package/dist/astro-location-selector/index.ts +1 -0
- package/dist/calendar/calendar.ts +11 -6
- package/dist/call-controls/call-controls.ts +2 -4
- package/dist/carousel/carousel.ts +45 -0
- package/dist/carousel/index.ts +9 -1
- package/dist/command-input/command-input.ts +5 -5
- package/dist/conversation/conversation.ts +9 -7
- package/dist/conversation/types.ts +2 -0
- package/dist/date-picker/date-picker.ts +6 -3
- package/dist/device-frame/device-frame.ts +2 -4
- package/dist/emoji-picker/emoji-picker.ts +2 -3
- package/dist/footer/footer.ts +4 -1
- package/dist/i18n/countries.ts +122 -0
- package/dist/i18n/detect-region.ts +243 -0
- package/dist/i18n/index.ts +47 -0
- package/dist/i18n/languages.ts +181 -0
- package/dist/i18n/locale-utils.ts +123 -0
- package/dist/i18n/voice-registry.ts +92 -0
- package/dist/index.ts +1 -0
- package/dist/location-selector/index.ts +1 -0
- package/dist/location-selector/location-selector.ts +60 -0
- package/dist/logger/telemetry-manager.ts +7 -4
- package/dist/logger/types.ts +4 -0
- package/dist/pagination/index.ts +14 -1
- package/dist/pagination/pagination.styles.ts +33 -0
- package/dist/pagination/pagination.ts +160 -0
- package/dist/presence-indicator/presence-indicator.ts +2 -4
- package/dist/reaction-bar/reaction-bar.ts +2 -3
- package/dist/slider/slider.ts +134 -2
- package/dist/status-indicator/status-indicator.ts +2 -4
- package/dist/thread-view/thread-view.ts +6 -7
- package/dist/toast/toast.ts +16 -5
- package/dist/voice-pill/voice-pill.ts +2 -4
- package/package.json +2 -1
package/dist/slider/slider.ts
CHANGED
|
@@ -1,2 +1,134 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @refraction-ui/slider — headless slider.
|
|
3
|
+
*
|
|
4
|
+
* Owns the *behavior* of a single-value range slider: min/max clamping,
|
|
5
|
+
* step rounding, and keyboard increment/decrement semantics. It has NO UI
|
|
6
|
+
* opinion — adapters render the control (a native `<input type="range">` in
|
|
7
|
+
* React, which already applies these same keyboard rules natively) and spread
|
|
8
|
+
* the ARIA object.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Options for {@link createSlider}. */
|
|
12
|
+
export interface SliderProps {
|
|
13
|
+
/** Current value. Clamped into `[min, max]` and rounded to `step`. */
|
|
14
|
+
value?: number
|
|
15
|
+
/** Minimum selectable value (default 0). */
|
|
16
|
+
min?: number
|
|
17
|
+
/** Maximum selectable value (default 100). */
|
|
18
|
+
max?: number
|
|
19
|
+
/** Granularity between selectable values (default 1). */
|
|
20
|
+
step?: number
|
|
21
|
+
/** Disables the control. */
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
/** Accessible label attached to the slider, when provided. */
|
|
24
|
+
'aria-label'?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The computed view of a slider control. */
|
|
28
|
+
export interface SliderAPI {
|
|
29
|
+
/** Normalized value (clamped + step-rounded). */
|
|
30
|
+
value: number
|
|
31
|
+
min: number
|
|
32
|
+
max: number
|
|
33
|
+
step: number
|
|
34
|
+
/** Value position within the range, 0–100. */
|
|
35
|
+
percentage: number
|
|
36
|
+
/** ARIA attributes for the slider element (`role="slider"` + value attrs). */
|
|
37
|
+
ariaProps: Record<string, string | number | boolean>
|
|
38
|
+
/** Data attributes for styling hooks. */
|
|
39
|
+
dataAttributes: Record<string, string>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Clamp a value into `[min, max]`. */
|
|
43
|
+
export function clampSliderValue(value: number, min = 0, max = 100): number {
|
|
44
|
+
if (Number.isNaN(value)) return min
|
|
45
|
+
return Math.min(Math.max(value, min), max)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Round a value to the nearest multiple of `step`, offset from `min`.
|
|
50
|
+
* Non-positive steps leave the value unchanged. The result is re-rounded to
|
|
51
|
+
* the step's decimal precision to avoid float noise (e.g. 0.30000000000000004).
|
|
52
|
+
*/
|
|
53
|
+
export function roundToStep(value: number, min = 0, step = 1): number {
|
|
54
|
+
if (!step || step <= 0 || Number.isNaN(value)) return value
|
|
55
|
+
const snapped = min + Math.round((value - min) / step) * step
|
|
56
|
+
const decimals = (String(step).split('.')[1] ?? '').length
|
|
57
|
+
return Number(snapped.toFixed(decimals))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Pure keyboard rule for a slider (WAI-ARIA slider pattern), shared by every
|
|
62
|
+
* adapter that does NOT render a native range input (native inputs already
|
|
63
|
+
* implement these keys):
|
|
64
|
+
*
|
|
65
|
+
* - ArrowRight / ArrowUp → +step
|
|
66
|
+
* - ArrowLeft / ArrowDown → -step
|
|
67
|
+
* - PageUp / PageDown → ±10×step
|
|
68
|
+
* - Home / End → min / max
|
|
69
|
+
*
|
|
70
|
+
* The result is clamped; unhandled keys return `current` unchanged.
|
|
71
|
+
*/
|
|
72
|
+
export function getSliderValueFromKey(
|
|
73
|
+
current: number,
|
|
74
|
+
key: string,
|
|
75
|
+
options: { min?: number; max?: number; step?: number } = {},
|
|
76
|
+
): number {
|
|
77
|
+
const { min = 0, max = 100, step = 1 } = options
|
|
78
|
+
switch (key) {
|
|
79
|
+
case 'ArrowRight':
|
|
80
|
+
case 'ArrowUp':
|
|
81
|
+
return clampSliderValue(current + step, min, max)
|
|
82
|
+
case 'ArrowLeft':
|
|
83
|
+
case 'ArrowDown':
|
|
84
|
+
return clampSliderValue(current - step, min, max)
|
|
85
|
+
case 'PageUp':
|
|
86
|
+
return clampSliderValue(current + step * 10, min, max)
|
|
87
|
+
case 'PageDown':
|
|
88
|
+
return clampSliderValue(current - step * 10, min, max)
|
|
89
|
+
case 'Home':
|
|
90
|
+
return min
|
|
91
|
+
case 'End':
|
|
92
|
+
return max
|
|
93
|
+
default:
|
|
94
|
+
return current
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* createSlider — the framework-agnostic view of a slider: normalized value,
|
|
100
|
+
* percentage position, and the ARIA object (`role="slider"`,
|
|
101
|
+
* `aria-valuemin/max/now`). Adapters own the value state
|
|
102
|
+
* (controlled/uncontrolled) and call this per render.
|
|
103
|
+
*/
|
|
104
|
+
export function createSlider(props: SliderProps = {}): SliderAPI {
|
|
105
|
+
const min = props.min ?? 0
|
|
106
|
+
const max = props.max ?? 100
|
|
107
|
+
const step = props.step ?? 1
|
|
108
|
+
const value = clampSliderValue(roundToStep(props.value ?? min, min, step), min, max)
|
|
109
|
+
const percentage = max === min ? 0 : ((value - min) / (max - min)) * 100
|
|
110
|
+
|
|
111
|
+
const ariaProps: Record<string, string | number | boolean> = {
|
|
112
|
+
role: 'slider',
|
|
113
|
+
'aria-valuemin': min,
|
|
114
|
+
'aria-valuemax': max,
|
|
115
|
+
'aria-valuenow': value,
|
|
116
|
+
'aria-orientation': 'horizontal',
|
|
117
|
+
}
|
|
118
|
+
if (props.disabled) ariaProps['aria-disabled'] = true
|
|
119
|
+
if (props['aria-label']) ariaProps['aria-label'] = props['aria-label']
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
value,
|
|
123
|
+
min,
|
|
124
|
+
max,
|
|
125
|
+
step,
|
|
126
|
+
percentage,
|
|
127
|
+
ariaProps,
|
|
128
|
+
dataAttributes: {
|
|
129
|
+
'data-value': String(value),
|
|
130
|
+
'data-min': String(min),
|
|
131
|
+
'data-max': String(max),
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
-
|
|
3
1
|
export type StatusType = 'success' | 'error' | 'warning' | 'info' | 'pending' | 'neutral'
|
|
4
2
|
|
|
5
3
|
export interface StatusProps {
|
|
@@ -13,7 +11,7 @@ export interface StatusProps {
|
|
|
13
11
|
|
|
14
12
|
export interface StatusAPI {
|
|
15
13
|
/** ARIA attributes for the status indicator */
|
|
16
|
-
ariaProps:
|
|
14
|
+
ariaProps: Record<string, string | number | boolean>
|
|
17
15
|
/** The status type */
|
|
18
16
|
type: StatusType
|
|
19
17
|
/** The display label */
|
|
@@ -54,7 +52,7 @@ export function createStatusIndicator(props: StatusProps): StatusAPI {
|
|
|
54
52
|
const label = labelOverride ?? STATUS_INDICATOR_LABELS[type]
|
|
55
53
|
const pulse = pulseOverride ?? (type === 'pending')
|
|
56
54
|
|
|
57
|
-
const ariaProps:
|
|
55
|
+
const ariaProps: Record<string, string | number | boolean> = {
|
|
58
56
|
role: 'status',
|
|
59
57
|
'aria-label': label,
|
|
60
58
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
1
|
import { generateId } from '../shared/index.ts'
|
|
3
2
|
|
|
4
3
|
export interface MessageReaction {
|
|
@@ -70,11 +69,11 @@ export interface ThreadViewAPI {
|
|
|
70
69
|
/** Format relative time (e.g., "2 minutes ago") */
|
|
71
70
|
formatRelativeTime(date: Date): string
|
|
72
71
|
/** ARIA props for the thread container */
|
|
73
|
-
ariaProps:
|
|
72
|
+
ariaProps: Record<string, string | number | boolean>
|
|
74
73
|
/** Get ARIA props for a message */
|
|
75
|
-
getMessageAriaProps(message: MessageData): Record<string,
|
|
74
|
+
getMessageAriaProps(message: MessageData): Record<string, string | number | boolean>
|
|
76
75
|
/** Get ARIA props for a reply button */
|
|
77
|
-
getReplyButtonAriaProps(messageId: string): Record<string,
|
|
76
|
+
getReplyButtonAriaProps(messageId: string): Record<string, string | number | boolean>
|
|
78
77
|
/** Generated IDs */
|
|
79
78
|
ids: {
|
|
80
79
|
thread: string
|
|
@@ -132,14 +131,14 @@ export function createThreadView(props: ThreadViewProps): ThreadViewAPI {
|
|
|
132
131
|
onReact?.(messageId, emoji)
|
|
133
132
|
}
|
|
134
133
|
|
|
135
|
-
const ariaProps:
|
|
134
|
+
const ariaProps: Record<string, string | number | boolean> = {
|
|
136
135
|
role: 'log',
|
|
137
136
|
'aria-label': 'Message thread',
|
|
138
137
|
'aria-live': 'polite',
|
|
139
138
|
id: threadId,
|
|
140
139
|
}
|
|
141
140
|
|
|
142
|
-
function getMessageAriaProps(message: MessageData): Record<string,
|
|
141
|
+
function getMessageAriaProps(message: MessageData): Record<string, string | number | boolean> {
|
|
143
142
|
const isOwn = currentUserId && message.author.id === currentUserId
|
|
144
143
|
return {
|
|
145
144
|
role: 'article',
|
|
@@ -147,7 +146,7 @@ export function createThreadView(props: ThreadViewProps): ThreadViewAPI {
|
|
|
147
146
|
}
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
function getReplyButtonAriaProps(_messageId: string): Record<string,
|
|
149
|
+
function getReplyButtonAriaProps(_messageId: string): Record<string, string | number | boolean> {
|
|
151
150
|
return {
|
|
152
151
|
role: 'button',
|
|
153
152
|
'aria-label': `Reply to message`,
|
package/dist/toast/toast.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface ToastProps {
|
|
|
25
25
|
open?: boolean
|
|
26
26
|
/** Callback when open state changes */
|
|
27
27
|
onOpenChange?: (open: boolean) => void
|
|
28
|
+
/** Elapsed-time clock for timer pause/resume math (ms). Default: ambient `Date.now()` — inject for deterministic tests. */
|
|
29
|
+
now?: () => number
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface ToastState {
|
|
@@ -55,6 +57,8 @@ export function createToast(props: ToastProps = {}): ToastAPI {
|
|
|
55
57
|
onOpenChange,
|
|
56
58
|
} = props
|
|
57
59
|
|
|
60
|
+
// Injection boundary: ambient time is the default; inject `now` for deterministic timer math.
|
|
61
|
+
const now = props.now ?? (() => Date.now())
|
|
58
62
|
let isOpen = controlledOpen ?? true
|
|
59
63
|
let timerId: ReturnType<typeof setTimeout> | null = null
|
|
60
64
|
let remaining = duration
|
|
@@ -77,21 +81,21 @@ export function createToast(props: ToastProps = {}): ToastAPI {
|
|
|
77
81
|
if (duration <= 0) return
|
|
78
82
|
clearTimer()
|
|
79
83
|
remaining = duration
|
|
80
|
-
startedAt =
|
|
84
|
+
startedAt = now()
|
|
81
85
|
timerId = setTimeout(dismiss, remaining)
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
function pauseTimer(): void {
|
|
85
89
|
if (timerId === null) return
|
|
86
90
|
clearTimer()
|
|
87
|
-
remaining = remaining - (
|
|
91
|
+
remaining = remaining - (now() - startedAt)
|
|
88
92
|
if (remaining < 0) remaining = 0
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
function resumeTimer(): void {
|
|
92
96
|
if (duration <= 0 || remaining <= 0) return
|
|
93
97
|
clearTimer()
|
|
94
|
-
startedAt =
|
|
98
|
+
startedAt = now()
|
|
95
99
|
timerId = setTimeout(dismiss, remaining)
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -136,7 +140,14 @@ export interface ToastManagerAPI {
|
|
|
136
140
|
subscribe(fn: (toasts: ReadonlyArray<ToastEntry>) => void): () => void
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
export
|
|
143
|
+
export interface ToastManagerConfig {
|
|
144
|
+
/** Clock for entry `createdAt` stamps (ms). Default: ambient `Date.now()` — inject for deterministic tests. */
|
|
145
|
+
now?: () => number
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function createToastManager(config: ToastManagerConfig = {}): ToastManagerAPI {
|
|
149
|
+
// Injection boundary: ambient time is the default; inject `now` for deterministic createdAt.
|
|
150
|
+
const now = config.now ?? (() => Date.now())
|
|
140
151
|
let toasts: ToastEntry[] = []
|
|
141
152
|
const listeners = new Set<(toasts: ReadonlyArray<ToastEntry>) => void>()
|
|
142
153
|
const timers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
@@ -171,7 +182,7 @@ export function createToastManager(): ToastManagerAPI {
|
|
|
171
182
|
message,
|
|
172
183
|
variant,
|
|
173
184
|
duration,
|
|
174
|
-
createdAt:
|
|
185
|
+
createdAt: now(),
|
|
175
186
|
}
|
|
176
187
|
|
|
177
188
|
toasts = [...toasts, entry]
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
-
|
|
3
1
|
export type VoicePillSpeaker = 'ai' | 'user' | (string & {})
|
|
4
2
|
|
|
5
3
|
export type VoicePillPosition =
|
|
@@ -60,7 +58,7 @@ export interface VoicePillAPI {
|
|
|
60
58
|
/** Invoke the mute callback when provided */
|
|
61
59
|
toggleMute(): void
|
|
62
60
|
/** ARIA props for the live status pill */
|
|
63
|
-
ariaProps:
|
|
61
|
+
ariaProps: Record<string, string | number | boolean>
|
|
64
62
|
/** ARIA props for the mute toggle */
|
|
65
63
|
toggleMuteAriaProps: Record<string, unknown>
|
|
66
64
|
/** Data attributes reflecting speaker/state */
|
|
@@ -196,7 +194,7 @@ export function createVoicePill(props: VoicePillProps): VoicePillAPI {
|
|
|
196
194
|
onToggleMute?.()
|
|
197
195
|
}
|
|
198
196
|
|
|
199
|
-
const ariaProps:
|
|
197
|
+
const ariaProps: Record<string, string | number | boolean> = {
|
|
200
198
|
role: 'status',
|
|
201
199
|
'aria-live': 'polite',
|
|
202
200
|
'aria-atomic': true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@refraction-ui/astro",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.2",
|
|
4
4
|
"description": "All Refraction UI Astro components in one package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"@refraction-ui/astro-live-captions": "workspace:*",
|
|
85
85
|
"@refraction-ui/astro-live-cursors": "workspace:*",
|
|
86
86
|
"@refraction-ui/astro-live-transcript": "workspace:*",
|
|
87
|
+
"@refraction-ui/astro-location-selector": "workspace:*",
|
|
87
88
|
"@refraction-ui/astro-logger": "workspace:*",
|
|
88
89
|
"@refraction-ui/astro-markdown-renderer": "workspace:*",
|
|
89
90
|
"@refraction-ui/astro-marquee-strip": "workspace:*",
|