@lightningtv/solid 2.10.3 → 2.10.5

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,80 +25,157 @@ 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 Math.max(bufferSize(), scrollIndex());
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() - Math.max(bufferSize(), scrollIndex()), 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
- updateSelected([index]);
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
- const onSelectedChanged: lngp.OnSelectedChanged = function (_idx, elm, active, _lastIdx) {
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
+ let active = _active;
97
+ const initialRun = idx === lastIdx;
44
98
 
45
- if (idx === lastIdx) return;
99
+ if (initialRun && !props.wrap) return;
46
100
 
47
- const prevChildPos = component === lngp.Row
48
- ? this.x + active.x
49
- : this.y + active.y;
50
-
51
- const prevStart = start();
101
+ if (!initialRun) {
102
+ if (props.wrap) {
103
+ setCursor(c => utils.mod(c + idx - lastIdx, itemCount()));
104
+ } else {
105
+ setCursor(c => utils.clamp(c + idx - lastIdx, 0, Math.max(0, itemCount() - 1)));
106
+ }
52
107
 
53
- setCursor(prevStart + idx);
54
- setSlice(items().slice(start(), end()));
108
+ setSlice(getSlice());
55
109
 
56
- const idxCorrection = prevStart - start();
57
- if (lastIdx) lastIdx += idxCorrection;
58
- idx += idxCorrection;
59
- this.selected += idxCorrection;
110
+ const c = cursor();
111
+ const scroll = scrollType();
112
+ if (props.wrap) {
113
+ this.selected = Math.max(bufferSize(), scrollIndex());
114
+ } else if (props.scrollIndex) {
115
+ this.selected = Math.min(c, props.scrollIndex);
116
+ if (c >= itemCount() - props.displaySize + bufferSize()) {
117
+ this.selected = c - (itemCount() - props.displaySize) + bufferSize();
118
+ }
119
+ } else if (scroll === 'always' || scroll === 'auto') {
120
+ if (c < bufferSize()) {
121
+ this.selected = c;
122
+ } else if (c >= itemCount() - props.displaySize) {
123
+ this.selected = c - (itemCount() - props.displaySize) + bufferSize();
124
+ } else {
125
+ this.selected = bufferSize();
126
+ }
127
+ }
60
128
 
61
- if (props.onEndReachedThreshold !== undefined && cursor() >= items().length - props.onEndReachedThreshold) {
62
- props.onEndReached?.();
129
+ if (props.onEndReachedThreshold !== undefined && cursor() >= items().length - props.onEndReachedThreshold) {
130
+ props.onEndReached?.();
131
+ }
63
132
  }
133
+ const isRow = component === lngp.Row;
134
+ const prevChildPos = isRow
135
+ ? this.x + active.x
136
+ : this.y + active.y;
64
137
 
65
138
  queueMicrotask(() => {
66
139
  this.updateLayout();
67
-
140
+ if (this._initialPosition === undefined && props.wrap) {
141
+ this.offset = 0;
142
+ const axis = isRow ? 'x' : 'y';
143
+ this._initialPosition = this[axis];
144
+ if (scrollIndex() > 0) {
145
+ active = this.children[1] as lng.ElementNode;
146
+ }
147
+ }
68
148
  if (component === lngp.Row) {
69
- this.lng.x = prevChildPos - active.x;
149
+ this.lng.x = this._targetPosition = prevChildPos - active.x;
70
150
  } else {
71
- this.lng.y = prevChildPos - active.y;
151
+ this.lng.y = this._targetPosition = prevChildPos - active.y;
72
152
  }
73
-
74
153
  scrollFn(idx, elm, active, lastIdx);
75
154
  });
76
155
  };
77
156
 
78
157
  const chainedOnSelectedChanged = lngp.chainFunctions(props.onSelectedChanged, onSelectedChanged)!;
79
158
 
80
- const updateSelected = ([selected, _items] : [number?, any?]) => {
81
- if (!viewRef || !selected) return;
82
- const item = items()![selected];
159
+ const updateSelected = ([selected, _items]: [number?, any?]) => {
160
+ if (!viewRef || selected === undefined) return;
161
+ const sel = selected;
162
+ const item = items()[sel];
83
163
  let active = viewRef.children.find(x => x.item === item);
84
164
  const lastSelected = viewRef.selected;
85
165
 
86
166
  if (active instanceof lng.ElementNode) {
87
167
  viewRef.selected = viewRef.children.indexOf(active);
88
168
  chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
169
+ active.setFocus();
89
170
  } else {
90
- setCursor(selected);
91
- setSlice(items().slice(start(), end()));
171
+ setCursor(sel);
172
+ setSlice(getSlice());
92
173
  queueMicrotask(() => {
93
174
  viewRef.updateLayout();
94
175
  active = viewRef.children.find(x => x.item === item);
95
176
  if (active instanceof lng.ElementNode) {
96
177
  viewRef.selected = viewRef.children.indexOf(active);
97
178
  chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
98
- active.setFocus();
99
179
  }
100
180
  });
101
181
  }
@@ -105,56 +185,57 @@ function createVirtual<T>(
105
185
 
106
186
  s.createEffect(s.on(items, () => {
107
187
  if (!viewRef) return;
108
- setSlice(items().slice(start(), end()));
188
+ if (cursor() >= itemCount()) {
189
+ setCursor(Math.max(0, itemCount() - 1));
190
+ }
191
+ setSlice(getSlice());
109
192
  }, { defer: true }));
110
193
 
111
194
  return (<view
112
- {...props}
113
- scroll={props.scroll || 'always'}
114
- ref={lngp.chainRefs(el => { viewRef = el as lngp.NavigableElement; }, props.ref)}
115
- selected={props.selected || 0}
116
- cursor={cursor()}
117
- {/* @once */ ...keyHandlers}
118
- forwardFocus={/* @once */ lngp.navigableForwardFocus}
119
- scrollToIndex={/* @once */ scrollToIndex}
120
- onCreate={/* @once */
121
- props.selected
122
- ? lngp.chainFunctions(props.onCreate, scrollFn)
123
- : props.onCreate
124
- }
125
- onSelectedChanged={/* @once */ chainedOnSelectedChanged}
126
- style={/* @once */ lng.combineStyles(
127
- props.style,
128
- component === lngp.Row
129
- ? {
130
- display: 'flex',
131
- gap: 30,
132
- transition: { x: { duration: 250, easing: 'ease-out' } },
133
- }
134
- : {
135
- display: 'flex',
136
- flexDirection: 'column',
137
- gap: 30,
138
- transition: { y: { duration: 250, easing: 'ease-out' } },
139
- }
140
- )}
141
- >
142
- <List each={slice()}>{props.children}</List>
143
- </view>
144
- );
195
+ {...props}
196
+ ref={lngp.chainRefs(el => { viewRef = el as lngp.NavigableElement; }, props.ref)}
197
+ selected={selected()}
198
+ cursor={cursor()}
199
+ {...keyHandlers}
200
+ forwardFocus={/* @once */ lngp.navigableForwardFocus}
201
+ scrollToIndex={/* @once */ scrollToIndex}
202
+ onCreate={/* @once */
203
+ props.selected
204
+ ? lngp.chainFunctions(props.onCreate, scrollFn)
205
+ : props.onCreate
206
+ }
207
+ onSelectedChanged={/* @once */ chainedOnSelectedChanged}
208
+ style={/* @once */ lng.combineStyles(
209
+ props.style,
210
+ component === lngp.Row
211
+ ? {
212
+ display: 'flex',
213
+ gap: 30,
214
+ transition: { x: { duration: 250, easing: 'ease-out' } },
215
+ }
216
+ : {
217
+ display: 'flex',
218
+ flexDirection: 'column',
219
+ gap: 30,
220
+ transition: { y: { duration: 250, easing: 'ease-out' } },
221
+ }
222
+ )}
223
+ >
224
+ <List each={slice()}>{props.children}</List>
225
+ </view>
226
+ );
145
227
  }
146
228
 
147
-
148
229
  export function VirtualRow<T>(props: VirtualProps<T>) {
149
- return createVirtual(lngp.Row, props, lngp.withScrolling(true), {
150
- onLeft: lngp.navigableHandleNavigation,
151
- onRight: lngp.navigableHandleNavigation,
230
+ return createVirtual(lngp.Row, props, props.doScroll || lngp.withScrolling(true), {
231
+ onLeft: lngp.chainFunctions(props.onLeft, lngp.navigableHandleNavigation) as lng.KeyHandler,
232
+ onRight: lngp.chainFunctions(props.onRight, lngp.navigableHandleNavigation) as lng.KeyHandler,
152
233
  });
153
234
  }
154
235
 
155
236
  export function VirtualColumn<T>(props: VirtualProps<T>) {
156
- return createVirtual(lngp.Column, props, lngp.withScrolling(false), {
157
- onUp: lngp.navigableHandleNavigation,
158
- onDown: lngp.navigableHandleNavigation,
237
+ return createVirtual(lngp.Column, props, props.doScroll || lngp.withScrolling(false), {
238
+ onUp: lngp.chainFunctions(props.onUp, lngp.navigableHandleNavigation) as lng.KeyHandler,
239
+ onDown: lngp.chainFunctions(props.onDown, lngp.navigableHandleNavigation) as lng.KeyHandler,
159
240
  });
160
241
  }
@@ -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,12 @@ 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
+ /** @deprecated Use {@link scrollRow} or {@link scrollColumn} */
44
+ export function withScrolling(isRow: boolean): Scroller {
37
45
  const dimension = isRow ? 'width' : 'height';
38
46
  const axis = isRow ? 'x' : 'y';
39
47
 
40
- return (
41
- selected: number | ElementNode,
42
- component?: ElementNode,
43
- selectedElement?: ElementNode | ElementText,
44
- lastSelected?: number,
45
- ) => {
48
+ return (selected, component, selectedElement, lastSelected) => {
46
49
  let componentRef = component as ScrollableElement;
47
50
  if (typeof selected !== 'number') {
48
51
  componentRef = selected as ScrollableElement;
@@ -187,3 +190,6 @@ export function withScrolling(isRow: boolean) {
187
190
  }
188
191
  };
189
192
  }
193
+
194
+ export const scrollRow = /* @__PURE__ */ withScrolling(true);
195
+ export const scrollColumn = /* @__PURE__ */ withScrolling(false);
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
+ }