@roku-sdk/components 0.0.0-stage → 2.18.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 (211) 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 +84 -3
  8. package/src/__tests__/container.test.tsx +150 -0
  9. package/src/__tests__/glyphs.test.ts +202 -0
  10. package/src/__tests__/modal-boundary-export.test.ts +12 -0
  11. package/src/__tests__/native-modal.test.tsx +224 -0
  12. package/src/__tests__/tween-release.test.tsx +125 -0
  13. package/src/__tests__/tween-stops-shape.test.tsx +165 -0
  14. package/src/__tests__/tween-without-compat.test.tsx +64 -0
  15. package/src/__tests__/tween.test.tsx +195 -0
  16. package/src/blended-busy-spinner.props.ts +37 -0
  17. package/src/blended-busy-spinner.tsx +13 -0
  18. package/src/bounding-tracker.props.ts +18 -0
  19. package/src/bounding-tracker.tsx +13 -0
  20. package/src/button-row.tsx +126 -0
  21. package/src/channel-store.props.ts +425 -0
  22. package/src/channel-store.tsx +76 -0
  23. package/src/container.tsx +130 -0
  24. package/src/core/jsx-runtime.ts +39 -0
  25. package/src/focusable-group.props.ts +8 -0
  26. package/src/focusable-group.tsx +17 -0
  27. package/src/focusable.props.ts +14 -0
  28. package/src/focusable.tsx +17 -0
  29. package/src/glyphs.ts +116 -0
  30. package/src/helpers/index.tsx +4 -0
  31. package/src/helpers/resolution-context.tsx +68 -0
  32. package/src/helpers/scale-for-device-resolution.ts +31 -0
  33. package/src/index.tsx +70 -0
  34. package/src/jsx-props.ts +87 -0
  35. package/src/kdfs.d.ts +14 -0
  36. package/src/kdfs.ts +15 -0
  37. package/src/key-handler.props.ts +33 -0
  38. package/src/key-handler.tsx +24 -0
  39. package/src/modal-dialog.props.ts +38 -0
  40. package/src/modal-dialog.tsx +41 -0
  41. package/src/retain-asset.ts +24 -0
  42. package/src/sg-intrinsics.json +1153 -0
  43. package/src/sg-nodes/__tests__/__snapshots__/font.test.tsx.snap +13 -0
  44. package/src/sg-nodes/__tests__/__snapshots__/prop-forwarding.test.tsx.snap +4409 -0
  45. package/src/sg-nodes/__tests__/core-props.test.ts +301 -0
  46. package/src/sg-nodes/__tests__/font.test.tsx +141 -0
  47. package/src/sg-nodes/__tests__/imperative-methods.test.ts +268 -0
  48. package/src/sg-nodes/__tests__/prop-forwarding.test.tsx +1049 -0
  49. package/src/sg-nodes/__tests__/solid-integration.test.tsx +719 -0
  50. package/src/sg-nodes/animated-image.props.ts +64 -0
  51. package/src/sg-nodes/animated-image.tsx +23 -0
  52. package/src/sg-nodes/animation.props.ts +292 -0
  53. package/src/sg-nodes/animation.tsx +190 -0
  54. package/src/sg-nodes/array-grid.ts +602 -0
  55. package/src/sg-nodes/audio.props.ts +302 -0
  56. package/src/sg-nodes/audio.tsx +57 -0
  57. package/src/sg-nodes/busy-spinner.props.ts +59 -0
  58. package/src/sg-nodes/busy-spinner.tsx +84 -0
  59. package/src/sg-nodes/button-group.props.ts +141 -0
  60. package/src/sg-nodes/button-group.tsx +30 -0
  61. package/src/sg-nodes/button.props.ts +119 -0
  62. package/src/sg-nodes/button.tsx +31 -0
  63. package/src/sg-nodes/check-list.props.ts +71 -0
  64. package/src/sg-nodes/check-list.tsx +55 -0
  65. package/src/sg-nodes/content-node.props.ts +1316 -0
  66. package/src/sg-nodes/content-node.tsx +153 -0
  67. package/src/sg-nodes/core-props.ts +197 -0
  68. package/src/sg-nodes/custom-keyboard.props.ts +108 -0
  69. package/src/sg-nodes/custom-keyboard.tsx +48 -0
  70. package/src/sg-nodes/dialog.props.ts +159 -0
  71. package/src/sg-nodes/dialog.tsx +36 -0
  72. package/src/sg-nodes/dynamic-key-grid.props.ts +22 -0
  73. package/src/sg-nodes/dynamic-key-grid.tsx +16 -0
  74. package/src/sg-nodes/dynamic-keyboard.props.ts +30 -0
  75. package/src/sg-nodes/dynamic-keyboard.tsx +18 -0
  76. package/src/sg-nodes/dynamic-mini-keyboard.props.ts +22 -0
  77. package/src/sg-nodes/dynamic-mini-keyboard.tsx +18 -0
  78. package/src/sg-nodes/dynamic-pinpad.props.ts +57 -0
  79. package/src/sg-nodes/dynamic-pinpad.tsx +18 -0
  80. package/src/sg-nodes/effect.props.ts +116 -0
  81. package/src/sg-nodes/effect.tsx +39 -0
  82. package/src/sg-nodes/font.props.ts +45 -0
  83. package/src/sg-nodes/font.tsx +80 -0
  84. package/src/sg-nodes/group.props.ts +105 -0
  85. package/src/sg-nodes/group.tsx +30 -0
  86. package/src/sg-nodes/label-list.props.ts +86 -0
  87. package/src/sg-nodes/label-list.tsx +64 -0
  88. package/src/sg-nodes/label.props.ts +207 -0
  89. package/src/sg-nodes/label.tsx +56 -0
  90. package/src/sg-nodes/layout-group.props.ts +69 -0
  91. package/src/sg-nodes/layout-group.tsx +75 -0
  92. package/src/sg-nodes/list-panel.props.ts +104 -0
  93. package/src/sg-nodes/list-panel.tsx +49 -0
  94. package/src/sg-nodes/markup-grid.props.ts +78 -0
  95. package/src/sg-nodes/markup-grid.tsx +38 -0
  96. package/src/sg-nodes/markup-list.props.ts +68 -0
  97. package/src/sg-nodes/markup-list.tsx +38 -0
  98. package/src/sg-nodes/mask-group.props.ts +78 -0
  99. package/src/sg-nodes/mask-group.tsx +59 -0
  100. package/src/sg-nodes/media-playback-methods.ts +20 -0
  101. package/src/sg-nodes/node.ts +77 -0
  102. package/src/sg-nodes/overhang.props.ts +169 -0
  103. package/src/sg-nodes/overhang.tsx +29 -0
  104. package/src/sg-nodes/panel-set.props.ts +71 -0
  105. package/src/sg-nodes/panel-set.tsx +53 -0
  106. package/src/sg-nodes/panel.props.ts +176 -0
  107. package/src/sg-nodes/panel.tsx +56 -0
  108. package/src/sg-nodes/poster-grid.props.ts +277 -0
  109. package/src/sg-nodes/poster-grid.tsx +45 -0
  110. package/src/sg-nodes/poster.props.ts +125 -0
  111. package/src/sg-nodes/poster.tsx +44 -0
  112. package/src/sg-nodes/radio-button-list.props.ts +59 -0
  113. package/src/sg-nodes/radio-button-list.tsx +68 -0
  114. package/src/sg-nodes/rectangle.props.ts +29 -0
  115. package/src/sg-nodes/rectangle.tsx +27 -0
  116. package/src/sg-nodes/row-list.props.ts +288 -0
  117. package/src/sg-nodes/row-list.tsx +47 -0
  118. package/src/sg-nodes/scene.props.ts +65 -0
  119. package/src/sg-nodes/scene.tsx +15 -0
  120. package/src/sg-nodes/scrollable-text.props.ts +133 -0
  121. package/src/sg-nodes/scrollable-text.tsx +64 -0
  122. package/src/sg-nodes/scrolling-label.props.ts +106 -0
  123. package/src/sg-nodes/scrolling-label.tsx +42 -0
  124. package/src/sg-nodes/simple-label.props.ts +156 -0
  125. package/src/sg-nodes/simple-label.tsx +59 -0
  126. package/src/sg-nodes/sound-effect.props.ts +130 -0
  127. package/src/sg-nodes/sound-effect.tsx +65 -0
  128. package/src/sg-nodes/standard-dialog.props.ts +520 -0
  129. package/src/sg-nodes/standard-dialog.tsx +428 -0
  130. package/src/sg-nodes/standard-keyboard-dialog.props.ts +84 -0
  131. package/src/sg-nodes/standard-keyboard-dialog.tsx +59 -0
  132. package/src/sg-nodes/standard-message-dialog.props.ts +56 -0
  133. package/src/sg-nodes/standard-message-dialog.tsx +48 -0
  134. package/src/sg-nodes/standard-pinpad-dialog.props.ts +64 -0
  135. package/src/sg-nodes/standard-pinpad-dialog.tsx +46 -0
  136. package/src/sg-nodes/standard-progress-dialog.props.ts +30 -0
  137. package/src/sg-nodes/standard-progress-dialog.tsx +43 -0
  138. package/src/sg-nodes/target-group.props.ts +278 -0
  139. package/src/sg-nodes/target-group.tsx +63 -0
  140. package/src/sg-nodes/target-list.props.ts +128 -0
  141. package/src/sg-nodes/target-list.tsx +66 -0
  142. package/src/sg-nodes/target-set.props.ts +63 -0
  143. package/src/sg-nodes/target-set.tsx +42 -0
  144. package/src/sg-nodes/time-grid.props.ts +646 -0
  145. package/src/sg-nodes/time-grid.tsx +87 -0
  146. package/src/sg-nodes/tween.props.ts +115 -0
  147. package/src/sg-nodes/types.ts +150 -0
  148. package/src/sg-nodes/video.props.ts +573 -0
  149. package/src/sg-nodes/video.tsx +73 -0
  150. package/src/sg-nodes/voice-text-edit-box.props.ts +91 -0
  151. package/src/sg-nodes/voice-text-edit-box.tsx +15 -0
  152. package/src/sg-nodes/zoom-row-list.props.ts +416 -0
  153. package/src/sg-nodes/zoom-row-list.tsx +42 -0
  154. package/src/standard-dialog-next/__tests__/standard-dialog-next.test.tsx +186 -0
  155. package/src/standard-dialog-next/index.ts +5 -0
  156. package/src/standard-dialog-next/standard-dialog-next.props.ts +80 -0
  157. package/src/standard-dialog-next/standard-dialog-next.tsx +326 -0
  158. package/src/test-helpers/tween-test-setup.ts +78 -0
  159. package/src/tween.tsx +173 -0
  160. package/src/types.ts +8 -0
  161. package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.brs +13 -0
  162. package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.xml +10 -0
  163. package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.xml.d.ts +42 -0
  164. package/src/xml-components/bounding-tracker/BoundingTracker.brs +26 -0
  165. package/src/xml-components/bounding-tracker/BoundingTracker.xml +14 -0
  166. package/src/xml-components/bounding-tracker/BoundingTracker.xml.d.ts +42 -0
  167. package/src/xml-components/channel-store/channelStoreExt.brs +159 -0
  168. package/src/xml-components/channel-store/channelStoreExt.xml +29 -0
  169. package/src/xml-components/channel-store/channelStoreExt.xml.d.ts +57 -0
  170. package/src/xml-components/focus/focusable-group.brs +251 -0
  171. package/src/xml-components/focus/focusable-group.xml +15 -0
  172. package/src/xml-components/focus/focusable-group.xml.d.ts +42 -0
  173. package/src/xml-components/focus/focusable.xml +18 -0
  174. package/src/xml-components/focus/focusable.xml.d.ts +48 -0
  175. package/src/xml-components/keyboards/CustomAddressKDF.keyboard.json +547 -0
  176. package/src/xml-components/keyboards/CustomKeyboard.brs +322 -0
  177. package/src/xml-components/keyboards/CustomKeyboard.xml +45 -0
  178. package/src/xml-components/keyboards/CustomKeyboard.xml.d.ts +73 -0
  179. package/src/xml-components/keyboards/KeyGridExt.xml +4 -0
  180. package/src/xml-components/keyboards/KeyGridExt.xml.d.ts +37 -0
  181. package/src/xml-components/keyboards/KeyboardExt.xml +4 -0
  182. package/src/xml-components/keyboards/KeyboardExt.xml.d.ts +37 -0
  183. package/src/xml-components/keyboards/MOPPinPadCustomKDF.keyboard.json +151 -0
  184. package/src/xml-components/keyboards/MiniKeyboardExt.xml +4 -0
  185. package/src/xml-components/keyboards/MiniKeyboardExt.xml.d.ts +37 -0
  186. package/src/xml-components/keyboards/PhonePadCustomKDF.keyboard.json +42 -0
  187. package/src/xml-components/keyboards/PinPadExt.brs +102 -0
  188. package/src/xml-components/keyboards/PinPadExt.xml +17 -0
  189. package/src/xml-components/keyboards/PinPadExt.xml.d.ts +49 -0
  190. package/src/xml-components/keyboards/WifiCustomKDF.keyboard.json +1242 -0
  191. package/src/xml-components/keyhandler/KeyHandler.brs +30 -0
  192. package/src/xml-components/keyhandler/KeyHandler.xml +16 -0
  193. package/src/xml-components/keyhandler/KeyHandler.xml.d.ts +45 -0
  194. package/src/xml-components/managed-list-panel/ManagedListPanel.brs +137 -0
  195. package/src/xml-components/managed-list-panel/ManagedListPanel.xml +10 -0
  196. package/src/xml-components/managed-list-panel/ManagedListPanel.xml.d.ts +37 -0
  197. package/src/xml-components/modal/Modal.brs +105 -0
  198. package/src/xml-components/modal/Modal.xml +22 -0
  199. package/src/xml-components/modal/Modal.xml.d.ts +54 -0
  200. package/src/xml-components/modal/StandardDialogExt.xml +10 -0
  201. package/src/xml-components/modal/StandardDialogExt.xml.d.ts +37 -0
  202. package/src/xml-components/modal/StandardKeyboardDialogExt.xml +10 -0
  203. package/src/xml-components/modal/StandardKeyboardDialogExt.xml.d.ts +37 -0
  204. package/src/xml-components/modal/StandardMessageDialogExt.xml +10 -0
  205. package/src/xml-components/modal/StandardMessageDialogExt.xml.d.ts +37 -0
  206. package/src/xml-components/modal/StandardPinPadDialogExt.xml +10 -0
  207. package/src/xml-components/modal/StandardPinPadDialogExt.xml.d.ts +37 -0
  208. package/src/xml-components/modal/StandardProgressDialogExt.xml +10 -0
  209. package/src/xml-components/modal/StandardProgressDialogExt.xml.d.ts +37 -0
  210. package/src/xml-components/modal/tests/Modal.test.brs +195 -0
  211. package/tsconfig.json +10 -0
