@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,126 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import {
|
|
4
|
+
Component,
|
|
5
|
+
EventHandler,
|
|
6
|
+
Focusable,
|
|
7
|
+
FocusableGroup,
|
|
8
|
+
IButtonDataProps,
|
|
9
|
+
ResType,
|
|
10
|
+
Uri,
|
|
11
|
+
Vector2D
|
|
12
|
+
} from "@roku-sdk/components";
|
|
13
|
+
import { createMemo, createSelector, For, Show } from "@roku-sdk/solid";
|
|
14
|
+
|
|
15
|
+
interface ISingleButtonProps extends IButtonDataProps {
|
|
16
|
+
text?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type IButtonRecord = ISingleButtonProps | string;
|
|
20
|
+
|
|
21
|
+
export interface IButtonRowProps extends IButtonDataProps {
|
|
22
|
+
buttons: IButtonRecord[];
|
|
23
|
+
translation?: ResType<Vector2D>;
|
|
24
|
+
onButtonFocused?: EventHandler<number>;
|
|
25
|
+
onButtonSelected?: EventHandler<number>;
|
|
26
|
+
opacity?: number;
|
|
27
|
+
focusIdx?: number;
|
|
28
|
+
itemSpacing?: number;
|
|
29
|
+
gainFocus?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const buttonDataKeys: (keyof IButtonDataProps)[] = [
|
|
33
|
+
"textColor",
|
|
34
|
+
"focusedTextColor",
|
|
35
|
+
"textFont",
|
|
36
|
+
"focusedTextFont",
|
|
37
|
+
"focusFootprintBitmapUri",
|
|
38
|
+
"focusBitmapUri",
|
|
39
|
+
"iconUri",
|
|
40
|
+
"focusedIconUri",
|
|
41
|
+
"height",
|
|
42
|
+
"minWidth",
|
|
43
|
+
"maxWidth",
|
|
44
|
+
"showFocusFootprint"
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const defaultButtonData: ISingleButtonProps = {
|
|
48
|
+
showFocusFootprint: true,
|
|
49
|
+
iconUri: "",
|
|
50
|
+
focusedIconUri: "",
|
|
51
|
+
textFont: "font:TinySystemFont",
|
|
52
|
+
focusedTextFont: "font:TinyBoldSystemFont"
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const createButtonProps = (button: IButtonRecord, componentProps: IButtonDataProps): ISingleButtonProps => {
|
|
56
|
+
let buttonProps: ISingleButtonProps = buttonDataKeys.reduce(
|
|
57
|
+
(acc: ISingleButtonProps, key) => {
|
|
58
|
+
const componentValue = componentProps[key];
|
|
59
|
+
if (componentValue !== undefined) {
|
|
60
|
+
(acc[key] as number | boolean | Uri) = componentValue;
|
|
61
|
+
}
|
|
62
|
+
return acc;
|
|
63
|
+
},
|
|
64
|
+
{ ...defaultButtonData }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (typeof button === "string") {
|
|
68
|
+
buttonProps.text = button;
|
|
69
|
+
} else {
|
|
70
|
+
buttonProps = { ...buttonProps, ...button };
|
|
71
|
+
}
|
|
72
|
+
return buttonProps;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const ButtonRow: Component<IButtonRowProps> = props => {
|
|
76
|
+
const focusIdx = () => props.focusIdx || 0;
|
|
77
|
+
|
|
78
|
+
const onButtonFocused: EventHandler<string | null> = (id: string | null) => {
|
|
79
|
+
if (props.onButtonFocused && id) {
|
|
80
|
+
props.onButtonFocused(parseInt(id.replace(props.id || "", "")));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const buttonsLength = createMemo(() => props.buttons.length);
|
|
85
|
+
|
|
86
|
+
// Create a stable array of button indices that only changes when length changes
|
|
87
|
+
const buttonIndices = createMemo(() => Array.from({ length: buttonsLength() }, (_, i) => i));
|
|
88
|
+
const isFocused = createSelector(focusIdx, (a, b) => (props.gainFocus === undefined || props.gainFocus) && a === b);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<FocusableGroup id={props.id} rememberLastFocus={true} gainFocus={props.gainFocus} opacity={props.opacity}>
|
|
92
|
+
<Show when={buttonsLength() > 0}>
|
|
93
|
+
<layoutGroup
|
|
94
|
+
layoutDirection="horiz"
|
|
95
|
+
horizAlignment="left"
|
|
96
|
+
translation={props.translation}
|
|
97
|
+
itemSpacings={[props.itemSpacing || 32]}
|
|
98
|
+
>
|
|
99
|
+
<For
|
|
100
|
+
each={buttonIndices()}
|
|
101
|
+
children={(index: number) => {
|
|
102
|
+
const buttonAtIndex = (): IButtonRecord | undefined => props.buttons[index];
|
|
103
|
+
const onButtonSelected: EventHandler<never> = () => props.onButtonSelected?.(index);
|
|
104
|
+
return (
|
|
105
|
+
<Show when={buttonAtIndex() !== undefined}>
|
|
106
|
+
<Focusable
|
|
107
|
+
nextFocusRight={`${props.id || ""}${index < buttonsLength() - 1 ? (index + 1).toString() : undefined}`}
|
|
108
|
+
nextFocusLeft={`${props.id || ""}${index > 0 ? (index - 1).toString() : undefined}`}
|
|
109
|
+
>
|
|
110
|
+
<button
|
|
111
|
+
gainFocus={isFocused(index)}
|
|
112
|
+
onFocusedChild={onButtonFocused}
|
|
113
|
+
onButtonSelected={onButtonSelected}
|
|
114
|
+
{...createButtonProps(buttonAtIndex()!, props)}
|
|
115
|
+
id={`${props.id || ""}${index.toString()}`}
|
|
116
|
+
/>
|
|
117
|
+
</Focusable>
|
|
118
|
+
</Show>
|
|
119
|
+
);
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
</layoutGroup>
|
|
123
|
+
</Show>
|
|
124
|
+
</FocusableGroup>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import type { EventHandler, ResObject } from "./sg-nodes/types";
|
|
5
|
+
|
|
6
|
+
export interface IChannelStoreRequestStatusEvent {
|
|
7
|
+
command: string;
|
|
8
|
+
context: Record<string, string>;
|
|
9
|
+
requestid: number;
|
|
10
|
+
result: Record<string, string | number | boolean>;
|
|
11
|
+
status: number;
|
|
12
|
+
statusmessage: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Command strings supported by the ChannelStore node.
|
|
17
|
+
*
|
|
18
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore}
|
|
19
|
+
*/
|
|
20
|
+
export type ChannelStoreCommand =
|
|
21
|
+
| "none"
|
|
22
|
+
| "getUserData"
|
|
23
|
+
| "getUserRegionData"
|
|
24
|
+
| "storeChannelCredData"
|
|
25
|
+
| "getChannelCred"
|
|
26
|
+
| "getCatalog"
|
|
27
|
+
| "doOrder"
|
|
28
|
+
| "getPurchases"
|
|
29
|
+
| "getAllPurchases"
|
|
30
|
+
| "requestPartnerOrder"
|
|
31
|
+
| "confirmPartnerOrder"
|
|
32
|
+
| "getDeviceAttestationToken";
|
|
33
|
+
|
|
34
|
+
/** Context for the RFI (Request for Information) screen. */
|
|
35
|
+
export type ChannelStoreUserDataContext = "signin" | "signup";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Custom request object for advanced ChannelStore operations.
|
|
39
|
+
*/
|
|
40
|
+
export interface IChannelStoreRequest {
|
|
41
|
+
command?: string;
|
|
42
|
+
params?: ResObject<Record<string, string | number | boolean>>;
|
|
43
|
+
context?: ResObject<Record<string, string>>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Roku customer account information returned by the `getUserData` command.
|
|
48
|
+
*
|
|
49
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getUserData Command}
|
|
50
|
+
*/
|
|
51
|
+
export interface IUserData {
|
|
52
|
+
email?: string;
|
|
53
|
+
phone?: string;
|
|
54
|
+
firstName?: string;
|
|
55
|
+
lastName?: string;
|
|
56
|
+
street?: string;
|
|
57
|
+
city?: string;
|
|
58
|
+
state?: string;
|
|
59
|
+
zip?: string;
|
|
60
|
+
country?: string;
|
|
61
|
+
birth?: string;
|
|
62
|
+
gender?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type EventHandlerUserData = EventHandler<IUserData>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Region data returned by the `getUserRegionData` command.
|
|
69
|
+
*
|
|
70
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getUserRegionData Command}
|
|
71
|
+
*/
|
|
72
|
+
export interface IUserRegionData {
|
|
73
|
+
state: string;
|
|
74
|
+
zip: string;
|
|
75
|
+
country: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type EventHandlerUserRegionData = EventHandler<IUserRegionData>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Configuration for the RFI (Request for Information) screen.
|
|
82
|
+
*
|
|
83
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | requestedUserDataInfo Field}
|
|
84
|
+
*/
|
|
85
|
+
export interface IRequestedUserDataInfo {
|
|
86
|
+
/** Whether the RFI screen is for sign-up or sign-in. */
|
|
87
|
+
context?: ChannelStoreUserDataContext;
|
|
88
|
+
/**
|
|
89
|
+
* When true, the RFI signup screen displays the values of the requested customer
|
|
90
|
+
* information. Has no effect if context is "signin".
|
|
91
|
+
* @default false
|
|
92
|
+
*/
|
|
93
|
+
forceShowData?: boolean;
|
|
94
|
+
[key: string]: unknown;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Status returned by the `storeChannelCredData` command.
|
|
99
|
+
*
|
|
100
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | storeChannelCredData Command}
|
|
101
|
+
*/
|
|
102
|
+
export interface IStoreChannelCredDataStatus {
|
|
103
|
+
/** JSON string with status, error, and error_detail fields. */
|
|
104
|
+
response: string;
|
|
105
|
+
/** Request status; 0 indicates success. */
|
|
106
|
+
status: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type EventHandlerStoreChannelCredDataStatus = EventHandler<IStoreChannelCredDataStatus>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Channel credential data returned by the `getChannelCred` command.
|
|
113
|
+
*
|
|
114
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getChannelCred Command}
|
|
115
|
+
*/
|
|
116
|
+
export interface IChannelCred {
|
|
117
|
+
/** The app ID (e.g., "2213" for Roku Media Player). */
|
|
118
|
+
channelId: string;
|
|
119
|
+
/** Error description; empty string on success. */
|
|
120
|
+
errorCode: string;
|
|
121
|
+
/** JSON string containing roku_pucid, token_type, channel_data, and error fields. */
|
|
122
|
+
json: string;
|
|
123
|
+
/** Unique identifier of the device. */
|
|
124
|
+
publisherDeviceId: string;
|
|
125
|
+
/** Request status; 0 indicates success. */
|
|
126
|
+
status: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type EventHandlerChannelCred = EventHandler<IChannelCred>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Product information returned by the `getCatalog` command.
|
|
133
|
+
*
|
|
134
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getCatalog Command}
|
|
135
|
+
*/
|
|
136
|
+
export interface IProduct {
|
|
137
|
+
/** Product identifier from the Developer Dashboard. */
|
|
138
|
+
code: string;
|
|
139
|
+
/** Item name / description. */
|
|
140
|
+
name: string;
|
|
141
|
+
/** Quantity (for consumable products). */
|
|
142
|
+
qty: number;
|
|
143
|
+
/** Product type (e.g., "MonthlySub"). */
|
|
144
|
+
productType: string;
|
|
145
|
+
/** Localized cost with currency symbol. */
|
|
146
|
+
cost: string;
|
|
147
|
+
/** Free trial length (e.g., 1 for 1-month, 7 for 7-day). */
|
|
148
|
+
freeTrialQuantity: number;
|
|
149
|
+
/** Free trial unit ("Days" or "Months"). */
|
|
150
|
+
freeTrialType: string;
|
|
151
|
+
/** Introductory pricing discounted price. */
|
|
152
|
+
trialCost: number;
|
|
153
|
+
/** Months of introductory pricing. */
|
|
154
|
+
trialQuantity: number;
|
|
155
|
+
/** Unit for introductory pricing ("months"). */
|
|
156
|
+
trialType: string;
|
|
157
|
+
/** Whether the product is "saved" or "approved for sale". */
|
|
158
|
+
status: string;
|
|
159
|
+
/** Subscription purchase date. */
|
|
160
|
+
purchaseDate: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type EventHandlerCatalog = EventHandler<IProduct[]>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* An item in an order for the `doOrder` command.
|
|
167
|
+
*
|
|
168
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | order Field}
|
|
169
|
+
*/
|
|
170
|
+
export interface IOrderItem {
|
|
171
|
+
/** Product identifier. */
|
|
172
|
+
code: string;
|
|
173
|
+
/** Quantity to purchase. */
|
|
174
|
+
qty: number;
|
|
175
|
+
/** Set to "Upgrade" or "Downgrade" for subscription plan changes. */
|
|
176
|
+
action?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Status returned by the `doOrder` command.
|
|
181
|
+
*
|
|
182
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | doOrder Command}
|
|
183
|
+
*/
|
|
184
|
+
export interface IOrderStatus {
|
|
185
|
+
/** Completion status (1 = success, 2 = interrupted, 0 = network error, etc.). */
|
|
186
|
+
status: number;
|
|
187
|
+
/** Description of the completion status. */
|
|
188
|
+
statusMessage: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type EventHandlerOrderStatus = EventHandler<IOrderStatus>;
|
|
192
|
+
|
|
193
|
+
export type EventHandlerOrderItemsStatus = EventHandler<IPurchase[]>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Status returned by `getPurchases` / `getAllPurchases` commands.
|
|
197
|
+
*/
|
|
198
|
+
export interface IPurchasesStatus {
|
|
199
|
+
status: number;
|
|
200
|
+
statusMessage: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export type EventHandlerPurchasesStatus = EventHandler<IPurchasesStatus>;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Purchase information returned by `getPurchases` or `getAllPurchases` commands.
|
|
207
|
+
*
|
|
208
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getPurchases Command}
|
|
209
|
+
*/
|
|
210
|
+
export interface IPurchase {
|
|
211
|
+
/** Product identifier. */
|
|
212
|
+
code: string;
|
|
213
|
+
/** Localized cost with currency symbol. */
|
|
214
|
+
cost: string;
|
|
215
|
+
/** Subscription expiration date (ISO 8601). */
|
|
216
|
+
expirationDate: string;
|
|
217
|
+
/** Free trial length. */
|
|
218
|
+
freeTrialQuantity: number;
|
|
219
|
+
/** Free trial unit ("Days" or "Months"). */
|
|
220
|
+
freeTrialType: string;
|
|
221
|
+
/** Whether subscription is past due ("true" if in dunning state). */
|
|
222
|
+
inDunning: string;
|
|
223
|
+
/** Item name / description. */
|
|
224
|
+
name: string;
|
|
225
|
+
/** Product type (e.g., "MonthlySub"). */
|
|
226
|
+
productType: string;
|
|
227
|
+
/** Where the purchase was made ("web" or "device"). */
|
|
228
|
+
purchaseChannel: string;
|
|
229
|
+
/** How the purchase was made ("isu" or "iap"). */
|
|
230
|
+
purchaseContext: string;
|
|
231
|
+
/** Purchase date (ISO 8601). */
|
|
232
|
+
purchaseDate: string;
|
|
233
|
+
/** Transaction ID. */
|
|
234
|
+
purchaseId: string;
|
|
235
|
+
/** Quantity purchased. */
|
|
236
|
+
qty: number;
|
|
237
|
+
/** Subscription renewal date (ISO 8601). */
|
|
238
|
+
renewalDate: string;
|
|
239
|
+
/** "Valid" for current subscriptions, "Invalid" for canceled/expired. */
|
|
240
|
+
status: string;
|
|
241
|
+
/** Description of the status. */
|
|
242
|
+
statusMessage: string;
|
|
243
|
+
/** Introductory pricing discounted price. */
|
|
244
|
+
trialCost: number;
|
|
245
|
+
/** Months of introductory pricing. */
|
|
246
|
+
trialQuantity: number;
|
|
247
|
+
/** Unit for introductory pricing ("months"). */
|
|
248
|
+
trialType: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export type EventHandlerPurchases = EventHandler<IPurchase[]>;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Partner order data for TVOD apps (`requestPartnerOrder` / `confirmPartnerOrder`).
|
|
255
|
+
*
|
|
256
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | requestPartnerOrder Field}
|
|
257
|
+
*/
|
|
258
|
+
export interface IPartnerOrder {
|
|
259
|
+
/** Original price of the product (no currency symbol, e.g., "3.99"). */
|
|
260
|
+
priceDisplay: string;
|
|
261
|
+
/** Final price including discounts (no currency symbol). */
|
|
262
|
+
price: string;
|
|
263
|
+
/** Alphanumeric coupon code for discounted pricing. */
|
|
264
|
+
couponCode?: string;
|
|
265
|
+
/** Publisher-specific SKU or unique identifier. */
|
|
266
|
+
contentKey: string;
|
|
267
|
+
/** Product identifier. */
|
|
268
|
+
code: string;
|
|
269
|
+
/** Product description (e.g., movie title). */
|
|
270
|
+
title?: string;
|
|
271
|
+
/** Order ID returned by Roku (for confirmPartnerOrder). */
|
|
272
|
+
orderID?: string;
|
|
273
|
+
pin?: string;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export type EventHandlerPartnerOrder = EventHandler<IPartnerOrder>;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Device attestation token returned by the `getDeviceAttestationToken` command.
|
|
280
|
+
*
|
|
281
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getDeviceAttestationToken Command}
|
|
282
|
+
*/
|
|
283
|
+
export interface IDeviceAttestationToken {
|
|
284
|
+
/** Request status; 0 indicates success. */
|
|
285
|
+
status: number;
|
|
286
|
+
/** The nonce used to generate the token. */
|
|
287
|
+
nonce: string;
|
|
288
|
+
/** Signed JWT for device attestation verification. */
|
|
289
|
+
token: string;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export type EventHandlerDeviceAttestationToken = EventHandler<IDeviceAttestationToken>;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Properties for the ChannelStore component.
|
|
296
|
+
*
|
|
297
|
+
* The ChannelStore component provides an interface to the Roku Streaming Store, enabling in-app purchases,
|
|
298
|
+
* user authentication, and data retrieval. It supports commands for fetching user data, managing orders,
|
|
299
|
+
* retrieving catalogs, and handling credentials. This component wraps the `<channelStoreExt>` XML element
|
|
300
|
+
* and exposes its fields as TypeScript props and event handlers.
|
|
301
|
+
*
|
|
302
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore}
|
|
303
|
+
*/
|
|
304
|
+
export interface IChannelStoreProps {
|
|
305
|
+
id?: string;
|
|
306
|
+
/**
|
|
307
|
+
* Specifies the command to be executed (e.g., "getUserData", "getCatalog").
|
|
308
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | ChannelStore Commands}
|
|
309
|
+
*/
|
|
310
|
+
command?: ChannelStoreCommand;
|
|
311
|
+
/**
|
|
312
|
+
* Specifies the Roku customer account fields to be retrieved when the `getUserData` command
|
|
313
|
+
* is executed. Set to "all" for all available fields, or a comma-separated list of specific
|
|
314
|
+
* fields (e.g., "email,firstname,lastname").
|
|
315
|
+
* @default "all"
|
|
316
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | requestedUserData Field}
|
|
317
|
+
*/
|
|
318
|
+
requestedUserData?: string;
|
|
319
|
+
/**
|
|
320
|
+
* Configures whether the RFI screen is used for sign-ups or sign-ins.
|
|
321
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | requestedUserDataInfo Field}
|
|
322
|
+
*/
|
|
323
|
+
requestedUserDataInfoObject?: IRequestedUserDataInfo;
|
|
324
|
+
/**
|
|
325
|
+
* Fired when user data is retrieved from the `getUserData` command. Returns `invalid`
|
|
326
|
+
* if the user clicked Cancel on the RFI screen.
|
|
327
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | userData Field}
|
|
328
|
+
*/
|
|
329
|
+
onUserDataObject?: EventHandlerUserData;
|
|
330
|
+
/**
|
|
331
|
+
* Fired when user region data is retrieved from the `getUserRegionData` command.
|
|
332
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getUserRegionData Command}
|
|
333
|
+
*/
|
|
334
|
+
onUserRegionDataObject?: EventHandlerUserRegionData;
|
|
335
|
+
/**
|
|
336
|
+
* Custom request object for advanced ChannelStore operations.
|
|
337
|
+
*/
|
|
338
|
+
request?: IChannelStoreRequest;
|
|
339
|
+
/**
|
|
340
|
+
* Fired when a custom request completes.
|
|
341
|
+
*/
|
|
342
|
+
onRequestStatus?: EventHandler<IChannelStoreRequestStatusEvent>;
|
|
343
|
+
/**
|
|
344
|
+
* Data to be stored securely for channel credentials via the `storeChannelCredData` command.
|
|
345
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | channelCredData Field}
|
|
346
|
+
*/
|
|
347
|
+
channelCredData?: string;
|
|
348
|
+
/**
|
|
349
|
+
* Fired when storing channel credential data completes.
|
|
350
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | storeChannelCredData Command}
|
|
351
|
+
*/
|
|
352
|
+
onStoreChannelCredDataStatusObject?: EventHandlerStoreChannelCredDataStatus;
|
|
353
|
+
/**
|
|
354
|
+
* Fired when channel credentials are retrieved via the `getChannelCred` command.
|
|
355
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getChannelCred Command}
|
|
356
|
+
*/
|
|
357
|
+
onChannelCredObject?: EventHandlerChannelCred;
|
|
358
|
+
/**
|
|
359
|
+
* Fired when the catalog is retrieved from the `getCatalog` command.
|
|
360
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | catalog Field}
|
|
361
|
+
*/
|
|
362
|
+
onCatalogObject?: EventHandlerCatalog;
|
|
363
|
+
/**
|
|
364
|
+
* Contains the order to be filled when the `doOrder` command is executed.
|
|
365
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | order Field}
|
|
366
|
+
*/
|
|
367
|
+
orderObject?: IOrderItem[];
|
|
368
|
+
/**
|
|
369
|
+
* Enables incremental population of the order field. Each time this is set,
|
|
370
|
+
* the order is modified (use negative `qty` to remove items).
|
|
371
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | deltaOrder Field}
|
|
372
|
+
*/
|
|
373
|
+
deltaOrder?: IOrderItem;
|
|
374
|
+
/**
|
|
375
|
+
* Fired when the `doOrder` command completes with completion status.
|
|
376
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | orderStatus Field}
|
|
377
|
+
*/
|
|
378
|
+
onOrderStatusObject?: EventHandlerOrderStatus;
|
|
379
|
+
/**
|
|
380
|
+
* Fired when order items are processed (child nodes of orderStatus).
|
|
381
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | doOrder Command}
|
|
382
|
+
*/
|
|
383
|
+
onOrderItemsStatusObject?: EventHandlerOrderItemsStatus;
|
|
384
|
+
/**
|
|
385
|
+
* Fired when purchases are retrieved from `getPurchases` or `getAllPurchases` commands
|
|
386
|
+
* (completion status).
|
|
387
|
+
*
|
|
388
|
+
* Receives the command's status, not the purchase data: the backing field
|
|
389
|
+
* `purchasesStatusObject` is an `assocarray` holding the request node's own fields, and is
|
|
390
|
+
* `{}` when the request failed. The purchases themselves arrive on
|
|
391
|
+
* {@link IChannelStoreProps.onPurchasesObject}.
|
|
392
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | purchases Field}
|
|
393
|
+
*/
|
|
394
|
+
onPurchasesStatusObject?: EventHandlerPurchasesStatus;
|
|
395
|
+
/**
|
|
396
|
+
* Fired when purchase data is available from `getPurchases` or `getAllPurchases` commands.
|
|
397
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | purchases Field}
|
|
398
|
+
*/
|
|
399
|
+
onPurchasesObject?: EventHandlerPurchases;
|
|
400
|
+
/**
|
|
401
|
+
* Specifies the product to be ordered from a TVOD app.
|
|
402
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | requestPartnerOrder Field}
|
|
403
|
+
*/
|
|
404
|
+
requestPartnerOrderObject?: IPartnerOrder;
|
|
405
|
+
/**
|
|
406
|
+
* Fired when requesting a partner order completes.
|
|
407
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | requestPartnerOrderStatus Field}
|
|
408
|
+
*/
|
|
409
|
+
onRequestPartnerOrderStatusObject?: EventHandlerPartnerOrder;
|
|
410
|
+
/**
|
|
411
|
+
* Confirms the product being ordered from a TVOD app.
|
|
412
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | confirmPartnerOrder Field}
|
|
413
|
+
*/
|
|
414
|
+
confirmPartnerOrderObject?: IPartnerOrder;
|
|
415
|
+
/**
|
|
416
|
+
* Fired when confirming a partner order completes.
|
|
417
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | confirmPartnerOrderStatus Field}
|
|
418
|
+
*/
|
|
419
|
+
onConfirmPartnerOrderStatusObject?: EventHandlerPartnerOrder;
|
|
420
|
+
/**
|
|
421
|
+
* Fired when a device attestation token is generated via `getDeviceAttestationToken`.
|
|
422
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore | getDeviceAttestationToken Command}
|
|
423
|
+
*/
|
|
424
|
+
onDeviceAttestationTokenObject?: EventHandlerDeviceAttestationToken;
|
|
425
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
// TODO: This component uses a custom XML extension (channelStoreExt) rather than a
|
|
5
|
+
// native SG node, so it lives in src/ alongside other custom components (like modal-dialog)
|
|
6
|
+
// instead of in sg-nodes/.
|
|
7
|
+
|
|
8
|
+
import type { IChannelStoreProps } from "./channel-store.props";
|
|
9
|
+
import { retainAsset } from "./retain-asset";
|
|
10
|
+
import { Component } from "./sg-nodes/types";
|
|
11
|
+
import ChannelStoreXML from "./xml-components/channel-store/channelStoreExt.xml";
|
|
12
|
+
export type { IChannelStoreRequestStatusEvent } from "./channel-store.props";
|
|
13
|
+
|
|
14
|
+
export type {
|
|
15
|
+
ChannelStoreCommand,
|
|
16
|
+
ChannelStoreUserDataContext,
|
|
17
|
+
IChannelStoreRequest,
|
|
18
|
+
IUserData,
|
|
19
|
+
EventHandlerUserData,
|
|
20
|
+
IUserRegionData,
|
|
21
|
+
EventHandlerUserRegionData,
|
|
22
|
+
IRequestedUserDataInfo,
|
|
23
|
+
IStoreChannelCredDataStatus,
|
|
24
|
+
EventHandlerStoreChannelCredDataStatus,
|
|
25
|
+
IChannelCred,
|
|
26
|
+
EventHandlerChannelCred,
|
|
27
|
+
IProduct,
|
|
28
|
+
EventHandlerCatalog,
|
|
29
|
+
IOrderItem,
|
|
30
|
+
IOrderStatus,
|
|
31
|
+
EventHandlerOrderStatus,
|
|
32
|
+
EventHandlerOrderItemsStatus,
|
|
33
|
+
IPurchasesStatus,
|
|
34
|
+
EventHandlerPurchasesStatus,
|
|
35
|
+
IPurchase,
|
|
36
|
+
EventHandlerPurchases,
|
|
37
|
+
IPartnerOrder,
|
|
38
|
+
EventHandlerPartnerOrder,
|
|
39
|
+
IDeviceAttestationToken,
|
|
40
|
+
EventHandlerDeviceAttestationToken,
|
|
41
|
+
IChannelStoreProps
|
|
42
|
+
} from "./channel-store.props";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* ChannelStore provides an interface to the Roku Streaming Store for in-app purchases,
|
|
46
|
+
* user authentication, subscription management, and credential storage.
|
|
47
|
+
*
|
|
48
|
+
* Usage follows a command pattern: set required data fields, observe the result event,
|
|
49
|
+
* then set `command` to trigger execution. Results arrive asynchronously via event handlers.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* // Retrieve user data for sign-in
|
|
54
|
+
* <ChannelStore
|
|
55
|
+
* requestedUserData="email,firstname,lastname"
|
|
56
|
+
* command="getUserData"
|
|
57
|
+
* onUserDataObject={(data) => console.log("User data:", data)}
|
|
58
|
+
* />
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```tsx
|
|
63
|
+
* // Perform an in-app purchase
|
|
64
|
+
* <ChannelStore
|
|
65
|
+
* orderObject={[{ code: "item1", qty: 1 }]}
|
|
66
|
+
* command="doOrder"
|
|
67
|
+
* onOrderStatusObject={(status) => console.log("Order status:", status)}
|
|
68
|
+
* />
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @see {@link https://developer.roku.com/dev/docs/channelstore}
|
|
72
|
+
*/
|
|
73
|
+
export const ChannelStore: Component<IChannelStoreProps> = props => {
|
|
74
|
+
retainAsset(ChannelStoreXML);
|
|
75
|
+
return <channelStoreExt {...props}></channelStoreExt>;
|
|
76
|
+
};
|