@24i/bigscreen-sdk 1.0.54-alpha.2835 → 1.0.54-alpha.2837
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.
- package/package.json +2 -1
- package/packages/input/src/Input.tsx +6 -0
- package/packages/list/DoubleList/README.md +105 -0
- package/packages/list/DoubleList/index.ts +1 -0
- package/packages/list/src/DoubleList/DoubleList.tsx +267 -0
- package/packages/list/src/DoubleList/DoubleListAdapter.ts +72 -0
- package/packages/list/src/DoubleList/config.ts +15 -0
- package/packages/list/src/DoubleList/interface.ts +82 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@24i/bigscreen-sdk",
|
|
3
|
-
"version": "1.0.54-alpha.
|
|
3
|
+
"version": "1.0.54-alpha.2837",
|
|
4
4
|
"description": "SmartApps BIGscreen SDK monorepo",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -193,6 +193,7 @@
|
|
|
193
193
|
"./l10n": "./packages/l10n/src/index.ts",
|
|
194
194
|
"./list/AttachDetachItem": "./packages/list/AttachDetachItem/index.ts",
|
|
195
195
|
"./list/Basic": "./packages/list/BasicList/index.ts",
|
|
196
|
+
"./list/Double": "./packages/list/DoubleList/index.ts",
|
|
196
197
|
"./list/Basic/static": "./packages/list/BasicList/static.ts",
|
|
197
198
|
"./list/Centered": "./packages/list/CenteredList/index.ts",
|
|
198
199
|
"./list/Centered/static": "./packages/list/CenteredList/static.ts",
|
|
@@ -106,6 +106,12 @@ export class Input extends Component<Props> implements IFocusable {
|
|
|
106
106
|
|
|
107
107
|
setValue(value: string) {
|
|
108
108
|
this.nativeInput.current!.value = value;
|
|
109
|
+
setText(this.text, this.filterText(value));
|
|
110
|
+
if (value.length === 0) {
|
|
111
|
+
this.showPlaceholder();
|
|
112
|
+
} else {
|
|
113
|
+
this.hidePlaceholder();
|
|
114
|
+
}
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
setType(type: Props['type']) {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: README
|
|
3
|
+
title: DoubleList
|
|
4
|
+
hide_title: true
|
|
5
|
+
sidebar_label: DoubleList
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# DoubleList
|
|
9
|
+
|
|
10
|
+
`DoubleList` renders a dataset into two synchronized rows (TOP and BOTTOM) for big-screen navigation.
|
|
11
|
+
|
|
12
|
+
Internally it splits the original `data` into:
|
|
13
|
+
- TOP row: items from original indices `0, 2, 4, ...`
|
|
14
|
+
- BOTTOM row: items from original indices `1, 3, 5, ...`
|
|
15
|
+
|
|
16
|
+
## List Specific Props
|
|
17
|
+
- `firstScrollStep` - value in pixels to move on the first scroll
|
|
18
|
+
- `scrollStep` - value to move on other scrolls (should be equal to item width + its spacing)
|
|
19
|
+
- `rowClassName` - CSS class applied to each row
|
|
20
|
+
- `animation` - animation configuration overrides
|
|
21
|
+
|
|
22
|
+
## Default Animation Configuration
|
|
23
|
+
- normal: 700ms with easeOutQuart
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { createRef } from '@24i/bigscreen-sdk/jsx';
|
|
29
|
+
import { DoubleList } from '@24i/bigscreen-sdk/list/DoubleList';
|
|
30
|
+
|
|
31
|
+
type Item = { id: string; title: string };
|
|
32
|
+
|
|
33
|
+
const listRef = createRef<DoubleList<any, Item>>();
|
|
34
|
+
|
|
35
|
+
export const Example = ({ items }: { items: Item[] }) => (
|
|
36
|
+
<DoubleList<any, Item>
|
|
37
|
+
ref={listRef as any}
|
|
38
|
+
data={items}
|
|
39
|
+
visibleItems={6}
|
|
40
|
+
firstScrollStep={50}
|
|
41
|
+
scrollStep={100}
|
|
42
|
+
animate={true}
|
|
43
|
+
renderItem={(item, columnIndex, ref) => (
|
|
44
|
+
<div ref={ref as any} className="card">
|
|
45
|
+
{item.title}
|
|
46
|
+
</div>
|
|
47
|
+
)}
|
|
48
|
+
onFocusChanged={(ref, columnIndex) => {
|
|
49
|
+
// `columnIndex` is row-local (column within the active row).
|
|
50
|
+
//
|
|
51
|
+
// To access the component-tracked absolute index, read it from the instance:
|
|
52
|
+
const absoluteIndex = (listRef.current as any)?.absoluteIndex;
|
|
53
|
+
// Use `absoluteIndex` for analytics/selection.
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### onFocusChanged
|
|
60
|
+
|
|
61
|
+
`onFocusChanged(itemRef, index)` receives:
|
|
62
|
+
- `itemRef`: reference to the focused item
|
|
63
|
+
- `index`: **row-local index** (column index within the focused row)
|
|
64
|
+
|
|
65
|
+
> `DoubleList` tracks an internal `absoluteIndex` value, but it is **not** passed as the callback argument.
|
|
66
|
+
> Read it from the `DoubleList` instance (via `ref`) after focus updates.
|
|
67
|
+
|
|
68
|
+
## Indices
|
|
69
|
+
|
|
70
|
+
### Column index (row-local)
|
|
71
|
+
|
|
72
|
+
Used by:
|
|
73
|
+
- `renderItem(item, columnIndex, ref)`
|
|
74
|
+
- `onFocusChanged(ref, columnIndex)`
|
|
75
|
+
|
|
76
|
+
### Absolute index (tracked by the component)
|
|
77
|
+
|
|
78
|
+
`DoubleList` maintains `absoluteIndex` to represent the position in the original `data[]`.
|
|
79
|
+
|
|
80
|
+
To consume it externally:
|
|
81
|
+
- keep a ref to the component
|
|
82
|
+
- read `ref.current.absoluteIndex` after focus updates
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Instance methods (ref API)
|
|
87
|
+
|
|
88
|
+
`DoubleList` implements `DoubleRowList<T, U>`:
|
|
89
|
+
|
|
90
|
+
- `appendData(data: U[]): void`
|
|
91
|
+
- `prependData(data: U[]): void`
|
|
92
|
+
- `scrollTo(index: number, animate?: boolean): void`
|
|
93
|
+
- `focus(options?: FocusOptions): void`
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
listRef.current?.scrollTo(10, true);
|
|
99
|
+
listRef.current?.focus();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Notes
|
|
103
|
+
|
|
104
|
+
- If `data.length` is odd, TOP row will contain one more item than BOTTOM row.
|
|
105
|
+
- For external analytics/selection logic that needs original `data[]` positions, use the component’s tracked `absoluteIndex` via `ref`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DoubleList } from '../src/DoubleList/DoubleList';
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { Component, DeclareProps } from '@24i/bigscreen-sdk/jsx';
|
|
2
|
+
import { noop } from '@24i/bigscreen-sdk/utils/noop';
|
|
3
|
+
import { device } from '@24i/bigscreen-sdk/device';
|
|
4
|
+
import { ListBase } from '../Base';
|
|
5
|
+
import { RowType, DirectionType, DIRECTION, ROW, DoubleRowList, Props } from './interface';
|
|
6
|
+
import { Item } from '../types';
|
|
7
|
+
import { DoubleListAdapter } from './DoubleListAdapter';
|
|
8
|
+
import { CONFIG, VISIBLE_ITEMS_OFFSET } from './config';
|
|
9
|
+
|
|
10
|
+
export class DoubleList<T extends Item<U>, U>
|
|
11
|
+
extends Component<Props<T, U>> implements DoubleRowList<T, U> {
|
|
12
|
+
/*
|
|
13
|
+
* Adapter managing the two row lists and their controllers
|
|
14
|
+
*/
|
|
15
|
+
adapter: DoubleListAdapter<T, U>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Track which row is currently active
|
|
19
|
+
*/
|
|
20
|
+
currentRow: RowType = ROW.TOP;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Track column position for vertical navigation
|
|
24
|
+
*/
|
|
25
|
+
columnIndex = 0;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Track absolute index for onFocusChanged callback
|
|
29
|
+
*/
|
|
30
|
+
absoluteIndex: number | null = -1;
|
|
31
|
+
|
|
32
|
+
static defaultProps = {
|
|
33
|
+
animation: CONFIG.defaultAnimation,
|
|
34
|
+
// eslint-disable-next-line react/default-props-match-prop-types
|
|
35
|
+
horizontal: true,
|
|
36
|
+
// eslint-disable-next-line react/default-props-match-prop-types
|
|
37
|
+
onFocusChanged: noop,
|
|
38
|
+
// eslint-disable-next-line react/default-props-match-prop-types
|
|
39
|
+
animate: CONFIG.animate,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
declare props: DeclareProps<Props<T, U>, typeof DoubleList.defaultProps>;
|
|
43
|
+
|
|
44
|
+
constructor(props: Props<T, U>) {
|
|
45
|
+
super(props);
|
|
46
|
+
this.adapter = new DoubleListAdapter<T, U>(props);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
componentDidMount(): void {
|
|
50
|
+
this.adapter.mount();
|
|
51
|
+
this.absoluteIndex = -1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
componentWillUnmount(): void {
|
|
55
|
+
this.adapter.unmount();
|
|
56
|
+
this.absoluteIndex = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Private helper: Switch between rows at the same column
|
|
61
|
+
* @param targetRow - The row to switch to (ROW.TOP or ROW.BOTTOM)
|
|
62
|
+
*/
|
|
63
|
+
private switchToRow = (targetRow: RowType): boolean => {
|
|
64
|
+
const { data } = this.props;
|
|
65
|
+
const targetAbsoluteIndex = this.adapter.getAbsoluteIndex(targetRow, this.columnIndex);
|
|
66
|
+
|
|
67
|
+
// Check if target item exists in data
|
|
68
|
+
if (targetAbsoluteIndex >= data.length) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const targetList = this.adapter.getList(targetRow);
|
|
73
|
+
const targetController = this.adapter.getController(targetRow);
|
|
74
|
+
|
|
75
|
+
if (!targetList) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
targetList.setIndex(this.columnIndex);
|
|
79
|
+
this.currentRow = targetRow;
|
|
80
|
+
this.absoluteIndex = targetAbsoluteIndex;
|
|
81
|
+
|
|
82
|
+
targetController.focusCurrentItem();
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Private helper: Handle DOWN key navigation
|
|
88
|
+
* @param event - The keyboard event
|
|
89
|
+
*/
|
|
90
|
+
private handleDownKey = (event: KeyboardEvent): boolean => {
|
|
91
|
+
if (this.currentRow !== ROW.TOP) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const switched = this.switchToRow(ROW.BOTTOM);
|
|
96
|
+
if (switched) {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
event.stopPropagation();
|
|
99
|
+
}
|
|
100
|
+
return switched;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Private helper: Handle UP key navigation
|
|
105
|
+
* @param event - The keyboard event
|
|
106
|
+
*/
|
|
107
|
+
private handleUpKey = (event: KeyboardEvent): boolean => {
|
|
108
|
+
if (this.currentRow !== ROW.BOTTOM) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const switched = this.switchToRow(ROW.TOP);
|
|
113
|
+
if (switched) {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
event.stopPropagation();
|
|
116
|
+
}
|
|
117
|
+
return switched;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Handle keyboard events
|
|
122
|
+
* Intercepts UP/DOWN keys for vertical navigation
|
|
123
|
+
* Delegates LEFT/RIGHT keys to forward/backward handlers
|
|
124
|
+
* @param event - The keyboard event
|
|
125
|
+
*/
|
|
126
|
+
handleKeyDown = (event: KeyboardEvent): boolean => {
|
|
127
|
+
const { horizontal } = this.props;
|
|
128
|
+
if (!horizontal) return false;
|
|
129
|
+
if (device.isDirectionDown(event)) return this.handleDownKey(event);
|
|
130
|
+
if (device.isDirectionUp(event)) return this.handleUpKey(event);
|
|
131
|
+
return false;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Handle LEFT/RIGHT navigation
|
|
136
|
+
* @param direction
|
|
137
|
+
* @returns boolean - whether the move was successful
|
|
138
|
+
*/
|
|
139
|
+
move = (direction: DirectionType): boolean => {
|
|
140
|
+
const { visibleItems, firstScrollStep, scrollStep } = this.props;
|
|
141
|
+
const { controller, list } = this.adapter.getActiveRow(this.currentRow);
|
|
142
|
+
const { controller: inactiveController } = this.adapter.getInactiveRow(this.currentRow);
|
|
143
|
+
const currentList = list.current;
|
|
144
|
+
|
|
145
|
+
if (!currentList) return false;
|
|
146
|
+
|
|
147
|
+
const delta = direction === DIRECTION.FORWARD ? 1 : -1;
|
|
148
|
+
const nextIndex = currentList.index + delta;
|
|
149
|
+
if (nextIndex < 0 || nextIndex > currentList.data.length - 1) return false;
|
|
150
|
+
|
|
151
|
+
currentList.index = nextIndex;
|
|
152
|
+
|
|
153
|
+
if (delta > 0) {
|
|
154
|
+
const scrollTargetIndex = currentList.index - visibleItems + VISIBLE_ITEMS_OFFSET;
|
|
155
|
+
if (scrollTargetIndex > controller.scrollIndex) {
|
|
156
|
+
controller.scroll(scrollTargetIndex);
|
|
157
|
+
inactiveController.scroll(scrollTargetIndex);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const offset = firstScrollStep < scrollStep ? 1 : 0;
|
|
161
|
+
if (currentList.index - offset < controller.scrollIndex) {
|
|
162
|
+
controller.scroll(currentList.index);
|
|
163
|
+
inactiveController.scroll(currentList.index);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.columnIndex = currentList.index;
|
|
168
|
+
this.absoluteIndex = this.adapter.getAbsoluteIndex(this.currentRow, this.columnIndex);
|
|
169
|
+
controller.focusCurrentItem();
|
|
170
|
+
return true;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
forward = (): boolean => this.move(DIRECTION.FORWARD);
|
|
174
|
+
|
|
175
|
+
backward = (): boolean => this.move(DIRECTION.BACKWARD);
|
|
176
|
+
|
|
177
|
+
appendData = (data: U[]): void => this.adapter.appendData(data);
|
|
178
|
+
|
|
179
|
+
prependData = (data: U[]): void => this.adapter.prependData(data);
|
|
180
|
+
|
|
181
|
+
focus(options?: FocusOptions) {
|
|
182
|
+
const { controller } = this.adapter.getActiveRow(this.currentRow);
|
|
183
|
+
this.absoluteIndex = this.adapter.getAbsoluteIndex(this.currentRow, this.columnIndex);
|
|
184
|
+
controller.focusCurrentItem(options);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
scrollTo(index: number, animate?: boolean): void {
|
|
188
|
+
const { data, visibleItems, firstScrollStep, scrollStep } = this.props;
|
|
189
|
+
|
|
190
|
+
const safeIndex = Math.max(0, Math.min(index, data.length - 1));
|
|
191
|
+
const targetRow = (safeIndex % 2 === 0) ? ROW.TOP : ROW.BOTTOM;
|
|
192
|
+
const targetColumnIndex = Math.floor(safeIndex / 2);
|
|
193
|
+
|
|
194
|
+
// Keep internal state consistent with requested target.
|
|
195
|
+
this.currentRow = targetRow;
|
|
196
|
+
this.columnIndex = targetColumnIndex;
|
|
197
|
+
this.absoluteIndex = safeIndex;
|
|
198
|
+
|
|
199
|
+
const { topRowList, bottomRowList } = this.adapter;
|
|
200
|
+
const topList = topRowList.current;
|
|
201
|
+
const bottomList = bottomRowList.current;
|
|
202
|
+
|
|
203
|
+
if (!topList || !bottomList) return;
|
|
204
|
+
|
|
205
|
+
// Keep both row indices aligned so switching rows preserves column.
|
|
206
|
+
topRowList.setIndex(Math.min(targetColumnIndex, Math.max(0, topList.data.length - 1)));
|
|
207
|
+
const bottomMaxIndex = Math.max(0, bottomList.data.length - 1);
|
|
208
|
+
bottomRowList.setIndex(Math.min(targetColumnIndex, bottomMaxIndex));
|
|
209
|
+
|
|
210
|
+
const targetController = this.adapter.getController(targetRow);
|
|
211
|
+
|
|
212
|
+
// Scroll both controllers in sync using the same logic as forward/backward.
|
|
213
|
+
const offset = firstScrollStep < scrollStep ? 1 : 0;
|
|
214
|
+
if (targetColumnIndex >= targetController.scrollIndex) {
|
|
215
|
+
const scrollTargetIndex = targetColumnIndex - visibleItems + VISIBLE_ITEMS_OFFSET;
|
|
216
|
+
if (scrollTargetIndex > targetController.scrollIndex) {
|
|
217
|
+
topRowList.controller.scroll(scrollTargetIndex, animate);
|
|
218
|
+
bottomRowList.controller.scroll(scrollTargetIndex, animate);
|
|
219
|
+
}
|
|
220
|
+
} else if (targetColumnIndex - offset < targetController.scrollIndex) {
|
|
221
|
+
topRowList.controller.scroll(targetColumnIndex, animate);
|
|
222
|
+
bottomRowList.controller.scroll(targetColumnIndex, animate);
|
|
223
|
+
}
|
|
224
|
+
targetController.focusCurrentItem();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
render() {
|
|
228
|
+
const {
|
|
229
|
+
data,
|
|
230
|
+
className,
|
|
231
|
+
rowClassName,
|
|
232
|
+
renderItem,
|
|
233
|
+
visibleItems,
|
|
234
|
+
horizontal,
|
|
235
|
+
} = this.props;
|
|
236
|
+
const { topData, bottomData } = this.adapter.splitData(data);
|
|
237
|
+
const { topRowList, bottomRowList } = this.adapter;
|
|
238
|
+
return (
|
|
239
|
+
<div className={className} onKeyDown={this.handleKeyDown}>
|
|
240
|
+
<ListBase
|
|
241
|
+
ref={topRowList.base}
|
|
242
|
+
data={topData}
|
|
243
|
+
renderItem={renderItem}
|
|
244
|
+
visibleItems={visibleItems}
|
|
245
|
+
forward={this.forward}
|
|
246
|
+
backward={this.backward}
|
|
247
|
+
shouldApplySlowdown={topRowList.controller.shouldApplySlowdown}
|
|
248
|
+
className={rowClassName}
|
|
249
|
+
horizontal={horizontal}
|
|
250
|
+
limit={topRowList.controller.itemsCount}
|
|
251
|
+
/>
|
|
252
|
+
<ListBase
|
|
253
|
+
ref={bottomRowList.base}
|
|
254
|
+
data={bottomData}
|
|
255
|
+
renderItem={renderItem}
|
|
256
|
+
visibleItems={visibleItems}
|
|
257
|
+
forward={this.forward}
|
|
258
|
+
backward={this.backward}
|
|
259
|
+
shouldApplySlowdown={bottomRowList.controller.shouldApplySlowdown}
|
|
260
|
+
className={rowClassName}
|
|
261
|
+
horizontal={horizontal}
|
|
262
|
+
limit={bottomRowList.controller.itemsCount}
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createRef } from '@24i/bigscreen-sdk/jsx';
|
|
2
|
+
import { ListBase } from '../Base';
|
|
3
|
+
import { UnifiedListController } from '../UnifiedListController/UnifiedListController';
|
|
4
|
+
import { RowList, RowType, ROW, Props } from './interface';
|
|
5
|
+
import { Item } from '../types';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* DoubleListAdapter class to manage two RowList instances
|
|
9
|
+
*/
|
|
10
|
+
export class DoubleListAdapter<T extends Item<U>, U> {
|
|
11
|
+
readonly topRowList: RowList<T, U>;
|
|
12
|
+
|
|
13
|
+
readonly bottomRowList: RowList<T, U>;
|
|
14
|
+
|
|
15
|
+
constructor(props: Props<T, U>) {
|
|
16
|
+
this.topRowList = new RowList<T, U>(createRef<ListBase<T, U>>(), props);
|
|
17
|
+
this.bottomRowList = new RowList<T, U>(createRef<ListBase<T, U>>(), props);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
mount(): void {
|
|
21
|
+
this.topRowList.controller.componentDidMount();
|
|
22
|
+
this.bottomRowList.controller.componentDidMount();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
unmount(): void {
|
|
26
|
+
this.topRowList.controller.componentWillUnmount();
|
|
27
|
+
this.bottomRowList.controller.componentWillUnmount();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
splitData = (data: U[]): { topData: U[]; bottomData: U[] } => {
|
|
31
|
+
const topData: U[] = [];
|
|
32
|
+
const bottomData: U[] = [];
|
|
33
|
+
data.forEach((item, index) => ((index % 2 === 0) ? topData : bottomData).push(item));
|
|
34
|
+
return { topData, bottomData };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
appendData(data: U[]): void {
|
|
38
|
+
const { topData, bottomData } = this.splitData(data);
|
|
39
|
+
this.topRowList.appendData(topData);
|
|
40
|
+
this.bottomRowList.appendData(bottomData);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getAbsoluteIndex = (row: RowType, columnIndex: number): number => (
|
|
44
|
+
row === ROW.TOP ? columnIndex * 2 : (columnIndex * 2) + 1
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
prependData(data: U[]): void {
|
|
48
|
+
const { topData, bottomData } = this.splitData(data);
|
|
49
|
+
this.topRowList.prependData(topData);
|
|
50
|
+
this.bottomRowList.prependData(bottomData);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getController(row: RowType): UnifiedListController<U> {
|
|
54
|
+
return row === ROW.TOP ? this.topRowList.controller : this.bottomRowList.controller;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getList(row: RowType): RowList<T, U> {
|
|
58
|
+
return row === ROW.TOP ? this.topRowList : this.bottomRowList;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getActiveRow(row: RowType): { controller: UnifiedListController<U>; list: RowList<T, U> } {
|
|
62
|
+
return row === ROW.TOP
|
|
63
|
+
? { controller: this.topRowList.controller, list: this.topRowList }
|
|
64
|
+
: { controller: this.bottomRowList.controller, list: this.bottomRowList };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getInactiveRow(row: RowType): { controller: UnifiedListController<U>; list: RowList<T, U> } {
|
|
68
|
+
return row === ROW.TOP
|
|
69
|
+
? { controller: this.bottomRowList.controller, list: this.bottomRowList }
|
|
70
|
+
: { controller: this.topRowList.controller, list: this.topRowList };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AnimationUtils } from '@24i/bigscreen-sdk/animations';
|
|
2
|
+
import { AnimationConfiguration } from '../types';
|
|
3
|
+
import { animate } from '../animate';
|
|
4
|
+
|
|
5
|
+
export const CONFIG = {
|
|
6
|
+
defaultAnimation: {
|
|
7
|
+
normal: {
|
|
8
|
+
time: 700,
|
|
9
|
+
easing: AnimationUtils.easeOutQuart,
|
|
10
|
+
},
|
|
11
|
+
} as AnimationConfiguration,
|
|
12
|
+
animate,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const VISIBLE_ITEMS_OFFSET = 3;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { IFocusable } from '@24i/bigscreen-sdk/focus';
|
|
2
|
+
import { Component } from '@24i/bigscreen-sdk/jsx';
|
|
3
|
+
import { ListBase } from '../Base';
|
|
4
|
+
import { UnifiedListController } from '../UnifiedListController/UnifiedListController';
|
|
5
|
+
import { Item, SharedProps, AnimationConfiguration } from '../types';
|
|
6
|
+
import { CONFIG } from './config';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Constants for row identification
|
|
10
|
+
*/
|
|
11
|
+
export const ROW = {
|
|
12
|
+
TOP: 'top',
|
|
13
|
+
BOTTOM: 'bottom',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export type RowType = typeof ROW[keyof typeof ROW];
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Constants for horizontal scroll direction
|
|
20
|
+
*/
|
|
21
|
+
export const DIRECTION = {
|
|
22
|
+
FORWARD: 'forward',
|
|
23
|
+
BACKWARD: 'backward',
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export type DirectionType = typeof DIRECTION[keyof typeof DIRECTION];
|
|
27
|
+
|
|
28
|
+
export type Props<T extends Item<U>, U> = SharedProps<T, U> & {
|
|
29
|
+
firstScrollStep: number,
|
|
30
|
+
animation?: Partial<AnimationConfiguration>,
|
|
31
|
+
rowClassName?: string,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Interface for DoubleList component
|
|
36
|
+
*/
|
|
37
|
+
export interface DoubleRowList<T extends Item<U>, U>
|
|
38
|
+
extends IFocusable, Component<SharedProps<T, U>> {
|
|
39
|
+
appendData(data: U[]): void;
|
|
40
|
+
prependData(data: U[]): void;
|
|
41
|
+
scrollTo(index: number, animate?: boolean): void;
|
|
42
|
+
focus(options?: FocusOptions): void;
|
|
43
|
+
forward(): boolean;
|
|
44
|
+
backward(): boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* RowList class representing a single row in the DoubleList
|
|
49
|
+
*/
|
|
50
|
+
export class RowList<T extends Item<U>, U> {
|
|
51
|
+
base: React.RefObject<ListBase<T, U>>;
|
|
52
|
+
|
|
53
|
+
props: Props<T, U>;
|
|
54
|
+
|
|
55
|
+
controller: UnifiedListController<U>;
|
|
56
|
+
|
|
57
|
+
constructor(
|
|
58
|
+
base: React.RefObject<ListBase<T, U>>,
|
|
59
|
+
props: Props<T, U>,
|
|
60
|
+
) {
|
|
61
|
+
this.base = base;
|
|
62
|
+
this.props = props;
|
|
63
|
+
this.controller = new UnifiedListController<U>(this as any, CONFIG);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get current() {
|
|
67
|
+
return this.base.current;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setIndex(index: number): void {
|
|
71
|
+
if (!this.base.current) return;
|
|
72
|
+
this.base.current.index = index;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
appendData(data: U[]): void {
|
|
76
|
+
this.controller.appendData(data);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
prependData(data: U[]): void {
|
|
80
|
+
this.controller.prependData(data);
|
|
81
|
+
}
|
|
82
|
+
}
|