@24i/bigscreen-sdk 1.0.52 → 1.0.53-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 +4 -3
- package/packages/analytics/src/clients/GoogleAnalytics/constants.ts +10 -2
- package/packages/analytics/src/clients/GoogleAnalytics/interface.ts +5 -1
- package/packages/analytics/src/clients/TwentyFourIQ/TwentyFourIQClient.ts +2 -0
- package/packages/analytics/src/clients/TwentyFourIQ/constants.ts +15 -4
- package/packages/analytics/src/clients/TwentyFourIQ/interface.ts +7 -4
- package/packages/analytics/src/clients/TwentyFourIQ/mappers/mapAdEvent.ts +20 -0
- package/packages/analytics/src/clients/TwentyFourIQ/mappers/mapBase.ts +2 -0
- package/packages/analytics/src/clients/TwentyFourIQ/mappers/mapPlayerClose.ts +2 -1
- package/packages/analytics/src/clients/TwentyFourIQ/mappers/mapVideoComplete.ts +2 -1
- package/packages/analytics/src/clients/TwentyFourIQ/mappers/mapVideoPause.ts +2 -1
- package/packages/analytics/src/clients/TwentyFourIQ/mappers/mapVideoProgress.ts +2 -1
- package/packages/analytics/src/clients/TwentyFourIQ/types.ts +38 -29
- package/packages/driver-androidtv/src/DeviceAndroidTV.ts +14 -0
- package/packages/driver-androidtv/src/__mocks__/javaScriptBridge.ts +5 -0
- package/packages/driver-androidtv/src/index.ts +1 -0
- package/packages/driver-androidtv/src/types.ts +22 -0
- package/packages/driver-base/src/DeviceBase.ts +14 -0
- package/packages/driver-base/src/__mocks__/DeviceBase.ts +13 -0
- package/packages/driver-base/src/index.ts +3 -0
- package/packages/driver-base/src/types/InAppPurchase.ts +25 -0
- package/packages/driver-browser/src/DeviceBrowser.ts +14 -0
- package/packages/driver-entone/src/DeviceEntone.ts +19 -1
- package/packages/driver-firetv/src/DeviceFireTV.ts +59 -1
- package/packages/driver-firetv/src/__mocks__/javaScriptBridge.ts +5 -0
- package/packages/driver-firetv/src/types.ts +1 -0
- package/packages/driver-hbbtv/src/DeviceHbbTV.ts +14 -0
- package/packages/driver-kreatv/src/DeviceKreaTV.ts +14 -0
- package/packages/driver-saphi/src/DeviceSaphi.ts +14 -0
- package/packages/driver-smartcast/src/DeviceSmartCast.ts +49 -0
- package/packages/driver-smartcast/src/__mocks__/api.ts +26 -0
- package/packages/driver-smartcast/src/types.ts +36 -0
- package/packages/driver-tizen/src/DeviceTizen.ts +102 -7
- package/packages/driver-tizen/src/ITizen.ts +15116 -202
- package/packages/driver-tizen/src/IWebapis.ts +8641 -92
- package/packages/driver-tizen/src/TizenKeys.ts +2 -2
- package/packages/driver-tizen/src/__mocks__/tizen.ts +0 -8
- package/packages/driver-tizen/src/constants.ts +1 -195
- package/packages/driver-vidaa/src/DeviceVidaa.ts +14 -0
- package/packages/driver-webos/src/DeviceWebos.ts +14 -0
- package/packages/driver-xbox/src/DeviceXbox.ts +19 -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/packages/analytics/src/clients/TwentyFourIQ/mappers/mapAdLoaded.ts +0 -13
- package/packages/analytics/src/clients/TwentyFourIQ/mappers/mapAdSkipped.ts +0 -16
- package/packages/driver-tizen/src/types.ts +0 -14
|
@@ -1,197 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-magic-numbers */
|
|
2
|
-
|
|
3
1
|
export const VENDOR = 'Samsung Tizen';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export type AudioOutputMode = (
|
|
8
|
-
| 'PCM'
|
|
9
|
-
| 'DOLBY'
|
|
10
|
-
| 'DTS'
|
|
11
|
-
| 'AAC'
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
export type AudioBeepType = (
|
|
15
|
-
| 'UP'
|
|
16
|
-
| 'DOWN'
|
|
17
|
-
| 'LEFT'
|
|
18
|
-
| 'RIGHT'
|
|
19
|
-
| 'PAGE_LEFT'
|
|
20
|
-
| 'PAGE_RIGHT'
|
|
21
|
-
| 'BACK'
|
|
22
|
-
| 'SELECT'
|
|
23
|
-
| 'CANCEL'
|
|
24
|
-
| 'KEYPAD'
|
|
25
|
-
| 'KEYPAD_ENTER'
|
|
26
|
-
| 'KEYPAD_DEL'
|
|
27
|
-
| 'MOVE'
|
|
28
|
-
| 'PREPARING'
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
// MARK: SystemInfo
|
|
32
|
-
|
|
33
|
-
export type SystemInfoPropertyId = (
|
|
34
|
-
| 'BATTERY'
|
|
35
|
-
| 'CPU'
|
|
36
|
-
| 'STORAGE'
|
|
37
|
-
| 'DISPLAY'
|
|
38
|
-
| 'DEVICE_ORIENTATION'
|
|
39
|
-
| 'BUILD'
|
|
40
|
-
| 'LOCALE'
|
|
41
|
-
| 'NETWORK'
|
|
42
|
-
| 'WIFI_NETWORK'
|
|
43
|
-
| 'ETHERNET_NETWORK'
|
|
44
|
-
| 'CELLULAR_NETWORK'
|
|
45
|
-
| 'NET_PROXY_NETWORK'
|
|
46
|
-
| 'SIM'
|
|
47
|
-
| 'PERIPHERAL'
|
|
48
|
-
| 'MEMORY'
|
|
49
|
-
| 'VIDEOSOURCE'
|
|
50
|
-
| 'CAMERA_FLASH'
|
|
51
|
-
| 'ADS'
|
|
52
|
-
| 'SERVICE_COUNTRY'
|
|
53
|
-
| 'SOURCE_INFO'
|
|
54
|
-
| 'PANEL'
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
export type SystemInfoNetworkType = (
|
|
58
|
-
| 'NONE'
|
|
59
|
-
| '2G'
|
|
60
|
-
| '2.5G'
|
|
61
|
-
| '3G'
|
|
62
|
-
| '4G'
|
|
63
|
-
| 'WIFI'
|
|
64
|
-
| 'ETHERNET'
|
|
65
|
-
| 'NET_PROXY'
|
|
66
|
-
| 'UNKNOWN'
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
export type SystemInfoWifiSecurityMode = (
|
|
70
|
-
| 'NONE'
|
|
71
|
-
| 'WEP'
|
|
72
|
-
| 'WPA_PSK'
|
|
73
|
-
| 'WPA2_PSK'
|
|
74
|
-
| 'EAP'
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
export type SystemInfoWifiEncryptionType = (
|
|
78
|
-
| 'NONE'
|
|
79
|
-
| 'WEP'
|
|
80
|
-
| 'TKIP'
|
|
81
|
-
| 'AES'
|
|
82
|
-
| 'TKIP_AES_MIXED'
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
export type SystemInfoNetworkIpMode = (
|
|
86
|
-
| 'NONE'
|
|
87
|
-
| 'STATIC'
|
|
88
|
-
| 'DYNAMIC'
|
|
89
|
-
| 'AUTO'
|
|
90
|
-
| 'FIXED'
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
export type SystemInfoDeviceOrientationStatus = (
|
|
94
|
-
| 'PORTRAIT_PRIMARY'
|
|
95
|
-
| 'PORTRAIT_SECONDARY'
|
|
96
|
-
| 'LANDSCAPE_PRIMARY'
|
|
97
|
-
| 'LANDSCAPE_SECONDARY'
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
export type SystemInfoSimState = (
|
|
101
|
-
| 'ABSENT'
|
|
102
|
-
| 'INITIALIZING'
|
|
103
|
-
| 'READY'
|
|
104
|
-
| 'PIN_REQUIRED'
|
|
105
|
-
| 'PUK_REQUIRED'
|
|
106
|
-
| 'NETWORK_LOCKED'
|
|
107
|
-
| 'SIM_LOCKED'
|
|
108
|
-
| 'UNKNOWN'
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
export type SystemInfoProfile = 'MOBILE_FULL' | 'MOBILE_WEB' | 'MOBILE' | 'WEARABLE' | 'TV';
|
|
112
|
-
|
|
113
|
-
export type SystemInfoLowMemoryStatus = 'NORMAL' | 'WARNING';
|
|
114
|
-
|
|
115
|
-
export type SystemInfoVideoSourceType = (
|
|
116
|
-
| 'TV'
|
|
117
|
-
| 'AV'
|
|
118
|
-
| 'SVIDEO'
|
|
119
|
-
| 'COMP'
|
|
120
|
-
| 'PC'
|
|
121
|
-
| 'HDMI'
|
|
122
|
-
| 'SCART'
|
|
123
|
-
| 'DVI'
|
|
124
|
-
| 'MEDIA'
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
// MARK: ProductInfo
|
|
128
|
-
|
|
129
|
-
export enum ProductInfoConfigKey {
|
|
130
|
-
CONFIG_KEY_DATA_SERVICE = 0,
|
|
131
|
-
CONFIG_KEY_SERVICE_COUNTRY = 1,
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export enum ProductInfoNoGlass3dSupport {
|
|
135
|
-
NO_GLASS_3D_NOT_SUPPORTED = 0,
|
|
136
|
-
NO_GLASS_3D_SUPPORTED = 1,
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export enum ProductInfoSiServerType {
|
|
140
|
-
SI_TYPE_OPERATIING_SERVER = 0,
|
|
141
|
-
SI_TYPE_DEVELOPMENT_SERVER = 1,
|
|
142
|
-
SI_TYPE_DEVELOPING_SERVER = 2,
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// MARK: Network
|
|
146
|
-
|
|
147
|
-
export enum NetworkActiveConnectionType {
|
|
148
|
-
DISCONNECTED = 0,
|
|
149
|
-
WIFI = 1,
|
|
150
|
-
CELLULAR = 2,
|
|
151
|
-
ETHERNET = 3,
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export enum NetworkIpMode {
|
|
155
|
-
NONE = 0,
|
|
156
|
-
STATIC = 1,
|
|
157
|
-
DYNAMIC = 2,
|
|
158
|
-
AUTO = 3,
|
|
159
|
-
FIXED = 4,
|
|
160
|
-
UNKNOWN = 5,
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export enum NetworkState {
|
|
164
|
-
LAN_CABLE_ATTACHED = 1,
|
|
165
|
-
LAN_CABLE_DETACHED = 2,
|
|
166
|
-
LAN_CABLE_STATE_UNKNOWN = 3,
|
|
167
|
-
GATEWAY_CONNECTED = 4,
|
|
168
|
-
GATEWAY_DISCONNECTED = 5,
|
|
169
|
-
WIFI_MODULE_STATE_ATTACHED = 6,
|
|
170
|
-
WIFI_MODULE_STATE_DETACHED = 7,
|
|
171
|
-
WIFI_MODULE_STATE_UNKNOWN = 8,
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export enum NetworkWiFiSecurityMode {
|
|
175
|
-
WEP = 1,
|
|
176
|
-
WPA_PSK = 2,
|
|
177
|
-
WPA2_PSK = 3,
|
|
178
|
-
EAP = 4,
|
|
179
|
-
NONE = 5,
|
|
180
|
-
UNKNOWN = 6,
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export enum NetworkWiFiEncryptionType {
|
|
184
|
-
WEP = 1,
|
|
185
|
-
TKIP = 2,
|
|
186
|
-
AES = 3,
|
|
187
|
-
TKIP_AES_MIXED = 4,
|
|
188
|
-
NONE = 5,
|
|
189
|
-
UNKNOWN = 6,
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// MARK: AppCommon
|
|
193
|
-
|
|
194
|
-
export enum AppCommonScreenSaverState {
|
|
195
|
-
SCREEN_SAVER_OFF = 0,
|
|
196
|
-
SCREEN_SAVER_ON = 1,
|
|
197
|
-
}
|
|
3
|
+
export const DPI_SUCCESS = '100000';
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
ConnectionType,
|
|
4
4
|
ScreenSaverStatus,
|
|
5
5
|
VolumeRange,
|
|
6
|
+
IAPSuccessPayload,
|
|
7
|
+
IAPPurchaseProductData,
|
|
6
8
|
} from '@24i/bigscreen-sdk/driver-base';
|
|
7
9
|
import { generateUuid } from '@24i/bigscreen-sdk/utils';
|
|
8
10
|
import { NetworkConnectionTypeMap } from './constants';
|
|
@@ -150,4 +152,16 @@ export class DeviceVidaa extends DeviceBase {
|
|
|
150
152
|
// not supported on Hisense VIDAA platform
|
|
151
153
|
return '';
|
|
152
154
|
}
|
|
155
|
+
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
157
|
+
async purchaseProduct(data: IAPPurchaseProductData): Promise<IAPSuccessPayload> {
|
|
158
|
+
// not supported
|
|
159
|
+
throw new Error('Not supported');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
163
|
+
async cancelSubscription(data: IAPPurchaseProductData): Promise<void> {
|
|
164
|
+
// not supported
|
|
165
|
+
throw new Error('Not supported');
|
|
166
|
+
}
|
|
153
167
|
}
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
ScreenSaverStatus,
|
|
5
5
|
VolumeRange,
|
|
6
6
|
ExitOptions,
|
|
7
|
+
IAPSuccessPayload,
|
|
8
|
+
IAPPurchaseProductData,
|
|
7
9
|
} from '@24i/bigscreen-sdk/driver-base';
|
|
8
10
|
import { Storage } from '@24i/bigscreen-sdk/storage';
|
|
9
11
|
import { initWebOSTVjs } from './webOSTVjs/webOSTVjs';
|
|
@@ -343,4 +345,16 @@ export class DeviceWebos extends DeviceBase {
|
|
|
343
345
|
// Not supported on LG webOS platform (for date 12.4.2021, latest webOS TV 5.x)
|
|
344
346
|
return '';
|
|
345
347
|
}
|
|
348
|
+
|
|
349
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
350
|
+
async purchaseProduct(data: IAPPurchaseProductData): Promise<IAPSuccessPayload> {
|
|
351
|
+
// not supported
|
|
352
|
+
throw new Error('Not supported');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
356
|
+
async cancelSubscription(data: IAPPurchaseProductData): Promise<void> {
|
|
357
|
+
// not supported
|
|
358
|
+
throw new Error('Not supported');
|
|
359
|
+
}
|
|
346
360
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DeviceBase,
|
|
3
|
+
IAPPurchaseProductData,
|
|
4
|
+
IAPSuccessPayload,
|
|
5
|
+
ScreenSaverStatus,
|
|
6
|
+
VolumeRange,
|
|
7
|
+
} from '@24i/bigscreen-sdk/driver-base';
|
|
2
8
|
import { runAsync } from '@24i/bigscreen-sdk/utils/timers';
|
|
3
9
|
import { generateUuid } from '@24i/bigscreen-sdk/utils/generateUuid';
|
|
4
10
|
import { find } from '@24i/bigscreen-sdk/perf-utils/array';
|
|
@@ -239,4 +245,16 @@ export class DeviceXbox extends DeviceBase {
|
|
|
239
245
|
getDeviceClass(): string {
|
|
240
246
|
return 'xbox';
|
|
241
247
|
}
|
|
248
|
+
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
250
|
+
async purchaseProduct(data: IAPPurchaseProductData): Promise<IAPSuccessPayload> {
|
|
251
|
+
// not supported
|
|
252
|
+
throw new Error('Not supported');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
256
|
+
async cancelSubscription(data: IAPPurchaseProductData): Promise<void> {
|
|
257
|
+
// not supported
|
|
258
|
+
throw new Error('Not supported');
|
|
259
|
+
}
|
|
242
260
|
}
|
|
@@ -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
|
+
}
|