@lightningtv/solid 2.10.2 → 2.10.4
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/Virtual.d.ts +3 -0
- package/dist/src/primitives/Virtual.jsx +126 -38
- package/dist/src/primitives/Virtual.jsx.map +1 -1
- package/dist/src/primitives/VirtualGrid.jsx +1 -1
- package/dist/src/primitives/VirtualGrid.jsx.map +1 -1
- package/dist/src/primitives/index.d.ts +1 -1
- package/dist/src/primitives/index.js +1 -1
- package/dist/src/primitives/index.js.map +1 -1
- package/dist/src/primitives/utils/withScrolling.d.ts +2 -1
- package/dist/src/primitives/utils/withScrolling.js.map +1 -1
- package/dist/src/render.d.ts +1 -0
- package/dist/src/render.js +4 -0
- package/dist/src/render.js.map +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.js +5 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/primitives/Virtual.tsx +152 -74
- package/src/primitives/VirtualGrid.tsx +1 -1
- package/src/primitives/index.ts +1 -1
- package/src/primitives/utils/withScrolling.ts +9 -7
- package/src/render.ts +5 -0
- package/src/utils.ts +5 -0
|
@@ -8,6 +8,9 @@ export type VirtualProps<T> = lng.NewOmit<lngp.RowProps, 'children'> & {
|
|
|
8
8
|
each: readonly T[] | undefined | null | false;
|
|
9
9
|
displaySize: number;
|
|
10
10
|
bufferSize?: number;
|
|
11
|
+
wrap?: boolean;
|
|
12
|
+
scrollIndex?: number;
|
|
13
|
+
doScroll?: lngp.Scroller;
|
|
11
14
|
onEndReached?: () => void;
|
|
12
15
|
onEndReachedThreshold?: number;
|
|
13
16
|
fallback?: s.JSX.Element;
|
|
@@ -22,75 +25,147 @@ function createVirtual<T>(
|
|
|
22
25
|
) {
|
|
23
26
|
const [cursor, setCursor] = s.createSignal(props.selected ?? 0);
|
|
24
27
|
const bufferSize = s.createMemo(() => props.bufferSize || 2);
|
|
28
|
+
const scrollIndex = s.createMemo(() => {
|
|
29
|
+
return props.scrollIndex || 0;
|
|
30
|
+
});
|
|
25
31
|
const items = s.createMemo(() => props.each || []);
|
|
32
|
+
const itemCount = s.createMemo(() => items().length);
|
|
33
|
+
const scrollType = s.createMemo(() => props.scroll || 'auto');
|
|
34
|
+
|
|
35
|
+
const selected = () => {
|
|
36
|
+
if (props.wrap) {
|
|
37
|
+
return bufferSize();
|
|
38
|
+
}
|
|
39
|
+
return props.selected || 0;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const start = () => {
|
|
43
|
+
if (itemCount() === 0) return 0;
|
|
44
|
+
if (props.wrap) {
|
|
45
|
+
return utils.mod(cursor() - bufferSize(), itemCount());
|
|
46
|
+
}
|
|
47
|
+
if (scrollType() === 'always') {
|
|
48
|
+
return Math.min(Math.max(cursor() - bufferSize(), 0), itemCount() - props.displaySize - bufferSize());
|
|
49
|
+
}
|
|
50
|
+
if (scrollType() === 'auto') {
|
|
51
|
+
return utils.clamp(cursor() - Math.max(bufferSize(), scrollIndex()), 0, Math.max(0, itemCount() - props.displaySize - bufferSize()));
|
|
52
|
+
}
|
|
53
|
+
return utils.clamp(cursor() - bufferSize(), 0, Math.max(0, itemCount() - props.displaySize));
|
|
54
|
+
};
|
|
26
55
|
|
|
27
|
-
const
|
|
28
|
-
|
|
56
|
+
const end = () => {
|
|
57
|
+
if (itemCount() === 0) return 0;
|
|
58
|
+
if (props.wrap) {
|
|
59
|
+
return (start() + props.displaySize + bufferSize()) % itemCount();
|
|
60
|
+
}
|
|
61
|
+
return Math.min(itemCount(), start() + props.displaySize + bufferSize());
|
|
62
|
+
};
|
|
29
63
|
|
|
30
|
-
const
|
|
31
|
-
|
|
64
|
+
const getSlice = s.createMemo(() => {
|
|
65
|
+
if (itemCount() === 0) return [];
|
|
66
|
+
if (!props.wrap) {
|
|
67
|
+
return items().slice(start(), end());
|
|
68
|
+
}
|
|
69
|
+
// Wrapping slice
|
|
70
|
+
const sIdx = start();
|
|
71
|
+
const eIdx = (sIdx + props.displaySize + bufferSize()) % itemCount();
|
|
72
|
+
if (sIdx < eIdx) {
|
|
73
|
+
return items().slice(sIdx, eIdx);
|
|
74
|
+
}
|
|
75
|
+
return [...items().slice(sIdx), ...items().slice(0, eIdx)];
|
|
76
|
+
});
|
|
32
77
|
|
|
33
|
-
const [slice, setSlice] = s.createSignal(
|
|
78
|
+
const [slice, setSlice] = s.createSignal(getSlice());
|
|
34
79
|
|
|
35
80
|
let viewRef!: lngp.NavigableElement;
|
|
36
81
|
|
|
37
82
|
function scrollToIndex(this: lng.ElementNode, index: number) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
83
|
+
if (itemCount() === 0) return;
|
|
84
|
+
let target = index;
|
|
85
|
+
if (props.wrap) {
|
|
86
|
+
target = utils.mod(index, itemCount());
|
|
87
|
+
} else {
|
|
88
|
+
target = utils.clamp(index, 0, itemCount() - 1);
|
|
89
|
+
}
|
|
90
|
+
updateSelected([target]);
|
|
41
91
|
}
|
|
42
92
|
|
|
43
93
|
const onSelectedChanged: lngp.OnSelectedChanged = function (_idx, elm, active, _lastIdx) {
|
|
44
94
|
let idx = _idx;
|
|
45
|
-
let lastIdx = _lastIdx;
|
|
95
|
+
let lastIdx = _lastIdx || 0;
|
|
96
|
+
const initialRun = idx === lastIdx;
|
|
46
97
|
|
|
47
|
-
if (
|
|
98
|
+
if (initialRun && !props.wrap) return;
|
|
48
99
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
100
|
+
if (!initialRun) {
|
|
101
|
+
if (props.wrap) {
|
|
102
|
+
setCursor(c => utils.mod(c + idx - lastIdx, itemCount()));
|
|
103
|
+
} else {
|
|
104
|
+
setCursor(c => utils.clamp(c + idx - lastIdx, 0, Math.max(0, itemCount() - 1)));
|
|
105
|
+
}
|
|
54
106
|
|
|
55
|
-
|
|
56
|
-
setSlice(items().slice(start(), end()));
|
|
107
|
+
setSlice(getSlice());
|
|
57
108
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
109
|
+
const c = cursor();
|
|
110
|
+
const scroll = scrollType();
|
|
111
|
+
if (props.wrap) {
|
|
112
|
+
this.selected = bufferSize();
|
|
113
|
+
} else if (props.scrollIndex) {
|
|
114
|
+
this.selected = Math.min(c, props.scrollIndex);
|
|
115
|
+
if (c >= itemCount() - props.displaySize + bufferSize()) {
|
|
116
|
+
this.selected = c - (itemCount() - props.displaySize) + bufferSize();
|
|
117
|
+
}
|
|
118
|
+
} else if (scroll === 'always' || scroll === 'auto') {
|
|
119
|
+
if (c < bufferSize()) {
|
|
120
|
+
this.selected = c;
|
|
121
|
+
} else if (c >= itemCount() - props.displaySize) {
|
|
122
|
+
this.selected = c - (itemCount() - props.displaySize) + bufferSize();
|
|
123
|
+
} else {
|
|
124
|
+
this.selected = bufferSize();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
62
127
|
|
|
63
|
-
|
|
64
|
-
|
|
128
|
+
if (props.onEndReachedThreshold !== undefined && cursor() >= items().length - props.onEndReachedThreshold) {
|
|
129
|
+
props.onEndReached?.();
|
|
130
|
+
}
|
|
65
131
|
}
|
|
132
|
+
const isRow = component === lngp.Row;
|
|
133
|
+
const prevChildPos = isRow
|
|
134
|
+
? this.x + active.x
|
|
135
|
+
: this.y + active.y;
|
|
66
136
|
|
|
67
137
|
queueMicrotask(() => {
|
|
68
138
|
this.updateLayout();
|
|
69
|
-
|
|
139
|
+
// if (this._initialPosition === undefined) {
|
|
140
|
+
// this.offset = 0;
|
|
141
|
+
// const axis = isRow ? 'x' : 'y';
|
|
142
|
+
// this._initialPosition = this[axis];
|
|
143
|
+
// }
|
|
70
144
|
if (component === lngp.Row) {
|
|
71
|
-
this.lng.x = prevChildPos - active.x;
|
|
145
|
+
this.lng.x = this._targetPosition = prevChildPos - active.x;
|
|
72
146
|
} else {
|
|
73
|
-
this.lng.y = prevChildPos - active.y;
|
|
147
|
+
this.lng.y = this._targetPosition = prevChildPos - active.y;
|
|
74
148
|
}
|
|
75
|
-
|
|
76
149
|
scrollFn(idx, elm, active, lastIdx);
|
|
77
150
|
});
|
|
78
151
|
};
|
|
79
152
|
|
|
80
153
|
const chainedOnSelectedChanged = lngp.chainFunctions(props.onSelectedChanged, onSelectedChanged)!;
|
|
81
154
|
|
|
82
|
-
|
|
83
|
-
if (!viewRef || selected
|
|
84
|
-
const
|
|
155
|
+
const updateSelected = ([selected, _items]: [number?, any?]) => {
|
|
156
|
+
if (!viewRef || selected === undefined) return;
|
|
157
|
+
const sel = selected;
|
|
158
|
+
const item = items()[sel];
|
|
85
159
|
let active = viewRef.children.find(x => x.item === item);
|
|
86
160
|
const lastSelected = viewRef.selected;
|
|
87
161
|
|
|
88
162
|
if (active instanceof lng.ElementNode) {
|
|
89
163
|
viewRef.selected = viewRef.children.indexOf(active);
|
|
90
164
|
chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
|
|
165
|
+
active.setFocus();
|
|
91
166
|
} else {
|
|
92
|
-
setCursor(
|
|
93
|
-
setSlice(
|
|
167
|
+
setCursor(sel);
|
|
168
|
+
setSlice(getSlice());
|
|
94
169
|
queueMicrotask(() => {
|
|
95
170
|
viewRef.updateLayout();
|
|
96
171
|
active = viewRef.children.find(x => x.item === item);
|
|
@@ -100,60 +175,63 @@ function createVirtual<T>(
|
|
|
100
175
|
}
|
|
101
176
|
});
|
|
102
177
|
}
|
|
103
|
-
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
s.createEffect(s.on([() => props.selected, items], updateSelected));
|
|
104
181
|
|
|
105
182
|
s.createEffect(s.on(items, () => {
|
|
106
183
|
if (!viewRef) return;
|
|
107
|
-
|
|
184
|
+
if (cursor() >= itemCount()) {
|
|
185
|
+
setCursor(Math.max(0, itemCount() - 1));
|
|
186
|
+
}
|
|
187
|
+
setSlice(getSlice());
|
|
108
188
|
}, { defer: true }));
|
|
109
189
|
|
|
110
190
|
return (<view
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
191
|
+
{...props}
|
|
192
|
+
ref={lngp.chainRefs(el => { viewRef = el as lngp.NavigableElement; }, props.ref)}
|
|
193
|
+
selected={selected()}
|
|
194
|
+
cursor={cursor()}
|
|
195
|
+
{...keyHandlers}
|
|
196
|
+
forwardFocus={/* @once */ lngp.navigableForwardFocus}
|
|
197
|
+
scrollToIndex={/* @once */ scrollToIndex}
|
|
198
|
+
onCreate={/* @once */
|
|
199
|
+
props.selected
|
|
200
|
+
? lngp.chainFunctions(props.onCreate, scrollFn)
|
|
201
|
+
: props.onCreate
|
|
202
|
+
}
|
|
203
|
+
onSelectedChanged={/* @once */ chainedOnSelectedChanged}
|
|
204
|
+
style={/* @once */ lng.combineStyles(
|
|
205
|
+
props.style,
|
|
206
|
+
component === lngp.Row
|
|
207
|
+
? {
|
|
208
|
+
display: 'flex',
|
|
209
|
+
gap: 30,
|
|
210
|
+
transition: { x: { duration: 250, easing: 'ease-out' } },
|
|
211
|
+
}
|
|
212
|
+
: {
|
|
213
|
+
display: 'flex',
|
|
214
|
+
flexDirection: 'column',
|
|
215
|
+
gap: 30,
|
|
216
|
+
transition: { y: { duration: 250, easing: 'ease-out' } },
|
|
217
|
+
}
|
|
218
|
+
)}
|
|
219
|
+
>
|
|
220
|
+
<List each={slice()}>{props.children}</List>
|
|
221
|
+
</view>
|
|
222
|
+
);
|
|
144
223
|
}
|
|
145
224
|
|
|
146
|
-
|
|
147
225
|
export function VirtualRow<T>(props: VirtualProps<T>) {
|
|
148
|
-
return createVirtual(lngp.Row, props, lngp.withScrolling(true), {
|
|
149
|
-
onLeft: lngp.navigableHandleNavigation,
|
|
150
|
-
onRight: lngp.navigableHandleNavigation,
|
|
226
|
+
return createVirtual(lngp.Row, props, props.doScroll || lngp.withScrolling(true), {
|
|
227
|
+
onLeft: lngp.chainFunctions(props.onLeft, lngp.navigableHandleNavigation) as lng.KeyHandler,
|
|
228
|
+
onRight: lngp.chainFunctions(props.onRight, lngp.navigableHandleNavigation) as lng.KeyHandler,
|
|
151
229
|
});
|
|
152
230
|
}
|
|
153
231
|
|
|
154
232
|
export function VirtualColumn<T>(props: VirtualProps<T>) {
|
|
155
|
-
return createVirtual(lngp.Column, props, lngp.withScrolling(false), {
|
|
156
|
-
onUp: lngp.navigableHandleNavigation,
|
|
157
|
-
onDown: lngp.navigableHandleNavigation,
|
|
233
|
+
return createVirtual(lngp.Column, props, props.doScroll || lngp.withScrolling(false), {
|
|
234
|
+
onUp: lngp.chainFunctions(props.onUp, lngp.navigableHandleNavigation) as lng.KeyHandler,
|
|
235
|
+
onDown: lngp.chainFunctions(props.onDown, lngp.navigableHandleNavigation) as lng.KeyHandler,
|
|
158
236
|
});
|
|
159
237
|
}
|
|
@@ -164,7 +164,7 @@ export function VirtualGrid<T>(props: VirtualGridProps<T>): s.JSX.Element {
|
|
|
164
164
|
return (
|
|
165
165
|
<view
|
|
166
166
|
{...props}
|
|
167
|
-
scroll='always'
|
|
167
|
+
scroll={props.scroll || 'always'}
|
|
168
168
|
ref={lngp.chainRefs(el => { viewRef = el as lngp.NavigableElement; }, props.ref)}
|
|
169
169
|
selected={props.selected || 0}
|
|
170
170
|
cursor={cursor()}
|
package/src/primitives/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export * from './createFocusStack.jsx';
|
|
|
18
18
|
export * from './useHold.js';
|
|
19
19
|
export * from './VirtualGrid.jsx';
|
|
20
20
|
export * from './Virtual.jsx';
|
|
21
|
-
export
|
|
21
|
+
export * from './utils/withScrolling.js';
|
|
22
22
|
export {
|
|
23
23
|
type AnyFunction,
|
|
24
24
|
chainFunctions,
|
|
@@ -5,6 +5,13 @@ import type {
|
|
|
5
5
|
Styles,
|
|
6
6
|
} from '@lightningtv/core';
|
|
7
7
|
|
|
8
|
+
export type Scroller = (
|
|
9
|
+
selected: number | ElementNode,
|
|
10
|
+
component?: ElementNode,
|
|
11
|
+
selectedElement?: ElementNode | ElementText,
|
|
12
|
+
lastSelected?: number,
|
|
13
|
+
) => void;
|
|
14
|
+
|
|
8
15
|
// Adds properties expected by withScrolling
|
|
9
16
|
export interface ScrollableElement extends ElementNode {
|
|
10
17
|
scrollIndex?: number;
|
|
@@ -33,16 +40,11 @@ const isNotShown = (node: ElementNode | ElementText) => {
|
|
|
33
40
|
Always scroll moves the list every time
|
|
34
41
|
*/
|
|
35
42
|
|
|
36
|
-
export function withScrolling(isRow: boolean) {
|
|
43
|
+
export function withScrolling(isRow: boolean): Scroller {
|
|
37
44
|
const dimension = isRow ? 'width' : 'height';
|
|
38
45
|
const axis = isRow ? 'x' : 'y';
|
|
39
46
|
|
|
40
|
-
return (
|
|
41
|
-
selected: number | ElementNode,
|
|
42
|
-
component?: ElementNode,
|
|
43
|
-
selectedElement?: ElementNode | ElementText,
|
|
44
|
-
lastSelected?: number,
|
|
45
|
-
) => {
|
|
47
|
+
return (selected, component, selectedElement, lastSelected) => {
|
|
46
48
|
let componentRef = component as ScrollableElement;
|
|
47
49
|
if (typeof selected !== 'number') {
|
|
48
50
|
componentRef = selected as ScrollableElement;
|
package/src/render.ts
CHANGED
|
@@ -156,3 +156,8 @@ export const Text = (props: TextProps) => {
|
|
|
156
156
|
spread(el, props, false);
|
|
157
157
|
return el as unknown as JSXElement;
|
|
158
158
|
};
|
|
159
|
+
|
|
160
|
+
export function registerDefaultShader(name: string, shader: any) {
|
|
161
|
+
// noop for v2
|
|
162
|
+
// renderer.stage.shManager.registerShaderType('rounded', Rounded);
|
|
163
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -66,3 +66,8 @@ export const clamp = (value: number, min: number, max: number) =>
|
|
|
66
66
|
min < max
|
|
67
67
|
? Math.min(Math.max(value, min), max)
|
|
68
68
|
: Math.min(Math.max(value, max), min);
|
|
69
|
+
|
|
70
|
+
export function mod(n: number, m: number): number {
|
|
71
|
+
if (m === 0) return 0;
|
|
72
|
+
return ((n % m) + m) % m;
|
|
73
|
+
}
|