@noriginmedia/norigin-spatial-navigation-core 3.0.0 → 3.1.0-beta.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.
@@ -0,0 +1,16 @@
1
+ type Task = () => unknown | Promise<unknown>;
2
+ /**
3
+ * Scheduler provides a simple way to queue and execute tasks in a strict sequence.
4
+ * - Regular tasks are run one after another; if a new task is scheduled before the current one starts, it replaces the pending next task.
5
+ * - Priority tasks are added to a separate queue and will be executed before any remaining regular tasks.
6
+ */
7
+ export default class Scheduler {
8
+ private currentTask;
9
+ private nextTask;
10
+ private nextPriorityTasks;
11
+ private tick;
12
+ bind<A extends unknown[], R>(fn: (...args: A) => R | Promise<R>, context: any): (...args: A) => Promise<void>;
13
+ schedule(task: Task): void;
14
+ schedulePriority(task: Task): void;
15
+ }
16
+ export {};
@@ -1,4 +1,10 @@
1
+ import { type LayoutAdapter } from './adapter/types';
1
2
  import WritingDirection from './WritingDirection';
3
+ export interface NodeTypeOverrides {
4
+ }
5
+ export type NodeType = NodeTypeOverrides extends {
6
+ node: infer N;
7
+ } ? N : HTMLElement;
2
8
  export type Direction = 'up' | 'down' | 'left' | 'right';
3
9
  type DistanceCalculationMethod = 'center' | 'edges' | 'corners';
4
10
  type DistanceCalculationFunction = (refCorners: Corners, siblingCorners: Corners, isVerticalDirection: boolean, distanceCalculationMethod: DistanceCalculationMethod) => number;
@@ -6,17 +12,17 @@ export declare const ROOT_FOCUS_KEY = "SN:ROOT";
6
12
  export interface FocusableComponentLayout {
7
13
  left: number;
8
14
  top: number;
9
- readonly right: number;
10
- readonly bottom: number;
15
+ right: number;
16
+ bottom: number;
11
17
  width: number;
12
18
  height: number;
13
19
  x: number;
14
20
  y: number;
15
- node: HTMLElement;
21
+ node: NodeType;
16
22
  }
