@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,36 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { Modal } from "../modal-dialog";
5
+ import type { IDialogProps } from "./dialog.props";
6
+ import { Component } from "./types";
7
+ export type { IDialogProps } from "./dialog.props";
8
+
9
+ /**
10
+ * Dialog is a modal pop-up used to present the user with information requiring their
11
+ * immediate attention. It supports a title, message, bullet text, graphic, and button
12
+ * regions. Dialogs intercept all key events except Home and are closed automatically
13
+ * by Back or Home.
14
+ *
15
+ * > **Note:** Consider using StandardDialog (Roku OS 10.0+) for new development, which
16
+ * > provides updated graphics and color palette support.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * <Dialog
21
+ * title="Confirm Action"
22
+ * message="Are you sure you want to proceed?"
23
+ * iconUri="pkg:/images/dialog-icon.png"
24
+ * buttons={["OK", "Cancel"]}
25
+ * close={false}
26
+ * onButtonSelected={(index) => {
27
+ * if (index === 0) handleConfirm();
28
+ * else handleCancel();
29
+ * }}
30
+ * onWasClosed={() => console.log("Dialog closed")}
31
+ * />
32
+ * ```
33
+ *
34
+ * @see {@link https://developer.roku.com/dev/docs/dialog}
35
+ */
36
+ export const Dialog: Component<IDialogProps> = props => <Modal dialog={<dialog {...props}></dialog>} />;
@@ -0,0 +1,22 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IGroupProps } from "./group.props";
5
+ import type { EventHandler } from "./types";
6
+
7
+ /**
8
+ * Properties for the {@link DynamicKeyGrid} component.
9
+ *
10
+ * @see {@link https://developer.roku.com/dev/docs/dynamic-key-grid}
11
+ */
12
+ export interface IDynamicKeyGridProps extends IGroupProps {
13
+ /** URI of a packaged `.keyboard.json` Key Definition File. */
14
+ keyDefinitionUri?: string;
15
+ /** Active grid mode (must match a mode in the KDF). */
16
+ mode?: string;
17
+ /** Fired when the `keySelected` field changes after a key press. */
18
+ onKeySelected?: EventHandler<string>;
19
+ focusVisible?: boolean;
20
+ horizWrap?: boolean;
21
+ vertWrap?: boolean;
22
+ }
@@ -0,0 +1,16 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { retainAsset } from "../retain-asset";
5
+ import KeyGridExtXML from "../xml-components/keyboards/KeyGridExt.xml";
6
+ import type { IDynamicKeyGridProps } from "./dynamic-key-grid.props";
7
+ import { Component } from "./types";
8
+ export type { IDynamicKeyGridProps } from "./dynamic-key-grid.props";
9
+
10
+ /**
11
+ * Standalone key grid driven by a Key Definition File (no text edit box).
12
+ */
13
+ export const DynamicKeyGrid: Component<IDynamicKeyGridProps> = props => {
14
+ retainAsset(KeyGridExtXML);
15
+ return <keyGridExt {...props}></keyGridExt>;
16
+ };
@@ -0,0 +1,30 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { KeyboardDomain } from "./custom-keyboard.props";
5
+ import type { IGroupProps } from "./group.props";
6
+ import type { EventHandler } from "./types";
7
+
8
+ /**
9
+ * Properties for the {@link DynamicKeyboard} component.
10
+ *
11
+ * Voice-enabled full keyboard with a fixed OS key layout (email/password style).
12
+ *
13
+ * @see {@link https://developer.roku.com/dev/docs/dynamic-keyboard}
14
+ */
15
+ export interface IDynamicKeyboardProps extends IGroupProps {
16
+ /** Current text in the internal VoiceTextEditBox. */
17
+ text?: string;
18
+ /** Fired when entered text changes. */
19
+ onText?: EventHandler<string>;
20
+ /** Speech-recognition domain for voice entry. */
21
+ domain?: KeyboardDomain;
22
+ /** Placeholder text shown when empty. */
23
+ hintText?: string;
24
+ /** Maximum number of characters that may be entered. */
25
+ maxTextLength?: number;
26
+ /** Shows underscore placeholders in the text edit box. */
27
+ showUnderscores?: boolean;
28
+ /** When true, entered characters are masked. */
29
+ secureMode?: boolean;
30
+ }
@@ -0,0 +1,18 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { retainAsset } from "../retain-asset";
5
+ import KeyboardExtXML from "../xml-components/keyboards/KeyboardExt.xml";
6
+ import type { IDynamicKeyboardProps } from "./dynamic-keyboard.props";
7
+ import { Component } from "./types";
8
+ export type { IDynamicKeyboardProps } from "./dynamic-keyboard.props";
9
+
10
+ /**
11
+ * Standalone {@link DynamicKeyboard} with built-in key layout and voice entry.
12
+ *
13
+ * Voice is always enabled on this fixed OS layout; there is no `voiceEnabled` prop.
14
+ */
15
+ export const DynamicKeyboard: Component<IDynamicKeyboardProps> = props => {
16
+ retainAsset(KeyboardExtXML);
17
+ return <keyboardExt {...props}></keyboardExt>;
18
+ };
@@ -0,0 +1,22 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { KeyboardDomain } from "./custom-keyboard.props";
5
+ import type { IGroupProps } from "./group.props";
6
+ import type { EventHandler } from "./types";
7
+
8
+ /**
9
+ * Properties for the {@link DynamicMiniKeyboard} component.
10
+ *
11
+ * @see {@link https://developer.roku.com/dev/docs/dynamic-mini-keyboard}
12
+ */
13
+ export interface IDynamicMiniKeyboardProps extends IGroupProps {
14
+ text?: string;
15
+ onText?: EventHandler<string>;
16
+ /** Speech-recognition domain for voice entry. */
17
+ domain?: KeyboardDomain;
18
+ /** Placeholder text shown when empty. */
19
+ hintText?: string;
20
+ /** Maximum number of characters that may be entered. */
21
+ maxTextLength?: number;
22
+ }
@@ -0,0 +1,18 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { retainAsset } from "../retain-asset";
5
+ import MiniKeyboardExtXML from "../xml-components/keyboards/MiniKeyboardExt.xml";
6
+ import type { IDynamicMiniKeyboardProps } from "./dynamic-mini-keyboard.props";
7
+ import { Component } from "./types";
8
+ export type { IDynamicMiniKeyboardProps } from "./dynamic-mini-keyboard.props";
9
+
10
+ /**
11
+ * Standalone search-style mini keyboard (A–Z, 0–9) with voice entry.
12
+ *
13
+ * Voice is always enabled on this fixed OS layout; there is no `voiceEnabled` prop.
14
+ */
15
+ export const DynamicMiniKeyboard: Component<IDynamicMiniKeyboardProps> = props => {
16
+ retainAsset(MiniKeyboardExtXML);
17
+ return <miniKeyboardExt {...props}></miniKeyboardExt>;
18
+ };
@@ -0,0 +1,57 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IGroupProps } from "./group.props";
5
+ import type { EventHandler } from "./types";
6
+
7
+ /**
8
+ * Properties for the {@link DynamicPinPad} component.
9
+ *
10
+ * DynamicPinPad is similar to the legacy PinPad node but adds voice entry functionality.
11
+ * It enables text and voice entry of numeric characters and is typically used for short
12
+ * PIN codes. The key layout is fixed based on the node's pre-built Key Definition File.
13
+ *
14
+ * This wrapper extends the native DynamicPinPad node via PinPadExt, forwarding text-edit-box
15
+ * styling fields to the internal VoiceTextEditBox.
16
+ *
17
+ * @see {@link https://developer.roku.com/dev/docs/dynamic-pinpad}
18
+ * @see {@link https://developer.roku.com/dev/docs/dynamic-keyboard-base}
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <DynamicPinPad
23
+ * id="pinpad"
24
+ * text={pin()}
25
+ * hintText="Enter PIN"
26
+ * maxTextLength={4}
27
+ * secureMode
28
+ * onText={setPin}
29
+ * translation={[80, 200]}
30
+ * />
31
+ * ```
32
+ */
33
+ export interface IDynamicPinPadProps extends IGroupProps {
34
+ /** Current PIN/text value. */
35
+ text?: string;
36
+ /** Fired when the entered text changes. */
37
+ onText?: EventHandler<string>;
38
+ /** When true, entered characters are masked. */
39
+ secureMode?: boolean;
40
+ /** Placeholder text shown when empty. */
41
+ hintText?: string;
42
+ /**
43
+ * Maximum number of characters that may be entered.
44
+ * @default 4
45
+ */
46
+ maxTextLength?: number;
47
+ /** Disables voice underscore control in the internal text edit box. */
48
+ disableVoiceUnderscoreControl?: boolean;
49
+ /** Shows underscore placeholders for each digit position. */
50
+ showUnderscores?: boolean;
51
+ /** Centers underscore placeholders within the text box. */
52
+ centerUnderscores?: boolean;
53
+ /** Color of the hint text. */
54
+ hintTextColor?: string;
55
+ /** Color of the entered text. */
56
+ textColor?: string;
57
+ }
@@ -0,0 +1,18 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { retainAsset } from "../retain-asset";
5
+ import PinPadExtXML from "../xml-components/keyboards/PinPadExt.xml";
6
+ import type { IDynamicPinPadProps } from "./dynamic-pinpad.props";
7
+ import { Component } from "./types";
8
+ export type { IDynamicPinPadProps } from "./dynamic-pinpad.props";
9
+
10
+ /**
11
+ * Voice-enabled numeric PIN pad for standalone on-screen entry (not inside a dialog).
12
+ *
13
+ * @see {@link https://developer.roku.com/dev/docs/dynamic-pinpad}
14
+ */
15
+ export const DynamicPinPad: Component<IDynamicPinPadProps> = props => {
16
+ retainAsset(PinPadExtXML);
17
+ return <pinPadExt {...props}></pinPadExt>;
18
+ };
@@ -0,0 +1,119 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { INodeProps } from "./node";
5
+ import type { Vector2D } from "./types";
6
+
7
+ export type GradientStyle = "none" | "linear" | "radial";
8
+
9
+ /**
10
+ * Properties for the Effect node.
11
+ *
12
+ * Effect applies GPU shader-based rendering to a Rectangle or Poster, enabling rounded corners,
13
+ * borders, and gradient fills. Attach an Effect node to a Rectangle or Poster via the `effect`
14
+ * prop.
15
+ *
16
+ * **Platform availability**: Effects require Roku OS 16.0 or later and the OpenGL graphics backend.
17
+ * Elsewhere the `effect` prop is ignored without an error and the content renders unstyled. To branch
18
+ * on it, read `getDeviceInfo()` from `@roku-sdk/rsg-ts/runtime` once:
19
+ * `Number(info.osVersion.major) >= 16 && info.graphicsPlatform === "opengl"`. The node's own `supported`
20
+ * field is set when the node is created and cannot be read from TypeScript, and effects disabled by
21
+ * device configuration are invisible to that check.
22
+ *
23
+ * Hoist the effect into a `const`. A signal read by the `effect={...}` expression itself builds a new
24
+ * effect node on every change; signals read by the effect's own fields update it in place.
25
+ *
26
+ * {@includeCode ../../../../examples/showcase/src/components/showcases/effect-showcase/shared-effect-showcase.tsx#docs:effect-shared}
27
+ *
28
+ * @example
29
+ * // Rounded border and linear gradient, each hoisted once.
30
+ * const framed = <effect borderRadius={16} borderWidth={2} borderColor="0xFF0000FF" />;
31
+ * <rectangle width={400} height={200} effect={framed} />;
32
+ * const fill = <effect gradientStyle="linear" gradientColors={["0xFF0000FF", "0x0000FFFF"]} />;
33
+ * <rectangle width={400} height={200} effect={fill} />;
34
+ *
35
+ * @since Roku OS 16.0
36
+ */
37
+ export interface IEffectProps extends INodeProps {
38
+ /**
39
+ * Per-corner border radius in pixels.
40
+ * - A single number applies a uniform radius to all corners.
41
+ * - A four-element tuple sets each corner independently: `[topLeft, topRight, bottomRight, bottomLeft]`.
42
+ * The tuple is one Vector4 field, so a FloatFieldInterpolator cannot animate it.
43
+ * @default 0
44
+ */
45
+ borderRadius?: number | [number, number, number, number];
46
+
47
+ /**
48
+ * Border stroke width in pixels. The border is drawn **outside** the content rect.
49
+ * @default 0
50
+ */
51
+ borderWidth?: number;
52
+
53
+ /**
54
+ * Padding in pixels between the border and the content rect. It changes nothing while
55
+ * `borderWidth` is 0, so it is not content padding.
56
+ * @default 0
57
+ */
58
+ borderPadding?: number;
59
+
60
+ /**
61
+ * Border stroke color as an RGBA hex string (e.g. `"0xFF0000FF"`).
62
+ * @default "0xFFFFFFFF"
63
+ */
64
+ borderColor?: string;
65
+
66
+ /**
67
+ * Gradient fill style applied over the content area. No gradient renders while this is `"none"`.
68
+ * @default "none"
69
+ */
70
+ gradientStyle?: GradientStyle;
71
+
72
+ /**
73
+ * Ordered list of RGBA color stops for the gradient (e.g. `["0xFF0000FF", "0x0000FFFF"]`).
74
+ * At least two colors are required for the gradient to render. Maximum of 8 colors.
75
+ * Outside that range the gradient does not render; in `__DEV__` builds the SDK warns once
76
+ * when `gradientStyle` is not `"none"`.
77
+ */
78
+ gradientColors?: string[];
79
+
80
+ /**
81
+ * Normalized stop positions `[0, 1]` corresponding to each entry in `gradientColors`.
82
+ * If omitted, colors are distributed evenly across the gradient. In `__DEV__` builds the SDK
83
+ * warns once on a length mismatch or an entry outside `[0, 1]` when `gradientStyle` is not `"none"`.
84
+ */
85
+ gradientStops?: number[];
86
+
87
+ /**
88
+ * Rotation angle of a linear gradient in degrees.
89
+ * The gradient fills the available space from opposite corners.
90
+ * @default 0
91
+ */
92
+ gradientAngle?: number;
93
+
94
+ /**
95
+ * Center point of a radial gradient in normalized coordinates.
96
+ * `[0, 0]` = top-left, `[1, 1]` = bottom-right.
97
+ * @default [0.5, 0.5]
98
+ */
99
+ gradientCentre?: Vector2D;
100
+
101
+ /**
102
+ * Radii of the radial gradient ellipse in normalized coordinates.
103
+ * `1.0` equals the rectangle's width or height respectively.
104
+ * @default [0.5, 0.5]
105
+ */
106
+ gradientRadius?: Vector2D;
107
+
108
+ /**
109
+ * Whether the gradient fills the content area (inside the border).
110
+ * @default true
111
+ */
112
+ gradientFillContent?: boolean;
113
+
114
+ /**
115
+ * Whether the gradient fills the border area.
116
+ * @default false
117
+ */
118
+ gradientFillBorder?: boolean;
119
+ }
@@ -0,0 +1,167 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { DomNode, TmValue } from "@roku-sdk/interfaces";
5
+ import { registerSetPropertyObserver } from "@roku-sdk/solid";
6
+
7
+ import type { GradientStyle, IEffectProps } from "./effect.props";
8
+ import { Component } from "./types";
9
+ export type { GradientStyle, IEffectProps } from "./effect.props";
10
+
11
+ declare const __DEV__: boolean;
12
+
13
+ const TRACKED_PROPS: ReadonlySet<string> = new Set(["gradientStyle", "gradientColors", "gradientStops", "id"]);
14
+
15
+ interface EffectGradientState {
16
+ gradientStyle?: GradientStyle;
17
+ gradientColors?: string[];
18
+ gradientStops?: number[];
19
+ id?: string;
20
+ warned: Set<string>;
21
+ scheduled: boolean;
22
+ }
23
+
24
+ /**
25
+ * Dev-only: describe each way gradient props violate the firmware's silent constraints.
26
+ * Returns `[key, message]` pairs so a caller can report each distinct problem once.
27
+ *
28
+ * Quiet while `gradientStyle` is `"none"` or unset — authors may park empty colors/stops
29
+ * and turn the style on later (see apps/effects-lab edit-demo).
30
+ */
31
+ function gradientPropProblems(
32
+ style: GradientStyle | undefined,
33
+ colors: readonly string[] | undefined,
34
+ stops: readonly number[] | undefined,
35
+ nodeLabel: string
36
+ ): [key: string, message: string][] {
37
+ if (style === undefined || style === "none") {
38
+ return [];
39
+ }
40
+
41
+ const problems: [string, string][] = [];
42
+ const colorList = Array.isArray(colors) ? colors : [];
43
+ const colorCount = colorList.length;
44
+
45
+ if (colorCount < 2 || colorCount > 8) {
46
+ problems.push([
47
+ "colors:count",
48
+ `${nodeLabel}: gradientColors must have 2–8 entries when gradientStyle is "${style}" (got ${colorCount}).`
49
+ ]);
50
+ }
51
+
52
+ if (stops !== undefined) {
53
+ const stopList = Array.isArray(stops) ? stops : [];
54
+ if (stopList.length !== colorCount) {
55
+ problems.push([
56
+ "stops:length",
57
+ `${nodeLabel}: gradientStops length (${stopList.length}) must equal gradientColors length (${colorCount}).`
58
+ ]);
59
+ }
60
+ const outOfRange = stopList.findIndex(stop => !(typeof stop === "number" && stop >= 0 && stop <= 1));
61
+ if (outOfRange !== -1) {
62
+ problems.push([
63
+ "stops:range",
64
+ `${nodeLabel}: gradientStops entries must be in [0, 1] (got ${String(stopList[outOfRange])} at index ${outOfRange}).`
65
+ ]);
66
+ }
67
+ }
68
+
69
+ return problems;
70
+ }
71
+
72
+ function nodeLabelFor(state: EffectGradientState): string {
73
+ return state.id !== undefined && state.id !== "" ? `Effect[id=${state.id}]` : "Effect";
74
+ }
75
+
76
+ let effectGradientObserverInstalled = false;
77
+
78
+ /**
79
+ * Installs the `__DEV__` setProperty observer that validates Effect gradient props for both
80
+ * `<effect>` and `<Effect>`. Safe to call more than once; no-ops when `__DEV__` is off.
81
+ * Tests call this after enabling `__DEV__` because Jest loads modules with `__DEV__` false.
82
+ */
83
+ export function installEffectGradientDevValidation(): void {
84
+ if (typeof __DEV__ === "undefined" || !__DEV__) return;
85
+ if (effectGradientObserverInstalled) return;
86
+ effectGradientObserverInstalled = true;
87
+
88
+ const stateByNode = new WeakMap<DomNode, EffectGradientState>();
89
+
90
+ registerSetPropertyObserver((node: DomNode, name: string, value: TmValue): void => {
91
+ if (typeof __DEV__ === "undefined" || !__DEV__) return;
92
+ if (node.t !== "effect") return;
93
+ if (!TRACKED_PROPS.has(name)) return;
94
+
95
+ let state = stateByNode.get(node);
96
+ if (!state) {
97
+ state = { warned: new Set(), scheduled: false };
98
+ stateByNode.set(node, state);
99
+ }
100
+
101
+ switch (name) {
102
+ case "gradientStyle":
103
+ state.gradientStyle = value as GradientStyle;
104
+ break;
105
+ case "gradientColors":
106
+ state.gradientColors = value as string[];
107
+ break;
108
+ case "gradientStops":
109
+ state.gradientStops = value as number[];
110
+ break;
111
+ case "id":
112
+ state.id = value as string;
113
+ break;
114
+ }
115
+
116
+ if (state.scheduled) return;
117
+ state.scheduled = true;
118
+ const pending = state;
119
+ queueMicrotask(() => {
120
+ pending.scheduled = false;
121
+ const label = nodeLabelFor(pending);
122
+ for (const [key, message] of gradientPropProblems(
123
+ pending.gradientStyle,
124
+ pending.gradientColors,
125
+ pending.gradientStops,
126
+ label
127
+ )) {
128
+ if (pending.warned.has(key)) continue;
129
+ pending.warned.add(key);
130
+ console.warn(message);
131
+ }
132
+ });
133
+ });
134
+ }
135
+
136
+ installEffectGradientDevValidation();
137
+
138
+ /**
139
+ * Effect applies GPU shader-based rendering to a Rectangle or Poster.
140
+ *
141
+ * Hoist it into a `const` and pass that as the `effect` prop on a Rectangle or
142
+ * Poster to enable rounded corners, borders, and gradient fills. Set
143
+ * `shaderCompatible` on a Poster that takes one.
144
+ *
145
+ * Prefer the `<effect>` intrinsic for most uses. The Effect wrapper is a pure passthrough (`<effect {...props} />`) and exists for consistency with the component library.
146
+ *
147
+ * {@includeCode ../../../../examples/showcase/src/components/showcases/effect-showcase/shared-effect-showcase.tsx#docs:effect-shared}
148
+ *
149
+ * @example
150
+ * import { Effect } from "@roku-sdk/components";
151
+ * const framed = <Effect borderRadius={24} borderWidth={6} borderColor="0xF39C12FF" />;
152
+ * <rectangle width={300} height={140} effect={framed} />;
153
+ *
154
+ * @platformAvailability Effects require Roku OS 16.0 or later and the OpenGL graphics backend.
155
+ * Elsewhere the `effect` prop is ignored without an error and the content renders unstyled. To branch
156
+ * on it, read `getDeviceInfo()` from `@roku-sdk/rsg-ts/runtime` once:
157
+ * `Number(info.osVersion.major) >= 16 && info.graphicsPlatform === "opengl"`. The node's own `supported`
158
+ * field is set when the node is created and cannot be read from TypeScript, and effects disabled by
159
+ * device configuration are invisible to that check.
160
+ *
161
+ * @since Roku OS 16.0
162
+ *
163
+ * @intrinsic effect
164
+ */
165
+ export const Effect: Component<IEffectProps> = props => {
166
+ return <effect {...props}></effect>;
167
+ };
@@ -0,0 +1,45 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { INodeProps } from "./node";
5
+ import type { Uri } from "./types";
6
+
7
+ /**
8
+ * Properties for the Font node.
9
+ *
10
+ * Font specifies the font to be used by a Label node, or any other nodes that render text.
11
+ * The uri field can be set to any TrueType/OpenType font file included in the application package.
12
+ *
13
+ * @see {@link https://developer.roku.com/dev/docs/font}
14
+ *
15
+ * @example
16
+ * // Using with a Label component
17
+ * <Label text="Hello World" font={<Font uri="pkg:/fonts/custom.ttf" size={24} />} />
18
+ */
19
+ export interface IFontProps extends INodeProps {
20
+ /**
21
+ * Specifies a TrueType or OpenType font file.
22
+ * Currently only font files included in the application package can be specified.
23
+ * @default ""
24
+ */
25
+ uri?: Uri;
26
+
27
+ /**
28
+ * Specifies the size of the font in points.
29
+ * @default 1
30
+ */
31
+ size?: number;
32
+
33
+ /**
34
+ * String representation of a Unicode character to display when an unsupported glyph is encountered.
35
+ * For example, "u0020" would render a space for any unrenderable characters.
36
+ * @default ""
37
+ */
38
+ fallbackGlyph?: string;
39
+
40
+ /**
41
+ * If true, forces SceneGraph to update the component before it is rendered
42
+ * @default false
43
+ */
44
+ preUpdate?: boolean;
45
+ }
@@ -0,0 +1,80 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IFontProps } from "./font.props";
5
+ import { Component } from "./types";
6
+ export type { IFontProps } from "./font.props";
7
+
8
+ /**
9
+ * Font specifies the font to be used by a Label node, or any other nodes that render text.
10
+ *
11
+ * The uri field can be set to any TrueType/OpenType font file. A default system font can also
12
+ * be specified using the `font:` prefix (e.g., `font:MediumBoldSystemFont`).
13
+ *
14
+ * **Components that support Font:**
15
+ * - Label / MultiStyleLabel: via `font` prop
16
+ * - ScrollableText: via `font` prop
17
+ * - ScrollingLabel: via `font` prop
18
+ * - LabelList: via `font` and `focusedFont` props
19
+ * - PosterGrid: via `caption1Font` and `caption2Font` props
20
+ *
21
+ * @see {@link https://developer.roku.com/dev/docs/font}
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * // Inline font prop with Label
26
+ * <label text="Custom styled text" font={<font uri="pkg:/fonts/orbitron-font.ttf" size={32} />} />
27
+ * ```
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * // Reusable font definition (declared outside JSX return)
32
+ * const myFont = <font uri="pkg:/fonts/cookie-regular.ttf" size={28} />;
33
+ *
34
+ * // Then in return statement:
35
+ * return (
36
+ * <group>
37
+ * <label text="ABCDEFGHIJKLMNOPQRSTUVWXYZ" font={myFont} />
38
+ * <label text="abcdefghijklmnopqrstuvwxyz" font={myFont} />
39
+ * </group>
40
+ * );
41
+ * ```
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * // Using with ScrollableText
46
+ * <scrollableText
47
+ * text="Long scrollable content..."
48
+ * width={800}
49
+ * height={400}
50
+ * font={<font uri="pkg:/fonts/readable.ttf" size={24} />}
51
+ * />
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * // Using with LabelList
57
+ * <labelList
58
+ * itemSize={[440, 48]}
59
+ * font={<font uri="pkg:/fonts/menu.ttf" size={20} />}
60
+ * focusedFont={<font uri="pkg:/fonts/menu-bold.ttf" size={22} />}
61
+ * content={content}
62
+ * />
63
+ * ```
64
+ *
65
+ * @example
66
+ * ```tsx
67
+ * // Using with PosterGrid captions
68
+ * <posterGrid
69
+ * basePosterSize={[200, 300]}
70
+ * caption1Font={<font uri="pkg:/fonts/title.ttf" size={18} />}
71
+ * caption2Font={<font uri="pkg:/fonts/subtitle.ttf" size={14} />}
72
+ * caption1NumLines={1}
73
+ * caption2NumLines={1}
74
+ * content={content}
75
+ * />
76
+ * ```
77
+ */
78
+ export const Font: Component<IFontProps> = props => {
79
+ return <font {...props}></font>;
80
+ };