@lightningtv/solid 3.0.0-1 → 3.0.0-3

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 (45) hide show
  1. package/dist/src/devtools/index.d.ts +6 -0
  2. package/dist/src/devtools/index.js +65 -0
  3. package/dist/src/devtools/index.js.map +1 -0
  4. package/dist/src/index.d.ts +1 -1
  5. package/dist/src/index.js +1 -1
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/jsx-runtime.d.ts +1 -1
  8. package/dist/src/primitives/Column.jsx +2 -2
  9. package/dist/src/primitives/Column.jsx.map +1 -1
  10. package/dist/src/primitives/FPSCounter.jsx +60 -61
  11. package/dist/src/primitives/FPSCounter.jsx.map +1 -1
  12. package/dist/src/primitives/Grid.d.ts +15 -6
  13. package/dist/src/primitives/Grid.jsx +11 -8
  14. package/dist/src/primitives/Grid.jsx.map +1 -1
  15. package/dist/src/primitives/Lazy.jsx +11 -4
  16. package/dist/src/primitives/Lazy.jsx.map +1 -1
  17. package/dist/src/primitives/LazyUp.jsx +1 -0
  18. package/dist/src/primitives/LazyUp.jsx.map +1 -1
  19. package/dist/src/primitives/Row.jsx +2 -2
  20. package/dist/src/primitives/Row.jsx.map +1 -1
  21. package/dist/src/primitives/utils/createSpriteMap.d.ts +2 -2
  22. package/dist/src/primitives/utils/createSpriteMap.js.map +1 -1
  23. package/dist/src/primitives/utils/handleNavigation.js +3 -2
  24. package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
  25. package/dist/src/render.d.ts +2 -2
  26. package/dist/src/render.js +8 -6
  27. package/dist/src/render.js.map +1 -1
  28. package/dist/src/solidOpts.d.ts +6 -0
  29. package/dist/src/solidOpts.js +27 -7
  30. package/dist/src/solidOpts.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/{src/jsx-runtime.ts → jsx-runtime.d.ts} +2 -3
  33. package/package.json +18 -5
  34. package/src/devtools/index.ts +77 -0
  35. package/src/index.ts +1 -1
  36. package/src/primitives/Column.tsx +2 -2
  37. package/src/primitives/FPSCounter.tsx +61 -61
  38. package/src/primitives/Grid.tsx +30 -16
  39. package/src/primitives/Lazy.tsx +14 -5
  40. package/src/primitives/Row.tsx +1 -1
  41. package/src/primitives/utils/createSpriteMap.ts +3 -5
  42. package/src/primitives/utils/handleNavigation.ts +2 -2
  43. package/src/render.ts +10 -7
  44. package/src/solidOpts.ts +38 -7
  45. package/src/primitives/jsx-runtime.d.ts +0 -8
@@ -1,4 +1,4 @@
1
- import { View, Text, type Stage, type NodeProps, RendererMain } from '@lightningtv/solid';
1
+ import { type Stage, type NodeProps, RendererMain } from '@lightningtv/solid';
2
2
  import { createSignal } from 'solid-js';
3
3
 
