@lightningtv/solid 2.4.5 → 2.4.7

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 (37) hide show
  1. package/dist/src/primitives/Column.d.ts +3 -0
  2. package/dist/src/primitives/Column.jsx +22 -0
  3. package/dist/src/primitives/Column.jsx.map +1 -0
  4. package/dist/src/primitives/Row.d.ts +3 -0
  5. package/dist/src/primitives/Row.jsx +21 -0
  6. package/dist/src/primitives/Row.jsx.map +1 -0
  7. package/dist/src/primitives/index.d.ts +7 -0
  8. package/dist/src/primitives/index.js +6 -0
  9. package/dist/src/primitives/index.js.map +1 -1
  10. package/dist/src/primitives/types.d.ts +60 -0
  11. package/dist/src/primitives/types.js +2 -0
  12. package/dist/src/primitives/types.js.map +1 -0
  13. package/dist/src/primitives/utils/chainFunctions.d.ts +4 -0
  14. package/dist/src/primitives/utils/chainFunctions.js +21 -0
  15. package/dist/src/primitives/utils/chainFunctions.js.map +1 -0
  16. package/dist/src/primitives/utils/createSpriteMap.d.ts +9 -0
  17. package/dist/src/primitives/utils/createSpriteMap.js +18 -0
  18. package/dist/src/primitives/utils/createSpriteMap.js.map +1 -0
  19. package/dist/src/primitives/utils/handleNavigation.d.ts +6 -0
  20. package/dist/src/primitives/utils/handleNavigation.js +79 -0
  21. package/dist/src/primitives/utils/handleNavigation.js.map +1 -0
  22. package/dist/src/primitives/utils/scrollToIndex.d.ts +2 -0
  23. package/dist/src/primitives/utils/scrollToIndex.js +33 -0
  24. package/dist/src/primitives/utils/scrollToIndex.js.map +1 -0
  25. package/dist/src/primitives/utils/withScrolling.d.ts +9 -0
  26. package/dist/src/primitives/utils/withScrolling.js +106 -0
  27. package/dist/src/primitives/utils/withScrolling.js.map +1 -0
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +16 -16
  30. package/src/primitives/Column.tsx +49 -0
  31. package/src/primitives/Row.tsx +49 -0
  32. package/src/primitives/index.ts +8 -0
  33. package/src/primitives/types.ts +79 -0
  34. package/src/primitives/utils/chainFunctions.ts +29 -0
  35. package/src/primitives/utils/createSpriteMap.ts +32 -0
  36. package/src/primitives/utils/handleNavigation.ts +100 -0
  37. 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
+ }