@@ -0,0 +1,130 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { transactionManager } from "@roku-sdk/common/transaction";
5
+ import { ComponentRef, DomNode } from "@roku-sdk/interfaces";
6
+ import { Component, JSX, splitProps, tmTransaction } from "@roku-sdk/solid";
7
+
8
+ import { IGroupProps } from "./sg-nodes/group";
9
+
10
+ /**
11
+ * Reference type for the Container component.
12
+ *
13
+ * Extends {@link ComponentRef} with imperative methods for dynamic node
14
+ * insertion and removal. This provides a typed public API for pooling
15
+ * patterns that need to reparent nodes at runtime.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * let containerRef!: ContainerRef;
20
+ *
21
+ * // Acquire a pooled node and insert it
22
+ * const node = pool.acquire();
23
+ * containerRef.insertNode(node.element);
24
+ *
25
+ * // Later, remove the node (returns it to pool)
26
+ * containerRef.removeNode(node.element);
27
+ * pool.release(node);
28
+ * ```
29
+ */
30
+ export interface ContainerRef extends ComponentRef<IGroupProps> {
31
+ /**
32
+ * Inserts a node as a child of this container.
33
+ * The node is appended to the end of the children list.
34
+ *
35
+ * @param node - The DomNode to insert into this container
36
+ */
37
+ insertNode: (node: DomNode) => void;
38
+
39
+ /**
40
+ * Removes a node from this container.
41
+ * The node is marked for removal and will be detached on the next commit.
42
+ *
43
+ * On firmware 15.2.x the parent link is the only strong reference to a
44
+ * SceneGraph node, so a committed detach destroys it and a later
45
+ * `insertNode` of the same node hands the render thread a deleted
46
+ * reference. Pooling that has to run on 15.2.x should reparent through
47
+ * {@link ContainerRef.insertNode} into a hidden container rather than
48
+ * detaching.
49
+ *
50
+ * @param node - The DomNode to remove from this container
51
+ */
52
+ removeNode: (node: DomNode) => void;
53
+ }
54
+
55
+ /**
56
+ * Props for the Container component.
57
+ */
58
+ export interface IContainerProps extends Omit<IGroupProps, "ref"> {
59
+ /** Callback ref to receive the ContainerRef with imperative methods. */
60
+ ref?: (ref: ContainerRef) => void;
61
+ /** Child elements to render inside the container. */
62
+ children?: JSX.Element;
63
+ }
64
+
65
+ /**
66
+ * Container is a Group wrapper that exposes typed imperative methods for
67
+ * dynamic node insertion and removal.
68
+ *
69
+ * This component is designed for advanced pooling patterns where nodes need
70
+ * to be reparented at runtime without triggering full reactive re-renders.
71
+ * It wraps the raw `tmTransaction.insertNode/removeNode` calls with a typed
72
+ * API, eliminating the need for `as any` casts and eslint-disable comments.
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * import { Container, ContainerRef } from "@roku-sdk/components";
77
+ *
78
+ * const PooledGrid: Component = () => {
79
+ * let slotContainerRef!: ContainerRef;
80
+ *
81
+ * createEffect(() => {
82
+ * const node = pool.acquire();
83
+ * slotContainerRef.insertNode(node.element);
84
+ * });
85
+ *
86
+ * return (
87
+ * <Container ref={(el) => (slotContainerRef = el)}>
88
+ * {/* Pooled nodes are inserted here dynamically *\/}
89
+ * </Container>
90
+ * );
91
+ * };
92
+ * ```
93
+ *
94
+ * @see {@link ContainerRef} for the imperative methods available on the ref
95
+ */
96
+ export const Container: Component<IContainerProps> = props => {
97
+ // Use splitProps to preserve reactivity on spread props
98
+ const [local, groupProps] = splitProps(props, ["ref", "children"]);
99
+
100
+ return (
101
+ <group
102
+ {...groupProps}
103
+ ref={(el: DomNode) => {
104
+ // Extend the element with imperative methods
105
+ const containerRef = el as ContainerRef;
106
+
107
+ // `tmTransaction` assumes an open transaction — the Solid renderer
108
+ // opens one before every call it makes. These methods run outside the
109
+ // renderer, so they open it themselves. `insertNode` reaches native
110
+ // `appendNode` synchronously (firmware 15.2.x raises a render-thread
111
+ // exception if no transaction is open) and `removeNode` only queues
112
+ // the detach for the pre-commit hook, which a commit has to drive.
113
+ containerRef.insertNode = (node: DomNode) => {
114
+ transactionManager.startTransaction();
115
+ tmTransaction.insertNode(el, node);
116
+ };
117
+
118
+ containerRef.removeNode = (node: DomNode) => {
119
+ transactionManager.startTransaction();
120
+ tmTransaction.removeNode(el, node);
121
+ };
122
+
123
+ // Pass the extended ref to the consumer
124
+ local.ref?.(containerRef);
125
+ }}
126
+ >
127
+ {local.children}
128
+ </group>
129
+ );
130
+ };
@@ -0,0 +1,39 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ /**
5
+ * JSX runtime entry point for the **frozen core** property contract.
6
+ *
7
+ * This module exists to be named by `jsxImportSource` in an application's tsconfig:
8
+ *
9
+ * ```jsonc
10
+ * { "compilerOptions": { "jsxImportSource": "@roku-sdk/components/core" } }
11
+ * ```
12
+ *
13
+ * TypeScript then resolves JSX intrinsic element types from
14
+ * `@roku-sdk/components/core/jsx-runtime`, and the sibling `core-jsx.d.ts` augments exactly this
15
+ * specifier with an intrinsic map keyed to the `I*CoreProps` interfaces. So `<poster>` resolves to
16
+ * `IPosterCoreProps` rather than the full `IPosterProps`, and a property outside the frozen subset
17
+ * is a compile error naming the property.
18
+ *
19
+ * Two things make this the enforcement point rather than a convenience:
20
+ *
21
+ * 1. **A module-scoped `JSX` namespace replaces the global one, it does not merge with it.** The
22
+ * full intrinsic map in `rsg-ts/src/types/sg-jsx.d.ts` augments a different specifier, so it is
23
+ * not in scope for a build that points here — provided that map is kept out of the program.
24
+ * That is the one easily-missed part of the setup: an app opting in must also drop
25
+ * `@roku-sdk/components/jsx` from `types[]` and the inherited `sg-jsx.d.ts` from `files[]`,
26
+ * because the star re-export below would otherwise carry those augmentations back in.
27
+ * 2. **Application source is unchanged.** Only the tsconfig differs.
28
+ *
29
+ * The runtime behaviour is unchanged — the factory is re-exported from `@roku-sdk/solid` — because
30
+ * the divergence is purely a compile-time *type* contract, not a second implementation. Builds do
31
+ * not read `jsxImportSource` at all: esbuild is configured with a classic `jsxFactory` and
32
+ * `babel-preset-solid` is pinned to `moduleName: "@roku-sdk/solid"`.
33
+ *
34
+ * The re-export must stay a star re-export. TypeScript resolves `JSX.Element`,
35
+ * `JSX.ElementChildrenAttribute` and `JSX.IntrinsicAttributes` from this same module, and those
36
+ * come from SolidJS; a named re-export of the factory alone would leave them undefined.
37
+ */
38
+
39
+ export * from "@roku-sdk/solid/jsx-runtime";
@@ -0,0 +1,8 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IGroupProps } from "./sg-nodes/group.props";
5
+
6
+ export interface IFocusableGroupProps extends IGroupProps {
7
+ rememberLastFocus?: boolean;
8
+ }
@@ -0,0 +1,17 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IFocusableGroupProps } from "./focusable-group.props";
5
+ import { Component } from "./index";
6
+ import { retainAsset } from "./retain-asset";
7
+ import FocusableGroupXML from "./xml-components/focus/focusable-group.xml";
8
+ export type { IFocusableGroupProps } from "./focusable-group.props";
9
+
10
+ export const FocusableGroup: Component<IFocusableGroupProps> = props => {
11
+ retainAsset(FocusableGroupXML);
12
+ return (
13
+ <focusable-group focusable={true} {...props}>
14
+ {props.children}
15
+ </focusable-group>
16
+ );
17
+ };
@@ -0,0 +1,14 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IGroupProps } from "./sg-nodes/group.props";
5
+
6
+ export interface IFocusableProps extends IGroupProps {
7
+ nextFocusRight?: string;
8
+ nextFocusLeft?: string;
9
+ nextFocusUp?: string;
10
+ nextFocusDown?: string;
11
+ nextFocusBack?: string;
12
+ nextFocusOk?: string;
13
+ hasPreferredFocus?: boolean;
14
+ }
@@ -0,0 +1,17 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IFocusableProps } from "./focusable.props";
5
+ import { retainAsset } from "./retain-asset";
6
+ import { Component } from "./types";
7
+ import FocusableXML from "./xml-components/focus/focusable.xml";
8
+ export type { IFocusableProps } from "./focusable.props";
9
+
10
+ export const Focusable: Component<IFocusableProps> = props => {
11
+ retainAsset(FocusableXML);
12
+ return (
13
+ <focusable focusable={true} {...props}>
14
+ {props.children}
15
+ </focusable>
16
+ );
17
+ };
package/src/glyphs.ts ADDED
@@ -0,0 +1,116 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ /**
5
+ * Named character constants for symbols that are awkward or impossible to type
6
+ * directly in source.
7
+ *
8
+ * The constants are grouped by the property that actually matters at runtime:
9
+ * which font has to supply the character.
10
+ *
11
+ * 1. RokuFont01 characters (the private-use area, plus ● ★ ☆). RokuFont01 is
12
+ * the system-wide fallback font in firmware, so these render under a system
13
+ * font AND under a custom app-supplied font.
14
+ * 2. RokuUI-Regular symbols. RokuUI-Regular is the default UI font but is not
15
+ * in the fallback chain, so these render under a system font but become tofu
16
+ * under a custom app-supplied font unless that font supplies them itself.
17
+ *
18
+ * The practical consequence, verified on device against firmware 15.3.5: if
19
+ * your app ships a custom font, the group that needs care is (2), not (1).
20
+ *
21
+ * A custom font that defines a codepoint itself always wins — no fallback
22
+ * occurs. A font carrying its own U+E0xx private-use glyphs will render those
23
+ * rather than the Roku icons.
24
+ *
25
+ * @packageDocumentation
26
+ */
27
+
28
+ // -----------------------------------------------------------------------------
29
+ // RokuFont01 — private-use area glyphs
30
+ //
31
+ // Supplied by the firmware fallback font, so these survive a custom app font.
32
+ // -----------------------------------------------------------------------------
33
+
34
+ export const OPTIONS_GLYPH = "\uE000";
35
+ export const OK_BUTTON_GLYPH = "\uE001";
36
+ export const OPTIONS_BUTTON_GLYPH = "\uE002";
37
+ export const MICROPHONE_GLYPH = "\uE003";
38
+ export const PLAY_PAUSE_BUTTON_GLYPH = "\uE004";
39
+ export const PLAY_PAUSE_GLYPH = "\uE005";
40
+ /**
41
+ * Baseline-aligned dot, sized and positioned to line up with the other icon
42
+ * glyphs in this block. Use {@link BLACK_CIRCLE} for a dot inside a run of
43
+ * text — that one is raised to text centre.
44
+ */
45
+ export const DOT_GLYPH = "\uE006";
46
+ export const HOME_GLYPH = "\uE007";
47
+ export const SEARCH_GLYPH = "\uE008";
48
+ export const REMOTE_GLYPH = "\uE009";
49
+ export const REPLAY_GLYPH = "\uE010";
50
+ export const MICROPHONE_BUTTON_GLYPH = "\uE011";
51
+ export const SEARCH_BUTTON_GLYPH = "\uE012";
52
+ export const FAST_FORWARD_GLYPH = "\uE013";
53
+ export const REWIND_GLYPH = "\uE014";
54
+ export const SHORTCUT_ONE_BUTTON_GLYPH = "\uE015";
55
+ export const SHORTCUT_TWO_BUTTON_GLYPH = "\uE016";
56
+ export const CLOSED_CAPTIONS_GLYPH = "\uE017";
57
+ export const LIVE_TV_GLYPH = "\uE018";
58
+ export const SLEEP_TIMER_GLYPH = "\uE019";
59
+ export const GENERAL_APP_GLYPH = "\uE020";
60
+ export const BACK_BUTTON_GLYPH = "\uE021";
61
+ export const HOME_BUTTON_GLYPH = "\uE022";
62
+ export const QUICK_LAUNCH_BUTTON_GLYPH = "\uE023";
63
+ export const DOWN_BUTTON_GLYPH = "\uE024";
64
+ export const LEFT_BUTTON_GLYPH = "\uE025";
65
+ export const RIGHT_BUTTON_GLYPH = "\uE026";
66
+ export const KIDS_PROFILE_GLYPH = "\uE027";
67
+ export const UP_BUTTON_GLYPH = "\uE028";
68
+ export const POWER_BUTTON_GLYPH = "\uE029";
69
+ export const THUMBS_UP_GLYPH = "\uE030";
70
+ export const THUMBS_DOWN_GLYPH = "\uE031";
71
+
72
+ // -----------------------------------------------------------------------------
73
+ // RokuFont01 — standard Unicode symbols
74
+ //
75
+ // These are real Unicode codepoints, but RokuUI-Regular does not contain them:
76
+ // they resolve through the RokuFont01 fallback, exactly like the private-use
77
+ // glyphs above. That fallback applies under a custom app font too, so these are
78
+ // the safe shapes to use in custom-font text.
79
+ // -----------------------------------------------------------------------------
80
+
81
+ /** ● U+25CF BLACK CIRCLE — raised to text centre, for a dot inside a text run. */
82
+ export const BLACK_CIRCLE = "\u25CF";
83
+ /** ★ U+2605 BLACK STAR — filled star, e.g. a filled rating position. */
84
+ export const BLACK_STAR = "\u2605";
85
+ /** ☆ U+2606 WHITE STAR — outlined star, e.g. an empty rating position. */
86
+ export const WHITE_STAR = "\u2606";
87
+
88
+ // -----------------------------------------------------------------------------
89
+ // RokuUI-Regular symbols
90
+ //
91
+ // Present in the default UI font, so no fallback is needed under a system font.
92
+ // Under a custom app font there is no RokuFont01 backstop for these either — a
93
+ // custom font that lacks them renders tofu. These are the constants to watch if
94
+ // your app ships its own font.
95
+ // -----------------------------------------------------------------------------
96
+
97
+ /** • U+2022 BULLET */
98
+ export const BULLET = "\u2022";
99
+ /** … U+2026 HORIZONTAL ELLIPSIS */
100
+ export const ELLIPSIS = "\u2026";
101
+ /** – U+2013 EN DASH */
102
+ export const EN_DASH = "\u2013";
103
+ /** — U+2014 EM DASH */
104
+ export const EM_DASH = "\u2014";
105
+ /** ← U+2190 LEFTWARDS ARROW */
106
+ export const LEFT_ARROW = "\u2190";
107
+ /** ↑ U+2191 UPWARDS ARROW */
108
+ export const UP_ARROW = "\u2191";
109
+ /** → U+2192 RIGHTWARDS ARROW */
110
+ export const RIGHT_ARROW = "\u2192";
111
+ /** ↓ U+2193 DOWNWARDS ARROW */
112
+ export const DOWN_ARROW = "\u2193";
113
+ /** ✓ U+2713 CHECK MARK */
114
+ export const CHECK_MARK = "\u2713";
115
+ /** ✗ U+2717 BALLOT X */
116
+ export const BALLOT_X = "\u2717";
@@ -0,0 +1,4 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+ export * from "./resolution-context";
4
+ export * from "./scale-for-device-resolution";
@@ -0,0 +1,68 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+ import { getAppInfo } from "@roku-sdk/runtime/app-info";
4
+ import { useUiResolution } from "@roku-sdk/runtime/device-info-events";
5
+ import type { Component, JSX } from "@roku-sdk/solid";
6
+ import { createContext, createEffect, createSignal, useContext } from "@roku-sdk/solid";
7
+
8
+ export enum Resolution {
9
+ HD = "hd",
10
+ FHD = "fhd",
11
+ // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
12
+ hd = "hd",
13
+ // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
14
+ fhd = "fhd"
15
+ }
16
+ export enum ResolutionScale {
17
+ HD = 1,
18
+ FHD = 1.5,
19
+ // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
20
+ hd = 1,
21
+ // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
22
+ fhd = 1.5
23
+ }
24
+ interface ResolutionContextValue {
25
+ isFHDOnly: boolean;
26
+ deviceResolution: () => Resolution | undefined;
27
+ }
28
+
29
+ export interface IResolutionContextProviderProps {
30
+ children: JSX.Element;
31
+ }
32
+
33
+ const ResolutionContext = createContext<ResolutionContextValue>();
34
+
35
+ export const ResolutionProvider: Component<IResolutionContextProviderProps> = props => {
36
+ const appInfo = getAppInfo();
37
+ const resolutions = appInfo?.getManifestValue("ui_resolutions")?.split(",");
38
+ const isFHDOnly = resolutions?.includes("fhd") && !resolutions?.includes("hd");
39
+ const { uiResolution } = useUiResolution();
40
+
41
+ const [deviceResolution, setDeviceResolution] = createSignal<Resolution>();
42
+
43
+ createEffect(() => {
44
+ const resolution = uiResolution();
45
+
46
+ if (resolution && !deviceResolution()) {
47
+ setDeviceResolution(resolution.name.toLowerCase() as Resolution);
48
+ }
49
+ });
50
+
51
+ const value: ResolutionContextValue = {
52
+ isFHDOnly: isFHDOnly === undefined ? true : isFHDOnly,
53
+ deviceResolution
54
+ };
55
+
56
+ return <ResolutionContext.Provider value={value}>{props.children}</ResolutionContext.Provider>;
57
+ };
58
+
59
+ export function useResolutionContext() {
60
+ const context = useContext(ResolutionContext);
61
+ if (!context) {
62
+ return {
63
+ isFHDOnly: true,
64
+ deviceResolution: () => Resolution.FHD
65
+ } as ResolutionContextValue;
66
+ }
67
+ return context;
68
+ }
@@ -0,0 +1,31 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+ import { Accessor, createMemo } from "@roku-sdk/solid";
4
+
5
+ import { Vector2D } from "../sg-nodes/types";
6
+ import { useResolutionContext } from "./resolution-context";
7
+
8
+ const scale = (value: number) => Math.floor((value * 2) / 3);
9
+
10
+ /**
11
+ * Workaround to scale the size based on the device resolution.
12
+ * Used to fix the lack of autoscaling when the device resolution is not FHD.
13
+ * Resolution-related properties of some SceneGraph nodes do not autoscale
14
+ * when the resolution is not FHD, even when autoscaling is enabled
15
+ * (manifest has FHD resolution only).
16
+ *
17
+ * @important This workaround works only under ResolutionProvider.
18
+ *
19
+ * @param size - The size accessor to apply the workaround to.
20
+ * @returns The autoscaled size accessor.
21
+ */
22
+ export const scaleForDeviceResolution = (size: Accessor<Vector2D>): Accessor<Vector2D> => {
23
+ const { deviceResolution } = useResolutionContext();
24
+ return createMemo<Vector2D>(() => {
25
+ const [width, height] = size();
26
+ if (deviceResolution() === "hd") {
27
+ return [scale(width), scale(height)];
28
+ }
29
+ return [width, height];
30
+ });
31
+ };
package/src/index.tsx ADDED
@@ -0,0 +1,70 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ export * from "./sg-nodes/animation";
5
+ export * from "./sg-nodes/animated-image";
6
+ export * from "./sg-nodes/array-grid";
7
+ export * from "./bounding-tracker";
8
+ export * from "./blended-busy-spinner";
9
+ export { BusySpinner } from "./sg-nodes/busy-spinner";
10
+ export * from "./sg-nodes/button";
11
+ export * from "./sg-nodes/button-group";
12
+ export * from "./channel-store";
13
+ export * from "./container";
14
+ export * from "./sg-nodes/content-node";
15
+ export * from "./sg-nodes/custom-keyboard";
16
+ export * from "./sg-nodes/dialog";
17
+ export * from "./sg-nodes/dynamic-key-grid";
18
+ export * from "./sg-nodes/dynamic-keyboard";
19
+ export * from "./sg-nodes/dynamic-mini-keyboard";
20
+ export * from "./sg-nodes/dynamic-pinpad";
21
+ export * from "./sg-nodes/effect";
22
+ export * from "./focusable";
23
+ export * from "./focusable-group";
24
+ export * from "./sg-nodes/font";
25
+ export * from "./sg-nodes/group";
26
+ export * from "./key-handler";
27
+ export * from "./sg-nodes/label";
28
+ export * from "./sg-nodes/label-list";
29
+ export * from "./sg-nodes/check-list";
30
+ export * from "./sg-nodes/layout-group";
31
+ export * from "./sg-nodes/list-panel";
32
+ export * from "./sg-nodes/markup-list";
33
+ export * from "./sg-nodes/mask-group";
34
+ // Implementation lives in @roku-sdk/navigation. Re-exported here so
35
+ // existing `import { ModalBoundary, useModal } from "@roku-sdk/components"` keeps working.
36
+ export { ModalBoundary, type IModalBoundaryProps, useModal, type UseModalResult } from "@roku-sdk/navigation";
37
+ export * from "./sg-nodes/node";
38
+ export * from "./sg-nodes/overhang";
39
+ export * from "./sg-nodes/panel";
40
+ export * from "./sg-nodes/panel-set";
41
+ export * from "./sg-nodes/poster";
42
+ export * from "./sg-nodes/poster-grid";
43
+ export * from "./sg-nodes/radio-button-list";
44
+ export * from "./sg-nodes/rectangle";
45
+ export * from "./sg-nodes/row-list";
46
+ export * from "./sg-nodes/time-grid";
47
+ export * from "./sg-nodes/zoom-row-list";
48
+ export * from "./sg-nodes/scene";
49
+ export * from "./sg-nodes/scrolling-label";
50
+ export * from "./sg-nodes/scrollable-text";
51
+ export * from "./sg-nodes/simple-label";
52
+ export * from "./sg-nodes/standard-dialog";
53
+ export * from "./sg-nodes/standard-message-dialog";
54
+ export * from "./sg-nodes/standard-keyboard-dialog";
55
+ export * from "./sg-nodes/standard-pinpad-dialog";
56
+ export * from "./sg-nodes/standard-progress-dialog";
57
+ export * from "./sg-nodes/target-set";
58
+ export * from "./sg-nodes/target-group";
59
+ export * from "./sg-nodes/target-list";
60
+ export * from "./types";
61
+ export * from "./sg-nodes/video";
62
+ export * from "./sg-nodes/voice-text-edit-box";
63
+ export * from "./sg-nodes/audio";
64
+ export * from "./sg-nodes/sound-effect";
65
+ export * from "./button-row";
66
+ export * from "./sg-nodes/markup-grid";
67
+
68
+ export { Tween } from "./tween";
69
+ export type { TweenProps } from "./tween";
70
+ export type { ITweenProps } from "./sg-nodes/tween.props";
@@ -0,0 +1,87 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ // Types-only barrel for JSX intrinsic augmentations. Imported by jsx.d.ts / sg-jsx.d.ts.
5
+ // Do not re-export from .tsx modules — that reintroduces consumer tsc circular errors.
6
+
7
+ export type { IBlendedBusySpinnerProps } from "./blended-busy-spinner.props";
8
+ export type { IBoundingTrackerProps } from "./bounding-tracker.props";
9
+ export type { IChannelStoreProps } from "./channel-store.props";
10
+ export type { IFocusableGroupProps } from "./focusable-group.props";
11
+ export type { IFocusableProps } from "./focusable.props";
12
+ export type { IKeyHandlerProps } from "./key-handler.props";
13
+ export type { IModalProps } from "./modal-dialog.props";
14
+ export type {
15
+ IAnimationBaseProps,
16
+ IAnimationProps,
17
+ IColorFieldInterpolatorProps,
18
+ IFloatFieldInterpolatorProps,
19
+ IVector2DFieldInterpolatorProps
20
+ } from "./sg-nodes/animation.props";
21
+ export type { IAnimatedImageProps } from "./sg-nodes/animated-image.props";
22
+ export type { IAudioProps } from "./sg-nodes/audio.props";
23
+ export type { IBusySpinnerProps } from "./sg-nodes/busy-spinner.props";
24
+ export type { IButtonGroupProps } from "./sg-nodes/button-group.props";
25
+ export type { IButtonProps } from "./sg-nodes/button.props";
26
+ export type { ICheckListProps } from "./sg-nodes/check-list.props";
27
+ export type { IContentNodeProps } from "./sg-nodes/content-node.props";
28
+ export type { IKeyboardPropsWithVoice } from "./sg-nodes/custom-keyboard.props";
29
+ export type { IDialogProps } from "./sg-nodes/dialog.props";
30
+ export type { IDynamicKeyGridProps } from "./sg-nodes/dynamic-key-grid.props";
31
+ export type { IDynamicKeyboardProps } from "./sg-nodes/dynamic-keyboard.props";
32
+ export type { IDynamicMiniKeyboardProps } from "./sg-nodes/dynamic-mini-keyboard.props";
33
+ export type { IDynamicPinPadProps } from "./sg-nodes/dynamic-pinpad.props";
34
+ export type { IEffectProps } from "./sg-nodes/effect.props";
35
+ export type { IFontProps } from "./sg-nodes/font.props";
36
+ export type { IGroupProps } from "./sg-nodes/group.props";
37
+ export type { ILabelListProps } from "./sg-nodes/label-list.props";
38
+ export type { ILabelProps, IMultiStyleLabelProps } from "./sg-nodes/label.props";
39
+ export type { ILayoutGroupProps } from "./sg-nodes/layout-group.props";
40
+ export type { IFullPanelProps, IListPanelProps } from "./sg-nodes/list-panel.props";
41
+ export type { IMarkupGridProps } from "./sg-nodes/markup-grid.props";
42
+ export type { IMarkupListProps } from "./sg-nodes/markup-list.props";
43
+ export type { IMaskGroupProps } from "./sg-nodes/mask-group.props";
44
+ export type { IOverhangProps } from "./sg-nodes/overhang.props";
45
+ export type { IPanelSetProps } from "./sg-nodes/panel-set.props";
46
+ export type { IPanelProps } from "./sg-nodes/panel.props";
47
+ export type { IPosterGridProps } from "./sg-nodes/poster-grid.props";
48
+ export type { IPosterProps } from "./sg-nodes/poster.props";
49
+ export type { IRadioButtonListProps } from "./sg-nodes/radio-button-list.props";
50
+ export type { IRectangleProps } from "./sg-nodes/rectangle.props";
51
+ export type { IRowListProps } from "./sg-nodes/row-list.props";
52
+ export type { ISceneProps } from "./sg-nodes/scene.props";
53
+ export type { IScrollableTextProps } from "./sg-nodes/scrollable-text.props";
54
+ export type { IScrollingLabelProps } from "./sg-nodes/scrolling-label.props";
55
+ export type { ISimpleLabelProps } from "./sg-nodes/simple-label.props";
56
+ export type { ISoundEffectProps } from "./sg-nodes/sound-effect.props";
57
+ export type {
58
+ IStandardDialogProps,
59
+ IStandardDialogRSGPaletteNodeProps,
60
+ IStdDlgActionCardItemProps,
61
+ IStdDlgBulletTextItemProps,
62
+ IStdDlgButtonAreaProps,
63
+ IStdDlgButtonProps,
64
+ IStdDlgContentAreaProps,
65
+ IStdDlgCustomItemProps,
66
+ IStdDlgDeterminateProgressItemProps,
67
+ IStdDlgGraphicItemProps,
68
+ IStdDlgItemGroupProps,
69
+ IStdDlgKeyboardItemProps,
70
+ IStdDlgMultiStyleTextItemProps,
71
+ IStdDlgProgressItemProps,
72
+ IStdDlgSideCardAreaProps,
73
+ IStdDlgTextItemProps,
74
+ IStdDlgTitleAreaProps
75
+ } from "./sg-nodes/standard-dialog.props";
76
+ export type { IStandardKeyboardDialogProps } from "./sg-nodes/standard-keyboard-dialog.props";
77
+ export type { IStandardMessageDialogProps } from "./sg-nodes/standard-message-dialog.props";
78
+ export type { IStandardPinPadDialogProps } from "./sg-nodes/standard-pinpad-dialog.props";
79
+ export type { IStandardProgressDialogProps } from "./sg-nodes/standard-progress-dialog.props";
80
+ export type { ITargetGroupProps } from "./sg-nodes/target-group.props";
81
+ export type { ITargetListProps } from "./sg-nodes/target-list.props";
82
+ export type { ITargetSetProps } from "./sg-nodes/target-set.props";
83
+ export type { ITimeGridProps } from "./sg-nodes/time-grid.props";
84
+ export type { ITweenProps } from "./sg-nodes/tween.props";
85
+ export type { IBifDisplayProps, IProgressBarProps, ITrickPlayBarProps, IVideoProps } from "./sg-nodes/video.props";
86
+ export type { IVoiceTextEditBoxProps } from "./sg-nodes/voice-text-edit-box.props";
87
+ export type { IZoomRowListProps } from "./sg-nodes/zoom-row-list.props";
package/src/kdfs.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ /**
5
+ * Packaged Key Definition File URI exports (resolved to pkg:/components/... at build time).
6
+ *
7
+ * Keep this in sync with `kdfs.ts`: every export added there must be declared here.
8
+ */
9
+ declare module "@roku-sdk/components/kdfs" {
10
+ export const CustomAddressKDF: string;
11
+ export const MOPPinPadCustomKDF: string;
12
+ export const PhonePadCustomKDF: string;
13
+ export const WifiCustomKDF: string;
14
+ }
package/src/kdfs.ts ADDED
@@ -0,0 +1,15 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ /**
5
+ * Packaged Key Definition File URIs for standard keyboard layouts.
6
+ *
7
+ * Import only the layouts you use so unused KDF files tree-shake out of the build.
8
+ *
9
+ * When adding a new KDF export here, add a matching declaration to `kdfs.d.ts`
10
+ * (the published type surface for `@roku-sdk/components/kdfs`); the two must stay in sync.
11
+ */
12
+ export { default as CustomAddressKDF } from "./xml-components/keyboards/CustomAddressKDF.keyboard.json";
13
+ export { default as MOPPinPadCustomKDF } from "./xml-components/keyboards/MOPPinPadCustomKDF.keyboard.json";
14
+ export { default as PhonePadCustomKDF } from "./xml-components/keyboards/PhonePadCustomKDF.keyboard.json";
15
+ export { default as WifiCustomKDF } from "./xml-components/keyboards/WifiCustomKDF.keyboard.json";