@refraction-ui/astro 0.12.0 → 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 (117) 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-floating-reactions/FloatingReactions.astro +56 -0
  12. package/dist/astro-floating-reactions/index.ts +1 -0
  13. package/dist/astro-flow-editor/FlowEditor.astro +99 -0
  14. package/dist/astro-flow-editor/index.ts +3 -0
  15. package/dist/astro-graph-view/GraphView.astro +109 -0
  16. package/dist/astro-graph-view/index.ts +1 -0
  17. package/dist/astro-infinite-canvas/InfiniteCanvas.astro +43 -0
  18. package/dist/astro-infinite-canvas/index.ts +1 -0
  19. package/dist/astro-live-captions/LiveCaptions.astro +54 -0
  20. package/dist/astro-live-captions/index.ts +1 -0
  21. package/dist/astro-live-cursors/LiveCursors.astro +55 -0
  22. package/dist/astro-live-cursors/index.ts +1 -0
  23. package/dist/astro-live-transcript/LiveTranscript.astro +55 -0
  24. package/dist/astro-live-transcript/index.ts +1 -0
  25. package/dist/astro-mini-map/MiniMap.astro +74 -0
  26. package/dist/astro-mini-map/index.ts +1 -0
  27. package/dist/astro-pre-call-lobby/PreCallLobby.astro +202 -0
  28. package/dist/astro-pre-call-lobby/index.ts +1 -0
  29. package/dist/astro-radial-gauge/RadialGauge.astro +144 -0
  30. package/dist/astro-radial-gauge/index.ts +1 -0
  31. package/dist/astro-rating-scale/RatingScale.astro +73 -0
  32. package/dist/astro-rating-scale/index.ts +1 -0
  33. package/dist/astro-sticky-note/StickyNote.astro +55 -0
  34. package/dist/astro-sticky-note/index.ts +1 -0
  35. package/dist/astro-terminal/Terminal.astro +67 -0
  36. package/dist/astro-terminal/index.ts +1 -0
  37. package/dist/astro-test-results/TestResults.astro +76 -0
  38. package/dist/astro-test-results/index.ts +1 -0
  39. package/dist/astro-timeline/Timeline.astro +59 -0
  40. package/dist/astro-timeline/index.ts +1 -0
  41. package/dist/astro-video-grid/VideoGrid.astro +182 -0
  42. package/dist/astro-video-grid/index.ts +1 -0
  43. package/dist/astro-video-tile/VideoTile.astro +104 -0
  44. package/dist/astro-video-tile/index.ts +1 -0
  45. package/dist/astro-wizard/Wizard.astro +137 -0
  46. package/dist/astro-wizard/index.ts +1 -0
  47. package/dist/audio-room/audio-room.styles.ts +88 -0
  48. package/dist/audio-room/audio-room.ts +70 -0
  49. package/dist/audio-room/index.ts +16 -0
  50. package/dist/call-controls/call-controls.styles.ts +63 -0
  51. package/dist/call-controls/call-controls.ts +79 -0
  52. package/dist/call-controls/index.ts +15 -0
  53. package/dist/checklist/checklist.styles.ts +76 -0
  54. package/dist/checklist/checklist.ts +62 -0
  55. package/dist/checklist/index.ts +18 -0
  56. package/dist/editor-status-bar/editor-status-bar.styles.ts +40 -0
  57. package/dist/editor-status-bar/editor-status-bar.ts +78 -0
  58. package/dist/editor-status-bar/index.ts +15 -0
  59. package/dist/editor-tabs/editor-tabs.styles.ts +52 -0
  60. package/dist/editor-tabs/editor-tabs.ts +82 -0
  61. package/dist/editor-tabs/index.ts +15 -0
  62. package/dist/floating-reactions/floating-reactions.styles.ts +54 -0
  63. package/dist/floating-reactions/floating-reactions.ts +85 -0
  64. package/dist/floating-reactions/index.ts +14 -0
  65. package/dist/flow-editor/flow-editor.styles.ts +56 -0
  66. package/dist/flow-editor/flow-editor.ts +126 -0
  67. package/dist/flow-editor/index.ts +18 -0
  68. package/dist/graph-view/graph-view.styles.ts +66 -0
  69. package/dist/graph-view/graph-view.ts +130 -0
  70. package/dist/graph-view/index.ts +21 -0
  71. package/dist/index.ts +23 -0
  72. package/dist/infinite-canvas/index.ts +17 -0
  73. package/dist/infinite-canvas/infinite-canvas.styles.ts +49 -0
  74. package/dist/infinite-canvas/infinite-canvas.ts +97 -0
  75. package/dist/live-captions/index.ts +13 -0
  76. package/dist/live-captions/live-captions.styles.ts +45 -0
  77. package/dist/live-captions/live-captions.ts +57 -0
  78. package/dist/live-cursors/index.ts +14 -0
  79. package/dist/live-cursors/live-cursors.styles.ts +41 -0
  80. package/dist/live-cursors/live-cursors.ts +73 -0
  81. package/dist/live-transcript/index.ts +16 -0
  82. package/dist/live-transcript/live-transcript.styles.ts +48 -0
  83. package/dist/live-transcript/live-transcript.ts +82 -0
  84. package/dist/mini-map/index.ts +16 -0
  85. package/dist/mini-map/mini-map.styles.ts +32 -0
  86. package/dist/mini-map/mini-map.ts +122 -0
  87. package/dist/pre-call-lobby/index.ts +22 -0
  88. package/dist/pre-call-lobby/pre-call-lobby.styles.ts +108 -0
  89. package/dist/pre-call-lobby/pre-call-lobby.ts +61 -0
  90. package/dist/radial-gauge/index.ts +26 -0
  91. package/dist/radial-gauge/radial-gauge.styles.ts +75 -0
  92. package/dist/radial-gauge/radial-gauge.ts +183 -0
  93. package/dist/rating-scale/index.ts +16 -0
  94. package/dist/rating-scale/rating-scale.styles.ts +48 -0
  95. package/dist/rating-scale/rating-scale.ts +101 -0
  96. package/dist/sticky-note/index.ts +14 -0
  97. package/dist/sticky-note/sticky-note.styles.ts +37 -0
  98. package/dist/sticky-note/sticky-note.ts +47 -0
  99. package/dist/terminal/index.ts +13 -0
  100. package/dist/terminal/terminal.styles.ts +56 -0
  101. package/dist/terminal/terminal.ts +60 -0
  102. package/dist/test-results/index.ts +19 -0
  103. package/dist/test-results/test-results.styles.ts +93 -0
  104. package/dist/test-results/test-results.ts +83 -0
  105. package/dist/timeline/index.ts +22 -0
  106. package/dist/timeline/timeline.styles.ts +98 -0
  107. package/dist/timeline/timeline.ts +86 -0
  108. package/dist/video-grid/index.ts +16 -0
  109. package/dist/video-grid/video-grid.styles.ts +38 -0
  110. package/dist/video-grid/video-grid.ts +93 -0
  111. package/dist/video-tile/index.ts +17 -0
  112. package/dist/video-tile/video-tile.styles.ts +59 -0
  113. package/dist/video-tile/video-tile.ts +74 -0
  114. package/dist/wizard/index.ts +28 -0
  115. package/dist/wizard/wizard.styles.ts +147 -0
  116. package/dist/wizard/wizard.ts +128 -0
  117. package/package.json +24 -1
