@lightningtv/solid 3.0.0-17 → 3.0.0-19

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 (72) hide show
  1. package/README.md +6 -0
  2. package/dist/src/primitives/Column.jsx +8 -7
  3. package/dist/src/primitives/Column.jsx.map +1 -1
  4. package/dist/src/primitives/Image.d.ts +8 -0
  5. package/dist/src/primitives/Image.jsx +24 -0
  6. package/dist/src/primitives/Image.jsx.map +1 -0
  7. package/dist/src/primitives/KeepAlive.d.ts +19 -5
  8. package/dist/src/primitives/KeepAlive.jsx +52 -21
  9. package/dist/src/primitives/KeepAlive.jsx.map +1 -1
  10. package/dist/src/primitives/Lazy.d.ts +6 -7
  11. package/dist/src/primitives/Lazy.jsx +23 -20
  12. package/dist/src/primitives/Lazy.jsx.map +1 -1
  13. package/dist/src/primitives/Row.jsx +8 -7
  14. package/dist/src/primitives/Row.jsx.map +1 -1
  15. package/dist/src/primitives/Virtual.d.ts +18 -0
  16. package/dist/src/primitives/Virtual.jsx +428 -0
  17. package/dist/src/primitives/Virtual.jsx.map +1 -0
  18. package/dist/src/primitives/VirtualGrid.d.ts +13 -0
  19. package/dist/src/primitives/VirtualGrid.jsx +139 -0
  20. package/dist/src/primitives/VirtualGrid.jsx.map +1 -0
  21. package/dist/src/primitives/VirtualList.d.ts +11 -0
  22. package/dist/src/primitives/VirtualList.jsx +96 -0
  23. package/dist/src/primitives/VirtualList.jsx.map +1 -0
  24. package/dist/src/primitives/VirtualRow.d.ts +13 -0
  25. package/dist/src/primitives/VirtualRow.jsx +97 -0
  26. package/dist/src/primitives/VirtualRow.jsx.map +1 -0
  27. package/dist/src/primitives/Visible.d.ts +0 -1
  28. package/dist/src/primitives/Visible.jsx +1 -1
  29. package/dist/src/primitives/Visible.jsx.map +1 -1
  30. package/dist/src/primitives/createFocusStack.d.ts +4 -4
  31. package/dist/src/primitives/createFocusStack.jsx +15 -6
  32. package/dist/src/primitives/createFocusStack.jsx.map +1 -1
  33. package/dist/src/primitives/index.d.ts +5 -1
  34. package/dist/src/primitives/index.js +5 -1
  35. package/dist/src/primitives/index.js.map +1 -1
  36. package/dist/src/primitives/types.d.ts +1 -0
  37. package/dist/src/primitives/useMouse.d.ts +6 -0
  38. package/dist/src/primitives/useMouse.js +26 -3
  39. package/dist/src/primitives/useMouse.js.map +1 -1
  40. package/dist/src/primitives/utils/handleNavigation.d.ts +0 -1
  41. package/dist/src/primitives/utils/handleNavigation.js +7 -5
  42. package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
  43. package/dist/src/primitives/utils/withScrolling.d.ts +5 -1
  44. package/dist/src/primitives/utils/withScrolling.js +11 -5
  45. package/dist/src/primitives/utils/withScrolling.js.map +1 -1
  46. package/dist/src/render.d.ts +1 -0
  47. package/dist/src/render.js +4 -0
  48. package/dist/src/render.js.map +1 -1
  49. package/dist/src/universal.d.ts +25 -0
  50. package/dist/src/universal.js +232 -0
  51. package/dist/src/universal.js.map +1 -0
  52. package/dist/src/utils.d.ts +2 -0
  53. package/dist/src/utils.js +8 -0
  54. package/dist/src/utils.js.map +1 -1
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +7 -4
  57. package/src/primitives/Column.tsx +10 -9
  58. package/src/primitives/Image.tsx +36 -0
  59. package/src/primitives/KeepAlive.tsx +124 -0
  60. package/src/primitives/Lazy.tsx +34 -38
  61. package/src/primitives/Row.tsx +11 -9
  62. package/src/primitives/Virtual.tsx +471 -0
  63. package/src/primitives/VirtualGrid.tsx +199 -0
  64. package/src/primitives/Visible.tsx +1 -2
  65. package/src/primitives/createFocusStack.tsx +18 -7
  66. package/src/primitives/index.ts +5 -1
  67. package/src/primitives/types.ts +1 -0
  68. package/src/primitives/useMouse.ts +52 -7
  69. package/src/primitives/utils/handleNavigation.ts +8 -5
  70. package/src/primitives/utils/withScrolling.ts +22 -14
  71. package/src/render.ts +5 -0
  72. package/src/utils.ts +10 -0
