@refraction-ui/astro 0.11.1 → 0.13.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 (148) hide show
  1. package/dist/astro-audio-room/AudioRoom.astro +81 -0
  2. package/dist/astro-audio-room/index.ts +1 -0
  3. package/dist/astro-call-controls/CallControls.astro +57 -0
  4. package/dist/astro-call-controls/index.ts +1 -0
  5. package/dist/astro-checklist/Checklist.astro +76 -0
  6. package/dist/astro-checklist/index.ts +1 -0
  7. package/dist/astro-editor-status-bar/EditorStatusBar.astro +92 -0
  8. package/dist/astro-editor-status-bar/index.ts +1 -0
  9. package/dist/astro-editor-tabs/EditorTabs.astro +74 -0
  10. package/dist/astro-editor-tabs/index.ts +1 -0
  11. package/dist/astro-empty-state/EmptyState.astro +57 -0
  12. package/dist/astro-empty-state/index.ts +1 -0
  13. package/dist/astro-floating-reactions/FloatingReactions.astro +56 -0
  14. package/dist/astro-floating-reactions/index.ts +1 -0
  15. package/dist/astro-flow-editor/FlowEditor.astro +99 -0
  16. package/dist/astro-flow-editor/index.ts +3 -0
  17. package/dist/astro-graph-view/GraphView.astro +109 -0
  18. package/dist/astro-graph-view/index.ts +1 -0
  19. package/dist/astro-infinite-canvas/InfiniteCanvas.astro +43 -0
  20. package/dist/astro-infinite-canvas/index.ts +1 -0
  21. package/dist/astro-input/Input.astro +5 -1
  22. package/dist/astro-live-captions/LiveCaptions.astro +54 -0
  23. package/dist/astro-live-captions/index.ts +1 -0
  24. package/dist/astro-live-cursors/LiveCursors.astro +55 -0
  25. package/dist/astro-live-cursors/index.ts +1 -0
  26. package/dist/astro-live-transcript/LiveTranscript.astro +55 -0
  27. package/dist/astro-live-transcript/index.ts +1 -0
  28. package/dist/astro-mini-map/MiniMap.astro +74 -0
  29. package/dist/astro-mini-map/index.ts +1 -0
  30. package/dist/astro-password-input/PasswordInput.astro +102 -0
  31. package/dist/astro-password-input/index.ts +1 -0
  32. package/dist/astro-pre-call-lobby/PreCallLobby.astro +202 -0
  33. package/dist/astro-pre-call-lobby/index.ts +1 -0
  34. package/dist/astro-radial-gauge/RadialGauge.astro +144 -0
  35. package/dist/astro-radial-gauge/index.ts +1 -0
  36. package/dist/astro-rating-scale/RatingScale.astro +73 -0
  37. package/dist/astro-rating-scale/index.ts +1 -0
  38. package/dist/astro-segmented-control/SegmentedControl.astro +106 -0
  39. package/dist/astro-segmented-control/SegmentedControlItem.astro +32 -0
  40. package/dist/astro-segmented-control/index.ts +1 -0
  41. package/dist/astro-separator/Separator.astro +49 -0
  42. package/dist/astro-separator/index.ts +1 -0
  43. package/dist/astro-social-auth-button/SocialAuthButton.astro +110 -0
  44. package/dist/astro-social-auth-button/SocialAuthRow.astro +13 -0
  45. package/dist/astro-social-auth-button/index.ts +1 -0
  46. package/dist/astro-sticky-note/StickyNote.astro +55 -0
  47. package/dist/astro-sticky-note/index.ts +1 -0
  48. package/dist/astro-terminal/Terminal.astro +67 -0
  49. package/dist/astro-terminal/index.ts +1 -0
  50. package/dist/astro-test-results/TestResults.astro +76 -0
  51. package/dist/astro-test-results/index.ts +1 -0
  52. package/dist/astro-timeline/Timeline.astro +59 -0
  53. package/dist/astro-timeline/index.ts +1 -0
  54. package/dist/astro-video-grid/VideoGrid.astro +182 -0
  55. package/dist/astro-video-grid/index.ts +1 -0
  56. package/dist/astro-video-tile/VideoTile.astro +104 -0
  57. package/dist/astro-video-tile/index.ts +1 -0
  58. package/dist/astro-wizard/Wizard.astro +137 -0
  59. package/dist/astro-wizard/index.ts +1 -0
  60. package/dist/audio-room/audio-room.styles.ts +88 -0
  61. package/dist/audio-room/audio-room.ts +70 -0
  62. package/dist/audio-room/index.ts +16 -0
  63. package/dist/call-controls/call-controls.styles.ts +63 -0
  64. package/dist/call-controls/call-controls.ts +79 -0
  65. package/dist/call-controls/index.ts +15 -0
  66. package/dist/checklist/checklist.styles.ts +76 -0
  67. package/dist/checklist/checklist.ts +62 -0
  68. package/dist/checklist/index.ts +18 -0
  69. package/dist/editor-status-bar/editor-status-bar.styles.ts +40 -0
  70. package/dist/editor-status-bar/editor-status-bar.ts +78 -0
  71. package/dist/editor-status-bar/index.ts +15 -0
  72. package/dist/editor-tabs/editor-tabs.styles.ts +52 -0
  73. package/dist/editor-tabs/editor-tabs.ts +82 -0
  74. package/dist/editor-tabs/index.ts +15 -0
  75. package/dist/empty-state/empty-state.styles.ts +42 -0
  76. package/dist/empty-state/empty-state.ts +27 -0
  77. package/dist/empty-state/index.ts +14 -0
  78. package/dist/floating-reactions/floating-reactions.styles.ts +54 -0
  79. package/dist/floating-reactions/floating-reactions.ts +85 -0
  80. package/dist/floating-reactions/index.ts +14 -0
  81. package/dist/flow-editor/flow-editor.styles.ts +56 -0
  82. package/dist/flow-editor/flow-editor.ts +126 -0
  83. package/dist/flow-editor/index.ts +18 -0
  84. package/dist/graph-view/graph-view.styles.ts +66 -0
  85. package/dist/graph-view/graph-view.ts +130 -0
  86. package/dist/graph-view/index.ts +21 -0
  87. package/dist/index.ts +45 -17
  88. package/dist/infinite-canvas/index.ts +17 -0
  89. package/dist/infinite-canvas/infinite-canvas.styles.ts +49 -0
  90. package/dist/infinite-canvas/infinite-canvas.ts +97 -0
  91. package/dist/input/index.ts +2 -0
  92. package/dist/input/input.styles.ts +4 -0
  93. package/dist/input/input.ts +39 -3
  94. package/dist/live-captions/index.ts +13 -0
  95. package/dist/live-captions/live-captions.styles.ts +45 -0
  96. package/dist/live-captions/live-captions.ts +57 -0
  97. package/dist/live-cursors/index.ts +14 -0
  98. package/dist/live-cursors/live-cursors.styles.ts +41 -0
  99. package/dist/live-cursors/live-cursors.ts +73 -0
  100. package/dist/live-transcript/index.ts +16 -0
  101. package/dist/live-transcript/live-transcript.styles.ts +48 -0
  102. package/dist/live-transcript/live-transcript.ts +82 -0
  103. package/dist/mini-map/index.ts +16 -0
  104. package/dist/mini-map/mini-map.styles.ts +32 -0
  105. package/dist/mini-map/mini-map.ts +122 -0
  106. package/dist/password-input/index.ts +9 -0
  107. package/dist/password-input/password-input.styles.ts +10 -0
  108. package/dist/password-input/password-input.ts +45 -0
  109. package/dist/pre-call-lobby/index.ts +22 -0
  110. package/dist/pre-call-lobby/pre-call-lobby.styles.ts +108 -0
  111. package/dist/pre-call-lobby/pre-call-lobby.ts +61 -0
  112. package/dist/radial-gauge/index.ts +26 -0
  113. package/dist/radial-gauge/radial-gauge.styles.ts +75 -0
  114. package/dist/radial-gauge/radial-gauge.ts +183 -0
  115. package/dist/rating-scale/index.ts +16 -0
  116. package/dist/rating-scale/rating-scale.styles.ts +48 -0
  117. package/dist/rating-scale/rating-scale.ts +101 -0
  118. package/dist/segmented-control/index.ts +13 -0
  119. package/dist/segmented-control/segmented-control.styles.ts +42 -0
  120. package/dist/segmented-control/segmented-control.ts +80 -0
  121. package/dist/separator/index.ts +12 -0
  122. package/dist/separator/separator.styles.ts +34 -0
  123. package/dist/separator/separator.ts +53 -0
  124. package/dist/social-auth-button/index.ts +14 -0
  125. package/dist/social-auth-button/social-auth-button.styles.ts +16 -0
  126. package/dist/social-auth-button/social-auth-button.ts +51 -0
  127. package/dist/sticky-note/index.ts +14 -0
  128. package/dist/sticky-note/sticky-note.styles.ts +37 -0
  129. package/dist/sticky-note/sticky-note.ts +47 -0
  130. package/dist/terminal/index.ts +13 -0
  131. package/dist/terminal/terminal.styles.ts +56 -0
  132. package/dist/terminal/terminal.ts +60 -0
  133. package/dist/test-results/index.ts +19 -0
  134. package/dist/test-results/test-results.styles.ts +93 -0
  135. package/dist/test-results/test-results.ts +83 -0
  136. package/dist/timeline/index.ts +22 -0
  137. package/dist/timeline/timeline.styles.ts +98 -0
  138. package/dist/timeline/timeline.ts +86 -0
  139. package/dist/video-grid/index.ts +16 -0
  140. package/dist/video-grid/video-grid.styles.ts +38 -0
  141. package/dist/video-grid/video-grid.ts +93 -0
  142. package/dist/video-tile/index.ts +17 -0
  143. package/dist/video-tile/video-tile.styles.ts +59 -0
  144. package/dist/video-tile/video-tile.ts +74 -0
  145. package/dist/wizard/index.ts +28 -0
  146. package/dist/wizard/wizard.styles.ts +147 -0
  147. package/dist/wizard/wizard.ts +128 -0
  148. package/package.json +47 -19
