@noriginmedia/norigin-spatial-navigation-core 3.0.0

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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Norigin Spatial Navigation Core
2
+
3
+ Norigin Spatial Navigation Core is a framework-agnostic module providing the logic for spatial navigation in TV applications.
4
+
5
+ For more detailed documentation and usage examples, visit our [Developer Portal](https://devportal.noriginmedia.com/docs/Norigin-Spatial-Navigation/)
@@ -0,0 +1,281 @@
1
+ import WritingDirection from './WritingDirection';
2
+ export type Direction = 'up' | 'down' | 'left' | 'right';
3
+ type DistanceCalculationMethod = 'center' | 'edges' | 'corners';
4
+ type DistanceCalculationFunction = (refCorners: Corners, siblingCorners: Corners, isVerticalDirection: boolean, distanceCalculationMethod: DistanceCalculationMethod) => number;
5
+ export declare const ROOT_FOCUS_KEY = "SN:ROOT";
6
+ export interface FocusableComponentLayout {
7
+ left: number;
8
+ top: number;
9
+ readonly right: number;
10
+ readonly bottom: number;
11
+ width: number;
12
+ height: number;
13
+ x: number;
14
+ y: number;
15
+ node: HTMLElement;
16
+ }
17
+ interface FocusableComponent {
18
+ focusKey: string;
19
+ node: HTMLElement;
20
+ parentFocusKey: string;
21
+ onEnterPress: (details?: KeyPressDetails) => void;
22
+ onEnterRelease: () => void;
23
+ onArrowPress: (direction: string, details: KeyPressDetails) => boolean;
24
+ onArrowRelease: (direction: string) => void;
25
+ onFocus: (layout: FocusableComponentLayout, details: FocusDetails) => void;
26
+ onBlur: (layout: FocusableComponentLayout, details: FocusDetails) => void;
27
+ onUpdateFocus: (focused: boolean) => void;
28
+ onUpdateHasFocusedChild: (hasFocusedChild: boolean) => void;
29
+ saveLastFocusedChild: boolean;
30
+ trackChildren: boolean;
31
+ preferredChildFocusKey?: string;
32
+ focusable: boolean;
33
+ isFocusBoundary: boolean;
34
+ focusBoundaryDirections?: Direction[];
35
+ autoRestoreFocus: boolean;
36
+ forceFocus: boolean;
37
+ lastFocusedChildKey?: string;
38
+ layout?: FocusableComponentLayout;
39
+ layoutUpdated?: boolean;
40
+ }
41
+ interface FocusableComponentUpdatePayload {
42
+ node: HTMLElement;
43
+ preferredChildFocusKey?: string;
44
+ focusable: boolean;
45
+ isFocusBoundary: boolean;
46
+ focusBoundaryDirections?: Direction[];
47
+ onEnterPress: (details?: KeyPressDetails) => void;
48
+ onEnterRelease: () => void;
49
+ onArrowPress: (direction: string, details: KeyPressDetails) => boolean;
50
+ onArrowRelease: (direction: string) => void;
51
+ onFocus: (layout: FocusableComponentLayout, details: FocusDetails) => void;
52
+ onBlur: (layout: FocusableComponentLayout, details: FocusDetails) => void;
53
+ }
54
+ interface FocusableComponentRemovePayload {
55
+ focusKey: string;
56
+ }
57
+ interface CornerCoordinates {
58
+ x: number;
59
+ y: number;
60
+ }
61
+ interface Corners {
62
+ a: CornerCoordinates;
63
+ b: CornerCoordinates;
64
+ }
65
+ export type PressedKeys = {
66
+ [index: string]: number;
67
+ };
68
+ /**
69
+ * Extra details about pressed keys passed on the key events
70
+ */
71
+ export interface KeyPressDetails {
72
+ pressedKeys: PressedKeys;
73
+ }
74
+ /**
75
+ * Extra details passed from outside to be bounced back on other callbacks
76
+ */
77
+ export interface FocusDetails {
78
+ event?: Event;
79
+ nativeEvent?: Event;
80
+ [key: string]: any;
81
+ }
82
+ export type BackwardsCompatibleKeyMap = {
83
+ [index: string]: string | number | (number | string)[];
84
+ };
85
+ export type KeyMap = {
86
+ [index: string]: (string | number)[];
87
+ };
88
+ declare class SpatialNavigationService {
89
+ private focusableComponents;
90
+ private visualDebugger;
91
+ /**
92
+ * Focus key of the currently focused element
93
+ */
94
+ private focusKey;
95
+ private shouldFocusDOMNode;
96
+ private shouldUseNativeEvents;
97
+ /**
98
+ * This collection contains focus keys of the elements that are having a child focused
99
+ * Might be handy for styling of certain parent components if their child is focused.
100
+ */
101
+ private parentsHavingFocusedChild;
102
+ /**
103
+ * When shouldFocusDOMNode is true, this prop specifies the focus options that should be passed to the element being focused.
104
+ */
105
+ private domNodeFocusOptions;
106
+ private enabled;
107
+ /**
108
+ * Used in the React Native environment
109
+ * In this mode, the library works as a "read-only" helper to sync focused
110
+ * states for the components when they are focused by the native focus engine
111
+ */
112
+ private nativeMode;
113
+ /**
114
+ * Throttling delay for key presses in milliseconds
115
+ */
116
+ private throttle;
117
+ /**
118
+ * Enables/disables throttling feature
119
+ */
120
+ private throttleKeypresses;
121
+ /**
122
+ * Storing pressed keys counter by the eventType
123
+ */
124
+ private pressedKeys;
125
+ /**
126
+ * Flag used to block key events from this service
127
+ */
128
+ private paused;
129
+ /**
130
+ * Enables/disables getBoundingClientRect
131
+ */
132
+ private useGetBoundingClientRect;
133
+ private keyDownEventListener;
134
+ private keyDownEventListenerThrottled;
135
+ private keyUpEventListener;
136
+ private keyMap;
137
+ private debug;
138
+ private logIndex;
139
+ private setFocusDebounced;
140
+ private writingDirection;
141
+ private distanceCalculationMethod;
142
+ private customDistanceCalculationFunction?;
143
+ /**
144
+ * Used to determine the coordinate that will be used to filter items that are over the "edge"
145
+ */
146
+ static getCutoffCoordinate(isVertical: boolean, isIncremental: boolean, isSibling: boolean, layout: FocusableComponentLayout, writingDirection: WritingDirection): number;
147
+ /**
148
+ * Returns two corners (a and b) coordinates that are used as a reference points
149
+ * Where "a" is always leftmost and topmost corner, and "b" is rightmost bottommost corner
150
+ */
151
+ static getRefCorners(direction: string, isSibling: boolean, layout: FocusableComponentLayout): {
152
+ a: {
153
+ x: number;
154
+ y: number;
155
+ };
156
+ b: {
157
+ x: number;
158
+ y: number;
159
+ };
160
+ };
161
+ /**
162
+ * Calculates if the sibling node is intersecting enough with the ref node by the secondary coordinate
163
+ */
164
+ static isAdjacentSlice(refCorners: Corners, siblingCorners: Corners, isVerticalDirection: boolean): boolean;
165
+ static getPrimaryAxisDistance(refCorners: Corners, siblingCorners: Corners, isVerticalDirection: boolean): number;
166
+ static getSecondaryAxisDistance(refCorners: Corners, siblingCorners: Corners, isVerticalDirection: boolean, distanceCalculationMethod: DistanceCalculationMethod, customDistanceCalculationFunction?: DistanceCalculationFunction): number;
167
+ /**
168
+ * Inspired by: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS_for_TV/TV_remote_control_navigation#Algorithm_design
169
+ * Ref Corners are the 2 corners of the current component in the direction of navigation
170
+ * They are used as a base to measure adjacent slices
171
+ */
172
+ sortSiblingsByPriority(siblings: FocusableComponent[], currentLayout: FocusableComponentLayout, direction: string, focusKey: string): FocusableComponent[];
173
+ constructor();
174
+ init({ debug, visualDebug, nativeMode, throttle: throttleParam, throttleKeypresses, useGetBoundingClientRect, shouldFocusDOMNode, domNodeFocusOptions, shouldUseNativeEvents, rtl, distanceCalculationMethod, customDistanceCalculationFunction }?: {
175
+ debug?: boolean;
176
+ visualDebug?: boolean;
177
+ nativeMode?: boolean;
178
+ throttle?: number;
179
+ throttleKeypresses?: boolean;
180
+ useGetBoundingClientRect?: boolean;
181
+ shouldFocusDOMNode?: boolean;
182
+ domNodeFocusOptions?: {};
183
+ shouldUseNativeEvents?: boolean;
184
+ rtl?: boolean;
185
+ distanceCalculationMethod?: DistanceCalculationMethod;
186
+ customDistanceCalculationFunction?: DistanceCalculationFunction;
187
+ }): void;
188
+ setThrottle({ throttle: throttleParam, throttleKeypresses }?: {
189
+ throttle?: number;
190
+ throttleKeypresses?: boolean;
191
+ }): void;
192
+ destroy(): void;
193
+ getEventType(keyCode: number | string): string;
194
+ static getKeyCode(event: KeyboardEvent): string | number;
195
+ bindEventHandlers(): void;
196
+ unbindEventHandlers(): void;
197
+ onEnterPress(keysDetails: KeyPressDetails): void;
198
+ onEnterRelease(): void;
199
+ onArrowPress(direction: string, keysDetails: KeyPressDetails): boolean;
200
+ onArrowRelease(direction: string): void;
201
+ /**
202
+ * Move focus by direction, if you can't use buttons or focusing by key.
203
+ *
204
+ * @example
205
+ * navigateByDirection('right') // The focus is moved to right
206
+ */
207
+ navigateByDirection(direction: string, focusDetails: FocusDetails): void;
208
+ /**
209
+ * This function navigates between siblings OR goes up by the Tree
210
+ * Based on the Direction
211
+ */
212
+ smartNavigate(direction: string, fromParentFocusKey: string, focusDetails: FocusDetails): void;
213
+ saveLastFocusedChildKey(component: FocusableComponent, focusKey: string): void;
214
+ log(functionName: string, debugString: string, ...rest: any[]): void;
215
+ /**
216
+ * Returns the current focus key
217
+ */
218
+ getCurrentFocusKey(): string;
219
+ /**
220
+ * Returns the focus key to which focus can be forced if there are force-focusable components.
221
+ * A component closest to the top left viewport corner (0,0) is returned.
222
+ */
223
+ getForcedFocusKey(): string | undefined;
224
+ /**
225
+ * This function tries to determine the next component to Focus
226
+ * It's either the target node OR the one down by the Tree if node has children components
227
+ * Based on "targetFocusKey" which means the "intended component to focus"
228
+ */
229
+ getNextFocusKey(targetFocusKey: string): string;
230
+ addFocusable({ focusKey, node, parentFocusKey, onEnterPress, onEnterRelease, onArrowPress, onArrowRelease, onFocus, onBlur, saveLastFocusedChild, trackChildren, onUpdateFocus, onUpdateHasFocusedChild, preferredChildFocusKey, autoRestoreFocus, forceFocus, focusable, isFocusBoundary, focusBoundaryDirections }: FocusableComponent): void;
231
+ removeFocusable({ focusKey }: FocusableComponentRemovePayload): void;
232
+ getNodeLayoutByFocusKey(focusKey: string): FocusableComponentLayout;
233
+ setCurrentFocusedKey(newFocusKey: string, focusDetails: FocusDetails): void;
234
+ updateParentsHasFocusedChild(focusKey: string, focusDetails: FocusDetails): void;
235
+ updateParentsLastFocusedChild(focusKey: string): void;
236
+ getKeyMap(): KeyMap;
237
+ setKeyMap(keyMap: BackwardsCompatibleKeyMap): void;
238
+ isFocusableComponent(focusKey: string): boolean;
239
+ /**
240
+ * Checks whether the focusableComponent is actually participating in spatial navigation (in other words, is a
241
+ * 'focusable' focusableComponent). Seems less confusing than calling it isFocusableFocusableComponent()
242
+ */
243
+ isParticipatingFocusableComponent(focusKey: string): boolean;
244
+ onIntermediateNodeBecameFocused(focusKey: string, focusDetails: FocusDetails): void;
245
+ onIntermediateNodeBecameBlurred(focusKey: string, focusDetails: FocusDetails): void;
246
+ pause(): void;
247
+ resume(): void;
248
+ setFocus(focusKey: string, focusDetails?: FocusDetails): void;
249
+ updateAllLayouts(): void;
250
+ updateLayout(focusKey: string): void;
251
+ updateFocusable(focusKey: string, { node, preferredChildFocusKey, focusable, isFocusBoundary, focusBoundaryDirections, onEnterPress, onEnterRelease, onArrowPress, onFocus, onBlur }: FocusableComponentUpdatePayload): void;
252
+ isNativeMode(): boolean;
253
+ doesFocusableExist(focusKey: string): boolean;
254
+ /**
255
+ * This function updates the writing direction
256
+ * @param rtl whether the writing direction is right-to-left
257
+ */
258
+ updateRtl(rtl: boolean): void;
259
+ }
260
+ /**
261
+ * Export singleton
262
+ */
263
+ export declare const SpatialNavigation: SpatialNavigationService;
264
+ export declare const init: ({ debug, visualDebug, nativeMode, throttle: throttleParam, throttleKeypresses, useGetBoundingClientRect, shouldFocusDOMNode, domNodeFocusOptions, shouldUseNativeEvents, rtl, distanceCalculationMethod, customDistanceCalculationFunction }?: {
265
+ debug?: boolean;
266
+ visualDebug?: boolean;
267
+ nativeMode?: boolean;
268
+ throttle?: number;
269
+ throttleKeypresses?: boolean;
270
+ useGetBoundingClientRect?: boolean;
271
+ shouldFocusDOMNode?: boolean;
272
+ domNodeFocusOptions?: {};
273
+ shouldUseNativeEvents?: boolean;
274
+ rtl?: boolean;
275
+ distanceCalculationMethod?: DistanceCalculationMethod;
276
+ customDistanceCalculationFunction?: DistanceCalculationFunction;
277
+ }) => void, setThrottle: ({ throttle: throttleParam, throttleKeypresses }?: {
278
+ throttle?: number;
279
+ throttleKeypresses?: boolean;
280
+ }) => void, destroy: () => void, setKeyMap: (keyMap: BackwardsCompatibleKeyMap) => void, setFocus: (focusKey: string, focusDetails?: FocusDetails) => void, navigateByDirection: (direction: string, focusDetails: FocusDetails) => void, pause: () => void, resume: () => void, updateAllLayouts: () => void, getCurrentFocusKey: () => string, doesFocusableExist: (focusKey: string) => boolean, updateRtl: (rtl: boolean) => void;
281
+ export {};
@@ -0,0 +1,21 @@
1
+ import WritingDirection from './WritingDirection';
2
+ interface NodeLayout {
3
+ left: number;
4
+ top: number;
5
+ readonly right: number;
6
+ readonly bottom: number;
7
+ width: number;
8
+ height: number;
9
+ }
10
+ declare class VisualDebugger {
11
+ private debugCtx;
12
+ private layoutsCtx;
13
+ private writingDirection;
14
+ constructor(writingDirection: WritingDirection);
15
+ static createCanvas(id: string, zIndex: string, writingDirection: WritingDirection): CanvasRenderingContext2D;
16
+ clear(): void;
17
+ clearLayouts(): void;
18
+ drawLayout(layout: NodeLayout, focusKey: string, parentFocusKey: string): void;
19
+ drawPoint(x: number, y: number, color?: string, size?: number): void;
20
+ }
21
+ export default VisualDebugger;
@@ -0,0 +1,5 @@
1
+ declare enum WritingDirection {
2
+ LTR = 0,
3
+ RTL = 1
4
+ }
5
+ export default WritingDirection;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare const createRootNode: () => void;
2
+ export declare const createHorizontalLayout: () => void;
3
+ export declare const createVerticalLayout: () => void;
@@ -0,0 +1 @@
1
+ export {};