@lightningtv/solid 3.0.0-8 → 3.0.0-9
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/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/Lazy.jsx +1 -1
- 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/announcer/announcer.js +0 -1
- package/dist/src/primitives/announcer/announcer.js.map +1 -1
- package/dist/src/primitives/announcer/speech.d.ts +1 -1
- package/dist/src/primitives/announcer/speech.js +51 -21
- package/dist/src/primitives/announcer/speech.js.map +1 -1
- package/dist/src/primitives/index.d.ts +2 -1
- package/dist/src/primitives/index.js +2 -1
- package/dist/src/primitives/index.js.map +1 -1
- package/dist/src/primitives/types.d.ts +2 -0
- package/dist/src/primitives/utils/withScrolling.d.ts +2 -0
- package/dist/src/primitives/utils/withScrolling.js +7 -0
- package/dist/src/primitives/utils/withScrolling.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jsx-runtime.d.ts +2 -1
- package/package.json +3 -3
- package/src/primitives/Grid.tsx +32 -22
- package/src/primitives/Lazy.tsx +1 -1
- package/src/primitives/{marquee.tsx → Marquee.tsx} +1 -1
- package/src/primitives/announcer/announcer.ts +0 -1
- package/src/primitives/announcer/speech.ts +60 -23
- package/src/primitives/index.ts +2 -1
- package/src/primitives/types.ts +9 -0
- package/src/primitives/useHold.ts +69 -0
- package/src/primitives/utils/withScrolling.ts +15 -0
|
@@ -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 {
|
|
@@ -87,11 +91,6 @@ function speakSeries(
|
|
|
87
91
|
Array.isArray(series) ? series : [series],
|
|
88
92
|
);
|
|
89
93
|
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
94
|
const utterances: SpeechSynthesisUtterance[] = [];
|
|
96
95
|
let active: boolean = true;
|
|
97
96
|
|
|
@@ -100,24 +99,61 @@ function speakSeries(
|
|
|
100
99
|
while (active && remainingPhrases.length) {
|
|
101
100
|
const phrase = await Promise.resolve(remainingPhrases.shift());
|
|
102
101
|
if (!active) {
|
|
103
|
-
// Exit
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
if (isNaN(pause)) {
|
|
110
|
-
pause
|
|
102
|
+
break; // Exit if canceled
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (typeof phrase === 'string' && phrase.includes('PAUSE-')) {
|
|
106
|
+
// Handle pauses
|
|
107
|
+
const pause = Number(phrase.split('PAUSE-')[1]) * 1000;
|
|
108
|
+
if (!isNaN(pause)) {
|
|
109
|
+
await delay(pause);
|
|
110
|
+
}
|
|
111
|
+
} else if (typeof phrase === 'string') {
|
|
112
|
+
if (!phrase) {
|
|
113
|
+
continue; // Skip empty strings
|
|
111
114
|
}
|
|
112
|
-
|
|
113
|
-
} else if (typeof phrase === 'string' && phrase.length) {
|
|
114
|
-
// Speak it
|
|
115
|
+
// Handle regular strings with retry logic
|
|
115
116
|
const totalRetries = 3;
|
|
116
117
|
let retriesLeft = totalRetries;
|
|
118
|
+
|
|
117
119
|
while (active && retriesLeft > 0) {
|
|
118
120
|
try {
|
|
119
121
|
await speak(phrase, utterances, lang, voice);
|
|
120
|
-
retriesLeft = 0;
|
|
122
|
+
retriesLeft = 0; // Exit retry loop on success
|
|
123
|
+
} catch (e) {
|
|
124
|
+
if (e instanceof SpeechSynthesisErrorEvent) {
|
|
125
|
+
if (e.error === 'network') {
|
|
126
|
+
retriesLeft--;
|
|
127
|
+
console.warn(
|
|
128
|
+
`Speech synthesis network error. Retries left: ${retriesLeft}`,
|
|
129
|
+
);
|
|
130
|
+
await delay(500 * (totalRetries - retriesLeft));
|
|
131
|
+
} else if (
|
|
132
|
+
e.error === 'canceled' ||
|
|
133
|
+
e.error === 'interrupted'
|
|
134
|
+
) {
|
|
135
|
+
// Cancel or interrupt error (ignore)
|
|
136
|
+
retriesLeft = 0;
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error(`SpeechSynthesisErrorEvent: ${e.error}`);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} else if (phrase instanceof SpeechSynthesisUtterance) {
|
|
146
|
+
// Handle SpeechSynthesisUtterance objects with retry logic
|
|
147
|
+
const totalRetries = 3;
|
|
148
|
+
let retriesLeft = totalRetries;
|
|
149
|
+
const text = phrase.text;
|
|
150
|
+
const objectLang = phrase?.lang;
|
|
151
|
+
const objectVoice = phrase?.voice;
|
|
152
|
+
|
|
153
|
+
while (active && retriesLeft > 0) {
|
|
154
|
+
try {
|
|
155
|
+
await speak(text, utterances, objectLang, objectVoice?.name);
|
|
156
|
+
retriesLeft = 0; // Exit retry loop on success
|
|
121
157
|
} catch (e) {
|
|
122
158
|
if (e instanceof SpeechSynthesisErrorEvent) {
|
|
123
159
|
if (e.error === 'network') {
|
|
@@ -141,11 +177,12 @@ function speakSeries(
|
|
|
141
177
|
}
|
|
142
178
|
}
|
|
143
179
|
} else if (typeof phrase === 'function') {
|
|
180
|
+
// Handle functions
|
|
144
181
|
const seriesResult = speakSeries(phrase(), lang, voice, false);
|
|
145
182
|
nestedSeriesResults.push(seriesResult);
|
|
146
183
|
await seriesResult.series;
|
|
147
184
|
} else if (Array.isArray(phrase)) {
|
|
148
|
-
//
|
|
185
|
+
// Handle nested arrays
|
|
149
186
|
const seriesResult = speakSeries(phrase, lang, voice, false);
|
|
150
187
|
nestedSeriesResults.push(seriesResult);
|
|
151
188
|
await seriesResult.series;
|
|
@@ -155,6 +192,7 @@ function speakSeries(
|
|
|
155
192
|
active = false;
|
|
156
193
|
}
|
|
157
194
|
})();
|
|
195
|
+
|
|
158
196
|
return {
|
|
159
197
|
series: seriesChain,
|
|
160
198
|
get active() {
|
|
@@ -168,16 +206,15 @@ function speakSeries(
|
|
|
168
206
|
return;
|
|
169
207
|
}
|
|
170
208
|
if (root) {
|
|
171
|
-
synth.cancel();
|
|
209
|
+
synth.cancel(); // Cancel all ongoing speech
|
|
172
210
|
}
|
|
173
|
-
nestedSeriesResults.forEach((
|
|
174
|
-
|
|
211
|
+
nestedSeriesResults.forEach((nestedSeriesResult) => {
|
|
212
|
+
nestedSeriesResult.cancel();
|
|
175
213
|
});
|
|
176
214
|
active = false;
|
|
177
215
|
},
|
|
178
216
|
};
|
|
179
217
|
}
|
|
180
|
-
|
|
181
218
|
let currentSeries: SeriesResult | undefined;
|
|
182
219
|
export default function (
|
|
183
220
|
toSpeak: SpeechType,
|
package/src/primitives/index.ts
CHANGED
|
@@ -12,7 +12,8 @@ export * from './Grid.jsx';
|
|
|
12
12
|
export * from './FPSCounter.jsx';
|
|
13
13
|
export * from './FadeInOut.jsx';
|
|
14
14
|
export * from './createFocusStack.jsx';
|
|
15
|
-
export * from './
|
|
15
|
+
export * from './Marquee.jsx';
|
|
16
|
+
export * from './useHold.js';
|
|
16
17
|
export { withScrolling } from './utils/withScrolling.js';
|
|
17
18
|
export {
|
|
18
19
|
type AnyFunction,
|
package/src/primitives/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ElementNode, NodeProps, NodeStyles } from '@lightningtv/solid';
|
|
2
2
|
import type { KeyHandler } from '@lightningtv/core/focusManager';
|
|
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,6 +42,13 @@ 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
|
|
@@ -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;
|
|
@@ -11,8 +11,14 @@ export interface ScrollableElement extends ElementNode {
|
|
|
11
11
|
selected: number;
|
|
12
12
|
offset?: number;
|
|
13
13
|
endOffset?: number;
|
|
14
|
+
onScrolled?: (
|
|
15
|
+
elm: ScrollableElement,
|
|
16
|
+
offset: number,
|
|
17
|
+
isInitial: boolean,
|
|
18
|
+
) => void;
|
|
14
19
|
_targetPosition?: number;
|
|
15
20
|
_screenOffset?: number;
|
|
21
|
+
_initialPosition?: number;
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
// From the renderer, not exported
|
|
@@ -48,6 +54,10 @@ export function withScrolling(isRow: boolean) {
|
|
|
48
54
|
)
|
|
49
55
|
return;
|
|
50
56
|
|
|
57
|
+
if (componentRef._initialPosition === undefined) {
|
|
58
|
+
componentRef._initialPosition = componentRef[axis];
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
const lng = componentRef.lng as INode;
|
|
52
62
|
const screenSize = isRow ? lng.stage.root.width : lng.stage.root.height;
|
|
53
63
|
// Determine if movement is incremental or decremental
|
|
@@ -157,6 +167,11 @@ export function withScrolling(isRow: boolean) {
|
|
|
157
167
|
|
|
158
168
|
// Update position if it has changed
|
|
159
169
|
if (componentRef[axis] !== nextPosition) {
|
|
170
|
+
if (componentRef.onScrolled) {
|
|
171
|
+
const isInitial = nextPosition === componentRef._initialPosition;
|
|
172
|
+
componentRef.onScrolled(componentRef, nextPosition, isInitial);
|
|
173
|
+
}
|
|
174
|
+
|
|
160
175
|
componentRef[axis] = nextPosition;
|
|
161
176
|
// Store the new position to keep track during animations
|
|
162
177
|
componentRef._targetPosition = nextPosition;
|