@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.
Files changed (232) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +6 -0
  3. package/dist/jsx-runtime.d.ts +14 -0
  4. package/dist/src/activeElement.d.ts +1 -1
  5. package/dist/src/core/animation.d.ts +35 -0
  6. package/dist/src/core/animation.js +119 -0
  7. package/dist/src/core/animation.js.map +1 -0
  8. package/dist/src/core/config.d.ts +49 -0
  9. package/dist/src/core/config.js +33 -0
  10. package/dist/src/core/config.js.map +1 -0
  11. package/dist/src/core/domRenderer.d.ts +115 -0
  12. package/dist/src/core/domRenderer.js +1152 -0
  13. package/dist/src/core/domRenderer.js.map +1 -0
  14. package/dist/src/core/elementNode.d.ts +463 -0
  15. package/dist/src/core/elementNode.js +833 -0
  16. package/dist/src/core/elementNode.js.map +1 -0
  17. package/dist/src/core/flex.d.ts +2 -0
  18. package/dist/src/core/flex.js +243 -0
  19. package/dist/src/core/flex.js.map +1 -0
  20. package/dist/src/core/focusKeyTypes.d.ts +42 -0
  21. package/dist/src/core/focusKeyTypes.js +2 -0
  22. package/dist/src/core/focusKeyTypes.js.map +1 -0
  23. package/dist/src/core/focusManager.d.ts +13 -0
  24. package/dist/src/core/focusManager.js +276 -0
  25. package/dist/src/core/focusManager.js.map +1 -0
  26. package/dist/src/core/index.d.ts +12 -0
  27. package/dist/src/core/index.js +12 -0
  28. package/dist/src/core/index.js.map +1 -0
  29. package/dist/src/core/intrinsicTypes.d.ts +90 -0
  30. package/dist/src/core/intrinsicTypes.js +2 -0
  31. package/dist/src/core/intrinsicTypes.js.map +1 -0
  32. package/dist/src/core/lightningInit.d.ts +89 -0
  33. package/dist/src/core/lightningInit.js +26 -0
  34. package/dist/src/core/lightningInit.js.map +1 -0
  35. package/dist/src/core/nodeTypes.d.ts +6 -0
  36. package/dist/src/core/nodeTypes.js +6 -0
  37. package/dist/src/core/nodeTypes.js.map +1 -0
  38. package/dist/src/core/shaders.d.ts +51 -0
  39. package/dist/src/core/shaders.js +446 -0
  40. package/dist/src/core/shaders.js.map +1 -0
  41. package/dist/src/core/states.d.ts +12 -0
  42. package/dist/src/core/states.js +84 -0
  43. package/dist/src/core/states.js.map +1 -0
  44. package/dist/src/core/utils.d.ts +39 -0
  45. package/dist/src/core/utils.js +164 -0
  46. package/dist/src/core/utils.js.map +1 -0
  47. package/dist/src/devtools/index.d.ts +1 -1
  48. package/dist/src/devtools/index.js +1 -1
  49. package/dist/src/devtools/index.js.map +1 -1
  50. package/dist/src/index.d.ts +3 -3
  51. package/dist/src/index.js +1 -1
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/src/primitives/Column.jsx +9 -10
  54. package/dist/src/primitives/Column.jsx.map +1 -1
  55. package/dist/src/primitives/FPSCounter.jsx +15 -2
  56. package/dist/src/primitives/FPSCounter.jsx.map +1 -1
  57. package/dist/src/primitives/Grid.d.ts +2 -2
  58. package/dist/src/primitives/Grid.jsx +27 -17
  59. package/dist/src/primitives/Grid.jsx.map +1 -1
  60. package/dist/src/primitives/Image.d.ts +8 -0
  61. package/dist/src/primitives/Image.jsx +24 -0
  62. package/dist/src/primitives/Image.jsx.map +1 -0
  63. package/dist/src/primitives/KeepAlive.d.ts +30 -0
  64. package/dist/src/primitives/KeepAlive.jsx +77 -0
  65. package/dist/src/primitives/KeepAlive.jsx.map +1 -0
  66. package/dist/src/primitives/Lazy.d.ts +8 -7
  67. package/dist/src/primitives/Lazy.jsx +52 -20
  68. package/dist/src/primitives/Lazy.jsx.map +1 -1
  69. package/dist/src/primitives/Marquee.jsx +2 -2
  70. package/dist/src/primitives/Marquee.jsx.map +1 -1
  71. package/dist/src/primitives/Preserve.d.ts +4 -0
  72. package/dist/src/primitives/Preserve.jsx +11 -0
  73. package/dist/src/primitives/Preserve.jsx.map +1 -0
  74. package/dist/src/primitives/Row.jsx +9 -10
  75. package/dist/src/primitives/Row.jsx.map +1 -1
  76. package/dist/src/primitives/Suspense.d.ts +22 -0
  77. package/dist/src/primitives/Suspense.jsx +33 -0
  78. package/dist/src/primitives/Suspense.jsx.map +1 -0
  79. package/dist/src/primitives/Virtual.d.ts +18 -0
  80. package/dist/src/primitives/Virtual.jsx +443 -0
  81. package/dist/src/primitives/Virtual.jsx.map +1 -0
  82. package/dist/src/primitives/VirtualGrid.d.ts +13 -0
  83. package/dist/src/primitives/VirtualGrid.jsx +160 -0
  84. package/dist/src/primitives/VirtualGrid.jsx.map +1 -0
  85. package/dist/src/primitives/Visible.d.ts +0 -1
  86. package/dist/src/primitives/Visible.jsx +1 -1
  87. package/dist/src/primitives/Visible.jsx.map +1 -1
  88. package/dist/src/primitives/announcer/announcer.d.ts +1 -0
  89. package/dist/src/primitives/announcer/announcer.js +4 -4
  90. package/dist/src/primitives/announcer/announcer.js.map +1 -1
  91. package/dist/src/primitives/announcer/speech.d.ts +2 -2
  92. package/dist/src/primitives/announcer/speech.js +147 -27
  93. package/dist/src/primitives/announcer/speech.js.map +1 -1
  94. package/dist/src/primitives/createFocusStack.d.ts +4 -4
  95. package/dist/src/primitives/createFocusStack.jsx +15 -6
  96. package/dist/src/primitives/createFocusStack.jsx.map +1 -1
  97. package/dist/src/primitives/createTag.d.ts +8 -0
  98. package/dist/src/primitives/createTag.jsx +20 -0
  99. package/dist/src/primitives/createTag.jsx.map +1 -0
  100. package/dist/src/primitives/index.d.ts +13 -4
  101. package/dist/src/primitives/index.js +12 -3
  102. package/dist/src/primitives/index.js.map +1 -1
  103. package/dist/src/primitives/types.d.ts +5 -2
  104. package/dist/src/primitives/useFocusManager.d.ts +2 -2
  105. package/dist/src/primitives/useFocusManager.js +2 -2
  106. package/dist/src/primitives/useFocusManager.js.map +1 -1
  107. package/dist/src/primitives/useMouse.d.ts +18 -2
  108. package/dist/src/primitives/useMouse.js +171 -47
  109. package/dist/src/primitives/useMouse.js.map +1 -1
  110. package/dist/src/primitives/utils/createBlurredImage.d.ts +56 -0
  111. package/dist/src/primitives/utils/createBlurredImage.js +223 -0
  112. package/dist/src/primitives/utils/createBlurredImage.js.map +1 -0
  113. package/dist/src/primitives/utils/createSpriteMap.d.ts +2 -2
  114. package/dist/src/primitives/utils/createSpriteMap.js +3 -3
  115. package/dist/src/primitives/utils/createSpriteMap.js.map +1 -1
  116. package/dist/src/primitives/utils/handleNavigation.d.ts +79 -5
  117. package/dist/src/primitives/utils/handleNavigation.js +241 -69
  118. package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
  119. package/dist/src/primitives/utils/withScrolling.d.ts +14 -2
  120. package/dist/src/primitives/utils/withScrolling.js +66 -7
  121. package/dist/src/primitives/utils/withScrolling.js.map +1 -1
  122. package/dist/src/render.d.ts +5 -4
  123. package/dist/src/render.js +5 -1
  124. package/dist/src/render.js.map +1 -1
  125. package/dist/src/shaders/Rounded.d.ts +7 -0
  126. package/dist/src/shaders/Rounded.js +88 -0
  127. package/dist/src/shaders/Rounded.js.map +1 -0
  128. package/dist/src/shaders/RoundedWithBorder.d.ts +3 -0
  129. package/dist/src/shaders/RoundedWithBorder.js +217 -0
  130. package/dist/src/shaders/RoundedWithBorder.js.map +1 -0
  131. package/dist/src/shaders/index.d.ts +4 -0
  132. package/dist/src/shaders/index.js +5 -0
  133. package/dist/src/shaders/index.js.map +1 -0
  134. package/dist/src/shaders/templates/RoundedTemplate.d.ts +12 -0
  135. package/dist/src/shaders/templates/RoundedTemplate.js +48 -0
  136. package/dist/src/shaders/templates/RoundedTemplate.js.map +1 -0
  137. package/dist/src/shaders/templates/RoundedWithBorderTemplate.d.ts +20 -0
  138. package/dist/src/shaders/templates/RoundedWithBorderTemplate.js +93 -0
  139. package/dist/src/shaders/templates/RoundedWithBorderTemplate.js.map +1 -0
  140. package/dist/src/shaders/utils.d.ts +3 -0
  141. package/dist/src/shaders/utils.js +31 -0
  142. package/dist/src/shaders/utils.js.map +1 -0
  143. package/dist/src/solidOpts.d.ts +1 -7
  144. package/dist/src/solidOpts.js +9 -1
  145. package/dist/src/solidOpts.js.map +1 -1
  146. package/dist/src/types.d.ts +1 -13
  147. package/dist/src/utils.d.ts +3 -1
  148. package/dist/src/utils.js +9 -1
  149. package/dist/src/utils.js.map +1 -1
  150. package/dist/tsconfig.tsbuildinfo +1 -1
  151. package/jsx-runtime.d.ts +2 -1
  152. package/package.json +28 -16
  153. package/src/activeElement.ts +1 -1
  154. package/src/core/animation.ts +185 -0
  155. package/src/core/config.ts +89 -0
  156. package/src/core/domRenderer.ts +1300 -0
  157. package/src/core/elementNode.ts +1458 -0
  158. package/src/core/flex.ts +284 -0
  159. package/src/core/focusKeyTypes.ts +90 -0
  160. package/src/core/focusManager.ts +381 -0
  161. package/src/core/index.ts +13 -0
  162. package/src/core/intrinsicTypes.ts +199 -0
  163. package/src/core/lightningInit.ts +147 -0
  164. package/src/core/nodeTypes.ts +6 -0
  165. package/src/core/shaders.ts +567 -0
  166. package/src/core/states.ts +91 -0
  167. package/src/core/utils.ts +222 -0
  168. package/src/devtools/index.ts +1 -1
  169. package/src/index.ts +3 -3
  170. package/src/primitives/Column.tsx +10 -12
  171. package/src/primitives/FPSCounter.tsx +16 -2
  172. package/src/primitives/Grid.tsx +32 -22
  173. package/src/primitives/Image.tsx +36 -0
  174. package/src/primitives/KeepAlive.tsx +124 -0
  175. package/src/primitives/Lazy.tsx +66 -37
  176. package/src/primitives/{marquee.tsx → Marquee.tsx} +1 -1
  177. package/src/primitives/Preserve.tsx +18 -0
  178. package/src/primitives/Row.tsx +13 -14
  179. package/src/primitives/Suspense.tsx +39 -0
  180. package/src/primitives/Virtual.tsx +486 -0
  181. package/src/primitives/VirtualGrid.tsx +220 -0
  182. package/src/primitives/Visible.tsx +1 -2
  183. package/src/primitives/announcer/announcer.ts +10 -4
  184. package/src/primitives/announcer/speech.ts +170 -26
  185. package/src/primitives/createFocusStack.tsx +18 -7
  186. package/src/primitives/createTag.tsx +33 -0
  187. package/src/primitives/index.ts +13 -4
  188. package/src/primitives/types.ts +12 -2
  189. package/src/primitives/useFocusManager.ts +3 -3
  190. package/src/primitives/useHold.ts +69 -0
  191. package/src/primitives/useMouse.ts +306 -67
  192. package/src/primitives/utils/createBlurredImage.ts +366 -0
  193. package/src/primitives/utils/createSpriteMap.ts +8 -6
  194. package/src/primitives/utils/handleNavigation.ts +300 -84
  195. package/src/primitives/utils/withScrolling.ts +91 -18
  196. package/src/render.ts +7 -3
  197. package/src/shaders/Rounded.ts +100 -0
  198. package/src/shaders/RoundedWithBorder.ts +245 -0
  199. package/src/shaders/index.ts +4 -0
  200. package/src/shaders/templates/RoundedTemplate.ts +57 -0
  201. package/src/shaders/templates/RoundedWithBorderTemplate.ts +110 -0
  202. package/src/shaders/utils.ts +44 -0
  203. package/src/solidOpts.ts +9 -7
  204. package/src/types.ts +1 -15
  205. package/src/utils.ts +11 -1
  206. package/dist/src/client.d.ts +0 -1
  207. package/dist/src/client.js +0 -2
  208. package/dist/src/client.js.map +0 -1
  209. package/dist/src/core.d.ts +0 -1
  210. package/dist/src/core.js +0 -3
  211. package/dist/src/core.js.map +0 -1
  212. package/dist/src/jsx-runtime.d.ts +0 -10
  213. package/dist/src/jsx-runtime.js +0 -2
  214. package/dist/src/jsx-runtime.js.map +0 -1
  215. package/dist/src/primitives/Infinite.d.ts +0 -15
  216. package/dist/src/primitives/Infinite.jsx +0 -59
  217. package/dist/src/primitives/Infinite.jsx.map +0 -1
  218. package/dist/src/primitives/LazyUp.d.ts +0 -11
  219. package/dist/src/primitives/LazyUp.jsx +0 -38
  220. package/dist/src/primitives/LazyUp.jsx.map +0 -1
  221. package/dist/src/primitives/sprite.d.ts +0 -9
  222. package/dist/src/primitives/sprite.js +0 -18
  223. package/dist/src/primitives/sprite.js.map +0 -1
  224. package/dist/src/primitives/utils/createFocusStack.d.ts +0 -24
  225. package/dist/src/primitives/utils/createFocusStack.js +0 -59
  226. package/dist/src/primitives/utils/createFocusStack.js.map +0 -1
  227. package/dist/src/primitives/utils/scrollToIndex.d.ts +0 -2
  228. package/dist/src/primitives/utils/scrollToIndex.js +0 -33
  229. package/dist/src/primitives/utils/scrollToIndex.js.map +0 -1
  230. package/dist/src/renderClient.d.ts +0 -21
  231. package/dist/src/renderClient.js +0 -64
  232. 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 : props.fallback;
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(toSpeak: SpeechType, lang: string, voice?: string) {
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 = string | (() => SpeechType) | SpeechType[];
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
- // Need to check this after the await in case it was cancelled in between
105
- break;
106
- } else if (typeof phrase === 'string' && phrase.includes('PAUSE-')) {
107
- // Pause it
108
- let pause = Number(phrase.split('PAUSE-')[1]) * 1000;
109
- if (isNaN(pause)) {
110
- pause = 0;
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
- await delay(pause);
113
- } else if (typeof phrase === 'string' && phrase.length) {
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
- await speak(phrase, utterances, lang, voice);
120
- retriesLeft = 0;
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
- const seriesResult = speakSeries(phrase(), lang, voice, false);
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
- // Speak it (recursively)
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
- synth.cancel();
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((nestedSeriesResults) => {
174
- nestedSeriesResults.cancel();
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 { createSignal, createContext, useContext, JSX } from 'solid-js';
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
- setFocusStack((stack) => [...stack, prevElement || element]);
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
+ }
@@ -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 './marquee.jsx';
16
- export { withScrolling } from './utils/withScrolling.js';
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 { handleNavigation, onGridFocus } from './utils/handleNavigation.js';
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 '@lightningtv/core/focusManager';
35
+ export type { KeyHandler } from '../core/focusManager.js';
@@ -1,5 +1,6 @@
1
- import type { ElementNode, NodeProps, NodeStyles } from '@lightningtv/solid';
2
- import type { KeyHandler } from '@lightningtv/core/focusManager';
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 '@lightningtv/core';
10
- import type { ElementNode } from '@lightningtv/core';
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 '@lightningtv/core/focusManager';
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;