@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,251 @@
1
+ ' Copyright (c) 2026 Roku, Inc.
2
+ ' SPDX-License-Identifier: MIT
3
+
4
+ sub init()
5
+ m.top.observeField("change", "onChange")
6
+ m.top.observeField("focusedChild", "onFocusedChild")
7
+ m.focusableNodes = []
8
+ m.lastFocusedNode = invalid
9
+ m.focusablesParentNode = invalid
10
+ m.firstChangeEvent = true
11
+ end sub
12
+
13
+ ' Handle node change events to manage focusable nodes
14
+ ' Init is invoked when node is created but children are not appended yet.
15
+ ' Children are added later, so we need to observe the change event
16
+ sub onChange(msg as Object)
17
+ data = msg.getData()
18
+ node = msg.getRoSGNode()
19
+
20
+ if data.Operation = "add"
21
+ handleAddOperation(msg, data)
22
+ else
23
+ handleNonAddOperation(node)
24
+ end if
25
+
26
+ ' print "Focusable Group event:"; data.Operation
27
+ ' print "Node: "; node.id
28
+ ' print "Focusable Nodes: "; m.focusableNodes.count()
29
+ setupParentNodeObserver()
30
+ m.firstChangeEvent = false
31
+ end sub
32
+
33
+ ' Handle when nodes are added to the group
34
+ sub handleAddOperation(msg as Object, data as Object)
35
+ child = msg.getRoSGNode().getChild(data.Index1)
36
+ if child <> invalid
37
+ addFocusableNode(child)
38
+ end if
39
+ end sub
40
+
41
+ ' Handle operations other than 'add' (rebuild focusable nodes list)
42
+ ' TODO: This can be expensive. Look at the operation field and optimize
43
+ sub handleNonAddOperation(node as Object)
44
+ m.focusableNodes = []
45
+ m.lastFocusedNode = invalid
46
+ if node <> invalid and node.getChildCount() > 0
47
+ for each child in node.getChildren(node.getChildCount(), 0)
48
+ addFocusableNode(child)
49
+ end for
50
+ end if
51
+ end sub
52
+
53
+ ' Setup observer for parent node if needed
54
+ sub setupParentNodeObserver()
55
+ if m.firstChangeEvent = true and m.focusablesParentNode <> invalid
56
+ m.focusablesParentNode.observeField("change", "onChange")
57
+ end if
58
+ end sub
59
+
60
+ ' Add a focusable node to the collection or search its children
61
+ sub addFocusableNode(node as Object)
62
+ if node = invalid
63
+ return
64
+ end if
65
+
66
+ if isFocusableNode(node)
67
+ addToFocusableCollection(node)
68
+ else
69
+ findFocusableNodes(node)
70
+ end if
71
+ end sub
72
+
73
+ ' Add node to focusable collection and track its parent
74
+ sub addToFocusableCollection(node as Object)
75
+ m.focusableNodes.push(node)
76
+
77
+ if node.getParent() <> invalid and node.getParent().subtype() <> "focusable-group" and m.firstChangeEvent = true
78
+ if m.focusablesParentNode <> invalid and m.focusablesParentNode.id <> node.getParent().id
79
+ print "Warning: Each Focusable node has a different parent. This may lead to unexpected behavior."
80
+ end if
81
+
82
+ ' Can each focusable be wrapped in different parent?
83
+ ' If so, may be app should create a focusable group for each focusable node
84
+ m.focusablesParentNode = node.getParent()
85
+ end if
86
+ end sub
87
+
88
+ ' Recursively search for focusable nodes in children
89
+ sub findFocusableNodes(node as Object)
90
+ if node = invalid or node.getChildCount() = 0
91
+ return
92
+ end if
93
+
94
+ for each child in node.getChildren(node.getChildCount(), 0)
95
+ addFocusableNode(child)
96
+ end for
97
+ end sub
98
+
99
+ function isFocusableNode(node as Object) as Boolean
100
+ return node.subtype() = "focusable"
101
+ end function
102
+
103
+ function onKeyEvent(key as String, press as Boolean) as Boolean
104
+ ' When key is pressed, get the focused child
105
+ ' look for the nextFocusDirection on the node
106
+ ' if present, focus that node
107
+ handled = false
108
+ if press
109
+ inPathFocusables = getFocusableNodes(true) ' Should be only one focusable node in focus chain
110
+ if inPathFocusables.count() = 0 then return handled ' No focusable nodes in focus chain yet — bail early
111
+ item = inPathFocusables[0]
112
+ if item = invalid then return handled ' Safety guard against invalid array element
113
+ nextNodeId = item["nextFocus" + key] ' case insensitive
114
+ ' print "Next NodeId: "; nextNodeId
115
+ if nextNodeId <> invalid and nextNodeId <> "" then
116
+ focusables = getFocusableNodes(false)
117
+ for i = 0 to focusables.count() - 1
118
+ searchResult = findClosestFocusableGroup(focusables[i], nextNodeId, invalid)
119
+ if searchResult <> invalid and searchResult.focusableGroup <> invalid and searchResult.focusableGroup.id = nextNodeId
120
+ handled = searchResult.focusableGroup.callfunc("pickFocus")
121
+ return handled
122
+ end if
123
+ if searchResult.nodeFound
124
+ if searchResult.focusableGroup <> invalid
125
+ handled = searchResult.focusableGroup.callfunc("assignFocus", nextNodeId, false)
126
+ else
127
+ ' Moving focus within same focusable group
128
+ handled = assignFocus(nextNodeId, true)
129
+ end if
130
+ ' print "Handled: "; handled
131
+ return handled
132
+ end if
133
+ end for
134
+ end if
135
+ end if
136
+ return handled
137
+ end function
138
+
139
+ function getFocusableNodes(isInFocusChain as Boolean) as Object
140
+ focusables = []
141
+ for i = 0 to m.focusableNodes.count() - 1
142
+ item = m.focusableNodes[i]
143
+ if item.isInFocusChain() = isInFocusChain
144
+ focusables.push(item)
145
+ end if
146
+ end for
147
+ return focusables
148
+ end function
149
+
150
+ ' DFS to find the focus path for a given nodeId and closest focusable group
151
+ function findClosestFocusableGroup(rootNode as Object, nodeId as String, parentFocusableGroup as Object) as Object
152
+ result = {
153
+ nodeFound: false,
154
+ focusableGroup: invalid
155
+ }
156
+
157
+ if rootNode <> invalid
158
+ ' Update current focusable group if this node is one
159
+ currentFocusableGroup = parentFocusableGroup
160
+ if rootNode.subtype() = "focusable-group"
161
+ currentFocusableGroup = rootNode
162
+ end if
163
+
164
+ ' Check if current node is the target
165
+ if rootNode.id = nodeId
166
+ result.nodeFound = true
167
+ result.focusableGroup = currentFocusableGroup
168
+ return result
169
+ end if
170
+
171
+ ' Search children
172
+ for i = 0 to rootNode.getChildCount() - 1
173
+ child = rootNode.getChild(i)
174
+ if child <> invalid
175
+ childResult = findClosestFocusableGroup(child, nodeId, currentFocusableGroup)
176
+ if childResult.nodeFound
177
+ return childResult
178
+ end if
179
+ end if
180
+ end for
181
+ end if
182
+
183
+ return result
184
+ end function
185
+
186
+ ' Assign focus to a node by its ID
187
+ function assignFocus(nodeId as String, withInSameFocusableGroup as Boolean) as Object
188
+ if m.lastFocusedNode <> invalid and m.top.rememberLastFocus and withInSameFocusableGroup = false then
189
+ ' print "Setting Focus to last focused node: "; m.lastFocusedNode.id
190
+ m.lastFocusedNode.setFocus(true)
191
+ end if
192
+
193
+ foundNode = m.top.findNode(nodeId)
194
+ if foundNode <> invalid then
195
+ ' print "Setting focus to: "; foundNode.id
196
+ foundNode.setFocus(true)
197
+ m.lastFocusedNode = foundNode
198
+ return true
199
+ end if
200
+ return false
201
+ end function
202
+
203
+ ' Pick the first focusable node in the focusable group
204
+ function pickFocus() as Object
205
+ if m.lastFocusedNode <> invalid and m.top.rememberLastFocus
206
+ ' print "Setting focus to last focused node: "; m.lastFocusedNode.id
207
+ m.lastFocusedNode.setFocus(true)
208
+ return true
209
+ end if
210
+
211
+ firstFocusable = invalid
212
+ if m.focusableNodes.count() > 0
213
+ for each focusable in m.focusableNodes
214
+ if focusable.hasPreferredFocus = true
215
+ ' print "Setting initial focus to preferred focusable: "; focusable.getChild(0).id
216
+ firstFocusable = focusable
217
+ exit for
218
+ end if
219
+ end for
220
+ if firstFocusable = invalid
221
+ firstFocusable = m.focusableNodes[0]
222
+ end if
223
+
224
+ if firstFocusable <> invalid
225
+ firstFocusableChild = firstFocusable.getChild(0)
226
+ end if
227
+
228
+ if firstFocusableChild <> invalid
229
+ if firstFocusableChild.subtype() = "focusable-group"
230
+ return firstFocusableChild.callfunc("pickFocus")
231
+ else
232
+ ' print "Setting focus to first child of first focusable group: "; firstFocusableChild.id
233
+ firstFocusableChild.setFocus(true)
234
+ ' print "Setting initial focus to: "; firstFocusableChild.id
235
+ m.lastFocusedNode = firstFocusableChild
236
+ return true
237
+ end if
238
+ end if
239
+ end if
240
+ return false
241
+ end function
242
+
243
+ sub onFocusedChild(msg as Object)
244
+ data = msg.getData()
245
+
246
+ if m.top.isSameNode(data) ' this is the case when focus is set to this group
247
+ pickFocus()
248
+ end if
249
+ end sub
250
+
251
+
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!--
3
+ Copyright (c) 2026 Roku, Inc.
4
+ SPDX-License-Identifier: MIT
5
+ -->
6
+
7
+
8
+ <component name="focusable-group" extends="Group">
9
+ <script type="text/brightscript" uri="focusable-group.brs" />
10
+ <interface>
11
+ <field id="rememberLastFocus" type="boolean" default="true" />
12
+ <function name="assignFocus" />
13
+ <function name="pickFocus" />
14
+ </interface>
15
+ </component>
@@ -0,0 +1,42 @@
1
+ // Auto-generated TypeScript declarations for XML component import.
2
+ // DO NOT EDIT — this file is regenerated during build.
3
+ //
4
+ // Source component: focusable-group
5
+ // Extends: Group
6
+
7
+ import type { IGroupProps } from "@roku-sdk/components";
8
+ import type { TypedXmlComponent } from "@roku-sdk/interfaces";
9
+
10
+ interface focusableGroupFields {
11
+ /** @default "true" */
12
+ rememberLastFocus: boolean;
13
+ }
14
+
15
+ export interface IfocusableGroupProps extends IGroupProps, Partial<focusableGroupFields> {}
16
+
17
+ declare const _default: TypedXmlComponent<"focusable-group", focusableGroupFields>;
18
+ export default _default;
19
+
20
+ declare module "@roku-sdk/solid" {
21
+ namespace JSX {
22
+ interface IntrinsicElements {
23
+ focusableGroup: IfocusableGroupProps;
24
+ }
25
+ }
26
+ }
27
+
28
+ declare module "@roku-sdk/solid/jsx-runtime" {
29
+ namespace JSX {
30
+ interface IntrinsicElements {
31
+ focusableGroup: IfocusableGroupProps;
32
+ }
33
+ }
34
+ }
35
+
36
+ declare module "solid-js" {
37
+ namespace JSX {
38
+ interface IntrinsicElements {
39
+ focusableGroup: IfocusableGroupProps;
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!--
3
+ Copyright (c) 2026 Roku, Inc.
4
+ SPDX-License-Identifier: MIT
5
+ -->
6
+
7
+
8
+ <component name="focusable" extends="Group">
9
+ <interface>
10
+ <field id="nextFocusUp" type="string" />
11
+ <field id="nextFocusDown" type="string" />
12
+ <field id="nextFocusLeft" type="string" />
13
+ <field id="nextFocusRight" type="string" />
14
+ <field id="nextFocusBack" type="string" />
15
+ <field id="nextFocusOk" type="string" />
16
+ <field id="hasPreferredFocus" type="boolean" default="false" />
17
+ </interface>
18
+ </component>
@@ -0,0 +1,48 @@
1
+ // Auto-generated TypeScript declarations for XML component import.
2
+ // DO NOT EDIT — this file is regenerated during build.
3
+ //
4
+ // Source component: focusable
5
+ // Extends: Group
6
+
7
+ import type { IGroupProps } from "@roku-sdk/components";
8
+ import type { TypedXmlComponent } from "@roku-sdk/interfaces";
9
+
10
+ interface focusableFields {
11
+ nextFocusUp: string;
12
+ nextFocusDown: string;
13
+ nextFocusLeft: string;
14
+ nextFocusRight: string;
15
+ nextFocusBack: string;
16
+ nextFocusOk: string;
17
+ /** @default "false" */
18
+ hasPreferredFocus: boolean;
19
+ }
20
+
21
+ export interface IfocusableProps extends IGroupProps, Partial<focusableFields> {}
22
+
23
+ declare const _default: TypedXmlComponent<"focusable", focusableFields>;
24
+ export default _default;
25
+
26
+ declare module "@roku-sdk/solid" {
27
+ namespace JSX {
28
+ interface IntrinsicElements {
29
+ focusable: IfocusableProps;
30
+ }
31
+ }
32
+ }
33
+
34
+ declare module "@roku-sdk/solid/jsx-runtime" {
35
+ namespace JSX {
36
+ interface IntrinsicElements {
37
+ focusable: IfocusableProps;
38
+ }
39
+ }
40
+ }
41
+
42
+ declare module "solid-js" {
43
+ namespace JSX {
44
+ interface IntrinsicElements {
45
+ focusable: IfocusableProps;
46
+ }
47
+ }
48
+ }