@@ -0,0 +1,130 @@
1
+ /** Visual mastery state of a node in the knowledge graph. */
2
+ export type GraphNodeState = 'mastered' | 'in-progress' | 'not-started' | 'highlight'
3
+
4
+ /** A concept node in the knowledge / dependency graph. */
5
+ export interface GraphNode {
6
+ /** Unique identifier. */
7
+ id: string
8
+ /** X position in the graph canvas (pixels). */
9
+ x: number
10
+ /** Y position in the graph canvas (pixels). */
11
+ y: number
12
+ /** Human-readable label shown in the node chip. */
13
+ label: string
14
+ /** Visual mastery state. Defaults to `'not-started'`. */
15
+ state?: GraphNodeState
16
+ }
17
+
18
+ /** A directed edge connecting two nodes. */
19
+ export interface GraphEdge {
20
+ /** Unique identifier. */
21
+ id: string
22
+ /** ID of the source node. */
23
+ source: string
24
+ /** ID of the target node. */
25
+ target: string
26
+ }
27
+
28
+ /** Axis-aligned bounding box of a set of nodes. */
29
+ export interface GraphBounds {
30
+ minX: number
31
+ minY: number
32
+ maxX: number
33
+ maxY: number
34
+ width: number
35
+ height: number
36
+ }
37
+
38
+ /** Per-state counts returned by {@link summarizeStates}. */
39
+ export type GraphStateSummary = Record<GraphNodeState, number>
40
+
41
+ export interface GraphViewAPI {
42
+ /** ARIA/role attributes to spread on the graph container. */
43
+ ariaProps: { role: string; 'aria-label': string }
44
+ /** Data attributes for styling hooks. */
45
+ dataAttributes: Record<string, string>
46
+ }
47
+
48
+ /**
49
+ * Build framework-agnostic container props for a GraphView.
50
+ *
51
+ * The graph is a read-only knowledge / dependency visualisation. It uses
52
+ * `role="group"` (not `role="img"`) so that interactive node chips inside it
53
+ * remain in the accessibility tree and keyboard reachable.
54
+ */
55
+ export function createGraphView(): GraphViewAPI {
56
+ return {
57
+ ariaProps: {
58
+ role: 'group',
59
+ 'aria-label': 'Knowledge graph',
60
+ },
61
+ dataAttributes: {},
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Look up a node by id. Returns `undefined` when not found.
67
+ */
68
+ export function nodeById(nodes: GraphNode[], id: string): GraphNode | undefined {
69
+ return nodes.find((n) => n.id === id)
70
+ }
71
+
72
+ /**
73
+ * Build an SVG cubic-bezier path `d` string between two node positions.
74
+ *
75
+ * The control points bend the curve vertically so edges arc gracefully between
76
+ * horizontally or vertically offset nodes. Works for any pair of positions.
77
+ */
78
+ export function graphEdgePath(source: GraphNode, target: GraphNode): string {
79
+ const sx = source.x
80
+ const sy = source.y
81
+ const tx = target.x
82
+ const ty = target.y
83
+
84
+ // Control-point offset: half the vertical distance, minimum 40px.
85
+ const dy = Math.abs(ty - sy)
86
+ const offset = Math.max(dy * 0.5, 40)
87
+
88
+ return `M ${sx} ${sy} C ${sx} ${sy + offset}, ${tx} ${ty - offset}, ${tx} ${ty}`
89
+ }
90
+
91
+ /**
92
+ * Compute the axis-aligned bounding box of a set of nodes.
93
+ *
94
+ * Returns zero-area bounds centred at the origin when the array is empty.
95
+ */
96
+ export function graphBounds(nodes: GraphNode[]): GraphBounds {
97
+ if (nodes.length === 0) {
98
+ return { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 }
99
+ }
100
+ let minX = Infinity
101
+ let minY = Infinity
102
+ let maxX = -Infinity
103
+ let maxY = -Infinity
104
+ for (const n of nodes) {
105
+ if (n.x < minX) minX = n.x
106
+ if (n.y < minY) minY = n.y
107
+ if (n.x > maxX) maxX = n.x
108
+ if (n.y > maxY) maxY = n.y
109
+ }
110
+ return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY }
111
+ }
112
+
113
+ /**
114
+ * Count nodes per mastery state.
115
+ *
116
+ * Nodes without an explicit `state` are counted as `'not-started'`.
117
+ */
118
+ export function summarizeStates(nodes: GraphNode[]): GraphStateSummary {
119
+ const counts: GraphStateSummary = {
120
+ mastered: 0,
121
+ 'in-progress': 0,
122
+ 'not-started': 0,
123
+ highlight: 0,
124
+ }
125
+ for (const n of nodes) {
126
+ const s: GraphNodeState = n.state ?? 'not-started'
127
+ counts[s] += 1
128
+ }
129
+ return counts
130
+ }
@@ -0,0 +1,21 @@
1
+ export {
2
+ createGraphView,
3
+ nodeById,
4
+ graphEdgePath,
5
+ graphBounds,
6
+ summarizeStates,
7
+ type GraphNodeState,
8
+ type GraphNode,
9
+ type GraphEdge,
10
+ type GraphBounds,
11
+ type GraphStateSummary,
12
+ type GraphViewAPI,
13
+ } from './graph-view.js'
14
+
15
+ export {
16
+ graphViewVariants,
17
+ graphNodeVariants,
18
+ graphNodeLabelClass,
19
+ graphEdgeClass,
20
+ graphLegendChipVariants,
21
+ } from './graph-view.styles.js'
package/dist/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from './astro-ai/index.ts';
5
5
  export * from './astro-analytics/index.ts';
6
6
  export * from './astro-animated-text/index.ts';
7
7
  export * from './astro-app-shell/index.ts';
8
+ export * from './astro-audio-room/index.ts';
8
9
  export * from './astro-auth/index.ts';
9
10
  export * from './astro-avatar/index.ts';
10
11
  export * from './astro-avatar-group/index.ts';
@@ -13,12 +14,14 @@ export * from './astro-bottom-nav/index.ts';
13
14
  export * from './astro-breadcrumbs/index.ts';
14
15
  export * from './astro-button/index.ts';
15
16
  export * from './astro-calendar/index.ts';
17
+ export * from './astro-call-controls/index.ts';
16
18
  export * from './astro-callout/index.ts';
17
19
  export * from './astro-card/index.ts';
18
20
  export * from './astro-card-grid/index.ts';
19
21
  export * from './astro-carousel/index.ts';
20
22
  export * from './astro-charts/index.ts';
21
23
  export * from './astro-checkbox/index.ts';
24
+ export * from './astro-checklist/index.ts';
22
25
  export * from './astro-code-block/index.ts';
23
26
  export * from './astro-code-editor/index.ts';
24
27
  export * from './astro-collapsible/index.ts';
@@ -33,13 +36,19 @@ export * from './astro-device-frame/index.ts';
33
36
  export * from './astro-dialog/index.ts';
34
37
  export * from './astro-diff-viewer/index.ts';
35
38
  export * from './astro-dropdown-menu/index.ts';
39
+ export * from './astro-editor-status-bar/index.ts';
40
+ export * from './astro-editor-tabs/index.ts';
36
41
  export * from './astro-emoji-picker/index.ts';
37
42
  export * from './astro-empty-state/index.ts';
38
43
  export * from './astro-feedback-dialog/index.ts';
39
44
  export * from './astro-file-tree/index.ts';
40
45
  export * from './astro-file-upload/index.ts';
46
+ export * from './astro-floating-reactions/index.ts';
47
+ export * from './astro-flow-editor/index.ts';
41
48
  export * from './astro-footer/index.ts';
49
+ export * from './astro-graph-view/index.ts';
42
50
  export * from './astro-icon-system/index.ts';
51
+ export * from './astro-infinite-canvas/index.ts';
43
52
  export * from './astro-inline-editor/index.ts';
44
53
  export * from './astro-input/index.ts';
45
54
  export * from './astro-input-group/index.ts';
@@ -47,8 +56,12 @@ export * from './astro-install-prompt/index.ts';
47
56
  export * from './astro-keyboard-shortcut/index.ts';
48
57
  export * from './astro-language-selector/index.ts';
49
58
  export * from './astro-link-card/index.ts';
59
+ export * from './astro-live-captions/index.ts';
60
+ export * from './astro-live-cursors/index.ts';
61
+ export * from './astro-live-transcript/index.ts';
50
62
  export * from './astro-logger/index.ts';
51
63
  export * from './astro-markdown-renderer/index.ts';
64
+ export * from './astro-mini-map/index.ts';
52
65
  export * from './astro-mobile-nav/index.ts';
53
66
  export * from './astro-navbar/index.ts';
54
67
  export * from './astro-otp-input/index.ts';
@@ -56,9 +69,12 @@ export * from './astro-pagination/index.ts';
56
69
  export * from './astro-password-input/index.ts';
57
70
  export * from './astro-payment/index.ts';
58
71
  export * from './astro-popover/index.ts';
72
+ export * from './astro-pre-call-lobby/index.ts';
59
73
  export * from './astro-presence-indicator/index.ts';
60
74
  export * from './astro-progress-display/index.ts';
75
+ export * from './astro-radial-gauge/index.ts';
61
76
  export * from './astro-radio/index.ts';
77
+ export * from './astro-rating-scale/index.ts';
62
78
  export * from './astro-reaction-bar/index.ts';
63
79
  export * from './astro-resizable-layout/index.ts';
64
80
  export * from './astro-rich-editor/index.ts';
@@ -74,15 +90,22 @@ export * from './astro-slider/index.ts';
74
90
  export * from './astro-social-auth-button/index.ts';
75
91
  export * from './astro-status-indicator/index.ts';
76
92
  export * from './astro-steps/index.ts';
93
+ export * from './astro-sticky-note/index.ts';
77
94
  export * from './astro-switch/index.ts';
78
95
  export * from './astro-table-of-contents/index.ts';
79
96
  export * from './astro-tabs/index.ts';
97
+ export * from './astro-terminal/index.ts';
98
+ export * from './astro-test-results/index.ts';
80
99
  export * from './astro-textarea/index.ts';
81
100
  export * from './astro-theme/index.ts';
82
101
  export * from './astro-thread-view/index.ts';
102
+ export * from './astro-timeline/index.ts';
83
103
  export * from './astro-toast/index.ts';
84
104
  export * from './astro-tooltip/index.ts';
85
105
  export * from './astro-version-selector/index.ts';
106
+ export * from './astro-video-grid/index.ts';
86
107
  export * from './astro-video-player/index.ts';
108
+ export * from './astro-video-tile/index.ts';
87
109
  export * from './astro-voice-pill/index.ts';
88
110
  export * from './astro-waveform/index.ts';
111
+ export * from './astro-wizard/index.ts';
@@ -0,0 +1,17 @@
1
+ export {
2
+ clampZoom,
3
+ fitTransform,
4
+ transformToCss,
5
+ createInfiniteCanvas,
6
+ type CanvasTransform,
7
+ type Bounds,
8
+ type CreateInfiniteCanvasOptions,
9
+ type InfiniteCanvasAPI,
10
+ } from './infinite-canvas.js'
11
+
12
+ export {
13
+ infiniteCanvasVariants,
14
+ canvasContentClass,
15
+ canvasControlsClass,
16
+ canvasZoomButtonVariants,
17
+ } from './infinite-canvas.styles.js'
@@ -0,0 +1,49 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * The outer viewport container. `overflow-hidden` clips panned content;
5
+ * `relative` anchors the absolute controls overlay.
6
+ *
7
+ * The `grid` variant adds a subtle dot-grid background rendered with a radial
8
+ * gradient using the `--border` semantic token so it respects the active theme.
9
+ */
10
+ export const infiniteCanvasVariants = cva({
11
+ base: 'relative overflow-hidden bg-background',
12
+ variants: {
13
+ grid: {
14
+ 'true': [
15
+ 'bg-[radial-gradient(circle,hsl(var(--border))_1px,transparent_1px)]',
16
+ '[background-size:24px_24px]',
17
+ ].join(' '),
18
+ 'false': '',
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ grid: 'false',
23
+ },
24
+ })
25
+
26
+ /**
27
+ * The transformed content layer that wraps children.
28
+ * `transform-gpu` promotes the layer for smooth panning/zooming.
29
+ * `origin-top-left` matches `transformOrigin: '0 0'` in the React adapter.
30
+ */
31
+ export const canvasContentClass =
32
+ 'absolute inset-0 origin-top-left will-change-transform'
33
+
34
+ /** Zoom-controls cluster — pinned to the bottom-right of the viewport. */
35
+ export const canvasControlsClass =
36
+ 'absolute bottom-4 right-4 z-10 flex items-center gap-1'
37
+
38
+ /** Individual zoom button (+ / − / fit). */
39
+ export const canvasZoomButtonVariants = cva({
40
+ base: [
41
+ 'inline-flex h-8 w-8 items-center justify-center rounded-md border border-border',
42
+ 'bg-card text-foreground text-sm font-medium',
43
+ 'hover:bg-muted transition-colors',
44
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
45
+ 'disabled:pointer-events-none disabled:opacity-50',
46
+ ].join(' '),
47
+ variants: {},
48
+ defaultVariants: {},
49
+ })
@@ -0,0 +1,97 @@
1
+ /** Transform state for an infinite canvas viewport. */
2
+ export type CanvasTransform = {
3
+ zoom: number
4
+ x: number
5
+ y: number
6
+ }
7
+
8
+ /** Axis-aligned bounding box of canvas content. */
9
+ export interface Bounds {
10
+ minX: number
11
+ minY: number
12
+ maxX: number
13
+ maxY: number
14
+ }
15
+
16
+ /**
17
+ * Clamp a zoom level to [min, max].
18
+ *
19
+ * Defaults match common design-tool conventions: 25 % (0.25) to 400 % (4).
20
+ */
21
+ export function clampZoom(zoom: number, min = 0.25, max = 4): number {
22
+ return Math.min(Math.max(zoom, min), max)
23
+ }
24
+
25
+ /**
26
+ * Compute the transform that centers `bounds` inside a viewport of size
27
+ * `viewportW × viewportH` with optional uniform `padding` (default 32 px).
28
+ *
29
+ * Returns a `CanvasTransform` clamped to [0.25, 4].
30
+ */
31
+ export function fitTransform(
32
+ bounds: Bounds,
33
+ viewportW: number,
34
+ viewportH: number,
35
+ padding = 32,
36
+ ): CanvasTransform {
37
+ const contentW = bounds.maxX - bounds.minX
38
+ const contentH = bounds.maxY - bounds.minY
39
+
40
+ if (contentW <= 0 || contentH <= 0 || viewportW <= 0 || viewportH <= 0) {
41
+ return { zoom: 1, x: 0, y: 0 }
42
+ }
43
+
44
+ const availW = viewportW - padding * 2
45
+ const availH = viewportH - padding * 2
46
+ const rawZoom = Math.min(availW / contentW, availH / contentH)
47
+ const zoom = clampZoom(rawZoom)
48
+
49
+ // Center the scaled content within the viewport.
50
+ const scaledW = contentW * zoom
51
+ const scaledH = contentH * zoom
52
+ const x = (viewportW - scaledW) / 2 - bounds.minX * zoom
53
+ const y = (viewportH - scaledH) / 2 - bounds.minY * zoom
54
+
55
+ return { zoom, x, y }
56
+ }
57
+
58
+ /**
59
+ * Convert a `CanvasTransform` to a CSS `transform` string suitable for the
60
+ * content layer (`transformOrigin: '0 0'`).
61
+ */
62
+ export function transformToCss(t: CanvasTransform): string {
63
+ return `translate(${t.x}px, ${t.y}px) scale(${t.zoom})`
64
+ }
65
+
66
+ export interface CreateInfiniteCanvasOptions {
67
+ zoom?: number
68
+ }
69
+
70
+ export interface InfiniteCanvasAPI {
71
+ /** ARIA attributes to spread on the viewport element. */
72
+ ariaProps: Record<string, string | number | boolean>
73
+ /** Data attributes for styling hooks. */
74
+ dataAttributes: Record<string, string | number | boolean>
75
+ }
76
+
77
+ /**
78
+ * Build the framework-agnostic props for an infinite canvas viewport.
79
+ *
80
+ * Returns `role="group"` plus data attributes; adapters spread these onto
81
+ * their container element.
82
+ */
83
+ export function createInfiniteCanvas(
84
+ options: CreateInfiniteCanvasOptions = {},
85
+ ): InfiniteCanvasAPI {
86
+ const { zoom = 1 } = options
87
+
88
+ return {
89
+ ariaProps: {
90
+ role: 'group',
91
+ 'aria-label': 'Canvas',
92
+ },
93
+ dataAttributes: {
94
+ 'data-zoom': zoom,
95
+ },
96
+ }
97
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ visibleCues,
3
+ formatCue,
4
+ createLiveCaptions,
5
+ type CaptionCue,
6
+ type LiveCaptionsAPI,
7
+ } from './live-captions.js'
8
+
9
+ export {
10
+ liveCaptionsVariants,
11
+ liveCaptionsCueVariants,
12
+ liveCaptionsSpeakerClass,
13
+ } from './live-captions.styles.js'
@@ -0,0 +1,45 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /**
4
+ * Caption overlay container — an absolute-friendly, translucent dark surface
5
+ * centred over its positioned ancestor (e.g. a video element).
6
+ */
7
+ export const liveCaptionsVariants = cva({
8
+ base: [
9
+ 'relative mx-auto w-full max-w-2xl rounded-xl',
10
+ 'bg-card/80 backdrop-blur-sm',
11
+ 'px-4 py-3 space-y-1',
12
+ ].join(' '),
13
+ variants: {
14
+ /** Controls how the container is positioned in the layout. */
15
+ position: {
16
+ /** Sits in normal document flow (default). */
17
+ static: '',
18
+ /** Absolutely positioned — consumer places it with inset utilities. */
19
+ absolute: 'absolute',
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ position: 'static',
24
+ },
25
+ })
26
+
27
+ /**
28
+ * A single cue line inside the overlay.
29
+ * Non-final (interim) cues get a dimmed italic treatment to signal they may change.
30
+ */
31
+ export const liveCaptionsCueVariants = cva({
32
+ base: 'text-sm leading-snug text-foreground',
33
+ variants: {
34
+ interim: {
35
+ true: 'opacity-60 italic',
36
+ false: '',
37
+ },
38
+ },
39
+ defaultVariants: {
40
+ interim: 'false',
41
+ },
42
+ })
43
+
44
+ /** Speaker-name prefix — rendered slightly muted to distinguish it from the speech text. */
45
+ export const liveCaptionsSpeakerClass = 'font-medium text-muted-foreground'
@@ -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'