@@ -0,0 +1,199 @@
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
+ const columnScroll = lngp.withScrolling(false);
8
+
9
+ const rowStyles: lng.NodeStyles = {
10
+ display: 'flex',
11
+ flexWrap: 'wrap',
12
+ transition: {
13
+ y: true,
14
+ },
15
+ };
16
+
17
+ export type VirtualGridProps<T> = lng.NewOmit<lngp.RowProps, 'children'> & {
18
+ each: readonly T[] | undefined | null | false;
19
+ columns: number; // items per row
20
+ rows?: number; // number of visible rows (default: 1)
21
+ buffer?: number;
22
+ onEndReached?: () => void;
23
+ onEndReachedThreshold?: number;
24
+ children: (item: s.Accessor<T>, index: s.Accessor<number>) => s.JSX.Element;
25
+ };
26
+
27
+ export function VirtualGrid<T>(props: VirtualGridProps<T>): s.JSX.Element {
28
+ const bufferSize = () => props.buffer ?? 2;
29
+ const [ cursor, setCursor ] = s.createSignal(props.selected ?? 0);
30
+ const items = s.createMemo(() => props.each || []);
31
+ const itemsPerRow = () => props.columns;
32
+ const numberOfRows = () => props.rows ?? 1;
33
+ const totalVisibleItems = () => itemsPerRow() * numberOfRows();
34
+
35
+ const start = s.createMemo(() => {
36
+ const perRow = itemsPerRow();
37
+ const newRowIndex = Math.floor(cursor() / perRow);
38
+ const rawStart = newRowIndex * perRow - bufferSize() * perRow;
39
+ return Math.max(0, rawStart);
40
+ });
41
+
42
+ const end = s.createMemo(() => {
43
+ const perRow = itemsPerRow();
44
+ const newRowIndex = Math.floor(cursor() / perRow);
45
+ const rawEnd = (newRowIndex + bufferSize()) * perRow + totalVisibleItems();
46
+ return Math.min(items().length, rawEnd);
47
+ });
48
+
49
+ const [slice, setSlice] = s.createSignal(items().slice(start(), end()));
50
+
51
+ let viewRef!: lngp.NavigableElement;
52
+
53
+ function onVerticalNav(dir: -1 | 1): lngp.KeyHandler {
54
+ return function () {
55
+ const perRow = itemsPerRow();
56
+ const currentRowIndex = Math.floor(cursor() / perRow);
57
+ const maxRows = Math.floor(items().length / perRow);
58
+
59
+ if (
60
+ currentRowIndex === 0 && dir === -1
61
+ || currentRowIndex === maxRows && dir === 1
62
+ ) return;
63
+
64
+ const selected = this.selected || 0;
65
+ const offset = dir * perRow;
66
+ const newIndex = utils.clamp(selected + offset, 0, items().length - 1);
67
+ const lastIdx = selected;
68
+ this.selected = newIndex;
69
+ const active = this.children[this.selected];
70
+
71
+ if (active instanceof lng.ElementNode) {
72
+ active.setFocus();
73
+ chainedOnSelectedChanged.call(
74
+ this as lngp.NavigableElement,
75
+ this.selected,
76
+ this as lngp.NavigableElement,
77
+ active,
78
+ lastIdx
79
+ );
80
+ return true;
81
+ }
82
+ };
83
+ }
84
+
85
+ const onUp = onVerticalNav(-1);
86
+ const onDown = onVerticalNav(1);
87
+
88
+ const onSelectedChanged: lngp.OnSelectedChanged = function (_idx, elm, active, _lastIdx,) {
89
+ let idx = _idx;
90
+ let lastIdx = _lastIdx;
91
+ const perRow = itemsPerRow();
92
+ const newRowIndex = Math.floor(idx / perRow);
93
+ const prevRowIndex = Math.floor((lastIdx || 0) / perRow);
94
+ const prevStart = start();
95
+
96
+ setCursor(prevStart + idx);
97
+ if (newRowIndex === prevRowIndex) return;
98
+
99
+ setSlice(items().slice(start(), end()));
100
+
101
+ // this.selected is relative to the slice
102
+ // and it doesn't get corrected automatically after children change
103
+ const idxCorrection = prevStart - start();
104
+ if (lastIdx) lastIdx += idxCorrection;
105
+ idx += idxCorrection;
106
+ this.selected += idxCorrection;
107
+
108
+ if (props.onEndReachedThreshold !== undefined && cursor() >= items().length - props.onEndReachedThreshold) {
109
+ props.onEndReached?.();
110
+ }
111
+
112
+ queueMicrotask(() => {
113
+ const prevRowY = this.y + active.y;
114
+ this.updateLayout();
115
+ this.lng.y = prevRowY - active.y;
116
+ columnScroll(idx, elm, active, lastIdx);
117
+ });
118
+ };
119
+
120
+ const chainedOnSelectedChanged = lngp.chainFunctions(props.onSelectedChanged, onSelectedChanged)!;
121
+
122
+ let cachedSelected: number | undefined;
123
+ const updateSelected = ([selected, _items]: [number?, any?]) => {
124
+ if (!viewRef || selected == null) return;
125
+
126
+ if (cachedSelected !== undefined) {
127
+ selected = cachedSelected;
128
+ cachedSelected = undefined;
129
+ }
130
+
131
+ if (selected >= items().length && props.onEndReached) {
132
+ props.onEndReached?.();
133
+ cachedSelected = selected;
134
+ return;
135
+ }
136
+
137
+ const item = items()[selected];
138
+ let active = viewRef.children.find(x => x.item === item);
139
+ const lastSelected = viewRef.selected;
140
+
141
+ if (active instanceof lng.ElementNode) {
142
+ viewRef.selected = viewRef.children.indexOf(active);
143
+ active.setFocus();
144
+ chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
145
+ } else {
146
+ setCursor(selected);
147
+ setSlice(items().slice(start(), end()));
148
+
149
+ queueMicrotask(() => {
150
+ viewRef.updateLayout();
151
+ active = viewRef.children.find(x => x.item === item);
152
+ if (active instanceof lng.ElementNode) {
153
+ viewRef.selected = viewRef.children.indexOf(active);
154
+ active.setFocus();
155
+ chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
156
+ }
157
+ });
158
+ }
159
+ };
160
+
161
+ const scrollToIndex = (index: number) => {
162
+ s.untrack(() => updateSelected([index]));
163
+ }
164
+
165
+ s.createEffect(s.on([() => props.selected, items], updateSelected));
166
+
167
+ s.createEffect(
168
+ s.on(items, () => {
169
+ if (!viewRef) return;
170
+ if (cachedSelected !== undefined) {
171
+ updateSelected([cachedSelected]);
172
+ return;
173
+ }
174
+ setSlice(items().slice(start(), end()));
175
+ }, { defer: true })
176
+ );
177
+
178
+
179
+ return (
180
+ <view
181
+ {...props}
182
+ scroll={props.scroll || 'always'}
183
+ ref={lngp.chainRefs(el => { viewRef = el as lngp.NavigableElement; }, props.ref)}
184
+ selected={props.selected || 0}
185
+ cursor={cursor()}
186
+ onLeft={/* @once */ lngp.chainFunctions(props.onLeft, lngp.navigableHandleNavigation)}
187
+ onRight={/* @once */ lngp.chainFunctions(props.onRight, lngp.navigableHandleNavigation)}
188
+ onUp={/* @once */ lngp.chainFunctions(props.onUp, onUp)}
189
+ onDown={/* @once */ lngp.chainFunctions(props.onDown, onDown)}
190
+ forwardFocus={/* @once */ lngp.navigableForwardFocus}
191
+ onCreate={/* @once */ props.selected ? lngp.chainFunctions(props.onCreate, columnScroll) : props.onCreate}
192
+ scrollToIndex={/* @once */ scrollToIndex}
193
+ onSelectedChanged={/* @once */ chainedOnSelectedChanged}
194
+ style={/* @once */ lng.combineStyles(props.style, rowStyles)}
195
+ >
196
+ <List each={slice()}>{props.children}</List>
197
+ </view>
198
+ );
199
+ }
@@ -13,7 +13,6 @@ import { ElementNode } from '@lightningtv/solid';
13
13
  export function Visible<T>(props: {
14
14
  when: T | undefined | null | false;
15
15
  keyed?: boolean;
16
- fallback?: JSX.Element;
17
16
  children: JSX.Element;
18
17
  }): JSX.Element {
19
18
  let child: ChildrenReturn | undefined;
@@ -55,6 +54,6 @@ export function Visible<T>(props: {
55
54
  }
56
55
  });