4
4
  const fpsStyle = {
@@ -90,90 +90,90 @@ export function setupFPS(root: any) {
90
90
 
91
91
  export const FPSCounter = (props: NodeProps) => {
92
92
  return (
93
- <View {...props} style={fpsStyle}>
94
- <View y={6}>
95
- <Text style={fpsLabel}>FPS:</Text>
96
- <Text style={fpsValue} x={90}>
93
+ <view {...props} style={fpsStyle}>
94
+ <view y={6}>
95
+ <text style={fpsLabel}>FPS:</text>
96
+ <text style={fpsValue} x={90}>
97
97
  {fps().toString()}
98
- </Text>
99
- </View>
98
+ </text>
99
+ </view>
100
100
 
101
- <View y={6} x={160}>
102
- <Text style={fpsLabel}>AVG:</Text>
103
- <Text style={fpsValue} x={100}>
101
+ <view y={6} x={160}>
102
+ <text style={fpsLabel}>AVG:</text>
103
+ <text style={fpsValue} x={100}>
104
104
  {avgFps().toString()}
105
- </Text>
106
- </View>
105
+ </text>
106
+ </view>
107
107
 
108
- <View x={0} y={26}>
109
- <Text style={fpsLabel}>MIN:</Text>
110
- <Text style={fpsValue} x={90}>
108
+ <view x={0} y={26}>
109
+ <text style={fpsLabel}>MIN:</text>
110
+ <text style={fpsValue} x={90}>
111
111
  {minFps().toString()}
112
- </Text>
113
- </View>
112
+ </text>
113
+ </view>
114
114
 
115
- <View x={160} y={26}>
116
- <Text style={fpsLabel}>MAX:</Text>
117
- <Text style={fpsValue} x={100}>
115
+ <view x={160} y={26}>
116
+ <text style={fpsLabel}>MAX:</text>
117
+ <text style={fpsValue} x={100}>
118
118
  {maxFps().toString()}
119
- </Text>
120
- </View>
119
+ </text>
120
+ </view>
121
121
 
122
- <View display="flex" flexDirection="column" y={58} gap={4}>
123
- <View height={infoFontSize}>
124
- <Text fontSize={infoFontSize} style={fpsLabel}>
122
+ <view display="flex" flexDirection="column" y={58} gap={4}>
123
+ <view height={infoFontSize}>
124
+ <text fontSize={infoFontSize} style={fpsLabel}>
125
125
  criticalThreshold:
126
- </Text>
127
- <Text fontSize={infoFontSize} style={fpsLabel} x={230}>
126
+ </text>
127
+ <text fontSize={infoFontSize} style={fpsLabel} x={230}>
128
128
  {criticalThresholdSignal()}
129
- </Text>
130
- </View>
129
+ </text>
130
+ </view>
131
131
 
132
- <View height={infoFontSize}>
133
- <Text fontSize={infoFontSize} style={fpsLabel}>
132
+ <view height={infoFontSize}>
133
+ <text fontSize={infoFontSize} style={fpsLabel}>
134
134
  targetThreshold:
135
- </Text>
136
- <Text fontSize={infoFontSize} style={fpsLabel} x={230}>
135
+ </text>
136
+ <text fontSize={infoFontSize} style={fpsLabel} x={230}>
137
137
  {targetThresholdSignal()}
138
- </Text>
139
- </View>
138
+ </text>
139
+ </view>
140
140
 
141
- <View height={infoFontSize}>
142
- <Text fontSize={infoFontSize} style={fpsLabel}>
141
+ <view height={infoFontSize}>
142
+ <text fontSize={infoFontSize} style={fpsLabel}>
143
143
  renderableMemUsed:
144
- </Text>
145
- <Text fontSize={infoFontSize} style={fpsLabel} x={230}>
144
+ </text>
145
+ <text fontSize={infoFontSize} style={fpsLabel} x={230}>
146
146
  {renderableMemUsedSignal()}
147
- </Text>
148
- </View>
147
+ </text>
148
+ </view>
149
149
 
150
- <View height={infoFontSize}>
151
- <Text fontSize={infoFontSize} style={fpsLabel}>
150
+ <view height={infoFontSize}>
151
+ <text fontSize={infoFontSize} style={fpsLabel}>
152
152
  memUsed:
153
- </Text>
154
- <Text fontSize={infoFontSize} style={fpsLabel} x={230}>
153
+ </text>
154
+ <text fontSize={infoFontSize} style={fpsLabel} x={230}>
155
155
  {memUsedSignal()}
156
- </Text>
157
- </View>
156
+ </text>
157
+ </view>
158
158
 
159
- <View height={infoFontSize}>
160
- <Text fontSize={infoFontSize} style={fpsLabel}>
159
+ <view height={infoFontSize}>
160
+ <text fontSize={infoFontSize} style={fpsLabel}>
161
161
  renderableTexturesLoaded:
162
- </Text>
163
- <Text fontSize={infoFontSize} style={fpsLabel} x={230}>
162
+ </text>
163
+ <text fontSize={infoFontSize} style={fpsLabel} x={230}>
164
164
  {renderableTexturesLoadedSignal().toString()}
165
- </Text>
166
- </View>
165
+ </text>
166
+ </view>
167
167
 
168
- <View height={infoFontSize}>
169
- <Text fontSize={infoFontSize} style={fpsLabel}>
168
+ <view height={infoFontSize}>
169
+ <text fontSize={infoFontSize} style={fpsLabel}>
170
170
  loadedTextures:
171
- </Text>
172
- <Text fontSize={infoFontSize} style={fpsLabel} x={230}>
171
+ </text>
172
+ <text fontSize={infoFontSize} style={fpsLabel} x={230}>
173
173
  {loadedTexturesSignal().toString()}
174
- </Text>
175
- </View>
176
- </View>
177
- </View>
174
+ </text>
175
+ </view>
176
+ </view>
177
+ </view>
178
178
  );
179
179
  };
@@ -1,23 +1,38 @@
1
- import { ValidComponent, For, createSignal, createMemo } from "solid-js";
2
- import { View, Dynamic, type NodeProps, type ElementNode, isFunction } from "@lightningtv/solid";
1
+ import { For, createSignal, createMemo, JSX } from "solid-js";
2
+ import { type NodeProps, type ElementNode, isFunction, NewOmit } from "@lightningtv/solid";
3
3
 
4
- export const Grid = <T,>(props: {
5
- item: ValidComponent;
4
+ export interface GridItemProps<T> {
5
+ item: T
6
+ index: number
7
+ width: number
8
+ height: number
9
+ x: number
10
+ y: number
11
+ }
12
+
13
+ export interface GridProps<T> extends NewOmit<NodeProps, 'children'> {
14
+ items: T[];
15
+ children: (props: GridItemProps<T>) => JSX.Element,
6
16
  itemHeight?: number;
7
17
  itemWidth?: number;
8
18
  itemOffset?: number;
9
- items: T[];
10
19
  columns?: number;
11
20
  looping?: boolean;
12
21
  scroll?: "auto" | "none";
13
22
  onSelectedChanged?: (index: number, grid: ElementNode, elm?: ElementNode) => void;
14
- } & NodeProps) => {
23
+ }
24
+
25
+ export function Grid<T>(props: GridProps<T>): JSX.Element {
26
+
15
27
  const [focusedIndex, setFocusedIndex] = createSignal(0);
16
28
  const baseColumns = 4;
17
29
 
30
+ const itemWidth = () => props.itemWidth ?? 300
31
+ const itemHeight = () => props.itemHeight ?? 300
32
+
18
33
  const columns = createMemo(() => props.columns || baseColumns);
19
- const totalWidth = createMemo(() => (props.itemWidth ?? 300) + (props.itemOffset ?? 0));
20
- const totalHeight = createMemo(() => (props.itemHeight ?? 300) + (props.itemOffset ?? 0));
34
+ const totalWidth = createMemo(() => itemWidth() + (props.itemOffset ?? 0));
35
+ const totalHeight = createMemo(() => itemHeight() + (props.itemOffset ?? 0));
21
36
 
22
37
  const moveFocus = (delta: number, elm: ElementNode) => {
23
38
  if (!props.items || props.items.length === 0) return false;
@@ -72,7 +87,7 @@ export const Grid = <T,>(props: {
72
87
  );
73
88
 
74
89
  return (
75
- <View
90
+ <view
76
91
  transition={{ y: true }}
77
92
  {...props}
78
93
  onUp={(_e, elm) => moveFocus(-columns(), elm)}
@@ -85,17 +100,16 @@ export const Grid = <T,>(props: {
85
100
  >
86
101
  <For each={props.items}>
87
102
  {(item, index) => (
88
- <Dynamic
89
- {...item}
90
- component={props.item}
91
- width={props.itemWidth}
92
- height={props.itemHeight}
103
+ <props.children
104
+ item={item}
105
+ index={index()}
106
+ width={itemWidth()}
107
+ height={itemHeight()}
93
108
  x={(index() % columns()) * totalWidth()}
94
109
  y={Math.floor(index() / columns()) * totalHeight()}
95
110
  />
96
111
  )}
97
112
  </For>
98
- </View>
113
+ </view>
99
114
  );
100
115
  };
101
-
@@ -31,23 +31,22 @@ function createLazy<T>(
31
31
  const [offset, setOffset] = createSignal(1);
32
32
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
33
33
 
34
- createEffect(() => setOffset(props.selected || 1));
34
+ createEffect(() => setOffset((props.selected || 0) + 1));
35
35
 
36
36
  if (props.sync) {
37
37
  setOffset(props.upCount);
38
38
  } else {
39
39
  createEffect(() => {
40
40
  if (props.each) {
41
- let count = untrack(offset);
42
-
43
41
  const loadItems = () => {
42
+ let count = untrack(offset);
44
43
  if (count < props.upCount) {
45
44
  setOffset(count + 1);
46
45
  timeoutId = setTimeout(loadItems, 16); // ~60fps
47
46
  count++;
48
47
  } else if (props.eagerLoad) {
49
48
  const maxOffset = props.each ? props.each.length : 0;
50
- if (offset() > maxOffset) return;
49
+ if (offset() >= maxOffset) return;
51
50
  setOffset((prev) => Math.min(prev + 1, maxOffset));
52
51
  scheduleTask(loadItems);
53
52
  }
@@ -63,7 +62,17 @@ function createLazy<T>(
63
62
  const maxOffset = props.each ? props.each.length : 0;
64
63
  if (offset() >= maxOffset) return;
65
64
 
66
- if (timeoutId) clearTimeout(timeoutId);
65
+ if (!props.delay) {
66
+ setOffset((prev) => Math.min(prev + 1, maxOffset));
67
+ return;
68
+ }
69
+
70
+ if (timeoutId) {
71
+ clearTimeout(timeoutId);
72
+ //Moving faster than the delay so need to go sync
73
+ setOffset((prev) => Math.min(prev + 1, maxOffset));
74
+ }
75
+
67
76
  timeoutId = setTimeout(() => {
68
77
  setOffset((prev) => Math.min(prev + 1, maxOffset));
69
78
  timeoutId = null;
@@ -31,7 +31,7 @@ function scrollToIndex(this: ElementNode, index: number) {
31
31
 
32
32
  export const Row: Component<RowProps> = (props) => {
33
33
  return (
34
- <View
34
+ <view
35
35
  {...props}
36
36
  selected={props.selected || 0}
37
37
  onLeft={/* @once */ chainFunctions(props.onLeft, onLeft)}
@@ -1,4 +1,4 @@
1
- import { renderer, type TextureMap } from '@lightningtv/core';
1
+ import { type IRendererTexture, renderer } from '@lightningtv/core';
2
2
 
3
3
  export interface SpriteDef {
4
4
  name: string | number;
@@ -11,14 +11,12 @@ export interface SpriteDef {
11
11
  export function createSpriteMap(
12
12
  src: string,
13
13
  subTextures: SpriteDef[],
14
- ): Record<string, InstanceType<TextureMap['SubTexture']>> {
14
+ ): Record<string, IRendererTexture> {
15
15
  const spriteMapTexture = renderer.createTexture('ImageTexture', {
16
16
  src,
17
17
  });
18
18
 
19
- return subTextures.reduce<
20
- Record<string, InstanceType<TextureMap['SubTexture']>>
21
- >((acc, t) => {
19
+ return subTextures.reduce<Record<string, IRendererTexture>>((acc, t) => {
22
20
  const { x, y, width, height } = t;
23
21
  acc[t.name] = renderer.createTexture('SubTexture', {
24
22
  texture: spriteMapTexture,
@@ -73,8 +73,8 @@ export function handleNavigation(
73
73
  return false;
74
74
  }
75
75
  }
76
- const active = this.children[this.selected || 0];
77
- assertTruthy(active instanceof ElementNode);
76
+ const active = this.children[this.selected || 0] || this.children[0];
77
+ if (!(active instanceof ElementNode)) return false;
78
78
  const navigableThis = this as NavigableElement;
79
79
 
80
80
  navigableThis.onSelectedChanged &&
package/src/render.ts CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  type NodeProps,
5
5
  type TextProps,
6
6
  startLightningRenderer,
7
- type RendererMain,
8
7
  type RendererMainSettings,
8
+ type IRendererMain,
9
9
  } from '@lightningtv/core';
10
10
  import nodeOpts from './solidOpts.js';
11
11
  import {
@@ -15,13 +15,14 @@ import {
15
15
  untrack,
16
16
  type JSXElement,
17
17
  type ValidComponent,
18
+ createRoot,
18
19
  } from 'solid-js';
19
20
  import type { SolidNode } from './types.js';
20
21
  import { activeElement, setActiveElement } from './activeElement.js';
21
22
 
22
23
  const solidRenderer = solidCreateRenderer<SolidNode>(nodeOpts);
23
24
 
24
- let renderer: RendererMain;
25
+ let renderer: IRendererMain;
25
26
  export const rootNode = nodeOpts.createElement('App');
26
27
 
27
28
  const render = function (code: () => JSXElement) {
@@ -71,11 +72,13 @@ type Task = () => void;
71
72
  const taskQueue: Task[] = [];
72
73
  let tasksEnabled = false;
73
74
 
74
- createRenderEffect(() => {
75
- // should change whenever a keypress occurs, so we disable the task queue
76
- // until the renderer is idle again.
77
- activeElement();
78
- tasksEnabled = false;
75
+ createRoot(() => {
76
+ createRenderEffect(() => {
77
+ // should change whenever a keypress occurs, so we disable the task queue
78
+ // until the renderer is idle again.
79
+ activeElement();
80
+ tasksEnabled = false;
81
+ });
79
82
  });
80
83
 
81
84
  export function setTasksEnabled(enabled: boolean): void {
package/src/solidOpts.ts CHANGED
@@ -9,6 +9,36 @@ import {
9
9
  } from '@lightningtv/core';
10
10
  import type { SolidNode, SolidRendererOptions } from './types.js';
11
11
 
12
+ declare module '@lightningtv/core' {
13
+ interface ElementNode {
14
+ /** @internal for managing series of insertions and deletions */
15
+ _queueDelete?: number;
16
+ }
17
+ }
18
+
19
+ let elementDeleteQueue: ElementNode[] = [];
20
+
21
+ function flushDeleteQueue(): void {
22
+ for (let el of elementDeleteQueue) {
23
+ if (Number(el._queueDelete) < 0) {
24
+ el.destroy();
25
+ }
26
+ el._queueDelete = undefined;
27
+ }
28
+ elementDeleteQueue.length = 0;
29
+ }
30
+
31
+ function pushDeleteQueue(node: ElementNode, n: number): void {
32
+ if (node._queueDelete === undefined) {
33
+ node._queueDelete = n;
34
+ if (elementDeleteQueue.push(node) === 1) {
35
+ queueMicrotask(flushDeleteQueue);
36
+ }
37
+ } else {
38
+ node._queueDelete += n;
39
+ }
40
+ }
41
+
12
42
  export default {
13
43
  createElement(name: string): ElementNode {
14
44
  return new ElementNode(name);
@@ -30,11 +60,14 @@ export default {
30
60
  insertNode(parent: ElementNode, node: SolidNode, anchor: SolidNode): void {
31
61
  log('INSERT: ', parent, node, anchor);
32
62
 
63
+ let prevParent = node.parent;
33
64
  parent.insertChild(node, anchor);
34
- node._queueDelete = false;
35
65
 
36
66
  if (node instanceof ElementNode) {
37
- parent.rendered && node.render(true);
67
+ node.parent!.rendered && node.render(true);
68
+ if (prevParent !== undefined) {
69
+ pushDeleteQueue(node, 1);
70
+ }
38
71
  } else if (isElementText(parent)) {
39
72
  // TextNodes can be placed outside of <text> nodes when <Show> is used as placeholder
40
73
  parent.text = parent.getText();
@@ -45,13 +78,11 @@ export default {
45
78
  },
46
79
  removeNode(parent: ElementNode, node: SolidNode): void {
47
80
  log('REMOVE: ', parent, node);
81
+
48
82
  parent.removeChild(node);
49
- node._queueDelete = true;
83
+
50
84
  if (node instanceof ElementNode) {
51
- // Solid replacesNodes to move them (via insert and remove),
52
- // so we need to wait for the next microtask to destroy the node
53
- // in the event it gets a new parent.
54
- queueMicrotask(() => node.destroy());
85
+ pushDeleteQueue(node, -1);
55
86
  }
56
87
  },
57
88
  getParentNode(node: SolidNode): ElementNode | ElementText | undefined {
@@ -1,8 +0,0 @@
1
- import 'solid-js';
2
- declare module 'solid-js' {
3
- namespace JSX {
4
- interface Directives {
5
- model: [() => any, (v: any) => any];
6
- }
7
- }
8
- }