@roku-sdk/components 0.0.0-stage → 2.19.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 (212) hide show
  1. package/BREAKING_CHANGES.md +84 -0
  2. package/LICENSE +21 -0
  3. package/README.md +19 -2
  4. package/core-jsx.d.ts +53 -0
  5. package/eslint.config.mjs +3 -0
  6. package/jsx.d.ts +273 -0
  7. package/package.json +85 -3
  8. package/src/__tests__/container.test.tsx +150 -0
  9. package/src/__tests__/effect-gradient-validation.test.tsx +133 -0
  10. package/src/__tests__/glyphs.test.ts +202 -0
  11. package/src/__tests__/modal-boundary-export.test.ts +12 -0
  12. package/src/__tests__/native-modal.test.tsx +224 -0
  13. package/src/__tests__/tween-release.test.tsx +125 -0
  14. package/src/__tests__/tween-stops-shape.test.tsx +165 -0
  15. package/src/__tests__/tween-without-compat.test.tsx +64 -0
  16. package/src/__tests__/tween.test.tsx +195 -0
  17. package/src/blended-busy-spinner.props.ts +37 -0
  18. package/src/blended-busy-spinner.tsx +13 -0
  19. package/src/bounding-tracker.props.ts +18 -0
  20. package/src/bounding-tracker.tsx +13 -0
  21. package/src/button-row.tsx +126 -0
  22. package/src/channel-store.props.ts +425 -0
  23. package/src/channel-store.tsx +76 -0
  24. package/src/container.tsx +130 -0
  25. package/src/core/jsx-runtime.ts +39 -0
  26. package/src/focusable-group.props.ts +8 -0
  27. package/src/focusable-group.tsx +17 -0
  28. package/src/focusable.props.ts +14 -0
  29. package/src/focusable.tsx +17 -0
  30. package/src/glyphs.ts +116 -0
  31. package/src/helpers/index.tsx +4 -0
  32. package/src/helpers/resolution-context.tsx +68 -0
  33. package/src/helpers/scale-for-device-resolution.ts +31 -0
  34. package/src/index.tsx +70 -0
  35. package/src/jsx-props.ts +87 -0
  36. package/src/kdfs.d.ts +14 -0
  37. package/src/kdfs.ts +15 -0
  38. package/src/key-handler.props.ts +33 -0
  39. package/src/key-handler.tsx +24 -0
  40. package/src/modal-dialog.props.ts +38 -0
  41. package/src/modal-dialog.tsx +41 -0
  42. package/src/retain-asset.ts +24 -0
  43. package/src/sg-intrinsics.json +1153 -0
  44. package/src/sg-nodes/__tests__/__snapshots__/font.test.tsx.snap +13 -0
  45. package/src/sg-nodes/__tests__/__snapshots__/prop-forwarding.test.tsx.snap +4409 -0
  46. package/src/sg-nodes/__tests__/core-props.test.ts +301 -0
  47. package/src/sg-nodes/__tests__/font.test.tsx +141 -0
  48. package/src/sg-nodes/__tests__/imperative-methods.test.ts +276 -0
  49. package/src/sg-nodes/__tests__/prop-forwarding.test.tsx +1049 -0
  50. package/src/sg-nodes/__tests__/solid-integration.test.tsx +719 -0
  51. package/src/sg-nodes/animated-image.props.ts +64 -0
  52. package/src/sg-nodes/animated-image.tsx +23 -0
  53. package/src/sg-nodes/animation.props.ts +292 -0
  54. package/src/sg-nodes/animation.tsx +190 -0
  55. package/src/sg-nodes/array-grid.ts +602 -0
  56. package/src/sg-nodes/audio.props.ts +302 -0
  57. package/src/sg-nodes/audio.tsx +57 -0
  58. package/src/sg-nodes/busy-spinner.props.ts +59 -0
  59. package/src/sg-nodes/busy-spinner.tsx +84 -0
  60. package/src/sg-nodes/button-group.props.ts +141 -0
  61. package/src/sg-nodes/button-group.tsx +30 -0
  62. package/src/sg-nodes/button.props.ts +119 -0
  63. package/src/sg-nodes/button.tsx +31 -0
  64. package/src/sg-nodes/check-list.props.ts +71 -0
  65. package/src/sg-nodes/check-list.tsx +55 -0
  66. package/src/sg-nodes/content-node.props.ts +1316 -0
  67. package/src/sg-nodes/content-node.tsx +153 -0
  68. package/src/sg-nodes/core-props.ts +197 -0
  69. package/src/sg-nodes/custom-keyboard.props.ts +108 -0
  70. package/src/sg-nodes/custom-keyboard.tsx +48 -0
  71. package/src/sg-nodes/dialog.props.ts +159 -0
  72. package/src/sg-nodes/dialog.tsx +36 -0
  73. package/src/sg-nodes/dynamic-key-grid.props.ts +22 -0
  74. package/src/sg-nodes/dynamic-key-grid.tsx +16 -0
  75. package/src/sg-nodes/dynamic-keyboard.props.ts +30 -0
  76. package/src/sg-nodes/dynamic-keyboard.tsx +18 -0
  77. package/src/sg-nodes/dynamic-mini-keyboard.props.ts +22 -0
  78. package/src/sg-nodes/dynamic-mini-keyboard.tsx +18 -0
  79. package/src/sg-nodes/dynamic-pinpad.props.ts +57 -0
  80. package/src/sg-nodes/dynamic-pinpad.tsx +18 -0
  81. package/src/sg-nodes/effect.props.ts +119 -0
  82. package/src/sg-nodes/effect.tsx +167 -0
  83. package/src/sg-nodes/font.props.ts +45 -0
  84. package/src/sg-nodes/font.tsx +80 -0
  85. package/src/sg-nodes/group.props.ts +105 -0
  86. package/src/sg-nodes/group.tsx +30 -0
  87. package/src/sg-nodes/label-list.props.ts +86 -0
  88. package/src/sg-nodes/label-list.tsx +64 -0
  89. package/src/sg-nodes/label.props.ts +207 -0
  90. package/src/sg-nodes/label.tsx +56 -0
  91. package/src/sg-nodes/layout-group.props.ts +69 -0
  92. package/src/sg-nodes/layout-group.tsx +75 -0
  93. package/src/sg-nodes/list-panel.props.ts +103 -0
  94. package/src/sg-nodes/list-panel.tsx +49 -0
  95. package/src/sg-nodes/markup-grid.props.ts +78 -0
  96. package/src/sg-nodes/markup-grid.tsx +38 -0
  97. package/src/sg-nodes/markup-list.props.ts +66 -0
  98. package/src/sg-nodes/markup-list.tsx +38 -0
  99. package/src/sg-nodes/mask-group.props.ts +78 -0
  100. package/src/sg-nodes/mask-group.tsx +59 -0
  101. package/src/sg-nodes/media-playback-methods.ts +20 -0
  102. package/src/sg-nodes/node.ts +77 -0
  103. package/src/sg-nodes/overhang.props.ts +169 -0
  104. package/src/sg-nodes/overhang.tsx +29 -0
  105. package/src/sg-nodes/panel-set.props.ts +71 -0
  106. package/src/sg-nodes/panel-set.tsx +53 -0
  107. package/src/sg-nodes/panel.props.ts +176 -0
  108. package/src/sg-nodes/panel.tsx +56 -0
  109. package/src/sg-nodes/poster-grid.props.ts +277 -0
  110. package/src/sg-nodes/poster-grid.tsx +45 -0
  111. package/src/sg-nodes/poster.props.ts +125 -0
  112. package/src/sg-nodes/poster.tsx +44 -0
  113. package/src/sg-nodes/radio-button-list.props.ts +59 -0
  114. package/src/sg-nodes/radio-button-list.tsx +68 -0
  115. package/src/sg-nodes/rectangle.props.ts +29 -0
  116. package/src/sg-nodes/rectangle.tsx +27 -0
  117. package/src/sg-nodes/row-list.props.ts +288 -0
  118. package/src/sg-nodes/row-list.tsx +47 -0
  119. package/src/sg-nodes/scene.props.ts +65 -0
  120. package/src/sg-nodes/scene.tsx +15 -0
  121. package/src/sg-nodes/scrollable-text.props.ts +133 -0
  122. package/src/sg-nodes/scrollable-text.tsx +64 -0
  123. package/src/sg-nodes/scrolling-label.props.ts +106 -0
  124. package/src/sg-nodes/scrolling-label.tsx +42 -0
  125. package/src/sg-nodes/simple-label.props.ts +156 -0
  126. package/src/sg-nodes/simple-label.tsx +59 -0
  127. package/src/sg-nodes/sound-effect.props.ts +130 -0
  128. package/src/sg-nodes/sound-effect.tsx +65 -0
  129. package/src/sg-nodes/standard-dialog.props.ts +520 -0
  130. package/src/sg-nodes/standard-dialog.tsx +428 -0
  131. package/src/sg-nodes/standard-keyboard-dialog.props.ts +84 -0
  132. package/src/sg-nodes/standard-keyboard-dialog.tsx +58 -0
  133. package/src/sg-nodes/standard-message-dialog.props.ts +56 -0
  134. package/src/sg-nodes/standard-message-dialog.tsx +47 -0
  135. package/src/sg-nodes/standard-pinpad-dialog.props.ts +64 -0
  136. package/src/sg-nodes/standard-pinpad-dialog.tsx +44 -0
  137. package/src/sg-nodes/standard-progress-dialog.props.ts +30 -0
  138. package/src/sg-nodes/standard-progress-dialog.tsx +40 -0
  139. package/src/sg-nodes/target-group.props.ts +278 -0
  140. package/src/sg-nodes/target-group.tsx +63 -0
  141. package/src/sg-nodes/target-list.props.ts +128 -0
  142. package/src/sg-nodes/target-list.tsx +66 -0
  143. package/src/sg-nodes/target-set.props.ts +63 -0
  144. package/src/sg-nodes/target-set.tsx +42 -0
  145. package/src/sg-nodes/time-grid.props.ts +646 -0
  146. package/src/sg-nodes/time-grid.tsx +87 -0
  147. package/src/sg-nodes/tween.props.ts +115 -0
  148. package/src/sg-nodes/types.ts +150 -0
  149. package/src/sg-nodes/video.props.ts +642 -0
  150. package/src/sg-nodes/video.tsx +75 -0
  151. package/src/sg-nodes/voice-text-edit-box.props.ts +91 -0
  152. package/src/sg-nodes/voice-text-edit-box.tsx +15 -0
  153. package/src/sg-nodes/zoom-row-list.props.ts +416 -0
  154. package/src/sg-nodes/zoom-row-list.tsx +42 -0
  155. package/src/standard-dialog-next/__tests__/standard-dialog-next.test.tsx +186 -0
  156. package/src/standard-dialog-next/index.ts +5 -0
  157. package/src/standard-dialog-next/standard-dialog-next.props.ts +80 -0
  158. package/src/standard-dialog-next/standard-dialog-next.tsx +326 -0
  159. package/src/test-helpers/tween-test-setup.ts +78 -0
  160. package/src/tween.tsx +173 -0
  161. package/src/types.ts +8 -0
  162. package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.brs +13 -0
  163. package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.xml +10 -0
  164. package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.xml.d.ts +42 -0
  165. package/src/xml-components/bounding-tracker/BoundingTracker.brs +26 -0
  166. package/src/xml-components/bounding-tracker/BoundingTracker.xml +14 -0
  167. package/src/xml-components/bounding-tracker/BoundingTracker.xml.d.ts +42 -0
  168. package/src/xml-components/channel-store/channelStoreExt.brs +159 -0
  169. package/src/xml-components/channel-store/channelStoreExt.xml +29 -0
  170. package/src/xml-components/channel-store/channelStoreExt.xml.d.ts +57 -0
  171. package/src/xml-components/focus/focusable-group.brs +251 -0
  172. package/src/xml-components/focus/focusable-group.xml +15 -0
  173. package/src/xml-components/focus/focusable-group.xml.d.ts +42 -0
  174. package/src/xml-components/focus/focusable.xml +18 -0
  175. package/src/xml-components/focus/focusable.xml.d.ts +48 -0
  176. package/src/xml-components/keyboards/CustomAddressKDF.keyboard.json +547 -0
  177. package/src/xml-components/keyboards/CustomKeyboard.brs +322 -0
  178. package/src/xml-components/keyboards/CustomKeyboard.xml +45 -0
  179. package/src/xml-components/keyboards/CustomKeyboard.xml.d.ts +73 -0
  180. package/src/xml-components/keyboards/KeyGridExt.xml +4 -0
  181. package/src/xml-components/keyboards/KeyGridExt.xml.d.ts +37 -0
  182. package/src/xml-components/keyboards/KeyboardExt.xml +4 -0
  183. package/src/xml-components/keyboards/KeyboardExt.xml.d.ts +37 -0
  184. package/src/xml-components/keyboards/MOPPinPadCustomKDF.keyboard.json +151 -0
  185. package/src/xml-components/keyboards/MiniKeyboardExt.xml +4 -0
  186. package/src/xml-components/keyboards/MiniKeyboardExt.xml.d.ts +37 -0
  187. package/src/xml-components/keyboards/PhonePadCustomKDF.keyboard.json +42 -0
  188. package/src/xml-components/keyboards/PinPadExt.brs +102 -0
  189. package/src/xml-components/keyboards/PinPadExt.xml +17 -0
  190. package/src/xml-components/keyboards/PinPadExt.xml.d.ts +49 -0
  191. package/src/xml-components/keyboards/WifiCustomKDF.keyboard.json +1242 -0
  192. package/src/xml-components/keyhandler/KeyHandler.brs +30 -0
  193. package/src/xml-components/keyhandler/KeyHandler.xml +16 -0
  194. package/src/xml-components/keyhandler/KeyHandler.xml.d.ts +45 -0
  195. package/src/xml-components/managed-list-panel/ManagedListPanel.brs +137 -0
  196. package/src/xml-components/managed-list-panel/ManagedListPanel.xml +10 -0
  197. package/src/xml-components/managed-list-panel/ManagedListPanel.xml.d.ts +37 -0
  198. package/src/xml-components/modal/Modal.brs +105 -0
  199. package/src/xml-components/modal/Modal.xml +22 -0
  200. package/src/xml-components/modal/Modal.xml.d.ts +54 -0
  201. package/src/xml-components/modal/StandardDialogExt.xml +10 -0
  202. package/src/xml-components/modal/StandardDialogExt.xml.d.ts +37 -0
  203. package/src/xml-components/modal/StandardKeyboardDialogExt.xml +10 -0
  204. package/src/xml-components/modal/StandardKeyboardDialogExt.xml.d.ts +37 -0
  205. package/src/xml-components/modal/StandardMessageDialogExt.xml +10 -0
  206. package/src/xml-components/modal/StandardMessageDialogExt.xml.d.ts +37 -0
  207. package/src/xml-components/modal/StandardPinPadDialogExt.xml +10 -0
  208. package/src/xml-components/modal/StandardPinPadDialogExt.xml.d.ts +37 -0
  209. package/src/xml-components/modal/StandardProgressDialogExt.xml +10 -0
  210. package/src/xml-components/modal/StandardProgressDialogExt.xml.d.ts +37 -0
  211. package/src/xml-components/modal/tests/Modal.test.brs +195 -0
  212. package/tsconfig.json +10 -0
