@lightningtv/solid 3.0.0-8 → 3.0.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/LICENSE +1 -1
- package/README.md +6 -0
- package/dist/jsx-runtime.d.ts +14 -0
- package/dist/src/activeElement.d.ts +1 -1
- package/dist/src/core/animation.d.ts +35 -0
- package/dist/src/core/animation.js +119 -0
- package/dist/src/core/animation.js.map +1 -0
- package/dist/src/core/config.d.ts +49 -0
- package/dist/src/core/config.js +33 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/domRenderer.d.ts +115 -0
- package/dist/src/core/domRenderer.js +1152 -0
- package/dist/src/core/domRenderer.js.map +1 -0
- package/dist/src/core/elementNode.d.ts +463 -0
- package/dist/src/core/elementNode.js +833 -0
- package/dist/src/core/elementNode.js.map +1 -0
- package/dist/src/core/flex.d.ts +2 -0
- package/dist/src/core/flex.js +243 -0
- package/dist/src/core/flex.js.map +1 -0
- package/dist/src/core/focusKeyTypes.d.ts +42 -0
- package/dist/src/core/focusKeyTypes.js +2 -0
- package/dist/src/core/focusKeyTypes.js.map +1 -0
- package/dist/src/core/focusManager.d.ts +13 -0
- package/dist/src/core/focusManager.js +276 -0
- package/dist/src/core/focusManager.js.map +1 -0
- package/dist/src/core/index.d.ts +12 -0
- package/dist/src/core/index.js +12 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/core/intrinsicTypes.d.ts +90 -0
- package/dist/src/core/intrinsicTypes.js +2 -0
- package/dist/src/core/intrinsicTypes.js.map +1 -0
- package/dist/src/core/lightningInit.d.ts +89 -0
- package/dist/src/core/lightningInit.js +26 -0
- package/dist/src/core/lightningInit.js.map +1 -0
- package/dist/src/core/nodeTypes.d.ts +6 -0
- package/dist/src/core/nodeTypes.js +6 -0
- package/dist/src/core/nodeTypes.js.map +1 -0
- package/dist/src/core/shaders.d.ts +51 -0
- package/dist/src/core/shaders.js +446 -0
- package/dist/src/core/shaders.js.map +1 -0
- package/dist/src/core/states.d.ts +12 -0
- package/dist/src/core/states.js +84 -0
- package/dist/src/core/states.js.map +1 -0
- package/dist/src/core/utils.d.ts +39 -0
- package/dist/src/core/utils.js +164 -0
- package/dist/src/core/utils.js.map +1 -0
- package/dist/src/devtools/index.d.ts +1 -1
- package/dist/src/devtools/index.js +1 -1
- package/dist/src/devtools/index.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/primitives/Column.jsx +9 -10
- package/dist/src/primitives/Column.jsx.map +1 -1
- package/dist/src/primitives/FPSCounter.jsx +15 -2
- package/dist/src/primitives/FPSCounter.jsx.map +1 -1
- package/dist/src/primitives/Grid.d.ts +2 -2
- package/dist/src/primitives/Grid.jsx +27 -17
- package/dist/src/primitives/Grid.jsx.map +1 -1
- package/dist/src/primitives/Image.d.ts +8 -0
- package/dist/src/primitives/Image.jsx +24 -0
- package/dist/src/primitives/Image.jsx.map +1 -0
- package/dist/src/primitives/KeepAlive.d.ts +30 -0
- package/dist/src/primitives/KeepAlive.jsx +77 -0
- package/dist/src/primitives/KeepAlive.jsx.map +1 -0
- package/dist/src/primitives/Lazy.d.ts +8 -7
- package/dist/src/primitives/Lazy.jsx +52 -20
- package/dist/src/primitives/Lazy.jsx.map +1 -1
- package/dist/src/primitives/Marquee.jsx +2 -2
- package/dist/src/primitives/Marquee.jsx.map +1 -1
- package/dist/src/primitives/Preserve.d.ts +4 -0
- package/dist/src/primitives/Preserve.jsx +11 -0
- package/dist/src/primitives/Preserve.jsx.map +1 -0
- package/dist/src/primitives/Row.jsx +9 -10
- package/dist/src/primitives/Row.jsx.map +1 -1
- package/dist/src/primitives/Suspense.d.ts +22 -0
- package/dist/src/primitives/Suspense.jsx +33 -0
- package/dist/src/primitives/Suspense.jsx.map +1 -0
- package/dist/src/primitives/Virtual.d.ts +18 -0
- package/dist/src/primitives/Virtual.jsx +443 -0
- package/dist/src/primitives/Virtual.jsx.map +1 -0
- package/dist/src/primitives/VirtualGrid.d.ts +13 -0
- package/dist/src/primitives/VirtualGrid.jsx +160 -0
- package/dist/src/primitives/VirtualGrid.jsx.map +1 -0
- package/dist/src/primitives/Visible.d.ts +0 -1
- package/dist/src/primitives/Visible.jsx +1 -1
- package/dist/src/primitives/Visible.jsx.map +1 -1
- package/dist/src/primitives/announcer/announcer.d.ts +1 -0
- package/dist/src/primitives/announcer/announcer.js +4 -4
- package/dist/src/primitives/announcer/announcer.js.map +1 -1
- package/dist/src/primitives/announcer/speech.d.ts +2 -2
- package/dist/src/primitives/announcer/speech.js +147 -27
- package/dist/src/primitives/announcer/speech.js.map +1 -1
- package/dist/src/primitives/createFocusStack.d.ts +4 -4
- package/dist/src/primitives/createFocusStack.jsx +15 -6
- package/dist/src/primitives/createFocusStack.jsx.map +1 -1
- package/dist/src/primitives/createTag.d.ts +8 -0
- package/dist/src/primitives/createTag.jsx +20 -0
- package/dist/src/primitives/createTag.jsx.map +1 -0
- package/dist/src/primitives/index.d.ts +13 -4
- package/dist/src/primitives/index.js +12 -3
- package/dist/src/primitives/index.js.map +1 -1
- package/dist/src/primitives/types.d.ts +5 -2
- package/dist/src/primitives/useFocusManager.d.ts +2 -2
- package/dist/src/primitives/useFocusManager.js +2 -2
- package/dist/src/primitives/useFocusManager.js.map +1 -1
- package/dist/src/primitives/useMouse.d.ts +18 -2
- package/dist/src/primitives/useMouse.js +171 -47
- package/dist/src/primitives/useMouse.js.map +1 -1
- package/dist/src/primitives/utils/createBlurredImage.d.ts +56 -0
- package/dist/src/primitives/utils/createBlurredImage.js +223 -0
- package/dist/src/primitives/utils/createBlurredImage.js.map +1 -0
- package/dist/src/primitives/utils/createSpriteMap.d.ts +2 -2
- package/dist/src/primitives/utils/createSpriteMap.js +3 -3
- package/dist/src/primitives/utils/createSpriteMap.js.map +1 -1
- package/dist/src/primitives/utils/handleNavigation.d.ts +79 -5
- package/dist/src/primitives/utils/handleNavigation.js +241 -69
- package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
- package/dist/src/primitives/utils/withScrolling.d.ts +14 -2
- package/dist/src/primitives/utils/withScrolling.js +66 -7
- package/dist/src/primitives/utils/withScrolling.js.map +1 -1
- package/dist/src/render.d.ts +5 -4
- package/dist/src/render.js +5 -1
- package/dist/src/render.js.map +1 -1
- package/dist/src/shaders/Rounded.d.ts +7 -0
- package/dist/src/shaders/Rounded.js +88 -0
- package/dist/src/shaders/Rounded.js.map +1 -0
- package/dist/src/shaders/RoundedWithBorder.d.ts +3 -0
- package/dist/src/shaders/RoundedWithBorder.js +217 -0
- package/dist/src/shaders/RoundedWithBorder.js.map +1 -0
- package/dist/src/shaders/index.d.ts +4 -0
- package/dist/src/shaders/index.js +5 -0
- package/dist/src/shaders/index.js.map +1 -0
- package/dist/src/shaders/templates/RoundedTemplate.d.ts +12 -0
- package/dist/src/shaders/templates/RoundedTemplate.js +48 -0
- package/dist/src/shaders/templates/RoundedTemplate.js.map +1 -0
- package/dist/src/shaders/templates/RoundedWithBorderTemplate.d.ts +20 -0
- package/dist/src/shaders/templates/RoundedWithBorderTemplate.js +93 -0
- package/dist/src/shaders/templates/RoundedWithBorderTemplate.js.map +1 -0
- package/dist/src/shaders/utils.d.ts +3 -0
- package/dist/src/shaders/utils.js +31 -0
- package/dist/src/shaders/utils.js.map +1 -0
- package/dist/src/solidOpts.d.ts +1 -7
- package/dist/src/solidOpts.js +9 -1
- package/dist/src/solidOpts.js.map +1 -1
- package/dist/src/types.d.ts +1 -13
- package/dist/src/utils.d.ts +3 -1
- package/dist/src/utils.js +9 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jsx-runtime.d.ts +2 -1
- package/package.json +28 -16
- package/src/activeElement.ts +1 -1
- package/src/core/animation.ts +185 -0
- package/src/core/config.ts +89 -0
- package/src/core/domRenderer.ts +1300 -0
- package/src/core/elementNode.ts +1458 -0
- package/src/core/flex.ts +284 -0
- package/src/core/focusKeyTypes.ts +90 -0
- package/src/core/focusManager.ts +381 -0
- package/src/core/index.ts +13 -0
- package/src/core/intrinsicTypes.ts +199 -0
- package/src/core/lightningInit.ts +147 -0
- package/src/core/nodeTypes.ts +6 -0
- package/src/core/shaders.ts +567 -0
- package/src/core/states.ts +91 -0
- package/src/core/utils.ts +222 -0
- package/src/devtools/index.ts +1 -1
- package/src/index.ts +3 -3
- package/src/primitives/Column.tsx +10 -12
- package/src/primitives/FPSCounter.tsx +16 -2
- package/src/primitives/Grid.tsx +32 -22
- package/src/primitives/Image.tsx +36 -0
- package/src/primitives/KeepAlive.tsx +124 -0
- package/src/primitives/Lazy.tsx +66 -37
- package/src/primitives/{marquee.tsx → Marquee.tsx} +1 -1
- package/src/primitives/Preserve.tsx +18 -0
- package/src/primitives/Row.tsx +13 -14
- package/src/primitives/Suspense.tsx +39 -0
- package/src/primitives/Virtual.tsx +486 -0
- package/src/primitives/VirtualGrid.tsx +220 -0
- package/src/primitives/Visible.tsx +1 -2
- package/src/primitives/announcer/announcer.ts +10 -4
- package/src/primitives/announcer/speech.ts +170 -26
- package/src/primitives/createFocusStack.tsx +18 -7
- package/src/primitives/createTag.tsx +33 -0
- package/src/primitives/index.ts +13 -4
- package/src/primitives/types.ts +12 -2
- package/src/primitives/useFocusManager.ts +3 -3
- package/src/primitives/useHold.ts +69 -0
- package/src/primitives/useMouse.ts +306 -67
- package/src/primitives/utils/createBlurredImage.ts +366 -0
- package/src/primitives/utils/createSpriteMap.ts +8 -6
- package/src/primitives/utils/handleNavigation.ts +300 -84
- package/src/primitives/utils/withScrolling.ts +91 -18
- package/src/render.ts +7 -3
- package/src/shaders/Rounded.ts +100 -0
- package/src/shaders/RoundedWithBorder.ts +245 -0
- package/src/shaders/index.ts +4 -0
- package/src/shaders/templates/RoundedTemplate.ts +57 -0
- package/src/shaders/templates/RoundedWithBorderTemplate.ts +110 -0
- package/src/shaders/utils.ts +44 -0
- package/src/solidOpts.ts +9 -7
- package/src/types.ts +1 -15
- package/src/utils.ts +11 -1
- package/dist/src/client.d.ts +0 -1
- package/dist/src/client.js +0 -2
- package/dist/src/client.js.map +0 -1
- package/dist/src/core.d.ts +0 -1
- package/dist/src/core.js +0 -3
- package/dist/src/core.js.map +0 -1
- package/dist/src/jsx-runtime.d.ts +0 -10
- package/dist/src/jsx-runtime.js +0 -2
- package/dist/src/jsx-runtime.js.map +0 -1
- package/dist/src/primitives/Infinite.d.ts +0 -15
- package/dist/src/primitives/Infinite.jsx +0 -59
- package/dist/src/primitives/Infinite.jsx.map +0 -1
- package/dist/src/primitives/LazyUp.d.ts +0 -11
- package/dist/src/primitives/LazyUp.jsx +0 -38
- package/dist/src/primitives/LazyUp.jsx.map +0 -1
- package/dist/src/primitives/sprite.d.ts +0 -9
- package/dist/src/primitives/sprite.js +0 -18
- package/dist/src/primitives/sprite.js.map +0 -1
- package/dist/src/primitives/utils/createFocusStack.d.ts +0 -24
- package/dist/src/primitives/utils/createFocusStack.js +0 -59
- package/dist/src/primitives/utils/createFocusStack.js.map +0 -1
- package/dist/src/primitives/utils/scrollToIndex.d.ts +0 -2
- package/dist/src/primitives/utils/scrollToIndex.js +0 -33
- package/dist/src/primitives/utils/scrollToIndex.js.map +0 -1
- package/dist/src/renderClient.d.ts +0 -21
- package/dist/src/renderClient.js +0 -64
- package/dist/src/renderClient.js.map +0 -1
|
@@ -13,7 +13,6 @@ import { ElementNode } from '@lightningtv/solid';
|
|
|
13
13
|
export function Visible<T>(props: {
|
|
14
14
|
when: T | undefined | null | false;
|
|
15
15
|
keyed?: boolean;
|
|
16
|
-
fallback?: JSX.Element;
|
|
17
16
|
children: JSX.Element;
|
|
18
17
|
}): JSX.Element {
|
|
19
18
|
let child: ChildrenReturn | undefined;
|
|
@@ -55,6 +54,6 @@ export function Visible<T>(props: {
|
|
|
55
54
|
}
|
|
56
55
|
});
|
|
57
56
|
|
|
58
|
-
return c ? child :
|
|
57
|
+
return c || child ? child : null;
|
|
59
58
|
}) as unknown as JSX.Element;
|
|
60
59
|
};
|
|
@@ -109,18 +109,24 @@ function onFocusChangeCore(focusPath: ElementNode[] = []) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function textToSpeech(
|
|
112
|
+
function textToSpeech(
|
|
113
|
+
toSpeak: SpeechType,
|
|
114
|
+
aria: boolean,
|
|
115
|
+
lang: string,
|
|
116
|
+
voice?: string,
|
|
117
|
+
) {
|
|
113
118
|
if (voiceOutDisabled) {
|
|
114
119
|
return;
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
return (currentlySpeaking = SpeechEngine(toSpeak, lang, voice));
|
|
122
|
+
return (currentlySpeaking = SpeechEngine(toSpeak, aria, lang, voice));
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
export interface Announcer {
|
|
121
126
|
debug: boolean;
|
|
122
127
|
enabled: boolean;
|
|
123
128
|
lang: string;
|
|
129
|
+
aria: boolean;
|
|
124
130
|
voice?: string;
|
|
125
131
|
cancel: VoidFunction;
|
|
126
132
|
clearPrevFocus: (depth?: number) => void;
|
|
@@ -140,6 +146,7 @@ export const Announcer: Announcer = {
|
|
|
140
146
|
debug: false,
|
|
141
147
|
enabled: true,
|
|
142
148
|
lang: 'en-US',
|
|
149
|
+
aria: false,
|
|
143
150
|
cancel: function () {
|
|
144
151
|
currentlySpeaking && currentlySpeaking.cancel();
|
|
145
152
|
},
|
|
@@ -149,12 +156,11 @@ export const Announcer: Announcer = {
|
|
|
149
156
|
},
|
|
150
157
|
speak: function (text, { append = false, notification = false } = {}) {
|
|
151
158
|
if (Announcer.onFocusChange && Announcer.enabled) {
|
|
152
|
-
Announcer.onFocusChange.flush();
|
|
153
159
|
if (append && currentlySpeaking && currentlySpeaking.active) {
|
|
154
160
|
currentlySpeaking.append(text);
|
|
155
161
|
} else {
|
|
156
162
|
Announcer.cancel();
|
|
157
|
-
textToSpeech(text, Announcer.lang, Announcer.voice);
|
|
163
|
+
textToSpeech(text, Announcer.aria, Announcer.lang, Announcer.voice);
|
|
158
164
|
}
|
|
159
165
|
|
|
160
166
|
if (notification) {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
type CoreSpeechType =
|
|
1
|
+
type CoreSpeechType =
|
|
2
|
+
| string
|
|
3
|
+
| (() => SpeechType)
|
|
4
|
+
| SpeechType[]
|
|
5
|
+
| SpeechSynthesisUtterance;
|
|
2
6
|
export type SpeechType = CoreSpeechType | Promise<CoreSpeechType>;
|
|
3
7
|
|
|
4
8
|
export interface SeriesResult {
|
|
@@ -8,6 +12,11 @@ export interface SeriesResult {
|
|
|
8
12
|
cancel: () => void;
|
|
9
13
|
}
|
|
10
14
|
|
|
15
|
+
// Aria label
|
|
16
|
+
type AriaLabel = { text: string; lang: string };
|
|
17
|
+
const ARIA_PARENT_ID = 'aria-parent';
|
|
18
|
+
let ariaLabelPhrases: AriaLabel[] = [];
|
|
19
|
+
|
|
11
20
|
/* global SpeechSynthesisErrorEvent */
|
|
12
21
|
function flattenStrings(series: SpeechType[] = []): SpeechType[] {
|
|
13
22
|
const flattenedSeries = [];
|
|
@@ -36,6 +45,82 @@ function delay(pause: number) {
|
|
|
36
45
|
});
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @description This function is called at the end of the speak series
|
|
50
|
+
* @param Phrase is an object containing the text and the language
|
|
51
|
+
*/
|
|
52
|
+
function addChildrenToAriaDiv(phrase: AriaLabel) {
|
|
53
|
+
if (phrase?.text?.trim().length === 0) return;
|
|
54
|
+
ariaLabelPhrases.push(phrase);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @description This function is triggered finally when the speak series is finished and we are to speak the aria labels
|
|
59
|
+
*/
|
|
60
|
+
function focusElementForAria() {
|
|
61
|
+
const element = createAriaElement();
|
|
62
|
+
|
|
63
|
+
if (!element) {
|
|
64
|
+
console.error(`ARIA div not found: ${ARIA_PARENT_ID}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const object of ariaLabelPhrases) {
|
|
69
|
+
const span = document.createElement('span');
|
|
70
|
+
|
|
71
|
+
// TODO: Not sure LG or Samsung support lang attribute on span or switching language
|
|
72
|
+
span.setAttribute('lang', object.lang);
|
|
73
|
+
span.setAttribute('aria-label', object.text);
|
|
74
|
+
element.appendChild(span);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Cleanup
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
ariaLabelPhrases = [];
|
|
80
|
+
cleanAriaLabelParent();
|
|
81
|
+
focusCanvas();
|
|
82
|
+
}, 100);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @description Clean the aria label parent after speaking
|
|
87
|
+
*/
|
|
88
|
+
function cleanAriaLabelParent(): void {
|
|
89
|
+
const parentTag = document.getElementById(ARIA_PARENT_ID);
|
|
90
|
+
if (parentTag) {
|
|
91
|
+
while (parentTag.firstChild) {
|
|
92
|
+
parentTag.removeChild(parentTag.firstChild);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @description Focus the canvas element
|
|
99
|
+
*/
|
|
100
|
+
function focusCanvas(): void {
|
|
101
|
+
const canvas = document.getElementById('app')?.firstChild as HTMLElement;
|
|
102
|
+
canvas?.focus();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @description Create the aria element in the DOM if it doesn't exist
|
|
107
|
+
* @private For xbox, we may need to create a different element each time we wanna use aria
|
|
108
|
+
*/
|
|
109
|
+
function createAriaElement(): HTMLDivElement | HTMLElement {
|
|
110
|
+
const aria_container = document.getElementById(ARIA_PARENT_ID);
|
|
111
|
+
|
|
112
|
+
if (!aria_container) {
|
|
113
|
+
const element = document.createElement('div');
|
|
114
|
+
element.setAttribute('id', ARIA_PARENT_ID);
|
|
115
|
+
element.setAttribute('aria-live', 'assertive');
|
|
116
|
+
element.setAttribute('tabindex', '0');
|
|
117
|
+
document.body.appendChild(element);
|
|
118
|
+
return element;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return aria_container;
|
|
122
|
+
}
|
|
123
|
+
|
|
39
124
|
/**
|
|
40
125
|
* Speak a string
|
|
41
126
|
*
|
|
@@ -78,6 +163,7 @@ function speak(
|
|
|
78
163
|
|
|
79
164
|
function speakSeries(
|
|
80
165
|
series: SpeechType,
|
|
166
|
+
aria: boolean,
|
|
81
167
|
lang: string,
|
|
82
168
|
voice?: string,
|
|
83
169
|
root = true,
|
|
@@ -87,11 +173,6 @@ function speakSeries(
|
|
|
87
173
|
Array.isArray(series) ? series : [series],
|
|
88
174
|
);
|
|
89
175
|
const nestedSeriesResults: SeriesResult[] = [];
|
|
90
|
-
/*
|
|
91
|
-
We hold this array of SpeechSynthesisUtterances in order to prevent them from being
|
|
92
|
-
garbage collected prematurely on STB hardware which can cause the 'onend' events of
|
|
93
|
-
utterances to not fire consistently.
|
|
94
|
-
*/
|
|
95
176
|
const utterances: SpeechSynthesisUtterance[] = [];
|
|
96
177
|
let active: boolean = true;
|
|
97
178
|
|
|
@@ -100,24 +181,66 @@ function speakSeries(
|
|
|
100
181
|
while (active && remainingPhrases.length) {
|
|
101
182
|
const phrase = await Promise.resolve(remainingPhrases.shift());
|
|
102
183
|
if (!active) {
|
|
103
|
-
// Exit
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
if (isNaN(pause)) {
|
|
110
|
-
pause
|
|
184
|
+
break; // Exit if canceled
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (typeof phrase === 'string' && phrase.includes('PAUSE-')) {
|
|
188
|
+
// Handle pauses
|
|
189
|
+
const pause = Number(phrase.split('PAUSE-')[1]) * 1000;
|
|
190
|
+
if (!isNaN(pause)) {
|
|
191
|
+
await delay(pause);
|
|
192
|
+
}
|
|
193
|
+
} else if (typeof phrase === 'string') {
|
|
194
|
+
if (!phrase) {
|
|
195
|
+
continue; // Skip empty strings
|
|
196
|
+
}
|
|
197
|
+
// Handle regular strings with retry logic
|
|
198
|
+
const totalRetries = 3;
|
|
199
|
+
let retriesLeft = totalRetries;
|
|
200
|
+
|
|
201
|
+
while (active && retriesLeft > 0) {
|
|
202
|
+
try {
|
|
203
|
+
if (aria) addChildrenToAriaDiv({ text: phrase, lang });
|
|
204
|
+
else await speak(phrase, utterances, lang, voice);
|
|
205
|
+
retriesLeft = 0; // Exit retry loop on success
|
|
206
|
+
} catch (e) {
|
|
207
|
+
if (e instanceof SpeechSynthesisErrorEvent) {
|
|
208
|
+
if (e.error === 'network') {
|
|
209
|
+
retriesLeft--;
|
|
210
|
+
console.warn(
|
|
211
|
+
`Speech synthesis network error. Retries left: ${retriesLeft}`,
|
|
212
|
+
);
|
|
213
|
+
await delay(500 * (totalRetries - retriesLeft));
|
|
214
|
+
} else if (
|
|
215
|
+
e.error === 'canceled' ||
|
|
216
|
+
e.error === 'interrupted'
|
|
217
|
+
) {
|
|
218
|
+
// Cancel or interrupt error (ignore)
|
|
219
|
+
retriesLeft = 0;
|
|
220
|
+
} else {
|
|
221
|
+
throw new Error(`SpeechSynthesisErrorEvent: ${e.error}`);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
throw e;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
111
227
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// Speak it
|
|
228
|
+
} else if (phrase instanceof SpeechSynthesisUtterance) {
|
|
229
|
+
// Handle SpeechSynthesisUtterance objects with retry logic
|
|
115
230
|
const totalRetries = 3;
|
|
116
231
|
let retriesLeft = totalRetries;
|
|
232
|
+
const text = phrase.text;
|
|
233
|
+
const objectLang = phrase?.lang;
|
|
234
|
+
const objectVoice = phrase?.voice;
|
|
235
|
+
|
|
117
236
|
while (active && retriesLeft > 0) {
|
|
118
237
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
238
|
+
if (text) {
|
|
239
|
+
if (aria) addChildrenToAriaDiv({ text, lang: objectLang });
|
|
240
|
+
else
|
|
241
|
+
await speak(text, utterances, objectLang, objectVoice?.name);
|
|
242
|
+
retriesLeft = 0; // Exit retry loop on success
|
|
243
|
+
}
|
|
121
244
|
} catch (e) {
|
|
122
245
|
if (e instanceof SpeechSynthesisErrorEvent) {
|
|
123
246
|
if (e.error === 'network') {
|
|
@@ -141,20 +264,26 @@ function speakSeries(
|
|
|
141
264
|
}
|
|
142
265
|
}
|
|
143
266
|
} else if (typeof phrase === 'function') {
|
|
144
|
-
|
|
267
|
+
// Handle functions
|
|
268
|
+
const seriesResult = speakSeries(phrase(), aria, lang, voice, false);
|
|
145
269
|
nestedSeriesResults.push(seriesResult);
|
|
146
270
|
await seriesResult.series;
|
|
147
271
|
} else if (Array.isArray(phrase)) {
|
|
148
|
-
//
|
|
149
|
-
const seriesResult = speakSeries(phrase, lang, voice, false);
|
|
272
|
+
// Handle nested arrays
|
|
273
|
+
const seriesResult = speakSeries(phrase, aria, lang, voice, false);
|
|
150
274
|
nestedSeriesResults.push(seriesResult);
|
|
151
275
|
await seriesResult.series;
|
|
152
276
|
}
|
|
153
277
|
}
|
|
154
278
|
} finally {
|
|
155
279
|
active = false;
|
|
280
|
+
// Call completion logic only for the original (root) series
|
|
281
|
+
if (root && aria) {
|
|
282
|
+
focusElementForAria();
|
|
283
|
+
}
|
|
156
284
|
}
|
|
157
285
|
})();
|
|
286
|
+
|
|
158
287
|
return {
|
|
159
288
|
series: seriesChain,
|
|
160
289
|
get active() {
|
|
@@ -167,11 +296,25 @@ function speakSeries(
|
|
|
167
296
|
if (!active) {
|
|
168
297
|
return;
|
|
169
298
|
}
|
|
299
|
+
|
|
170
300
|
if (root) {
|
|
171
|
-
|
|
301
|
+
if (aria) {
|
|
302
|
+
const element = createAriaElement();
|
|
303
|
+
|
|
304
|
+
if (element) {
|
|
305
|
+
ariaLabelPhrases = [];
|
|
306
|
+
cleanAriaLabelParent();
|
|
307
|
+
element.focus();
|
|
308
|
+
focusCanvas();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
synth.cancel(); // Cancel all ongoing speech
|
|
172
315
|
}
|
|
173
|
-
nestedSeriesResults.forEach((
|
|
174
|
-
|
|
316
|
+
nestedSeriesResults.forEach((nestedSeriesResult) => {
|
|
317
|
+
nestedSeriesResult.cancel();
|
|
175
318
|
});
|
|
176
319
|
active = false;
|
|
177
320
|
},
|
|
@@ -181,10 +324,11 @@ function speakSeries(
|
|
|
181
324
|
let currentSeries: SeriesResult | undefined;
|
|
182
325
|
export default function (
|
|
183
326
|
toSpeak: SpeechType,
|
|
327
|
+
aria: boolean,
|
|
184
328
|
lang: string = 'en-US',
|
|
185
329
|
voice?: string,
|
|
186
330
|
) {
|
|
187
331
|
currentSeries && currentSeries.cancel();
|
|
188
|
-
currentSeries = speakSeries(toSpeak, lang, voice);
|
|
332
|
+
currentSeries = speakSeries(toSpeak, aria, lang, voice);
|
|
189
333
|
return currentSeries;
|
|
190
334
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - `restoreFocus()`: Restores focus to the last stored element and removes it from the stack. Returns `true` if successful, `false` otherwise.
|
|
18
18
|
* - `clearFocusStack()`: Empties the focus stack.
|
|
19
19
|
*/
|
|
20
|
-
import
|
|
20
|
+
import * as s from 'solid-js';
|
|
21
21
|
import { type ElementNode } from '@lightningtv/solid';
|
|
22
22
|
|
|
23
23
|
interface FocusStackContextType {
|
|
@@ -26,13 +26,16 @@ interface FocusStackContextType {
|
|
|
26
26
|
clearFocusStack: () => void;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const FocusStackContext = createContext<FocusStackContextType | undefined>(undefined);
|
|
29
|
+
const FocusStackContext = s.createContext<FocusStackContextType | undefined>(undefined);
|
|
30
30
|
|
|
31
|
-
export function FocusStackProvider(props: { children: JSX.Element}) {
|
|
32
|
-
const [_focusStack, setFocusStack] = createSignal<ElementNode[]>([]);
|
|
31
|
+
export function FocusStackProvider(props: { children: s.JSX.Element}) {
|
|
32
|
+
const [_focusStack, setFocusStack] = s.createSignal<ElementNode[]>([]);
|
|
33
33
|
|
|
34
34
|
function storeFocus(element: ElementNode, prevElement?: ElementNode) {
|
|
35
|
-
|
|
35
|
+
const elm = prevElement || element;
|
|
36
|
+
if (elm) {
|
|
37
|
+
setFocusStack(stack => [...stack, elm]);
|
|
38
|
+
}
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
function restoreFocus(): boolean {
|
|
@@ -59,10 +62,18 @@ export function FocusStackProvider(props: { children: JSX.Element}) {
|
|
|
59
62
|
);
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
export function useFocusStack() {
|
|
63
|
-
const context = useContext(FocusStackContext);
|
|
65
|
+
export function useFocusStack(autoClear = true) {
|
|
66
|
+
const context = s.useContext(FocusStackContext);
|
|
64
67
|
if (!context) {
|
|
65
68
|
throw new Error("useFocusStack must be used within a FocusStackProvider");
|
|
66
69
|
}
|
|
70
|
+
|
|
71
|
+
if (autoClear) {
|
|
72
|
+
s.onCleanup(() => {
|
|
73
|
+
// delay clearing the focus stack so restoreFocus can happen first.
|
|
74
|
+
setTimeout(() => context.clearFocusStack(), 5);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
return context;
|
|
68
79
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as s from 'solid-js'
|
|
2
|
+
import * as lng from '@lightningtv/solid'
|
|
3
|
+
|
|
4
|
+
interface Destroyable {
|
|
5
|
+
(props: lng.NodeProps): s.JSX.Element;
|
|
6
|
+
destroy: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createTag(children: s.JSX.Element): Destroyable {
|
|
10
|
+
const [texture, setTexture] = s.createSignal<lng.Texture | null | undefined>(null);
|
|
11
|
+
const Tag = <view
|
|
12
|
+
x={lng.rootNode.w - 1}
|
|
13
|
+
y={lng.rootNode.h - 1}
|
|
14
|
+
display='flex'
|
|
15
|
+
onLayout={(n) => {
|
|
16
|
+
if (n.preFlexwidth && n.width !== n.preFlexwidth) {
|
|
17
|
+
n.rtt = true;
|
|
18
|
+
setTimeout(() => setTexture(n.texture), 1);
|
|
19
|
+
}
|
|
20
|
+
}}
|
|
21
|
+
parent={lng.rootNode} children={children}
|
|
22
|
+
textureOptions={{
|
|
23
|
+
preventCleanup: true
|
|
24
|
+
}} /> as any as lng.ElementNode
|
|
25
|
+
Tag.render(false);
|
|
26
|
+
|
|
27
|
+
const TagComponent = (props: lng.NodeProps) => {
|
|
28
|
+
return <view color={0xffffffff} autosize {...props} texture={texture()} />;
|
|
29
|
+
};
|
|
30
|
+
TagComponent.destroy = () => Tag.destroy();
|
|
31
|
+
|
|
32
|
+
return TagComponent;
|
|
33
|
+
}
|
package/src/primitives/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './createInfiniteItems.js';
|
|
|
4
4
|
export * from './useMouse.js';
|
|
5
5
|
export * from './portal.jsx';
|
|
6
6
|
export * from './Lazy.jsx';
|
|
7
|
+
export * from './Image.jsx';
|
|
7
8
|
export * from './Visible.jsx';
|
|
8
9
|
export * from './router.js';
|
|
9
10
|
export * from './Column.jsx';
|
|
@@ -11,16 +12,24 @@ export * from './Row.jsx';
|
|
|
11
12
|
export * from './Grid.jsx';
|
|
12
13
|
export * from './FPSCounter.jsx';
|
|
13
14
|
export * from './FadeInOut.jsx';
|
|
15
|
+
export * from './Preserve.jsx';
|
|
16
|
+
export * from './Suspense.jsx';
|
|
17
|
+
export * from './Marquee.jsx';
|
|
14
18
|
export * from './createFocusStack.jsx';
|
|
15
|
-
export * from './
|
|
16
|
-
export
|
|
19
|
+
export * from './useHold.js';
|
|
20
|
+
export * from './KeepAlive.jsx';
|
|
21
|
+
export * from './VirtualGrid.jsx';
|
|
22
|
+
export * from './Virtual.jsx';
|
|
23
|
+
export * from './utils/withScrolling.js';
|
|
24
|
+
export * from './createTag.jsx';
|
|
17
25
|
export {
|
|
18
26
|
type AnyFunction,
|
|
19
27
|
chainFunctions,
|
|
20
28
|
chainRefs,
|
|
21
29
|
} from './utils/chainFunctions.js';
|
|
22
|
-
export
|
|
30
|
+
export * from './utils/handleNavigation.js';
|
|
23
31
|
export { createSpriteMap, type SpriteDef } from './utils/createSpriteMap.js';
|
|
32
|
+
export { createBlurredImage } from './utils/createBlurredImage.js';
|
|
24
33
|
|
|
25
34
|
export type * from './types.js';
|
|
26
|
-
export type { KeyHandler } from '
|
|
35
|
+
export type { KeyHandler } from '../core/focusManager.js';
|
package/src/primitives/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { ElementNode, NodeProps, NodeStyles } from '
|
|
2
|
-
import type { KeyHandler } from '
|
|
1
|
+
import type { ElementNode, NodeProps, NodeStyles } from '../index.js';
|
|
2
|
+
import type { KeyHandler } from '../core/focusManager.js';
|
|
3
|
+
|
|
3
4
|
export type OnSelectedChanged = (
|
|
4
5
|
this: NavigableElement,
|
|
5
6
|
selectedIndex: number,
|
|
@@ -7,6 +8,7 @@ export type OnSelectedChanged = (
|
|
|
7
8
|
active: ElementNode,
|
|
8
9
|
lastSelectedIndex?: number,
|
|
9
10
|
) => void;
|
|
11
|
+
|
|
10
12
|
export interface NavigableProps extends NodeProps {
|
|
11
13
|
/** function to be called when the selected of the component changes */
|
|
12
14
|
onSelectedChanged?: OnSelectedChanged;
|
|
@@ -40,11 +42,19 @@ export interface NavigableProps extends NodeProps {
|
|
|
40
42
|
* Wrap the row so active goes back to the beginning of the row
|
|
41
43
|
*/
|
|
42
44
|
wrap?: boolean;
|
|
45
|
+
|
|
46
|
+
/** function to be called when scrolled */
|
|
47
|
+
onScrolled?: (
|
|
48
|
+
elm: NavigableElement,
|
|
49
|
+
offset: number,
|
|
50
|
+
isInitial: boolean,
|
|
51
|
+
) => void;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
// @ts-expect-error animationSettings is not identical - weird
|
|
46
55
|
export interface NavigableElement extends ElementNode, NavigableProps {
|
|
47
56
|
selected: number;
|
|
57
|
+
scrollToIndex: (this: NavigableElement, index: number) => void;
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
export interface NavigableStyleProperties {
|
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
getOwner,
|
|
7
7
|
runWithOwner,
|
|
8
8
|
} from 'solid-js';
|
|
9
|
-
import { Config } from '
|
|
10
|
-
import type { ElementNode } from '
|
|
9
|
+
import { Config } from '../core/index.js';
|
|
10
|
+
import type { ElementNode } from '../core/index.js';
|
|
11
11
|
import {
|
|
12
12
|
useFocusManager as useFocusManagerCore,
|
|
13
13
|
type KeyMap,
|
|
14
14
|
type KeyHoldOptions,
|
|
15
|
-
} from '
|
|
15
|
+
} from '../core/focusManager.js';
|
|
16
16
|
import { activeElement, setActiveElement } from '../activeElement.js';
|
|
17
17
|
|
|
18
18
|
const [focusPath, setFocusPath] = createSignal<ElementNode[]>([]);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createMemo } from 'solid-js';
|
|
2
|
+
|
|
3
|
+
export type UseHoldProps = {
|
|
4
|
+
onHold: () => void;
|
|
5
|
+
onEnter: () => void;
|
|
6
|
+
onRelease?: () => void;
|
|
7
|
+
holdThreshold?: number;
|
|
8
|
+
performOnEnterImmediately?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @example
|
|
13
|
+
* const [holdRight, releaseRight] = useHold({
|
|
14
|
+
* onHold: handleHoldRight,
|
|
15
|
+
* onEnter: handleOnRight,
|
|
16
|
+
* onRelease: handleReleaseHold,
|
|
17
|
+
* holdThreshold: 200,
|
|
18
|
+
* performOnEnterImmediately: true
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* <View
|
|
22
|
+
* onRight={holdRight}
|
|
23
|
+
* onRightRelease={releaseRight}
|
|
24
|
+
* />
|
|
25
|
+
*
|
|
26
|
+
* @param {UseHoldProps} props - The properties for configuring the hold behavior.
|
|
27
|
+
* @returns {[() => boolean, () => boolean]} A tuple containing `startHold` and `releaseHold` functions.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export function useHold(props: UseHoldProps) {
|
|
31
|
+
const holdThreshold = createMemo(() => props.holdThreshold ?? 500);
|
|
32
|
+
const performOnEnterImmediately = createMemo(
|
|
33
|
+
() => props.performOnEnterImmediately ?? false,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
let holdTimeout = -1;
|
|
37
|
+
let wasHeld = false;
|
|
38
|
+
|
|
39
|
+
const startHold = () => {
|
|
40
|
+
if (holdTimeout === -1) {
|
|
41
|
+
if (performOnEnterImmediately()) {
|
|
42
|
+
props.onEnter();
|
|
43
|
+
}
|
|
44
|
+
holdTimeout = setTimeout(() => {
|
|
45
|
+
wasHeld = true;
|
|
46
|
+
props.onHold();
|
|
47
|
+
}, holdThreshold()) as unknown as number;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const releaseHold = () => {
|
|
53
|
+
if (holdTimeout !== -1) {
|
|
54
|
+
clearTimeout(holdTimeout);
|
|
55
|
+
holdTimeout = -1;
|
|
56
|
+
if (!wasHeld) {
|
|
57
|
+
if (!performOnEnterImmediately()) props.onEnter();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
props.onRelease?.();
|
|
61
|
+
wasHeld = false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return [startHold, releaseHold];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default useHold;
|