@lightningtv/solid 3.0.0-9 → 3.0.1

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 (228) 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/Image.d.ts +8 -0
  58. package/dist/src/primitives/Image.jsx +24 -0
  59. package/dist/src/primitives/Image.jsx.map +1 -0
  60. package/dist/src/primitives/KeepAlive.d.ts +30 -0
  61. package/dist/src/primitives/KeepAlive.jsx +77 -0
  62. package/dist/src/primitives/KeepAlive.jsx.map +1 -0
  63. package/dist/src/primitives/Lazy.d.ts +8 -7
  64. package/dist/src/primitives/Lazy.jsx +52 -20
  65. package/dist/src/primitives/Lazy.jsx.map +1 -1
  66. package/dist/src/primitives/LazyImport.d.ts +8 -0
  67. package/dist/src/primitives/LazyImport.js +40 -0
  68. package/dist/src/primitives/LazyImport.js.map +1 -0
  69. package/dist/src/primitives/Preserve.d.ts +4 -0
  70. package/dist/src/primitives/Preserve.jsx +11 -0
  71. package/dist/src/primitives/Preserve.jsx.map +1 -0
  72. package/dist/src/primitives/Row.jsx +9 -10
  73. package/dist/src/primitives/Row.jsx.map +1 -1
  74. package/dist/src/primitives/Suspense.d.ts +22 -0
  75. package/dist/src/primitives/Suspense.jsx +33 -0
  76. package/dist/src/primitives/Suspense.jsx.map +1 -0
  77. package/dist/src/primitives/Virtual.d.ts +18 -0
  78. package/dist/src/primitives/Virtual.jsx +443 -0
  79. package/dist/src/primitives/Virtual.jsx.map +1 -0
  80. package/dist/src/primitives/VirtualGrid.d.ts +13 -0
  81. package/dist/src/primitives/VirtualGrid.jsx +160 -0
  82. package/dist/src/primitives/VirtualGrid.jsx.map +1 -0
  83. package/dist/src/primitives/Visible.d.ts +0 -1
  84. package/dist/src/primitives/Visible.jsx +1 -1
  85. package/dist/src/primitives/Visible.jsx.map +1 -1
  86. package/dist/src/primitives/announcer/announcer.d.ts +1 -0
  87. package/dist/src/primitives/announcer/announcer.js +4 -3
  88. package/dist/src/primitives/announcer/announcer.js.map +1 -1
  89. package/dist/src/primitives/announcer/speech.d.ts +1 -1
  90. package/dist/src/primitives/announcer/speech.js +98 -8
  91. package/dist/src/primitives/announcer/speech.js.map +1 -1
  92. package/dist/src/primitives/createFocusStack.d.ts +4 -4
  93. package/dist/src/primitives/createFocusStack.jsx +15 -6
  94. package/dist/src/primitives/createFocusStack.jsx.map +1 -1
  95. package/dist/src/primitives/createTag.d.ts +8 -0
  96. package/dist/src/primitives/createTag.jsx +20 -0
  97. package/dist/src/primitives/createTag.jsx.map +1 -0
  98. package/dist/src/primitives/index.d.ts +13 -4
  99. package/dist/src/primitives/index.js +12 -3
  100. package/dist/src/primitives/index.js.map +1 -1
  101. package/dist/src/primitives/types.d.ts +3 -2
  102. package/dist/src/primitives/useFocusManager.d.ts +2 -2
  103. package/dist/src/primitives/useFocusManager.js +2 -2
  104. package/dist/src/primitives/useFocusManager.js.map +1 -1
  105. package/dist/src/primitives/useMouse.d.ts +18 -2
  106. package/dist/src/primitives/useMouse.js +171 -47
  107. package/dist/src/primitives/useMouse.js.map +1 -1
  108. package/dist/src/primitives/utils/createBlurredImage.d.ts +56 -0
  109. package/dist/src/primitives/utils/createBlurredImage.js +223 -0
  110. package/dist/src/primitives/utils/createBlurredImage.js.map +1 -0
  111. package/dist/src/primitives/utils/createSpriteMap.d.ts +2 -2
  112. package/dist/src/primitives/utils/createSpriteMap.js +3 -3
  113. package/dist/src/primitives/utils/createSpriteMap.js.map +1 -1
  114. package/dist/src/primitives/utils/handleNavigation.d.ts +79 -5
  115. package/dist/src/primitives/utils/handleNavigation.js +241 -69
  116. package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
  117. package/dist/src/primitives/utils/withScrolling.d.ts +12 -2
  118. package/dist/src/primitives/utils/withScrolling.js +59 -7
  119. package/dist/src/primitives/utils/withScrolling.js.map +1 -1
  120. package/dist/src/render.d.ts +5 -4
  121. package/dist/src/render.js +5 -1
  122. package/dist/src/render.js.map +1 -1
  123. package/dist/src/shaders/Rounded.d.ts +7 -0
  124. package/dist/src/shaders/Rounded.js +88 -0
  125. package/dist/src/shaders/Rounded.js.map +1 -0
  126. package/dist/src/shaders/RoundedWithBorder.d.ts +3 -0
  127. package/dist/src/shaders/RoundedWithBorder.js +217 -0
  128. package/dist/src/shaders/RoundedWithBorder.js.map +1 -0
  129. package/dist/src/shaders/index.d.ts +4 -0
  130. package/dist/src/shaders/index.js +5 -0
  131. package/dist/src/shaders/index.js.map +1 -0
  132. package/dist/src/shaders/templates/RoundedTemplate.d.ts +12 -0
  133. package/dist/src/shaders/templates/RoundedTemplate.js +48 -0
  134. package/dist/src/shaders/templates/RoundedTemplate.js.map +1 -0
  135. package/dist/src/shaders/templates/RoundedWithBorderTemplate.d.ts +20 -0
  136. package/dist/src/shaders/templates/RoundedWithBorderTemplate.js +93 -0
  137. package/dist/src/shaders/templates/RoundedWithBorderTemplate.js.map +1 -0
  138. package/dist/src/shaders/utils.d.ts +3 -0
  139. package/dist/src/shaders/utils.js +31 -0
  140. package/dist/src/shaders/utils.js.map +1 -0
  141. package/dist/src/solidOpts.d.ts +1 -7
  142. package/dist/src/solidOpts.js +9 -1
  143. package/dist/src/solidOpts.js.map +1 -1
  144. package/dist/src/types.d.ts +1 -13
  145. package/dist/src/utils.d.ts +3 -1
  146. package/dist/src/utils.js +9 -1
  147. package/dist/src/utils.js.map +1 -1
  148. package/dist/tsconfig.tsbuildinfo +1 -1
  149. package/jsx-runtime.d.ts +1 -1
  150. package/package.json +28 -16
  151. package/src/activeElement.ts +1 -1
  152. package/src/core/animation.ts +185 -0
  153. package/src/core/config.ts +89 -0
  154. package/src/core/domRenderer.ts +1300 -0
  155. package/src/core/elementNode.ts +1458 -0
  156. package/src/core/flex.ts +284 -0
  157. package/src/core/focusKeyTypes.ts +90 -0
  158. package/src/core/focusManager.ts +381 -0
  159. package/src/core/index.ts +13 -0
  160. package/src/core/intrinsicTypes.ts +199 -0
  161. package/src/core/lightningInit.ts +147 -0
  162. package/src/core/nodeTypes.ts +6 -0
  163. package/src/core/shaders.ts +567 -0
  164. package/src/core/states.ts +91 -0
  165. package/src/core/utils.ts +222 -0
  166. package/src/devtools/index.ts +1 -1
  167. package/src/index.ts +3 -3
  168. package/src/primitives/Column.tsx +10 -12
  169. package/src/primitives/FPSCounter.tsx +16 -2
  170. package/src/primitives/Image.tsx +36 -0
  171. package/src/primitives/KeepAlive.tsx +124 -0
  172. package/src/primitives/Lazy.tsx +66 -37
  173. package/src/primitives/LazyImport.ts +53 -0
  174. package/src/primitives/Preserve.tsx +18 -0
  175. package/src/primitives/Row.tsx +13 -14
  176. package/src/primitives/Suspense.tsx +39 -0
  177. package/src/primitives/Virtual.tsx +486 -0
  178. package/src/primitives/VirtualGrid.tsx +220 -0
  179. package/src/primitives/Visible.tsx +1 -2
  180. package/src/primitives/announcer/announcer.ts +10 -3
  181. package/src/primitives/announcer/speech.ts +113 -6
  182. package/src/primitives/createFocusStack.tsx +18 -7
  183. package/src/primitives/createTag.tsx +33 -0
  184. package/src/primitives/index.ts +13 -4
  185. package/src/primitives/types.ts +3 -2
  186. package/src/primitives/useFocusManager.ts +3 -3
  187. package/src/primitives/useMouse.ts +306 -67
  188. package/src/primitives/utils/createBlurredImage.ts +366 -0
  189. package/src/primitives/utils/createSpriteMap.ts +8 -6
  190. package/src/primitives/utils/handleNavigation.ts +300 -84
  191. package/src/primitives/utils/withScrolling.ts +76 -18
  192. package/src/render.ts +7 -3
  193. package/src/shaders/Rounded.ts +100 -0
  194. package/src/shaders/RoundedWithBorder.ts +245 -0
  195. package/src/shaders/index.ts +4 -0
  196. package/src/shaders/templates/RoundedTemplate.ts +57 -0
  197. package/src/shaders/templates/RoundedWithBorderTemplate.ts +110 -0
  198. package/src/shaders/utils.ts +44 -0
  199. package/src/solidOpts.ts +9 -7
  200. package/src/types.ts +1 -15
  201. package/src/utils.ts +11 -1
  202. package/dist/src/client.d.ts +0 -1
  203. package/dist/src/client.js +0 -2
  204. package/dist/src/client.js.map +0 -1
  205. package/dist/src/core.d.ts +0 -1
  206. package/dist/src/core.js +0 -3
  207. package/dist/src/core.js.map +0 -1
  208. package/dist/src/jsx-runtime.d.ts +0 -10
  209. package/dist/src/jsx-runtime.js +0 -2
  210. package/dist/src/jsx-runtime.js.map +0 -1
  211. package/dist/src/primitives/Infinite.d.ts +0 -15
  212. package/dist/src/primitives/Infinite.jsx +0 -59
  213. package/dist/src/primitives/Infinite.jsx.map +0 -1
  214. package/dist/src/primitives/LazyUp.d.ts +0 -11
  215. package/dist/src/primitives/LazyUp.jsx +0 -38
  216. package/dist/src/primitives/LazyUp.jsx.map +0 -1
  217. package/dist/src/primitives/sprite.d.ts +0 -9
  218. package/dist/src/primitives/sprite.js +0 -18
  219. package/dist/src/primitives/sprite.js.map +0 -1
  220. package/dist/src/primitives/utils/createFocusStack.d.ts +0 -24
  221. package/dist/src/primitives/utils/createFocusStack.js +0 -59
  222. package/dist/src/primitives/utils/createFocusStack.js.map +0 -1
  223. package/dist/src/primitives/utils/scrollToIndex.d.ts +0 -2
  224. package/dist/src/primitives/utils/scrollToIndex.js +0 -33
  225. package/dist/src/primitives/utils/scrollToIndex.js.map +0 -1
  226. package/dist/src/renderClient.d.ts +0 -21
  227. package/dist/src/renderClient.js +0 -64
  228. package/dist/src/renderClient.js.map +0 -1
