@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.
@@ -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 start = () =>
28
- utils.clamp(cursor() - bufferSize(), 0, Math.max(0, items().length - props.displaySize - bufferSize()));
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 end = () =>
31
- Math.min(items().length, cursor() + props.displaySize + bufferSize());
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(items().slice(start(), end()));
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
- this.selected = index;
39
- scrollFn(index, this);
40
- this.setFocus();
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 (idx === lastIdx) return;
98
+ if (initialRun && !props.wrap) return;
48
99
 
49
- const prevChildPos = component === lngp.Row
50
- ? this.x + active.x
51
- : this.y + active.y;
52
-
53
- const prevStart = start();
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
- setCursor(prevStart + idx);
56
- setSlice(items().slice(start(), end()));
107
+ setSlice(getSlice());
57
108
 
58
- const idxCorrection = prevStart - start();
59
- if (lastIdx) lastIdx += idxCorrection;
60
- idx += idxCorrection;
61
- this.selected += idxCorrection;
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
- if (props.onEndReachedThreshold !== undefined && cursor() >= items().length - props.onEndReachedThreshold) {
64
- props.onEndReached?.();
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
- s.createEffect(s.on([() => props.selected, items], ([selected]) => {
83
- if (!viewRef || selected == null) return;
84
- const item = items()![selected];
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(selected);
93
- setSlice(items().slice(start(), end()));
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
- setSlice(items().slice(start(), end()));
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
- {...props}
112
- scroll={props.scroll || 'always'}
113
- ref={lngp.chainRefs(el => { viewRef = el as lngp.NavigableElement; }, props.ref)}
114
- selected={props.selected || 0}
115
- cursor={cursor()}
116
- {/* @once */ ...keyHandlers}
117
- forwardFocus={/* @once */ lngp.navigableForwardFocus}
118
- scrollToIndex={/* @once */ scrollToIndex}
119
- onCreate={/* @once */
120
- props.selected
121
- ? lngp.chainFunctions(props.onCreate, scrollFn)
122
- : props.onCreate
123
- }
124
- onSelectedChanged={/* @once */ chainedOnSelectedChanged}
125
- style={/* @once */ lng.combineStyles(
126
- props.style,
127
- component === lngp.Row
128
- ? {
129
- display: 'flex',
130
- gap: 30,
131
- transition: { x: { duration: 250, easing: 'ease-out' } },
132
- }
133
- : {
134
- display: 'flex',
135
- flexDirection: 'column',
136
- gap: 30,
137
- transition: { y: { duration: 250, easing: 'ease-out' } },
138
- }
139
- )}
140
- >
141
- <List each={slice()}>{props.children}</List>
142
- </view>
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()}
@@ -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 { withScrolling } from './utils/withScrolling.js';
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
+ }