@lightningtv/solid 3.0.0-2 → 3.0.0-20
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/LICENSE +1 -1
- package/README.md +6 -0
- package/dist/src/jsx-runtime.d.ts +1 -3
- package/dist/src/primitives/Column.jsx +9 -10
- package/dist/src/primitives/Column.jsx.map +1 -1
- package/dist/src/primitives/Grid.d.ts +15 -6
- package/dist/src/primitives/Grid.jsx +35 -22
- package/dist/src/primitives/Grid.jsx.map +1 -1
- package/dist/src/primitives/Image.d.ts +8 -0
- package/dist/src/primitives/Image.jsx +24 -0
- package/dist/src/primitives/Image.jsx.map +1 -0
- package/dist/src/primitives/KeepAlive.d.ts +30 -0
- package/dist/src/primitives/KeepAlive.jsx +77 -0
- package/dist/src/primitives/KeepAlive.jsx.map +1 -0
- package/dist/src/primitives/Lazy.d.ts +8 -7
- package/dist/src/primitives/Lazy.jsx +49 -23
- package/dist/src/primitives/Lazy.jsx.map +1 -1
- package/dist/src/primitives/Marquee.d.ts +64 -0
- package/dist/src/primitives/Marquee.jsx +86 -0
- package/dist/src/primitives/Marquee.jsx.map +1 -0
- package/dist/src/primitives/Preserve.d.ts +4 -0
- package/dist/src/primitives/Preserve.jsx +11 -0
- package/dist/src/primitives/Preserve.jsx.map +1 -0
- package/dist/src/primitives/Row.jsx +9 -10
- package/dist/src/primitives/Row.jsx.map +1 -1
- package/dist/src/primitives/Suspense.d.ts +22 -0
- package/dist/src/primitives/Suspense.jsx +33 -0
- package/dist/src/primitives/Suspense.jsx.map +1 -0
- package/dist/src/primitives/Virtual.d.ts +18 -0
- package/dist/src/primitives/Virtual.jsx +434 -0
- package/dist/src/primitives/Virtual.jsx.map +1 -0
- package/dist/src/primitives/VirtualGrid.d.ts +13 -0
- package/dist/src/primitives/VirtualGrid.jsx +139 -0
- package/dist/src/primitives/VirtualGrid.jsx.map +1 -0
- package/dist/src/primitives/VirtualList.d.ts +11 -0
- package/dist/src/primitives/VirtualList.jsx +96 -0
- package/dist/src/primitives/VirtualList.jsx.map +1 -0
- package/dist/src/primitives/VirtualRow.d.ts +13 -0
- package/dist/src/primitives/VirtualRow.jsx +97 -0
- package/dist/src/primitives/VirtualRow.jsx.map +1 -0
- package/dist/src/primitives/Visible.d.ts +0 -1
- package/dist/src/primitives/Visible.jsx +1 -1
- package/dist/src/primitives/Visible.jsx.map +1 -1
- package/dist/src/primitives/announcer/announcer.d.ts +2 -0
- package/dist/src/primitives/announcer/announcer.js +7 -5
- package/dist/src/primitives/announcer/announcer.js.map +1 -1
- package/dist/src/primitives/announcer/index.d.ts +5 -1
- package/dist/src/primitives/announcer/index.js +8 -2
- package/dist/src/primitives/announcer/index.js.map +1 -1
- package/dist/src/primitives/announcer/speech.d.ts +2 -2
- package/dist/src/primitives/announcer/speech.js +157 -28
- package/dist/src/primitives/announcer/speech.js.map +1 -1
- package/dist/src/primitives/createFocusStack.d.ts +4 -4
- package/dist/src/primitives/createFocusStack.jsx +15 -6
- package/dist/src/primitives/createFocusStack.jsx.map +1 -1
- package/dist/src/primitives/createTag.d.ts +8 -0
- package/dist/src/primitives/createTag.jsx +20 -0
- package/dist/src/primitives/createTag.jsx.map +1 -0
- package/dist/src/primitives/index.d.ts +13 -3
- package/dist/src/primitives/index.js +13 -3
- package/dist/src/primitives/index.js.map +1 -1
- package/dist/src/primitives/types.d.ts +3 -0
- package/dist/src/primitives/useHold.d.ts +27 -0
- package/dist/src/primitives/useHold.js +54 -0
- package/dist/src/primitives/useHold.js.map +1 -0
- package/dist/src/primitives/useMouse.d.ts +24 -1
- package/dist/src/primitives/useMouse.js +153 -47
- package/dist/src/primitives/useMouse.js.map +1 -1
- package/dist/src/primitives/utils/chainFunctions.d.ts +30 -4
- package/dist/src/primitives/utils/chainFunctions.js +14 -3
- package/dist/src/primitives/utils/chainFunctions.js.map +1 -1
- package/dist/src/primitives/utils/createBlurredImage.d.ts +56 -0
- package/dist/src/primitives/utils/createBlurredImage.js +223 -0
- package/dist/src/primitives/utils/createBlurredImage.js.map +1 -0
- package/dist/src/primitives/utils/createSpriteMap.d.ts +2 -2
- package/dist/src/primitives/utils/createSpriteMap.js.map +1 -1
- package/dist/src/primitives/utils/handleNavigation.d.ts +85 -5
- package/dist/src/primitives/utils/handleNavigation.js +242 -69
- package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
- package/dist/src/primitives/utils/withScrolling.d.ts +8 -1
- package/dist/src/primitives/utils/withScrolling.js +25 -6
- package/dist/src/primitives/utils/withScrolling.js.map +1 -1
- package/dist/src/render.d.ts +6 -5
- package/dist/src/render.js +4 -0
- package/dist/src/render.js.map +1 -1
- package/dist/src/solidOpts.d.ts +3 -2
- package/dist/src/solidOpts.js +31 -15
- package/dist/src/solidOpts.js.map +1 -1
- package/dist/src/universal.d.ts +25 -0
- package/dist/src/universal.js +232 -0
- package/dist/src/universal.js.map +1 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.js +8 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jsx-runtime.d.ts +2 -4
- package/package.json +19 -10
- package/src/primitives/Column.tsx +10 -12
- package/src/primitives/Grid.tsx +57 -33
- package/src/primitives/Image.tsx +36 -0
- package/src/primitives/KeepAlive.tsx +124 -0
- package/src/primitives/Lazy.tsx +60 -37
- package/src/primitives/Marquee.tsx +149 -0
- package/src/primitives/Preserve.tsx +18 -0
- package/src/primitives/Row.tsx +11 -12
- package/src/primitives/Suspense.tsx +39 -0
- package/src/primitives/Virtual.tsx +478 -0
- package/src/primitives/VirtualGrid.tsx +199 -0
- package/src/primitives/Visible.tsx +1 -2
- package/src/primitives/announcer/announcer.ts +16 -10
- package/src/primitives/announcer/index.ts +12 -2
- package/src/primitives/announcer/speech.ts +188 -27
- package/src/primitives/createFocusStack.tsx +18 -7
- package/src/primitives/createTag.tsx +31 -0
- package/src/primitives/index.ts +17 -3
- package/src/primitives/types.ts +10 -0
- package/src/primitives/useHold.ts +69 -0
- package/src/primitives/useMouse.ts +283 -66
- package/src/primitives/utils/chainFunctions.ts +40 -9
- package/src/primitives/utils/createBlurredImage.ts +366 -0
- package/src/primitives/utils/createSpriteMap.ts +6 -4
- package/src/primitives/utils/handleNavigation.ts +307 -84
- package/src/primitives/utils/withScrolling.ts +47 -16
- package/src/render.ts +9 -7
- package/src/solidOpts.ts +34 -19
- package/src/utils.ts +10 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as s from 'solid-js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tracks all resources inside a component and renders a fallback until they are all resolved.
|
|
5
|
+
*
|
|
6
|
+
* ```tsx
|
|
7
|
+
* const [data] = createResource(async () => ...);
|
|
8
|
+
*
|
|
9
|
+
* <Suspense fallback={<LoadingIndicator />}>
|
|
10
|
+
* <view>
|
|
11
|
+
* <text>{data()}</text>
|
|
12
|
+
* </view>
|
|
13
|
+
* </Suspense>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* This is a modified version of the SolidJS Suspense component that works with Lightning.
|
|
17
|
+
*
|
|
18
|
+
* @see https://docs.solidjs.com/reference/components/suspense
|
|
19
|
+
*/
|
|
20
|
+
export function Suspense(props: {
|
|
21
|
+
fallback?: s.JSX.Element;
|
|
22
|
+
children: s.JSX.Element;
|
|
23
|
+
}): s.JSX.Element {
|
|
24
|
+
|
|
25
|
+
let children: s.JSX.Element;
|
|
26
|
+
|
|
27
|
+
let suspense = s.Suspense({
|
|
28
|
+
get children() {
|
|
29
|
+
return [children = s.children(() => props.children) as any];
|
|
30
|
+
},
|
|
31
|
+
}) as any as () => s.JSX.Element;
|
|
32
|
+
|
|
33
|
+
return <>
|
|
34
|
+
{suspense() ?? props.fallback}
|
|
35
|
+
<view hidden forwardFocus={0}>
|
|
36
|
+
{suspense() ? null : children}
|
|
37
|
+
</view>
|
|
38
|
+
</>
|
|
39
|
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import * as s from 'solid-js';
|
|
2
|
+
import * as lng from '@lightningtv/solid';
|
|
3
|
+
import * as lngp from '@lightningtv/solid/primitives';
|
|
4
|
+
import { List } from '@solid-primitives/list';
|
|
5
|
+
import * as utils from '../utils.js';
|
|
6
|
+
|
|
7
|
+
export type VirtualProps<T> = lng.NewOmit<lngp.RowProps, 'children'> & {
|
|
8
|
+
each: readonly T[] | undefined | null | false;
|
|
9
|
+
displaySize: number;
|
|
10
|
+
bufferSize?: number;
|
|
11
|
+
wrap?: boolean;
|
|
12
|
+
scrollIndex?: number;
|
|
13
|
+
onEndReached?: () => void;
|
|
14
|
+
onEndReachedThreshold?: number;
|
|
15
|
+
debugInfo?: boolean;
|
|
16
|
+
factorScale?: boolean;
|
|
17
|
+
uniformSize?: boolean;
|
|
18
|
+
children: (item: s.Accessor<T>, index: s.Accessor<number>) => s.JSX.Element;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function createVirtual<T>(
|
|
22
|
+
component: typeof lngp.Row | typeof lngp.Column,
|
|
23
|
+
props: VirtualProps<T>,
|
|
24
|
+
keyHandlers: Record<string, lng.KeyHandler>
|
|
25
|
+
) {
|
|
26
|
+
const isRow = component === lngp.Row;
|
|
27
|
+
const axis = isRow ? 'x' : 'y';
|
|
28
|
+
const [cursor, setCursor] = s.createSignal(props.selected ?? 0);
|
|
29
|
+
const bufferSize = s.createMemo(() => props.bufferSize || 2);
|
|
30
|
+
const scrollIndex = s.createMemo(() => props.scrollIndex || 0);
|
|
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 Math.max(bufferSize(), scrollIndex());
|
|
38
|
+
}
|
|
39
|
+
return props.selected || 0;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let cachedScaledSize: number | undefined;
|
|
43
|
+
let targetPosition: number | undefined;
|
|
44
|
+
let cachedAnimationController: lng.IAnimationController | undefined;
|
|
45
|
+
const uniformSize = s.createMemo(() => {
|
|
46
|
+
return props.uniformSize !== false;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
type SliceState = { start: number; slice: T[]; selected: number, delta: number, shiftBy: number, atStart: boolean };
|
|
50
|
+
const [slice, setSlice] = s.createSignal<SliceState>({
|
|
51
|
+
start: 0,
|
|
52
|
+
slice: [],
|
|
53
|
+
selected: 0,
|
|
54
|
+
delta: 0,
|
|
55
|
+
shiftBy: 0,
|
|
56
|
+
atStart: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function normalizeDeltaForWindow(delta: number, windowLen: number): number {
|
|
60
|
+
if (!windowLen) return 0;
|
|
61
|
+
const half = windowLen / 2;
|
|
62
|
+
if (delta > half) return delta - windowLen;
|
|
63
|
+
if (delta < -half) return delta + windowLen;
|
|
64
|
+
return delta;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function computeSize(selected: number = 0) {
|
|
68
|
+
if (uniformSize() && cachedScaledSize) {
|
|
69
|
+
return cachedScaledSize;
|
|
70
|
+
} else if (viewRef) {
|
|
71
|
+
const gap = viewRef.gap || 0;
|
|
72
|
+
const dimension = isRow ? 'width' : 'height'; // This can't be moved up as it depends on viewRef
|
|
73
|
+
const prevSelectedChild = viewRef.children[selected];
|
|
74
|
+
|
|
75
|
+
if (prevSelectedChild instanceof lng.ElementNode) {
|
|
76
|
+
const itemSize = prevSelectedChild[dimension] || 0;
|
|
77
|
+
const focusStyle = (prevSelectedChild.style?.focus as lng.NodeStyles);
|
|
78
|
+
const scale = (focusStyle?.scale ?? prevSelectedChild.scale ?? 1);
|
|
79
|
+
const scaledSize = itemSize * (props.factorScale ? scale : 1) + gap;
|
|
80
|
+
cachedScaledSize = scaledSize;
|
|
81
|
+
return scaledSize;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function computeSlice(c: number, delta: number, prev: SliceState): SliceState {
|
|
88
|
+
const total = itemCount();
|
|
89
|
+
if (total === 0) return { start: 0, slice: [], selected: 0, delta, shiftBy: 0, atStart: true };
|
|
90
|
+
|
|
91
|
+
const length = props.displaySize + bufferSize();
|
|
92
|
+
let start = prev.start;
|
|
93
|
+
let selected = prev.selected;
|
|
94
|
+
let atStart = prev.atStart;
|
|
95
|
+
let shiftBy = -delta;
|
|
96
|
+
|
|
97
|
+
switch (scrollType()) {
|
|
98
|
+
case 'always':
|
|
99
|
+
if (props.wrap) {
|
|
100
|
+
start = utils.mod(c - 1, total);
|
|
101
|
+
selected = 1;
|
|
102
|
+
} else {
|
|
103
|
+
start = utils.clamp(
|
|
104
|
+
c - bufferSize(),
|
|
105
|
+
0,
|
|
106
|
+
Math.max(0, total - props.displaySize - bufferSize()),
|
|
107
|
+
);
|
|
108
|
+
if (delta === 0 && c > 3) {
|
|
109
|
+
shiftBy = c < 3 ? -c : -2;
|
|
110
|
+
selected = 2;
|
|
111
|
+
} else {
|
|
112
|
+
selected =
|
|
113
|
+
c < bufferSize()
|
|
114
|
+
? c
|
|
115
|
+
: c >= total - props.displaySize
|
|
116
|
+
? c - (total - props.displaySize) + bufferSize()
|
|
117
|
+
: bufferSize();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case 'auto':
|
|
123
|
+
if (props.wrap) {
|
|
124
|
+
if (delta === 0) {
|
|
125
|
+
selected = scrollIndex() || 1;
|
|
126
|
+
start = utils.mod(c - (scrollIndex() || 1), total);
|
|
127
|
+
} else {
|
|
128
|
+
start = utils.mod(c - (prev.selected || 1), total);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
if (delta < 0) {
|
|
132
|
+
// Moving left
|
|
133
|
+
if (prev.start > 0 && prev.selected >= props.displaySize) {
|
|
134
|
+
// Move selection left inside slice
|
|
135
|
+
start = prev.start;
|
|
136
|
+
selected = prev.selected - 1;
|
|
137
|
+
} else if (prev.start > 0) {
|
|
138
|
+
// Move selection left inside slice
|
|
139
|
+
start = prev.start - 1;
|
|
140
|
+
selected = prev.selected;
|
|
141
|
+
// shiftBy = 0;
|
|
142
|
+
} else if (prev.start === 0 && !prev.atStart) {
|
|
143
|
+
start = 0;
|
|
144
|
+
selected = prev.selected - 1;
|
|
145
|
+
atStart = true;
|
|
146
|
+
} else if (selected >= props.displaySize - 1) {
|
|
147
|
+
// Shift window left, keep selection pinned
|
|
148
|
+
start = 0;
|
|
149
|
+
selected = prev.selected - 1;
|
|
150
|
+
} else {
|
|
151
|
+
start = 0;
|
|
152
|
+
selected = prev.selected - 1;
|
|
153
|
+
shiftBy = 0;
|
|
154
|
+
}
|
|
155
|
+
} else if (delta > 0) {
|
|
156
|
+
// Moving right
|
|
157
|
+
if (prev.selected < scrollIndex()) {
|
|
158
|
+
// Move selection right inside slice
|
|
159
|
+
start = prev.start;
|
|
160
|
+
selected = prev.selected + 1;
|
|
161
|
+
shiftBy = 0;
|
|
162
|
+
} else if (prev.selected === scrollIndex() || atStart) {
|
|
163
|
+
start = prev.start;
|
|
164
|
+
selected = prev.selected + 1;
|
|
165
|
+
atStart = false;
|
|
166
|
+
} else if (prev.start === 0 && prev.selected === 0) {
|
|
167
|
+
start = 0;
|
|
168
|
+
selected = 1;
|
|
169
|
+
atStart = false;
|
|
170
|
+
} else if (prev.start >= total - props.displaySize) {
|
|
171
|
+
// At end: clamp slice, selection drifts right
|
|
172
|
+
start = prev.start;
|
|
173
|
+
selected = c - start;
|
|
174
|
+
shiftBy = 0;
|
|
175
|
+
} else {
|
|
176
|
+
// Shift window right, keep selection pinned
|
|
177
|
+
start = prev.start + 1;
|
|
178
|
+
selected = Math.max(prev.selected, scrollIndex() + 1);;
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
// Initial setup
|
|
182
|
+
if (c > 0) {
|
|
183
|
+
start = Math.min(c - (scrollIndex() || 1), total - props.displaySize - bufferSize());
|
|
184
|
+
selected = Math.max(scrollIndex() || 1, c - start);
|
|
185
|
+
shiftBy = total - c < 3 ? c - total : -1;
|
|
186
|
+
atStart = false;
|
|
187
|
+
} else {
|
|
188
|
+
start = prev.start;
|
|
189
|
+
selected = prev.selected;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case 'edge':
|
|
196
|
+
const startScrolling = Math.max(1, props.displaySize + (atStart ? -1 : 0));
|
|
197
|
+
if (props.wrap) {
|
|
198
|
+
if (delta > 0) {
|
|
199
|
+
if (prev.selected < startScrolling) {
|
|
200
|
+
selected = prev.selected + 1;
|
|
201
|
+
shiftBy = 0;
|
|
202
|
+
} else if (prev.selected === startScrolling && atStart) {
|
|
203
|
+
selected = prev.selected + 1;
|
|
204
|
+
atStart = false;
|
|
205
|
+
} else {
|
|
206
|
+
start = utils.mod(prev.start + 1, total);
|
|
207
|
+
selected = prev.selected;
|
|
208
|
+
}
|
|
209
|
+
} else if (delta < 0) {
|
|
210
|
+
if (prev.selected > 1) {
|
|
211
|
+
selected = prev.selected - 1;
|
|
212
|
+
shiftBy = 0;
|
|
213
|
+
} else {
|
|
214
|
+
start = utils.mod(prev.start - 1, total);
|
|
215
|
+
selected = 1;
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
start = utils.mod(c - 1, total);
|
|
219
|
+
selected = 1;
|
|
220
|
+
shiftBy = -1;
|
|
221
|
+
atStart = false;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
if (delta === 0 && c > 0) {
|
|
225
|
+
//initial setup
|
|
226
|
+
selected = c > startScrolling ? startScrolling : c;
|
|
227
|
+
start = Math.max(0, c - startScrolling + 1);
|
|
228
|
+
shiftBy = c > startScrolling ? -1 : 0;
|
|
229
|
+
atStart = c < startScrolling;
|
|
230
|
+
} else if (delta > 0) {
|
|
231
|
+
if (prev.selected < startScrolling) {
|
|
232
|
+
selected = prev.selected + 1;
|
|
233
|
+
shiftBy = 0;
|
|
234
|
+
} else if (prev.selected === startScrolling && atStart) {
|
|
235
|
+
selected = prev.selected + 1;
|
|
236
|
+
atStart = false;
|
|
237
|
+
} else {
|
|
238
|
+
start = prev.start + 1;
|
|
239
|
+
selected = prev.selected;
|
|
240
|
+
atStart = false;
|
|
241
|
+
}
|
|
242
|
+
} else if (delta < 0) {
|
|
243
|
+
if (prev.selected > 1) {
|
|
244
|
+
selected = prev.selected - 1;
|
|
245
|
+
shiftBy = 0;
|
|
246
|
+
} else if (c > 1) {
|
|
247
|
+
start = Math.max(0, c - 1);
|
|
248
|
+
selected = 1;
|
|
249
|
+
} else if (c === 1) {
|
|
250
|
+
start = 0;
|
|
251
|
+
selected = 1;
|
|
252
|
+
} else {
|
|
253
|
+
start = 0;
|
|
254
|
+
selected = 0;
|
|
255
|
+
shiftBy = atStart ? 0 : shiftBy;
|
|
256
|
+
atStart = true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
case 'none':
|
|
263
|
+
default:
|
|
264
|
+
start = 0;
|
|
265
|
+
selected = c;
|
|
266
|
+
shiftBy = 0;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let newSlice = prev.slice;
|
|
271
|
+
if (start !== prev.start || newSlice.length === 0) {
|
|
272
|
+
newSlice = props.wrap
|
|
273
|
+
? Array.from(
|
|
274
|
+
{ length },
|
|
275
|
+
(_, i) => items()[utils.mod(start + i, total)],
|
|
276
|
+
) as T[]
|
|
277
|
+
: items().slice(start, start + length);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const state: SliceState = { start, slice: newSlice, selected, delta, shiftBy, atStart };
|
|
281
|
+
|
|
282
|
+
if (props.debugInfo) {
|
|
283
|
+
console.log(`[Virtual]`, {
|
|
284
|
+
cursor: c,
|
|
285
|
+
delta,
|
|
286
|
+
start,
|
|
287
|
+
selected,
|
|
288
|
+
shiftBy,
|
|
289
|
+
slice: state.slice,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return state;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let viewRef!: lngp.NavigableElement;
|
|
297
|
+
|
|
298
|
+
function scrollToIndex(this: lng.ElementNode, index: number) {
|
|
299
|
+
s.untrack(() => {
|
|
300
|
+
if (itemCount() === 0) return;
|
|
301
|
+
|
|
302
|
+
lastNavTime = performance.now();
|
|
303
|
+
if (originalPosition !== undefined) {
|
|
304
|
+
viewRef.lng[axis] = originalPosition;
|
|
305
|
+
targetPosition = originalPosition;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!lng.hasFocus(viewRef)) {
|
|
309
|
+
// force focus as scrollToIndex is manually called
|
|
310
|
+
viewRef.setFocus();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
updateSelected([utils.clamp(index, 0, itemCount() - 1)]);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let lastNavTime = 0;
|
|
318
|
+
function getAdaptiveDuration(duration: number = 250) {
|
|
319
|
+
const now = performance.now();
|
|
320
|
+
const delta = now - lastNavTime;
|
|
321
|
+
lastNavTime = now;
|
|
322
|
+
if (delta < duration) return delta;
|
|
323
|
+
return duration;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let originalPosition: number | undefined;
|
|
327
|
+
const onSelectedChanged: lngp.OnSelectedChanged = function (_idx, elm, _active, _lastIdx) {
|
|
328
|
+
let idx = _idx;
|
|
329
|
+
let lastIdx = _lastIdx || 0;
|
|
330
|
+
let active = _active;
|
|
331
|
+
const noChange = idx === lastIdx;
|
|
332
|
+
const total = itemCount();
|
|
333
|
+
originalPosition = originalPosition ?? elm[axis];
|
|
334
|
+
|
|
335
|
+
if (props.onSelectedChanged) {
|
|
336
|
+
props.onSelectedChanged.call(this as lngp.NavigableElement, idx, this as lngp.NavigableElement, active, lastIdx);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (noChange) return;
|
|
340
|
+
|
|
341
|
+
const rawDelta = idx - (lastIdx ?? 0);
|
|
342
|
+
const windowLen =
|
|
343
|
+
elm?.children?.length ?? props.displaySize + bufferSize();
|
|
344
|
+
const delta = props.wrap
|
|
345
|
+
? normalizeDeltaForWindow(rawDelta, windowLen)
|
|
346
|
+
: rawDelta;
|
|
347
|
+
|
|
348
|
+
setCursor(c => {
|
|
349
|
+
const next = c + delta;
|
|
350
|
+
return props.wrap
|
|
351
|
+
? utils.mod(next, total)
|
|
352
|
+
: utils.clamp(next, 0, total - 1);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const newState = computeSlice(cursor(), delta, slice());
|
|
356
|
+
setSlice(newState);
|
|
357
|
+
elm.selected = newState.selected;
|
|
358
|
+
|
|
359
|
+
if (
|
|
360
|
+
props.onEndReachedThreshold !== undefined &&
|
|
361
|
+
cursor() >= itemCount() - props.onEndReachedThreshold
|
|
362
|
+
) {
|
|
363
|
+
props.onEndReached?.();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (newState.shiftBy === 0) return;
|
|
367
|
+
|
|
368
|
+
const prevChildPos = (targetPosition ?? this[axis]) + active[axis];
|
|
369
|
+
|
|
370
|
+
queueMicrotask(() => {
|
|
371
|
+
elm.updateLayout();
|
|
372
|
+
const childSize = computeSize(slice().selected);
|
|
373
|
+
|
|
374
|
+
if (cachedAnimationController && cachedAnimationController.state === 'running') {
|
|
375
|
+
cachedAnimationController.stop();;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (lng.Config.animationsEnabled) {
|
|
379
|
+
this.lng[axis] = prevChildPos - active[axis];
|
|
380
|
+
let offset = this.lng[axis] + (childSize * slice().shiftBy);
|
|
381
|
+
targetPosition = offset;
|
|
382
|
+
cachedAnimationController = this.animate(
|
|
383
|
+
{ [axis]: offset },
|
|
384
|
+
{ ...this.animationSettings, duration: getAdaptiveDuration(this.animationSettings?.duration)}
|
|
385
|
+
).start();
|
|
386
|
+
} else {
|
|
387
|
+
this.lng[axis] = this.lng[axis]! + (childSize * slice().shiftBy);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const updateSelected = ([sel, _items]: [number?, any?]) => {
|
|
393
|
+
if (!viewRef || sel === undefined || itemCount() === 0) return;
|
|
394
|
+
const item = items()[sel];
|
|
395
|
+
setCursor(sel);
|
|
396
|
+
const newState = computeSlice(cursor(), 0, slice());
|
|
397
|
+
setSlice(newState);
|
|
398
|
+
|
|
399
|
+
queueMicrotask(() => {
|
|
400
|
+
viewRef.updateLayout();
|
|
401
|
+
let activeIndex = viewRef.children.findIndex(x => x.item === item);
|
|
402
|
+
if (activeIndex === -1) return;
|
|
403
|
+
viewRef.selected = activeIndex;
|
|
404
|
+
if (lng.hasFocus(viewRef)) {
|
|
405
|
+
viewRef.children[activeIndex]?.setFocus();
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
let doOnce = false;
|
|
411
|
+
s.createEffect(s.on([() => props.wrap, items], () => {
|
|
412
|
+
if (!viewRef || itemCount() === 0 || !props.wrap || doOnce) return;
|
|
413
|
+
doOnce = true;
|
|
414
|
+
// offset just for wrap so we keep one item before
|
|
415
|
+
queueMicrotask(() => {
|
|
416
|
+
const childSize = computeSize(slice().selected);
|
|
417
|
+
viewRef.lng[axis] = (viewRef.lng[axis] || 0) + (childSize * -1);
|
|
418
|
+
// Original Position is offset to support scrollToIndex
|
|
419
|
+
originalPosition = viewRef.lng[axis];
|
|
420
|
+
targetPosition = viewRef.lng[axis];
|
|
421
|
+
});
|
|
422
|
+
}));
|
|
423
|
+
|
|
424
|
+
s.createEffect(s.on([() => props.selected, items], updateSelected));
|
|
425
|
+
|
|
426
|
+
s.createEffect(s.on(items, () => {
|
|
427
|
+
if (!viewRef || itemCount() === 0) return;
|
|
428
|
+
if (cursor() >= itemCount()) {
|
|
429
|
+
setCursor(itemCount() - 1);
|
|
430
|
+
}
|
|
431
|
+
const newState = computeSlice(cursor(), 0, slice());
|
|
432
|
+
setSlice(newState);
|
|
433
|
+
viewRef.selected = newState.selected;
|
|
434
|
+
}));
|
|
435
|
+
|
|
436
|
+
return (<view
|
|
437
|
+
{...props}
|
|
438
|
+
{...keyHandlers}
|
|
439
|
+
ref={lngp.chainRefs(el => { viewRef = el as lngp.NavigableElement; }, props.ref)}
|
|
440
|
+
selected={selected()}
|
|
441
|
+
cursor={cursor()}
|
|
442
|
+
forwardFocus={/* @once */ lngp.navigableForwardFocus}
|
|
443
|
+
scrollToIndex={/* @once */ scrollToIndex}
|
|
444
|
+
onSelectedChanged={/* @once */ onSelectedChanged}
|
|
445
|
+
style={/* @once */ lng.combineStyles(
|
|
446
|
+
props.style,
|
|
447
|
+
component === lngp.Row
|
|
448
|
+
? {
|
|
449
|
+
display: 'flex',
|
|
450
|
+
gap: 30,
|
|
451
|
+
transition: { x: { duration: 250, easing: 'ease-out' } },
|
|
452
|
+
}
|
|
453
|
+
: {
|
|
454
|
+
display: 'flex',
|
|
455
|
+
flexDirection: 'column',
|
|
456
|
+
gap: 30,
|
|
457
|
+
transition: { y: { duration: 250, easing: 'ease-out' } },
|
|
458
|
+
}
|
|
459
|
+
)}
|
|
460
|
+
>
|
|
461
|
+
<List each={slice().slice}>{props.children}</List>
|
|
462
|
+
</view>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function VirtualRow<T>(props: VirtualProps<T>) {
|
|
467
|
+
return createVirtual(lngp.Row, props, {
|
|
468
|
+
onLeft: lngp.chainFunctions(props.onLeft, lngp.handleNavigation('left')) as lng.KeyHandler,
|
|
469
|
+
onRight: lngp.chainFunctions(props.onRight, lngp.handleNavigation('right')) as lng.KeyHandler,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function VirtualColumn<T>(props: VirtualProps<T>) {
|
|
474
|
+
return createVirtual(lngp.Column, props, {
|
|
475
|
+
onUp: lngp.chainFunctions(props.onUp, lngp.handleNavigation('up')) as lng.KeyHandler,
|
|
476
|
+
onDown: lngp.chainFunctions(props.onDown, lngp.handleNavigation('down')) as lng.KeyHandler,
|
|
477
|
+
});
|
|
478
|
+
}
|