@@ -0,0 +1,302 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { ComponentRef } from "@roku-sdk/interfaces";
5
+
6
+ import type { MediaControl, MediaState } from "../types";
7
+ import type { IGroupProps } from "./group.props";
8
+ import type { EventHandler, JSX } from "./types";
9
+
10
+ /**
11
+ * Buffering progress information from the Audio node.
12
+ *
13
+ * Valid only while buffering is in progress, both at stream startup or when
14
+ * re-buffering is required.
15
+ *
16
+ * @see {@link https://developer.roku.com/dev/docs/audio}
17
+ */
18
+ export interface IAudioBufferingStatus {
19
+ /** Percent buffering complete as an integer. */
20
+ buffering: number;
21
+ /** Percent buffering complete as an integer. */
22
+ percent: number;
23
+ }
24
+
25
+ /** Alias for {@link MediaControl} used by the Audio node `control` field. */
26
+ export type AudioControl = MediaControl;
27
+
28
+ /** Alias for {@link MediaState} from the Audio node `state` field. */
29
+ export type AudioState = MediaState;
30
+
31
+ /**
32
+ * Audio codec format reported by the Audio node `audioFormat` field.
33
+ *
34
+ * @see {@link https://developer.roku.com/dev/docs/audio}
35
+ */
36
+ export type AudioFormat =
37
+ | ""
38
+ | "aac"
39
+ | "aac_adif"
40
+ | "aac_adts"
41
+ | "aac_latm"
42
+ | "ac3"
43
+ | "alac"
44
+ | "dts"
45
+ | "eac3"
46
+ | "flac"
47
+ | "mp2"
48
+ | "mp3"
49
+ | "none"
50
+ | "pcm"
51
+ | "unknown"
52
+ | "vorbis"
53
+ | "wma"
54
+ | "wmapro";
55
+
56
+ /**
57
+ * HDR mode reported in `streamingSegment` data.
58
+ *
59
+ * @see {@link https://developer.roku.com/dev/docs/audio}
60
+ */
61
+ export type HDRmode = "invalid" | "unknown" | "none" | "hdr10" | "dolby_vision" | "hlg10" | "sl-hdr2";
62
+
63
+ /**
64
+ * Information about the audio segment currently streaming (HLS/DASH).
65
+ *
66
+ * @see {@link https://developer.roku.com/dev/docs/audio}
67
+ */
68
+ export interface IAudioStreamingSegment {
69
+ /** Bitrate of the segment in bits per second. */
70
+ segBitrateBps: number;
71
+ /** The sequence number of the segment in the audio. */
72
+ segSequence: number;
73
+ /** The start time of the segment from the start of the audio, in seconds. */
74
+ segStart: number;
75
+ /** URL of the segment. */
76
+ segUrl: string;
77
+ /** The type of data in the segment: "unknown", "mux", "audio", "video", or "captions". */
78
+ segTypeStr: string;
79
+ /** The HDR format of the content. */
80
+ hdrModeStr: HDRmode;
81
+ }
82
+
83
+ /**
84
+ * Information about the audio stream currently playing or buffering.
85
+ *
86
+ * @see {@link https://developer.roku.com/dev/docs/audio}
87
+ */
88
+ export interface IAudioStreamInfo {
89
+ /** If true, the stream was downloaded due to an underrun. */
90
+ isUnderrun: boolean;
91
+ /** If true, playback was resumed after trickplay. */
92
+ isResumed: boolean;
93
+ /** The measured bitrate (bps) of the network when the stream was selected. */
94
+ measuredBitrate: number;
95
+ /** The bitrate of the stream. */
96
+ streamBitrate: number;
97
+ /** The URL of the stream. */
98
+ streamUrl: string;
99
+ }
100
+
101
+ /**
102
+ * Properties for the Audio node.
103
+ *
104
+ * The Audio node plays streaming audio. It has no built-in visual UI, but you can build
105
+ * your own UI for the node, including trick play, or showing an album cover or similar
106
+ * graphical image for each song selected by a user.
107
+ *
108
+ * @see {@link https://developer.roku.com/dev/docs/audio}
109
+ */
110
+ export interface IAudioProps extends IGroupProps {
111
+ /**
112
+ * The ContentNode with the content metadata for the audio or audio playlist to be played.
113
+ * If an audio playlist is to be played, the ContentNode must include complete child
114
+ * ContentNodes for each audio in the playlist.
115
+ * @default null
116
+ */
117
+ content?: JSX.Element;
118
+ /**
119
+ * When true, enables audio playlists. The ContentNode set in `content` must have children
120
+ * ContentNodes for each audio in the playlist. All audios will be played in sequence.
121
+ * @default false
122
+ */
123
+ contentIsPlaylist?: boolean;
124
+ /**
125
+ * Sets the index of the next audio in the playlist to be played. Takes effect when the
126
+ * current audio is completed or skipped. Resets to -1 after the specified audio begins.
127
+ * @default -1
128
+ */
129
+ nextContentIndex?: number;
130
+ /**
131
+ * When true, the audio or audio playlist will be restarted from the beginning after
132
+ * the end is reached.
133
+ * @default false
134
+ */
135
+ loop?: boolean;
136
+ /**
137
+ * Fired when buffering progress or status changes. Valid only while buffering is in
138
+ * progress, both at stream startup or when re-buffering is required.
139
+ */
140
+ onBufferingStatus?: EventHandler<IAudioBufferingStatus>;
141
+ /**
142
+ * Sets the desired play state for the audio (e.g. "play", "pause", "stop", "prebuffer").
143
+ * To dynamically monitor the actual state, observe the `onState` event.
144
+ * @default "none"
145
+ */
146
+ control?: AudioControl;
147
+ /**
148
+ * The interval between position event notifications, in seconds.
149
+ * If 0, no notifications are delivered.
150
+ * @default 0.5
151
+ */
152
+ notificationInterval?: number;
153
+ /**
154
+ * If the audio stream contains timed metadata such as ID3 tags, any metadata with a
155
+ * key matching an entry in this array will be reported via `onTimedMetaData`. If any
156
+ * entry is "*", all timed metadata will be selected.
157
+ * @default []
158
+ */
159
+ timedMetaDataSelectionKeys?: string[];
160
+ /**
161
+ * Sets the current position in the audio in seconds from the beginning of the stream (write-only).
162
+ */
163
+ seek?: number;
164
+ /**
165
+ * Fired when the current playlist content index changes. Set to 0 for single audio
166
+ * when playlists are not enabled.
167
+ */
168
+ onContentIndex?: EventHandler<number>;
169
+ /**
170
+ * Fired when timed metadata (e.g. ID3 tags) matching {@link IAudioProps.timedMetaDataSelectionKeys}
171
+ * is decoded from the audio stream.
172
+ */
173
+ onTimedMetaData?: EventHandler<{ [key: string]: unknown }>;
174
+ /**
175
+ * Fired when the audio play state changes.
176
+ * Possible values: "none", "buffering", "playing", "paused", "stopped", "finished", "error".
177
+ */
178
+ onState?: EventHandler<AudioState>;
179
+ /** Fired on position updates at the interval specified by {@link IAudioProps.notificationInterval}. */
180
+ onPosition?: EventHandler<number>;
181
+ /**
182
+ * Fired when the audio duration is known or changes.
183
+ * Value is in seconds; becomes valid when playback begins.
184
+ */
185
+ onDuration?: EventHandler<number>;
186
+ /** Fired when an error code is set after the state changes to "error". */
187
+ onErrorCode?: EventHandler<number>;
188
+ /** Fired when an error message is set describing an audio play error. */
189
+ onErrorMsg?: EventHandler<string>;
190
+ /**
191
+ * Fired when the format of the currently playing audio changes.
192
+ * @see {@link AudioFormat}
193
+ */
194
+ onAudioFormat?: EventHandler<AudioFormat>;
195
+ /**
196
+ * Fired with information about the audio segment currently streaming.
197
+ * Only meaningful for segmented transports (DASH, HLS).
198
+ */
199
+ onStreamingSegment?: EventHandler<IAudioStreamingSegment>;
200
+ /**
201
+ * When false, disables auto-play after rebuffering following a seek.
202
+ * @default true
203
+ */
204
+ autoplayAfterSeek?: boolean;
205
+ /**
206
+ * Set to true to mute the audio currently playing. Set to false to restore audio.
207
+ * @default false
208
+ */
209
+ mute?: boolean;
210
+ /** Fired when stream info for the currently playing or buffering audio is updated. */
211
+ onStreamInfo?: EventHandler<IAudioStreamInfo>;
212
+ /**
213
+ * Fired when the time-to-start-streaming value is available. Value is the time in seconds
214
+ * from playback being started until audio actually began playing (minimum 0.001s).
215
+ * Only valid when state is "playing".
216
+ */
217
+ onTimeToStartStreaming?: EventHandler<number>;
218
+ }
219
+
220
+ /**
221
+ * Handle for imperative control of an {@link Audio} component, obtained via the `ref` prop.
222
+ *
223
+ * Extends {@link ComponentRef} with convenience methods that map to
224
+ * {@link AudioControl} values, so you can drive playback without manually
225
+ * setting the `control` prop.
226
+ *
227
+ * @example
228
+ * ```tsx
229
+ * import { Audio, AudioRef, Button } from "@roku-sdk/components";
230
+ *
231
+ * const MusicPlayer = () => {
232
+ * let audioRef!: AudioRef;
233
+ *
234
+ * return (
235
+ * <>
236
+ * <Button text="Play" onButtonSelected={() => audioRef.play()} />
237
+ * <Audio
238
+ * ref={audioRef}
239
+ * content={<ContentNode url="https://example.com/song.mp3" streamFormat="mp3" />}
240
+ * />
241
+ * </>
242
+ * );
243
+ * };
244
+ * ```
245
+ *
246
+ * @see {@link https://developer.roku.com/dev/docs/audio}
247
+ */
248
+ export interface AudioRef extends ComponentRef<IAudioProps> {
249
+ /**
250
+ * Begin playback of the audio specified by `content`. Equivalent to setting
251
+ * `control="play"`. The node transitions through `buffering` to `playing`,
252
+ * which is observable via {@link IAudioProps.onState}.
253
+ */
254
+ play: () => void;
255
+ /**
256
+ * Pause playback while preserving the current position. Equivalent to setting
257
+ * `control="pause"`. Use {@link AudioRef.resume} to continue from where it
258
+ * paused. Has no effect when not currently `playing`.
259
+ */
260
+ pause: () => void;
261
+ /**
262
+ * Stop playback and release the audio stream. Equivalent to setting
263
+ * `control="stop"`. Position is reset; a subsequent {@link AudioRef.play}
264
+ * starts from the beginning.
265
+ */
266
+ stop: () => void;
267
+ /**
268
+ * Resume playback after a {@link AudioRef.pause}. Equivalent to setting
269
+ * `control="resume"`. Has no effect when the node is not in the `paused`
270
+ * state.
271
+ */
272
+ resume: () => void;
273
+ /**
274
+ * Restart the current audio (or playlist) from the beginning. Equivalent to
275
+ * setting `control="replay"`.
276
+ */
277
+ replay: () => void;
278
+ /**
279
+ * Pre-buffer the audio without starting playback. Equivalent to setting
280
+ * `control="prebuffer"`. Useful to reduce time-to-first-sample when playback
281
+ * is started later via {@link AudioRef.play}.
282
+ */
283
+ prebuffer: () => void;
284
+ /**
285
+ * Skip to the next item in a playlist. Equivalent to setting
286
+ * `control="skipcontent"`. Only meaningful when
287
+ * {@link IAudioProps.contentIsPlaylist} is true.
288
+ */
289
+ skipcontent: () => void;
290
+ /**
291
+ * Seek to a specific position in the current audio. Equivalent to setting
292
+ * the {@link IAudioProps.seek} prop.
293
+ *
294
+ * @param position Target position in seconds from the start of the stream.
295
+ */
296
+ seekTo: (position: number) => void;
297
+ /**
298
+ * Set the index of the next audio in the playlist to be played.
299
+ * @param index The index of the next audio in the playlist.
300
+ */
301
+ setNextContentIndex: (index: number) => void;
302
+ }
@@ -0,0 +1,57 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { registerImperativeMethods } from "@roku-sdk/solid";
5
+
6
+ import type { IAudioProps } from "./audio.props";
7
+ export type {
8
+ IAudioBufferingStatus,
9
+ AudioControl,
10
+ AudioState,
11
+ AudioFormat,
12
+ HDRmode,
13
+ IAudioStreamingSegment,
14
+ IAudioStreamInfo,
15
+ IAudioProps,
16
+ AudioRef
17
+ } from "./audio.props";
18
+
19
+ import { mediaPlaybackMethods } from "./media-playback-methods";
20
+ import { Component } from "./types";
21
+
22
+ registerImperativeMethods("audio", mediaPlaybackMethods);
23
+
24
+ /**
25
+ * Audio plays streaming audio content. It has no built-in visual UI, but you can build your
26
+ * own UI including trick play, album art, or any graphical elements for user interaction.
27
+ *
28
+ * Audio playlists are supported by setting `contentIsPlaylist` to true and providing a
29
+ * ContentNode with child ContentNodes for each audio in the playlist.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * <Audio
34
+ * content={<contentNode url="https://example.com/song.mp3" streamFormat="mp3" />}
35
+ * control="play"
36
+ * onState={(state) => console.log("Audio state:", state)}
37
+ * onPosition={(pos) => console.log("Position:", pos)}
38
+ * />
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * // Audio playlist
44
+ * <Audio
45
+ * content={playlist}
46
+ * contentIsPlaylist={true}
47
+ * loop={true}
48
+ * control="play"
49
+ * onContentIndex={(idx) => console.log("Now playing track:", idx)}
50
+ * />
51
+ * ```
52
+ *
53
+ * @see {@link https://developer.roku.com/dev/docs/audio}
54
+ */
55
+ export const Audio: Component<IAudioProps> = props => {
56
+ return <audio {...props}>{props.content}</audio>;
57
+ };
@@ -0,0 +1,59 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IGroupProps } from "./group.props";
5
+
6
+ /**
7
+ * Properties for the BusySpinner node.
8
+ *
9
+ * BusySpinner is a widget that displays a continuously rotating bitmap to indicate loading
10
+ * or processing state. It uses an internal Poster node instance to display the spinner bitmap.
11
+ *
12
+ * **Note:** Not all Roku Player hardware versions support arbitrary rotations. Some hardware
13
+ * versions only support 90-degree rotation increments. In those cases, the icon will step through
14
+ * 90°, 180°, 270°, and back to 0° rotations rather than spin smoothly.
15
+ *
16
+ * @extends Group
17
+ * @see {@link https://developer.roku.com/dev/docs/busyspinner}
18
+ */
19
+ export interface IBusySpinnerProps extends IGroupProps {
20
+ /**
21
+ * Sets the URI field of the internal Poster node to select the bitmap for the busy spinner.
22
+ * The Poster node instance is created internally and can be accessed to set the spinner image.
23
+ *
24
+ * @example
25
+ * // Using a theme resource
26
+ * uri="theme:ActionHudSpinner"
27
+ *
28
+ * @example
29
+ * // Using a package resource
30
+ * uri="pkg:/images/spinner.png"
31
+ */
32
+ uri?: string;
33
+
34
+ /**
35
+ * Sets the operational state of the busy spinner.
36
+ *
37
+ * - `"none"`: No operational state set. The busy spinner will run if not set to "stop"
38
+ * - `"start"`: Starts the busy spinner if not running
39
+ * - `"stop"`: Stops the busy spinner if running
40
+ *
41
+ * @default "none"
42
+ */
43
+ control?: "none" | "start" | "stop";
44
+
45
+ /**
46
+ * Specifies whether the bitmap rotates in a clockwise or counterclockwise direction.
47
+ *
48
+ * @default true
49
+ */
50
+ clockwise?: boolean;
51
+
52
+ /**
53
+ * The number of seconds to complete a 360-degree rotation of the spinner image.
54
+ * A value of 0 will cause the spinner to remain stationary and not spin.
55
+ *
56
+ * @default 2
57
+ */
58
+ spinInterval?: number;
59
+ }
@@ -0,0 +1,84 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IBusySpinnerProps } from "./busy-spinner.props";
5
+ import { Component, ResObject } from "./types";
6
+ export type { IBusySpinnerProps } from "./busy-spinner.props";
7
+
8
+ /**
9
+ * Standard dimensions for the default BusySpinner (theme:ActionHudSpinner).
10
+ * These dimensions are resolution-aware and scale appropriately for HD and FHD displays.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { getBusySpinnerWidth, getBusySpinnerHeight } from "@roku-sdk/components";
15
+ * import { useDeviceInfoEvents } from "@roku-sdk/rsg-ts/runtime";
16
+ *
17
+ * // Get current resolution and select appropriate dimension
18
+ * const { uiResolution } = useDeviceInfoEvents();
19
+ * const width = getBusySpinnerWidth(uiResolution()?.res);
20
+ * const height = getBusySpinnerHeight(uiResolution()?.res);
21
+ * ```
22
+ */
23
+ export const BUSY_SPINNER_DIMENSIONS = {
24
+ /** Width of ActionHudSpinner: 80px in HD, 120px in FHD */
25
+ width: { hd: 80, fhd: 120 } as ResObject<number>,
26
+ /** Height of ActionHudSpinner: 80px in HD, 120px in FHD */
27
+ height: { hd: 80, fhd: 120 } as ResObject<number>
28
+ };
29
+
30
+ /**
31
+ * Get the width of the BusySpinner for the given UI resolution.
32
+ * Defaults to FHD (120px) if resolution is not provided.
33
+ *
34
+ * @param res - The resolution key ("hd", "fhd" or undefined)
35
+ * @returns Width in pixels (80 for HD, 120 for FHD)
36
+ */
37
+ export function getBusySpinnerWidth(res?: "hd" | "fhd"): number {
38
+ return BUSY_SPINNER_DIMENSIONS.width[res ?? "fhd"];
39
+ }
40
+
41
+ /**
42
+ * Get the height of the BusySpinner for the given UI resolution.
43
+ * Defaults to FHD (120px) if resolution is not provided.
44
+ *
45
+ * @param res - The resolution key ("hd", "fhd" or undefined)
46
+ * @returns Height in pixels (80 for HD, 120 for FHD)
47
+ */
48
+ export function getBusySpinnerHeight(res?: "hd" | "fhd"): number {
49
+ return BUSY_SPINNER_DIMENSIONS.height[res ?? "fhd"];
50
+ }
51
+
52
+ /**
53
+ * BusySpinner component - displays an animated spinning indicator.
54
+ *
55
+ * The BusySpinner is commonly used to provide visual feedback during loading operations
56
+ * or to indicate that processing is in progress.
57
+ *
58
+ * @example
59
+ * ```tsx
60
+ * <busySpinner
61
+ * uri="theme:ActionHudSpinner"
62
+ * control="start"
63
+ * clockwise={true}
64
+ * spinInterval={2}
65
+ * />
66
+ * ```
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * // Controlling the spinner with state
71
+ * const [isLoading, setIsLoading] = createSignal(true);
72
+ *
73
+ * <busySpinner
74
+ * uri="pkg:/images/custom-spinner.png"
75
+ * control={isLoading() ? "start" : "stop"}
76
+ * visible={isLoading()}
77
+ * />
78
+ * ```
79
+ *
80
+ * @see {@link https://developer.roku.com/dev/docs/busyspinner}
81
+ */
82
+ export const BusySpinner: Component<IBusySpinnerProps> = (props: IBusySpinnerProps) => {
83
+ return <busySpinner {...props}></busySpinner>;
84
+ };
@@ -0,0 +1,141 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IGroupProps } from "./group.props";
5
+ import type { ILayoutGroupProps } from "./layout-group.props";
6
+ import type { EventHandler, Uri } from "./types";
7
+
8
+ /**
9
+ * Properties shared across ButtonGroup-like components.
10
+ *
11
+ * Provides the visual customization fields for button groups, including text styling,
12
+ * icon bitmaps, sizing, and button creation via string arrays.
13
+ *
14
+ * @see {@link https://developer.roku.com/dev/docs/buttongroup}
15
+ */
16
+ export interface IButtonGroupDataProps {
17
+ id?: string;
18
+ /**
19
+ * Specifies the button label color for all unfocused Button nodes in the group.
20
+ * Only set to override the system default.
21
+ * @default 0xffffffff
22
+ */
23
+ textColor?: string;
24
+ /**
25
+ * Specifies the button label color for the Button node that has focus, if any.
26
+ * Only set to override the system default.
27
+ * @default 0xffffffff
28
+ */
29
+ focusedTextColor?: string;
30
+ /**
31
+ * Specifies the font for all unfocused Button nodes in the group.
32
+ * Only set to override the system default.
33
+ * @default "font:MediumSystemFont"
34
+ */
35
+ textFont?: string;
36
+ /**
37
+ * Specifies the font for the Button node that has focus, if any.
38
+ * Only set to override the system default.
39
+ * @default "font:MediumBoldSystemFont"
40
+ */
41
+ focusedTextFont?: string;
42
+ /**
43
+ * Specifies the bitmap to be used as the focus footprint background when focus is not
44
+ * on the ButtonGroup node. The focus footprint is a visual indicator of the button that
45
+ * will take focus when focus moves back onto the ButtonGroup.
46
+ */
47
+ focusFootprintBitmapUri?: Uri;
48
+ /**
49
+ * Specifies the bitmap to be used as the background for the Button node that has focus.
50
+ * Only set to override the system default.
51
+ */
52
+ focusBitmapUri?: Uri;
53
+ /**
54
+ * Specifies the bitmap for the button icon for all unfocused Button nodes in the group.
55
+ * Only set to override the system default.
56
+ */
57
+ iconUri?: Uri;
58
+ /**
59
+ * Specifies the bitmap for the focused button icon.
60
+ * Only set to override the system default.
61
+ */
62
+ focusedIconUri?: Uri;
63
+ /**
64
+ * Specifies the height of each Button node in the group.
65
+ * Only set to override the system default.
66
+ * @default 0
67
+ */
68
+ height?: number;
69
+ /**
70
+ * Specifies the minimum width for the Button nodes in the group.
71
+ * Must be less than or equal to {@link IButtonGroupDataProps.maxWidth | maxWidth}.
72
+ * @default 0
73
+ */
74
+ minWidth?: number;
75
+ /**
76
+ * Specifies the maximum width for the Button nodes in the group.
77
+ * Must be greater than or equal to {@link IButtonGroupDataProps.minWidth | minWidth}.
78
+ * @default 32767
79
+ */
80
+ maxWidth?: number;
81
+ width?: number;
82
+ /**
83
+ * Controls whether the focus footprint bitmap is displayed when the ButtonGroup
84
+ * does not have the key focus.
85
+ * @default false
86
+ */
87
+ showFocusFootprint?: boolean;
88
+ /**
89
+ * Specifies the height of each Button node in the group.
90
+ * Only set to override the system default.
91
+ * @default 0
92
+ */
93
+ buttonHeight?: number;
94
+ /**
95
+ * Specifies whether the button labels and icons should be right- or left-justified.
96
+ * When right-justified and there is an icon, it appears to the right of the button label.
97
+ * @default false
98
+ */
99
+ rightJustify?: boolean;
100
+ /**
101
+ * Causes the button with the specified index to receive the focus when the ButtonGroup
102
+ * has the key focus. If the ButtonGroup does not have the key focus when set, the
103
+ * specified button will display the focus footprint as its background.
104
+ * @default 0
105
+ */
106
+ focusButton?: number;
107
+ /**
108
+ * Allows a set of Button nodes to be easily created by providing an array of button labels.
109
+ * Each string in the array results in a Button node added to the ButtonGroup, using the
110
+ * string as the button label.
111
+ * @default []
112
+ */
113
+ buttons?: string[];
114
+ }
115
+
116
+ /**
117
+ * Properties for the ButtonGroup node.
118
+ *
119
+ * ButtonGroup manages the layout, visual attributes, and focus management of a vertical
120
+ * list of Button nodes. When the ButtonGroup has focus, it sets the key focus on a single
121
+ * one of its child Button nodes.
122
+ *
123
+ * @see {@link https://developer.roku.com/dev/docs/buttongroup}
124
+ */
125
+ export interface IButtonGroupProps extends IButtonGroupDataProps, IGroupProps, ILayoutGroupProps {
126
+ /**
127
+ * Specifies the text for a single button. Typically `buttons` (string array) is preferred
128
+ * for creating multiple buttons at once.
129
+ */
130
+ text?: string;
131
+ /**
132
+ * Fired when a button in the group is selected by the user. The handler receives the
133
+ * zero-based index of the selected button.
134
+ */
135
+ onButtonSelected?: EventHandler<number>;
136
+ /**
137
+ * Fired when a button in the group receives the key focus. The handler receives the
138
+ * zero-based index of the focused button.
139
+ */
140
+ onButtonFocused?: EventHandler<number>;
141
+ }
@@ -0,0 +1,30 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IButtonGroupProps } from "./button-group.props";
5
+ import { Component } from "./types";
6
+ export type { IButtonGroupDataProps, IButtonGroupProps } from "./button-group.props";
7
+
8
+ /**
9
+ * ButtonGroup manages the layout, visual attributes, and focus management of a vertical
10
+ * list of Button nodes. Buttons can be created by setting the `buttons` field to an array
11
+ * of label strings. A single observer on `onButtonSelected` can watch for any button in
12
+ * the group to be selected.
13
+ *
14
+ * By default, Button nodes added to the group will have the default button appearance.
15
+ * Several fields allow you to override the appearance of all buttons at once.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <buttonGroup
20
+ * buttons={["Play", "Pause", "Stop"]}
21
+ * onButtonSelected={(index) => console.log("Selected button:", index)}
22
+ * onButtonFocused={(index) => console.log("Focused button:", index)}
23
+ * />
24
+ * ```
25
+ *
26
+ * @see {@link https://developer.roku.com/dev/docs/buttongroup}
27
+ */
28
+ export const ButtonGroup: Component<IButtonGroupProps> = props => {
29
+ return <buttonGroup {...props}></buttonGroup>;
30
+ };