@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.
- 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 +85 -3
- package/src/__tests__/container.test.tsx +150 -0
- package/src/__tests__/effect-gradient-validation.test.tsx +133 -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 +276 -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 +119 -0
- package/src/sg-nodes/effect.tsx +167 -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 +103 -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 +66 -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 +58 -0
- package/src/sg-nodes/standard-message-dialog.props.ts +56 -0
- package/src/sg-nodes/standard-message-dialog.tsx +47 -0
- package/src/sg-nodes/standard-pinpad-dialog.props.ts +64 -0
- package/src/sg-nodes/standard-pinpad-dialog.tsx +44 -0
- package/src/sg-nodes/standard-progress-dialog.props.ts +30 -0
- package/src/sg-nodes/standard-progress-dialog.tsx +40 -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 +642 -0
- package/src/sg-nodes/video.tsx +75 -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,17 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { IFocusableGroupProps } from "./focusable-group.props";
|
|
5
|
+
import { Component } from "./index";
|
|
6
|
+
import { retainAsset } from "./retain-asset";
|
|
7
|
+
import FocusableGroupXML from "./xml-components/focus/focusable-group.xml";
|
|
8
|
+
export type { IFocusableGroupProps } from "./focusable-group.props";
|
|
9
|
+
|
|
10
|
+
export const FocusableGroup: Component<IFocusableGroupProps> = props => {
|
|
11
|
+
retainAsset(FocusableGroupXML);
|
|
12
|
+
return (
|
|
13
|
+
<focusable-group focusable={true} {...props}>
|
|
14
|
+
{props.children}
|
|
15
|
+
</focusable-group>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { IGroupProps } from "./sg-nodes/group.props";
|
|
5
|
+
|
|
6
|
+
export interface IFocusableProps extends IGroupProps {
|
|
7
|
+
nextFocusRight?: string;
|
|
8
|
+
nextFocusLeft?: string;
|
|
9
|
+
nextFocusUp?: string;
|
|
10
|
+
nextFocusDown?: string;
|
|
11
|
+
nextFocusBack?: string;
|
|
12
|
+
nextFocusOk?: string;
|
|
13
|
+
hasPreferredFocus?: boolean;
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { IFocusableProps } from "./focusable.props";
|
|
5
|
+
import { retainAsset } from "./retain-asset";
|
|
6
|
+
import { Component } from "./types";
|
|
7
|
+
import FocusableXML from "./xml-components/focus/focusable.xml";
|
|
8
|
+
export type { IFocusableProps } from "./focusable.props";
|
|
9
|
+
|
|
10
|
+
export const Focusable: Component<IFocusableProps> = props => {
|
|
11
|
+
retainAsset(FocusableXML);
|
|
12
|
+
return (
|
|
13
|
+
<focusable focusable={true} {...props}>
|
|
14
|
+
{props.children}
|
|
15
|
+
</focusable>
|
|
16
|
+
);
|
|
17
|
+
};
|
package/src/glyphs.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Named character constants for symbols that are awkward or impossible to type
|
|
6
|
+
* directly in source.
|
|
7
|
+
*
|
|
8
|
+
* The constants are grouped by the property that actually matters at runtime:
|
|
9
|
+
* which font has to supply the character.
|
|
10
|
+
*
|
|
11
|
+
* 1. RokuFont01 characters (the private-use area, plus ● ★ ☆). RokuFont01 is
|
|
12
|
+
* the system-wide fallback font in firmware, so these render under a system
|
|
13
|
+
* font AND under a custom app-supplied font.
|
|
14
|
+
* 2. RokuUI-Regular symbols. RokuUI-Regular is the default UI font but is not
|
|
15
|
+
* in the fallback chain, so these render under a system font but become tofu
|
|
16
|
+
* under a custom app-supplied font unless that font supplies them itself.
|
|
17
|
+
*
|
|
18
|
+
* The practical consequence, verified on device against firmware 15.3.5: if
|
|
19
|
+
* your app ships a custom font, the group that needs care is (2), not (1).
|
|
20
|
+
*
|
|
21
|
+
* A custom font that defines a codepoint itself always wins — no fallback
|
|
22
|
+
* occurs. A font carrying its own U+E0xx private-use glyphs will render those
|
|
23
|
+
* rather than the Roku icons.
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// -----------------------------------------------------------------------------
|
|
29
|
+
// RokuFont01 — private-use area glyphs
|
|
30
|
+
//
|
|
31
|
+
// Supplied by the firmware fallback font, so these survive a custom app font.
|
|
32
|
+
// -----------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
export const OPTIONS_GLYPH = "\uE000";
|
|
35
|
+
export const OK_BUTTON_GLYPH = "\uE001";
|
|
36
|
+
export const OPTIONS_BUTTON_GLYPH = "\uE002";
|
|
37
|
+
export const MICROPHONE_GLYPH = "\uE003";
|
|
38
|
+
export const PLAY_PAUSE_BUTTON_GLYPH = "\uE004";
|
|
39
|
+
export const PLAY_PAUSE_GLYPH = "\uE005";
|
|
40
|
+
/**
|
|
41
|
+
* Baseline-aligned dot, sized and positioned to line up with the other icon
|
|
42
|
+
* glyphs in this block. Use {@link BLACK_CIRCLE} for a dot inside a run of
|
|
43
|
+
* text — that one is raised to text centre.
|
|
44
|
+
*/
|
|
45
|
+
export const DOT_GLYPH = "\uE006";
|
|
46
|
+
export const HOME_GLYPH = "\uE007";
|
|
47
|
+
export const SEARCH_GLYPH = "\uE008";
|
|
48
|
+
export const REMOTE_GLYPH = "\uE009";
|
|
49
|
+
export const REPLAY_GLYPH = "\uE010";
|
|
50
|
+
export const MICROPHONE_BUTTON_GLYPH = "\uE011";
|
|
51
|
+
export const SEARCH_BUTTON_GLYPH = "\uE012";
|
|
52
|
+
export const FAST_FORWARD_GLYPH = "\uE013";
|
|
53
|
+
export const REWIND_GLYPH = "\uE014";
|
|
54
|
+
export const SHORTCUT_ONE_BUTTON_GLYPH = "\uE015";
|
|
55
|
+
export const SHORTCUT_TWO_BUTTON_GLYPH = "\uE016";
|
|
56
|
+
export const CLOSED_CAPTIONS_GLYPH = "\uE017";
|
|
57
|
+
export const LIVE_TV_GLYPH = "\uE018";
|
|
58
|
+
export const SLEEP_TIMER_GLYPH = "\uE019";
|
|
59
|
+
export const GENERAL_APP_GLYPH = "\uE020";
|
|
60
|
+
export const BACK_BUTTON_GLYPH = "\uE021";
|
|
61
|
+
export const HOME_BUTTON_GLYPH = "\uE022";
|
|
62
|
+
export const QUICK_LAUNCH_BUTTON_GLYPH = "\uE023";
|
|
63
|
+
export const DOWN_BUTTON_GLYPH = "\uE024";
|
|
64
|
+
export const LEFT_BUTTON_GLYPH = "\uE025";
|
|
65
|
+
export const RIGHT_BUTTON_GLYPH = "\uE026";
|
|
66
|
+
export const KIDS_PROFILE_GLYPH = "\uE027";
|
|
67
|
+
export const UP_BUTTON_GLYPH = "\uE028";
|
|
68
|
+
export const POWER_BUTTON_GLYPH = "\uE029";
|
|
69
|
+
export const THUMBS_UP_GLYPH = "\uE030";
|
|
70
|
+
export const THUMBS_DOWN_GLYPH = "\uE031";
|
|
71
|
+
|
|
72
|
+
// -----------------------------------------------------------------------------
|
|
73
|
+
// RokuFont01 — standard Unicode symbols
|
|
74
|
+
//
|
|
75
|
+
// These are real Unicode codepoints, but RokuUI-Regular does not contain them:
|
|
76
|
+
// they resolve through the RokuFont01 fallback, exactly like the private-use
|
|
77
|
+
// glyphs above. That fallback applies under a custom app font too, so these are
|
|
78
|
+
// the safe shapes to use in custom-font text.
|
|
79
|
+
// -----------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
/** ● U+25CF BLACK CIRCLE — raised to text centre, for a dot inside a text run. */
|
|
82
|
+
export const BLACK_CIRCLE = "\u25CF";
|
|
83
|
+
/** ★ U+2605 BLACK STAR — filled star, e.g. a filled rating position. */
|
|
84
|
+
export const BLACK_STAR = "\u2605";
|
|
85
|
+
/** ☆ U+2606 WHITE STAR — outlined star, e.g. an empty rating position. */
|
|
86
|
+
export const WHITE_STAR = "\u2606";
|
|
87
|
+
|
|
88
|
+
// -----------------------------------------------------------------------------
|
|
89
|
+
// RokuUI-Regular symbols
|
|
90
|
+
//
|
|
91
|
+
// Present in the default UI font, so no fallback is needed under a system font.
|
|
92
|
+
// Under a custom app font there is no RokuFont01 backstop for these either — a
|
|
93
|
+
// custom font that lacks them renders tofu. These are the constants to watch if
|
|
94
|
+
// your app ships its own font.
|
|
95
|
+
// -----------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/** • U+2022 BULLET */
|
|
98
|
+
export const BULLET = "\u2022";
|
|
99
|
+
/** … U+2026 HORIZONTAL ELLIPSIS */
|
|
100
|
+
export const ELLIPSIS = "\u2026";
|
|
101
|
+
/** – U+2013 EN DASH */
|
|
102
|
+
export const EN_DASH = "\u2013";
|
|
103
|
+
/** — U+2014 EM DASH */
|
|
104
|
+
export const EM_DASH = "\u2014";
|
|
105
|
+
/** ← U+2190 LEFTWARDS ARROW */
|
|
106
|
+
export const LEFT_ARROW = "\u2190";
|
|
107
|
+
/** ↑ U+2191 UPWARDS ARROW */
|
|
108
|
+
export const UP_ARROW = "\u2191";
|
|
109
|
+
/** → U+2192 RIGHTWARDS ARROW */
|
|
110
|
+
export const RIGHT_ARROW = "\u2192";
|
|
111
|
+
/** ↓ U+2193 DOWNWARDS ARROW */
|
|
112
|
+
export const DOWN_ARROW = "\u2193";
|
|
113
|
+
/** ✓ U+2713 CHECK MARK */
|
|
114
|
+
export const CHECK_MARK = "\u2713";
|
|
115
|
+
/** ✗ U+2717 BALLOT X */
|
|
116
|
+
export const BALLOT_X = "\u2717";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { getAppInfo } from "@roku-sdk/runtime/app-info";
|
|
4
|
+
import { useUiResolution } from "@roku-sdk/runtime/device-info-events";
|
|
5
|
+
import type { Component, JSX } from "@roku-sdk/solid";
|
|
6
|
+
import { createContext, createEffect, createSignal, useContext } from "@roku-sdk/solid";
|
|
7
|
+
|
|
8
|
+
export enum Resolution {
|
|
9
|
+
HD = "hd",
|
|
10
|
+
FHD = "fhd",
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
|
12
|
+
hd = "hd",
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
|
14
|
+
fhd = "fhd"
|
|
15
|
+
}
|
|
16
|
+
export enum ResolutionScale {
|
|
17
|
+
HD = 1,
|
|
18
|
+
FHD = 1.5,
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
|
20
|
+
hd = 1,
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
|
22
|
+
fhd = 1.5
|
|
23
|
+
}
|
|
24
|
+
interface ResolutionContextValue {
|
|
25
|
+
isFHDOnly: boolean;
|
|
26
|
+
deviceResolution: () => Resolution | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IResolutionContextProviderProps {
|
|
30
|
+
children: JSX.Element;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ResolutionContext = createContext<ResolutionContextValue>();
|
|
34
|
+
|
|
35
|
+
export const ResolutionProvider: Component<IResolutionContextProviderProps> = props => {
|
|
36
|
+
const appInfo = getAppInfo();
|
|
37
|
+
const resolutions = appInfo?.getManifestValue("ui_resolutions")?.split(",");
|
|
38
|
+
const isFHDOnly = resolutions?.includes("fhd") && !resolutions?.includes("hd");
|
|
39
|
+
const { uiResolution } = useUiResolution();
|
|
40
|
+
|
|
41
|
+
const [deviceResolution, setDeviceResolution] = createSignal<Resolution>();
|
|
42
|
+
|
|
43
|
+
createEffect(() => {
|
|
44
|
+
const resolution = uiResolution();
|
|
45
|
+
|
|
46
|
+
if (resolution && !deviceResolution()) {
|
|
47
|
+
setDeviceResolution(resolution.name.toLowerCase() as Resolution);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const value: ResolutionContextValue = {
|
|
52
|
+
isFHDOnly: isFHDOnly === undefined ? true : isFHDOnly,
|
|
53
|
+
deviceResolution
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return <ResolutionContext.Provider value={value}>{props.children}</ResolutionContext.Provider>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function useResolutionContext() {
|
|
60
|
+
const context = useContext(ResolutionContext);
|
|
61
|
+
if (!context) {
|
|
62
|
+
return {
|
|
63
|
+
isFHDOnly: true,
|
|
64
|
+
deviceResolution: () => Resolution.FHD
|
|
65
|
+
} as ResolutionContextValue;
|
|
66
|
+
}
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { Accessor, createMemo } from "@roku-sdk/solid";
|
|
4
|
+
|
|
5
|
+
import { Vector2D } from "../sg-nodes/types";
|
|
6
|
+
import { useResolutionContext } from "./resolution-context";
|
|
7
|
+
|
|
8
|
+
const scale = (value: number) => Math.floor((value * 2) / 3);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Workaround to scale the size based on the device resolution.
|
|
12
|
+
* Used to fix the lack of autoscaling when the device resolution is not FHD.
|
|
13
|
+
* Resolution-related properties of some SceneGraph nodes do not autoscale
|
|
14
|
+
* when the resolution is not FHD, even when autoscaling is enabled
|
|
15
|
+
* (manifest has FHD resolution only).
|
|
16
|
+
*
|
|
17
|
+
* @important This workaround works only under ResolutionProvider.
|
|
18
|
+
*
|
|
19
|
+
* @param size - The size accessor to apply the workaround to.
|
|
20
|
+
* @returns The autoscaled size accessor.
|
|
21
|
+
*/
|
|
22
|
+
export const scaleForDeviceResolution = (size: Accessor<Vector2D>): Accessor<Vector2D> => {
|
|
23
|
+
const { deviceResolution } = useResolutionContext();
|
|
24
|
+
return createMemo<Vector2D>(() => {
|
|
25
|
+
const [width, height] = size();
|
|
26
|
+
if (deviceResolution() === "hd") {
|
|
27
|
+
return [scale(width), scale(height)];
|
|
28
|
+
}
|
|
29
|
+
return [width, height];
|
|
30
|
+
});
|
|
31
|
+
};
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
export * from "./sg-nodes/animation";
|
|
5
|
+
export * from "./sg-nodes/animated-image";
|
|
6
|
+
export * from "./sg-nodes/array-grid";
|
|
7
|
+
export * from "./bounding-tracker";
|
|
8
|
+
export * from "./blended-busy-spinner";
|
|
9
|
+
export { BusySpinner } from "./sg-nodes/busy-spinner";
|
|
10
|
+
export * from "./sg-nodes/button";
|
|
11
|
+
export * from "./sg-nodes/button-group";
|
|
12
|
+
export * from "./channel-store";
|
|
13
|
+
export * from "./container";
|
|
14
|
+
export * from "./sg-nodes/content-node";
|
|
15
|
+
export * from "./sg-nodes/custom-keyboard";
|
|
16
|
+
export * from "./sg-nodes/dialog";
|
|
17
|
+
export * from "./sg-nodes/dynamic-key-grid";
|
|
18
|
+
export * from "./sg-nodes/dynamic-keyboard";
|
|
19
|
+
export * from "./sg-nodes/dynamic-mini-keyboard";
|
|
20
|
+
export * from "./sg-nodes/dynamic-pinpad";
|
|
21
|
+
export * from "./sg-nodes/effect";
|
|
22
|
+
export * from "./focusable";
|
|
23
|
+
export * from "./focusable-group";
|
|
24
|
+
export * from "./sg-nodes/font";
|
|
25
|
+
export * from "./sg-nodes/group";
|
|
26
|
+
export * from "./key-handler";
|
|
27
|
+
export * from "./sg-nodes/label";
|
|
28
|
+
export * from "./sg-nodes/label-list";
|
|
29
|
+
export * from "./sg-nodes/check-list";
|
|
30
|
+
export * from "./sg-nodes/layout-group";
|
|
31
|
+
export * from "./sg-nodes/list-panel";
|
|
32
|
+
export * from "./sg-nodes/markup-list";
|
|
33
|
+
export * from "./sg-nodes/mask-group";
|
|
34
|
+
// Implementation lives in @roku-sdk/navigation. Re-exported here so
|
|
35
|
+
// existing `import { ModalBoundary, useModal } from "@roku-sdk/components"` keeps working.
|
|
36
|
+
export { ModalBoundary, type IModalBoundaryProps, useModal, type UseModalResult } from "@roku-sdk/navigation";
|
|
37
|
+
export * from "./sg-nodes/node";
|
|
38
|
+
export * from "./sg-nodes/overhang";
|
|
39
|
+
export * from "./sg-nodes/panel";
|
|
40
|
+
export * from "./sg-nodes/panel-set";
|
|
41
|
+
export * from "./sg-nodes/poster";
|
|
42
|
+
export * from "./sg-nodes/poster-grid";
|
|
43
|
+
export * from "./sg-nodes/radio-button-list";
|
|
44
|
+
export * from "./sg-nodes/rectangle";
|
|
45
|
+
export * from "./sg-nodes/row-list";
|
|
46
|
+
export * from "./sg-nodes/time-grid";
|
|
47
|
+
export * from "./sg-nodes/zoom-row-list";
|
|
48
|
+
export * from "./sg-nodes/scene";
|
|
49
|
+
export * from "./sg-nodes/scrolling-label";
|
|
50
|
+
export * from "./sg-nodes/scrollable-text";
|
|
51
|
+
export * from "./sg-nodes/simple-label";
|
|
52
|
+
export * from "./sg-nodes/standard-dialog";
|
|
53
|
+
export * from "./sg-nodes/standard-message-dialog";
|
|
54
|
+
export * from "./sg-nodes/standard-keyboard-dialog";
|
|
55
|
+
export * from "./sg-nodes/standard-pinpad-dialog";
|
|
56
|
+
export * from "./sg-nodes/standard-progress-dialog";
|
|
57
|
+
export * from "./sg-nodes/target-set";
|
|
58
|
+
export * from "./sg-nodes/target-group";
|
|
59
|
+
export * from "./sg-nodes/target-list";
|
|
60
|
+
export * from "./types";
|
|
61
|
+
export * from "./sg-nodes/video";
|
|
62
|
+
export * from "./sg-nodes/voice-text-edit-box";
|
|
63
|
+
export * from "./sg-nodes/audio";
|
|
64
|
+
export * from "./sg-nodes/sound-effect";
|
|
65
|
+
export * from "./button-row";
|
|
66
|
+
export * from "./sg-nodes/markup-grid";
|
|
67
|
+
|
|
68
|
+
export { Tween } from "./tween";
|
|
69
|
+
export type { TweenProps } from "./tween";
|
|
70
|
+
export type { ITweenProps } from "./sg-nodes/tween.props";
|
package/src/jsx-props.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
// Types-only barrel for JSX intrinsic augmentations. Imported by jsx.d.ts / sg-jsx.d.ts.
|
|
5
|
+
// Do not re-export from .tsx modules — that reintroduces consumer tsc circular errors.
|
|
6
|
+
|
|
7
|
+
export type { IBlendedBusySpinnerProps } from "./blended-busy-spinner.props";
|
|
8
|
+
export type { IBoundingTrackerProps } from "./bounding-tracker.props";
|
|
9
|
+
export type { IChannelStoreProps } from "./channel-store.props";
|
|
10
|
+
export type { IFocusableGroupProps } from "./focusable-group.props";
|
|
11
|
+
export type { IFocusableProps } from "./focusable.props";
|
|
12
|
+
export type { IKeyHandlerProps } from "./key-handler.props";
|
|
13
|
+
export type { IModalProps } from "./modal-dialog.props";
|
|
14
|
+
export type {
|
|
15
|
+
IAnimationBaseProps,
|
|
16
|
+
IAnimationProps,
|
|
17
|
+
IColorFieldInterpolatorProps,
|
|
18
|
+
IFloatFieldInterpolatorProps,
|
|
19
|
+
IVector2DFieldInterpolatorProps
|
|
20
|
+
} from "./sg-nodes/animation.props";
|
|
21
|
+
export type { IAnimatedImageProps } from "./sg-nodes/animated-image.props";
|
|
22
|
+
export type { IAudioProps } from "./sg-nodes/audio.props";
|
|
23
|
+
export type { IBusySpinnerProps } from "./sg-nodes/busy-spinner.props";
|
|
24
|
+
export type { IButtonGroupProps } from "./sg-nodes/button-group.props";
|
|
25
|
+
export type { IButtonProps } from "./sg-nodes/button.props";
|
|
26
|
+
export type { ICheckListProps } from "./sg-nodes/check-list.props";
|
|
27
|
+
export type { IContentNodeProps } from "./sg-nodes/content-node.props";
|
|
28
|
+
export type { IKeyboardPropsWithVoice } from "./sg-nodes/custom-keyboard.props";
|
|
29
|
+
export type { IDialogProps } from "./sg-nodes/dialog.props";
|
|
30
|
+
export type { IDynamicKeyGridProps } from "./sg-nodes/dynamic-key-grid.props";
|
|
31
|
+
export type { IDynamicKeyboardProps } from "./sg-nodes/dynamic-keyboard.props";
|
|
32
|
+
export type { IDynamicMiniKeyboardProps } from "./sg-nodes/dynamic-mini-keyboard.props";
|
|
33
|
+
export type { IDynamicPinPadProps } from "./sg-nodes/dynamic-pinpad.props";
|
|
34
|
+
export type { IEffectProps } from "./sg-nodes/effect.props";
|
|
35
|
+
export type { IFontProps } from "./sg-nodes/font.props";
|
|
36
|
+
export type { IGroupProps } from "./sg-nodes/group.props";
|
|
37
|
+
export type { ILabelListProps } from "./sg-nodes/label-list.props";
|
|
38
|
+
export type { ILabelProps, IMultiStyleLabelProps } from "./sg-nodes/label.props";
|
|
39
|
+
export type { ILayoutGroupProps } from "./sg-nodes/layout-group.props";
|
|
40
|
+
export type { IFullPanelProps, IListPanelProps } from "./sg-nodes/list-panel.props";
|
|
41
|
+
export type { IMarkupGridProps } from "./sg-nodes/markup-grid.props";
|
|
42
|
+
export type { IMarkupListProps } from "./sg-nodes/markup-list.props";
|
|
43
|
+
export type { IMaskGroupProps } from "./sg-nodes/mask-group.props";
|
|
44
|
+
export type { IOverhangProps } from "./sg-nodes/overhang.props";
|
|
45
|
+
export type { IPanelSetProps } from "./sg-nodes/panel-set.props";
|
|
46
|
+
export type { IPanelProps } from "./sg-nodes/panel.props";
|
|
47
|
+
export type { IPosterGridProps } from "./sg-nodes/poster-grid.props";
|
|
48
|
+
export type { IPosterProps } from "./sg-nodes/poster.props";
|
|
49
|
+
export type { IRadioButtonListProps } from "./sg-nodes/radio-button-list.props";
|
|
50
|
+
export type { IRectangleProps } from "./sg-nodes/rectangle.props";
|
|
51
|
+
export type { IRowListProps } from "./sg-nodes/row-list.props";
|
|
52
|
+
export type { ISceneProps } from "./sg-nodes/scene.props";
|
|
53
|
+
export type { IScrollableTextProps } from "./sg-nodes/scrollable-text.props";
|
|
54
|
+
export type { IScrollingLabelProps } from "./sg-nodes/scrolling-label.props";
|
|
55
|
+
export type { ISimpleLabelProps } from "./sg-nodes/simple-label.props";
|
|
56
|
+
export type { ISoundEffectProps } from "./sg-nodes/sound-effect.props";
|
|
57
|
+
export type {
|
|
58
|
+
IStandardDialogProps,
|
|
59
|
+
IStandardDialogRSGPaletteNodeProps,
|
|
60
|
+
IStdDlgActionCardItemProps,
|
|
61
|
+
IStdDlgBulletTextItemProps,
|
|
62
|
+
IStdDlgButtonAreaProps,
|
|
63
|
+
IStdDlgButtonProps,
|
|
64
|
+
IStdDlgContentAreaProps,
|
|
65
|
+
IStdDlgCustomItemProps,
|
|
66
|
+
IStdDlgDeterminateProgressItemProps,
|
|
67
|
+
IStdDlgGraphicItemProps,
|
|
68
|
+
IStdDlgItemGroupProps,
|
|
69
|
+
IStdDlgKeyboardItemProps,
|
|
70
|
+
IStdDlgMultiStyleTextItemProps,
|
|
71
|
+
IStdDlgProgressItemProps,
|
|
72
|
+
IStdDlgSideCardAreaProps,
|
|
73
|
+
IStdDlgTextItemProps,
|
|
74
|
+
IStdDlgTitleAreaProps
|
|
75
|
+
} from "./sg-nodes/standard-dialog.props";
|
|
76
|
+
export type { IStandardKeyboardDialogProps } from "./sg-nodes/standard-keyboard-dialog.props";
|
|
77
|
+
export type { IStandardMessageDialogProps } from "./sg-nodes/standard-message-dialog.props";
|
|
78
|
+
export type { IStandardPinPadDialogProps } from "./sg-nodes/standard-pinpad-dialog.props";
|
|
79
|
+
export type { IStandardProgressDialogProps } from "./sg-nodes/standard-progress-dialog.props";
|
|
80
|
+
export type { ITargetGroupProps } from "./sg-nodes/target-group.props";
|
|
81
|
+
export type { ITargetListProps } from "./sg-nodes/target-list.props";
|
|
82
|
+
export type { ITargetSetProps } from "./sg-nodes/target-set.props";
|
|
83
|
+
export type { ITimeGridProps } from "./sg-nodes/time-grid.props";
|
|
84
|
+
export type { ITweenProps } from "./sg-nodes/tween.props";
|
|
85
|
+
export type { IBifDisplayProps, IProgressBarProps, ITrickPlayBarProps, IVideoProps } from "./sg-nodes/video.props";
|
|
86
|
+
export type { IVoiceTextEditBoxProps } from "./sg-nodes/voice-text-edit-box.props";
|
|
87
|
+
export type { IZoomRowListProps } from "./sg-nodes/zoom-row-list.props";
|
package/src/kdfs.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Packaged Key Definition File URI exports (resolved to pkg:/components/... at build time).
|
|
6
|
+
*
|
|
7
|
+
* Keep this in sync with `kdfs.ts`: every export added there must be declared here.
|
|
8
|
+
*/
|
|
9
|
+
declare module "@roku-sdk/components/kdfs" {
|
|
10
|
+
export const CustomAddressKDF: string;
|
|
11
|
+
export const MOPPinPadCustomKDF: string;
|
|
12
|
+
export const PhonePadCustomKDF: string;
|
|
13
|
+
export const WifiCustomKDF: string;
|
|
14
|
+
}
|
package/src/kdfs.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Packaged Key Definition File URIs for standard keyboard layouts.
|
|
6
|
+
*
|
|
7
|
+
* Import only the layouts you use so unused KDF files tree-shake out of the build.
|
|
8
|
+
*
|
|
9
|
+
* When adding a new KDF export here, add a matching declaration to `kdfs.d.ts`
|
|
10
|
+
* (the published type surface for `@roku-sdk/components/kdfs`); the two must stay in sync.
|
|
11
|
+
*/
|
|
12
|
+
export { default as CustomAddressKDF } from "./xml-components/keyboards/CustomAddressKDF.keyboard.json";
|
|
13
|
+
export { default as MOPPinPadCustomKDF } from "./xml-components/keyboards/MOPPinPadCustomKDF.keyboard.json";
|
|
14
|
+
export { default as PhonePadCustomKDF } from "./xml-components/keyboards/PhonePadCustomKDF.keyboard.json";
|
|
15
|
+
export { default as WifiCustomKDF } from "./xml-components/keyboards/WifiCustomKDF.keyboard.json";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { RemoteKeyName } from "@roku-sdk/interfaces";
|
|
5
|
+
|
|
6
|
+
import type { IGroupProps } from "./sg-nodes/group.props";
|
|
7
|
+
import type { EventHandler, JSX } from "./sg-nodes/types";
|
|
8
|
+
|
|
9
|
+
export interface CommonKeyHandlerProps extends IGroupProps {
|
|
10
|
+
onKeyEvent: EventHandlerKey;
|
|
11
|
+
children?: JSX.Element;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IKeyHandlerData {
|
|
15
|
+
key: RemoteKeyName;
|
|
16
|
+
press: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type EventHandlerKey = EventHandler<IKeyHandlerData>;
|
|
20
|
+
|
|
21
|
+
export interface ScopedKeyHandlerProps extends CommonKeyHandlerProps {
|
|
22
|
+
handleAllKeys?: false;
|
|
23
|
+
keys: RemoteKeyName[];
|
|
24
|
+
bubbleUnhandledKeys?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AllKeysKeyHandlerProps extends CommonKeyHandlerProps {
|
|
28
|
+
handleAllKeys: true;
|
|
29
|
+
keys?: never;
|
|
30
|
+
bubbleUnhandledKeys?: never;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type IKeyHandlerProps = AllKeysKeyHandlerProps | ScopedKeyHandlerProps;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { IKeyHandlerProps } from "./key-handler.props";
|
|
5
|
+
export type { CommonKeyHandlerProps } from "./key-handler.props";
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
IKeyHandlerData,
|
|
9
|
+
EventHandlerKey,
|
|
10
|
+
ScopedKeyHandlerProps,
|
|
11
|
+
AllKeysKeyHandlerProps,
|
|
12
|
+
IKeyHandlerProps
|
|
13
|
+
} from "./key-handler.props";
|
|
14
|
+
|
|
15
|
+
import { retainAsset } from "./retain-asset";
|
|
16
|
+
import { Component } from "./types";
|
|
17
|
+
import KeyHandlerXML from "./xml-components/keyhandler/KeyHandler.xml";
|
|
18
|
+
|
|
19
|
+
// Only allow either 'handleAllKeys' or ('keys' and 'bubbleUnhandledKeys'), not both
|
|
20
|
+
|
|
21
|
+
export const KeyHandler: Component<IKeyHandlerProps> = props => {
|
|
22
|
+
retainAsset(KeyHandlerXML);
|
|
23
|
+
return <key-handler {...props}>{props.children}</key-handler>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { IGroupProps } from "./sg-nodes/group.props";
|
|
5
|
+
import type { EventHandler, JSX } from "./sg-nodes/types";
|
|
6
|
+
|
|
7
|
+
export interface IModalLifecycleProps {
|
|
8
|
+
/** Present the dialog. False prepares its children without taking focus. @default true */
|
|
9
|
+
open?: boolean;
|
|
10
|
+
/** Prepare the dialog while showing fallback, without granting it focus. @default true */
|
|
11
|
+
ready?: boolean;
|
|
12
|
+
/** Native dialog shown while ready=false; use a standardDialog intrinsic, not another Modal. */
|
|
13
|
+
fallback?: JSX.Element;
|
|
14
|
+
/** Allow replacing another native modal. False reports onBlocked instead. @default true */
|
|
15
|
+
replaceExisting?: boolean;
|
|
16
|
+
/** Fired after native attachment and initial focus, not after artwork has drawn. */
|
|
17
|
+
onOpened?: EventHandler<void>;
|
|
18
|
+
/** Fired at most once per presented lifetime while mounted, including native dismissal and open=false. */
|
|
19
|
+
onWasClosed?: EventHandler<void>;
|
|
20
|
+
/** Another native dialog prevented presentation. Toggle open to retry explicitly. */
|
|
21
|
+
onBlocked?: EventHandler<void>;
|
|
22
|
+
/** ID of the child that receives focus when the dialog opens. */
|
|
23
|
+
initialFocusId?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Props for the custom Modal XML component.
|
|
28
|
+
*
|
|
29
|
+
* Intentionally does not derive from `JSX.IntrinsicElements["modal"]` — that tag is
|
|
30
|
+
* declared as `IModalProps` in jsx.d.ts, so referencing the intrinsic here would create
|
|
31
|
+
* a circular type.
|
|
32
|
+
*/
|
|
33
|
+
export interface IModalProps extends IGroupProps, IModalLifecycleProps {
|
|
34
|
+
dialog?: JSX.Element;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @deprecated Use {@link IModalProps}. Kept for call-site casts onto the native modal tag. */
|
|
38
|
+
export type ModalIntrinsicProps = IModalProps;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { DomNode } from "@roku-sdk/interfaces";
|
|
5
|
+
import { createMemo, onCleanup, onMount, setProp, splitProps } from "@roku-sdk/solid";
|
|
6
|
+
|
|
7
|
+
import type { IModalProps } from "./modal-dialog.props";
|
|
8
|
+
import { retainAsset } from "./retain-asset";
|
|
9
|
+
import { Component } from "./types";
|
|
10
|
+
import ModalXML from "./xml-components/modal/Modal.xml";
|
|
11
|
+
export type { ModalIntrinsicProps, IModalProps, IModalLifecycleProps } from "./modal-dialog.props";
|
|
12
|
+
|
|
13
|
+
export const modalPropKeys = [
|
|
14
|
+
"initialFocusId",
|
|
15
|
+
"open",
|
|
16
|
+
"ready",
|
|
17
|
+
"fallback",
|
|
18
|
+
"replaceExisting",
|
|
19
|
+
"onOpened",
|
|
20
|
+
"onWasClosed",
|
|
21
|
+
"onBlocked"
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
export const Modal: Component<IModalProps> = (props: IModalProps) => {
|
|
25
|
+
retainAsset(ModalXML);
|
|
26
|
+
// JSX node getters must not reconstruct their subtrees when lifecycle props change.
|
|
27
|
+
const [content, rest] = splitProps(props, ["dialog", "fallback"]);
|
|
28
|
+
const dialog = createMemo(() => content.dialog);
|
|
29
|
+
const fallback = createMemo(() => content.fallback);
|
|
30
|
+
const element = <modal {...rest} dialog={dialog()} fallback={fallback()} visible={false} />;
|
|
31
|
+
const node = element as unknown as DomNode;
|
|
32
|
+
// Present only after the wrapper and all node-valued children have been attached.
|
|
33
|
+
onMount(() => setProp(node, "mounted", true));
|
|
34
|
+
onCleanup(() => {
|
|
35
|
+
setProp(node, "mounted", false);
|
|
36
|
+
// Node-valued props are not ordinary children; retire their observers explicitly.
|
|
37
|
+
setProp(node, "dialog", null, dialog());
|
|
38
|
+
setProp(node, "fallback", null, fallback());
|
|
39
|
+
});
|
|
40
|
+
return element;
|
|
41
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Keeps an imported asset live when its component survives tree-shaking.
|
|
6
|
+
*
|
|
7
|
+
* The asset import plugin uses esbuild's metafile to copy only assets referenced
|
|
8
|
+
* by bundled modules. This function is a no-op at runtime; its only job is to
|
|
9
|
+
* create a reference esbuild can see so TypeScript/esbuild does not elide the
|
|
10
|
+
* asset's import binding as unused.
|
|
11
|
+
*
|
|
12
|
+
* Call this inside the component function, not at module scope. Placement matters
|
|
13
|
+
* when a module exports more than one component: a module-scope reference keeps
|
|
14
|
+
* the asset alive whenever the module is loaded, even if the specific component
|
|
15
|
+
* export is dead-code-eliminated. Keeping the reference in the component body
|
|
16
|
+
* ties the asset to that export, so the two are removed together.
|
|
17
|
+
*
|
|
18
|
+
* Do NOT place this inside `createEffect`, `onMount`, or other lazy closures. The
|
|
19
|
+
* asset copies unconditionally once the module-scope import survives DCE, so a
|
|
20
|
+
* lazy call site would be misleading without changing packaging behavior.
|
|
21
|
+
*/
|
|
22
|
+
export function retainAsset(asset: unknown): void {
|
|
23
|
+
void asset;
|
|
24
|
+
}
|