@applicaster/zapp-react-native-utils 12.0.3-rc.2 → 13.0.0-alpha.1338463240
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/actionsExecutor/ActionExecutorContext.tsx +15 -1
- package/appUtils/HooksManager/{Hook.js → Hook.ts} +35 -23
- package/appUtils/HooksManager/__tests__/__snapshots__/hook.test.js.snap +4 -2
- package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +693 -0
- package/appUtils/HooksManager/__tests__/hook.test.js +196 -184
- package/appUtils/HooksManager/__tests__/hooksManager.test.js +96 -99
- package/appUtils/HooksManager/{constants.js → constants.ts} +1 -1
- package/appUtils/HooksManager/{index.js → index.ts} +121 -112
- package/appUtils/HooksManager/types.ts +116 -0
- package/appUtils/RiverFocusManager/index.js +17 -12
- package/appUtils/accessibilityManager/index.ts +1 -42
- package/appUtils/focusManager/index.ts +63 -90
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +2 -26
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +1 -3
- package/appUtils/{index.js → index.ts} +1 -1
- package/appUtils/keyCodes/__tests__/keyCodes.test.js +3 -3
- package/appUtils/keyCodes/{index.js → index.ts} +2 -0
- package/appUtils/keyCodes/keys/index.ts +3 -0
- package/appUtils/keyCodes/{keys.android.js → keys/keyEventMapper.ts} +0 -19
- package/appUtils/keyCodes/keys/keys.android.ts +18 -0
- package/appUtils/keyCodes/keys/keys.ios.ts +25 -0
- package/appUtils/keyCodes/keys/keys.ts +15 -0
- package/appUtils/keyCodes/keysByPlatform.ts +8 -0
- package/appUtils/platform/platformUtils.ts +0 -14
- package/appUtils/playerManager/index.ts +6 -2
- package/domUtils/{cookieManager.js → cookieManager.ts} +1 -2
- package/functionUtils/index.ts +15 -11
- package/manifestUtils/sharedConfiguration/screenPicker/__tests__/utils.test.js +304 -0
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +875 -0
- package/manifestUtils/sharedConfiguration/screenPicker/utils.js +130 -0
- package/modalState/index.ts +1 -5
- package/navigationUtils/index.ts +14 -5
- package/package.json +2 -2
- package/reactHooks/navigation/index.ts +7 -4
- package/reactUtils/index.ts +0 -14
- package/appUtils/focusManager/utils/enums.ts +0 -4
- package/appUtils/keyCodes/keys.ios.js +0 -46
- /package/appUtils/HooksManager/{logger.js → logger.ts} +0 -0
- /package/appUtils/{directionHelper.js → directionHelper.ts} +0 -0
- /package/appUtils/keyCodes/{androidTVKeys.js → androidTVKeys.ts} +0 -0
- /package/appUtils/keyCodes/{keys.web.js → keys/keys.web.ts} +0 -0
- /package/appUtils/keyCodes/{keysByPlatform.js → keys/keysByPlatform.ts} +0 -0
- /package/appUtils/{localizationsHelper.js → localizationsHelper.ts} +0 -0
- /package/domUtils/{logger.js → logger.ts} +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { SubscriberObj } from "../../functionUtils";
|
|
2
|
+
import { HOOKS_EVENTS } from "./constants";
|
|
3
|
+
|
|
4
|
+
export interface HookConfiguration {}
|
|
5
|
+
|
|
6
|
+
/* existing Hook Plugn props: sample */
|
|
7
|
+
interface _HookModulePlugin {
|
|
8
|
+
Component:
|
|
9
|
+
| React.FC<HookPluginProps>
|
|
10
|
+
| React.Component<HookPluginProps, any>
|
|
11
|
+
| React.ReactNode;
|
|
12
|
+
ModalContainer?: React.FC<ModalProps> | React.Component<ModalProps, any>;
|
|
13
|
+
skipHook?: (payload: ZappRiver | ZappEntry) => boolean;
|
|
14
|
+
presentFullScreen: boolean;
|
|
15
|
+
showNavBar?: boolean;
|
|
16
|
+
isFlowBlocker: (() => boolean) | boolean; // narrow this
|
|
17
|
+
hasPlayerHook?: boolean;
|
|
18
|
+
name: string;
|
|
19
|
+
runInBackground?: (
|
|
20
|
+
item: HookPluginProps["payload"],
|
|
21
|
+
callback: hookCallback,
|
|
22
|
+
hookPlugin: HookScreenData,
|
|
23
|
+
presentUI: (payload?: HookPluginProps["payload"]) => void
|
|
24
|
+
) => any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//* Derived from usage */
|
|
28
|
+
export interface HookModule {
|
|
29
|
+
ModalContainer?: React.ComponentType<any>;
|
|
30
|
+
Component: React.ComponentType<any>;
|
|
31
|
+
presentFullScreen: boolean;
|
|
32
|
+
run?: (
|
|
33
|
+
arg0: unknown,
|
|
34
|
+
callback: () => void,
|
|
35
|
+
configuration: HookConfiguration
|
|
36
|
+
) => void; // not sure about optional parameter
|
|
37
|
+
hasPlayerHook?: boolean;
|
|
38
|
+
configuration?: HookConfiguration;
|
|
39
|
+
isFlowBlocker: () => boolean;
|
|
40
|
+
skipHook?: (payload?: ZappRiver | ZappEntry) => boolean;
|
|
41
|
+
runInBackground?: (
|
|
42
|
+
arg0: unknown,
|
|
43
|
+
callback: () => void,
|
|
44
|
+
hookPlugin: Hook,
|
|
45
|
+
presentUI: (payload?: HookPluginProps["payload"]) => void
|
|
46
|
+
) => void;
|
|
47
|
+
name: string;
|
|
48
|
+
showNavBar?: boolean; // unsure
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type HookManagerArgs = {
|
|
52
|
+
rivers: Record<string, ZappRiver>;
|
|
53
|
+
plugins: ZappPlugin[];
|
|
54
|
+
targetScreen: ZappRiver;
|
|
55
|
+
startUpHooksData?: {
|
|
56
|
+
startUpHooks?: unknown[] | "in_process";
|
|
57
|
+
setStartUpHooks?: (arg0: string) => void;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export interface HookManager {
|
|
62
|
+
subscriber: SubscriberObj;
|
|
63
|
+
presentScreenHook: (
|
|
64
|
+
hook: Hook,
|
|
65
|
+
payload: unknown,
|
|
66
|
+
callback: (...any) => void
|
|
67
|
+
) => void;
|
|
68
|
+
executeHook: (
|
|
69
|
+
hook: Hook,
|
|
70
|
+
payload: unknown,
|
|
71
|
+
callback: (...any) => void
|
|
72
|
+
) => void;
|
|
73
|
+
handleHooks: (payload: unknown) => void;
|
|
74
|
+
runInBackground: (
|
|
75
|
+
hook: Hook,
|
|
76
|
+
payload: unknown,
|
|
77
|
+
callback: (...any) => void,
|
|
78
|
+
presentUI: (payload?: HookPluginProps["payload"]) => void
|
|
79
|
+
) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type HookIniObj = {
|
|
83
|
+
hook: HookModule & {
|
|
84
|
+
module: HookModule;
|
|
85
|
+
general: unknown;
|
|
86
|
+
screen_id?: string;
|
|
87
|
+
identifier?: string;
|
|
88
|
+
weight?: number | string;
|
|
89
|
+
};
|
|
90
|
+
hooks: Hook<any>[];
|
|
91
|
+
manager: HookManager;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export interface Hook<T extends HookModule = HookModule> {
|
|
95
|
+
manager: HookManager;
|
|
96
|
+
lastHook: boolean;
|
|
97
|
+
state: number;
|
|
98
|
+
module: T;
|
|
99
|
+
screen_id: string;
|
|
100
|
+
identifier: string;
|
|
101
|
+
configuration: T["configuration"];
|
|
102
|
+
setStateAndNotify(
|
|
103
|
+
event: typeof HOOKS_EVENTS[keyof typeof HOOKS_EVENTS],
|
|
104
|
+
opts: {
|
|
105
|
+
error?: typeof Error;
|
|
106
|
+
route?: string;
|
|
107
|
+
hookPlugin: Hook<T>;
|
|
108
|
+
payload: unknown;
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
general?: {
|
|
112
|
+
transition_type: typeof HOOKS_EVENTS[keyof typeof HOOKS_EVENTS];
|
|
113
|
+
allow_screen_plugin_presentation: boolean;
|
|
114
|
+
is_flow_blocker: boolean;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -26,7 +26,7 @@ export const riverFocusManager = (function () {
|
|
|
26
26
|
/**
|
|
27
27
|
* Retrieve screen focus data by id
|
|
28
28
|
*
|
|
29
|
-
* @param {
|
|
29
|
+
* @param {{screenFocusableGroupId: string}} screenFocusableGroupId Unique Id of the screen from layout.json
|
|
30
30
|
*
|
|
31
31
|
*/
|
|
32
32
|
function screenFocusableData({ screenFocusableGroupId }) {
|
|
@@ -36,8 +36,9 @@ export const riverFocusManager = (function () {
|
|
|
36
36
|
/**
|
|
37
37
|
* Clear screen data for specific screen
|
|
38
38
|
*
|
|
39
|
-
* @param {
|
|
40
|
-
*
|
|
39
|
+
* @param {{ screenId: string, isInsideContainer: boolean }}
|
|
40
|
+
* screenId Unique Id of the screen from layout.json
|
|
41
|
+
* isInsideContainer If this screen a screen picker child
|
|
41
42
|
*
|
|
42
43
|
*/
|
|
43
44
|
function clearScreenData({ screenId, isInsideContainer }) {
|
|
@@ -60,9 +61,13 @@ export const riverFocusManager = (function () {
|
|
|
60
61
|
/**
|
|
61
62
|
* Focus on selected items
|
|
62
63
|
*
|
|
63
|
-
* @param {
|
|
64
|
-
*
|
|
65
|
-
*
|
|
64
|
+
* @param {{
|
|
65
|
+
* screenFocusableGroupId: string,
|
|
66
|
+
* screenFocusedOnItem: () => void,
|
|
67
|
+
* previousAction: QuickBrickNavigationActionType,
|
|
68
|
+
* isDeepLink: boolean }} object contains data for focus
|
|
69
|
+
* - `screenFocusedOnItem` focusable item to focus
|
|
70
|
+
* - `previousActionhistory` navigator history object
|
|
66
71
|
*
|
|
67
72
|
*/
|
|
68
73
|
function focusOnSelectedItem({
|
|
@@ -115,15 +120,15 @@ export const riverFocusManager = (function () {
|
|
|
115
120
|
|
|
116
121
|
/**
|
|
117
122
|
* Create unique key that will be used for save focused group data inside specific screen
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
123
|
+
* @param {{ screenId: string, isInsideContainer: boolean }}
|
|
124
|
+
* screenId Unique Id of the screen from layout.json
|
|
125
|
+
* isInsideContainer If this screen a screen picker child
|
|
121
126
|
*
|
|
122
127
|
*/
|
|
123
128
|
function screenFocusableGroupId({ screenId, isInsideContainer }) {
|
|
124
|
-
return
|
|
125
|
-
?
|
|
126
|
-
|
|
129
|
+
return `RiverFocusableGroup-${screenId}${
|
|
130
|
+
R.isNil(isInsideContainer) ? "" : "-isInsideContainer"
|
|
131
|
+
}`;
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
return {
|
|
@@ -6,9 +6,6 @@ import { AccessibilityRole } from "react-native";
|
|
|
6
6
|
|
|
7
7
|
export class AccessibilityManager {
|
|
8
8
|
private static _instance: AccessibilityManager | null = null;
|
|
9
|
-
private headingTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
10
|
-
private WORDS_PER_MINUTE = 160;
|
|
11
|
-
private MINIMUM_PAUSE = 500;
|
|
12
9
|
private state$ = new BehaviorSubject<AccessibilityState>({
|
|
13
10
|
screenReaderEnabled: false,
|
|
14
11
|
reduceMotionEnabled: false,
|
|
@@ -23,7 +20,6 @@ export class AccessibilityManager {
|
|
|
23
20
|
|
|
24
21
|
private ttsManager = TTSManager.getInstance();
|
|
25
22
|
private localizations: { [key: string]: string } = {};
|
|
26
|
-
private headingQueue: string[] = [];
|
|
27
23
|
|
|
28
24
|
private constructor() {}
|
|
29
25
|
|
|
@@ -56,28 +52,6 @@ export class AccessibilityManager {
|
|
|
56
52
|
return this.state$.asObservable();
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
/** Calculates the reading time for a given text
|
|
60
|
-
* This method is a bit of a hack because we don't have a callback, or promise from VIZIO API
|
|
61
|
-
* @param text - The text to calculate the reading time for
|
|
62
|
-
* @returns The reading time in milliseconds
|
|
63
|
-
*/
|
|
64
|
-
private calculateReadingTime(text: string): number {
|
|
65
|
-
const words = text.trim().split(/\s+/).length;
|
|
66
|
-
|
|
67
|
-
return Math.max(
|
|
68
|
-
this.MINIMUM_PAUSE,
|
|
69
|
-
(words / this.WORDS_PER_MINUTE) * 60 * 1000
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Adds a heading to the queue, headings will be read before the next text
|
|
75
|
-
* Each heading will be read once and removed from the queue
|
|
76
|
-
*/
|
|
77
|
-
public addHeading(heading: string) {
|
|
78
|
-
this.headingQueue.push(heading);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
55
|
/**
|
|
82
56
|
* text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
|
|
83
57
|
* keyOfLocalizedText is the key to the localized text
|
|
@@ -111,22 +85,7 @@ export class AccessibilityManager {
|
|
|
111
85
|
textToRead = localizedMessage;
|
|
112
86
|
}
|
|
113
87
|
|
|
114
|
-
|
|
115
|
-
const heading = this.headingQueue.shift()!;
|
|
116
|
-
this.ttsManager?.readText(heading);
|
|
117
|
-
|
|
118
|
-
if (this.headingTimeout) {
|
|
119
|
-
clearTimeout(this.headingTimeout);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const pauseTime = this.calculateReadingTime(heading);
|
|
123
|
-
|
|
124
|
-
this.headingTimeout = setTimeout(() => {
|
|
125
|
-
this.ttsManager?.readText(textToRead);
|
|
126
|
-
}, pauseTime);
|
|
127
|
-
} else {
|
|
128
|
-
this.ttsManager?.readText(textToRead);
|
|
129
|
-
}
|
|
88
|
+
this.ttsManager && this.ttsManager.readText(textToRead);
|
|
130
89
|
|
|
131
90
|
this.announcements$.next({
|
|
132
91
|
message: textToRead,
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
haveSameParentBeforeRoot,
|
|
12
12
|
} from "./treeDataStructure/Utils";
|
|
13
13
|
import { subscriber } from "../../functionUtils";
|
|
14
|
+
import * as FOCUS_EVENTS from "./events";
|
|
14
15
|
import { coreLogger } from "../../logger";
|
|
15
|
-
import { ACTION } from "./utils/enums";
|
|
16
16
|
const logger = coreLogger.addSubsystem("focusManager");
|
|
17
17
|
|
|
18
18
|
const isFocusEnabled = (focusableItem): boolean => {
|
|
@@ -167,82 +167,13 @@ export const focusManager = (function () {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* @param {string} currentNodeID - The ID of the current node.
|
|
175
|
-
* @param {string} previousNodeID - The ID of the previous node.
|
|
176
|
-
* @returns {Object} An object containing the differences in parent IDs:
|
|
177
|
-
* - {Array<string>} currentNodeParentsIDs: Parent IDs present in the current node but not in the previous node.
|
|
178
|
-
* - {Array<string>} previousNodeParentsIDs: Parent IDs present in the previous node but not in the current node.
|
|
179
|
-
*/
|
|
180
|
-
const getParentDiffs = (currentNodeID, previousNodeID) => {
|
|
181
|
-
const currentNodeParentsIDs =
|
|
182
|
-
focusableTree.getParentGroupIds(currentNodeID);
|
|
183
|
-
|
|
184
|
-
const previousNodeParentsIDs =
|
|
185
|
-
focusableTree.getParentGroupIds(previousNodeID);
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
currentNodeParentsIDs: R.difference(
|
|
189
|
-
currentNodeParentsIDs,
|
|
190
|
-
previousNodeParentsIDs
|
|
191
|
-
),
|
|
192
|
-
previousNodeParentsIDs: R.difference(
|
|
193
|
-
previousNodeParentsIDs,
|
|
194
|
-
currentNodeParentsIDs
|
|
195
|
-
),
|
|
196
|
-
};
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Updates the focus state of nodes identified by their IDs.
|
|
201
|
-
* For each ID, if `setFocus` is true, the node will receive focus;
|
|
202
|
-
* otherwise, it will lose focus.
|
|
203
|
-
*
|
|
204
|
-
* @param {Array<string>} ids - An array of node IDs to update.
|
|
205
|
-
* @param {boolean} setFocus - A flag indicating whether to set focus (true) or blur (false) on the nodes.
|
|
170
|
+
* returns the current focusable's group
|
|
171
|
+
* @private
|
|
172
|
+
* @returns {React.Component} the current focus
|
|
206
173
|
*/
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const startNode = focusableTree.findInTree(ids[0]);
|
|
213
|
-
|
|
214
|
-
if (!startNode) {
|
|
215
|
-
return; // Start node not found
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const endNodeId = ids[ids.length - 1]; // Get the ID of the end node
|
|
219
|
-
|
|
220
|
-
let currentNode = startNode;
|
|
221
|
-
|
|
222
|
-
// Function to apply the action (focus or blur)
|
|
223
|
-
const applyAction = (node) => {
|
|
224
|
-
if (node && node.component) {
|
|
225
|
-
if (action === "focus") {
|
|
226
|
-
node.component.setFocus();
|
|
227
|
-
} else if (action === "blur") {
|
|
228
|
-
node.component.setBlur();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
// Apply action to the starting node
|
|
234
|
-
applyAction(currentNode);
|
|
235
|
-
|
|
236
|
-
// Traverse upwards
|
|
237
|
-
while (currentNode && currentNode.id !== endNodeId) {
|
|
238
|
-
currentNode = currentNode.parent;
|
|
239
|
-
|
|
240
|
-
if (currentNode) {
|
|
241
|
-
// check if current node exists, as parent might be root
|
|
242
|
-
applyAction(currentNode);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
};
|
|
174
|
+
function getCurrentGroup() {
|
|
175
|
+
return getGroupById(R.path(["props", "groupId"], getCurrentFocus()));
|
|
176
|
+
}
|
|
246
177
|
|
|
247
178
|
/**
|
|
248
179
|
* Sets the current focus to the provided id. If there is an existing focus, it will
|
|
@@ -250,29 +181,34 @@ export const focusManager = (function () {
|
|
|
250
181
|
* @private
|
|
251
182
|
* @param {String} id of the focusable to set as currentFocus
|
|
252
183
|
* @param {Object} direction of the navigation, which led to this focus change
|
|
184
|
+
* @param {Object} options
|
|
185
|
+
* @param {Boolean} options.groupFocusedChanged tells wether the focus has moved
|
|
253
186
|
* to another group or not. defaults to false
|
|
254
187
|
*/
|
|
255
|
-
function setFocus(
|
|
188
|
+
function setFocus(
|
|
189
|
+
id: string,
|
|
190
|
+
direction?: FocusManager.Web.Direction,
|
|
191
|
+
options?: Partial<{ groupFocusedChanged: boolean }>
|
|
192
|
+
) {
|
|
256
193
|
if (focusDisabled) return false;
|
|
257
194
|
|
|
195
|
+
const { groupFocusedChanged = false } = options || {};
|
|
196
|
+
|
|
258
197
|
// due to optimisiation it's recommanded to set currentFocusNode before setFocus
|
|
259
198
|
// is called, so there will be no delays on tree search.
|
|
260
199
|
// If currentFocusNode is null or undefined we still want it to findInTree
|
|
261
200
|
if (currentFocusNode?.id !== id) {
|
|
262
|
-
const { currentNodeParentsIDs, previousNodeParentsIDs } = getParentDiffs(
|
|
263
|
-
id,
|
|
264
|
-
currentFocusNode?.id
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
// Set focus on current node parents and blur on previous node parents
|
|
268
|
-
updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS);
|
|
269
|
-
updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR);
|
|
270
|
-
|
|
271
201
|
currentFocusNode = focusableTree.findInTree(id);
|
|
272
202
|
}
|
|
273
203
|
|
|
274
204
|
setLastFocusOnParentNode(currentFocusNode);
|
|
275
205
|
|
|
206
|
+
const currentGroup = getCurrentGroup();
|
|
207
|
+
|
|
208
|
+
if (groupFocusedChanged && currentGroup) {
|
|
209
|
+
currentGroup.hasLostFocus(currentGroup, direction);
|
|
210
|
+
}
|
|
211
|
+
|
|
276
212
|
focus(direction);
|
|
277
213
|
}
|
|
278
214
|
|
|
@@ -315,7 +251,6 @@ export const focusManager = (function () {
|
|
|
315
251
|
!focusableItem.isGroup &&
|
|
316
252
|
isFocusEnabled(focusableItem)
|
|
317
253
|
) {
|
|
318
|
-
// TODO: Move this to setFocus method
|
|
319
254
|
blur();
|
|
320
255
|
|
|
321
256
|
logger.log({
|
|
@@ -326,7 +261,7 @@ export const focusManager = (function () {
|
|
|
326
261
|
});
|
|
327
262
|
|
|
328
263
|
currentFocusNode = focusCandidate;
|
|
329
|
-
|
|
264
|
+
focusableItem.willReceiveFocus(focusableItem, null);
|
|
330
265
|
focusableItem && setFocus(focusCandidate.id, null);
|
|
331
266
|
|
|
332
267
|
return { success: true };
|
|
@@ -513,10 +448,20 @@ export const focusManager = (function () {
|
|
|
513
448
|
const nextRect = nextFocusable.component.getRect();
|
|
514
449
|
|
|
515
450
|
if (nextRect && nextRect.width > 0) {
|
|
516
|
-
|
|
451
|
+
nextFocusable.component.willReceiveFocus(
|
|
452
|
+
nextFocusable.component,
|
|
453
|
+
direction
|
|
454
|
+
);
|
|
455
|
+
|
|
517
456
|
blur(direction);
|
|
518
457
|
|
|
519
|
-
|
|
458
|
+
const groupChanged =
|
|
459
|
+
nextFocusable.component.props.groupId !==
|
|
460
|
+
currentFocusNode?.component?.props?.groupId;
|
|
461
|
+
|
|
462
|
+
setFocus(nextFocusable.id, direction, {
|
|
463
|
+
groupFocusedChanged: groupChanged,
|
|
464
|
+
});
|
|
520
465
|
}
|
|
521
466
|
} else {
|
|
522
467
|
currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
|
|
@@ -536,6 +481,34 @@ export const focusManager = (function () {
|
|
|
536
481
|
|
|
537
482
|
const { on, invokeHandler, removeHandler } = eventHandler;
|
|
538
483
|
|
|
484
|
+
on(FOCUS_EVENTS.FOCUS, (focusable, direction) => {
|
|
485
|
+
if (focusable.props.groupId) {
|
|
486
|
+
const parent = focusableTree.findInTree(focusable.props.groupId);
|
|
487
|
+
parent?.component?.onFocus(focusable.component, direction);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
on(FOCUS_EVENTS.BLUR, (focusable, direction) => {
|
|
492
|
+
if (focusable.props.groupId) {
|
|
493
|
+
const parent = focusableTree.findInTree(focusable.props.groupId);
|
|
494
|
+
parent?.component?.onBlur(focusable.component, direction);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
on(FOCUS_EVENTS.HAS_LOST_FOCUS, (focusable, direction) => {
|
|
499
|
+
if (focusable.props.groupId) {
|
|
500
|
+
const parent = focusableTree.findInTree(focusable.props.groupId);
|
|
501
|
+
parent?.component?.hasLostFocus(parent.component, direction);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
on(FOCUS_EVENTS.WILL_RECEIVE_FOCUS, (focusable, direction) => {
|
|
506
|
+
if (focusable.props.groupId) {
|
|
507
|
+
const parent = focusableTree.findInTree(focusable.props.groupId);
|
|
508
|
+
parent?.component?.willReceiveFocus(focusable.component, direction);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
539
512
|
/**
|
|
540
513
|
* this is the list of the functions available externally
|
|
541
514
|
* when importing the focus manager
|
|
@@ -59,7 +59,7 @@ export class Tree {
|
|
|
59
59
|
/**
|
|
60
60
|
* Update node with provided data from newNode
|
|
61
61
|
* @private
|
|
62
|
-
* @param {Object}
|
|
62
|
+
* @param {Object} currentNode to update
|
|
63
63
|
* @param {Object} newNode node to take data from
|
|
64
64
|
*/
|
|
65
65
|
updateNode(currentNode, newNode) {
|
|
@@ -175,7 +175,6 @@ export class Tree {
|
|
|
175
175
|
* @private
|
|
176
176
|
* @param {Object} node to check
|
|
177
177
|
* @returns True if node has defined groupID
|
|
178
|
-
* @returns False if node has NOT defined groupID
|
|
179
178
|
*/
|
|
180
179
|
hasGroupID(node) {
|
|
181
180
|
return node.component && node.component.props.groupId;
|
|
@@ -191,7 +190,7 @@ export class Tree {
|
|
|
191
190
|
const findChildNode = this.findInTree(node.component.props.groupId);
|
|
192
191
|
|
|
193
192
|
if (findChildNode) {
|
|
194
|
-
this.root.children.pop(
|
|
193
|
+
this.root.children.pop();
|
|
195
194
|
node.children.push(findChildNode);
|
|
196
195
|
}
|
|
197
196
|
}
|
|
@@ -232,27 +231,4 @@ export class Tree {
|
|
|
232
231
|
|
|
233
232
|
return retVal;
|
|
234
233
|
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Gets the IDs of all parent groups for a given node id.
|
|
238
|
-
* @public
|
|
239
|
-
* @param {String} nodeId - The id of the node to get parent IDs for.
|
|
240
|
-
* @returns {Array} An array of parent group IDs or an empty array if the node is not found.
|
|
241
|
-
*/
|
|
242
|
-
getParentGroupIds(nodeId) {
|
|
243
|
-
const parentIds = [];
|
|
244
|
-
let currentNode = this.findInTree(nodeId);
|
|
245
|
-
|
|
246
|
-
if (!currentNode) {
|
|
247
|
-
return []; // Node not found, return an empty array
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Traverse up the tree, collecting parent IDs
|
|
251
|
-
while (currentNode.parent && currentNode.parent.id !== "root") {
|
|
252
|
-
parentIds.push(currentNode.parent.id);
|
|
253
|
-
currentNode = currentNode.parent;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return parentIds;
|
|
257
|
-
}
|
|
258
234
|
}
|
|
@@ -191,7 +191,6 @@ const toDistance = ({ target, direction, comparisonRect }) => {
|
|
|
191
191
|
* Get target in shortest distance in specified direction
|
|
192
192
|
* @private
|
|
193
193
|
* @param {Object} direction of the navigation, which led to this focus change
|
|
194
|
-
* @param {Object} excludedNode node which should be excluded from target group
|
|
195
194
|
* @param {Array} targetNodes array of input target nodes
|
|
196
195
|
* @param {Object} comparisonRect rectangle to compare sides in direction.
|
|
197
196
|
* @returns {Object} target in direction.
|
|
@@ -300,8 +299,7 @@ export function findFocusableNode(direction, currentFocusNode) {
|
|
|
300
299
|
* @public
|
|
301
300
|
* @param {Object} direction of the navigation, which led to this focus change
|
|
302
301
|
* @param {Object} component - object to check props.prioritiseFocusOn
|
|
303
|
-
* @returns {Boolean} TRUE if object has priority in direction.
|
|
304
|
-
* @returns {Boolean} FALSE if object has NO priority in direction.
|
|
302
|
+
* @returns {Boolean} TRUE if object has priority in direction. FALSE if object has NO priority in direction.
|
|
305
303
|
*/
|
|
306
304
|
export function hasPriorityInDirection(direction, component) {
|
|
307
305
|
const { prioritiseFocusOn = 0 } = component.props;
|
|
@@ -9,7 +9,7 @@ export { KEYS, ARROW_KEYS } from "./keyCodes/keys";
|
|
|
9
9
|
|
|
10
10
|
export { playerManager } from "./playerManager";
|
|
11
11
|
|
|
12
|
-
export {
|
|
12
|
+
export { AccessibilityManager } from "./accessibilityManager";
|
|
13
13
|
|
|
14
14
|
export function deprecationMessage(fn, replacement) {
|
|
15
15
|
if (__DEV__ && process.env.NODE_ENV !== "test") {
|
|
@@ -13,7 +13,7 @@ const globalTizen = {
|
|
|
13
13
|
describe("keyCodes", () => {
|
|
14
14
|
describe("keyCode", () => {
|
|
15
15
|
const { keyCode } = require("..");
|
|
16
|
-
const { KEYS } = require("../keys.web");
|
|
16
|
+
const { KEYS } = require("../keys/keys.web");
|
|
17
17
|
|
|
18
18
|
it("returns an object for a given key event", () => {
|
|
19
19
|
expect(keyCode(KEYS.Enter)).toMatchSnapshot();
|
|
@@ -63,7 +63,7 @@ describe("keyCodes", () => {
|
|
|
63
63
|
it("uses the proper keyCode for the environment", () => {
|
|
64
64
|
Platform.OS = "samsung_tv";
|
|
65
65
|
const { keyCode } = require("../");
|
|
66
|
-
const { KEYS } = require("../keys.web");
|
|
66
|
+
const { KEYS } = require("../keys/keys.web");
|
|
67
67
|
|
|
68
68
|
expect(globalTizen.tvinputdevice.registerKeyBatch).toHaveBeenCalledWith([
|
|
69
69
|
"MediaPlay",
|
|
@@ -97,7 +97,7 @@ describe("keyCodes", () => {
|
|
|
97
97
|
it("uses the proper keyCode for the environment", () => {
|
|
98
98
|
Platform.OS = "lg_tv";
|
|
99
99
|
const { keyCode } = require("..");
|
|
100
|
-
const { KEYS } = require("../keys.web");
|
|
100
|
+
const { KEYS } = require("../keys/keys.web");
|
|
101
101
|
// Back is reserved on LG
|
|
102
102
|
expect(keyCode({ keyCode: 417 }).matches(KEYS.Forward)).toBeTruthy();
|
|
103
103
|
});
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
export const KEYS = {
|
|
2
|
-
Enter: { code: "Enter", keyCode: 13 },
|
|
3
|
-
EnterKeyboard: { code: "EnterKeyboard", keyCode: 66 },
|
|
4
|
-
Back: { code: "Back", keyCode: 8 },
|
|
5
|
-
Space: { code: "Space", keyCode: 32 },
|
|
6
|
-
m: { code: "m", keyCode: 77 },
|
|
7
|
-
ArrowLeft: { code: "ArrowLeft", keyCode: 37 },
|
|
8
|
-
ArrowUp: { code: "ArrowUp", keyCode: 38 },
|
|
9
|
-
ArrowRight: { code: "ArrowRight", keyCode: 39 },
|
|
10
|
-
ArrowDown: { code: "ArrowDown", keyCode: 40 },
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const ARROW_KEYS = [
|
|
14
|
-
KEYS.ArrowUp,
|
|
15
|
-
KEYS.ArrowLeft,
|
|
16
|
-
KEYS.ArrowDown,
|
|
17
|
-
KEYS.ArrowRight,
|
|
18
|
-
];
|
|
19
|
-
|
|
20
1
|
export function keyEventMapper(keyCode) {
|
|
21
2
|
switch (keyCode) {
|
|
22
3
|
case 19: // ArrowUp
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const KEYS = {
|
|
2
|
+
Enter: { code: "Enter", keyCode: 13 },
|
|
3
|
+
EnterKeyboard: { code: "EnterKeyboard", keyCode: 66 },
|
|
4
|
+
Back: { code: "Back", keyCode: 8 },
|
|
5
|
+
Space: { code: "Space", keyCode: 32 },
|
|
6
|
+
m: { code: "m", keyCode: 77 },
|
|
7
|
+
ArrowLeft: { code: "ArrowLeft", keyCode: 37 },
|
|
8
|
+
ArrowUp: { code: "ArrowUp", keyCode: 38 },
|
|
9
|
+
ArrowRight: { code: "ArrowRight", keyCode: 39 },
|
|
10
|
+
ArrowDown: { code: "ArrowDown", keyCode: 40 },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const ARROW_KEYS = [
|
|
14
|
+
KEYS.ArrowUp,
|
|
15
|
+
KEYS.ArrowLeft,
|
|
16
|
+
KEYS.ArrowDown,
|
|
17
|
+
KEYS.ArrowRight,
|
|
18
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const KEYS = {
|
|
2
|
+
Enter: { code: "Enter", keyCode: 13 },
|
|
3
|
+
Space: { code: "Space", keyCode: 32 },
|
|
4
|
+
m: { code: "m", keyCode: 77 },
|
|
5
|
+
ArrowLeft: { code: "ArrowLeft", keyCode: 37 },
|
|
6
|
+
ArrowUp: { code: "ArrowUp", keyCode: 38 },
|
|
7
|
+
ArrowRight: { code: "ArrowRight", keyCode: 39 },
|
|
8
|
+
ArrowDown: { code: "ArrowDown", keyCode: 40 },
|
|
9
|
+
Back: { code: "Backspace", keyCode: 8 },
|
|
10
|
+
Play: { code: "PlayPause", keyCode: 179 },
|
|
11
|
+
Pause: { code: "Pause", keyCode: 179 },
|
|
12
|
+
Forward: { code: "Forward", keyCode: 176 },
|
|
13
|
+
Rewind: { code: "Rewind", keyCode: 177 },
|
|
14
|
+
VolumeUp: { code: "VolumeUp", keyCode: 175 },
|
|
15
|
+
VolumeDown: { code: "VolumeDown", keyCode: 174 },
|
|
16
|
+
ToggleMute: { code: "VolumeMute", keyCode: 173 },
|
|
17
|
+
Stop: { code: "Q", keyCode: 81 },
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const ARROW_KEYS = [
|
|
21
|
+
KEYS.ArrowUp,
|
|
22
|
+
KEYS.ArrowLeft,
|
|
23
|
+
KEYS.ArrowDown,
|
|
24
|
+
KEYS.ArrowRight,
|
|
25
|
+
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// dummy file to make sure the folder structure is created
|
|
2
|
+
export const KEYS: Record<string, { code: string; keyCode: number }> = {
|
|
3
|
+
ArrowLeft: { code: "ArrowLeft", keyCode: -1 },
|
|
4
|
+
ArrowUp: { code: "ArrowUp", keyCode: -1 },
|
|
5
|
+
ArrowRight: { code: "ArrowRight", keyCode: -1 },
|
|
6
|
+
ArrowDown: { code: "ArrowDown", keyCode: -1 },
|
|
7
|
+
Back: { code: "Back", keyCode: -1 },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const ARROW_KEYS = [
|
|
11
|
+
KEYS.ArrowUp,
|
|
12
|
+
KEYS.ArrowLeft,
|
|
13
|
+
KEYS.ArrowDown,
|
|
14
|
+
KEYS.ArrowRight,
|
|
15
|
+
];
|