@@ -0,0 +1,222 @@
1
+ import { type INode, type Point } from '@lightningjs/renderer';
2
+ import { Config, isDev } from './config.js';
3
+ import type { Styles, ElementText, TextNode } from './intrinsicTypes.js';
4
+ import { ElementNode } from './elementNode.js';
5
+ import { NodeType } from './nodeTypes.js';
6
+
7
+ function hasDebug(node: any) {
8
+ return isObject(node) && node.debug;
9
+ }
10
+
11
+ export function log(
12
+ msg: string,
13
+ node: ElementNode | ElementText | TextNode,
14
+ ...args: any[]
15
+ ) {
16
+ if (isDev) {
17
+ if (Config.debug || hasDebug(node) || hasDebug(args[0])) {
18
+ console.log(msg, node, ...args);
19
+ }
20
+ }
21
+ }
22
+
23
+ export const isFunc = (obj: unknown): obj is CallableFunction =>
24
+ obj instanceof Function;
25
+
26
+ export const isFunction = (obj: unknown): obj is Function =>
27
+ typeof obj === 'function';
28
+
29
+ export function isObject(
30
+ item: unknown,
31
+ ): item is Record<string | number | symbol, unknown> {
32
+ return typeof item === 'object';
33
+ }
34
+
35
+ export function isArray(item: unknown): item is any[] {
36
+ return Array.isArray(item);
37
+ }
38
+
39
+ export function isString(item: unknown): item is string {
40
+ return typeof item === 'string';
41
+ }
42
+
43
+ export function isNumber(item: unknown): item is number {
44
+ return typeof item === 'number';
45
+ }
46
+
47
+ export function isInteger(item: unknown): item is number {
48
+ return Number.isInteger(item);
49
+ }
50
+
51
+ export function isINode(node: object): node is INode {
52
+ return 'destroy' in node && typeof node.destroy === 'function';
53
+ }
54
+
55
+ export function isElementNode(node: unknown): node is ElementNode {
56
+ return node instanceof ElementNode;
57
+ }
58
+
59
+ export function isElementText(
60
+ node: ElementNode | ElementText | TextNode,
61
+ ): node is ElementText {
62
+ return node._type === NodeType.TextNode;
63
+ }
64
+
65
+ export function isTextNode(
66
+ node: ElementNode | ElementText | TextNode,
67
+ ): node is TextNode {
68
+ return node._type === NodeType.Text;
69
+ }
70
+
71
+ export function keyExists(
72
+ obj: Record<string, unknown>,
73
+ keys: (string | number | symbol)[],
74
+ ) {
75
+ for (const key of keys) {
76
+ if (key in obj) {
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+
83
+ export function spliceItem<T>(
84
+ arr: T[],
85
+ item: T,
86
+ deleteCount: number,
87
+ ...insert: T[]
88
+ ): number {
89
+ const index = arr.indexOf(item);
90
+ if (index > -1) {
91
+ arr.splice(index, deleteCount, ...insert);
92
+ }
93
+ return index;
94
+ }
95
+
96
+ export function flattenStyles(
97
+ obj: Styles | undefined | (Styles | undefined)[],
98
+ result: Styles = {},
99
+ ): Styles {
100
+ if (isArray(obj)) {
101
+ obj.forEach((item) => {
102
+ flattenStyles(item, result);
103
+ });
104
+ } else if (obj) {
105
+ // handle the case where the object is not an array
106
+ for (const key in obj) {
107
+ // be careful of 0 values
108
+ if (result[key] === undefined) {
109
+ result[key as keyof Styles] = obj[key as keyof Styles]!;
110
+ }
111
+ }
112
+ }
113
+
114
+ return result;
115
+ }
116
+
117
+ export function logRenderTree(node: ElementNode) {
118
+ const tree = [node];
119
+ let parent = node.parent;
120
+ while (parent) {
121
+ tree.push(parent);
122
+ parent = parent.parent;
123
+ }
124
+ tree.reverse();
125
+
126
+ let output = `
127
+ function convertEffectsToShader(styleEffects) {
128
+ const effects = [];
129
+ let index = 0;
130
+
131
+ for (const [type, props] of Object.entries(styleEffects)) {
132
+ effects.push({ type, props });
133
+ index++;
134
+ }
135
+ return createShader('DynamicShader', { effects });
136
+ }
137
+ `;
138
+ tree.forEach((node, i) => {
139
+ if (!node._rendererProps) {
140
+ return;
141
+ }
142
+ node._rendererProps.parent = undefined;
143
+ node._rendererProps.shader = undefined;
144
+ const props = JSON.stringify(node._rendererProps, null, 2);
145
+ const effects = node._effects
146
+ ? `props${i}.shader = convertEffectsToShader(${JSON.stringify(node._effects, null, 2)});`
147
+ : '';
148
+ const parent = i === 0 ? 'rootNode' : `node${i - 1}`;
149
+ output += `
150
+ const props${i} = ${props};
151
+ props${i}.parent = ${parent};
152
+ ${effects}
153
+ const node${i} = renderer.createNode(props${i});
154
+ `;
155
+ });
156
+
157
+ return output;
158
+ }
159
+
160
+ export interface Rect extends Point {
161
+ width: number;
162
+ height: number;
163
+ }
164
+
165
+ /**
166
+ * Calculates the rectangle of an element on the screen,
167
+ * taking into account its position, size, and scaling.
168
+ *
169
+ * @param el - The element to calculate the rectangle for.
170
+ * @param from - Optional ancestor element to calculate the rectangle relative to.
171
+ * @param out - Optional output rectangle to fill with the result.
172
+ * @returns The rectangle of the element on the screen.
173
+ */
174
+ export function getElementScreenRect(
175
+ el: ElementNode | ElementText,
176
+ from?: ElementNode,
177
+ out: Rect = { x: 0, y: 0, width: 0, height: 0 },
178
+ ): Rect {
179
+ out.x = 0;
180
+ out.y = 0;
181
+ out.width = el.width;
182
+ out.height = el.height;
183
+
184
+ if (el.scaleX != null) out.width *= el.scaleX;
185
+ if (el.scaleY != null) out.height *= el.scaleY;
186
+
187
+ let curr = el as ElementNode | undefined | null;
188
+ while (curr != null && curr !== from) {
189
+ out.x += curr.x;
190
+ out.y += curr.y;
191
+
192
+ if (curr.scaleX != null) {
193
+ out.x += (curr.width / 2) * (1 - curr.scaleX);
194
+ }
195
+ if (curr.scaleY != null) {
196
+ out.y += (curr.height / 2) * (1 - curr.scaleY);
197
+ }
198
+
199
+ curr = curr.parent;
200
+ }
201
+
202
+ if (Config.rendererOptions != null) {
203
+ let dpr = Config.rendererOptions.deviceLogicalPixelRatio;
204
+ if (dpr != null) {
205
+ out.x *= dpr;
206
+ out.y *= dpr;
207
+ out.width *= dpr;
208
+ out.height *= dpr;
209
+ }
210
+ }
211
+
212
+ return out;
213
+ }
214
+
215
+ /**
216
+ * Checks if the element has focus.\
217
+ * ({@link ElementNode.states} contains the {@link Config.focusStateKey} focus state)
218
+ */
219
+ export function isFocused(el: ElementNode | ElementText): boolean {
220
+ return el?.states?.has(Config.focusStateKey);
221
+ }
222
+ export const hasFocus = isFocused;
@@ -1,5 +1,5 @@
1
1
  import * as debug from '@solid-devtools/debugger/types';
2
- import * as lng from '@lightningtv/core';
2
+ import * as lng from '../core/index.js';
3
3
 
4
4
  const EMPTY_CHILDREN: (lng.ElementNode | lng.ElementText)[] = [];
5
5
 
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from '@lightningtv/solid/jsx-runtime';
2
- export * from '@lightningtv/core';
3
- export type * from '@lightningtv/core';
4
- export type { KeyHandler, KeyMap } from '@lightningtv/core/focusManager';
2
+ export * from './core/index.js';
3
+ export type * from './core/index.js';
4
+ export type { KeyHandler, KeyMap } from './core/focusManager.js';
5
5
  export * from './activeElement.js';
6
6
  export * from './utils.js';
7
7
  export * from './render.js';
@@ -1,10 +1,9 @@
1
1
  import { type Component } from 'solid-js';
2
2
  import { ElementNode, combineStyles, type NodeStyles } from '@lightningtv/solid';
3
3
  import {
4
- handleNavigation,
5
- onGridFocus,
4
+ navigableForwardFocus, handleNavigation
6
5
  } from './utils/handleNavigation.js';
7
- import { withScrolling } from './utils/withScrolling.js';
6
+ import { scrollColumn } from './utils/withScrolling.js';
8
7
  import { chainFunctions } from './utils/chainFunctions.js';
9
8
  import type { ColumnProps } from './types.js';
10
9
 
@@ -20,16 +19,15 @@ const ColumnStyles: NodeStyles = {
20
19
  },
21
20
  };
22
21
 
23
- const onUp = handleNavigation('up');
24
- const onDown = handleNavigation('down');
25
- const scroll = withScrolling(false);
26
-
27
22
  function scrollToIndex(this: ElementNode, index: number) {
28
23
  this.selected = index;
29
- scroll(index, this);
30
- this.setFocus();
24
+ scrollColumn(index, this);
25
+ this.children[index]?.setFocus();
31
26
  }
32
27
 
28
+ const onUp = handleNavigation('up');
29
+ const onDown = handleNavigation('down');
30
+
33
31
  export const Column: Component<ColumnProps> = (props) => {
34
32
  return (
35
33
  <view
@@ -38,15 +36,15 @@ export const Column: Component<ColumnProps> = (props) => {
38
36
  onDown={/* @once */ chainFunctions(props.onDown, onDown)}
39
37
  selected={props.selected || 0}
40
38
  scrollToIndex={scrollToIndex}
41
- forwardFocus={/* once */ onGridFocus(props.onSelectedChanged)}
39
+ forwardFocus={navigableForwardFocus}
42
40
  onLayout={
43
41
  /* @once */
44
- props.selected ? chainFunctions(props.onLayout, scroll) : props.onLayout
42
+ props.selected ? chainFunctions(props.onLayout, scrollColumn) : props.onLayout
45
43
  }
46
44
  onSelectedChanged={
47
45
  /* @once */ chainFunctions(
48
46
  props.onSelectedChanged,
49
- props.scroll !== 'none' ? scroll : undefined,
47
+ props.scroll !== 'none' ? scrollColumn : undefined,
50
48
  )
51
49
  }
52
50
  style={/* @once */ combineStyles(props.style, ColumnStyles)}
@@ -3,12 +3,12 @@ import { createSignal } from 'solid-js';
3
3
 
4
4
  const fpsStyle = {
5
5
  color: 0x000000ff,
6
- height: 180,
6
+ height: 192,
7
7
  width: 330,
8
8
  x: 1900,
9
9
  y: 6,
10
10
  mountX: 1,
11
- alpha: 0.8,
11
+ alpha: 1,
12
12
  zIndex: 100
13
13
  };
14
14
 
@@ -27,6 +27,7 @@ const [fps, setFps] = createSignal(0);
27
27
  const [avgFps, setAvgFps] = createSignal(0);
28
28
  const [minFps, setMinFps] = createSignal(99);
29
29
  const [maxFps, setMaxFps] = createSignal(0);
30
+ const [quads, setQuads] = createSignal(0);
30
31
  const [criticalThresholdSignal, setCriticalThresholdSignal] = createSignal('');
31
32
  const [targetThresholdSignal, setTargetThresholdSignal] = createSignal('');
32
33
  const [renderableMemUsedSignal, setRenderableMemUsedSignal] = createSignal('');
@@ -86,6 +87,10 @@ export function setupFPS(root: any) {
86
87
  frameCount++;
87
88
  }
88
89
  });
90
+
91
+ root.renderer.on('quadsUpdate', (target: RendererMain, quadsData: any) => {
92
+ setQuads(quadsData.quads);
93
+ });
89
94
  }
90
95
 
91
96
  export const FPSCounter = (props: NodeProps) => {
@@ -173,6 +178,15 @@ export const FPSCounter = (props: NodeProps) => {
173
178
  {loadedTexturesSignal().toString()}
174
179
  </text>
175
180
  </view>
181
+
182
+ <view height={infoFontSize}>
183
+ <text fontSize={infoFontSize} style={fpsLabel}>
184
+ quads:
185
+ </text>
186
+ <text fontSize={infoFontSize} style={fpsLabel} x={230}>
187
+ {quads().toString()}
188
+ </text>
189
+ </view>
176
190
  </view>
177
191
  </view>
178
192
  );
@@ -0,0 +1,36 @@
1
+ import { type Component, createRenderEffect, createSignal } from 'solid-js';
2
+ import { renderer, type NodeProps, type ImageTexture} from '@lightningtv/solid';
3
+ export interface ImageProps extends NodeProps {
4
+ src: string;
5
+ /* image to load while src is being loaded */
6
+ placeholder?: string;
7
+ fallback?: string;
8
+ }
9
+
10
+ export const Image: Component<ImageProps> = (props) => {
11
+ const [texture, setTexture] = createSignal<any>(null);
12
+ const [src, setSrc] = createSignal<string | null>(props.placeholder || null);
13
+
14
+ createRenderEffect(() => {
15
+ const srcTexture = renderer.createTexture('ImageTexture', props) as ImageTexture;
16
+
17
+ if (props.fallback) {
18
+ srcTexture.once('failed', () => {
19
+ if (props.fallback === props.placeholder) {
20
+ return;
21
+ }
22
+ setSrc(props.fallback!);
23
+ });
24
+ }
25
+
26
+ srcTexture.getTextureData().then(resp => {
27
+ // if texture fails to load, this is still called after the failed handler
28
+ if (resp.data)
29
+ setTexture(srcTexture);
30
+ })
31
+ })
32
+
33
+ return (
34
+ <view {...props} src={src()} color={props.color || 0xffffffff} texture={texture()} />
35
+ );
36
+ };
@@ -0,0 +1,124 @@
1
+ import { Route, RoutePreloadFuncArgs, RouteProps } from "@solidjs/router";
2
+ import * as s from 'solid-js';
3
+ import { ElementNode } from "@lightningtv/solid";
4
+
5
+ export interface KeepAliveElement {
6
+ id: string;
7
+ owner: s.Owner | null;
8
+ children: s.JSX.Element;
9
+ routeSignal?: s.Signal<unknown>;
10
+ dispose: () => void;
11
+ }
12
+
13
+ const keepAliveElements = new Map<string, KeepAliveElement>();
14
+
15
+ export const storeKeepAlive = (
16
+ element: KeepAliveElement
17
+ ): KeepAliveElement | undefined => {
18
+ if (keepAliveElements.has(element.id)) {
19
+ console.warn(`[KeepAlive] Element with id "${element.id}" already in cache. Recreating.`);
20
+ return element;
21
+ }
22
+ keepAliveElements.set(element.id, element);
23
+ return element;
24
+ };
25
+
26
+ export const removeKeepAlive = (id: string): void => {
27
+ const element = keepAliveElements.get(id);
28
+ if (element) {
29
+ element.dispose();
30
+ keepAliveElements.delete(id);
31
+ }
32
+ };
33
+
34
+ interface KeepAliveProps {
35
+ id: string;
36
+ shouldDispose?: (key: string) => boolean;
37
+ onRemove?: ElementNode['onRemove'];
38
+ onRender?: ElementNode['onRender'];
39
+ transition?: ElementNode['transition'];
40
+ }
41
+
42
+ function wrapChildren(props: s.ParentProps<KeepAliveProps>) {
43
+ const onRemove = props.onRemove || ((elm: ElementNode) => { elm.alpha = 0; });
44
+ const onRender = props.onRender || ((elm: ElementNode) => { elm.alpha = 1; });
45
+ const transition = props.transition || { alpha: true };
46
+
47
+ return (
48
+ <view
49
+ preserve
50
+ onRemove={onRemove}
51
+ onRender={onRender}
52
+ forwardFocus={0}
53
+ transition={transition}
54
+ {...props}
55
+ />)
56
+ }
57
+
58
+ export const KeepAlive = (props: s.ParentProps<KeepAliveProps>) => {
59
+ let existing = keepAliveElements.get(props.id)
60
+
61
+ if (existing && props.shouldDispose?.(props.id)) {
62
+ existing.dispose();
63
+ keepAliveElements.delete(props.id);
64
+ existing = undefined;
65
+ }
66
+
67
+ if (!existing) {
68
+ return s.createRoot((dispose) => {
69
+ const children = wrapChildren(props);
70
+ storeKeepAlive({
71
+ id: props.id,
72
+ owner: s.getOwner(),
73
+ children,
74
+ dispose,
75
+ });
76
+ return children;
77
+ });
78
+ } else if (existing && !existing.children) {
79
+ existing.children = s.runWithOwner(existing.owner, () => wrapChildren(props));
80
+ }
81
+ return existing.children;
82
+ };
83
+
84
+ export const KeepAliveRoute = <S extends string>(props: RouteProps<S> & {
85
+ id?: string,
86
+ path: string,
87
+ component: s.Component<RouteProps<S>>,
88
+ shouldDispose?: (key: string) => boolean,
89
+ onRemove?: ElementNode['onRemove'];
90
+ onRender?: ElementNode['onRender'];
91
+ transition?: ElementNode['transition'];
92
+ }) => {
93
+ const key = props.id || props.path;
94
+
95
+ const preload = props.preload ? (preloadProps: RoutePreloadFuncArgs) => {
96
+ let existing = keepAliveElements.get(key)
97
+
98
+ if (existing && props.shouldDispose?.(key)) {
99
+ existing.dispose();
100
+ keepAliveElements.delete(key);
101
+ existing = undefined;
102
+ }
103
+
104
+ if (!existing) {
105
+ return s.createRoot((dispose) => {
106
+ storeKeepAlive({
107
+ id: key,
108
+ owner: s.getOwner(),
109
+ dispose,
110
+ children: null,
111
+ });
112
+ return props.preload!(preloadProps);
113
+ });
114
+ } else if (existing.children) {
115
+ (existing.children as unknown as ElementNode)?.setFocus();
116
+ }
117
+ } : undefined;
118
+
119
+ return (<Route {...props} preload={preload} component={(childProps) =>
120
+ <KeepAlive id={key} onRemove={props.onRemove} onRender={props.onRender} transition={props.transition}>
121
+ {props.component(childProps)}
122
+ </KeepAlive>
123
+ }/>);
124
+ };
@@ -1,52 +1,61 @@
1
- import {
2
- Index,
3
- createEffect,
4
- createMemo,
5
- createSignal,
6
- Show,
7
- type JSX,
8
- type ValidComponent,
9
- untrack,
10
- type Accessor,
11
- } from 'solid-js'; // Dynamic removed
12
- import { type NewOmit, scheduleTask, type NodeProps, Dynamic } from '@lightningtv/solid'; // Dynamic removed from imports
13
- import { Row, Column } from '@lightningtv/solid/primitives';
1
+ import * as lng from '@lightningtv/solid';
2
+ import * as lngp from '@lightningtv/solid/primitives';
3
+ import * as s from 'solid-js';
14
4
 
15
- type LazyProps<T extends readonly any[]> = NewOmit<NodeProps, 'children'> & {
5
+ type LazyProps<T extends readonly any[]> = lng.NewOmit<lng.NodeProps, 'children'> & {
16
6
  each: T | undefined | null | false;
17
- fallback?: JSX.Element;
18
7
  upCount: number;
8
+ buffer?: number;
19
9
  delay?: number;
20
10
  sync?: boolean;
21
11
  eagerLoad?: boolean;
22
- children: (item: Accessor<T[number]>, index: number) => JSX.Element;
12
+ noRefocus?: boolean;
13
+ children: (item: s.Accessor<T[number]>, index: number) => s.JSX.Element;
23
14
  };
24
15
 
25
16
  function createLazy<T>(
26
- component: ValidComponent,
17
+ component: s.ValidComponent,
27
18
  props: LazyProps<readonly T[]>,
28
- keyHandler: (updateOffset: () => void) => Record<string, () => void>
19
+ keyHandler: (updateOffset: (event: KeyboardEvent, container: lng.ElementNode) => void) => Record<string, (event: KeyboardEvent, container: lng.ElementNode) => void>
29
20
  ) {
30
21
  // Need at least one item so it can be focused
31
- const [offset, setOffset] = createSignal<number>(props.sync ? props.upCount : 1);
22
+ const [offset, setOffset] = s.createSignal<number>(props.sync ? props.upCount : 0);
32
23
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
24
+ let viewRef!: lngp.NavigableElement;
25
+ let itemLength: number = 0;
33
26
 
34
- createEffect(() => setOffset(offset => Math.max(offset, (props.selected || 0) + 1)));
27
+ const buffer = s.createMemo(() => {
28
+ if (typeof props.buffer === 'number') {
29
+ return props.buffer;
30
+ }
31
+ const scroll = props.scroll || props.style?.scroll;
32
+ if (
33
+ !scroll ||
34
+ scroll === 'auto' ||
35
+ scroll === 'always' ||
36
+ scroll === 'bounded'
37
+ )
38
+ return props.upCount + 1;
39
+ if (scroll === 'center') return Math.ceil(props.upCount / 2) + 1;
40
+ return 2;
41
+ });
42
+
43
+ s.createRenderEffect(() => setOffset(offset => Math.max(offset, (props.selected || 0) + buffer())));
35
44
 
36
- if (!props.sync || props.eaglerLoad) {
37
- createEffect(() => {
45
+ if (!props.sync || props.eagerLoad) {
46
+ s.createEffect(() => {
38
47
  if (props.each) {
39
48
  const loadItems = () => {
40
- let count = untrack(offset);
49
+ let count = s.untrack(offset);
41
50
  if (count < props.upCount) {
42
51
  setOffset(count + 1);
43
52
  timeoutId = setTimeout(loadItems, 16); // ~60fps
44
53
  count++;
45
54
  } else if (props.eagerLoad) {
46
55
  const maxOffset = props.each ? props.each.length : 0;
47
- if (offset() >= maxOffset) return;
56
+ if (count >= maxOffset) return;
48
57
  setOffset((prev) => Math.min(prev + 1, maxOffset));
49
- scheduleTask(loadItems);
58
+ lng.scheduleTask(loadItems);
50
59
  }
51
60
  };
52
61
  loadItems();
@@ -54,13 +63,30 @@ function createLazy<T>(
54
63
  });
55
64
  }
56
65
 
57
- const items = createMemo(() => (
58
- Array.isArray(props.each) ? props.each.slice(0, offset()) : [])
59
- );
66
+ const items: s.Accessor<T[]> = s.createMemo(() => {
67
+ if (Array.isArray(props.each)) {
68
+ if (itemLength != props.each.length) {
69
+ itemLength = props.each.length;
70
+ if (viewRef && !viewRef.noRefocus && lng.hasFocus(viewRef)) {
71
+ queueMicrotask(viewRef.setFocus);
72
+ }
73
+ }
74
+ return props.each.slice(0, offset());
75
+ }
76
+ itemLength = 0;
77
+ return [];
78
+ });
79
+
80
+ function lazyScrollToIndex(this: lngp.NavigableElement, index: number) {
81
+ setOffset(Math.max(index, 0) + buffer())
82
+ queueMicrotask(() => viewRef.scrollToIndex(index));
83
+ }
60
84
 
61
- const updateOffset = () => {
85
+ const updateOffset = (_event: KeyboardEvent, container: lng.ElementNode) => {
62
86
  const maxOffset = props.each ? props.each.length : 0;
63
- if (offset() >= maxOffset) return;
87
+ const selected = container.selected || 0;
88
+ const numChildren = container.children.length;
89
+ if (offset() >= maxOffset || selected < numChildren - buffer()) return;
64
90
 
65
91
  if (!props.delay) {
66
92
  setOffset((prev) => Math.min(prev + 1, maxOffset));
@@ -82,18 +108,21 @@ function createLazy<T>(
82
108
  const handler = keyHandler(updateOffset);
83
109
 
84
110
  return (
85
- <Show when={items()} fallback={props.fallback}>
86
- <Dynamic component={component} {...props} {/* @once */ ...handler}>
87
- <Index each={items()} children={props.children} />
88
- </Dynamic>
89
- </Show>
111
+ <lng.Dynamic
112
+ {...props}
113
+ component={component}
114
+ {/* @once */ ...handler}
115
+ lazyScrollToIndex={lazyScrollToIndex}
116
+ ref={lngp.chainRefs(el => { viewRef = el as lngp.NavigableElement; }, props.ref)} >
117
+ <s.Index each={items()} children={props.children} />
118
+ </lng.Dynamic>
90
119
  );
91
120
  }
92
121
 
93
122
  export function LazyRow<T extends readonly any[]>(props: LazyProps<T>) {
94
- return createLazy(Row, props, (updateOffset) => ({ onRight: updateOffset }));
123
+ return createLazy(lngp.Row, props, (updateOffset) => ({ onRight: updateOffset }));
95
124
  }
96
125
 
97
126
  export function LazyColumn<T extends readonly any[]>(props: LazyProps<T>) {
98
- return createLazy(Column, props, (updateOffset) => ({ onDown: updateOffset }));
127
+ return createLazy(lngp.Column, props, (updateOffset) => ({ onDown: updateOffset }));
99
128
  }