@lightningtv/solid 2.10.3 → 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 +123 -35
- 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 +149 -72
- 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,80 +25,153 @@ 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');
|
|
26
34
|
|
|
27
|
-
const
|
|
28
|
-
|
|
35
|
+
const selected = () => {
|
|
36
|
+
if (props.wrap) {
|
|
37
|
+
return bufferSize();
|
|
38
|
+
}
|
|
39
|
+
return props.selected || 0;
|
|
40
|
+
};
|
|
29
41
|
|
|
30
|
-
const
|
|
31
|
-
|
|
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
|
+
};
|
|
32
55
|
|
|
33
|
-
const
|
|
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
|
+
};
|
|
63
|
+
|
|
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
|
+
});
|
|
77
|
+
|
|
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
|
-
|
|
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]);
|
|
39
91
|
}
|
|
40
92
|
|
|
41
93
|
const onSelectedChanged: lngp.OnSelectedChanged = function (_idx, elm, active, _lastIdx) {
|
|
42
94
|
let idx = _idx;
|
|
43
|
-
let lastIdx = _lastIdx;
|
|
95
|
+
let lastIdx = _lastIdx || 0;
|
|
96
|
+
const initialRun = idx === lastIdx;
|
|
44
97
|
|
|
45
|
-
if (
|
|
98
|
+
if (initialRun && !props.wrap) return;
|
|
46
99
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
}
|
|
52
106
|
|
|
53
|
-
|
|
54
|
-
setSlice(items().slice(start(), end()));
|
|
107
|
+
setSlice(getSlice());
|
|
55
108
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
}
|
|
60
127
|
|
|
61
|
-
|
|
62
|
-
|
|
128
|
+
if (props.onEndReachedThreshold !== undefined && cursor() >= items().length - props.onEndReachedThreshold) {
|
|
129
|
+
props.onEndReached?.();
|
|
130
|
+
}
|
|
63
131
|
}
|
|
132
|
+
const isRow = component === lngp.Row;
|
|
133
|
+
const prevChildPos = isRow
|
|
134
|
+
? this.x + active.x
|
|
135
|
+
: this.y + active.y;
|
|
64
136
|
|
|
65
137
|
queueMicrotask(() => {
|
|
66
138
|
this.updateLayout();
|
|
67
|
-
|
|
139
|
+
// if (this._initialPosition === undefined) {
|
|
140
|
+
// this.offset = 0;
|
|
141
|
+
// const axis = isRow ? 'x' : 'y';
|
|
142
|
+
// this._initialPosition = this[axis];
|
|
143
|
+
// }
|
|
68
144
|
if (component === lngp.Row) {
|
|
69
|
-
this.lng.x = prevChildPos - active.x;
|
|
145
|
+
this.lng.x = this._targetPosition = prevChildPos - active.x;
|
|
70
146
|
} else {
|
|
71
|
-
this.lng.y = prevChildPos - active.y;
|
|
147
|
+
this.lng.y = this._targetPosition = prevChildPos - active.y;
|
|
72
148
|
}
|
|
73
|
-
|
|
74
149
|
scrollFn(idx, elm, active, lastIdx);
|
|
75
150
|
});
|
|
76
151
|
};
|
|
77
152
|
|
|
78
153
|
const chainedOnSelectedChanged = lngp.chainFunctions(props.onSelectedChanged, onSelectedChanged)!;
|
|
79
154
|
|
|
80
|
-
const updateSelected = ([selected, _items]
|
|
81
|
-
if (!viewRef ||
|
|
82
|
-
const
|
|
155
|
+
const updateSelected = ([selected, _items]: [number?, any?]) => {
|
|
156
|
+
if (!viewRef || selected === undefined) return;
|
|
157
|
+
const sel = selected;
|
|
158
|
+
const item = items()[sel];
|
|
83
159
|
let active = viewRef.children.find(x => x.item === item);
|
|
84
160
|
const lastSelected = viewRef.selected;
|
|
85
161
|
|
|
86
162
|
if (active instanceof lng.ElementNode) {
|
|
87
163
|
viewRef.selected = viewRef.children.indexOf(active);
|
|
88
164
|
chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
|
|
165
|
+
active.setFocus();
|
|
89
166
|
} else {
|
|
90
|
-
setCursor(
|
|
91
|
-
setSlice(
|
|
167
|
+
setCursor(sel);
|
|
168
|
+
setSlice(getSlice());
|
|
92
169
|
queueMicrotask(() => {
|
|
93
170
|
viewRef.updateLayout();
|
|
94
171
|
active = viewRef.children.find(x => x.item === item);
|
|
95
172
|
if (active instanceof lng.ElementNode) {
|
|
96
173
|
viewRef.selected = viewRef.children.indexOf(active);
|
|
97
174
|
chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
|
|
98
|
-
active.setFocus();
|
|
99
175
|
}
|
|
100
176
|
});
|
|
101
177
|
}
|
|
@@ -105,56 +181,57 @@ function createVirtual<T>(
|
|
|
105
181
|
|
|
106
182
|
s.createEffect(s.on(items, () => {
|
|
107
183
|
if (!viewRef) return;
|
|
108
|
-
|
|
184
|
+
if (cursor() >= itemCount()) {
|
|
185
|
+
setCursor(Math.max(0, itemCount() - 1));
|
|
186
|
+
}
|
|
187
|
+
setSlice(getSlice());
|
|
109
188
|
}, { defer: true }));
|
|
110
189
|
|
|
111
190
|
return (<view
|
|
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
|
-
|
|
144
|
-
);
|
|
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
|
+
);
|
|
145
223
|
}
|
|
146
224
|
|
|
147
|
-
|
|
148
225
|
export function VirtualRow<T>(props: VirtualProps<T>) {
|
|
149
|
-
return createVirtual(lngp.Row, props, lngp.withScrolling(true), {
|
|
150
|
-
onLeft: lngp.navigableHandleNavigation,
|
|
151
|
-
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,
|
|
152
229
|
});
|
|
153
230
|
}
|
|
154
231
|
|
|
155
232
|
export function VirtualColumn<T>(props: VirtualProps<T>) {
|
|
156
|
-
return createVirtual(lngp.Column, props, lngp.withScrolling(false), {
|
|
157
|
-
onUp: lngp.navigableHandleNavigation,
|
|
158
|
-
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,
|
|
159
236
|
});
|
|
160
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
|
+
}
|