@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,64 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IStandardDialogProps } from "./standard-dialog.props";
5
+ import type { EventHandler } from "./types";
6
+
7
+ /**
8
+ * Properties for the {@link StandardPinPadDialog} component.
9
+ *
10
+ * Extends {@link IStandardDialogProps} with PIN pad-specific fields for numeric entry.
11
+ *
12
+ * @see {@link https://developer.roku.com/dev/docs/standard-pinpad-dialog}
13
+ */
14
+ export interface IStandardPinPadDialogProps extends IStandardDialogProps {
15
+ /**
16
+ * The title displayed at the top of the dialog.
17
+ * @default ""
18
+ */
19
+ title?: string;
20
+ /**
21
+ * One or more blocks of text describing the data to be entered.
22
+ * Each string in the array is displayed as a separate block of text.
23
+ * Minimize message length to avoid automatic scrollbar.
24
+ * @default []
25
+ */
26
+ message?: string[];
27
+ /**
28
+ * List of button labels to display in the button area at the bottom of the dialog.
29
+ * Minimize the number of buttons to ensure visibility without scrolling.
30
+ * @default []
31
+ */
32
+ buttons?: string[];
33
+ /**
34
+ * The entered PIN value displayed in the text edit box.
35
+ * Updated with the currently entered value as the user enters each digit.
36
+ * @default ""
37
+ */
38
+ pin?: string;
39
+ /**
40
+ * Fired whenever the PIN in the text edit box changes (on each digit entry).
41
+ * The handler receives the full current PIN string entered by the user.
42
+ *
43
+ * Use this to react to live input — for example to validate the PIN length
44
+ * or enable/disable action buttons based on what the user has entered so far.
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * const [pin, setPin] = createSignal("");
49
+ *
50
+ * <StandardPinPadDialog
51
+ * title="Enter PIN"
52
+ * pin={pin()}
53
+ * onPin={(value) => {
54
+ * // Called on every digit entry with the full current PIN
55
+ * setPin(value);
56
+ * console.log("User is entering PIN:", value);
57
+ * }}
58
+ * buttons={["Submit", "Cancel"]}
59
+ * onButtonSelected={(index) => console.log("Button:", index)}
60
+ * />
61
+ * ```
62
+ */
63
+ onPin?: EventHandler<string>;
64
+ }
@@ -0,0 +1,46 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { Modal, modalPropKeys } from "../modal-dialog";
5
+ import { retainAsset } from "../retain-asset";
6
+ import StandardPinPadDialogExtXML from "../xml-components/modal/StandardPinPadDialogExt.xml";
7
+ import type { IStandardPinPadDialogProps } from "./standard-pinpad-dialog.props";
8
+ import { Component, splitProps } from "./types";
9
+ export type { IStandardPinPadDialogProps } from "./standard-pinpad-dialog.props";
10
+
11
+ /**
12
+ * Displays a PIN pad dialog for text and voice entry of numeric PIN codes.
13
+ *
14
+ * `StandardPinPadDialog` is a pre-built dialog similar to the legacy PinDialog node,
15
+ * but includes voice entry of numeric digits through its internal DynamicPinPad and
16
+ * VoiceTextEditBox nodes.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * const [showPinPad, setShowPinPad] = createSignal(false);
21
+ * const [pin, setPin] = createSignal("");
22
+ *
23
+ * {showPinPad() && (
24
+ * <StandardPinPadDialog
25
+ * title="Enter PIN"
26
+ * message={["Please enter your 4-digit PIN:"]}
27
+ * pin={pin()}
28
+ * buttons={["Submit", "Cancel"]}
29
+ * onButtonSelected={(index) => {
30
+ * if (index === 0) {
31
+ * console.log("PIN entered:", pin());
32
+ * }
33
+ * setShowPinPad(false);
34
+ * }}
35
+ * onWasClosed={() => setShowPinPad(false)}
36
+ * />
37
+ * )}
38
+ * ```
39
+ *
40
+ * @see {@link https://developer.roku.com/dev/docs/standard-pinpad-dialog}
41
+ */
42
+ export const StandardPinPadDialog: Component<IStandardPinPadDialogProps> = props => {
43
+ retainAsset(StandardPinPadDialogExtXML);
44
+ const [local, rest] = splitProps(props, modalPropKeys);
45
+ return <Modal {...local} dialog={<standardPinPadDialogExt {...rest} />} />;
46
+ };
@@ -0,0 +1,30 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IStandardDialogProps } from "./standard-dialog.props";
5
+
6
+ /**
7
+ * Properties for the {@link StandardProgressDialog} component.
8
+ *
9
+ * Extends {@link IStandardDialogProps} (omitting button events since progress dialogs have no buttons)
10
+ * with fields for displaying a spinning progress indicator.
11
+ *
12
+ * @see {@link https://developer.roku.com/dev/docs/standard-progress-dialog}
13
+ */
14
+ export interface IStandardProgressDialogProps extends Omit<
15
+ IStandardDialogProps,
16
+ "onButtonSelected" | "onButtonFocused"
17
+ > {
18
+ /**
19
+ * The title displayed at the top of the dialog.
20
+ * If empty, the dialog displays without a title area and uses minimum width.
21
+ * @default ""
22
+ */
23
+ title?: string;
24
+ /**
25
+ * A string displayed next to the spinning progress indicator.
26
+ * Typically tells the user why they are waiting. Minimize message length.
27
+ * @default ""
28
+ */
29
+ message?: string;
30
+ }
@@ -0,0 +1,43 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { Modal, modalPropKeys } from "../modal-dialog";
5
+ import { retainAsset } from "../retain-asset";
6
+ import StandardProgressDialogExtXML from "../xml-components/modal/StandardProgressDialogExt.xml";
7
+ import type { IStandardProgressDialogProps } from "./standard-progress-dialog.props";
8
+ import { Component, splitProps } from "./types";
9
+ export type { IStandardProgressDialogProps } from "./standard-progress-dialog.props";
10
+
11
+ /**
12
+ * Displays a progress dialog with a spinning indicator and message.
13
+ *
14
+ * `StandardProgressDialog` is a pre-built dialog similar to the legacy ProgressDialog node.
15
+ * It displays a spinning progress indicator for tasks with an indeterminate duration.
16
+ *
17
+ * Note: Unlike other standard dialogs, `StandardProgressDialog` has no buttons — it only
18
+ * displays a spinner with a message. Close the dialog programmatically by setting
19
+ * {@link IStandardProgressDialogProps.close | close} to `true` or by unmounting the component.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * const [isLoading, setIsLoading] = createSignal(true);
24
+ *
25
+ * // Show progress dialog while loading
26
+ * {isLoading() && (
27
+ * <StandardProgressDialog
28
+ * title="Loading"
29
+ * message="Please wait..."
30
+ * />
31
+ * )}
32
+ *
33
+ * // When loading completes, hide the dialog
34
+ * setTimeout(() => setIsLoading(false), 3000);
35
+ * ```
36
+ *
37
+ * @see {@link https://developer.roku.com/dev/docs/standard-progress-dialog}
38
+ */
39
+ export const StandardProgressDialog: Component<IStandardProgressDialogProps> = props => {
40
+ retainAsset(StandardProgressDialogExtXML);
41
+ const [local, rest] = splitProps(props, modalPropKeys);
42
+ return <Modal {...local} dialog={<standardProgressDialogExt {...rest} />} />;
43
+ };
@@ -0,0 +1,278 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { IGroupProps } from "./group.props";
5
+ import type { EventHandler, JSX } from "./types";
6
+
7
+ /**
8
+ * Properties for the TargetGroup node.
9
+ *
10
+ * TargetGroup associates a set of rectangular target regions (defined by a
11
+ * {@link ITargetSetProps | TargetSet}) with child items rendered by a compiled
12
+ * XML component. It is typically used to create scrolling lists where the
13
+ * focused item occupies more space than unfocused items, with smooth
14
+ * animations between states.
15
+ *
16
+ * **How it works:**
17
+ * 1. Define target rectangles via a TargetSet — one rectangle per visible
18
+ * slot, including off-screen slots for smooth entry/exit.
19
+ * 2. Specify an XML component via `itemComponentName` to render each item.
20
+ * The TargetGroup creates instances on demand and updates their fields
21
+ * (`currTarget`, `currRect`, `index`, `groupHasFocus`, `itemContent`,
22
+ * `focusPercent`, `itemHasFocus`) as items animate.
23
+ * 3. Navigate by setting `animateToItem` or `jumpToItem` to move the
24
+ * content so that the specified index occupies the focus region.
25
+ * 4. Animate between layouts by setting `animateToTargetSet` to smoothly
26
+ * transition all items from one set of target rectangles to another.
27
+ *
28
+ * @extends IGroupProps
29
+ * @see {@link https://developer.roku.com/dev/docs/targetgroup}
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * import "./compiled-components/MyItem.xml";
34
+ *
35
+ * <TargetGroup
36
+ * itemComponentName="MyItem"
37
+ * targetSet={
38
+ * <TargetSet
39
+ * targetRects={[
40
+ * [0, -200, 1920, 200],
41
+ * [0, 20, 1920, 200],
42
+ * [0, 240, 1920, 400],
43
+ * [0, 660, 1920, 200],
44
+ * [0, 880, 1920, 200],
45
+ * ]}
46
+ * focusIndex={2}
47
+ * />
48
+ * }
49
+ * content={contentNode}
50
+ * onItemFocused={(index) => console.log("Focused:", index)}
51
+ * onItemSelected={(index) => console.log("Selected:", index)}
52
+ * />
53
+ * ```
54
+ */
55
+ export interface ITargetGroupProps extends IGroupProps {
56
+ // ─────────────────────────────────────────────────────────────────────────
57
+ // Item Rendering
58
+ // ─────────────────────────────────────────────────────────────────────────
59
+
60
+ /**
61
+ * Specifies the name of the XML component used to render each group item.
62
+ * An instance is created on demand for each visible target region.
63
+ *
64
+ * Register the compiled component by importing its XML file before use:
65
+ * ```ts
66
+ * import "./compiled-components/MyItem.xml";
67
+ * ```
68
+ *
69
+ * The XML component may define interface fields that TargetGroup will
70
+ * automatically update: `currTarget`, `currRect`, `index`,
71
+ * `groupHasFocus`, `itemContent`, `focusPercent`, and `itemHasFocus`.
72
+ *
73
+ * @example "MyItem"
74
+ * @see {@link https://developer.roku.com/dev/docs/targetgroup#targetgroup-xml-component | TargetGroup XML Component}
75
+ */
76
+ itemComponentName?: string;
77
+
78
+ /**
79
+ * Specifies the content for the group.
80
+ *
81
+ * Should be a single root `ContentNode` with one child `ContentNode` per
82
+ * item. Each child node should contain the data required by the XML
83
+ * component specified in `itemComponentName`.
84
+ *
85
+ * **Important:** Place `content` before `jumpToItem` and `animateToItem` in the
86
+ * component. When content is updated dynamically, update `jumpToItem`/`animateToItem`
87
+ * in the same transaction or after content has been updated.
88
+ *
89
+ * @see {@link https://developer.roku.com/dev/docs/targetgroup#data-bindings | Data Bindings}
90
+ */
91
+ content?: JSX.Element;
92
+
93
+ // ─────────────────────────────────────────────────────────────────────────
94
+ // Target Configuration
95
+ // ─────────────────────────────────────────────────────────────────────────
96
+
97
+ /**
98
+ * Specifies the TargetSet to use to define the target regions of the
99
+ * items in the group. When set or modified, the target regions are
100
+ * immediately adjusted to use the new values.
101
+ *
102
+ * @see {@link https://developer.roku.com/dev/docs/targetgroup | TargetGroup targetSet}
103
+ */
104
+ targetSet?: JSX.Element;
105
+
106
+ /**
107
+ * For TargetSets that do not specify a `focusIndex`, this value will
108
+ * be used as the index of the target rectangle where the focused item
109
+ * is located. If a TargetSet specifies any value for `focusIndex`,
110
+ * that value will be used instead.
111
+ *
112
+ * @default 0
113
+ */
114
+ defaultTargetSetFocusIndex?: number;
115
+
116
+ // ─────────────────────────────────────────────────────────────────────────
117
+ // Scrolling Behaviour
118
+ // ─────────────────────────────────────────────────────────────────────────
119
+
120
+ /**
121
+ * Specifies whether the content items wrap around at the end of the
122
+ * TargetGroup to fill all of the target rectangles.
123
+ *
124
+ * @default false
125
+ */
126
+ wrap?: boolean;
127
+
128
+ /**
129
+ * Forces the item index to be interpolated in a forward direction when
130
+ * animating between items. Only has an effect when `wrap` is `true`.
131
+ *
132
+ * For example, when the number of items is 10 and animating from item 9
133
+ * to item 0, enabling this causes the sequence `9 → 0` instead of
134
+ * `9 → 8 → 7 → … → 0`.
135
+ *
136
+ * @default false
137
+ */
138
+ advancing?: boolean;
139
+
140
+ /**
141
+ * Forces the item index to be interpolated in a reverse direction when
142
+ * animating between items. Only has an effect when `wrap` is `true`.
143
+ *
144
+ * For example, when the number of items is 10 and animating from item 0
145
+ * to item 9, enabling this causes the sequence `0 → 9` instead of
146
+ * `0 → 1 → … → 9`.
147
+ *
148
+ * @default false
149
+ */
150
+ reversing?: boolean;
151
+
152
+ // ─────────────────────────────────────────────────────────────────────────
153
+ // Animation
154
+ // ─────────────────────────────────────────────────────────────────────────
155
+
156
+ /**
157
+ * Specifies the time, in seconds, to perform the animation when the
158
+ * `animateToItem` or `animateToTargetSet` fields are set.
159
+ *
160
+ * @default 0.3
161
+ */
162
+ duration?: number;
163
+
164
+ /**
165
+ * Specifies the interpolator function used in TargetGroup animations
166
+ * between items.
167
+ *
168
+ * @default "inOutCubic"
169
+ */
170
+ easeFunction?: string;
171
+
172
+ // ─────────────────────────────────────────────────────────────────────────
173
+ // Debug
174
+ // ─────────────────────────────────────────────────────────────────────────
175
+
176
+ /**
177
+ * When `true`, the current target rectangles (as defined in the
178
+ * `currTargetSet`) are drawn using the color specified in the
179
+ * TargetSet's `color` field. Typically only set to `true` for
180
+ * debugging purposes.
181
+ *
182
+ * @default false
183
+ */
184
+ showTargetRects?: boolean;
185
+
186
+ // ─────────────────────────────────────────────────────────────────────────
187
+ // Navigation (Write-Only)
188
+ // ─────────────────────────────────────────────────────────────────────────
189
+
190
+ /**
191
+ * When set to a valid item index, causes the group to immediately
192
+ * update so that the specified index moves to the target region
193
+ * specified by the TargetSet's `focusIndex`.
194
+ *
195
+ * **Important:** Set this property after `content` in the component order. When
196
+ * content is updated dynamically, update `jumpToItem` in the same transaction
197
+ * or after content has been updated.
198
+ *
199
+ * @example
200
+ * ```tsx
201
+ * <TargetGroup
202
+ * content={contentNode}
203
+ * jumpToItem={jumpToItem}
204
+ * ...
205
+ * />
206
+ * ```
207
+ *
208
+ * @writeonly
209
+ * @default 0
210
+ */
211
+ jumpToItem?: number;
212
+
213
+ /**
214
+ * When set to a valid item index, causes the group to smoothly
215
+ * animate so that the specified index moves to the target region
216
+ * specified by the TargetSet's `focusIndex`.
217
+ *
218
+ * **Important:** Set this property after `content` in the component order. When
219
+ * content is updated dynamically, update `animateToItem` in the same transaction
220
+ * or after content has been updated.
221
+ *
222
+ * @example
223
+ * ```tsx
224
+ * <TargetGroup
225
+ * content={contentNode}
226
+ * animateToItem={animateToItem}
227
+ * ...
228
+ * />
229
+ * ```
230
+ *
231
+ * @writeonly
232
+ * @default 0
233
+ */
234
+ animateToItem?: number;
235
+
236
+ /**
237
+ * When set to a valid TargetSet, causes the group to smoothly
238
+ * animate all items from the current target regions to the
239
+ * corresponding regions of the new TargetSet. If the two
240
+ * TargetSets have different `focusIndex` values, the focus
241
+ * index is also animated.
242
+ *
243
+ * @writeonly
244
+ */
245
+ animateToTargetSet?: JSX.Element;
246
+
247
+ // ─────────────────────────────────────────────────────────────────────────
248
+ // Events (Read-Only)
249
+ // ─────────────────────────────────────────────────────────────────────────
250
+
251
+ /**
252
+ * Fired when the current focus item index changes during animation.
253
+ * The value is a `float` — integer when the item occupies the focus
254
+ * target, fractional during animation (e.g., `5.7` means item 6
255
+ * occupies 70% and item 5 occupies 30%).
256
+ *
257
+ * @readonly
258
+ */
259
+ onCurrFocusItemIndexChange?: EventHandler<number>;
260
+
261
+ /**
262
+ * Fired when the current target set changes during an
263
+ * `animateToTargetSet` animation. Contains the interpolated
264
+ * TargetSet as the animation proceeds.
265
+ *
266
+ * @readonly
267
+ */
268
+ onCurrTargetSetChange?: EventHandler<JSX.Element>;
269
+
270
+ /** Fired when a group item is selected (e.g., OK button press). */
271
+ onItemSelected?: EventHandler<number>;
272
+
273
+ /** Fired when a group item gains the key focus. */
274
+ onItemFocused?: EventHandler<number>;
275
+
276
+ /** Fired when a group item loses the key focus. */
277
+ onItemUnfocused?: EventHandler<number>;
278
+ }
@@ -0,0 +1,63 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { ITargetGroupProps } from "./target-group.props";
5
+ import { Component } from "./types";
6
+ export type { ITargetGroupProps } from "./target-group.props";
7
+
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ // TargetGroup Properties
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+
12
+ /**
13
+ * TargetGroup creates scrolling lists (or rows) of items where each item's
14
+ * size and position is defined by rectangular target regions in a
15
+ * {@link TargetSet}. The focused item typically occupies a larger region
16
+ * than unfocused items, with smooth animations between states.
17
+ *
18
+ * **Common use cases:**
19
+ * - A full-screen vertical list where the focused item is larger
20
+ * - A list that collapses when unfocused and expands when focused,
21
+ * using two TargetSets animated via `animateToTargetSet`
22
+ * - A horizontal row with a floating focus region
23
+ * - A circular arrangement with fixed positions
24
+ *
25
+ * @see {@link https://developer.roku.com/dev/docs/targetgroup}
26
+ * @see {@link https://github.com/rokudev/samples/blob/master/ux%20components/screen%20elements/target_group/FloatingFocusWrap | FloatingFocusWrap Sample}
27
+ * @see {@link https://github.com/rokudev/samples/blob/master/ux%20components/screen%20elements/target_group/TwoRowFixedFocus | TwoRowFixedFocus Sample}
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * import "./compiled-components/MyItem.xml";
32
+ *
33
+ * <targetGroup
34
+ * itemComponentName="MyItem"
35
+ * targetSet={
36
+ * <targetSet
37
+ * targetRects={[
38
+ * [0, -200, 1920, 200],
39
+ * [0, 20, 1920, 200],
40
+ * [0, 240, 1920, 400],
41
+ * [0, 660, 1920, 200],
42
+ * [0, 880, 1920, 200],
43
+ * ]}
44
+ * focusIndex={2}
45
+ * />
46
+ * }
47
+ * content={
48
+ * <contentNode>
49
+ * <contentNode title="Item 1" description="First item" />
50
+ * <contentNode title="Item 2" description="Second item" />
51
+ * <contentNode title="Item 3" description="Third item" />
52
+ * </contentNode>
53
+ * }
54
+ * duration={0.4}
55
+ * wrap={true}
56
+ * onItemFocused={(index) => console.log("Focused:", index)}
57
+ * onItemSelected={(index) => console.log("Selected:", index)}
58
+ * />
59
+ * ```
60
+ */
61
+ export const TargetGroup: Component<ITargetGroupProps> = props => {
62
+ return <targetGroup {...props}></targetGroup>;
63
+ };
@@ -0,0 +1,128 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { ITargetGroupProps } from "./target-group.props";
5
+ import type { JSX } from "./types";
6
+
7
+ /**
8
+ * Properties for the TargetList node.
9
+ *
10
+ * TargetList extends {@link ITargetGroupProps | TargetGroup} with built-in
11
+ * focused/unfocused transitions, focus management policies (fixed focus,
12
+ * floating focus), and default key handling for navigating the list or row.
13
+ *
14
+ * **Fixed focus:** Set `focusedTargetSet` to a single-element array containing
15
+ * one TargetSet. The focus stays fixed on the target rectangle at the
16
+ * TargetSet's `focusIndex`. Optionally set `unfocusedTargetSet` for a smooth
17
+ * transition when the list gains or loses focus.
18
+ *
19
+ * **Floating focus:** Set `focusedTargetSet` to an array of TargetSets — one
20
+ * per focus position — with incrementing `focusIndex` values. As the user
21
+ * navigates, the list advances through the array until the last element is
22
+ * reached, at which point items begin to scroll.
23
+ *
24
+ * @extends ITargetGroupProps
25
+ * @see {@link https://developer.roku.com/dev/docs/targetlist}
26
+ *
27
+ * @example Fixed focus
28
+ * ```tsx
29
+ * import { TargetList, TargetSet } from "@roku-sdk/components";
30
+ * import "./compiled-components/MyItem.xml";
31
+ *
32
+ * <TargetList
33
+ * itemComponentName="MyItem"
34
+ * focusedTargetSet={[
35
+ * <TargetSet
36
+ * targetRects={[
37
+ * [-178, -64, 240, 134],
38
+ * [72, -96, 360, 202],
39
+ * [442, -64, 240, 134],
40
+ * [692, -64, 240, 134],
41
+ * ]}
42
+ * focusIndex={1}
43
+ * />,
44
+ * ]}
45
+ * unfocusedTargetSet={
46
+ * <TargetSet
47
+ * targetRects={[
48
+ * [-100, -64, 240, 134],
49
+ * [150, -64, 240, 134],
50
+ * [400, -64, 240, 134],
51
+ * [650, -64, 240, 134],
52
+ * ]}
53
+ * />
54
+ * }
55
+ * content={contentNode}
56
+ * />
57
+ * ```
58
+ *
59
+ * @example Floating focus
60
+ * ```tsx
61
+ * <TargetList
62
+ * itemComponentName="MyItem"
63
+ * advanceKey="right"
64
+ * reverseKey="left"
65
+ * focusedTargetSet={[
66
+ * <TargetSet targetRects={rectsForFocus0} focusIndex={0} />,
67
+ * <TargetSet targetRects={rectsForFocus1} focusIndex={1} />,
68
+ * <TargetSet targetRects={rectsForFocus2} focusIndex={2} />,
69
+ * ]}
70
+ * content={contentNode}
71
+ * />
72
+ * ```
73
+ */
74
+ export interface ITargetListProps extends ITargetGroupProps {
75
+ // ─────────────────────────────────────────────────────────────────────────
76
+ // Focus Configuration
77
+ // ─────────────────────────────────────────────────────────────────────────
78
+
79
+ /**
80
+ * Specifies one or more TargetSets to be used when the TargetList has
81
+ * the focus.
82
+ *
83
+ * - **Fixed focus**: provide a single-element array. Focus stays fixed on
84
+ * the target rectangle corresponding to the TargetSet's `focusIndex`
85
+ * (or the TargetGroup's `defaultTargetSetFocusIndex` if the TargetSet
86
+ * does not specify one).
87
+ * - **Floating focus**: provide multiple TargetSets with incrementing
88
+ * `focusIndex` values. As the user presses `advanceKey`, focus floats
89
+ * from one TargetSet to the next until the last element is reached, at
90
+ * which point items begin to scroll. Reversing works symmetrically.
91
+ *
92
+ * @default []
93
+ * @see {@link https://developer.roku.com/dev/docs/targetlist | TargetList Fields}
94
+ */
95
+ focusedTargetSet?: JSX.Element[];
96
+
97
+ /**
98
+ * Specifies the TargetSet to be used when the TargetList does **not**
99
+ * have the focus. Setting this enables a smooth transition between the
100
+ * focused and unfocused layouts when the list gains or loses focus.
101
+ *
102
+ * @default invalid
103
+ * @see {@link https://developer.roku.com/dev/docs/targetlist | TargetList Fields}
104
+ */
105
+ unfocusedTargetSet?: JSX.Element;
106
+
107
+ // ─────────────────────────────────────────────────────────────────────────
108
+ // Key Handling
109
+ // ─────────────────────────────────────────────────────────────────────────
110
+
111
+ /**
112
+ * Specifies which remote button will move the focus forward. For vertical
113
+ * lists, this is typically `"down"`; for horizontal rows, `"right"`.
114
+ *
115
+ * @default "down"
116
+ * @see {@link https://developer.roku.com/dev/docs/targetlist | TargetList Fields}
117
+ */
118
+ advanceKey?: string;
119
+
120
+ /**
121
+ * Specifies which remote button will move the focus backward. For vertical
122
+ * lists, this is typically `"up"`; for horizontal rows, `"left"`.
123
+ *
124
+ * @default "up"
125
+ * @see {@link https://developer.roku.com/dev/docs/targetlist | TargetList Fields}
126
+ */
127
+ reverseKey?: string;
128
+ }