@roku-sdk/components 2.18.0 → 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/package.json +10 -9
- package/src/__tests__/effect-gradient-validation.test.tsx +133 -0
- package/src/sg-nodes/__tests__/imperative-methods.test.ts +8 -0
- package/src/sg-nodes/content-node.props.ts +1 -1
- package/src/sg-nodes/content-node.tsx +1 -1
- package/src/sg-nodes/effect.props.ts +5 -2
- package/src/sg-nodes/effect.tsx +132 -4
- package/src/sg-nodes/font.tsx +5 -5
- package/src/sg-nodes/list-panel.props.ts +5 -6
- package/src/sg-nodes/list-panel.tsx +2 -2
- package/src/sg-nodes/markup-list.props.ts +1 -3
- package/src/sg-nodes/panel-set.props.ts +1 -1
- package/src/sg-nodes/poster.tsx +1 -1
- package/src/sg-nodes/standard-keyboard-dialog.tsx +1 -2
- package/src/sg-nodes/standard-message-dialog.tsx +1 -2
- package/src/sg-nodes/standard-pinpad-dialog.tsx +1 -3
- package/src/sg-nodes/standard-progress-dialog.tsx +2 -5
- package/src/sg-nodes/time-grid.props.ts +3 -3
- package/src/sg-nodes/video.props.ts +69 -0
- package/src/sg-nodes/video.tsx +2 -0
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roku-sdk/components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
4
4
|
"description": "Roku SceneGraph Solid components",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"src/**/*.brs",
|
|
7
7
|
"src/sg-nodes/animation.tsx",
|
|
8
8
|
"src/sg-nodes/audio.tsx",
|
|
9
|
+
"src/sg-nodes/effect.tsx",
|
|
9
10
|
"src/sg-nodes/sound-effect.tsx",
|
|
10
11
|
"src/sg-nodes/video.tsx"
|
|
11
12
|
],
|
|
@@ -47,20 +48,20 @@
|
|
|
47
48
|
"node": ">=24.0.0"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"@roku-sdk/common": "2.
|
|
51
|
-
"@roku-sdk/
|
|
52
|
-
"@roku-sdk/
|
|
53
|
-
"@roku-sdk/
|
|
54
|
-
"@roku-sdk/
|
|
51
|
+
"@roku-sdk/common": "2.19.0",
|
|
52
|
+
"@roku-sdk/navigation": "2.19.0",
|
|
53
|
+
"@roku-sdk/runtime": "2.19.0",
|
|
54
|
+
"@roku-sdk/interfaces": "2.19.0",
|
|
55
|
+
"@roku-sdk/solid": "2.19.0"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@jest/globals": "^30.4.1",
|
|
58
59
|
"@types/jest": "^30.0.0",
|
|
59
60
|
"jest": "^30.4.1",
|
|
60
61
|
"jest-transform-stub": "^2.0.0",
|
|
61
|
-
"@roku-sdk/dev-tools": "2.
|
|
62
|
-
"@roku-sdk/testing-library": "2.
|
|
63
|
-
"@roku-sdk/compat": "2.
|
|
62
|
+
"@roku-sdk/dev-tools": "2.19.0",
|
|
63
|
+
"@roku-sdk/testing-library": "2.19.0",
|
|
64
|
+
"@roku-sdk/compat": "2.19.0"
|
|
64
65
|
},
|
|
65
66
|
"author": "Roku Inc",
|
|
66
67
|
"license": "MIT",
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Effect warns in __DEV__ when gradientColors / gradientStops violate the firmware
|
|
5
|
+
// constraints (2–8 colors, stops length and [0, 1] range) while gradientStyle is not
|
|
6
|
+
// "none". Quiet for valid gradients and for style "none" with empty arrays (effects-lab
|
|
7
|
+
// edit-demo). Each distinct problem is reported once per node; nothing without __DEV__.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import "@roku-sdk/compat";
|
|
11
|
+
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
13
|
+
import { createSignal, type JSX } from "@roku-sdk/solid";
|
|
14
|
+
|
|
15
|
+
import { Effect, installEffectGradientDevValidation } from "../sg-nodes/effect";
|
|
16
|
+
import { commitEarly, createAnchorNode, installSolidTestRenderer } from "../test-helpers/tween-test-setup";
|
|
17
|
+
|
|
18
|
+
async function flushMicrotasks(): Promise<void> {
|
|
19
|
+
await Promise.resolve();
|
|
20
|
+
await Promise.resolve();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("Effect gradient prop warning (dev only)", () => {
|
|
24
|
+
let render: ReturnType<typeof installSolidTestRenderer>;
|
|
25
|
+
let warn: jest.SpiedFunction<typeof console.warn>;
|
|
26
|
+
let realDev: boolean | undefined;
|
|
27
|
+
let dispose: (() => void) | undefined;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
realDev = (globalThis as { __DEV__?: boolean }).__DEV__;
|
|
31
|
+
(globalThis as { __DEV__?: boolean }).__DEV__ = true;
|
|
32
|
+
installEffectGradientDevValidation();
|
|
33
|
+
render = installSolidTestRenderer();
|
|
34
|
+
warn = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
dispose?.();
|
|
39
|
+
dispose = undefined;
|
|
40
|
+
(globalThis as { __DEV__?: boolean }).__DEV__ = realDev;
|
|
41
|
+
warn.mockRestore();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const mount = (element: JSX.Element): void => {
|
|
45
|
+
dispose = render(() => <group>{element}</group>, createAnchorNode());
|
|
46
|
+
commitEarly();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const effectWarnings = (): string[] =>
|
|
50
|
+
warn.mock.calls.map(c => String(c[0])).filter(message => message.startsWith("Effect"));
|
|
51
|
+
|
|
52
|
+
test("warns when gradientColors has fewer than 2 entries", async () => {
|
|
53
|
+
mount(<effect gradientStyle="linear" gradientColors={["0xFF0000FF"]} />);
|
|
54
|
+
await flushMicrotasks();
|
|
55
|
+
expect(effectWarnings()).toEqual([
|
|
56
|
+
'Effect: gradientColors must have 2–8 entries when gradientStyle is "linear" (got 1).'
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("warns when gradientColors has more than 8 entries", async () => {
|
|
61
|
+
const colors = Array.from({ length: 9 }, (_, i) => `0xFF00000${i}`);
|
|
62
|
+
mount(<effect gradientStyle="radial" gradientColors={colors} />);
|
|
63
|
+
await flushMicrotasks();
|
|
64
|
+
expect(effectWarnings()).toEqual([
|
|
65
|
+
'Effect: gradientColors must have 2–8 entries when gradientStyle is "radial" (got 9).'
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("warns when gradientStops length does not match gradientColors", async () => {
|
|
70
|
+
mount(
|
|
71
|
+
<effect gradientStyle="linear" gradientColors={["0xFF0000FF", "0x0000FFFF"]} gradientStops={[0, 0.5, 1]} />
|
|
72
|
+
);
|
|
73
|
+
await flushMicrotasks();
|
|
74
|
+
expect(effectWarnings()).toEqual(["Effect: gradientStops length (3) must equal gradientColors length (2)."]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("warns when a gradientStops entry is outside [0, 1]", async () => {
|
|
78
|
+
mount(<effect gradientStyle="linear" gradientColors={["0xFF0000FF", "0x0000FFFF"]} gradientStops={[0, 1.5]} />);
|
|
79
|
+
await flushMicrotasks();
|
|
80
|
+
expect(effectWarnings()).toEqual(["Effect: gradientStops entries must be in [0, 1] (got 1.5 at index 1)."]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("includes id in the warning when the effect has one", async () => {
|
|
84
|
+
mount(<effect id="hero-fill" gradientStyle="linear" gradientColors={["0xFF0000FF"]} />);
|
|
85
|
+
await flushMicrotasks();
|
|
86
|
+
expect(effectWarnings()).toEqual([
|
|
87
|
+
'Effect[id=hero-fill]: gradientColors must have 2–8 entries when gradientStyle is "linear" (got 1).'
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("warns through the PascalCase Effect wrapper", async () => {
|
|
92
|
+
mount(<Effect gradientStyle="linear" gradientColors={["0xFF0000FF"]} />);
|
|
93
|
+
await flushMicrotasks();
|
|
94
|
+
expect(effectWarnings()).toEqual([
|
|
95
|
+
'Effect: gradientColors must have 2–8 entries when gradientStyle is "linear" (got 1).'
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("is silent for a valid gradient", async () => {
|
|
100
|
+
mount(<effect gradientStyle="linear" gradientColors={["0xFF0000FF", "0x0000FFFF"]} gradientStops={[0, 1]} />);
|
|
101
|
+
await flushMicrotasks();
|
|
102
|
+
expect(effectWarnings()).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("is silent for gradientStyle none with empty colors and stops (effects-lab edit-demo)", async () => {
|
|
106
|
+
mount(<effect gradientStyle="none" gradientColors={[]} gradientStops={[]} />);
|
|
107
|
+
await flushMicrotasks();
|
|
108
|
+
expect(effectWarnings()).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("reports each distinct problem once per node across updates", async () => {
|
|
112
|
+
const [colors, setColors] = createSignal<string[]>(["0xFF0000FF"]);
|
|
113
|
+
mount(<effect gradientStyle="linear" gradientColors={colors()} />);
|
|
114
|
+
await flushMicrotasks();
|
|
115
|
+
setColors(["0xFF0000FF"]);
|
|
116
|
+
commitEarly();
|
|
117
|
+
await flushMicrotasks();
|
|
118
|
+
setColors(Array.from({ length: 9 }, (_, i) => `0xFF00000${i}`));
|
|
119
|
+
commitEarly();
|
|
120
|
+
await flushMicrotasks();
|
|
121
|
+
// Same key "colors:count" — still one warning even though the count changed.
|
|
122
|
+
expect(effectWarnings()).toEqual([
|
|
123
|
+
'Effect: gradientColors must have 2–8 entries when gradientStyle is "linear" (got 1).'
|
|
124
|
+
]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("is silent without __DEV__", async () => {
|
|
128
|
+
(globalThis as { __DEV__?: boolean }).__DEV__ = false;
|
|
129
|
+
mount(<effect gradientStyle="linear" gradientColors={["0xFF0000FF"]} />);
|
|
130
|
+
await flushMicrotasks();
|
|
131
|
+
expect(effectWarnings()).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -264,5 +264,13 @@ describe("imperative methods", () => {
|
|
|
264
264
|
const unlisted = registrars.filter(file => !patterns.some(pattern => pattern.test(file)));
|
|
265
265
|
expect(unlisted).toEqual([]);
|
|
266
266
|
});
|
|
267
|
+
|
|
268
|
+
it("lists effect.tsx in sideEffects so the gradient dev observer is not tree-shaken", () => {
|
|
269
|
+
const { sideEffects } = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")) as {
|
|
270
|
+
sideEffects: string[];
|
|
271
|
+
};
|
|
272
|
+
const patterns = sideEffects.map(globToRegExp);
|
|
273
|
+
expect(patterns.some(pattern => pattern.test("src/sg-nodes/effect.tsx"))).toBe(true);
|
|
274
|
+
});
|
|
267
275
|
});
|
|
268
276
|
});
|
|
@@ -1303,7 +1303,7 @@ export interface IVideoContentNodeProps extends IContentNodeProps {
|
|
|
1303
1303
|
*
|
|
1304
1304
|
* Valid values: `"mp4"`, `"hls"`, `"ism"`, `"dash"`, `"mkv"`.
|
|
1305
1305
|
*
|
|
1306
|
-
* @remarks The RSG SDK does not currently wire this through for
|
|
1306
|
+
* @remarks The RSG 2.0 SDK does not currently wire this through for
|
|
1307
1307
|
* `VideoContentNode`; omit it and rely on URL / player behavior until support
|
|
1308
1308
|
* lands.
|
|
1309
1309
|
*/
|
|
@@ -129,7 +129,7 @@ export const ContentNode: Component<IContentNodeProps> = props => {
|
|
|
129
129
|
* content with stream URL and format. Typically used as a child of a Video node.
|
|
130
130
|
*
|
|
131
131
|
* @remarks Assigning {@link IVideoContentNodeProps.streamformat} is not supported
|
|
132
|
-
* by the RSG SDK yet; use `url` (and other forwarded fields) only.
|
|
132
|
+
* by the RSG 2.0 SDK yet; use `url` (and other forwarded fields) only.
|
|
133
133
|
*
|
|
134
134
|
* @example
|
|
135
135
|
* ```tsx
|
|
@@ -39,7 +39,7 @@ export interface IEffectProps extends INodeProps {
|
|
|
39
39
|
* Per-corner border radius in pixels.
|
|
40
40
|
* - A single number applies a uniform radius to all corners.
|
|
41
41
|
* - A four-element tuple sets each corner independently: `[topLeft, topRight, bottomRight, bottomLeft]`.
|
|
42
|
-
* The tuple is one Vector4 field, so a
|
|
42
|
+
* The tuple is one Vector4 field, so a FloatFieldInterpolator cannot animate it.
|
|
43
43
|
* @default 0
|
|
44
44
|
*/
|
|
45
45
|
borderRadius?: number | [number, number, number, number];
|
|
@@ -72,12 +72,15 @@ export interface IEffectProps extends INodeProps {
|
|
|
72
72
|
/**
|
|
73
73
|
* Ordered list of RGBA color stops for the gradient (e.g. `["0xFF0000FF", "0x0000FFFF"]`).
|
|
74
74
|
* At least two colors are required for the gradient to render. Maximum of 8 colors.
|
|
75
|
+
* Outside that range the gradient does not render; in `__DEV__` builds the SDK warns once
|
|
76
|
+
* when `gradientStyle` is not `"none"`.
|
|
75
77
|
*/
|
|
76
78
|
gradientColors?: string[];
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
81
|
* Normalized stop positions `[0, 1]` corresponding to each entry in `gradientColors`.
|
|
80
|
-
* If omitted, colors are distributed evenly across the gradient.
|
|
82
|
+
* If omitted, colors are distributed evenly across the gradient. In `__DEV__` builds the SDK
|
|
83
|
+
* warns once on a length mismatch or an entry outside `[0, 1]` when `gradientStyle` is not `"none"`.
|
|
81
84
|
*/
|
|
82
85
|
gradientStops?: number[];
|
|
83
86
|
|
package/src/sg-nodes/effect.tsx
CHANGED
|
@@ -1,10 +1,140 @@
|
|
|
1
1
|
// Copyright (c) 2026 Roku, Inc.
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { DomNode, TmValue } from "@roku-sdk/interfaces";
|
|
5
|
+
import { registerSetPropertyObserver } from "@roku-sdk/solid";
|
|
6
|
+
|
|
7
|
+
import type { GradientStyle, IEffectProps } from "./effect.props";
|
|
5
8
|
import { Component } from "./types";
|
|
6
9
|
export type { GradientStyle, IEffectProps } from "./effect.props";
|
|
7
10
|
|
|
11
|
+
declare const __DEV__: boolean;
|
|
12
|
+
|
|
13
|
+
const TRACKED_PROPS: ReadonlySet<string> = new Set(["gradientStyle", "gradientColors", "gradientStops", "id"]);
|
|
14
|
+
|
|
15
|
+
interface EffectGradientState {
|
|
16
|
+
gradientStyle?: GradientStyle;
|
|
17
|
+
gradientColors?: string[];
|
|
18
|
+
gradientStops?: number[];
|
|
19
|
+
id?: string;
|
|
20
|
+
warned: Set<string>;
|
|
21
|
+
scheduled: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Dev-only: describe each way gradient props violate the firmware's silent constraints.
|
|
26
|
+
* Returns `[key, message]` pairs so a caller can report each distinct problem once.
|
|
27
|
+
*
|
|
28
|
+
* Quiet while `gradientStyle` is `"none"` or unset — authors may park empty colors/stops
|
|
29
|
+
* and turn the style on later (see apps/effects-lab edit-demo).
|
|
30
|
+
*/
|
|
31
|
+
function gradientPropProblems(
|
|
32
|
+
style: GradientStyle | undefined,
|
|
33
|
+
colors: readonly string[] | undefined,
|
|
34
|
+
stops: readonly number[] | undefined,
|
|
35
|
+
nodeLabel: string
|
|
36
|
+
): [key: string, message: string][] {
|
|
37
|
+
if (style === undefined || style === "none") {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const problems: [string, string][] = [];
|
|
42
|
+
const colorList = Array.isArray(colors) ? colors : [];
|
|
43
|
+
const colorCount = colorList.length;
|
|
44
|
+
|
|
45
|
+
if (colorCount < 2 || colorCount > 8) {
|
|
46
|
+
problems.push([
|
|
47
|
+
"colors:count",
|
|
48
|
+
`${nodeLabel}: gradientColors must have 2–8 entries when gradientStyle is "${style}" (got ${colorCount}).`
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (stops !== undefined) {
|
|
53
|
+
const stopList = Array.isArray(stops) ? stops : [];
|
|
54
|
+
if (stopList.length !== colorCount) {
|
|
55
|
+
problems.push([
|
|
56
|
+
"stops:length",
|
|
57
|
+
`${nodeLabel}: gradientStops length (${stopList.length}) must equal gradientColors length (${colorCount}).`
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
const outOfRange = stopList.findIndex(stop => !(typeof stop === "number" && stop >= 0 && stop <= 1));
|
|
61
|
+
if (outOfRange !== -1) {
|
|
62
|
+
problems.push([
|
|
63
|
+
"stops:range",
|
|
64
|
+
`${nodeLabel}: gradientStops entries must be in [0, 1] (got ${String(stopList[outOfRange])} at index ${outOfRange}).`
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return problems;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function nodeLabelFor(state: EffectGradientState): string {
|
|
73
|
+
return state.id !== undefined && state.id !== "" ? `Effect[id=${state.id}]` : "Effect";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let effectGradientObserverInstalled = false;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Installs the `__DEV__` setProperty observer that validates Effect gradient props for both
|
|
80
|
+
* `<effect>` and `<Effect>`. Safe to call more than once; no-ops when `__DEV__` is off.
|
|
81
|
+
* Tests call this after enabling `__DEV__` because Jest loads modules with `__DEV__` false.
|
|
82
|
+
*/
|
|
83
|
+
export function installEffectGradientDevValidation(): void {
|
|
84
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
85
|
+
if (effectGradientObserverInstalled) return;
|
|
86
|
+
effectGradientObserverInstalled = true;
|
|
87
|
+
|
|
88
|
+
const stateByNode = new WeakMap<DomNode, EffectGradientState>();
|
|
89
|
+
|
|
90
|
+
registerSetPropertyObserver((node: DomNode, name: string, value: TmValue): void => {
|
|
91
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
92
|
+
if (node.t !== "effect") return;
|
|
93
|
+
if (!TRACKED_PROPS.has(name)) return;
|
|
94
|
+
|
|
95
|
+
let state = stateByNode.get(node);
|
|
96
|
+
if (!state) {
|
|
97
|
+
state = { warned: new Set(), scheduled: false };
|
|
98
|
+
stateByNode.set(node, state);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
switch (name) {
|
|
102
|
+
case "gradientStyle":
|
|
103
|
+
state.gradientStyle = value as GradientStyle;
|
|
104
|
+
break;
|
|
105
|
+
case "gradientColors":
|
|
106
|
+
state.gradientColors = value as string[];
|
|
107
|
+
break;
|
|
108
|
+
case "gradientStops":
|
|
109
|
+
state.gradientStops = value as number[];
|
|
110
|
+
break;
|
|
111
|
+
case "id":
|
|
112
|
+
state.id = value as string;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (state.scheduled) return;
|
|
117
|
+
state.scheduled = true;
|
|
118
|
+
const pending = state;
|
|
119
|
+
queueMicrotask(() => {
|
|
120
|
+
pending.scheduled = false;
|
|
121
|
+
const label = nodeLabelFor(pending);
|
|
122
|
+
for (const [key, message] of gradientPropProblems(
|
|
123
|
+
pending.gradientStyle,
|
|
124
|
+
pending.gradientColors,
|
|
125
|
+
pending.gradientStops,
|
|
126
|
+
label
|
|
127
|
+
)) {
|
|
128
|
+
if (pending.warned.has(key)) continue;
|
|
129
|
+
pending.warned.add(key);
|
|
130
|
+
console.warn(message);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
installEffectGradientDevValidation();
|
|
137
|
+
|
|
8
138
|
/**
|
|
9
139
|
* Effect applies GPU shader-based rendering to a Rectangle or Poster.
|
|
10
140
|
*
|
|
@@ -12,9 +142,7 @@ export type { GradientStyle, IEffectProps } from "./effect.props";
|
|
|
12
142
|
* Poster to enable rounded corners, borders, and gradient fills. Set
|
|
13
143
|
* `shaderCompatible` on a Poster that takes one.
|
|
14
144
|
*
|
|
15
|
-
* Prefer the `<effect>` intrinsic for most uses. The
|
|
16
|
-
* passthrough (`<effect {...props} />`) and exists for consistency with the component
|
|
17
|
-
* library.
|
|
145
|
+
* Prefer the `<effect>` intrinsic for most uses. The Effect wrapper is a pure passthrough (`<effect {...props} />`) and exists for consistency with the component library.
|
|
18
146
|
*
|
|
19
147
|
* {@includeCode ../../../../examples/showcase/src/components/showcases/effect-showcase/shared-effect-showcase.tsx#docs:effect-shared}
|
|
20
148
|
*
|
package/src/sg-nodes/font.tsx
CHANGED
|
@@ -12,11 +12,11 @@ export type { IFontProps } from "./font.props";
|
|
|
12
12
|
* be specified using the `font:` prefix (e.g., `font:MediumBoldSystemFont`).
|
|
13
13
|
*
|
|
14
14
|
* **Components that support Font:**
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
15
|
+
* - Label / MultiStyleLabel: via `font` prop
|
|
16
|
+
* - ScrollableText: via `font` prop
|
|
17
|
+
* - ScrollingLabel: via `font` prop
|
|
18
|
+
* - LabelList: via `font` and `focusedFont` props
|
|
19
|
+
* - PosterGrid: via `caption1Font` and `caption2Font` props
|
|
20
20
|
*
|
|
21
21
|
* @see {@link https://developer.roku.com/dev/docs/font}
|
|
22
22
|
*
|
|
@@ -72,13 +72,12 @@ export interface IListPanelProps extends IPanelProps {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Properties for the
|
|
75
|
+
* Properties for the raw `<listPanel>` SceneGraph element.
|
|
76
76
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* @internal
|
|
77
|
+
* These are the {@link IListPanelProps} plus the `list` and `nextPanel` node fields, which the
|
|
78
|
+
* managed `ListPanel` component fills in from its children. They type the lower-case
|
|
79
|
+
* `listPanel` JSX intrinsic and the generated SceneGraph declarations. Prefer the managed
|
|
80
|
+
* `ListPanel` component; use the raw element only when you set `list` and `nextPanel` yourself.
|
|
82
81
|
*/
|
|
83
82
|
export interface IFullPanelProps extends IListPanelProps {
|
|
84
83
|
/**
|
|
@@ -16,7 +16,7 @@ export type { IListPanelProps, IFullPanelProps } from "./list-panel.props";
|
|
|
16
16
|
* - Children extending `ArrayGrid` (LabelList, MarkupList) are automatically assigned to `list`
|
|
17
17
|
* - Children extending `Panel` are automatically assigned to `nextPanel`
|
|
18
18
|
*
|
|
19
|
-
* Declare the list child
|
|
19
|
+
* Declare the list child before the panel child. The panel is matched to the row it was
|
|
20
20
|
* built for by consulting the list, so a panel declared first cannot be attributed to a row.
|
|
21
21
|
*
|
|
22
22
|
* While an asynchronously supplied `nextPanel` is still the previous row's, `ListPanel`
|
|
@@ -27,7 +27,7 @@ export type { IListPanelProps, IFullPanelProps } from "./list-panel.props";
|
|
|
27
27
|
* its associated next panel as JSX children rather than explicit property assignments.
|
|
28
28
|
*
|
|
29
29
|
* @see {@link https://developer.roku.com/dev/docs/listpanel | Roku ListPanel Documentation}
|
|
30
|
-
* @see **ListPanel, GridPanel Best Practices** at the internal RSG SDK documentation (knowledge-base/list-grid-panel-best-practice.md)
|
|
30
|
+
* @see **ListPanel, GridPanel Best Practices** at the internal RSG 2.0 SDK documentation (knowledge-base/list-grid-panel-best-practice.md)
|
|
31
31
|
*
|
|
32
32
|
* @example
|
|
33
33
|
* ```tsx
|
|
@@ -54,9 +54,7 @@ export interface IMarkupListProps extends IArrayGridProps {
|
|
|
54
54
|
/**
|
|
55
55
|
* Specifies the content for the list.
|
|
56
56
|
*
|
|
57
|
-
* Should be a single root
|
|
58
|
-
* If the data contains section markers (`CONTENTTYPE="SECTION"`), section
|
|
59
|
-
* dividers will be drawn between each section, optionally with an icon and/or label.
|
|
57
|
+
* Should be a single root ContentNode with one child ContentNode per item. If the data contains section markers (`CONTENTTYPE="SECTION"`), section dividers will be drawn between each section, optionally with an icon and/or label.
|
|
60
58
|
*
|
|
61
59
|
* **Important:** Place `content` before `jumpToItem` and `animateToItem` in the
|
|
62
60
|
* component. When content is updated dynamically, update `jumpToItem`/`animateToItem`
|
|
@@ -59,7 +59,7 @@ export interface IPanelSetProps extends IGroupProps {
|
|
|
59
59
|
* The value is `true` when the PanelSet panels are sliding right, back towards the original
|
|
60
60
|
* home position, as a result of a Left or Back key press. The value is `false` otherwise.
|
|
61
61
|
*
|
|
62
|
-
* @see **ListPanel, GridPanel Best Practices** at the internal RSG SDK documentation (knowledge-base/list-grid-panel-best-practice.md) for handling slidingBack prop
|
|
62
|
+
* @see **ListPanel, GridPanel Best Practices** at the internal RSG 2.0 SDK documentation (knowledge-base/list-grid-panel-best-practice.md) for handling slidingBack prop
|
|
63
63
|
*/
|
|
64
64
|
onIsGoingBack?: EventHandler<boolean>;
|
|
65
65
|
|
package/src/sg-nodes/poster.tsx
CHANGED
|
@@ -14,7 +14,7 @@ export type { PosterDisplayMode, LoadDisplayMode, PosterLoadStatus, BitmapMargin
|
|
|
14
14
|
* dimension information. See the Static Image Management documentation for details.
|
|
15
15
|
*
|
|
16
16
|
* **Attribute ordering pitfall:** When using `<poster>` as a JSX intrinsic,
|
|
17
|
-
* write event handlers (`onLoadStatus`, etc.)
|
|
17
|
+
* write event handlers (`onLoadStatus`, etc.) before `uri` and other props that trigger
|
|
18
18
|
* synchronous events on assignment. If `uri` is set first and the image is already cached,
|
|
19
19
|
* the load event fires before your handler is registered and is silently dropped.
|
|
20
20
|
* Writing handlers first is good practice regardless.
|
|
@@ -11,8 +11,7 @@ export type { IKeyboardDomain, IStandardKeyboardDialogProps } from "./standard-k
|
|
|
11
11
|
/**
|
|
12
12
|
* Displays a keyboard dialog for text and voice entry of alphanumeric characters and symbols.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* but includes voice entry functionality through its internal DynamicKeyboard node.
|
|
14
|
+
* StandardKeyboardDialog is a pre-built dialog similar to the legacy KeyboardDialog node, but includes voice entry functionality through its internal DynamicKeyboard node.
|
|
16
15
|
*
|
|
17
16
|
* The {@link IStandardKeyboardDialogProps.keyboardDomain | keyboardDomain} property controls
|
|
18
17
|
* how voice input is processed:
|
|
@@ -11,8 +11,7 @@ export type { IStandardBulletType, IStandardMessageDialogProps } from "./standar
|
|
|
11
11
|
/**
|
|
12
12
|
* Displays a message dialog with optional title, text blocks, bulleted list, and buttons.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* a convenient way to display informational messages with:
|
|
14
|
+
* StandardMessageDialog is a pre-built dialog similar to the legacy Dialog node. It provides a convenient way to display informational messages with:
|
|
16
15
|
* - One or more text blocks at the top ({@link IStandardMessageDialogProps.message | message})
|
|
17
16
|
* - An optional bulleted/numbered/lettered list ({@link IStandardMessageDialogProps.bulletText | bulletText})
|
|
18
17
|
* - One or more text blocks at the bottom ({@link IStandardMessageDialogProps.bottomMessage | bottomMessage})
|
|
@@ -11,9 +11,7 @@ export type { IStandardPinPadDialogProps } from "./standard-pinpad-dialog.props"
|
|
|
11
11
|
/**
|
|
12
12
|
* Displays a PIN pad dialog for text and voice entry of numeric PIN codes.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* but includes voice entry of numeric digits through its internal DynamicPinPad and
|
|
16
|
-
* VoiceTextEditBox nodes.
|
|
14
|
+
* StandardPinPadDialog is a pre-built dialog similar to the legacy PinDialog node, but includes voice entry of numeric digits through its internal DynamicPinPad and VoiceTextEditBox nodes.
|
|
17
15
|
*
|
|
18
16
|
* @example
|
|
19
17
|
* ```tsx
|
|
@@ -11,12 +11,9 @@ export type { IStandardProgressDialogProps } from "./standard-progress-dialog.pr
|
|
|
11
11
|
/**
|
|
12
12
|
* Displays a progress dialog with a spinning indicator and message.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* It displays a spinning progress indicator for tasks with an indeterminate duration.
|
|
14
|
+
* StandardProgressDialog is a pre-built dialog similar to the legacy ProgressDialog node. It displays a spinning progress indicator for tasks with an indeterminate duration.
|
|
16
15
|
*
|
|
17
|
-
* Note: Unlike other standard dialogs,
|
|
18
|
-
* displays a spinner with a message. Close the dialog programmatically by setting
|
|
19
|
-
* {@link IStandardProgressDialogProps.close | close} to `true` or by unmounting the component.
|
|
16
|
+
* Note: Unlike other standard dialogs, StandardProgressDialog has no buttons — it only displays a spinner with a message. Close the dialog programmatically by setting {@link IStandardProgressDialogProps.close | close} to `true` or by unmounting the component.
|
|
20
17
|
*
|
|
21
18
|
* @example
|
|
22
19
|
* ```tsx
|
|
@@ -604,9 +604,9 @@ export interface ITimeGridNowNextModeProps {
|
|
|
604
604
|
* and each cell within a row represents a program with its time slot.
|
|
605
605
|
*
|
|
606
606
|
* The content tree structure is:
|
|
607
|
-
* - Root
|
|
608
|
-
* - Channel
|
|
609
|
-
* - Program
|
|
607
|
+
* - Root ContentNode
|
|
608
|
+
* - Channel ContentNode per row (with `title`, `hdPosterUrl` for logo)
|
|
609
|
+
* - Program ContentNode per cell (with `title`, `description`, `playStart`, `playDuration`)
|
|
610
610
|
*
|
|
611
611
|
* Program nodes use `playStart` (epoch seconds) and `playDuration` (seconds)
|
|
612
612
|
* to determine their horizontal position and width in the grid.
|
|
@@ -363,6 +363,62 @@ export interface IVideoUiProps {
|
|
|
363
363
|
pivotNode?: JSX.Element;
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
/**
|
|
367
|
+
* One entry of the Video node `availableAudioTracks` field.
|
|
368
|
+
*
|
|
369
|
+
* Firmware always emits every key below; the TypeScript and BrightScript
|
|
370
|
+
* bridges both build each entry from the media player's audio track list.
|
|
371
|
+
*
|
|
372
|
+
* @see {@link https://developer.roku.com/dev/docs/video}
|
|
373
|
+
*/
|
|
374
|
+
export interface IVideoAudioTrack {
|
|
375
|
+
/**
|
|
376
|
+
* Language code as declared by the stream. Roku docs describe an ISO 639-2
|
|
377
|
+
* three-letter code (`"eng"`), but the value is passed through from the
|
|
378
|
+
* manifest, so two-letter codes such as `"en"` also occur.
|
|
379
|
+
*/
|
|
380
|
+
Language: string;
|
|
381
|
+
/** Descriptive name of the audio track. */
|
|
382
|
+
Name: string;
|
|
383
|
+
/**
|
|
384
|
+
* Human-readable audio format description from the demuxer, for example
|
|
385
|
+
* `"ISO/IEC 14496-3, Advanced Audio Coding, ADTS container"`. Not listed in
|
|
386
|
+
* the Roku docs; may be an empty string when the demuxer reports no format.
|
|
387
|
+
*/
|
|
388
|
+
Format: string;
|
|
389
|
+
/** Track identifier; assign it to `audioTrack` to select the track. */
|
|
390
|
+
Track: string;
|
|
391
|
+
/** HLS "public.accessibility.describes-video", or a DASH audio description. */
|
|
392
|
+
HasAccessibilityDescription: boolean;
|
|
393
|
+
/** DASH Enhanced Audio Intelligibility track. */
|
|
394
|
+
HasAccessibilityEAI: boolean;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* One entry of the Video node `availableSubtitleTracks` field.
|
|
399
|
+
*
|
|
400
|
+
* Firmware always emits every key below.
|
|
401
|
+
*
|
|
402
|
+
* @see {@link https://developer.roku.com/dev/docs/video}
|
|
403
|
+
*/
|
|
404
|
+
export interface IVideoSubtitleTrack {
|
|
405
|
+
/** Descriptive name of the subtitle track. */
|
|
406
|
+
Description: string;
|
|
407
|
+
/**
|
|
408
|
+
* Language code as declared by the stream. Roku docs describe an ISO 639-2
|
|
409
|
+
* three-letter code (`"eng"`), but two-letter codes such as `"en"` also occur.
|
|
410
|
+
*/
|
|
411
|
+
Language: string;
|
|
412
|
+
/** Track identifier; assign it to `subtitleTrack` to select the track. */
|
|
413
|
+
TrackName: string;
|
|
414
|
+
/** HLS "public.accessibility.describes-music-and-sound". */
|
|
415
|
+
HasAccessibilityDescription: boolean;
|
|
416
|
+
/** HLS "public.accessibility.transcribes-spoken-dialog", or DASH captions. */
|
|
417
|
+
HasAccessibilityCaption: boolean;
|
|
418
|
+
/** DASH sign-language interpretation. */
|
|
419
|
+
HasAccessibilitySign: boolean;
|
|
420
|
+
}
|
|
421
|
+
|
|
366
422
|
/**
|
|
367
423
|
* Closed caption and subtitle styling fields.
|
|
368
424
|
*
|
|
@@ -375,6 +431,10 @@ export interface IVideoCaptionProps {
|
|
|
375
431
|
suppressCaptions?: boolean;
|
|
376
432
|
/** Selected subtitle track identifier (read/write). */
|
|
377
433
|
subtitleTrack?: string;
|
|
434
|
+
/** Fired with the subtitle tracks found in the stream and content metadata (read-only on the node). */
|
|
435
|
+
onAvailableSubtitleTracks?: EventHandler<IVideoSubtitleTrack[]>;
|
|
436
|
+
/** Fired when the playing subtitle track changes; carries the track identifier (`TrackName`). */
|
|
437
|
+
onCurrentSubtitleTrack?: EventHandler<string>;
|
|
378
438
|
/**
|
|
379
439
|
* Caption styling keys (Text/Font, Text/Size, Background/Color, etc.) per Roku docs.
|
|
380
440
|
*/
|
|
@@ -391,6 +451,15 @@ export interface IVideoAudioProps {
|
|
|
391
451
|
mute?: boolean;
|
|
392
452
|
/** Selected audio track identifier (prefer platform selection when possible). */
|
|
393
453
|
audioTrack?: string;
|
|
454
|
+
/**
|
|
455
|
+
* Fired with the audio tracks found in the stream (read-only on the node).
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* <Video onAvailableAudioTracks={tracks => setTracks(tracks)} onCurrentAudioTrack={id => setActive(id)} />
|
|
459
|
+
*/
|
|
460
|
+
onAvailableAudioTracks?: EventHandler<IVideoAudioTrack[]>;
|
|
461
|
+
/** Fired when the playing audio track changes; carries the track identifier (`Track`). */
|
|
462
|
+
onCurrentAudioTrack?: EventHandler<string>;
|
|
394
463
|
/**
|
|
395
464
|
* When true, continues video on audio track switch (HLS only; set before any control command).
|
|
396
465
|
*/
|