57
56
 
58
- return c ? child : props.fallback;
57
+ return c || child ? child : null;
59
58
  }) as unknown as JSX.Element;
60
59
  };
@@ -17,7 +17,7 @@
17
17
  * - `restoreFocus()`: Restores focus to the last stored element and removes it from the stack. Returns `true` if successful, `false` otherwise.
18
18
  * - `clearFocusStack()`: Empties the focus stack.
19
19
  */
20
- import { createSignal, createContext, useContext, JSX } from 'solid-js';
20
+ import * as s from 'solid-js';
21
21
  import { type ElementNode } from '@lightningtv/solid';
22
22
 
23
23
  interface FocusStackContextType {
@@ -26,13 +26,16 @@ interface FocusStackContextType {
26
26
  clearFocusStack: () => void;
27
27
  }
28
28
 
29
- const FocusStackContext = createContext<FocusStackContextType | undefined>(undefined);
29
+ const FocusStackContext = s.createContext<FocusStackContextType | undefined>(undefined);
30
30
 
31
- export function FocusStackProvider(props: { children: JSX.Element}) {
32
- const [_focusStack, setFocusStack] = createSignal<ElementNode[]>([]);
31
+ export function FocusStackProvider(props: { children: s.JSX.Element}) {
32
+ const [_focusStack, setFocusStack] = s.createSignal<ElementNode[]>([]);
33
33
 
34
34
  function storeFocus(element: ElementNode, prevElement?: ElementNode) {
35
- setFocusStack((stack) => [...stack, prevElement || element]);
35
+ const elm = prevElement || element;
36
+ if (elm) {
37
+ setFocusStack(stack => [...stack, elm]);
38
+ }
36
39
  }
37
40
 
38
41
  function restoreFocus(): boolean {
@@ -59,10 +62,18 @@ export function FocusStackProvider(props: { children: JSX.Element}) {
59
62
  );
60
63
  }
61
64
 
62
- export function useFocusStack() {
63
- const context = useContext(FocusStackContext);
65
+ export function useFocusStack(autoClear = true) {
66
+ const context = s.useContext(FocusStackContext);
64
67
  if (!context) {
65
68
  throw new Error("useFocusStack must be used within a FocusStackProvider");
66
69
  }
70
+
71
+ if (autoClear) {
72
+ s.onCleanup(() => {
73
+ // delay clearing the focus stack so restoreFocus can happen first.
74
+ setTimeout(() => context.clearFocusStack(), 5);
75
+ });
76
+ }
77
+
67
78
  return context;
68
79
  }
@@ -4,6 +4,7 @@ export * from './createInfiniteItems.js';
4
4
  export * from './useMouse.js';
5
5
  export * from './portal.jsx';
6
6
  export * from './Lazy.jsx';
7
+ export * from './Image.jsx';
7
8
  export * from './Visible.jsx';
8
9
  export * from './router.js';
9
10
  export * from './Column.jsx';
@@ -16,7 +17,10 @@ export * from './Suspense.jsx';
16
17
  export * from './Marquee.jsx';
17
18
  export * from './createFocusStack.jsx';
18
19
  export * from './useHold.js';
19
- export { withScrolling } from './utils/withScrolling.js';
20
+ export * from './KeepAlive.jsx';
21
+ export * from './VirtualGrid.jsx';
22
+ export * from './Virtual.jsx';
23
+ export * from './utils/withScrolling.js';
20
24
  export {
21
25
  type AnyFunction,
22
26
  chainFunctions,
@@ -54,6 +54,7 @@ export interface NavigableProps extends NodeProps {
54
54
  // @ts-expect-error animationSettings is not identical - weird
55
55
  export interface NavigableElement extends ElementNode, NavigableProps {
56
56
  selected: number;
57
+ scrollToIndex: (this: NavigableElement, index: number) => void;
57
58
  }
58
59
 
59
60
  export interface NavigableStyleProperties {
@@ -1,4 +1,4 @@
1
- import type { ElementText, INode, TextNode } from '@lightningtv/core';
1
+ import type { ElementText, TextNode } from '@lightningtv/core';
2
2
  import {
3
3
  ElementNode,
4
4
  activeElement,
@@ -6,12 +6,24 @@ import {
6
6
  isTextNode,
7
7
  rootNode,
8
8
  Config,
9
+ isFunc,
9
10
  } from '@lightningtv/solid';
10
11
  import { makeEventListener } from '@solid-primitives/event-listener';
11
12
  import { useMousePosition } from '@solid-primitives/mouse';
12
13
  import { createScheduled, throttle } from '@solid-primitives/scheduled';
13
14
  import { createEffect } from 'solid-js';
14
15
 
16
+ declare module '@lightningtv/core' {
17
+ interface ElementNode {
18
+ /** function to be called on mouse click */
19
+ onMouseClick?: (
20
+ this: ElementNode,
21
+ event: MouseEvent,
22
+ active: ElementNode,
23
+ ) => void;
24
+ }
25
+ }
26
+
15
27
  function createKeyboardEvent(
16
28
  key: string,
17
29
  keyCode: number,
@@ -29,6 +41,7 @@ function createKeyboardEvent(
29
41
  });
30
42
  }
31
43
 
44
+ let scrollTimeout: number;
32
45
  const handleScroll = throttle((e: WheelEvent): void => {
33
46
  const deltaY = e.deltaY;
34
47
  if (deltaY < 0) {
@@ -36,6 +49,14 @@ const handleScroll = throttle((e: WheelEvent): void => {
36
49
  } else if (deltaY > 0) {
37
50
  document.body.dispatchEvent(createKeyboardEvent('ArrowDown', 40));
38
51
  }
52
+
53
+ // clear the last timeout if the user is still scrolling
54
+ clearTimeout(scrollTimeout);
55
+ // after 250ms of no scroll events, we send a keyup event to stop the scrolling
56
+ scrollTimeout = setTimeout(() => {
57
+ document.body.dispatchEvent(createKeyboardEvent('ArrowUp', 38, 'keyup'));
58
+ document.body.dispatchEvent(createKeyboardEvent('ArrowDown', 40, 'keyup'));
59
+ }, 250);
39
60
  }, 250);
40
61
 
41
62
  const handleClick = (e: MouseEvent): void => {
@@ -46,18 +67,42 @@ const handleClick = (e: MouseEvent): void => {
46
67
  testCollision(
47
68
  e.clientX,
48
69
  e.clientY,
49
- (active.lng.absX as number) || 0 * precision,
50
- (active.lng.absY as number) || 0 * precision,
51
- active.width || 0 * precision,
52
- active.height || 0 * precision,
70
+ ((active.lng.absX as number) || 0) * precision,
71
+ ((active.lng.absY as number) || 0) * precision,
72
+ (active.width || 0) * precision,
73
+ (active.height || 0) * precision,
53
74
  )
54
75
  ) {
76
+ if (isFunc(active.onMouseClick)) {
77
+ active.onMouseClick.call(active, e, active);
78
+ return;
79
+ }
80
+
55
81
  document.dispatchEvent(createKeyboardEvent('Enter', 13));
56
82
  setTimeout(
57
83
  () =>
58
84
  document.body.dispatchEvent(createKeyboardEvent('Enter', 13, 'keyup')),
59
85
  1,
60
86
  );
87
+ } else {
88
+ let parent = active?.parent;
89
+ while (parent) {
90
+ if (
91
+ isFunc(parent.onMouseClick) &&
92
+ testCollision(
93
+ e.clientX,
94
+ e.clientY,
95
+ ((parent.lng.absX as number) || 0) * precision,
96
+ ((parent.lng.absY as number) || 0) * precision,
97
+ (parent.width || 0) * precision,
98
+ (parent.height || 0) * precision,
99
+ )
100
+ ) {
101
+ parent.onMouseClick.call(parent, e, active!);
102
+ return;
103
+ }
104
+ parent = parent.parent;
105
+ }
61
106
  }
62
107
  };
63
108
 
@@ -95,8 +140,8 @@ function getChildrenByPosition(
95
140
  testCollision(
96
141
  x,
97
142
  y,
98
- (currentNode.lng.absX as number) || 0 * precision,
99
- (currentNode.lng.absY as number) || 0 * precision,
143
+ ((currentNode.lng.absX as number) || 0) * precision,
144
+ ((currentNode.lng.absY as number) || 0) * precision,
100
145
  (currentNode.width || 0) * precision,
101
146
  (currentNode.height || 0) * precision,
102
147
  )
@@ -24,7 +24,7 @@ function findFirstFocusableChildIdx(
24
24
  i = (i + el.children.length) % el.children.length;
25
25
  } else break;
26
26
  }
27
- if (!el.children[i]!.skipFocus) {
27
+ if (!el.children[i]?.skipFocus) {
28
28
  return i;
29
29
  }
30
30
  }
@@ -41,12 +41,14 @@ function selectChild(el: lngp.NavigableElement, index: number): boolean {
41
41
 
42
42
  const lastSelected = el.selected;
43
43
  el.selected = index;
44
- child.setFocus();
45
44
 
46
- if (lastSelected !== index || !lng.hasFocus(el)) {
47
- el.onSelectedChanged?.(index, el, child as lng.ElementNode, lastSelected);
45
+ if (!lng.isFocused(child)) {
46
+ child.setFocus();
48
47
  }
49
48
 
49
+ // Always call onSelectedChanged on first focus for clients
50
+ el.onSelectedChanged?.(index, el, child as lng.ElementNode, lastSelected);
51
+
50
52
  return true;
51
53
  }
52
54
 
@@ -90,10 +92,11 @@ export const navigableForwardFocus: lng.ForwardFocusHandler = function () {
90
92
  let selected = navigable.selected;
91
93
  selected = idxInArray(selected, this.children) ? selected : 0;
92
94
  selected = findFirstFocusableChildIdx(navigable, selected);
95
+ // update selected as firstfocusable maybe different if first element has skipFocus
96
+ navigable.selected = selected;
93
97
  return selectChild(navigable, selected);
94
98
  };
95
99
 
96
- /** @deprecated Use {@link navigableHandleNavigation} instead */
97
100
  export function handleNavigation(
98
101
  direction: 'up' | 'right' | 'down' | 'left',
99
102
  ): lng.KeyHandler {
@@ -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
+ /** 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;
@@ -51,6 +54,7 @@ export function withScrolling(isRow: boolean) {
51
54
  if (
52
55
  !componentRef ||
53
56
  componentRef.scroll === 'none' ||
57
+ selected === lastSelected ||
54
58
  !componentRef.children.length
55
59
  )
56
60
  return;
@@ -60,7 +64,7 @@ export function withScrolling(isRow: boolean) {
60
64
  }
61
65
 
62
66
  const lng = componentRef.lng as unknown as INode;
63
- const screenSize = isRow ? lng.stage.root.width : lng.stage.root.height;
67
+ const screenSize = isRow ? lng.stage.root.w : lng.stage.root.h;
64
68
  // Determine if movement is incremental or decremental
65
69
  const isIncrementing =
66
70
  lastSelected === undefined || lastSelected - 1 !== selected;
@@ -69,6 +73,7 @@ export function withScrolling(isRow: boolean) {
69
73
  if (componentRef.parent!.clipping) {
70
74
  const p = componentRef.parent!;
71
75
  componentRef.endOffset =
76
+ componentRef.endOffset ??
72
77
  screenSize - ((isRow ? p.absX : p.absY) || 0) - p[dimension];
73
78
  }
74
79
 
@@ -90,10 +95,9 @@ export function withScrolling(isRow: boolean) {
90
95
 
91
96
  // Allows manual position control
92
97
  const targetPosition = componentRef._targetPosition ?? componentRef[axis];
93
- const rootPosition =
94
- isIncrementing || scroll === 'auto'
95
- ? Math.min(targetPosition, componentRef[axis])
96
- : Math.max(targetPosition, componentRef[axis]);
98
+ const rootPosition = isIncrementing
99
+ ? Math.min(targetPosition, componentRef[axis])
100
+ : Math.max(targetPosition, componentRef[axis]);
97
101
  componentRef.offset = componentRef.offset ?? rootPosition;
98
102
  const offset = componentRef.offset;
99
103
  selectedElement =
@@ -115,7 +119,7 @@ export function withScrolling(isRow: boolean) {
115
119
  screenSize -
116
120
  containerSize -
117
121
  screenOffset -
118
- (componentRef.endOffset || 2 * gap),
122
+ (componentRef.endOffset ?? 2 * gap),
119
123
  offset,
120
124
  );
121
125
 
@@ -156,7 +160,8 @@ export function withScrolling(isRow: boolean) {
156
160
  nextPosition = rootPosition + selectedSize + gap;
157
161
  }
158
162
  } else if (isIncrementing) {
159
- nextPosition = -selectedPosition + offset;
163
+ //nextPosition = -selectedPosition + offset;
164
+ nextPosition = rootPosition - selectedSize - gap;
160
165
  } else {
161
166
  nextPosition = rootPosition + selectedSize + gap;
162
167
  }
@@ -186,3 +191,6 @@ export function withScrolling(isRow: boolean) {
186
191
  }
187
192
  };
188
193
  }
194
+
195
+ export const scrollRow = /* @__PURE__ */ withScrolling(true);
196
+ 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
@@ -61,3 +61,13 @@ export function combineStylesMemo<T extends Styles>(
61
61
  ...style1,
62
62
  }));
63
63
  }
64
+
65
+ export const clamp = (value: number, min: number, max: number) =>
66
+ min < max
67
+ ? Math.min(Math.max(value, min), max)
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
+ }