@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.
- package/BREAKING_CHANGES.md +84 -0
- package/LICENSE +21 -0
- package/README.md +19 -2
- package/core-jsx.d.ts +53 -0
- package/eslint.config.mjs +3 -0
- package/jsx.d.ts +273 -0
- package/package.json +84 -3
- package/src/__tests__/container.test.tsx +150 -0
- package/src/__tests__/glyphs.test.ts +202 -0
- package/src/__tests__/modal-boundary-export.test.ts +12 -0
- package/src/__tests__/native-modal.test.tsx +224 -0
- package/src/__tests__/tween-release.test.tsx +125 -0
- package/src/__tests__/tween-stops-shape.test.tsx +165 -0
- package/src/__tests__/tween-without-compat.test.tsx +64 -0
- package/src/__tests__/tween.test.tsx +195 -0
- package/src/blended-busy-spinner.props.ts +37 -0
- package/src/blended-busy-spinner.tsx +13 -0
- package/src/bounding-tracker.props.ts +18 -0
- package/src/bounding-tracker.tsx +13 -0
- package/src/button-row.tsx +126 -0
- package/src/channel-store.props.ts +425 -0
- package/src/channel-store.tsx +76 -0
- package/src/container.tsx +130 -0
- package/src/core/jsx-runtime.ts +39 -0
- package/src/focusable-group.props.ts +8 -0
- package/src/focusable-group.tsx +17 -0
- package/src/focusable.props.ts +14 -0
- package/src/focusable.tsx +17 -0
- package/src/glyphs.ts +116 -0
- package/src/helpers/index.tsx +4 -0
- package/src/helpers/resolution-context.tsx +68 -0
- package/src/helpers/scale-for-device-resolution.ts +31 -0
- package/src/index.tsx +70 -0
- package/src/jsx-props.ts +87 -0
- package/src/kdfs.d.ts +14 -0
- package/src/kdfs.ts +15 -0
- package/src/key-handler.props.ts +33 -0
- package/src/key-handler.tsx +24 -0
- package/src/modal-dialog.props.ts +38 -0
- package/src/modal-dialog.tsx +41 -0
- package/src/retain-asset.ts +24 -0
- package/src/sg-intrinsics.json +1153 -0
- package/src/sg-nodes/__tests__/__snapshots__/font.test.tsx.snap +13 -0
- package/src/sg-nodes/__tests__/__snapshots__/prop-forwarding.test.tsx.snap +4409 -0
- package/src/sg-nodes/__tests__/core-props.test.ts +301 -0
- package/src/sg-nodes/__tests__/font.test.tsx +141 -0
- package/src/sg-nodes/__tests__/imperative-methods.test.ts +268 -0
- package/src/sg-nodes/__tests__/prop-forwarding.test.tsx +1049 -0
- package/src/sg-nodes/__tests__/solid-integration.test.tsx +719 -0
- package/src/sg-nodes/animated-image.props.ts +64 -0
- package/src/sg-nodes/animated-image.tsx +23 -0
- package/src/sg-nodes/animation.props.ts +292 -0
- package/src/sg-nodes/animation.tsx +190 -0
- package/src/sg-nodes/array-grid.ts +602 -0
- package/src/sg-nodes/audio.props.ts +302 -0
- package/src/sg-nodes/audio.tsx +57 -0
- package/src/sg-nodes/busy-spinner.props.ts +59 -0
- package/src/sg-nodes/busy-spinner.tsx +84 -0
- package/src/sg-nodes/button-group.props.ts +141 -0
- package/src/sg-nodes/button-group.tsx +30 -0
- package/src/sg-nodes/button.props.ts +119 -0
- package/src/sg-nodes/button.tsx +31 -0
- package/src/sg-nodes/check-list.props.ts +71 -0
- package/src/sg-nodes/check-list.tsx +55 -0
- package/src/sg-nodes/content-node.props.ts +1316 -0
- package/src/sg-nodes/content-node.tsx +153 -0
- package/src/sg-nodes/core-props.ts +197 -0
- package/src/sg-nodes/custom-keyboard.props.ts +108 -0
- package/src/sg-nodes/custom-keyboard.tsx +48 -0
- package/src/sg-nodes/dialog.props.ts +159 -0
- package/src/sg-nodes/dialog.tsx +36 -0
- package/src/sg-nodes/dynamic-key-grid.props.ts +22 -0
- package/src/sg-nodes/dynamic-key-grid.tsx +16 -0
- package/src/sg-nodes/dynamic-keyboard.props.ts +30 -0
- package/src/sg-nodes/dynamic-keyboard.tsx +18 -0
- package/src/sg-nodes/dynamic-mini-keyboard.props.ts +22 -0
- package/src/sg-nodes/dynamic-mini-keyboard.tsx +18 -0
- package/src/sg-nodes/dynamic-pinpad.props.ts +57 -0
- package/src/sg-nodes/dynamic-pinpad.tsx +18 -0
- package/src/sg-nodes/effect.props.ts +116 -0
- package/src/sg-nodes/effect.tsx +39 -0
- package/src/sg-nodes/font.props.ts +45 -0
- package/src/sg-nodes/font.tsx +80 -0
- package/src/sg-nodes/group.props.ts +105 -0
- package/src/sg-nodes/group.tsx +30 -0
- package/src/sg-nodes/label-list.props.ts +86 -0
- package/src/sg-nodes/label-list.tsx +64 -0
- package/src/sg-nodes/label.props.ts +207 -0
- package/src/sg-nodes/label.tsx +56 -0
- package/src/sg-nodes/layout-group.props.ts +69 -0
- package/src/sg-nodes/layout-group.tsx +75 -0
- package/src/sg-nodes/list-panel.props.ts +104 -0
- package/src/sg-nodes/list-panel.tsx +49 -0
- package/src/sg-nodes/markup-grid.props.ts +78 -0
- package/src/sg-nodes/markup-grid.tsx +38 -0
- package/src/sg-nodes/markup-list.props.ts +68 -0
- package/src/sg-nodes/markup-list.tsx +38 -0
- package/src/sg-nodes/mask-group.props.ts +78 -0
- package/src/sg-nodes/mask-group.tsx +59 -0
- package/src/sg-nodes/media-playback-methods.ts +20 -0
- package/src/sg-nodes/node.ts +77 -0
- package/src/sg-nodes/overhang.props.ts +169 -0
- package/src/sg-nodes/overhang.tsx +29 -0
- package/src/sg-nodes/panel-set.props.ts +71 -0
- package/src/sg-nodes/panel-set.tsx +53 -0
- package/src/sg-nodes/panel.props.ts +176 -0
- package/src/sg-nodes/panel.tsx +56 -0
- package/src/sg-nodes/poster-grid.props.ts +277 -0
- package/src/sg-nodes/poster-grid.tsx +45 -0
- package/src/sg-nodes/poster.props.ts +125 -0
- package/src/sg-nodes/poster.tsx +44 -0
- package/src/sg-nodes/radio-button-list.props.ts +59 -0
- package/src/sg-nodes/radio-button-list.tsx +68 -0
- package/src/sg-nodes/rectangle.props.ts +29 -0
- package/src/sg-nodes/rectangle.tsx +27 -0
- package/src/sg-nodes/row-list.props.ts +288 -0
- package/src/sg-nodes/row-list.tsx +47 -0
- package/src/sg-nodes/scene.props.ts +65 -0
- package/src/sg-nodes/scene.tsx +15 -0
- package/src/sg-nodes/scrollable-text.props.ts +133 -0
- package/src/sg-nodes/scrollable-text.tsx +64 -0
- package/src/sg-nodes/scrolling-label.props.ts +106 -0
- package/src/sg-nodes/scrolling-label.tsx +42 -0
- package/src/sg-nodes/simple-label.props.ts +156 -0
- package/src/sg-nodes/simple-label.tsx +59 -0
- package/src/sg-nodes/sound-effect.props.ts +130 -0
- package/src/sg-nodes/sound-effect.tsx +65 -0
- package/src/sg-nodes/standard-dialog.props.ts +520 -0
- package/src/sg-nodes/standard-dialog.tsx +428 -0
- package/src/sg-nodes/standard-keyboard-dialog.props.ts +84 -0
- package/src/sg-nodes/standard-keyboard-dialog.tsx +59 -0
- package/src/sg-nodes/standard-message-dialog.props.ts +56 -0
- package/src/sg-nodes/standard-message-dialog.tsx +48 -0
- package/src/sg-nodes/standard-pinpad-dialog.props.ts +64 -0
- package/src/sg-nodes/standard-pinpad-dialog.tsx +46 -0
- package/src/sg-nodes/standard-progress-dialog.props.ts +30 -0
- package/src/sg-nodes/standard-progress-dialog.tsx +43 -0
- package/src/sg-nodes/target-group.props.ts +278 -0
- package/src/sg-nodes/target-group.tsx +63 -0
- package/src/sg-nodes/target-list.props.ts +128 -0
- package/src/sg-nodes/target-list.tsx +66 -0
- package/src/sg-nodes/target-set.props.ts +63 -0
- package/src/sg-nodes/target-set.tsx +42 -0
- package/src/sg-nodes/time-grid.props.ts +646 -0
- package/src/sg-nodes/time-grid.tsx +87 -0
- package/src/sg-nodes/tween.props.ts +115 -0
- package/src/sg-nodes/types.ts +150 -0
- package/src/sg-nodes/video.props.ts +573 -0
- package/src/sg-nodes/video.tsx +73 -0
- package/src/sg-nodes/voice-text-edit-box.props.ts +91 -0
- package/src/sg-nodes/voice-text-edit-box.tsx +15 -0
- package/src/sg-nodes/zoom-row-list.props.ts +416 -0
- package/src/sg-nodes/zoom-row-list.tsx +42 -0
- package/src/standard-dialog-next/__tests__/standard-dialog-next.test.tsx +186 -0
- package/src/standard-dialog-next/index.ts +5 -0
- package/src/standard-dialog-next/standard-dialog-next.props.ts +80 -0
- package/src/standard-dialog-next/standard-dialog-next.tsx +326 -0
- package/src/test-helpers/tween-test-setup.ts +78 -0
- package/src/tween.tsx +173 -0
- package/src/types.ts +8 -0
- package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.brs +13 -0
- package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.xml +10 -0
- package/src/xml-components/blended-busy-spinner/BlendedBusySpinner.xml.d.ts +42 -0
- package/src/xml-components/bounding-tracker/BoundingTracker.brs +26 -0
- package/src/xml-components/bounding-tracker/BoundingTracker.xml +14 -0
- package/src/xml-components/bounding-tracker/BoundingTracker.xml.d.ts +42 -0
- package/src/xml-components/channel-store/channelStoreExt.brs +159 -0
- package/src/xml-components/channel-store/channelStoreExt.xml +29 -0
- package/src/xml-components/channel-store/channelStoreExt.xml.d.ts +57 -0
- package/src/xml-components/focus/focusable-group.brs +251 -0
- package/src/xml-components/focus/focusable-group.xml +15 -0
- package/src/xml-components/focus/focusable-group.xml.d.ts +42 -0
- package/src/xml-components/focus/focusable.xml +18 -0
- package/src/xml-components/focus/focusable.xml.d.ts +48 -0
- package/src/xml-components/keyboards/CustomAddressKDF.keyboard.json +547 -0
- package/src/xml-components/keyboards/CustomKeyboard.brs +322 -0
- package/src/xml-components/keyboards/CustomKeyboard.xml +45 -0
- package/src/xml-components/keyboards/CustomKeyboard.xml.d.ts +73 -0
- package/src/xml-components/keyboards/KeyGridExt.xml +4 -0
- package/src/xml-components/keyboards/KeyGridExt.xml.d.ts +37 -0
- package/src/xml-components/keyboards/KeyboardExt.xml +4 -0
- package/src/xml-components/keyboards/KeyboardExt.xml.d.ts +37 -0
- package/src/xml-components/keyboards/MOPPinPadCustomKDF.keyboard.json +151 -0
- package/src/xml-components/keyboards/MiniKeyboardExt.xml +4 -0
- package/src/xml-components/keyboards/MiniKeyboardExt.xml.d.ts +37 -0
- package/src/xml-components/keyboards/PhonePadCustomKDF.keyboard.json +42 -0
- package/src/xml-components/keyboards/PinPadExt.brs +102 -0
- package/src/xml-components/keyboards/PinPadExt.xml +17 -0
- package/src/xml-components/keyboards/PinPadExt.xml.d.ts +49 -0
- package/src/xml-components/keyboards/WifiCustomKDF.keyboard.json +1242 -0
- package/src/xml-components/keyhandler/KeyHandler.brs +30 -0
- package/src/xml-components/keyhandler/KeyHandler.xml +16 -0
- package/src/xml-components/keyhandler/KeyHandler.xml.d.ts +45 -0
- package/src/xml-components/managed-list-panel/ManagedListPanel.brs +137 -0
- package/src/xml-components/managed-list-panel/ManagedListPanel.xml +10 -0
- package/src/xml-components/managed-list-panel/ManagedListPanel.xml.d.ts +37 -0
- package/src/xml-components/modal/Modal.brs +105 -0
- package/src/xml-components/modal/Modal.xml +22 -0
- package/src/xml-components/modal/Modal.xml.d.ts +54 -0
- package/src/xml-components/modal/StandardDialogExt.xml +10 -0
- package/src/xml-components/modal/StandardDialogExt.xml.d.ts +37 -0
- package/src/xml-components/modal/StandardKeyboardDialogExt.xml +10 -0
- package/src/xml-components/modal/StandardKeyboardDialogExt.xml.d.ts +37 -0
- package/src/xml-components/modal/StandardMessageDialogExt.xml +10 -0
- package/src/xml-components/modal/StandardMessageDialogExt.xml.d.ts +37 -0
- package/src/xml-components/modal/StandardPinPadDialogExt.xml +10 -0
- package/src/xml-components/modal/StandardPinPadDialogExt.xml.d.ts +37 -0
- package/src/xml-components/modal/StandardProgressDialogExt.xml +10 -0
- package/src/xml-components/modal/StandardProgressDialogExt.xml.d.ts +37 -0
- package/src/xml-components/modal/tests/Modal.test.brs +195 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
' Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
' SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Sub init()
|
|
5
|
+
print "init keyhandler"
|
|
6
|
+
m.top.observeField("keys", "updateKeyMap")
|
|
7
|
+
updateKeyMap()
|
|
8
|
+
end Sub
|
|
9
|
+
|
|
10
|
+
function onKeyEvent(key as String, press as Boolean) as Boolean
|
|
11
|
+
' print "Key Event: "; key; " Press: "; press
|
|
12
|
+
|
|
13
|
+
if m.top.handleAllKeys = true
|
|
14
|
+
m.top.SetField("keyEvent", {"key": key, "press": press})
|
|
15
|
+
return true
|
|
16
|
+
end if
|
|
17
|
+
|
|
18
|
+
if m.keyMap[key] <> true and m.top.bubbleUnhandledKeys = true
|
|
19
|
+
return false
|
|
20
|
+
end if
|
|
21
|
+
m.top.SetField("keyEvent", {"key": key, "press": press})
|
|
22
|
+
return true
|
|
23
|
+
end function
|
|
24
|
+
|
|
25
|
+
sub updateKeyMap()
|
|
26
|
+
m.keyMap = {}
|
|
27
|
+
for each key in m.top.keys
|
|
28
|
+
m.keyMap[key] = true
|
|
29
|
+
end for
|
|
30
|
+
end sub
|
|
@@ -0,0 +1,16 @@
|
|
|
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="key-handler" extends="Group" focusable="true">
|
|
9
|
+
<script type="text/brightscript" uri="KeyHandler.brs" />
|
|
10
|
+
<interface>
|
|
11
|
+
<field id="bubbleUnhandledKeys" type="boolean" value="true" />
|
|
12
|
+
<field id="keyEvent" type="assocarray" />
|
|
13
|
+
<field id="keys" type="stringarray" />
|
|
14
|
+
<field id="handleAllKeys" type="boolean" />
|
|
15
|
+
</interface>
|
|
16
|
+
</component>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Auto-generated TypeScript declarations for XML component import.
|
|
2
|
+
// DO NOT EDIT — this file is regenerated during build.
|
|
3
|
+
//
|
|
4
|
+
// Source component: key-handler
|
|
5
|
+
// Extends: Group
|
|
6
|
+
|
|
7
|
+
import type { IGroupProps } from "@roku-sdk/components";
|
|
8
|
+
import type { TypedXmlComponent } from "@roku-sdk/interfaces";
|
|
9
|
+
|
|
10
|
+
interface keyHandlerFields {
|
|
11
|
+
/** @default "true" */
|
|
12
|
+
bubbleUnhandledKeys: boolean;
|
|
13
|
+
keyEvent: Record<string, unknown>;
|
|
14
|
+
keys: string[];
|
|
15
|
+
handleAllKeys: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IkeyHandlerProps extends IGroupProps, Partial<keyHandlerFields> {}
|
|
19
|
+
|
|
20
|
+
declare const _default: TypedXmlComponent<"key-handler", keyHandlerFields>;
|
|
21
|
+
export default _default;
|
|
22
|
+
|
|
23
|
+
declare module "@roku-sdk/solid" {
|
|
24
|
+
namespace JSX {
|
|
25
|
+
interface IntrinsicElements {
|
|
26
|
+
keyHandler: IkeyHandlerProps;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module "@roku-sdk/solid/jsx-runtime" {
|
|
32
|
+
namespace JSX {
|
|
33
|
+
interface IntrinsicElements {
|
|
34
|
+
keyHandler: IkeyHandlerProps;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare module "solid-js" {
|
|
40
|
+
namespace JSX {
|
|
41
|
+
interface IntrinsicElements {
|
|
42
|
+
keyHandler: IkeyHandlerProps;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
' Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
' SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
sub Init()
|
|
5
|
+
' The row the attached nextPanel was built for, and a select still waiting for its panel.
|
|
6
|
+
m.attachedIndex = -1
|
|
7
|
+
m.pendingSelectIndex = -1
|
|
8
|
+
m.forwardSuppressed = false
|
|
9
|
+
' Read rather than assume the default, so this does not have to be kept in step with it.
|
|
10
|
+
m.savedSelectForward = m.top.selectButtonMovesPanelForward
|
|
11
|
+
m.observedList = invalid
|
|
12
|
+
m.top.observeField("change", "onChildrenChange")
|
|
13
|
+
handleChildren()
|
|
14
|
+
end sub
|
|
15
|
+
|
|
16
|
+
' The app supplies nextPanel asynchronously, a render-thread hop after the list focus
|
|
17
|
+
' moves, so in between it still holds the previous row's panel. Stop the built-in
|
|
18
|
+
' forward move for as long as that is true. Clearing nextPanel instead would be
|
|
19
|
+
' destructive: a ListPanel does not re-host a next panel once the field has been set
|
|
20
|
+
' invalid, leaving the slot permanently empty.
|
|
21
|
+
sub suppressForward()
|
|
22
|
+
if m.forwardSuppressed then return
|
|
23
|
+
m.savedSelectForward = m.top.selectButtonMovesPanelForward
|
|
24
|
+
m.forwardSuppressed = true
|
|
25
|
+
m.top.selectButtonMovesPanelForward = false
|
|
26
|
+
end sub
|
|
27
|
+
|
|
28
|
+
sub restoreForward()
|
|
29
|
+
if not m.forwardSuppressed then return
|
|
30
|
+
m.forwardSuppressed = false
|
|
31
|
+
m.top.selectButtonMovesPanelForward = m.savedSelectForward
|
|
32
|
+
end sub
|
|
33
|
+
|
|
34
|
+
sub onListItemFocused()
|
|
35
|
+
if m.observedList = invalid then return
|
|
36
|
+
focused = m.observedList.itemFocused
|
|
37
|
+
' Navigating away abandons a select that was still waiting for its panel.
|
|
38
|
+
if m.pendingSelectIndex >= 0 and focused <> m.pendingSelectIndex
|
|
39
|
+
m.pendingSelectIndex = -1
|
|
40
|
+
end if
|
|
41
|
+
if m.attachedIndex >= 0 and focused <> m.attachedIndex
|
|
42
|
+
if m.top.createNextPanelOnItemFocus
|
|
43
|
+
m.attachedIndex = -1
|
|
44
|
+
' Focus-driven panels arrive per row; suppress until the matching child lands.
|
|
45
|
+
suppressForward()
|
|
46
|
+
else
|
|
47
|
+
' Static next panel: one panel serves every row. Keep attachedIndex in sync so
|
|
48
|
+
' Select is not blocked waiting for a per-row panel that never arrives.
|
|
49
|
+
m.attachedIndex = focused
|
|
50
|
+
end if
|
|
51
|
+
end if
|
|
52
|
+
end sub
|
|
53
|
+
|
|
54
|
+
' Select on a row whose panel has not landed yet. The forward move is suppressed, so
|
|
55
|
+
' remember the press rather than lose it and replay it once the panel arrives.
|
|
56
|
+
sub onListItemSelected()
|
|
57
|
+
if m.observedList = invalid then return
|
|
58
|
+
' If the app-level setting already blocks forward, there is nothing to replay.
|
|
59
|
+
if not m.savedSelectForward then return
|
|
60
|
+
selected = m.observedList.itemSelected
|
|
61
|
+
if m.attachedIndex <> selected
|
|
62
|
+
' Record the press only when we are in the suppression window (stale-panel swap in
|
|
63
|
+
' progress) or when no panel exists yet (first panel still on its way). In both cases
|
|
64
|
+
' the native Select cannot do the right thing; flushPendingSelect will replay it once
|
|
65
|
+
' the correct panel arrives. For static panels with a valid nextPanel and no active
|
|
66
|
+
' suppression, skip recording and let the native behaviour handle it.
|
|
67
|
+
if m.forwardSuppressed or m.top.nextPanel = invalid
|
|
68
|
+
m.pendingSelectIndex = selected
|
|
69
|
+
end if
|
|
70
|
+
end if
|
|
71
|
+
end sub
|
|
72
|
+
|
|
73
|
+
' Replay a remembered select. The panel cannot be focused where it sits: nextPanel holds
|
|
74
|
+
' it outside the tree, so setFocus alone is a no-op. Appending it to the PanelSet is what
|
|
75
|
+
' performs the forward slide, the same move the suppressed Select would have made.
|
|
76
|
+
sub flushPendingSelect()
|
|
77
|
+
if m.pendingSelectIndex < 0 or m.pendingSelectIndex <> m.attachedIndex then return
|
|
78
|
+
' Honour the app-level setting: if selectButtonMovesPanelForward was false before
|
|
79
|
+
' suppression began, replaying the select must not slide the panel forward either.
|
|
80
|
+
if not m.savedSelectForward then return
|
|
81
|
+
np = m.top.nextPanel
|
|
82
|
+
if np = invalid then return
|
|
83
|
+
panelSet = m.top.getParent()
|
|
84
|
+
if panelSet = invalid or not panelSet.isSubtype("PanelSet") then return
|
|
85
|
+
m.pendingSelectIndex = -1
|
|
86
|
+
panelSet.appendChild(np)
|
|
87
|
+
np.setFocus(true)
|
|
88
|
+
end sub
|
|
89
|
+
|
|
90
|
+
' Handle changes to the children of the managed list panel
|
|
91
|
+
sub onChildrenChange(msg as Object)
|
|
92
|
+
changeDta = msg.getData()
|
|
93
|
+
operation = changeDta.operation
|
|
94
|
+
handleChildren()
|
|
95
|
+
end sub
|
|
96
|
+
|
|
97
|
+
' Depends on the list child being declared before the panel child: only the ArrayGrid branch
|
|
98
|
+
' sets m.observedList, and the Panel branch needs it to learn which row the panel belongs to.
|
|
99
|
+
' A consumer that puts the panel first would leave m.attachedIndex unset on the first pass, so
|
|
100
|
+
' the stale-panel guard below would never arm. ListPanel documents the required child order.
|
|
101
|
+
sub handleChildren()
|
|
102
|
+
for c = 0 to m.top.getChildCount() - 1
|
|
103
|
+
child = m.top.getChild(c)
|
|
104
|
+
if child <> invalid
|
|
105
|
+
hasBeenAssigned = false
|
|
106
|
+
subtype = child.subtype()
|
|
107
|
+
if child.isSubtype("Panel")
|
|
108
|
+
m.top.nextPanel = child
|
|
109
|
+
m.top.removeChild(child)
|
|
110
|
+
if m.observedList <> invalid
|
|
111
|
+
' A list that has not resolved a focused item yet reports -1, and
|
|
112
|
+
' the first panel arrives in exactly that window. Storing -1 leaves
|
|
113
|
+
' onListItemFocused unable to arm on the very first row change (it
|
|
114
|
+
' skips while attachedIndex is negative), so the first Select after
|
|
115
|
+
' the first Down is neither suppressed nor replayed — it is lost.
|
|
116
|
+
' The panel that just arrived is row 0's: index 0 is what the app
|
|
117
|
+
' was asked to build before the list reported any focus.
|
|
118
|
+
attachedRow = m.observedList.itemFocused
|
|
119
|
+
if attachedRow < 0 then attachedRow = 0
|
|
120
|
+
m.attachedIndex = attachedRow
|
|
121
|
+
restoreForward()
|
|
122
|
+
flushPendingSelect()
|
|
123
|
+
end if
|
|
124
|
+
hasBeenAssigned = true
|
|
125
|
+
else if child.isSubtype("ArrayGrid")
|
|
126
|
+
if not child.isSameNode(m.top.list)
|
|
127
|
+
m.top.list = child
|
|
128
|
+
m.observedList = child
|
|
129
|
+
' Scoped so the callbacks resolve in this component, not the list's.
|
|
130
|
+
child.observeFieldScoped("itemFocused", "onListItemFocused")
|
|
131
|
+
child.observeFieldScoped("itemSelected", "onListItemSelected")
|
|
132
|
+
hasBeenAssigned = true
|
|
133
|
+
end if
|
|
134
|
+
end if
|
|
135
|
+
end if
|
|
136
|
+
end for
|
|
137
|
+
end sub
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Auto-generated TypeScript declarations for XML component import.
|
|
2
|
+
// DO NOT EDIT — this file is regenerated during build.
|
|
3
|
+
//
|
|
4
|
+
// Source component: managedListPanel
|
|
5
|
+
// Extends: ListPanel
|
|
6
|
+
|
|
7
|
+
import type { IListPanelProps } from "@roku-sdk/components";
|
|
8
|
+
import type { TypedXmlComponent } from "@roku-sdk/interfaces";
|
|
9
|
+
|
|
10
|
+
export interface ImanagedListPanelProps extends IListPanelProps {}
|
|
11
|
+
|
|
12
|
+
declare const _default: TypedXmlComponent<"managedListPanel", Record<string, never>>;
|
|
13
|
+
export default _default;
|
|
14
|
+
|
|
15
|
+
declare module "@roku-sdk/solid" {
|
|
16
|
+
namespace JSX {
|
|
17
|
+
interface IntrinsicElements {
|
|
18
|
+
managedListPanel: ImanagedListPanelProps;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module "@roku-sdk/solid/jsx-runtime" {
|
|
24
|
+
namespace JSX {
|
|
25
|
+
interface IntrinsicElements {
|
|
26
|
+
managedListPanel: ImanagedListPanelProps;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module "solid-js" {
|
|
32
|
+
namespace JSX {
|
|
33
|
+
interface IntrinsicElements {
|
|
34
|
+
managedListPanel: ImanagedListPanelProps;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
' Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
' SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
sub init()
|
|
5
|
+
m.dialog = invalid
|
|
6
|
+
m.dismissed = false
|
|
7
|
+
m.presented = false
|
|
8
|
+
m.prepared = []
|
|
9
|
+
end sub
|
|
10
|
+
|
|
11
|
+
sub onDialogChange()
|
|
12
|
+
m.dismissed = false
|
|
13
|
+
updateDialog()
|
|
14
|
+
end sub
|
|
15
|
+
|
|
16
|
+
sub onOpenChange()
|
|
17
|
+
if m.top.open then m.dismissed = false
|
|
18
|
+
updateDialog()
|
|
19
|
+
end sub
|
|
20
|
+
|
|
21
|
+
function sameDialog(left as Dynamic, right as Dynamic) as Boolean
|
|
22
|
+
if left = invalid or right = invalid then return false
|
|
23
|
+
return left.isSameNode(right)
|
|
24
|
+
end function
|
|
25
|
+
|
|
26
|
+
sub updateDialog()
|
|
27
|
+
if not m.top.mounted then
|
|
28
|
+
closeSession(true)
|
|
29
|
+
m.prepared = []
|
|
30
|
+
return
|
|
31
|
+
end if
|
|
32
|
+
scene = m.top.getScene()
|
|
33
|
+
if scene = invalid then return
|
|
34
|
+
prepareDialogs(scene)
|
|
35
|
+
if not m.top.open or m.top.dialog = invalid then
|
|
36
|
+
m.dismissed = true
|
|
37
|
+
if sameDialog(scene.dialog, m.dialog) then
|
|
38
|
+
m.dialog.close = true
|
|
39
|
+
else
|
|
40
|
+
closeSession(false)
|
|
41
|
+
end if
|
|
42
|
+
return
|
|
43
|
+
end if
|
|
44
|
+
dialog = m.top.dialog
|
|
45
|
+
if not m.top.ready then dialog = m.top.fallback
|
|
46
|
+
if dialog = invalid then
|
|
47
|
+
releaseDialog(true)
|
|
48
|
+
return
|
|
49
|
+
end if
|
|
50
|
+
if m.dismissed or sameDialog(m.dialog, dialog) then return
|
|
51
|
+
if scene.dialog <> invalid and not sameDialog(scene.dialog, m.dialog) and not m.top.replaceExisting then
|
|
52
|
+
m.dismissed = true
|
|
53
|
+
closeSession(false)
|
|
54
|
+
m.top.blocked = true
|
|
55
|
+
return
|
|
56
|
+
end if
|
|
57
|
+
releaseDialog(false)
|
|
58
|
+
m.dialog = dialog
|
|
59
|
+
dialog.observeFieldScopedEx("wasClosed", "onNativeClosed")
|
|
60
|
+
scene.dialog = dialog
|
|
61
|
+
m.presented = true
|
|
62
|
+
if m.top.ready and m.top.initialFocusId <> "" then
|
|
63
|
+
target = dialog.findNode(m.top.initialFocusId)
|
|
64
|
+
if target <> invalid then target.setFocus(true)
|
|
65
|
+
end if
|
|
66
|
+
if m.top.ready then m.top.opened = true
|
|
67
|
+
end sub
|
|
68
|
+
|
|
69
|
+
sub prepareDialogs(scene as Object)
|
|
70
|
+
for each old in m.prepared
|
|
71
|
+
if old <> invalid and not sameDialog(old, m.top.dialog) and not sameDialog(old, m.top.fallback) then
|
|
72
|
+
if sameDialog(old.getParent(), m.top) then m.top.removeChild(old)
|
|
73
|
+
end if
|
|
74
|
+
end for
|
|
75
|
+
m.prepared = [m.top.dialog, m.top.fallback]
|
|
76
|
+
for each dialog in m.prepared
|
|
77
|
+
if dialog <> invalid and not sameDialog(scene.dialog, dialog) then
|
|
78
|
+
if not sameDialog(dialog.getParent(), m.top) then m.top.appendChild(dialog)
|
|
79
|
+
end if
|
|
80
|
+
end for
|
|
81
|
+
end sub
|
|
82
|
+
|
|
83
|
+
sub onNativeClosed(event as Object)
|
|
84
|
+
if not sameDialog(m.dialog, event.getRoSGNode()) then return
|
|
85
|
+
m.dismissed = true
|
|
86
|
+
closeSession(false)
|
|
87
|
+
end sub
|
|
88
|
+
|
|
89
|
+
sub closeSession(closeNative as Boolean)
|
|
90
|
+
releaseDialog(closeNative)
|
|
91
|
+
if not m.presented then return
|
|
92
|
+
m.presented = false
|
|
93
|
+
m.top.wasClosed = true
|
|
94
|
+
end sub
|
|
95
|
+
|
|
96
|
+
sub releaseDialog(closeNative as Boolean)
|
|
97
|
+
dialog = m.dialog
|
|
98
|
+
if dialog = invalid then return
|
|
99
|
+
m.dialog = invalid
|
|
100
|
+
dialog.unobserveFieldScoped("wasClosed")
|
|
101
|
+
scene = dialog.getScene()
|
|
102
|
+
if closeNative and scene <> invalid then
|
|
103
|
+
if sameDialog(scene.dialog, dialog) then dialog.close = true
|
|
104
|
+
end if
|
|
105
|
+
end sub
|
|
@@ -0,0 +1,22 @@
|
|
|
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="modal" extends="Group">
|
|
9
|
+
<script type="text/brightscript" uri="Modal.brs" />
|
|
10
|
+
<interface>
|
|
11
|
+
<field id="dialog" type="node" onChange="onDialogChange" />
|
|
12
|
+
<field id="initialFocusId" type="string" />
|
|
13
|
+
<field id="open" type="boolean" value="true" onChange="onOpenChange" />
|
|
14
|
+
<field id="fallback" type="node" onChange="updateDialog" />
|
|
15
|
+
<field id="replaceExisting" type="boolean" value="true" />
|
|
16
|
+
<field id="ready" type="boolean" value="true" onChange="updateDialog" />
|
|
17
|
+
<field id="mounted" type="boolean" value="false" onChange="updateDialog" />
|
|
18
|
+
<field id="opened" type="boolean" alwaysNotify="true" />
|
|
19
|
+
<field id="wasClosed" type="boolean" alwaysNotify="true" />
|
|
20
|
+
<field id="blocked" type="boolean" alwaysNotify="true" />
|
|
21
|
+
</interface>
|
|
22
|
+
</component>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Auto-generated TypeScript declarations for XML component import.
|
|
2
|
+
// DO NOT EDIT — this file is regenerated during build.
|
|
3
|
+
//
|
|
4
|
+
// Source component: modal
|
|
5
|
+
// Extends: Group
|
|
6
|
+
|
|
7
|
+
import type { IGroupProps } from "@roku-sdk/components";
|
|
8
|
+
import type { TypedXmlComponent } from "@roku-sdk/interfaces";
|
|
9
|
+
|
|
10
|
+
interface modalFields {
|
|
11
|
+
dialog: import("@roku-sdk/interfaces").DomNode;
|
|
12
|
+
initialFocusId: string;
|
|
13
|
+
/** @default "true" */
|
|
14
|
+
open: boolean;
|
|
15
|
+
fallback: import("@roku-sdk/interfaces").DomNode;
|
|
16
|
+
/** @default "true" */
|
|
17
|
+
replaceExisting: boolean;
|
|
18
|
+
/** @default "true" */
|
|
19
|
+
ready: boolean;
|
|
20
|
+
/** @default "false" */
|
|
21
|
+
mounted: boolean;
|
|
22
|
+
opened: boolean;
|
|
23
|
+
wasClosed: boolean;
|
|
24
|
+
blocked: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ImodalProps extends IGroupProps, Partial<modalFields> {}
|
|
28
|
+
|
|
29
|
+
declare const _default: TypedXmlComponent<"modal", modalFields>;
|
|
30
|
+
export default _default;
|
|
31
|
+
|
|
32
|
+
declare module "@roku-sdk/solid" {
|
|
33
|
+
namespace JSX {
|
|
34
|
+
interface IntrinsicElements {
|
|
35
|
+
modal: ImodalProps;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare module "@roku-sdk/solid/jsx-runtime" {
|
|
41
|
+
namespace JSX {
|
|
42
|
+
interface IntrinsicElements {
|
|
43
|
+
modal: ImodalProps;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare module "solid-js" {
|
|
49
|
+
namespace JSX {
|
|
50
|
+
interface IntrinsicElements {
|
|
51
|
+
modal: ImodalProps;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Auto-generated TypeScript declarations for XML component import.
|
|
2
|
+
// DO NOT EDIT — this file is regenerated during build.
|
|
3
|
+
//
|
|
4
|
+
// Source component: StandardDialogExt
|
|
5
|
+
// Extends: StandardDialog
|
|
6
|
+
|
|
7
|
+
import type { IStandardDialogProps } from "@roku-sdk/components";
|
|
8
|
+
import type { TypedXmlComponent } from "@roku-sdk/interfaces";
|
|
9
|
+
|
|
10
|
+
export interface IStandardDialogExtProps extends IStandardDialogProps {}
|
|
11
|
+
|
|
12
|
+
declare const _default: TypedXmlComponent<"StandardDialogExt", Record<string, never>>;
|
|
13
|
+
export default _default;
|
|
14
|
+
|
|
15
|
+
declare module "@roku-sdk/solid" {
|
|
16
|
+
namespace JSX {
|
|
17
|
+
interface IntrinsicElements {
|
|
18
|
+
standardDialogExt: IStandardDialogExtProps;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module "@roku-sdk/solid/jsx-runtime" {
|
|
24
|
+
namespace JSX {
|
|
25
|
+
interface IntrinsicElements {
|
|
26
|
+
standardDialogExt: IStandardDialogExtProps;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module "solid-js" {
|
|
32
|
+
namespace JSX {
|
|
33
|
+
interface IntrinsicElements {
|
|
34
|
+
standardDialogExt: IStandardDialogExtProps;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Auto-generated TypeScript declarations for XML component import.
|
|
2
|
+
// DO NOT EDIT — this file is regenerated during build.
|
|
3
|
+
//
|
|
4
|
+
// Source component: StandardKeyboardDialogExt
|
|
5
|
+
// Extends: StandardKeyboardDialog
|
|
6
|
+
|
|
7
|
+
import type { IStandardKeyboardDialogProps } from "@roku-sdk/components";
|
|
8
|
+
import type { TypedXmlComponent } from "@roku-sdk/interfaces";
|
|
9
|
+
|
|
10
|
+
export interface IStandardKeyboardDialogExtProps extends IStandardKeyboardDialogProps {}
|
|
11
|
+
|
|
12
|
+
declare const _default: TypedXmlComponent<"StandardKeyboardDialogExt", Record<string, never>>;
|
|
13
|
+
export default _default;
|
|
14
|
+
|
|
15
|
+
declare module "@roku-sdk/solid" {
|
|
16
|
+
namespace JSX {
|
|
17
|
+
interface IntrinsicElements {
|
|
18
|
+
standardKeyboardDialogExt: IStandardKeyboardDialogExtProps;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module "@roku-sdk/solid/jsx-runtime" {
|
|
24
|
+
namespace JSX {
|
|
25
|
+
interface IntrinsicElements {
|
|
26
|
+
standardKeyboardDialogExt: IStandardKeyboardDialogExtProps;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module "solid-js" {
|
|
32
|
+
namespace JSX {
|
|
33
|
+
interface IntrinsicElements {
|
|
34
|
+
standardKeyboardDialogExt: IStandardKeyboardDialogExtProps;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Auto-generated TypeScript declarations for XML component import.
|
|
2
|
+
// DO NOT EDIT — this file is regenerated during build.
|
|
3
|
+
//
|
|
4
|
+
// Source component: StandardMessageDialogExt
|
|
5
|
+
// Extends: StandardMessageDialog
|
|
6
|
+
|
|
7
|
+
import type { IStandardMessageDialogProps } from "@roku-sdk/components";
|
|
8
|
+
import type { TypedXmlComponent } from "@roku-sdk/interfaces";
|
|
9
|
+
|
|
10
|
+
export interface IStandardMessageDialogExtProps extends IStandardMessageDialogProps {}
|
|
11
|
+
|
|
12
|
+
declare const _default: TypedXmlComponent<"StandardMessageDialogExt", Record<string, never>>;
|
|
13
|
+
export default _default;
|
|
14
|
+
|
|
15
|
+
declare module "@roku-sdk/solid" {
|
|
16
|
+
namespace JSX {
|
|
17
|
+
interface IntrinsicElements {
|
|
18
|
+
standardMessageDialogExt: IStandardMessageDialogExtProps;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module "@roku-sdk/solid/jsx-runtime" {
|
|
24
|
+
namespace JSX {
|
|
25
|
+
interface IntrinsicElements {
|
|
26
|
+
standardMessageDialogExt: IStandardMessageDialogExtProps;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module "solid-js" {
|
|
32
|
+
namespace JSX {
|
|
33
|
+
interface IntrinsicElements {
|
|
34
|
+
standardMessageDialogExt: IStandardMessageDialogExtProps;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Auto-generated TypeScript declarations for XML component import.
|
|
2
|
+
// DO NOT EDIT — this file is regenerated during build.
|
|
3
|
+
//
|
|
4
|
+
// Source component: StandardPinPadDialogExt
|
|
5
|
+
// Extends: StandardPinPadDialog
|
|
6
|
+
|
|
7
|
+
import type { IStandardPinPadDialogProps } from "@roku-sdk/components";
|
|
8
|
+
import type { TypedXmlComponent } from "@roku-sdk/interfaces";
|
|
9
|
+
|
|
10
|
+
export interface IStandardPinPadDialogExtProps extends IStandardPinPadDialogProps {}
|
|
11
|
+
|
|
12
|
+
declare const _default: TypedXmlComponent<"StandardPinPadDialogExt", Record<string, never>>;
|
|
13
|
+
export default _default;
|
|
14
|
+
|
|
15
|
+
declare module "@roku-sdk/solid" {
|
|
16
|
+
namespace JSX {
|
|
17
|
+
interface IntrinsicElements {
|
|
18
|
+
standardPinPadDialogExt: IStandardPinPadDialogExtProps;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module "@roku-sdk/solid/jsx-runtime" {
|
|
24
|
+
namespace JSX {
|
|
25
|
+
interface IntrinsicElements {
|
|
26
|
+
standardPinPadDialogExt: IStandardPinPadDialogExtProps;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module "solid-js" {
|
|
32
|
+
namespace JSX {
|
|
33
|
+
interface IntrinsicElements {
|
|
34
|
+
standardPinPadDialogExt: IStandardPinPadDialogExtProps;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|