@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.
Files changed (126) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +6 -0
  3. package/dist/src/jsx-runtime.d.ts +1 -3
  4. package/dist/src/primitives/Column.jsx +9 -10
  5. package/dist/src/primitives/Column.jsx.map +1 -1
  6. package/dist/src/primitives/Grid.d.ts +15 -6
  7. package/dist/src/primitives/Grid.jsx +35 -22
  8. package/dist/src/primitives/Grid.jsx.map +1 -1
  9. package/dist/src/primitives/Image.d.ts +8 -0
  10. package/dist/src/primitives/Image.jsx +24 -0
  11. package/dist/src/primitives/Image.jsx.map +1 -0
  12. package/dist/src/primitives/KeepAlive.d.ts +30 -0
  13. package/dist/src/primitives/KeepAlive.jsx +77 -0
  14. package/dist/src/primitives/KeepAlive.jsx.map +1 -0
  15. package/dist/src/primitives/Lazy.d.ts +8 -7
  16. package/dist/src/primitives/Lazy.jsx +49 -23
  17. package/dist/src/primitives/Lazy.jsx.map +1 -1
  18. package/dist/src/primitives/Marquee.d.ts +64 -0
  19. package/dist/src/primitives/Marquee.jsx +86 -0
  20. package/dist/src/primitives/Marquee.jsx.map +1 -0
  21. package/dist/src/primitives/Preserve.d.ts +4 -0
  22. package/dist/src/primitives/Preserve.jsx +11 -0
  23. package/dist/src/primitives/Preserve.jsx.map +1 -0
  24. package/dist/src/primitives/Row.jsx +9 -10
  25. package/dist/src/primitives/Row.jsx.map +1 -1
  26. package/dist/src/primitives/Suspense.d.ts +22 -0
  27. package/dist/src/primitives/Suspense.jsx +33 -0
  28. package/dist/src/primitives/Suspense.jsx.map +1 -0
  29. package/dist/src/primitives/Virtual.d.ts +18 -0
  30. package/dist/src/primitives/Virtual.jsx +434 -0
  31. package/dist/src/primitives/Virtual.jsx.map +1 -0
  32. package/dist/src/primitives/VirtualGrid.d.ts +13 -0
  33. package/dist/src/primitives/VirtualGrid.jsx +139 -0
  34. package/dist/src/primitives/VirtualGrid.jsx.map +1 -0
  35. package/dist/src/primitives/VirtualList.d.ts +11 -0
  36. package/dist/src/primitives/VirtualList.jsx +96 -0
  37. package/dist/src/primitives/VirtualList.jsx.map +1 -0
  38. package/dist/src/primitives/VirtualRow.d.ts +13 -0
  39. package/dist/src/primitives/VirtualRow.jsx +97 -0
  40. package/dist/src/primitives/VirtualRow.jsx.map +1 -0
  41. package/dist/src/primitives/Visible.d.ts +0 -1
  42. package/dist/src/primitives/Visible.jsx +1 -1
  43. package/dist/src/primitives/Visible.jsx.map +1 -1
  44. package/dist/src/primitives/announcer/announcer.d.ts +2 -0
  45. package/dist/src/primitives/announcer/announcer.js +7 -5
  46. package/dist/src/primitives/announcer/announcer.js.map +1 -1
  47. package/dist/src/primitives/announcer/index.d.ts +5 -1
  48. package/dist/src/primitives/announcer/index.js +8 -2
  49. package/dist/src/primitives/announcer/index.js.map +1 -1
  50. package/dist/src/primitives/announcer/speech.d.ts +2 -2
  51. package/dist/src/primitives/announcer/speech.js +157 -28
  52. package/dist/src/primitives/announcer/speech.js.map +1 -1
  53. package/dist/src/primitives/createFocusStack.d.ts +4 -4
  54. package/dist/src/primitives/createFocusStack.jsx +15 -6
  55. package/dist/src/primitives/createFocusStack.jsx.map +1 -1
  56. package/dist/src/primitives/createTag.d.ts +8 -0
  57. package/dist/src/primitives/createTag.jsx +20 -0
  58. package/dist/src/primitives/createTag.jsx.map +1 -0
  59. package/dist/src/primitives/index.d.ts +13 -3
  60. package/dist/src/primitives/index.js +13 -3
  61. package/dist/src/primitives/index.js.map +1 -1
  62. package/dist/src/primitives/types.d.ts +3 -0
  63. package/dist/src/primitives/useHold.d.ts +27 -0
  64. package/dist/src/primitives/useHold.js +54 -0
  65. package/dist/src/primitives/useHold.js.map +1 -0
  66. package/dist/src/primitives/useMouse.d.ts +24 -1
  67. package/dist/src/primitives/useMouse.js +153 -47
  68. package/dist/src/primitives/useMouse.js.map +1 -1
  69. package/dist/src/primitives/utils/chainFunctions.d.ts +30 -4
  70. package/dist/src/primitives/utils/chainFunctions.js +14 -3
  71. package/dist/src/primitives/utils/chainFunctions.js.map +1 -1
  72. package/dist/src/primitives/utils/createBlurredImage.d.ts +56 -0
  73. package/dist/src/primitives/utils/createBlurredImage.js +223 -0
  74. package/dist/src/primitives/utils/createBlurredImage.js.map +1 -0
  75. package/dist/src/primitives/utils/createSpriteMap.d.ts +2 -2
  76. package/dist/src/primitives/utils/createSpriteMap.js.map +1 -1
  77. package/dist/src/primitives/utils/handleNavigation.d.ts +85 -5
  78. package/dist/src/primitives/utils/handleNavigation.js +242 -69
  79. package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
  80. package/dist/src/primitives/utils/withScrolling.d.ts +8 -1
  81. package/dist/src/primitives/utils/withScrolling.js +25 -6
  82. package/dist/src/primitives/utils/withScrolling.js.map +1 -1
  83. package/dist/src/render.d.ts +6 -5
  84. package/dist/src/render.js +4 -0
  85. package/dist/src/render.js.map +1 -1
  86. package/dist/src/solidOpts.d.ts +3 -2
  87. package/dist/src/solidOpts.js +31 -15
  88. package/dist/src/solidOpts.js.map +1 -1
  89. package/dist/src/universal.d.ts +25 -0
  90. package/dist/src/universal.js +232 -0
  91. package/dist/src/universal.js.map +1 -0
  92. package/dist/src/utils.d.ts +2 -0
  93. package/dist/src/utils.js +8 -0
  94. package/dist/src/utils.js.map +1 -1
  95. package/dist/tsconfig.tsbuildinfo +1 -1
  96. package/jsx-runtime.d.ts +2 -4
  97. package/package.json +19 -10
  98. package/src/primitives/Column.tsx +10 -12
  99. package/src/primitives/Grid.tsx +57 -33
  100. package/src/primitives/Image.tsx +36 -0
  101. package/src/primitives/KeepAlive.tsx +124 -0
  102. package/src/primitives/Lazy.tsx +60 -37
  103. package/src/primitives/Marquee.tsx +149 -0
  104. package/src/primitives/Preserve.tsx +18 -0
  105. package/src/primitives/Row.tsx +11 -12
  106. package/src/primitives/Suspense.tsx +39 -0
  107. package/src/primitives/Virtual.tsx +478 -0
  108. package/src/primitives/VirtualGrid.tsx +199 -0
  109. package/src/primitives/Visible.tsx +1 -2
  110. package/src/primitives/announcer/announcer.ts +16 -10
  111. package/src/primitives/announcer/index.ts +12 -2
  112. package/src/primitives/announcer/speech.ts +188 -27
  113. package/src/primitives/createFocusStack.tsx +18 -7
  114. package/src/primitives/createTag.tsx +31 -0
  115. package/src/primitives/index.ts +17 -3
  116. package/src/primitives/types.ts +10 -0
  117. package/src/primitives/useHold.ts +69 -0
  118. package/src/primitives/useMouse.ts +283 -66
  119. package/src/primitives/utils/chainFunctions.ts +40 -9
  120. package/src/primitives/utils/createBlurredImage.ts +366 -0
  121. package/src/primitives/utils/createSpriteMap.ts +6 -4
  122. package/src/primitives/utils/handleNavigation.ts +307 -84
  123. package/src/primitives/utils/withScrolling.ts +47 -16
  124. package/src/render.ts +9 -7
  125. package/src/solidOpts.ts +34 -19
  126. 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
+ }