@lightningtv/solid 2.4.5 → 2.4.6
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/Column.d.ts +4 -0
- package/dist/src/primitives/Column.jsx +23 -0
- package/dist/src/primitives/Column.jsx.map +1 -0
- package/dist/src/primitives/Row.d.ts +4 -0
- package/dist/src/primitives/Row.jsx +22 -0
- package/dist/src/primitives/Row.jsx.map +1 -0
- package/dist/src/primitives/index.d.ts +7 -0
- package/dist/src/primitives/index.js +6 -0
- package/dist/src/primitives/index.js.map +1 -1
- package/dist/src/primitives/types.d.ts +60 -0
- package/dist/src/primitives/types.js +2 -0
- package/dist/src/primitives/types.js.map +1 -0
- package/dist/src/primitives/utils/chainFunctions.d.ts +4 -0
- package/dist/src/primitives/utils/chainFunctions.js +21 -0
- package/dist/src/primitives/utils/chainFunctions.js.map +1 -0
- package/dist/src/primitives/utils/createSpriteMap.d.ts +9 -0
- package/dist/src/primitives/utils/createSpriteMap.js +18 -0
- package/dist/src/primitives/utils/createSpriteMap.js.map +1 -0
- package/dist/src/primitives/utils/handleNavigation.d.ts +6 -0
- package/dist/src/primitives/utils/handleNavigation.js +79 -0
- package/dist/src/primitives/utils/handleNavigation.js.map +1 -0
- package/dist/src/primitives/utils/scrollToIndex.d.ts +2 -0
- package/dist/src/primitives/utils/scrollToIndex.js +33 -0
- package/dist/src/primitives/utils/scrollToIndex.js.map +1 -0
- package/dist/src/primitives/utils/withScrolling.d.ts +9 -0
- package/dist/src/primitives/utils/withScrolling.js +106 -0
- package/dist/src/primitives/utils/withScrolling.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/primitives/Column.tsx +51 -0
- package/src/primitives/Row.tsx +51 -0
- package/src/primitives/index.ts +8 -0
- package/src/primitives/types.ts +79 -0
- package/src/primitives/utils/chainFunctions.ts +29 -0
- package/src/primitives/utils/createSpriteMap.ts +32 -0
- package/src/primitives/utils/handleNavigation.ts +100 -0
- package/src/primitives/utils/withScrolling.ts +136 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { renderer, type TextureMap } from '@lightningtv/core';
|
|
2
|
+
|
|
3
|
+
export interface SpriteDef {
|
|
4
|
+
name: string;
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createSpriteMap(
|
|
12
|
+
src: string,
|
|
13
|
+
subTextures: SpriteDef[],
|
|
14
|
+
): Record<string, InstanceType<TextureMap['SubTexture']>> {
|
|
15
|
+
const spriteMapTexture = renderer.createTexture('ImageTexture', {
|
|
16
|
+
src,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return subTextures.reduce<
|
|
20
|
+
Record<string, InstanceType<TextureMap['SubTexture']>>
|
|
21
|
+
>((acc, t) => {
|
|
22
|
+
const { x, y, width, height } = t;
|
|
23
|
+
acc[t.name] = renderer.createTexture('SubTexture', {
|
|
24
|
+
texture: spriteMapTexture,
|
|
25
|
+
x,
|
|
26
|
+
y,
|
|
27
|
+
width,
|
|
28
|
+
height,
|
|
29
|
+
});
|
|
30
|
+
return acc;
|
|
31
|
+
}, {});
|
|
32
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ElementNode, assertTruthy } from '@lightningtv/core';
|
|
2
|
+
import { type KeyHandler } from '@lightningtv/core/focusManager';
|
|
3
|
+
import type { NavigableElement, OnSelectedChanged } from '../types.js';
|
|
4
|
+
|
|
5
|
+
export function onGridFocus(this: ElementNode) {
|
|
6
|
+
if (!this || this.children.length === 0) return false;
|
|
7
|
+
|
|
8
|
+
this.selected = this.selected || 0;
|
|
9
|
+
let child = this.selected ? this.children[this.selected] : this.selectedNode;
|
|
10
|
+
|
|
11
|
+
while (child?.skipFocus) {
|
|
12
|
+
this.selected++;
|
|
13
|
+
child = this.children[this.selected];
|
|
14
|
+
}
|
|
15
|
+
if (!(child instanceof ElementNode)) return false;
|
|
16
|
+
child.setFocus();
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Converts params from onFocus to onSelectedChanged
|
|
21
|
+
export function handleOnSelect(onSelectedChanged: OnSelectedChanged) {
|
|
22
|
+
return function (this: NavigableElement) {
|
|
23
|
+
return onSelectedChanged.call(
|
|
24
|
+
this,
|
|
25
|
+
this.selected,
|
|
26
|
+
this,
|
|
27
|
+
this.children[this.selected] as ElementNode,
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function handleNavigation(
|
|
33
|
+
direction: 'up' | 'right' | 'down' | 'left',
|
|
34
|
+
): KeyHandler {
|
|
35
|
+
return function () {
|
|
36
|
+
const numChildren = this.children.length;
|
|
37
|
+
const wrap = this.wrap;
|
|
38
|
+
const lastSelected = this.selected || 0;
|
|
39
|
+
|
|
40
|
+
if (numChildren === 0) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (direction === 'right' || direction === 'down') {
|
|
45
|
+
do {
|
|
46
|
+
this.selected = ((this.selected || 0) % numChildren) + 1;
|
|
47
|
+
if (this.selected >= numChildren) {
|
|
48
|
+
if (!wrap) {
|
|
49
|
+
this.selected = -1;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
this.selected = 0;
|
|
53
|
+
}
|
|
54
|
+
} while (this.children[this.selected]?.skipFocus);
|
|
55
|
+
} else if (direction === 'left' || direction === 'up') {
|
|
56
|
+
do {
|
|
57
|
+
this.selected = ((this.selected || 0) % numChildren) - 1;
|
|
58
|
+
if (this.selected < 0) {
|
|
59
|
+
if (!wrap) {
|
|
60
|
+
this.selected = -1;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
this.selected = numChildren - 1;
|
|
64
|
+
}
|
|
65
|
+
} while (this.children[this.selected]?.skipFocus);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this.selected === -1) {
|
|
69
|
+
this.selected = lastSelected;
|
|
70
|
+
if (this.children[this.selected]?.states!.has('focus')) {
|
|
71
|
+
// This child is already focused, so bubble up to next handler
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const active = this.children[this.selected || 0];
|
|
76
|
+
assertTruthy(active instanceof ElementNode);
|
|
77
|
+
const navigableThis = this as NavigableElement;
|
|
78
|
+
|
|
79
|
+
navigableThis.onSelectedChanged &&
|
|
80
|
+
navigableThis.onSelectedChanged.call(
|
|
81
|
+
navigableThis,
|
|
82
|
+
navigableThis.selected,
|
|
83
|
+
navigableThis,
|
|
84
|
+
active,
|
|
85
|
+
lastSelected,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (this.plinko) {
|
|
89
|
+
// Set the next item to have the same selected index
|
|
90
|
+
// so we move up / down directly
|
|
91
|
+
const lastSelectedChild = this.children[lastSelected];
|
|
92
|
+
assertTruthy(lastSelectedChild instanceof ElementNode);
|
|
93
|
+
const num = lastSelectedChild.selected || 0;
|
|
94
|
+
active.selected =
|
|
95
|
+
num < active.children.length ? num : active.children.length - 1;
|
|
96
|
+
}
|
|
97
|
+
active.setFocus();
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ElementNode,
|
|
3
|
+
ElementText,
|
|
4
|
+
INode,
|
|
5
|
+
Styles,
|
|
6
|
+
} from '@lightningtv/core';
|
|
7
|
+
|
|
8
|
+
// Adds properties expected by withScrolling
|
|
9
|
+
export interface ScrollableElement extends ElementNode {
|
|
10
|
+
scrollIndex?: number;
|
|
11
|
+
selected: number;
|
|
12
|
+
offset?: number;
|
|
13
|
+
_targetPosition?: number;
|
|
14
|
+
_screenOffset?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// From the renderer, not exported
|
|
18
|
+
const InViewPort = 8;
|
|
19
|
+
const isNotShown = (node: ElementNode | ElementText) => {
|
|
20
|
+
return node.lng.renderState !== InViewPort;
|
|
21
|
+
};
|
|
22
|
+
/*
|
|
23
|
+
Auto Scrolling starts scrolling right away until the last item is shown. Keeping a full view of the list.
|
|
24
|
+
Edge starts scrolling when it reaches the edge of the viewport.
|
|
25
|
+
Always scroll moves the list every time
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export function withScrolling(isRow: boolean) {
|
|
29
|
+
const dimension = isRow ? 'width' : 'height';
|
|
30
|
+
const axis = isRow ? 'x' : 'y';
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
selected: number | ElementNode,
|
|
34
|
+
component?: ElementNode,
|
|
35
|
+
selectedElement?: ElementNode | ElementText,
|
|
36
|
+
lastSelected?: number,
|
|
37
|
+
) => {
|
|
38
|
+
let componentRef = component as ScrollableElement;
|
|
39
|
+
if (typeof selected !== 'number') {
|
|
40
|
+
componentRef = selected as ScrollableElement;
|
|
41
|
+
selected = componentRef.selected || 0;
|
|
42
|
+
}
|
|
43
|
+
if (!componentRef || !componentRef.children.length) return;
|
|
44
|
+
|
|
45
|
+
const lng = componentRef.lng as INode;
|
|
46
|
+
const screenSize = isRow ? lng.stage.root.width : lng.stage.root.height;
|
|
47
|
+
// Determine if movement is incremental or decremental
|
|
48
|
+
const isIncrementing =
|
|
49
|
+
lastSelected === undefined || lastSelected - 1 !== selected;
|
|
50
|
+
|
|
51
|
+
if (componentRef._screenOffset === undefined) {
|
|
52
|
+
componentRef._screenOffset =
|
|
53
|
+
componentRef.offset ??
|
|
54
|
+
(isRow ? lng.absX : lng.absY) - componentRef[axis];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const screenOffset = componentRef._screenOffset;
|
|
58
|
+
const gap = componentRef.gap || 0;
|
|
59
|
+
const scroll = componentRef.scroll || 'auto';
|
|
60
|
+
|
|
61
|
+
// Allows manual position control
|
|
62
|
+
const targetPosition = componentRef._targetPosition ?? componentRef[axis];
|
|
63
|
+
const rootPosition = isIncrementing
|
|
64
|
+
? Math.min(targetPosition, componentRef[axis])
|
|
65
|
+
: Math.max(targetPosition, componentRef[axis]);
|
|
66
|
+
componentRef.offset = componentRef.offset ?? rootPosition;
|
|
67
|
+
const offset = componentRef.offset;
|
|
68
|
+
selectedElement =
|
|
69
|
+
selectedElement || (componentRef.children[selected] as ElementNode);
|
|
70
|
+
const selectedPosition = selectedElement[axis] ?? 0;
|
|
71
|
+
const selectedSize = selectedElement[dimension] ?? 0;
|
|
72
|
+
const selectedScale =
|
|
73
|
+
selectedElement.scale ??
|
|
74
|
+
(selectedElement.style?.focus as Styles)?.scale ??
|
|
75
|
+
1;
|
|
76
|
+
const selectedSizeScaled = selectedSize * selectedScale;
|
|
77
|
+
const containerSize = componentRef[dimension] ?? 0;
|
|
78
|
+
const maxOffset = Math.min(
|
|
79
|
+
screenSize - containerSize - screenOffset - 2 * gap,
|
|
80
|
+
0,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Determine the next element based on whether incrementing or decrementing
|
|
84
|
+
const nextIndex = isIncrementing ? selected + 1 : selected - 1;
|
|
85
|
+
const nextElement = componentRef.children[nextIndex] || null;
|
|
86
|
+
|
|
87
|
+
// Default nextPosition to align with the selected position and offset
|
|
88
|
+
let nextPosition = rootPosition;
|
|
89
|
+
|
|
90
|
+
// Update nextPosition based on scroll type and specific conditions
|
|
91
|
+
if (selectedElement.centerScroll) {
|
|
92
|
+
nextPosition = -selectedPosition + (screenSize - selectedSizeScaled) / 2;
|
|
93
|
+
} else if (scroll === 'always') {
|
|
94
|
+
nextPosition = -selectedPosition + offset;
|
|
95
|
+
} else if (scroll === 'center') {
|
|
96
|
+
nextPosition =
|
|
97
|
+
-selectedPosition +
|
|
98
|
+
(screenSize - selectedSizeScaled) / 2 -
|
|
99
|
+
screenOffset;
|
|
100
|
+
} else if (!nextElement) {
|
|
101
|
+
// If at the last element, align to end
|
|
102
|
+
nextPosition = isIncrementing ? maxOffset : offset;
|
|
103
|
+
} else if (scroll === 'auto') {
|
|
104
|
+
if (
|
|
105
|
+
isIncrementing &&
|
|
106
|
+
componentRef.scrollIndex &&
|
|
107
|
+
componentRef.scrollIndex > 0 &&
|
|
108
|
+
componentRef.selected >= componentRef.scrollIndex
|
|
109
|
+
) {
|
|
110
|
+
nextPosition = rootPosition - selectedSize - gap;
|
|
111
|
+
} else if (isIncrementing) {
|
|
112
|
+
nextPosition = -selectedPosition + offset;
|
|
113
|
+
} else {
|
|
114
|
+
nextPosition = rootPosition + selectedSize + gap;
|
|
115
|
+
}
|
|
116
|
+
} // Handle Edge scrolling
|
|
117
|
+
else if (isIncrementing && isNotShown(nextElement)) {
|
|
118
|
+
nextPosition = rootPosition - selectedSize - gap;
|
|
119
|
+
} else if (isNotShown(nextElement)) {
|
|
120
|
+
nextPosition = -selectedPosition + offset;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Prevent container from moving beyond bounds
|
|
124
|
+
nextPosition =
|
|
125
|
+
isIncrementing && scroll !== 'always'
|
|
126
|
+
? Math.max(nextPosition, maxOffset)
|
|
127
|
+
: Math.min(nextPosition, offset);
|
|
128
|
+
|
|
129
|
+
// Update position if it has changed
|
|
130
|
+
if (componentRef[axis] !== nextPosition) {
|
|
131
|
+
componentRef[axis] = nextPosition;
|
|
132
|
+
// Store the new position to keep track during animations
|
|
133
|
+
componentRef._targetPosition = nextPosition;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|