@lightningtv/solid 2.10.5 → 2.10.7

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.
@@ -1,5 +1,5 @@
1
1
  import { combineStyles } from '@lightningtv/solid';
2
- import { navigableForwardFocus, navigableHandleNavigation } from './utils/handleNavigation.js';
2
+ import { navigableForwardFocus, handleNavigation } from './utils/handleNavigation.js';
3
3
  import { scrollColumn } from './utils/withScrolling.js';
4
4
  import { chainFunctions } from './utils/chainFunctions.js';
5
5
  const ColumnStyles = {
@@ -18,8 +18,10 @@ function scrollToIndex(index) {
18
18
  scrollColumn(index, this);
19
19
  this.children[index]?.setFocus();
20
20
  }
21
+ const onUp = handleNavigation('up');
22
+ const onDown = handleNavigation('down');
21
23
  export const Column = (props) => {
22
- return (<view {...props} onUp={/* @once */chainFunctions(props.onUp, navigableHandleNavigation)} onDown={/* @once */chainFunctions(props.onDown, navigableHandleNavigation)} selected={props.selected || 0} scrollToIndex={scrollToIndex} forwardFocus={navigableForwardFocus} onLayout={
24
+ return (<view {...props} onUp={/* @once */chainFunctions(props.onUp, onUp)} onDown={/* @once */chainFunctions(props.onDown, onDown)} selected={props.selected || 0} scrollToIndex={scrollToIndex} forwardFocus={navigableForwardFocus} onLayout={
23
25
  /* @once */
24
26
  props.selected ? chainFunctions(props.onLayout, scrollColumn) : props.onLayout} onSelectedChanged={
25
27
  /* @once */ chainFunctions(props.onSelectedChanged, props.scroll !== 'none' ? scrollColumn : undefined)} style={/* @once */combineStyles(props.style, ColumnStyles)}/>);
@@ -1 +1 @@
1
- {"version":3,"file":"Column.jsx","sourceRoot":"","sources":["../../../src/primitives/Column.tsx"],"names":[],"mappings":"AACA,OAAO,EAAe,aAAa,EAAmB,MAAM,oBAAoB,CAAC;AACjF,OAAO,EACL,qBAAqB,EAAE,yBAAyB,EACjD,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,MAAM,YAAY,GAAe;IAC/B,OAAO,EAAE,MAAM;IACf,aAAa,EAAE,QAAQ;IACvB,GAAG,EAAE,EAAE;IACP,UAAU,EAAE;QACV,CAAC,EAAE;YACD,QAAQ,EAAE,GAAG;YACb,MAAM,EAAE,aAAa;SACtB;KACF;CACF,CAAC;AAEF,SAAS,aAAa,CAAoB,KAAa;IACrD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IACtB,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAA2B,CAAC,KAAK,EAAE,EAAE;IACtD,OAAO,CACL,CAAC,IAAI,CACH,IAAI,KAAK,CAAC,CACV,IAAI,CAAC,CAAC,WAAY,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAC,CACxE,MAAM,CAAC,CAAC,WAAY,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAC5E,QAAQ,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,CAC9B,aAAa,CAAC,CAAC,aAAa,CAAC,CAC7B,YAAY,CAAC,CAAC,qBAAqB,CAAC,CACpC,QAAQ,CAAC,CAAC;QACR,WAAW;QACX,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QACxE,CAAC,CACD,iBAAiB,CAAC,CAAC;QACjB,WAAW,CAAC,cAAc,CACxB,KAAK,CAAC,iBAAiB,EACvB,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAEtD,CAAC,CACD,KAAK,CAAC,CAAC,WAAY,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,EAC5D,CACH,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"Column.jsx","sourceRoot":"","sources":["../../../src/primitives/Column.tsx"],"names":[],"mappings":"AACA,OAAO,EAAe,aAAa,EAAmB,MAAM,oBAAoB,CAAC;AACjF,OAAO,EACL,qBAAqB,EAAE,gBAAgB,EACxC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,MAAM,YAAY,GAAe;IAC/B,OAAO,EAAE,MAAM;IACf,aAAa,EAAE,QAAQ;IACvB,GAAG,EAAE,EAAE;IACP,UAAU,EAAE;QACV,CAAC,EAAE;YACD,QAAQ,EAAE,GAAG;YACb,MAAM,EAAE,aAAa;SACtB;KACF;CACF,CAAC;AAEF,SAAS,aAAa,CAAoB,KAAa;IACrD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IACtB,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;AACpC,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAExC,MAAM,CAAC,MAAM,MAAM,GAA2B,CAAC,KAAK,EAAE,EAAE;IACtD,OAAO,CACL,CAAC,IAAI,CACH,IAAI,KAAK,CAAC,CACV,IAAI,CAAC,CAAC,WAAY,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CACnD,MAAM,CAAC,CAAC,WAAY,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CACzD,QAAQ,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,CAC9B,aAAa,CAAC,CAAC,aAAa,CAAC,CAC7B,YAAY,CAAC,CAAC,qBAAqB,CAAC,CACpC,QAAQ,CAAC,CAAC;QACR,WAAW;QACX,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QACxE,CAAC,CACD,iBAAiB,CAAC,CAAC;QACjB,WAAW,CAAC,cAAc,CACxB,KAAK,CAAC,iBAAiB,EACvB,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAEtD,CAAC,CACD,KAAK,CAAC,CAAC,WAAY,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,EAC5D,CACH,CAAC;AACJ,CAAC,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { combineStyles } from '@lightningtv/solid';
2
2
  import { chainFunctions } from './utils/chainFunctions.js';
3
- import { navigableForwardFocus, navigableHandleNavigation } from './utils/handleNavigation.js';
3
+ import { handleNavigation, navigableForwardFocus } from './utils/handleNavigation.js';
4
4
  import { scrollRow } from './utils/withScrolling.js';
5
5
  const RowStyles = {
6
6
  display: 'flex',
@@ -17,8 +17,10 @@ function scrollToIndex(index) {
17
17
  scrollRow(index, this);
18
18
  this.children[index]?.setFocus();
19
19
  }
20
+ const onLeft = handleNavigation('left');
21
+ const onRight = handleNavigation('right');
20
22
  export const Row = (props) => {
21
- return (<view {...props} selected={props.selected || 0} onLeft={/* @once */chainFunctions(props.onLeft, navigableHandleNavigation)} onRight={/* @once */chainFunctions(props.onRight, navigableHandleNavigation)} forwardFocus={navigableForwardFocus} scrollToIndex={scrollToIndex} onLayout={
23
+ return (<view {...props} selected={props.selected || 0} onLeft={/* @once */chainFunctions(props.onLeft, onLeft)} onRight={/* @once */chainFunctions(props.onRight, onRight)} forwardFocus={navigableForwardFocus} scrollToIndex={scrollToIndex} onLayout={
22
24
  /* @once */
23
25
  props.selected ? chainFunctions(props.onLayout, scrollRow) : props.onLayout} onSelectedChanged={
24
26
  /* @once */ chainFunctions(props.onSelectedChanged, props.scroll !== 'none' ? scrollRow : undefined)} style={/* @once */combineStyles(props.style, RowStyles)}/>);
@@ -1 +1 @@
1
- {"version":3,"file":"Row.jsx","sourceRoot":"","sources":["../../../src/primitives/Row.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAqC,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EACL,qBAAqB,EAAE,yBAAyB,EACjD,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,MAAM,SAAS,GAAe;IAC5B,OAAO,EAAE,MAAM;IACf,GAAG,EAAE,EAAE;IACP,UAAU,EAAE;QACV,CAAC,EAAE;YACD,QAAQ,EAAE,GAAG;YACb,MAAM,EAAE,aAAa;SACtB;KACF;CACF,CAAC;AAEF,SAAS,aAAa,CAAoB,KAAa;IACrD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IACtB,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAwB,CAAC,KAAK,EAAE,EAAE;IAChD,OAAO,CACL,CAAC,IAAI,CACH,IAAI,KAAK,CAAC,CACV,QAAQ,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,CAC9B,MAAM,CAAC,CAAC,WAAY,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAC5E,OAAO,CAAC,CAAC,WAAY,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC,CAC9E,YAAY,CAAC,CAAC,qBAAqB,CAAC,CACpC,aAAa,CAAC,CAAC,aAAa,CAAC,CAC7B,QAAQ,CAAC,CAAC;QACR,WAAW;QACX,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QACrE,CAAC,CACD,iBAAiB,CAAC,CAAC;QACjB,WAAW,CAAC,cAAc,CACxB,KAAK,CAAC,iBAAiB,EACvB,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAEnD,CAAC,CACD,KAAK,CAAC,CAAC,WAAY,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,EACzD,CACH,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"Row.jsx","sourceRoot":"","sources":["../../../src/primitives/Row.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAqC,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,MAAM,SAAS,GAAe;IAC5B,OAAO,EAAE,MAAM;IACf,GAAG,EAAE,EAAE;IACP,UAAU,EAAE;QACV,CAAC,EAAE;YACD,QAAQ,EAAE,GAAG;YACb,MAAM,EAAE,aAAa;SACtB;KACF;CACF,CAAC;AAEF,SAAS,aAAa,CAAoB,KAAa;IACrD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IACtB,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACxC,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAE1C,MAAM,CAAC,MAAM,GAAG,GAAwB,CAAC,KAAK,EAAE,EAAE;IAChD,OAAO,CACL,CAAC,IAAI,CACH,IAAI,KAAK,CAAC,CACV,QAAQ,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,CAC9B,MAAM,CAAC,CAAC,WAAY,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CACzD,OAAO,CAAC,CAAC,WAAY,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAC5D,YAAY,CAAC,CAAC,qBAAqB,CAAC,CACpC,aAAa,CAAC,CAAC,aAAa,CAAC,CAC7B,QAAQ,CAAC,CAAC;QACR,WAAW;QACX,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QACrE,CAAC,CACD,iBAAiB,CAAC,CAAC;QACjB,WAAW,CAAC,cAAc,CACxB,KAAK,CAAC,iBAAiB,EACvB,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAEnD,CAAC,CACD,KAAK,CAAC,CAAC,WAAY,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,EACzD,CACH,CAAC;AACJ,CAAC,CAAC"}
@@ -7,10 +7,11 @@ export type VirtualProps<T> = lng.NewOmit<lngp.RowProps, 'children'> & {
7
7
  bufferSize?: number;
8
8
  wrap?: boolean;
9
9
  scrollIndex?: number;
10
- doScroll?: lngp.Scroller;
11
10
  onEndReached?: () => void;
12
11
  onEndReachedThreshold?: number;
13
- fallback?: s.JSX.Element;
12
+ debugInfo?: boolean;
13
+ factorScale?: boolean;
14
+ uniformSize?: boolean;
14
15
  children: (item: s.Accessor<T>, index: s.Accessor<number>) => s.JSX.Element;
15
16
  };
16
17
  export declare function VirtualRow<T>(props: VirtualProps<T>): lng.JSX.Element;
@@ -3,12 +3,10 @@ import * as lng from '@lightningtv/solid';
3
3
  import * as lngp from '@lightningtv/solid/primitives';
4
4
  import { List } from '@solid-primitives/list';
5
5
  import * as utils from '../utils.js';
6
- function createVirtual(component, props, scrollFn, keyHandlers) {
6
+ function createVirtual(component, props, keyHandlers) {
7
7
  const [cursor, setCursor] = s.createSignal(props.selected ?? 0);
8
8
  const bufferSize = s.createMemo(() => props.bufferSize || 2);
9
- const scrollIndex = s.createMemo(() => {
10
- return props.scrollIndex || 0;
11
- });
9
+ const scrollIndex = s.createMemo(() => props.scrollIndex || 0);
12
10
  const items = s.createMemo(() => props.each || []);
13
11
  const itemCount = s.createMemo(() => items().length);
14
12
  const scrollType = s.createMemo(() => props.scroll || 'auto');
@@ -18,158 +16,363 @@ function createVirtual(component, props, scrollFn, keyHandlers) {
18
16
  }
19
17
  return props.selected || 0;
20
18
  };
21
- const start = () => {
22
- if (itemCount() === 0)
19
+ let cachedScaledSize;
20
+ let targetPosition;
21
+ let cachedAnimationController;
22
+ const uniformSize = s.createMemo(() => {
23
+ return props.uniformSize !== false;
24
+ });
25
+ const [slice, setSlice] = s.createSignal({
26
+ start: 0,
27
+ slice: [],
28
+ selected: 0,
29
+ delta: 0,
30
+ shiftBy: 0,
31
+ atStart: true,
32
+ });
33
+ function normalizeDeltaForWindow(delta, windowLen) {
34
+ if (!windowLen)
23
35
  return 0;
24
- if (props.wrap) {
25
- return utils.mod(cursor() - Math.max(bufferSize(), scrollIndex()), itemCount());
26
- }
27
- if (scrollType() === 'always') {
28
- return Math.min(Math.max(cursor() - bufferSize(), 0), itemCount() - props.displaySize - bufferSize());
36
+ const half = windowLen / 2;
37
+ if (delta > half)
38
+ return delta - windowLen;
39
+ if (delta < -half)
40
+ return delta + windowLen;
41
+ return delta;
42
+ }
43
+ function computeSize(selected = 0) {
44
+ if (uniformSize() && cachedScaledSize) {
45
+ return cachedScaledSize;
29
46
  }
30
- if (scrollType() === 'auto') {
31
- return utils.clamp(cursor() - Math.max(bufferSize(), scrollIndex()), 0, Math.max(0, itemCount() - props.displaySize - bufferSize()));
47
+ else if (viewRef) {
48
+ const gap = viewRef.gap || 0;
49
+ const isRow = component === lngp.Row;
50
+ const dimension = isRow ? 'width' : 'height';
51
+ const prevSelectedChild = viewRef.children[selected];
52
+ if (prevSelectedChild instanceof lng.ElementNode) {
53
+ const itemSize = prevSelectedChild[dimension] || 0;
54
+ const focusStyle = prevSelectedChild.style?.focus;
55
+ const scale = (focusStyle?.scale ?? prevSelectedChild.scale ?? 1);
56
+ const scaledSize = itemSize * (props.factorScale ? scale : 1) + gap;
57
+ cachedScaledSize = scaledSize;
58
+ return scaledSize;
59
+ }
32
60
  }
33
- return utils.clamp(cursor() - bufferSize(), 0, Math.max(0, itemCount() - props.displaySize));
34
- };
35
- const end = () => {
36
- if (itemCount() === 0)
37
- return 0;
38
- if (props.wrap) {
39
- return (start() + props.displaySize + bufferSize()) % itemCount();
61
+ return 0;
62
+ }
63
+ function computeSlice(c, delta, prev) {
64
+ const total = itemCount();
65
+ if (total === 0)
66
+ return { start: 0, slice: [], selected: 0, delta, shiftBy: 0, atStart: true };
67
+ const length = props.displaySize + bufferSize();
68
+ let start = prev.start;
69
+ let selected = prev.selected;
70
+ let atStart = prev.atStart;
71
+ let shiftBy = -delta;
72
+ switch (scrollType()) {
73
+ case 'always':
74
+ if (props.wrap) {
75
+ start = utils.mod(c - 1, total);
76
+ selected = 1;
77
+ }
78
+ else {
79
+ start = utils.clamp(c - bufferSize(), 0, Math.max(0, total - props.displaySize - bufferSize()));
80
+ if (delta === 0 && c > 3) {
81
+ shiftBy = c < 3 ? -c : -2;
82
+ selected = 2;
83
+ }
84
+ else {
85
+ selected =
86
+ c < bufferSize()
87
+ ? c
88
+ : c >= total - props.displaySize
89
+ ? c - (total - props.displaySize) + bufferSize()
90
+ : bufferSize();
91
+ }
92
+ }
93
+ break;
94
+ case 'auto':
95
+ if (props.wrap) {
96
+ if (scrollIndex() && prev.selected < scrollIndex()) {
97
+ start = total - 1;
98
+ selected = Math.max(1, prev.selected + delta);
99
+ }
100
+ else {
101
+ start = utils.mod(c - (scrollIndex() || 1), total);
102
+ selected = Math.max(1, prev.selected);
103
+ }
104
+ }
105
+ else {
106
+ if (delta < 0) {
107
+ // Moving left
108
+ if (prev.start > 0 && prev.selected >= props.displaySize) {
109
+ // Move selection left inside slice
110
+ start = prev.start;
111
+ selected = prev.selected - 1;
112
+ }
113
+ else if (prev.start > 0) {
114
+ // Move selection left inside slice
115
+ start = prev.start - 1;
116
+ selected = prev.selected;
117
+ // shiftBy = 0;
118
+ }
119
+ else if (prev.start === 0 && !prev.atStart) {
120
+ start = 0;
121
+ selected = prev.selected - 1;
122
+ atStart = true;
123
+ }
124
+ else if (selected >= props.displaySize - 1) {
125
+ // Shift window left, keep selection pinned
126
+ start = 0;
127
+ selected = prev.selected - 1;
128
+ }
129
+ else {
130
+ start = 0;
131
+ selected = prev.selected - 1;
132
+ shiftBy = 0;
133
+ }
134
+ }
135
+ else if (delta > 0) {
136
+ // Moving right
137
+ if (prev.selected < scrollIndex()) {
138
+ // Move selection right inside slice
139
+ start = prev.start;
140
+ selected = prev.selected + 1;
141
+ shiftBy = 0;
142
+ }
143
+ else if (prev.selected === scrollIndex()) {
144
+ start = prev.start;
145
+ selected = prev.selected + 1;
146
+ atStart = false;
147
+ }
148
+ else if (prev.start === 0 && prev.selected === 0) {
149
+ start = 0;
150
+ selected = 1;
151
+ atStart = false;
152
+ }
153
+ else if (prev.start >= total - props.displaySize) {
154
+ // At end: clamp slice, selection drifts right
155
+ start = prev.start;
156
+ selected = c - start;
157
+ shiftBy = 0;
158
+ }
159
+ else {
160
+ // Shift window right, keep selection pinned
161
+ start = prev.start + 1;
162
+ selected = Math.max(prev.selected, scrollIndex() + 1);
163
+ ;
164
+ }
165
+ }
166
+ else {
167
+ // Initial setup
168
+ if (c > 0) {
169
+ start = Math.min(c - (scrollIndex() || 1), total - props.displaySize - bufferSize());
170
+ selected = Math.max(scrollIndex() || 1, c - start);
171
+ shiftBy = total - c < 3 ? c - total : -1;
172
+ atStart = false;
173
+ }
174
+ else {
175
+ start = prev.start;
176
+ selected = prev.selected;
177
+ }
178
+ }
179
+ }
180
+ break;
181
+ case 'edge':
182
+ const startScrolling = Math.max(1, props.displaySize - 1);
183
+ if (props.wrap) {
184
+ if (delta > 0) {
185
+ if (prev.selected < startScrolling) {
186
+ selected = prev.selected + 1;
187
+ shiftBy = 0;
188
+ }
189
+ else {
190
+ start = utils.mod(prev.start + 1, total);
191
+ selected = startScrolling;
192
+ }
193
+ }
194
+ else if (delta < 0) {
195
+ if (prev.selected > 1) {
196
+ selected = prev.selected - 1;
197
+ shiftBy = 0;
198
+ }
199
+ else {
200
+ start = utils.mod(prev.start - 1, total);
201
+ selected = 1;
202
+ }
203
+ }
204
+ else {
205
+ start = utils.mod(c - 1, total);
206
+ selected = 1;
207
+ }
208
+ }
209
+ else {
210
+ if (delta === 0 && c > 0) {
211
+ //initial setup
212
+ selected = c > startScrolling ? startScrolling : c;
213
+ start = Math.max(0, c - startScrolling + 1);
214
+ shiftBy = c > startScrolling ? -1 : 0;
215
+ atStart = c < startScrolling;
216
+ }
217
+ else if (delta > 0) {
218
+ if (prev.selected < startScrolling - 1) {
219
+ selected = prev.selected + 1;
220
+ shiftBy = 0;
221
+ }
222
+ else {
223
+ start = prev.start + 1;
224
+ selected = prev.selected;
225
+ atStart = false;
226
+ }
227
+ }
228
+ else if (delta < 0) {
229
+ if (prev.selected > 1) {
230
+ selected = prev.selected - 1;
231
+ shiftBy = 0;
232
+ }
233
+ else if (c > 1) {
234
+ start = Math.max(0, c - 1);
235
+ selected = 1;
236
+ }
237
+ else if (!atStart) {
238
+ start = 0;
239
+ selected = 0;
240
+ atStart = true;
241
+ }
242
+ else {
243
+ start = 0;
244
+ selected = 0;
245
+ shiftBy = 0;
246
+ atStart = true;
247
+ }
248
+ }
249
+ }
250
+ break;
251
+ case 'none':
252
+ default:
253
+ start = 0;
254
+ selected = c;
255
+ shiftBy = 0;
256
+ break;
40
257
  }
41
- return Math.min(itemCount(), start() + props.displaySize + bufferSize());
42
- };
43
- const getSlice = s.createMemo(() => {
44
- if (itemCount() === 0)
45
- return [];
46
- if (!props.wrap) {
47
- return items().slice(start(), end());
258
+ let newSlice = prev.slice;
259
+ if (start !== prev.start || newSlice.length === 0) {
260
+ newSlice = props.wrap
261
+ ? Array.from({ length }, (_, i) => items()[utils.mod(start + i, total)])
262
+ : items().slice(start, start + length);
48
263
  }
49
- // Wrapping slice
50
- const sIdx = start();
51
- const eIdx = (sIdx + props.displaySize + bufferSize()) % itemCount();
52
- if (sIdx < eIdx) {
53
- return items().slice(sIdx, eIdx);
264
+ const state = { start, slice: newSlice, selected, delta, shiftBy, atStart };
265
+ if (props.debugInfo) {
266
+ console.log(`[Virtual]`, {
267
+ cursor: c,
268
+ delta,
269
+ start,
270
+ selected,
271
+ shiftBy,
272
+ slice: state.slice,
273
+ });
54
274
  }
55
- return [...items().slice(sIdx), ...items().slice(0, eIdx)];
56
- });
57
- const [slice, setSlice] = s.createSignal(getSlice());
275
+ return state;
276
+ }
58
277
  let viewRef;
59
278
  function scrollToIndex(index) {
60
279
  if (itemCount() === 0)
61
280
  return;
62
- let target = index;
63
- if (props.wrap) {
64
- target = utils.mod(index, itemCount());
65
- }
66
- else {
67
- target = utils.clamp(index, 0, itemCount() - 1);
68
- }
69
- updateSelected([target]);
281
+ updateSelected([utils.clamp(index, 0, itemCount() - 1)]);
282
+ }
283
+ let lastNavTime = 0;
284
+ function getAdaptiveDuration(duration = 250) {
285
+ const now = performance.now();
286
+ const delta = now - lastNavTime;
287
+ lastNavTime = now;
288
+ if (delta < duration)
289
+ return delta;
290
+ return duration;
70
291
  }
71
292
  const onSelectedChanged = function (_idx, elm, _active, _lastIdx) {
72
293
  let idx = _idx;
73
294
  let lastIdx = _lastIdx || 0;
74
295
  let active = _active;
75
296
  const initialRun = idx === lastIdx;
297
+ const total = itemCount();
298
+ const isRow = component === lngp.Row;
299
+ const axis = isRow ? 'x' : 'y';
300
+ if (props.onSelectedChanged) {
301
+ props.onSelectedChanged.call(this, idx, this, active, lastIdx);
302
+ }
76
303
  if (initialRun && !props.wrap)
77
304
  return;
305
+ const rawDelta = idx - (lastIdx ?? 0);
306
+ const windowLen = elm?.children?.length ?? props.displaySize + bufferSize();
307
+ const delta = props.wrap
308
+ ? normalizeDeltaForWindow(rawDelta, windowLen)
309
+ : rawDelta;
78
310
  if (!initialRun) {
79
- if (props.wrap) {
80
- setCursor(c => utils.mod(c + idx - lastIdx, itemCount()));
81
- }
82
- else {
83
- setCursor(c => utils.clamp(c + idx - lastIdx, 0, Math.max(0, itemCount() - 1)));
84
- }
85
- setSlice(getSlice());
86
- const c = cursor();
87
- const scroll = scrollType();
88
- if (props.wrap) {
89
- this.selected = Math.max(bufferSize(), scrollIndex());
90
- }
91
- else if (props.scrollIndex) {
92
- this.selected = Math.min(c, props.scrollIndex);
93
- if (c >= itemCount() - props.displaySize + bufferSize()) {
94
- this.selected = c - (itemCount() - props.displaySize) + bufferSize();
95
- }
96
- }
97
- else if (scroll === 'always' || scroll === 'auto') {
98
- if (c < bufferSize()) {
99
- this.selected = c;
100
- }
101
- else if (c >= itemCount() - props.displaySize) {
102
- this.selected = c - (itemCount() - props.displaySize) + bufferSize();
103
- }
104
- else {
105
- this.selected = bufferSize();
106
- }
107
- }
108
- if (props.onEndReachedThreshold !== undefined && cursor() >= items().length - props.onEndReachedThreshold) {
311
+ setCursor(c => {
312
+ const next = c + delta;
313
+ return props.wrap
314
+ ? utils.mod(next, total)
315
+ : utils.clamp(next, 0, total - 1);
316
+ });
317
+ const newState = computeSlice(cursor(), delta, slice());
318
+ setSlice(newState);
319
+ elm.selected = newState.selected;
320
+ if (props.onEndReachedThreshold !== undefined &&
321
+ cursor() >= itemCount() - props.onEndReachedThreshold) {
109
322
  props.onEndReached?.();
110
323
  }
324
+ if (newState.shiftBy === 0)
325
+ return;
111
326
  }
112
- const isRow = component === lngp.Row;
113
- const prevChildPos = isRow
114
- ? this.x + active.x
115
- : this.y + active.y;
327
+ const prevChildPos = (targetPosition ?? this[axis]) + active[axis];
116
328
  queueMicrotask(() => {
117
- this.updateLayout();
118
- if (this._initialPosition === undefined && props.wrap) {
119
- this.offset = 0;
120
- const axis = isRow ? 'x' : 'y';
121
- this._initialPosition = this[axis];
122
- if (scrollIndex() > 0) {
123
- active = this.children[1];
124
- }
329
+ elm.updateLayout();
330
+ const childSize = computeSize(slice().selected);
331
+ if (cachedAnimationController && cachedAnimationController.state === 'running') {
332
+ cachedAnimationController.stop();
333
+ ;
125
334
  }
126
- if (component === lngp.Row) {
127
- this.lng.x = this._targetPosition = prevChildPos - active.x;
128
- }
129
- else {
130
- this.lng.y = this._targetPosition = prevChildPos - active.y;
131
- }
132
- scrollFn(idx, elm, active, lastIdx);
335
+ this.lng[axis] = prevChildPos - active[axis];
336
+ let offset = this.lng[axis] + (childSize * slice().shiftBy);
337
+ targetPosition = offset;
338
+ cachedAnimationController = this.animate({ [axis]: offset }, { ...this.animationSettings, duration: getAdaptiveDuration(this.animationSettings?.duration) }).start();
133
339
  });
134
340
  };
135
- const chainedOnSelectedChanged = lngp.chainFunctions(props.onSelectedChanged, onSelectedChanged);
136
- const updateSelected = ([selected, _items]) => {
137
- if (!viewRef || selected === undefined)
341
+ const updateSelected = ([sel, _items]) => {
342
+ if (!viewRef || sel === undefined || itemCount() === 0)
138
343
  return;
139
- const sel = selected;
140
344
  const item = items()[sel];
141
- let active = viewRef.children.find(x => x.item === item);
142
- const lastSelected = viewRef.selected;
143
- if (active instanceof lng.ElementNode) {
144
- viewRef.selected = viewRef.children.indexOf(active);
145
- chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
146
- active.setFocus();
147
- }
148
- else {
149
- setCursor(sel);
150
- setSlice(getSlice());
151
- queueMicrotask(() => {
152
- viewRef.updateLayout();
153
- active = viewRef.children.find(x => x.item === item);
154
- if (active instanceof lng.ElementNode) {
155
- viewRef.selected = viewRef.children.indexOf(active);
156
- chainedOnSelectedChanged.call(viewRef, viewRef.selected, viewRef, active, lastSelected);
157
- }
158
- });
159
- }
345
+ setCursor(sel);
346
+ const newState = computeSlice(cursor(), 0, slice());
347
+ setSlice(newState);
348
+ queueMicrotask(() => {
349
+ viewRef.updateLayout();
350
+ if (slice().shiftBy) {
351
+ const isRow = component === lngp.Row;
352
+ const axis = isRow ? 'x' : 'y';
353
+ const childSize = computeSize(slice().selected);
354
+ viewRef.lng[axis] = viewRef.lng[axis] + (childSize * slice().shiftBy);
355
+ targetPosition = viewRef.lng[axis];
356
+ }
357
+ let activeIndex = viewRef.children.findIndex(x => x.item === item);
358
+ if (activeIndex === -1)
359
+ return;
360
+ viewRef.selected = activeIndex;
361
+ viewRef.children[activeIndex]?.setFocus();
362
+ });
160
363
  };
161
- s.createEffect(s.on([() => props.selected, items], updateSelected));
364
+ s.createEffect(s.on([() => props.selected, items], updateSelected, { defer: true }));
162
365
  s.createEffect(s.on(items, () => {
163
- if (!viewRef)
366
+ if (!viewRef || itemCount() === 0)
164
367
  return;
165
368
  if (cursor() >= itemCount()) {
166
- setCursor(Math.max(0, itemCount() - 1));
369
+ setCursor(itemCount() - 1);
167
370
  }
168
- setSlice(getSlice());
169
- }, { defer: true }));
170
- return (<view {...props} ref={lngp.chainRefs(el => { viewRef = el; }, props.ref)} selected={selected()} cursor={cursor()} {...keyHandlers} forwardFocus={/* @once */lngp.navigableForwardFocus} scrollToIndex={/* @once */scrollToIndex} onCreate={/* @once */props.selected
171
- ? lngp.chainFunctions(props.onCreate, scrollFn)
172
- : props.onCreate} onSelectedChanged={/* @once */chainedOnSelectedChanged} style={/* @once */lng.combineStyles(props.style, component === lngp.Row
371
+ const newState = computeSlice(cursor(), 0, slice());
372
+ setSlice(newState);
373
+ viewRef.selected = newState.selected;
374
+ }));
375
+ return (<view {...props} {...keyHandlers} ref={lngp.chainRefs(el => { viewRef = el; }, props.ref)} selected={selected()} cursor={cursor()} forwardFocus={/* @once */lngp.navigableForwardFocus} scrollToIndex={/* @once */scrollToIndex} onSelectedChanged={/* @once */onSelectedChanged} style={/* @once */lng.combineStyles(props.style, component === lngp.Row
173
376
  ? {
174
377
  display: 'flex',
175
378
  gap: 30,
@@ -181,19 +384,19 @@ function createVirtual(component, props, scrollFn, keyHandlers) {
181
384
  gap: 30,
182
385
  transition: { y: { duration: 250, easing: 'ease-out' } },
183
386
  })}>
184
- <List each={slice()}>{props.children}</List>
387
+ <List each={slice().slice}>{props.children}</List>
185
388
  </view>);
186
389
  }
187
390
  export function VirtualRow(props) {
188
- return createVirtual(lngp.Row, props, props.doScroll || lngp.withScrolling(true), {
189
- onLeft: lngp.chainFunctions(props.onLeft, lngp.navigableHandleNavigation),
190
- onRight: lngp.chainFunctions(props.onRight, lngp.navigableHandleNavigation),
391
+ return createVirtual(lngp.Row, props, {
392
+ onLeft: lngp.chainFunctions(props.onLeft, lngp.handleNavigation('left')),
393
+ onRight: lngp.chainFunctions(props.onRight, lngp.handleNavigation('right')),
191
394
  });
192
395
  }
193
396
  export function VirtualColumn(props) {
194
- return createVirtual(lngp.Column, props, props.doScroll || lngp.withScrolling(false), {
195
- onUp: lngp.chainFunctions(props.onUp, lngp.navigableHandleNavigation),
196
- onDown: lngp.chainFunctions(props.onDown, lngp.navigableHandleNavigation),
397
+ return createVirtual(lngp.Column, props, {
398
+ onUp: lngp.chainFunctions(props.onUp, lngp.handleNavigation('up')),
399
+ onDown: lngp.chainFunctions(props.onDown, lngp.handleNavigation('down')),
197
400
  });
198
401
  }
199
402
  //# sourceMappingURL=Virtual.jsx.map