17
- interface FocusableComponent {
23
+ export interface FocusableComponent {
18
24
  focusKey: string;
19
- node: HTMLElement;
25
+ node: NodeType;
20
26
  parentFocusKey: string;
21
27
  onEnterPress: (details?: KeyPressDetails) => void;
22
28
  onEnterRelease: () => void;
@@ -36,10 +42,10 @@ interface FocusableComponent {
36
42
  forceFocus: boolean;
37
43
  lastFocusedChildKey?: string;
38
44
  layout?: FocusableComponentLayout;
39
- layoutUpdated?: boolean;
45
+ layoutUpdatedAt?: number;
40
46
  }
41
47
  interface FocusableComponentUpdatePayload {
42
- node: HTMLElement;
48
+ node: NodeType;
43
49
  preferredChildFocusKey?: string;
44
50
  focusable: boolean;
45
51
  isFocusBoundary: boolean;
@@ -85,7 +91,28 @@ export type BackwardsCompatibleKeyMap = {
85
91
  export type KeyMap = {
86
92
  [index: string]: (string | number)[];
87
93
  };
88
- declare class SpatialNavigationService {
94
+ export type SpatialNavigationServiceOptions = {
95
+ debug: boolean;
96
+ visualDebug: boolean;
97
+ throttle: number;
98
+ throttleKeypresses: boolean;
99
+ /**
100
+ * @deprecated Use layoutAdapter API instead.
101
+ */
102
+ useGetBoundingClientRect: boolean;
103
+ shouldFocusDOMNode: boolean;
104
+ domNodeFocusOptions: FocusOptions;
105
+ shouldUseNativeEvents: boolean;
106
+ rtl: boolean;
107
+ /**
108
+ * Can be a class (constructor) that implements LayoutAdapter,
109
+ * or an object that partially implements LayoutAdapter.
110
+ */
111
+ layoutAdapter?: (new (...args: any[]) => LayoutAdapter) | Partial<LayoutAdapter>;
112
+ distanceCalculationMethod: DistanceCalculationMethod;
113
+ customDistanceCalculationFunction?: DistanceCalculationFunction;
114
+ };
115
+ export declare class SpatialNavigationService {
89
116
  private focusableComponents;
90
117
  private visualDebugger;
91
118
  /**
@@ -104,12 +131,6 @@ declare class SpatialNavigationService {
104
131
  */
105
132
  private domNodeFocusOptions;
106
133
  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
134
  /**
114
135
  * Throttling delay for key presses in milliseconds
115
136
  */
@@ -126,10 +147,7 @@ declare class SpatialNavigationService {
126
147
  * Flag used to block key events from this service
127
148
  */
128
149
  private paused;
129
- /**
130
- * Enables/disables getBoundingClientRect
131
- */
132
- private useGetBoundingClientRect;
150
+ private layoutAdapter;
133
151
  private keyDownEventListener;
134
152
  private keyDownEventListenerThrottled;
135
153
  private keyUpEventListener;
@@ -140,6 +158,12 @@ declare class SpatialNavigationService {
140
158
  private writingDirection;
141
159
  private distanceCalculationMethod;
142
160
  private customDistanceCalculationFunction?;
161
+ private scheduler;
162
+ get options(): {
163
+ shouldFocusDOMNode: boolean;
164
+ domNodeFocusOptions: FocusOptions;
165
+ shouldUseNativeEvents: boolean;
166
+ };
143
167
  /**
144
168
  * Used to determine the coordinate that will be used to filter items that are over the "edge"
145
169
  */
@@ -171,27 +195,13 @@ declare class SpatialNavigationService {
171
195
  */
172
196
  sortSiblingsByPriority(siblings: FocusableComponent[], currentLayout: FocusableComponentLayout, direction: string, focusKey: string): FocusableComponent[];
173
197
  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;
198
+ init({ debug, visualDebug, throttle: throttleParam, throttleKeypresses, useGetBoundingClientRect, layoutAdapter, shouldFocusDOMNode, domNodeFocusOptions, shouldUseNativeEvents, rtl, distanceCalculationMethod, customDistanceCalculationFunction }?: Partial<SpatialNavigationServiceOptions>): void;
188
199
  setThrottle({ throttle: throttleParam, throttleKeypresses }?: {
189
200
  throttle?: number;
190
201
  throttleKeypresses?: boolean;
191
202
  }): void;
192
203
  destroy(): void;
193
204
  getEventType(keyCode: number | string): string;
194
- static getKeyCode(event: KeyboardEvent): string | number;
195
205
  bindEventHandlers(): void;
196
206
  unbindEventHandlers(): void;
197
207
  onEnterPress(keysDetails: KeyPressDetails): void;
@@ -204,12 +214,12 @@ declare class SpatialNavigationService {
204
214
  * @example
205
215
  * navigateByDirection('right') // The focus is moved to right
206
216
  */
207
- navigateByDirection(direction: string, focusDetails: FocusDetails): void;
217
+ navigateByDirection(direction: string, focusDetails: FocusDetails): Promise<void>;
208
218
  /**
209
219
  * This function navigates between siblings OR goes up by the Tree
210
220
  * Based on the Direction
211
221
  */
212
- smartNavigate(direction: string, fromParentFocusKey: string, focusDetails: FocusDetails): void;
222
+ smartNavigate(direction: string, fromParentFocusKey: string, focusDetails: FocusDetails): Promise<void>;
213
223
  saveLastFocusedChildKey(component: FocusableComponent, focusKey: string): void;
214
224
  log(functionName: string, debugString: string, ...rest: any[]): void;
215
225
  /**
@@ -226,10 +236,10 @@ declare class SpatialNavigationService {
226
236
  * It's either the target node OR the one down by the Tree if node has children components
227
237
  * Based on "targetFocusKey" which means the "intended component to focus"
228
238
  */
229
- getNextFocusKey(targetFocusKey: string): string;
239
+ getNextFocusKey(targetFocusKey: string): Promise<string>;
230
240
  addFocusable({ focusKey, node, parentFocusKey, onEnterPress, onEnterRelease, onArrowPress, onArrowRelease, onFocus, onBlur, saveLastFocusedChild, trackChildren, onUpdateFocus, onUpdateHasFocusedChild, preferredChildFocusKey, autoRestoreFocus, forceFocus, focusable, isFocusBoundary, focusBoundaryDirections }: FocusableComponent): void;
231
241
  removeFocusable({ focusKey }: FocusableComponentRemovePayload): void;
232
- getNodeLayoutByFocusKey(focusKey: string): FocusableComponentLayout;
242
+ getNodeLayoutByFocusKey(focusKey: string): Promise<FocusableComponentLayout>;
233
243
  setCurrentFocusedKey(newFocusKey: string, focusDetails: FocusDetails): void;
234
244
  updateParentsHasFocusedChild(focusKey: string, focusDetails: FocusDetails): void;
235
245
  updateParentsLastFocusedChild(focusKey: string): void;
@@ -245,11 +255,10 @@ declare class SpatialNavigationService {
245
255
  onIntermediateNodeBecameBlurred(focusKey: string, focusDetails: FocusDetails): void;
246
256
  pause(): void;
247
257
  resume(): void;
248
- setFocus(focusKey: string, focusDetails?: FocusDetails): void;
249
- updateAllLayouts(): void;
250
- updateLayout(focusKey: string): void;
258
+ setFocus(focusKey: string, focusDetails?: FocusDetails): Promise<void>;
259
+ updateAllLayouts(): Promise<void>;
260
+ updateLayout(focusKey: string): Promise<void>;
251
261
  updateFocusable(focusKey: string, { node, preferredChildFocusKey, focusable, isFocusBoundary, focusBoundaryDirections, onEnterPress, onEnterRelease, onArrowPress, onFocus, onBlur }: FocusableComponentUpdatePayload): void;
252
- isNativeMode(): boolean;
253
262
  doesFocusableExist(focusKey: string): boolean;
254
263
  /**
255
264
  * This function updates the writing direction
@@ -261,21 +270,8 @@ declare class SpatialNavigationService {
261
270
  * Export singleton
262
271
  */
263
272
  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 }?: {
273
+ export declare const init: ({ debug, visualDebug, throttle: throttleParam, throttleKeypresses, useGetBoundingClientRect, layoutAdapter, shouldFocusDOMNode, domNodeFocusOptions, shouldUseNativeEvents, rtl, distanceCalculationMethod, customDistanceCalculationFunction }?: Partial<SpatialNavigationServiceOptions>) => void, setThrottle: ({ throttle: throttleParam, throttleKeypresses }?: {
278
274
  throttle?: number;
279
275
  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;
276
+ }) => void, destroy: () => void, setKeyMap: (keyMap: BackwardsCompatibleKeyMap) => void, setFocus: (focusKey: string, focusDetails?: FocusDetails) => Promise<void>, navigateByDirection: (direction: string, focusDetails: FocusDetails) => Promise<void>, pause: () => void, resume: () => void, updateAllLayouts: () => Promise<void>, getCurrentFocusKey: () => string, doesFocusableExist: (focusKey: string) => boolean, updateRtl: (rtl: boolean) => void;
281
277
  export {};
@@ -0,0 +1,15 @@
1
+ import { type FocusableComponent } from '../SpatialNavigation';
2
+ import BaseWebAdapter from './web';
3
+ export default class GetBoundingClientRectAdapter extends BaseWebAdapter {
4
+ measureLayout: (component: FocusableComponent) => Promise<{
5
+ node: HTMLElement;
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ left: number;
11
+ top: number;
12
+ right: any;
13
+ bottom: any;
14
+ }>;
15
+ }
@@ -0,0 +1,15 @@
1
+ import { type FocusableComponent, type FocusableComponentLayout } from '../SpatialNavigation';
2
+ export type Key = 'left' | 'right' | 'up' | 'down' | 'enter';
3
+ export type KeyDownEventListener = (key: Key, event: Event) => void;
4
+ export type KeyUpEventListener = (key: Key) => void;
5
+ export type AddEventListenersOptions = {
6
+ keyDown?: KeyDownEventListener;
7
+ keyUp?: KeyUpEventListener;
8
+ };
9
+ export interface LayoutAdapter {
10
+ addEventListeners: (options: AddEventListenersOptions) => void;
11
+ removeEventListeners: () => void;
12
+ measureLayout: (component: FocusableComponent) => Promise<FocusableComponentLayout>;
13
+ blurNode: (component: FocusableComponent) => void;
14
+ focusNode: (component: FocusableComponent) => void;
15
+ }
@@ -0,0 +1,23 @@
1
+ import { type FocusableComponent, type SpatialNavigationService } from '../SpatialNavigation';
2
+ import { type AddEventListenersOptions, type LayoutAdapter } from './types';
3
+ export default class BaseWebAdapter implements LayoutAdapter {
4
+ private service;
5
+ constructor(service: SpatialNavigationService);
6
+ private keyDownEventListener;
7
+ private keyUpEventListener;
8
+ addEventListeners({ keyDown, keyUp }: AddEventListenersOptions): void;
9
+ removeEventListeners(): void;
10
+ measureLayout: (component: FocusableComponent) => Promise<{
11
+ node: HTMLElement;
12
+ x: number;
13
+ y: number;
14
+ width: number;
15
+ height: number;
16
+ left: number;
17
+ top: number;
18
+ right: any;
19
+ bottom: any;
20
+ }>;
21
+ blurNode: (component: FocusableComponent) => void;
22
+ focusNode: (component: FocusableComponent) => void;
23
+ }