@@ -0,0 +1,57 @@
1
+ /** A single caption cue — may be interim (non-final) while speech is in flight. */
2
+ export interface CaptionCue {
3
+ /** Unique identifier for this cue. */
4
+ id: string
5
+ /** Speaker name, shown as a prefix when present. */
6
+ speaker?: string
7
+ /** The caption text. */
8
+ text: string
9
+ /** Whether this cue is finalised (true) or still being transcribed (false/absent). */
10
+ final?: boolean
11
+ }
12
+
13
+ /**
14
+ * Return the last `maxLines` cues to display in the caption overlay.
15
+ * Preserves ordering (oldest first within the slice) so the overlay reads top-to-bottom.
16
+ */
17
+ export function visibleCues(cues: CaptionCue[], maxLines = 2): CaptionCue[] {
18
+ if (maxLines <= 0) return []
19
+ return cues.slice(-maxLines)
20
+ }
21
+
22
+ /**
23
+ * Format a single cue for display.
24
+ * When `speaker` is present the result is `'Maya: the bottleneck is review capacity'`.
25
+ * Without a speaker it is just the text.
26
+ */
27
+ export function formatCue(cue: CaptionCue): string {
28
+ return cue.speaker ? `${cue.speaker}: ${cue.text}` : cue.text
29
+ }
30
+
31
+ export interface LiveCaptionsAPI {
32
+ /** ARIA attributes to spread on the caption container element. */
33
+ ariaProps: Record<string, string | number | boolean>
34
+ /** Data attributes for styling hooks. */
35
+ dataAttributes: Record<string, string>
36
+ }
37
+
38
+ /**
39
+ * Build the framework-agnostic accessibility props for a live-caption overlay.
40
+ *
41
+ * Returns `role="log"` with `aria-live="polite"` and `aria-atomic=false` so
42
+ * screen readers announce individual cues as they arrive without re-reading the
43
+ * whole log. Adapters spread these onto the container element.
44
+ */
45
+ export function createLiveCaptions(): LiveCaptionsAPI {
46
+ const ariaProps: Record<string, string | number | boolean> = {
47
+ role: 'log',
48
+ 'aria-live': 'polite',
49
+ 'aria-atomic': false,
50
+ }
51
+
52
+ const dataAttributes: Record<string, string> = {
53
+ 'data-component': 'live-captions',
54
+ }
55
+
56
+ return { ariaProps, dataAttributes }
57
+ }
@@ -0,0 +1,14 @@
1
+ export {
2
+ createLiveCursors,
3
+ assignCursorColor,
4
+ CURSOR_COLORS,
5
+ type CursorData,
6
+ type LiveCursorsAPI,
7
+ } from './live-cursors.js'
8
+
9
+ export {
10
+ liveCursorsVariants,
11
+ cursorWrapperClass,
12
+ cursorArrowClass,
13
+ cursorLabelClass,
14
+ } from './live-cursors.styles.js'
@@ -0,0 +1,41 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Full-bleed overlay that contains all cursor elements.
5
+ * `pointer-events-none` and `overflow-hidden` ensure cursors are decorative
6
+ * and don't interfere with the underlying canvas interactions.
7
+ */
8
+ export const liveCursorsVariants = cva({
9
+ base: 'absolute inset-0 overflow-hidden pointer-events-none',
10
+ variants: {
11
+ /** Future variant hook — reserved for theming overrides. */
12
+ hidden: {
13
+ true: 'invisible',
14
+ false: '',
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ hidden: 'false',
19
+ },
20
+ })
21
+
22
+ /**
23
+ * Wrapper for a single cursor (SVG + label). Absolutely positioned via inline
24
+ * `left`/`top` style; the transition provides smooth movement when coordinates
25
+ * update.
26
+ */
27
+ export const cursorWrapperClass =
28
+ 'absolute flex flex-col items-start transition-[left,top] duration-100 ease-linear'
29
+
30
+ /**
31
+ * The SVG cursor arrow container. Fill is applied via inline style (per
32
+ * collaborator color) so no utility class is needed here.
33
+ */
34
+ export const cursorArrowClass = 'h-5 w-5 drop-shadow-sm'
35
+
36
+ /**
37
+ * Name-label chip that appears below the cursor arrow. Background is applied
38
+ * via inline style (per collaborator color); text is always white for contrast.
39
+ */
40
+ export const cursorLabelClass =
41
+ 'mt-0.5 ml-2 rounded px-1.5 py-0.5 text-xs font-medium text-white shadow-sm whitespace-nowrap'
@@ -0,0 +1,73 @@
1
+ /** Data for a single collaborator cursor. */
2
+ export type CursorData = {
3
+ id: string
4
+ name: string
5
+ x: number
6
+ y: number
7
+ color?: string
8
+ }
9
+
10
+ /**
11
+ * A fixed palette of 8 hex colors for distinguishing collaborator cursors.
12
+ * Each color is vivid enough to be visible against both light and dark surfaces.
13
+ */
14
+ export const CURSOR_COLORS: string[] = [
15
+ '#E94560', // red
16
+ '#F57C00', // orange
17
+ '#F9C840', // yellow
18
+ '#4CAF50', // green
19
+ '#00ACC1', // cyan
20
+ '#5C6BC0', // indigo
21
+ '#AB47BC', // purple
22
+ '#EC407A', // pink
23
+ ]
24
+
25
+ /**
26
+ * Assign a stable color to a cursor.
27
+ *
28
+ * When `index` is supplied the color is simply `CURSOR_COLORS[index % length]`
29
+ * (deterministic for ordered participant lists). Otherwise a lightweight
30
+ * djb2-style hash of the `id` string is used so the same id always maps to the
31
+ * same color across renders without using Math.random or Date.now.
32
+ */
33
+ export function assignCursorColor(id: string, index?: number): string {
34
+ if (index !== undefined) {
35
+ return CURSOR_COLORS[index % CURSOR_COLORS.length]
36
+ }
37
+
38
+ // djb2 hash — deterministic, no side-effects
39
+ let hash = 5381
40
+ for (let i = 0; i < id.length; i++) {
41
+ hash = ((hash << 5) + hash) ^ id.charCodeAt(i)
42
+ hash = hash >>> 0 // keep unsigned 32-bit
43
+ }
44
+ return CURSOR_COLORS[hash % CURSOR_COLORS.length]
45
+ }
46
+
47
+ export interface LiveCursorsAPI {
48
+ /** ARIA attributes to spread on the overlay container. */
49
+ ariaProps: Record<string, string | number | boolean>
50
+ /** Data attributes for styling hooks. */
51
+ dataAttributes: Record<string, string>
52
+ }
53
+
54
+ /**
55
+ * Build the framework-agnostic props for the live-cursors overlay.
56
+ *
57
+ * Cursors are purely decorative real-time presence indicators — aria-hidden
58
+ * true keeps them out of the accessibility tree. The group role is present for
59
+ * semantic correctness should a developer choose to un-hide it.
60
+ */
61
+ export function createLiveCursors(): LiveCursorsAPI {
62
+ const ariaProps: Record<string, string | number | boolean> = {
63
+ role: 'group',
64
+ 'aria-label': 'Collaborators',
65
+ 'aria-hidden': true,
66
+ }
67
+
68
+ const dataAttributes: Record<string, string> = {
69
+ 'data-live-cursors': '',
70
+ }
71
+
72
+ return { ariaProps, dataAttributes }
73
+ }
@@ -0,0 +1,16 @@
1
+ export {
2
+ groupConsecutiveBySpeaker,
3
+ createLiveTranscript,
4
+ type TranscriptEntry,
5
+ type TranscriptBlock,
6
+ type LiveTranscriptAPI,
7
+ } from './live-transcript.js'
8
+
9
+ export {
10
+ liveTranscriptVariants,
11
+ transcriptBlockVariants,
12
+ transcriptSpeakerRowClass,
13
+ transcriptSpeakerNameClass,
14
+ transcriptTimestampClass,
15
+ transcriptTextLineClass,
16
+ } from './live-transcript.styles.js'
@@ -0,0 +1,48 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Scroll container that wraps all speaker blocks.
5
+ * The `compact` variant tightens the gap between blocks.
6
+ */
7
+ export const liveTranscriptVariants = cva({
8
+ base: 'flex flex-col overflow-y-auto p-4 gap-4',
9
+ variants: {
10
+ compact: {
11
+ true: 'gap-2 p-3',
12
+ false: '',
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ compact: 'false',
17
+ },
18
+ })
19
+
20
+ /** A single grouped speaker block (header + text lines). */
21
+ export const transcriptBlockVariants = cva({
22
+ base: 'flex flex-col gap-1',
23
+ variants: {
24
+ compact: {
25
+ true: 'gap-0.5',
26
+ false: '',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ compact: 'false',
31
+ },
32
+ })
33
+
34
+ /** The row containing the speaker name and optional timestamp. */
35
+ export const transcriptSpeakerRowClass =
36
+ 'flex items-baseline gap-2'
37
+
38
+ /** Speaker name badge / label. */
39
+ export const transcriptSpeakerNameClass =
40
+ 'text-sm font-semibold text-foreground leading-snug'
41
+
42
+ /** Timestamp displayed beside the speaker name. */
43
+ export const transcriptTimestampClass =
44
+ 'text-xs text-muted-foreground tabular-nums'
45
+
46
+ /** A single line of transcript text. */
47
+ export const transcriptTextLineClass =
48
+ 'text-sm text-foreground/80 leading-relaxed'
@@ -0,0 +1,82 @@
1
+ /** A single transcript entry from one speaker at a point in time. */
2
+ export interface TranscriptEntry {
3
+ /** Unique identifier for this entry. */
4
+ id: string
5
+ /** Display name of the speaker. */
6
+ speaker: string
7
+ /** The spoken text. */
8
+ text: string
9
+ /** Optional formatted timestamp string (e.g. "0:42"). */
10
+ timestamp?: string
11
+ /** Optional CSS color for the speaker's name (e.g. "#3b82f6"). */
12
+ speakerColor?: string
13
+ }
14
+
15
+ /** A merged block of consecutive entries from the same speaker. */
16
+ export interface TranscriptBlock {
17
+ /** Speaker display name. */
18
+ speaker: string
19
+ /** Timestamp of the first entry in the run (if present). */
20
+ timestamp?: string
21
+ /** Optional CSS color for the speaker name. */
22
+ speakerColor?: string
23
+ /** All text segments from the consecutive run, in order. */
24
+ texts: string[]
25
+ }
26
+
27
+ /**
28
+ * Merge consecutive `TranscriptEntry` items from the same speaker into
29
+ * `TranscriptBlock` objects. A new block is started whenever the speaker
30
+ * changes; the block uses the timestamp of the *first* entry in that run.
31
+ */
32
+ export function groupConsecutiveBySpeaker(
33
+ entries: TranscriptEntry[],
34
+ ): TranscriptBlock[] {
35
+ const blocks: TranscriptBlock[] = []
36
+
37
+ for (const entry of entries) {
38
+ const last = blocks[blocks.length - 1]
39
+ if (last && last.speaker === entry.speaker) {
40
+ last.texts.push(entry.text)
41
+ } else {
42
+ blocks.push({
43
+ speaker: entry.speaker,
44
+ timestamp: entry.timestamp,
45
+ speakerColor: entry.speakerColor,
46
+ texts: [entry.text],
47
+ })
48
+ }
49
+ }
50
+
51
+ return blocks
52
+ }
53
+
54
+ export interface LiveTranscriptAPI {
55
+ /**
56
+ * ARIA attributes to spread on the scroll container.
57
+ * Typed as a plain record so frameworks can spread without fighting their
58
+ * aria-* type narrowing.
59
+ */
60
+ ariaProps: Record<string, string | number | boolean>
61
+ /** Data attributes for styling hooks. */
62
+ dataAttributes: Record<string, string>
63
+ }
64
+
65
+ /**
66
+ * Build the framework-agnostic container props for a live transcript panel.
67
+ *
68
+ * Returns `role="log"` and `aria-live="polite"` so screen readers announce
69
+ * new transcript entries without interrupting the user.
70
+ */
71
+ export function createLiveTranscript(): LiveTranscriptAPI {
72
+ const ariaProps: Record<string, string | number | boolean> = {
73
+ role: 'log',
74
+ 'aria-live': 'polite',
75
+ }
76
+
77
+ const dataAttributes: Record<string, string> = {
78
+ 'data-component': 'live-transcript',
79
+ }
80
+
81
+ return { ariaProps, dataAttributes }
82
+ }
@@ -0,0 +1,16 @@
1
+ export {
2
+ contentBounds,
3
+ miniScale,
4
+ worldToMini,
5
+ viewportRectInMini,
6
+ createMiniMap,
7
+ type Rect,
8
+ type MiniMapItem,
9
+ type MiniMapAPI,
10
+ } from './mini-map.js'
11
+
12
+ export {
13
+ miniMapVariants,
14
+ miniMapDotClass,
15
+ miniMapViewportClass,
16
+ } from './mini-map.styles.js'
@@ -0,0 +1,32 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * The outer minimap box: small, rounded, bordered, card background.
5
+ * The `interactive` variant switches to grab cursor for clickable minimaps.
6
+ */
7
+ export const miniMapVariants = cva({
8
+ base: [
9
+ 'relative overflow-hidden rounded-md border border-border bg-card',
10
+ 'select-none',
11
+ ].join(' '),
12
+ variants: {
13
+ interactive: {
14
+ true: 'cursor-grab active:cursor-grabbing',
15
+ false: '',
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ interactive: 'false',
20
+ },
21
+ })
22
+
23
+ /** A single node dot in the minimap (represents one `MiniMapItem`). */
24
+ export const miniMapDotClass =
25
+ 'absolute rounded-sm bg-muted'
26
+
27
+ /**
28
+ * The draggable viewport indicator rectangle.
29
+ * Uses a semi-transparent primary fill with a primary ring/border.
30
+ */
31
+ export const miniMapViewportClass =
32
+ 'absolute rounded-sm border border-primary ring-1 ring-primary bg-primary/10'
@@ -0,0 +1,122 @@
1
+ /** A rectangle in 2-D space. */
2
+ export interface Rect {
3
+ x: number
4
+ y: number
5
+ width: number
6
+ height: number
7
+ }
8
+
9
+ /** A single item to be represented as a dot on the minimap. */
10
+ export interface MiniMapItem {
11
+ id: string
12
+ x: number
13
+ y: number
14
+ width?: number
15
+ height?: number
16
+ }
17
+
18
+ export interface MiniMapAPI {
19
+ /** ARIA attributes to spread on the minimap container element. */
20
+ ariaProps: { role: 'img'; 'aria-label': string }
21
+ /** Data attributes for styling hooks. */
22
+ dataAttributes: Record<string, string>
23
+ }
24
+
25
+ /**
26
+ * Compute the bounding box that encloses all items (using their x/y + optional
27
+ * width/height). Returns a zero-size rect at the origin when `items` is empty.
28
+ */
29
+ export function contentBounds(items: MiniMapItem[]): Rect {
30
+ if (items.length === 0) return { x: 0, y: 0, width: 0, height: 0 }
31
+
32
+ let minX = Infinity
33
+ let minY = Infinity
34
+ let maxX = -Infinity
35
+ let maxY = -Infinity
36
+
37
+ for (const item of items) {
38
+ const right = item.x + (item.width ?? 0)
39
+ const bottom = item.y + (item.height ?? 0)
40
+ if (item.x < minX) minX = item.x
41
+ if (item.y < minY) minY = item.y
42
+ if (right > maxX) maxX = right
43
+ if (bottom > maxY) maxY = bottom
44
+ }
45
+
46
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
47
+ }
48
+
49
+ /**
50
+ * Compute the uniform scale factor that fits `contentRect` into the minimap
51
+ * box (after subtracting `padding` on each side). Returns 1 when the content
52
+ * has zero area (nothing to scale).
53
+ */
54
+ export function miniScale(
55
+ contentRect: Rect,
56
+ miniW: number,
57
+ miniH: number,
58
+ padding = 8,
59
+ ): number {
60
+ const availW = miniW - padding * 2
61
+ const availH = miniH - padding * 2
62
+ if (availW <= 0 || availH <= 0) return 1
63
+ if (contentRect.width === 0 || contentRect.height === 0) return 1
64
+ return Math.min(availW / contentRect.width, availH / contentRect.height)
65
+ }
66
+
67
+ /**
68
+ * Map a world-space point `{ x, y }` to minimap pixel coordinates.
69
+ *
70
+ * @param point World coordinate
71
+ * @param contentRect Bounding box of all content (from `contentBounds`)
72
+ * @param scale Scale factor (from `miniScale`)
73
+ * @param padding Padding inset in px (must match the value passed to `miniScale`)
74
+ */
75
+ export function worldToMini(
76
+ point: { x: number; y: number },
77
+ contentRect: Rect,
78
+ scale: number,
79
+ padding = 8,
80
+ ): { x: number; y: number } {
81
+ return {
82
+ x: (point.x - contentRect.x) * scale + padding,
83
+ y: (point.y - contentRect.y) * scale + padding,
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Compute the position and size of the viewport indicator rectangle in
89
+ * minimap coordinates, ready to be applied as CSS `left/top/width/height`.
90
+ */
91
+ export function viewportRectInMini(
92
+ viewport: Rect,
93
+ contentRect: Rect,
94
+ scale: number,
95
+ padding = 8,
96
+ ): Rect {
97
+ const { x, y } = worldToMini(
98
+ { x: viewport.x, y: viewport.y },
99
+ contentRect,
100
+ scale,
101
+ padding,
102
+ )
103
+ return {
104
+ x,
105
+ y,
106
+ width: viewport.width * scale,
107
+ height: viewport.height * scale,
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Build the framework-agnostic ARIA props for the minimap container.
113
+ *
114
+ * Every adapter spreads `ariaProps` onto its outermost element; `dataAttributes`
115
+ * are available for CSS hooks.
116
+ */
117
+ export function createMiniMap(): MiniMapAPI {
118
+ return {
119
+ ariaProps: { role: 'img', 'aria-label': 'Minimap' },
120
+ dataAttributes: { 'data-component': 'mini-map' },
121
+ }
122
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ createPasswordInput,
3
+ DEFAULT_REVEAL_LABEL,
4
+ DEFAULT_HIDE_LABEL,
5
+ type PasswordInputProps,
6
+ type PasswordInputAPI,
7
+ } from './password-input.js'
8
+
9
+ export { passwordToggleVariants } from './password-input.styles.js'
@@ -0,0 +1,10 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Positioning classes for the reveal/hide toggle button rendered inside a
5
+ * password input's trailing padding. The wrapper must be `relative` and the
6
+ * input must reserve trailing space (e.g. `pr-10`).
7
+ */
8
+ export const passwordToggleVariants = cva({
9
+ base: 'absolute right-2 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
10
+ })
@@ -0,0 +1,45 @@
1
+ export interface PasswordInputProps {
2
+ /** Whether the password is currently revealed (shown as plain text). */
3
+ revealed?: boolean
4
+ /**
5
+ * Accessible label for the reveal (show password) action.
6
+ * @default 'Show password'
7
+ */
8
+ revealLabel?: string
9
+ /**
10
+ * Accessible label for the hide action.
11
+ * @default 'Hide password'
12
+ */
13
+ hideLabel?: string
14
+ }
15
+
16
+ export interface PasswordInputAPI {
17
+ /** The `type` attribute for the underlying input given the revealed state. */
18
+ inputType: 'text' | 'password'
19
+ /** The accessible label for the toggle button given the revealed state. */
20
+ toggleLabel: string
21
+ /** The `aria-pressed` value for the toggle button. */
22
+ togglePressed: boolean
23
+ }
24
+
25
+ export const DEFAULT_REVEAL_LABEL = 'Show password'
26
+ export const DEFAULT_HIDE_LABEL = 'Hide password'
27
+
28
+ /**
29
+ * State-free password-input helper. Given a `revealed` flag (owned by the
30
+ * framework adapter), it returns the input `type`, the toggle button's
31
+ * accessible label, and its `aria-pressed` value. It does not hold state.
32
+ */
33
+ export function createPasswordInput(props: PasswordInputProps = {}): PasswordInputAPI {
34
+ const {
35
+ revealed = false,
36
+ revealLabel = DEFAULT_REVEAL_LABEL,
37
+ hideLabel = DEFAULT_HIDE_LABEL,
38
+ } = props
39
+
40
+ return {
41
+ inputType: revealed ? 'text' : 'password',
42
+ toggleLabel: revealed ? hideLabel : revealLabel,
43
+ togglePressed: revealed,
44
+ }
45
+ }
@@ -0,0 +1,22 @@
1
+ export {
2
+ createPreCallLobby,
3
+ micLevelToBars,
4
+ type MediaDeviceOption,
5
+ type DeviceKind,
6
+ type PreCallLobbyProps,
7
+ type PreCallLobbyAPI,
8
+ } from './pre-call-lobby.js'
9
+
10
+ export {
11
+ preCallLobbyVariants,
12
+ preCallCameraVariants,
13
+ preCallCameraPlaceholderClass,
14
+ preCallDeviceRowClass,
15
+ preCallDeviceLabelClass,
16
+ preCallDeviceSelectClass,
17
+ preCallMicMeterClass,
18
+ preCallMicBarVariants,
19
+ preCallControlsRowClass,
20
+ preCallToggleVariants,
21
+ preCallJoinButtonClass,
22
+ } from './pre-call-lobby.styles.js'