@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.
package/dist/index.cjs CHANGED
@@ -18,6 +18,20 @@ PERFORMANCE OF THIS SOFTWARE.
18
18
  ***************************************************************************** */
19
19
  /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
20
20
 
21
+ var extendStatics = function(d, b) {
22
+ extendStatics = Object.setPrototypeOf ||
23
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
24
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
25
+ return extendStatics(d, b);
26
+ };
27
+
28
+ function __extends(d, b) {
29
+ if (typeof b !== "function" && b !== null)
30
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
31
+ extendStatics(d, b);
32
+ function __() { this.constructor = d; }
33
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
34
+ }
21
35
 
22
36
  var __assign = function() {
23
37
  __assign = Object.assign || function __assign(t) {
@@ -30,6 +44,44 @@ var __assign = function() {
30
44
  return __assign.apply(this, arguments);
31
45
  };
32
46
 
47
+ function __awaiter(thisArg, _arguments, P, generator) {
48
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
49
+ return new (P || (P = Promise))(function (resolve, reject) {
50
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
51
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
52
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
53
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
54
+ });
55
+ }
56
+
57
+ function __generator(thisArg, body) {
58
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
59
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
60
+ function verb(n) { return function (v) { return step([n, v]); }; }
61
+ function step(op) {
62
+ if (f) throw new TypeError("Generator is already executing.");
63
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
64
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
65
+ if (y = 0, t) op = [op[0] & 2, t.value];
66
+ switch (op[0]) {
67
+ case 0: case 1: t = op; break;
68
+ case 4: _.label++; return { value: op[1], done: false };
69
+ case 5: _.label++; y = op[1]; op = [0]; continue;
70
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
71
+ default:
72
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
73
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
74
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
75
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
76
+ if (t[2]) _.ops.pop();
77
+ _.trys.pop(); continue;
78
+ }
79
+ op = body.call(thisArg, _);
80
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
81
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
82
+ }
83
+ }
84
+
33
85
  function __spreadArray(to, from, pack) {
34
86
  if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
35
87
  if (ar || !(i in from)) {
@@ -45,79 +97,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
45
97
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
46
98
  };
47
99
 
48
- var WritingDirection$1;
49
- (function (WritingDirection) {
50
- WritingDirection[WritingDirection["LTR"] = 0] = "LTR";
51
- WritingDirection[WritingDirection["RTL"] = 1] = "RTL";
52
- })(WritingDirection$1 || (WritingDirection$1 = {}));
53
- var WritingDirection = WritingDirection$1;
54
-
55
- // We'll make VisualDebugger no-op for any environments lacking a DOM (e.g. SSR and React Native non-web platforms).
56
- var hasDOM = typeof window !== 'undefined' && window.document;
57
- var WIDTH = hasDOM ? window.innerWidth : 0;
58
- var HEIGHT = hasDOM ? window.innerHeight : 0;
59
- var VisualDebugger = /** @class */ (function () {
60
- function VisualDebugger(writingDirection) {
61
- if (hasDOM) {
62
- this.debugCtx = VisualDebugger.createCanvas('sn-debug', '1010', writingDirection);
63
- this.layoutsCtx = VisualDebugger.createCanvas('sn-layouts', '1000', writingDirection);
64
- this.writingDirection = writingDirection;
65
- }
66
- }
67
- VisualDebugger.createCanvas = function (id, zIndex, writingDirection) {
68
- var canvas = document.querySelector("#".concat(id)) || document.createElement('canvas');
69
- canvas.setAttribute('id', id);
70
- canvas.setAttribute('dir', writingDirection === WritingDirection.LTR ? 'ltr' : 'rtl');
71
- var ctx = canvas.getContext('2d');
72
- canvas.style.zIndex = zIndex;
73
- canvas.style.position = 'fixed';
74
- canvas.style.top = '0';
75
- canvas.style.left = '0';
76
- document.body.appendChild(canvas);
77
- canvas.width = WIDTH;
78
- canvas.height = HEIGHT;
79
- return ctx;
80
- };
81
- VisualDebugger.prototype.clear = function () {
82
- if (!hasDOM) {
83
- return;
84
- }
85
- this.debugCtx.clearRect(0, 0, WIDTH, HEIGHT);
86
- };
87
- VisualDebugger.prototype.clearLayouts = function () {
88
- if (!hasDOM) {
89
- return;
90
- }
91
- this.layoutsCtx.clearRect(0, 0, WIDTH, HEIGHT);
92
- };
93
- VisualDebugger.prototype.drawLayout = function (layout, focusKey, parentFocusKey) {
94
- if (!hasDOM) {
95
- return;
96
- }
97
- this.layoutsCtx.strokeStyle = 'green';
98
- this.layoutsCtx.strokeRect(layout.left, layout.top, layout.width, layout.height);
99
- this.layoutsCtx.font = '8px monospace';
100
- this.layoutsCtx.fillStyle = 'red';
101
- var horizontalStartDirection = this.writingDirection === WritingDirection.LTR ? 'left' : 'right';
102
- var horizontalStartCoordinate = layout[horizontalStartDirection];
103
- this.layoutsCtx.fillText(focusKey, horizontalStartCoordinate, layout.top + 10);
104
- this.layoutsCtx.fillText(parentFocusKey, horizontalStartCoordinate, layout.top + 25);
105
- this.layoutsCtx.fillText("".concat(horizontalStartDirection, ": ").concat(horizontalStartCoordinate), horizontalStartCoordinate, layout.top + 40);
106
- this.layoutsCtx.fillText("top: ".concat(layout.top), horizontalStartCoordinate, layout.top + 55);
107
- };
108
- VisualDebugger.prototype.drawPoint = function (x, y, color, size) {
109
- if (color === void 0) { color = 'blue'; }
110
- if (size === void 0) { size = 10; }
111
- if (!hasDOM) {
112
- return;
113
- }
114
- this.debugCtx.strokeStyle = color;
115
- this.debugCtx.lineWidth = 3;
116
- this.debugCtx.strokeRect(x - size / 2, y - size / 2, size, size);
117
- };
118
- return VisualDebugger;
119
- }());
120
-
121
100
  var ELEMENT_NODE = 1;
122
101
  var getRect = function (node) {
123
102
  var offsetParent = node.offsetParent;
@@ -200,6 +179,241 @@ var getBoundingClientRect = function (node) {
200
179
  };
201
180
  };
202
181
 
182
+ var getKeyCode = function (event) {
183
+ return event.keyCode || event.code || event.key;
184
+ };
185
+ var BaseWebAdapter = /** @class */ (function () {
186
+ function BaseWebAdapter(service) {
187
+ var _this = this;
188
+ this.service = service;
189
+ this.measureLayout = function (component) { return __awaiter(_this, void 0, void 0, function () {
190
+ return __generator(this, function (_a) {
191
+ return [2 /*return*/, (__assign(__assign({}, measureLayout(component.node)), { node: component.node }))];
192
+ });
193
+ }); };
194
+ this.blurNode = function (component) {
195
+ var _a, _b;
196
+ if (component.node && _this.service.options.shouldFocusDOMNode) {
197
+ (_b = (_a = component.node) === null || _a === void 0 ? void 0 : _a.removeAttribute) === null || _b === void 0 ? void 0 : _b.call(_a, 'data-focused');
198
+ }
199
+ };
200
+ this.focusNode = function (component) {
201
+ var _a, _b;
202
+ if (component.node && _this.service.options.shouldFocusDOMNode) {
203
+ component.node.focus(_this.service.options.domNodeFocusOptions);
204
+ }
205
+ (_b = (_a = component.node) === null || _a === void 0 ? void 0 : _a.setAttribute) === null || _b === void 0 ? void 0 : _b.call(_a, 'data-focused', 'true');
206
+ };
207
+ }
208
+ BaseWebAdapter.prototype.addEventListeners = function (_a) {
209
+ var _this = this;
210
+ var keyDown = _a.keyDown, keyUp = _a.keyUp;
211
+ this.keyDownEventListener = function (event) {
212
+ var keyCode = getKeyCode(event);
213
+ var key = lodashEs.findKey(_this.service.getKeyMap(), function (codeList) {
214
+ return codeList.includes(keyCode);
215
+ });
216
+ if (!key) {
217
+ return;
218
+ }
219
+ if (!_this.service.options.shouldUseNativeEvents) {
220
+ event.preventDefault();
221
+ event.stopPropagation();
222
+ }
223
+ keyDown === null || keyDown === void 0 ? void 0 : keyDown(key, event);
224
+ };
225
+ this.keyUpEventListener = function (event) {
226
+ var keyCode = getKeyCode(event);
227
+ var key = lodashEs.findKey(_this.service.getKeyMap(), function (codeList) {
228
+ return codeList.includes(keyCode);
229
+ });
230
+ if (!key) {
231
+ return;
232
+ }
233
+ keyUp === null || keyUp === void 0 ? void 0 : keyUp(key);
234
+ };
235
+ window.addEventListener('keyup', this.keyUpEventListener);
236
+ window.addEventListener('keydown', this.keyDownEventListener);
237
+ };
238
+ BaseWebAdapter.prototype.removeEventListeners = function () {
239
+ window.removeEventListener('keyup', this.keyUpEventListener);
240
+ window.removeEventListener('keydown', this.keyDownEventListener);
241
+ };
242
+ return BaseWebAdapter;
243
+ }());
244
+
245
+ var GetBoundingClientRectAdapter = /** @class */ (function (_super) {
246
+ __extends(GetBoundingClientRectAdapter, _super);
247
+ function GetBoundingClientRectAdapter() {
248
+ var _this = _super !== null && _super.apply(this, arguments) || this;
249
+ _this.measureLayout = function (component) { return __awaiter(_this, void 0, void 0, function () {
250
+ return __generator(this, function (_a) {
251
+ return [2 /*return*/, (__assign(__assign({}, getBoundingClientRect(component.node)), { node: component.node }))];
252
+ });
253
+ }); };
254
+ return _this;
255
+ }
256
+ return GetBoundingClientRectAdapter;
257
+ }(BaseWebAdapter));
258
+
259
+ var WritingDirection$1;
260
+ (function (WritingDirection) {
261
+ WritingDirection[WritingDirection["LTR"] = 0] = "LTR";
262
+ WritingDirection[WritingDirection["RTL"] = 1] = "RTL";
263
+ })(WritingDirection$1 || (WritingDirection$1 = {}));
264
+ var WritingDirection = WritingDirection$1;
265
+
266
+ // We'll make VisualDebugger no-op for any environments lacking a DOM (e.g. SSR and React Native non-web platforms).
267
+ var hasDOM = typeof window !== 'undefined' && window.document;
268
+ var WIDTH = hasDOM ? window.innerWidth : 0;
269
+ var HEIGHT = hasDOM ? window.innerHeight : 0;
270
+ var VisualDebugger = /** @class */ (function () {
271
+ function VisualDebugger(writingDirection) {
272
+ if (hasDOM) {
273
+ this.debugCtx = VisualDebugger.createCanvas('sn-debug', '1010', writingDirection);
274
+ this.layoutsCtx = VisualDebugger.createCanvas('sn-layouts', '1000', writingDirection);
275
+ this.writingDirection = writingDirection;
276
+ }
277
+ }
278
+ VisualDebugger.createCanvas = function (id, zIndex, writingDirection) {
279
+ var canvas = document.querySelector("#".concat(id)) || document.createElement('canvas');
280
+ canvas.setAttribute('id', id);
281
+ canvas.setAttribute('dir', writingDirection === WritingDirection.LTR ? 'ltr' : 'rtl');
282
+ var ctx = canvas.getContext('2d');
283
+ canvas.style.zIndex = zIndex;
284
+ canvas.style.position = 'fixed';
285
+ canvas.style.top = '0';
286
+ canvas.style.left = '0';
287
+ document.body.appendChild(canvas);
288
+ canvas.width = WIDTH;
289
+ canvas.height = HEIGHT;
290
+ return ctx;
291
+ };
292
+ VisualDebugger.prototype.clear = function () {
293
+ if (!hasDOM) {
294
+ return;
295
+ }
296
+ this.debugCtx.clearRect(0, 0, WIDTH, HEIGHT);
297
+ };
298
+ VisualDebugger.prototype.clearLayouts = function () {
299
+ if (!hasDOM) {
300
+ return;
301
+ }
302
+ this.layoutsCtx.clearRect(0, 0, WIDTH, HEIGHT);
303
+ };
304
+ VisualDebugger.prototype.drawLayout = function (layout, focusKey, parentFocusKey) {
305
+ if (!hasDOM) {
306
+ return;
307
+ }
308
+ this.layoutsCtx.strokeStyle = 'green';
309
+ this.layoutsCtx.strokeRect(layout.left, layout.top, layout.width, layout.height);
310
+ this.layoutsCtx.font = '8px monospace';
311
+ this.layoutsCtx.fillStyle = 'red';
312
+ var horizontalStartDirection = this.writingDirection === WritingDirection.LTR ? 'left' : 'right';
313
+ var horizontalStartCoordinate = layout[horizontalStartDirection];
314
+ this.layoutsCtx.fillText(focusKey, horizontalStartCoordinate, layout.top + 10);
315
+ this.layoutsCtx.fillText(parentFocusKey, horizontalStartCoordinate, layout.top + 25);
316
+ this.layoutsCtx.fillText("".concat(horizontalStartDirection, ": ").concat(horizontalStartCoordinate), horizontalStartCoordinate, layout.top + 40);
317
+ this.layoutsCtx.fillText("top: ".concat(layout.top), horizontalStartCoordinate, layout.top + 55);
318
+ };
319
+ VisualDebugger.prototype.drawPoint = function (x, y, color, size) {
320
+ if (color === void 0) { color = 'blue'; }
321
+ if (size === void 0) { size = 10; }
322
+ if (!hasDOM) {
323
+ return;
324
+ }
325
+ this.debugCtx.strokeStyle = color;
326
+ this.debugCtx.lineWidth = 3;
327
+ this.debugCtx.strokeRect(x - size / 2, y - size / 2, size, size);
328
+ };
329
+ return VisualDebugger;
330
+ }());
331
+
332
+ /**
333
+ * Scheduler provides a simple way to queue and execute tasks in a strict sequence.
334
+ * - Regular tasks are run one after another; if a new task is scheduled before the current one starts, it replaces the pending next task.
335
+ * - Priority tasks are added to a separate queue and will be executed before any remaining regular tasks.
336
+ */
337
+ var Scheduler = /** @class */ (function () {
338
+ function Scheduler() {
339
+ this.nextPriorityTasks = [];
340
+ }
341
+ Scheduler.prototype.tick = function () {
342
+ return __awaiter(this, void 0, void 0, function () {
343
+ var _this = this;
344
+ var _a;
345
+ return __generator(this, function (_b) {
346
+ switch (_b.label) {
347
+ case 0:
348
+ _b.trys.push([0, , 2, 8]);
349
+ return [4 /*yield*/, ((_a = this.currentTask) === null || _a === void 0 ? void 0 : _a.call(this))];
350
+ case 1:
351
+ _b.sent();
352
+ return [3 /*break*/, 8];
353
+ case 2:
354
+ if (!(this.nextPriorityTasks.length > 0)) return [3 /*break*/, 4];
355
+ this.currentTask = function () {
356
+ return Promise.all(_this.nextPriorityTasks.map(function (task) { return task(); }));
357
+ };
358
+ this.nextPriorityTasks = [];
359
+ return [4 /*yield*/, this.tick()];
360
+ case 3:
361
+ _b.sent();
362
+ return [3 /*break*/, 7];
363
+ case 4:
364
+ if (!this.nextTask) return [3 /*break*/, 6];
365
+ this.currentTask = this.nextTask;
366
+ this.nextTask = undefined;
367
+ return [4 /*yield*/, this.tick()];
368
+ case 5:
369
+ _b.sent();
370
+ return [3 /*break*/, 7];
371
+ case 6:
372
+ this.currentTask = undefined;
373
+ _b.label = 7;
374
+ case 7: return [7 /*endfinally*/];
375
+ case 8: return [2 /*return*/];
376
+ }
377
+ });
378
+ });
379
+ };
380
+ Scheduler.prototype.bind = function (fn, context) {
381
+ var _this = this;
382
+ return function () {
383
+ var args = [];
384
+ for (var _i = 0; _i < arguments.length; _i++) {
385
+ args[_i] = arguments[_i];
386
+ }
387
+ return __awaiter(_this, void 0, void 0, function () {
388
+ var _this = this;
389
+ return __generator(this, function (_a) {
390
+ return [2 /*return*/, this.schedulePriority(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
391
+ return [2 /*return*/, fn.bind(context).apply(void 0, args)];
392
+ }); }); })];
393
+ });
394
+ });
395
+ };
396
+ };
397
+ Scheduler.prototype.schedule = function (task) {
398
+ if (this.currentTask) {
399
+ this.nextTask = task;
400
+ }
401
+ else {
402
+ this.currentTask = task;
403
+ this.tick();
404
+ }
405
+ };
406
+ Scheduler.prototype.schedulePriority = function (task) {
407
+ if (this.currentTask) {
408
+ this.nextPriorityTasks.push(task);
409
+ }
410
+ else {
411
+ task();
412
+ }
413
+ };
414
+ return Scheduler;
415
+ }());
416
+
203
417
  var _a;
204
418
  var DIRECTION_LEFT = 'left';
205
419
  var DIRECTION_RIGHT = 'right';
@@ -225,6 +439,7 @@ var DIAGONAL_SLICE_WEIGHT = 1;
225
439
  */
226
440
  var MAIN_COORDINATE_WEIGHT = 5;
227
441
  var AUTO_RESTORE_FOCUS_DELAY = 300;
442
+ var LAYOUT_STALE_TIME = 16; // 60fps
228
443
  var DEBUG_FN_COLORS = ['#0FF', '#FF0', '#F0F'];
229
444
  var THROTTLE_OPTIONS = {
230
445
  leading: true,
@@ -255,8 +470,16 @@ var normalizeKeyMap = function (keyMap) {
255
470
  });
256
471
  return newKeyMap;
257
472
  };
473
+ var DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS = {
474
+ debug: false,
475
+ throttle: 0,
476
+ throttleKeypresses: false,
477
+ domNodeFocusOptions: {},
478
+ shouldUseNativeEvents: false,
479
+ distanceCalculationMethod: 'corners'};
258
480
  var SpatialNavigationService = /** @class */ (function () {
259
481
  function SpatialNavigationService() {
482
+ this.scheduler = new Scheduler();
260
483
  /**
261
484
  * Storage for all focusable components
262
485
  */
@@ -272,10 +495,8 @@ var SpatialNavigationService = /** @class */ (function () {
272
495
  this.parentsHavingFocusedChild = [];
273
496
  this.domNodeFocusOptions = {};
274
497
  this.enabled = false;
275
- this.nativeMode = false;
276
498
  this.throttle = 0;
277
499
  this.throttleKeypresses = false;
278
- this.useGetBoundingClientRect = false;
279
500
  this.shouldFocusDOMNode = false;
280
501
  this.shouldUseNativeEvents = false;
281
502
  this.writingDirection = WritingDirection.LTR;
@@ -291,8 +512,10 @@ var SpatialNavigationService = /** @class */ (function () {
291
512
  this.pause = this.pause.bind(this);
292
513
  this.resume = this.resume.bind(this);
293
514
  this.setFocus = this.setFocus.bind(this);
294
- this.updateAllLayouts = this.updateAllLayouts.bind(this);
295
- this.navigateByDirection = this.navigateByDirection.bind(this);
515
+ this.updateAllLayouts = this.scheduler.bind(this.updateAllLayouts, this);
516
+ this.navigateByDirection = this.scheduler.bind(this.navigateByDirection, this);
517
+ this.addFocusable = this.scheduler.bind(this.addFocusable, this);
518
+ this.removeFocusable = this.scheduler.bind(this.removeFocusable, this);
296
519
  this.init = this.init.bind(this);
297
520
  this.setThrottle = this.setThrottle.bind(this);
298
521
  this.destroy = this.destroy.bind(this);
@@ -309,6 +532,17 @@ var SpatialNavigationService = /** @class */ (function () {
309
532
  this.logIndex = 0;
310
533
  this.distanceCalculationMethod = 'corners';
311
534
  }
535
+ Object.defineProperty(SpatialNavigationService.prototype, "options", {
536
+ get: function () {
537
+ return {
538
+ shouldFocusDOMNode: this.shouldFocusDOMNode,
539
+ domNodeFocusOptions: this.domNodeFocusOptions,
540
+ shouldUseNativeEvents: this.shouldUseNativeEvents
541
+ };
542
+ },
543
+ enumerable: false,
544
+ configurable: true
545
+ });
312
546
  /**
313
547
  * Used to determine the coordinate that will be used to filter items that are over the "edge"
314
548
  */
@@ -497,56 +731,73 @@ var SpatialNavigationService = /** @class */ (function () {
497
731
  };
498
732
  SpatialNavigationService.prototype.init = function (_a) {
499
733
  var _this = this;
500
- var _b = _a === void 0 ? {} : _a, _c = _b.debug, debug = _c === void 0 ? false : _c, _d = _b.visualDebug, visualDebug = _d === void 0 ? false : _d, _e = _b.nativeMode, nativeMode = _e === void 0 ? false : _e, _f = _b.throttle, throttleParam = _f === void 0 ? 0 : _f, _g = _b.throttleKeypresses, throttleKeypresses = _g === void 0 ? false : _g, _h = _b.useGetBoundingClientRect, useGetBoundingClientRect = _h === void 0 ? false : _h, _j = _b.shouldFocusDOMNode, shouldFocusDOMNode = _j === void 0 ? false : _j, _k = _b.domNodeFocusOptions, domNodeFocusOptions = _k === void 0 ? {} : _k, _l = _b.shouldUseNativeEvents, shouldUseNativeEvents = _l === void 0 ? false : _l, _m = _b.rtl, rtl = _m === void 0 ? false : _m, _o = _b.distanceCalculationMethod, distanceCalculationMethod = _o === void 0 ? 'corners' : _o, _p = _b.customDistanceCalculationFunction, customDistanceCalculationFunction = _p === void 0 ? undefined : _p;
734
+ var _b = _a === void 0 ? {} : _a, debug = _b.debug, visualDebug = _b.visualDebug, throttleParam = _b.throttle, throttleKeypresses = _b.throttleKeypresses, useGetBoundingClientRect = _b.useGetBoundingClientRect, layoutAdapter = _b.layoutAdapter, shouldFocusDOMNode = _b.shouldFocusDOMNode, domNodeFocusOptions = _b.domNodeFocusOptions, shouldUseNativeEvents = _b.shouldUseNativeEvents, rtl = _b.rtl, distanceCalculationMethod = _b.distanceCalculationMethod, _c = _b.customDistanceCalculationFunction, customDistanceCalculationFunction = _c === void 0 ? undefined : _c;
501
735
  if (!this.enabled) {
502
- this.domNodeFocusOptions = domNodeFocusOptions;
503
- this.enabled = true;
504
- this.nativeMode = nativeMode;
505
- this.throttleKeypresses = throttleKeypresses;
506
- this.useGetBoundingClientRect = useGetBoundingClientRect;
507
- this.shouldFocusDOMNode = shouldFocusDOMNode && !nativeMode;
508
- this.shouldUseNativeEvents = shouldUseNativeEvents;
736
+ this.domNodeFocusOptions =
737
+ domNodeFocusOptions !== null && domNodeFocusOptions !== void 0 ? domNodeFocusOptions : DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS.domNodeFocusOptions;
738
+ this.throttleKeypresses =
739
+ throttleKeypresses !== null && throttleKeypresses !== void 0 ? throttleKeypresses : DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS.throttleKeypresses;
740
+ /*
741
+ * If layoutAdapter is a constructor, create a new instance of the class
742
+ * If it's an object, merge it with the default adapter
743
+ */
744
+ if (typeof layoutAdapter === 'function') {
745
+ var LayoutAdapterClass = layoutAdapter;
746
+ this.layoutAdapter = new LayoutAdapterClass(this);
747
+ }
748
+ else {
749
+ if (useGetBoundingClientRect) {
750
+ console.warn('useGetBoundingClientRect is deprecated. Please use layoutAdapter API instead.');
751
+ this.layoutAdapter = new GetBoundingClientRectAdapter(this);
752
+ }
753
+ else {
754
+ this.layoutAdapter = new BaseWebAdapter(this);
755
+ }
756
+ // Override specific methods
757
+ if (layoutAdapter) {
758
+ lodashEs.assign(this.layoutAdapter, layoutAdapter);
759
+ }
760
+ }
761
+ this.shouldFocusDOMNode = shouldFocusDOMNode;
762
+ this.shouldUseNativeEvents =
763
+ shouldUseNativeEvents !== null && shouldUseNativeEvents !== void 0 ? shouldUseNativeEvents : DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS.shouldUseNativeEvents;
509
764
  this.writingDirection = rtl ? WritingDirection.RTL : WritingDirection.LTR;
510
- this.distanceCalculationMethod = distanceCalculationMethod;
765
+ this.distanceCalculationMethod =
766
+ distanceCalculationMethod !== null && distanceCalculationMethod !== void 0 ? distanceCalculationMethod : DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS.distanceCalculationMethod;
511
767
  this.customDistanceCalculationFunction =
512
768
  customDistanceCalculationFunction;
513
- this.debug = debug;
514
- if (!this.nativeMode) {
515
- if (Number.isInteger(throttleParam) && throttleParam > 0) {
516
- this.throttle = throttleParam;
517
- }
518
- this.bindEventHandlers();
519
- if (visualDebug) {
520
- this.visualDebugger = new VisualDebugger(this.writingDirection);
521
- var draw_1 = function () {
522
- requestAnimationFrame(function () {
523
- _this.visualDebugger.clearLayouts();
524
- lodashEs.forOwn(_this.focusableComponents, function (component, focusKey) {
525
- _this.visualDebugger.drawLayout(component.layout, focusKey, component.parentFocusKey);
526
- });
527
- draw_1();
769
+ this.debug = debug !== null && debug !== void 0 ? debug : DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS.debug;
770
+ this.throttle =
771
+ throttleParam !== null && throttleParam !== void 0 ? throttleParam : DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS.throttle;
772
+ this.bindEventHandlers();
773
+ if (visualDebug) {
774
+ this.visualDebugger = new VisualDebugger(this.writingDirection);
775
+ var draw_1 = function () {
776
+ requestAnimationFrame(function () {
777
+ _this.visualDebugger.clearLayouts();
778
+ lodashEs.forOwn(_this.focusableComponents, function (component, focusKey) {
779
+ _this.visualDebugger.drawLayout(component.layout, focusKey, component.parentFocusKey);
528
780
  });
529
- };
530
- draw_1();
531
- }
781
+ draw_1();
782
+ });
783
+ };
784
+ draw_1();
532
785
  }
786
+ this.enabled = true;
533
787
  }
534
788
  };
535
789
  SpatialNavigationService.prototype.setThrottle = function (_a) {
536
- var _b = _a === void 0 ? {} : _a, _c = _b.throttle, throttleParam = _c === void 0 ? 0 : _c, _d = _b.throttleKeypresses, throttleKeypresses = _d === void 0 ? false : _d;
790
+ var _b = _a === void 0 ? {} : _a, _c = _b.throttle, throttleParam = _c === void 0 ? DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS.throttle : _c, _d = _b.throttleKeypresses, throttleKeypresses = _d === void 0 ? DEFAULT_SPATIAL_NAVIGATION_SERVICE_OPTIONS.throttleKeypresses : _d;
537
791
  this.throttleKeypresses = throttleKeypresses;
538
- if (!this.nativeMode) {
539
- this.unbindEventHandlers();
540
- if (Number.isInteger(throttleParam)) {
541
- this.throttle = throttleParam;
542
- }
543
- this.bindEventHandlers();
792
+ this.unbindEventHandlers();
793
+ if (Number.isInteger(throttleParam)) {
794
+ this.throttle = throttleParam;
544
795
  }
796
+ this.bindEventHandlers();
545
797
  };
546
798
  SpatialNavigationService.prototype.destroy = function () {
547
799
  if (this.enabled) {
548
800
  this.enabled = false;
549
- this.nativeMode = false;
550
801
  this.throttle = 0;
551
802
  this.throttleKeypresses = false;
552
803
  this.focusKey = null;
@@ -560,92 +811,78 @@ var SpatialNavigationService = /** @class */ (function () {
560
811
  SpatialNavigationService.prototype.getEventType = function (keyCode) {
561
812
  return lodashEs.findKey(this.getKeyMap(), function (codeList) { return codeList.includes(keyCode); });
562
813
  };
563
- SpatialNavigationService.getKeyCode = function (event) {
564
- return event.keyCode || event.code || event.key;
565
- };
566
814
  SpatialNavigationService.prototype.bindEventHandlers = function () {
567
815
  var _this = this;
568
- // We check both because the React Native remote debugger implements window, but not window.addEventListener.
569
- if (typeof window !== 'undefined' && window.addEventListener) {
570
- this.keyDownEventListener = function (event) {
571
- if (_this.paused === true) {
572
- return;
573
- }
574
- if (_this.debug) {
575
- _this.logIndex += 1;
576
- }
577
- var keyCode = SpatialNavigationService.getKeyCode(event);
578
- var eventType = _this.getEventType(keyCode);
579
- if (!eventType) {
580
- return;
581
- }
582
- _this.pressedKeys[eventType] = _this.pressedKeys[eventType]
583
- ? _this.pressedKeys[eventType] + 1
584
- : 1;
585
- if (!_this.shouldUseNativeEvents) {
586
- event.preventDefault();
587
- event.stopPropagation();
588
- }
589
- var keysDetails = {
590
- pressedKeys: _this.pressedKeys
591
- };
592
- if (eventType === KEY_ENTER && _this.focusKey) {
593
- _this.onEnterPress(keysDetails);
594
- return;
595
- }
596
- var preventDefaultNavigation = _this.onArrowPress(eventType, keysDetails) === false;
597
- if (_this.visualDebugger) {
598
- _this.visualDebugger.clear();
599
- }
600
- if (preventDefaultNavigation) {
601
- _this.log('keyDownEventListener', 'default navigation prevented');
602
- }
603
- else {
604
- var direction = lodashEs.findKey(_this.getKeyMap(), function (codeList) {
605
- return codeList.includes(keyCode);
606
- });
607
- _this.smartNavigate(direction, null, { event: event });
608
- }
609
- };
610
- // Apply throttle only if the option we got is > 0 to avoid limiting the listener to every animation frame
611
- if (this.throttle) {
612
- this.keyDownEventListenerThrottled = lodashEs.throttle(this.keyDownEventListener.bind(this), this.throttle, THROTTLE_OPTIONS);
613
- }
614
- // When throttling then make sure to only throttle key down and cancel any queued functions in case of key up
615
- this.keyUpEventListener = function (event) {
616
- var keyCode = SpatialNavigationService.getKeyCode(event);
617
- var eventType = _this.getEventType(keyCode);
618
- delete _this.pressedKeys[eventType];
816
+ this.keyDownEventListener = function (key, event) {
817
+ _this.scheduler.schedule(function () { return __awaiter(_this, void 0, void 0, function () {
818
+ var preventDefaultNavigation;
819
+ return __generator(this, function (_a) {
820
+ switch (_a.label) {
821
+ case 0:
822
+ if (this.paused === true) {
823
+ return [2 /*return*/];
824
+ }
825
+ if (this.debug) {
826
+ this.logIndex += 1;
827
+ }
828
+ this.pressedKeys[key] = this.pressedKeys[key]
829
+ ? this.pressedKeys[key] + 1
830
+ : 1;
831
+ if (key === KEY_ENTER && this.focusKey) {
832
+ this.onEnterPress({ pressedKeys: this.pressedKeys });
833
+ return [2 /*return*/];
834
+ }
835
+ preventDefaultNavigation = this.onArrowPress(key, { pressedKeys: this.pressedKeys }) === false;
836
+ if (this.visualDebugger) {
837
+ this.visualDebugger.clear();
838
+ }
839
+ if (!preventDefaultNavigation) return [3 /*break*/, 1];
840
+ this.log('keyDownEventListener', 'default navigation prevented');
841
+ return [3 /*break*/, 3];
842
+ case 1: return [4 /*yield*/, this.smartNavigate(key, null, { event: event })];
843
+ case 2:
844
+ _a.sent();
845
+ _a.label = 3;
846
+ case 3: return [2 /*return*/];
847
+ }
848
+ });
849
+ }); });
850
+ };
851
+ // Apply throttle only if the option we got is > 0 to avoid limiting the listener to every animation frame
852
+ if (this.throttle) {
853
+ this.keyDownEventListenerThrottled = lodashEs.throttle(this.keyDownEventListener.bind(this), this.throttle, THROTTLE_OPTIONS);
854
+ }
855
+ // When throttling then make sure to only throttle key down and cancel any queued functions in case of key up
856
+ this.keyUpEventListener = function (key) {
857
+ _this.scheduler.schedule(function () {
858
+ delete _this.pressedKeys[key];
619
859
  if (_this.throttle && !_this.throttleKeypresses) {
620
860
  _this.keyDownEventListenerThrottled.cancel();
621
861
  }
622
- if (eventType === KEY_ENTER && _this.focusKey) {
862
+ if (key === KEY_ENTER && _this.focusKey) {
623
863
  _this.onEnterRelease();
624
864
  }
625
- if (_this.focusKey && (eventType === DIRECTION_LEFT ||
626
- eventType === DIRECTION_RIGHT ||
627
- eventType === DIRECTION_UP ||
628
- eventType === DIRECTION_DOWN)) {
629
- _this.onArrowRelease(eventType);
865
+ if (_this.focusKey &&
866
+ (key === DIRECTION_LEFT ||
867
+ key === DIRECTION_RIGHT ||
868
+ key === DIRECTION_UP ||
869
+ key === DIRECTION_DOWN)) {
870
+ _this.onArrowRelease(key);
630
871
  }
631
- };
632
- window.addEventListener('keyup', this.keyUpEventListener);
633
- window.addEventListener('keydown', this.throttle
872
+ });
873
+ };
874
+ this.layoutAdapter.addEventListeners({
875
+ keyDown: this.throttle
634
876
  ? this.keyDownEventListenerThrottled
635
- : this.keyDownEventListener);
636
- }
877
+ : this.keyDownEventListener,
878
+ keyUp: this.keyUpEventListener
879
+ });
637
880
  };
638
881
  SpatialNavigationService.prototype.unbindEventHandlers = function () {
639
- // We check both because the React Native remote debugger implements window, but not window.removeEventListener.
640
- if (typeof window !== 'undefined' && window.removeEventListener) {
641
- window.removeEventListener('keyup', this.keyUpEventListener);
642
- this.keyUpEventListener = null;
643
- var listener = this.throttle
644
- ? this.keyDownEventListenerThrottled
645
- : this.keyDownEventListener;
646
- window.removeEventListener('keydown', listener);
647
- this.keyDownEventListener = null;
648
- }
882
+ this.layoutAdapter.removeEventListeners();
883
+ this.keyUpEventListener = null;
884
+ this.keyDownEventListener = null;
885
+ this.keyDownEventListenerThrottled = null;
649
886
  };
650
887
  SpatialNavigationService.prototype.onEnterPress = function (keysDetails) {
651
888
  var component = this.focusableComponents[this.focusKey];
@@ -715,107 +952,135 @@ var SpatialNavigationService = /** @class */ (function () {
715
952
  * navigateByDirection('right') // The focus is moved to right
716
953
  */
717
954
  SpatialNavigationService.prototype.navigateByDirection = function (direction, focusDetails) {
718
- if (this.paused === true || !this.enabled || this.nativeMode) {
719
- return;
720
- }
721
- var validDirections = [
722
- DIRECTION_DOWN,
723
- DIRECTION_UP,
724
- DIRECTION_LEFT,
725
- DIRECTION_RIGHT
726
- ];
727
- if (validDirections.includes(direction)) {
728
- this.log('navigateByDirection', 'direction', direction);
729
- this.smartNavigate(direction, null, focusDetails);
730
- }
731
- else {
732
- this.log('navigateByDirection', "Invalid direction. You passed: `".concat(direction, "`, but you can use only these: "), validDirections);
733
- }
955
+ return __awaiter(this, void 0, void 0, function () {
956
+ var validDirections;
957
+ return __generator(this, function (_a) {
958
+ switch (_a.label) {
959
+ case 0:
960
+ if (this.paused === true || !this.enabled) {
961
+ return [2 /*return*/];
962
+ }
963
+ validDirections = [
964
+ DIRECTION_DOWN,
965
+ DIRECTION_UP,
966
+ DIRECTION_LEFT,
967
+ DIRECTION_RIGHT
968
+ ];
969
+ if (!validDirections.includes(direction)) return [3 /*break*/, 2];
970
+ this.log('navigateByDirection', 'direction', direction);
971
+ return [4 /*yield*/, this.smartNavigate(direction, null, focusDetails)];
972
+ case 1:
973
+ _a.sent();
974
+ return [3 /*break*/, 3];
975
+ case 2:
976
+ this.log('navigateByDirection', "Invalid direction. You passed: `".concat(direction, "`, but you can use only these: "), validDirections);
977
+ _a.label = 3;
978
+ case 3: return [2 /*return*/];
979
+ }
980
+ });
981
+ });
734
982
  };
735
983
  /**
736
984
  * This function navigates between siblings OR goes up by the Tree
737
985
  * Based on the Direction
738
986
  */
739
987
  SpatialNavigationService.prototype.smartNavigate = function (direction, fromParentFocusKey, focusDetails) {
740
- var _this = this;
741
- if (this.nativeMode) {
742
- return;
743
- }
744
- var isVerticalDirection = direction === DIRECTION_DOWN || direction === DIRECTION_UP;
745
- var isIncrementalDirection = direction === DIRECTION_DOWN ||
746
- (this.writingDirection === WritingDirection.LTR
747
- ? direction === DIRECTION_RIGHT
748
- : direction === DIRECTION_LEFT);
749
- this.log('smartNavigate', 'direction', direction);
750
- this.log('smartNavigate', 'fromParentFocusKey', fromParentFocusKey);
751
- this.log('smartNavigate', 'this.focusKey', this.focusKey);
752
- if (!fromParentFocusKey) {
753
- lodashEs.forOwn(this.focusableComponents, function (component) {
754
- // eslint-disable-next-line no-param-reassign
755
- component.layoutUpdated = false;
756
- });
757
- }
758
- var currentComponent = this.focusableComponents[fromParentFocusKey || this.focusKey];
759
- /**
760
- * When there's no currently focused component, an attempt is made, to force focus one of
761
- * the Focusable Containers, that have "forceFocus" flag enabled.
762
- */
763
- if (!fromParentFocusKey && !currentComponent) {
764
- this.setFocus(this.getForcedFocusKey());
765
- return;
766
- }
767
- this.log('smartNavigate', 'currentComponent', currentComponent ? currentComponent.focusKey : undefined, currentComponent ? currentComponent.node : undefined, currentComponent);
768
- if (currentComponent) {
769
- this.updateLayout(currentComponent.focusKey);
770
- var parentFocusKey_1 = currentComponent.parentFocusKey, focusKey = currentComponent.focusKey, layout = currentComponent.layout;
771
- var currentCutoffCoordinate_1 = SpatialNavigationService.getCutoffCoordinate(isVerticalDirection, isIncrementalDirection, false, layout, this.writingDirection);
772
- /**
773
- * Get only the siblings with the coords on the way of our moving direction
774
- */
775
- var siblings = lodashEs.filter(this.focusableComponents, function (component) {
776
- if (component.parentFocusKey === parentFocusKey_1 &&
777
- component.focusable) {
778
- _this.updateLayout(component.focusKey);
779
- var siblingCutoffCoordinate = SpatialNavigationService.getCutoffCoordinate(isVerticalDirection, isIncrementalDirection, true, component.layout, _this.writingDirection);
780
- return isVerticalDirection
781
- ? isIncrementalDirection
782
- ? siblingCutoffCoordinate >= currentCutoffCoordinate_1 // vertical next
783
- : siblingCutoffCoordinate <= currentCutoffCoordinate_1 // vertical previous
784
- : _this.writingDirection === WritingDirection.LTR
785
- ? isIncrementalDirection
786
- ? siblingCutoffCoordinate >= currentCutoffCoordinate_1 // horizontal LTR next
787
- : siblingCutoffCoordinate <= currentCutoffCoordinate_1 // horizontal LTR previous
788
- : isIncrementalDirection
789
- ? siblingCutoffCoordinate <= currentCutoffCoordinate_1 // horizontal RTL next
790
- : siblingCutoffCoordinate >= currentCutoffCoordinate_1; // horizontal RTL previous
988
+ return __awaiter(this, void 0, void 0, function () {
989
+ var isVerticalDirection, isIncrementalDirection, currentComponent, forcedKey, parentFocusKey_1, focusKey, layout, currentCutoffCoordinate_1, threshold_1, siblings, refCorners, sortedSiblings, nextComponent, parentComponent, focusBoundaryDirections;
990
+ var _this = this;
991
+ return __generator(this, function (_a) {
992
+ switch (_a.label) {
993
+ case 0:
994
+ isVerticalDirection = direction === DIRECTION_DOWN || direction === DIRECTION_UP;
995
+ isIncrementalDirection = direction === DIRECTION_DOWN ||
996
+ (this.writingDirection === WritingDirection.LTR
997
+ ? direction === DIRECTION_RIGHT
998
+ : direction === DIRECTION_LEFT);
999
+ this.log('smartNavigate', 'direction', direction);
1000
+ this.log('smartNavigate', 'fromParentFocusKey', fromParentFocusKey);
1001
+ this.log('smartNavigate', 'this.focusKey', this.focusKey);
1002
+ currentComponent = this.focusableComponents[fromParentFocusKey || this.focusKey];
1003
+ /**
1004
+ * When there's no currently focused component, an attempt is made, to force focus one of
1005
+ * the Focusable Containers, that have "forceFocus" flag enabled.
1006
+ */
1007
+ if (!fromParentFocusKey && !currentComponent) {
1008
+ forcedKey = this.getForcedFocusKey();
1009
+ if (forcedKey) {
1010
+ this.setFocus(forcedKey);
1011
+ }
1012
+ else {
1013
+ this.log('smartNavigate', 'Aborted due to missing current component and force-focusable key');
1014
+ }
1015
+ // Current component is null (e.g. nothing to navigate from)
1016
+ return [2 /*return*/];
1017
+ }
1018
+ this.log('smartNavigate', 'currentComponent', currentComponent ? currentComponent.focusKey : undefined, currentComponent ? currentComponent.node : undefined, currentComponent);
1019
+ if (!currentComponent) return [3 /*break*/, 5];
1020
+ return [4 /*yield*/, this.updateLayout(currentComponent.focusKey)];
1021
+ case 1:
1022
+ _a.sent();
1023
+ parentFocusKey_1 = currentComponent.parentFocusKey, focusKey = currentComponent.focusKey, layout = currentComponent.layout;
1024
+ currentCutoffCoordinate_1 = SpatialNavigationService.getCutoffCoordinate(isVerticalDirection, isIncrementalDirection, false, layout, this.writingDirection);
1025
+ threshold_1 = Date.now() - LAYOUT_STALE_TIME;
1026
+ return [4 /*yield*/, Promise.all(Object.values(this.focusableComponents)
1027
+ .filter(function (component) {
1028
+ return component.parentFocusKey === parentFocusKey_1 &&
1029
+ component.focusable &&
1030
+ component.layoutUpdatedAt <= threshold_1;
1031
+ })
1032
+ .map(function (component) { return _this.updateLayout(component.focusKey); }))];
1033
+ case 2:
1034
+ _a.sent();
1035
+ siblings = lodashEs.filter(this.focusableComponents, function (component) {
1036
+ if (component.parentFocusKey === parentFocusKey_1 &&
1037
+ component.focusKey !== currentComponent.focusKey &&
1038
+ component.focusable &&
1039
+ component.layout) {
1040
+ var siblingCutoffCoordinate = SpatialNavigationService.getCutoffCoordinate(isVerticalDirection, isIncrementalDirection, true, component.layout, _this.writingDirection);
1041
+ return isVerticalDirection
1042
+ ? isIncrementalDirection
1043
+ ? siblingCutoffCoordinate >= currentCutoffCoordinate_1 // vertical next
1044
+ : siblingCutoffCoordinate <= currentCutoffCoordinate_1 // vertical previous
1045
+ : _this.writingDirection === WritingDirection.LTR
1046
+ ? isIncrementalDirection
1047
+ ? siblingCutoffCoordinate >= currentCutoffCoordinate_1 // horizontal LTR next
1048
+ : siblingCutoffCoordinate <= currentCutoffCoordinate_1 // horizontal LTR previous
1049
+ : isIncrementalDirection
1050
+ ? siblingCutoffCoordinate <= currentCutoffCoordinate_1 // horizontal RTL next
1051
+ : siblingCutoffCoordinate >= currentCutoffCoordinate_1; // horizontal RTL previous
1052
+ }
1053
+ return false;
1054
+ });
1055
+ if (this.debug) {
1056
+ this.log('smartNavigate', 'currentCutoffCoordinate', currentCutoffCoordinate_1);
1057
+ this.log('smartNavigate', 'siblings', "".concat(siblings.length, " elements:"), siblings.map(function (sibling) { return sibling.focusKey; }).join(', '), siblings.map(function (sibling) { return sibling.node; }), siblings.map(function (sibling) { return sibling; }));
1058
+ }
1059
+ if (this.visualDebugger) {
1060
+ refCorners = SpatialNavigationService.getRefCorners(direction, false, layout);
1061
+ this.visualDebugger.drawPoint(refCorners.a.x, refCorners.a.y);
1062
+ this.visualDebugger.drawPoint(refCorners.b.x, refCorners.b.y);
1063
+ }
1064
+ sortedSiblings = this.sortSiblingsByPriority(siblings, layout, direction, focusKey);
1065
+ nextComponent = lodashEs.first(sortedSiblings);
1066
+ this.log('smartNavigate', 'nextComponent', nextComponent ? nextComponent.focusKey : undefined, nextComponent ? nextComponent.node : undefined, nextComponent);
1067
+ if (!nextComponent) return [3 /*break*/, 3];
1068
+ this.setFocus(nextComponent.focusKey, focusDetails);
1069
+ return [3 /*break*/, 5];
1070
+ case 3:
1071
+ parentComponent = this.focusableComponents[parentFocusKey_1];
1072
+ focusBoundaryDirections = (parentComponent === null || parentComponent === void 0 ? void 0 : parentComponent.isFocusBoundary)
1073
+ ? parentComponent.focusBoundaryDirections || [direction]
1074
+ : [];
1075
+ if (!(!parentComponent || !focusBoundaryDirections.includes(direction))) return [3 /*break*/, 5];
1076
+ return [4 /*yield*/, this.smartNavigate(direction, parentFocusKey_1, focusDetails)];
1077
+ case 4:
1078
+ _a.sent();
1079
+ _a.label = 5;
1080
+ case 5: return [2 /*return*/];
791
1081
  }
792
- return false;
793
1082
  });
794
- if (this.debug) {
795
- this.log('smartNavigate', 'currentCutoffCoordinate', currentCutoffCoordinate_1);
796
- this.log('smartNavigate', 'siblings', "".concat(siblings.length, " elements:"), siblings.map(function (sibling) { return sibling.focusKey; }).join(', '), siblings.map(function (sibling) { return sibling.node; }), siblings.map(function (sibling) { return sibling; }));
797
- }
798
- if (this.visualDebugger) {
799
- var refCorners = SpatialNavigationService.getRefCorners(direction, false, layout);
800
- this.visualDebugger.drawPoint(refCorners.a.x, refCorners.a.y);
801
- this.visualDebugger.drawPoint(refCorners.b.x, refCorners.b.y);
802
- }
803
- var sortedSiblings = this.sortSiblingsByPriority(siblings, layout, direction, focusKey);
804
- var nextComponent = lodashEs.first(sortedSiblings);
805
- this.log('smartNavigate', 'nextComponent', nextComponent ? nextComponent.focusKey : undefined, nextComponent ? nextComponent.node : undefined, nextComponent);
806
- if (nextComponent) {
807
- this.setFocus(nextComponent.focusKey, focusDetails);
808
- }
809
- else {
810
- var parentComponent = this.focusableComponents[parentFocusKey_1];
811
- var focusBoundaryDirections = (parentComponent === null || parentComponent === void 0 ? void 0 : parentComponent.isFocusBoundary)
812
- ? parentComponent.focusBoundaryDirections || [direction]
813
- : [];
814
- if (!parentComponent || !focusBoundaryDirections.includes(direction)) {
815
- this.smartNavigate(direction, parentFocusKey_1, focusDetails);
816
- }
817
- }
818
- }
1083
+ });
819
1084
  };
820
1085
  SpatialNavigationService.prototype.saveLastFocusedChildKey = function (component, focusKey) {
821
1086
  if (component) {
@@ -870,51 +1135,64 @@ var SpatialNavigationService = /** @class */ (function () {
870
1135
  * Based on "targetFocusKey" which means the "intended component to focus"
871
1136
  */
872
1137
  SpatialNavigationService.prototype.getNextFocusKey = function (targetFocusKey) {
873
- var _this = this;
874
- var targetComponent = this.focusableComponents[targetFocusKey];
875
- /**
876
- * Security check, if component doesn't exist, stay on the same focusKey
877
- */
878
- if (!targetComponent || this.nativeMode) {
879
- return targetFocusKey;
880
- }
881
- var children = lodashEs.filter(this.focusableComponents, function (component) {
882
- return component.parentFocusKey === targetFocusKey && component.focusable;
1138
+ return __awaiter(this, void 0, void 0, function () {
1139
+ var targetComponent, children, lastFocusedChildKey, preferredChildFocusKey, childKey;
1140
+ var _this = this;
1141
+ return __generator(this, function (_a) {
1142
+ switch (_a.label) {
1143
+ case 0:
1144
+ targetComponent = this.focusableComponents[targetFocusKey];
1145
+ /**
1146
+ * Security check, if component doesn't exist, stay on the same focusKey
1147
+ */
1148
+ if (!targetComponent) {
1149
+ return [2 /*return*/, targetFocusKey];
1150
+ }
1151
+ children = lodashEs.filter(this.focusableComponents, function (component) {
1152
+ return component.parentFocusKey === targetFocusKey && component.focusable;
1153
+ });
1154
+ if (!(children.length > 0)) return [3 /*break*/, 2];
1155
+ lastFocusedChildKey = targetComponent.lastFocusedChildKey, preferredChildFocusKey = targetComponent.preferredChildFocusKey;
1156
+ this.log('getNextFocusKey', 'lastFocusedChildKey is', lastFocusedChildKey);
1157
+ this.log('getNextFocusKey', 'preferredChildFocusKey is', preferredChildFocusKey);
1158
+ /**
1159
+ * First of all trying to focus last focused child
1160
+ */
1161
+ if (lastFocusedChildKey &&
1162
+ targetComponent.saveLastFocusedChild &&
1163
+ this.isParticipatingFocusableComponent(lastFocusedChildKey)) {
1164
+ this.log('getNextFocusKey', 'lastFocusedChildKey will be focused', lastFocusedChildKey);
1165
+ return [2 /*return*/, this.getNextFocusKey(lastFocusedChildKey)];
1166
+ }
1167
+ /**
1168
+ * If there is no lastFocusedChild, trying to focus the preferred focused key
1169
+ */
1170
+ if (preferredChildFocusKey &&
1171
+ this.isParticipatingFocusableComponent(preferredChildFocusKey)) {
1172
+ this.log('getNextFocusKey', 'preferredChildFocusKey will be focused', preferredChildFocusKey);
1173
+ return [2 /*return*/, this.getNextFocusKey(preferredChildFocusKey)];
1174
+ }
1175
+ /**
1176
+ * Otherwise, trying to focus something by coordinates
1177
+ */
1178
+ return [4 /*yield*/, Promise.all(children.map(function (component) { return _this.updateLayout(component.focusKey); }))];
1179
+ case 1:
1180
+ /**
1181
+ * Otherwise, trying to focus something by coordinates
1182
+ */
1183
+ _a.sent();
1184
+ childKey = getChildClosestToOrigin(children, this.writingDirection).focusKey;
1185
+ this.log('getNextFocusKey', 'childKey will be focused', childKey);
1186
+ return [2 /*return*/, this.getNextFocusKey(childKey)];
1187
+ case 2:
1188
+ /**
1189
+ * If no children, just return targetFocusKey back
1190
+ */
1191
+ this.log('getNextFocusKey', 'targetFocusKey', targetFocusKey);
1192
+ return [2 /*return*/, targetFocusKey];
1193
+ }
1194
+ });
883
1195
  });
884
- if (children.length > 0) {
885
- var lastFocusedChildKey = targetComponent.lastFocusedChildKey, preferredChildFocusKey = targetComponent.preferredChildFocusKey;
886
- this.log('getNextFocusKey', 'lastFocusedChildKey is', lastFocusedChildKey);
887
- this.log('getNextFocusKey', 'preferredChildFocusKey is', preferredChildFocusKey);
888
- /**
889
- * First of all trying to focus last focused child
890
- */
891
- if (lastFocusedChildKey &&
892
- targetComponent.saveLastFocusedChild &&
893
- this.isParticipatingFocusableComponent(lastFocusedChildKey)) {
894
- this.log('getNextFocusKey', 'lastFocusedChildKey will be focused', lastFocusedChildKey);
895
- return this.getNextFocusKey(lastFocusedChildKey);
896
- }
897
- /**
898
- * If there is no lastFocusedChild, trying to focus the preferred focused key
899
- */
900
- if (preferredChildFocusKey &&
901
- this.isParticipatingFocusableComponent(preferredChildFocusKey)) {
902
- this.log('getNextFocusKey', 'preferredChildFocusKey will be focused', preferredChildFocusKey);
903
- return this.getNextFocusKey(preferredChildFocusKey);
904
- }
905
- /**
906
- * Otherwise, trying to focus something by coordinates
907
- */
908
- children.forEach(function (component) { return _this.updateLayout(component.focusKey); });
909
- var childKey = getChildClosestToOrigin(children, this.writingDirection).focusKey;
910
- this.log('getNextFocusKey', 'childKey will be focused', childKey);
911
- return this.getNextFocusKey(childKey);
912
- }
913
- /**
914
- * If no children, just return targetFocusKey back
915
- */
916
- this.log('getNextFocusKey', 'targetFocusKey', targetFocusKey);
917
- return targetFocusKey;
918
1196
  };
919
1197
  SpatialNavigationService.prototype.addFocusable = function (_a) {
920
1198
  var focusKey = _a.focusKey, node = _a.node, parentFocusKey = _a.parentFocusKey, onEnterPress = _a.onEnterPress, onEnterRelease = _a.onEnterRelease, onArrowPress = _a.onArrowPress, onArrowRelease = _a.onArrowRelease, onFocus = _a.onFocus, onBlur = _a.onBlur, saveLastFocusedChild = _a.saveLastFocusedChild, trackChildren = _a.trackChildren, onUpdateFocus = _a.onUpdateFocus, onUpdateHasFocusedChild = _a.onUpdateHasFocusedChild, preferredChildFocusKey = _a.preferredChildFocusKey, autoRestoreFocus = _a.autoRestoreFocus, forceFocus = _a.forceFocus, focusable = _a.focusable, isFocusBoundary = _a.isFocusBoundary, focusBoundaryDirections = _a.focusBoundaryDirections;
@@ -939,6 +1217,7 @@ var SpatialNavigationService = /** @class */ (function () {
939
1217
  autoRestoreFocus: autoRestoreFocus,
940
1218
  forceFocus: forceFocus,
941
1219
  lastFocusedChildKey: null,
1220
+ layoutUpdatedAt: 0,
942
1221
  layout: {
943
1222
  x: 0,
944
1223
  y: 0,
@@ -952,16 +1231,12 @@ var SpatialNavigationService = /** @class */ (function () {
952
1231
  * Node ref is also duplicated in layout to be reported in onFocus callback
953
1232
  */
954
1233
  node: node
955
- },
956
- layoutUpdated: false
1234
+ }
957
1235
  };
958
1236
  if (!node) {
959
1237
  // eslint-disable-next-line no-console
960
1238
  console.warn('Component added without a node reference. This will result in its coordinates being empty and may cause lost focus. Check the "ref" passed to "useFocusable": ', this.focusableComponents[focusKey]);
961
1239
  }
962
- if (this.nativeMode) {
963
- return;
964
- }
965
1240
  this.updateLayout(focusKey);
966
1241
  this.log('addFocusable', 'Component added: ', this.focusableComponents[focusKey]);
967
1242
  /**
@@ -1003,9 +1278,6 @@ var SpatialNavigationService = /** @class */ (function () {
1003
1278
  if (parentComponent && parentComponent.lastFocusedChildKey === focusKey) {
1004
1279
  parentComponent.lastFocusedChildKey = null;
1005
1280
  }
1006
- if (this.nativeMode) {
1007
- return;
1008
- }
1009
1281
  /**
1010
1282
  * If the component was also focused at this time, OR had focused child, focus its parent -> it will focus another child
1011
1283
  * Normally the order of components unmount is children -> parents, but sometimes parent can be removed before the child
@@ -1024,33 +1296,43 @@ var SpatialNavigationService = /** @class */ (function () {
1024
1296
  }
1025
1297
  };
1026
1298
  SpatialNavigationService.prototype.getNodeLayoutByFocusKey = function (focusKey) {
1027
- var component = this.focusableComponents[focusKey];
1028
- if (component) {
1029
- this.updateLayout(component.focusKey);
1030
- return component.layout;
1031
- }
1032
- return null;
1299
+ return __awaiter(this, void 0, void 0, function () {
1300
+ var component;
1301
+ return __generator(this, function (_a) {
1302
+ switch (_a.label) {
1303
+ case 0:
1304
+ component = this.focusableComponents[focusKey];
1305
+ if (!component) return [3 /*break*/, 2];
1306
+ return [4 /*yield*/, this.updateLayout(component.focusKey)];
1307
+ case 1:
1308
+ _a.sent();
1309
+ return [2 /*return*/, component.layout];
1310
+ case 2: return [2 /*return*/, null];
1311
+ }
1312
+ });
1313
+ });
1033
1314
  };
1034
1315
  SpatialNavigationService.prototype.setCurrentFocusedKey = function (newFocusKey, focusDetails) {
1035
- var _a, _b, _c, _d;
1316
+ var _this = this;
1036
1317
  if (this.isFocusableComponent(this.focusKey) &&
1037
1318
  newFocusKey !== this.focusKey) {
1038
- var oldComponent = this.focusableComponents[this.focusKey];
1039
- oldComponent.onUpdateFocus(false);
1040
- oldComponent.onBlur(this.getNodeLayoutByFocusKey(this.focusKey), focusDetails);
1041
- (_b = (_a = oldComponent.node) === null || _a === void 0 ? void 0 : _a.removeAttribute) === null || _b === void 0 ? void 0 : _b.call(_a, 'data-focused');
1042
- this.log('setCurrentFocusedKey', 'onBlur', oldComponent);
1319
+ var oldComponent_1 = this.focusableComponents[this.focusKey];
1320
+ oldComponent_1.onUpdateFocus(false);
1321
+ this.layoutAdapter.blurNode(oldComponent_1);
1322
+ this.getNodeLayoutByFocusKey(this.focusKey).then(function (layout) {
1323
+ oldComponent_1.onBlur(layout, focusDetails);
1324
+ _this.log('setCurrentFocusedKey', 'onBlur', oldComponent_1);
1325
+ });
1043
1326
  }
1044
1327
  this.focusKey = newFocusKey;
1045
1328
  if (this.isFocusableComponent(this.focusKey)) {
1046
- var newComponent = this.focusableComponents[this.focusKey];
1047
- if (this.shouldFocusDOMNode && newComponent.node) {
1048
- newComponent.node.focus(this.domNodeFocusOptions);
1049
- }
1050
- (_d = (_c = newComponent.node) === null || _c === void 0 ? void 0 : _c.setAttribute) === null || _d === void 0 ? void 0 : _d.call(_c, 'data-focused', 'true');
1051
- newComponent.onUpdateFocus(true);
1052
- newComponent.onFocus(this.getNodeLayoutByFocusKey(this.focusKey), focusDetails);
1053
- this.log('setCurrentFocusedKey', 'onFocus', newComponent);
1329
+ var newComponent_1 = this.focusableComponents[this.focusKey];
1330
+ this.layoutAdapter.focusNode(newComponent_1);
1331
+ newComponent_1.onUpdateFocus(true);
1332
+ this.getNodeLayoutByFocusKey(this.focusKey).then(function (layout) {
1333
+ newComponent_1.onFocus(layout, focusDetails);
1334
+ _this.log('setCurrentFocusedKey', 'onFocus', newComponent_1);
1335
+ });
1054
1336
  }
1055
1337
  };
1056
1338
  SpatialNavigationService.prototype.updateParentsHasFocusedChild = function (focusKey, focusDetails) {
@@ -1119,13 +1401,19 @@ var SpatialNavigationService = /** @class */ (function () {
1119
1401
  this.focusableComponents[focusKey].focusable);
1120
1402
  };
1121
1403
  SpatialNavigationService.prototype.onIntermediateNodeBecameFocused = function (focusKey, focusDetails) {
1404
+ var _this = this;
1122
1405
  if (this.isParticipatingFocusableComponent(focusKey)) {
1123
- this.focusableComponents[focusKey].onFocus(this.getNodeLayoutByFocusKey(focusKey), focusDetails);
1406
+ this.getNodeLayoutByFocusKey(focusKey).then(function (layout) {
1407
+ _this.focusableComponents[focusKey].onFocus(layout, focusDetails);
1408
+ });
1124
1409
  }
1125
1410
  };
1126
1411
  SpatialNavigationService.prototype.onIntermediateNodeBecameBlurred = function (focusKey, focusDetails) {
1412
+ var _this = this;
1127
1413
  if (this.isParticipatingFocusableComponent(focusKey)) {
1128
- this.focusableComponents[focusKey].onBlur(this.getNodeLayoutByFocusKey(focusKey), focusDetails);
1414
+ this.getNodeLayoutByFocusKey(focusKey).then(function (layout) {
1415
+ _this.focusableComponents[focusKey].onBlur(layout, focusDetails);
1416
+ });
1129
1417
  }
1130
1418
  };
1131
1419
  SpatialNavigationService.prototype.pause = function () {
@@ -1134,54 +1422,90 @@ var SpatialNavigationService = /** @class */ (function () {
1134
1422
  SpatialNavigationService.prototype.resume = function () {
1135
1423
  this.paused = false;
1136
1424
  };
1137
- SpatialNavigationService.prototype.setFocus = function (focusKey, focusDetails) {
1138
- if (focusDetails === void 0) { focusDetails = {}; }
1139
- // Cancel any pending auto-restore focus calls if we are setting focus manually
1140
- this.setFocusDebounced.cancel();
1141
- if (!this.enabled) {
1142
- return;
1143
- }
1144
- this.log('setFocus', 'focusKey', focusKey);
1145
- /**
1146
- * When focusKey is not provided or is equal to `ROOT_FOCUS_KEY`, an attempt is made,
1147
- * to force focus one of the Focusable Containers, that have "forceFocus" flag enabled.
1148
- * A component closest to the top left viewport corner (0,0) is force-focused.
1149
- */
1150
- if (!focusKey || focusKey === ROOT_FOCUS_KEY) {
1151
- // eslint-disable-next-line no-param-reassign
1152
- focusKey = this.getForcedFocusKey();
1153
- }
1154
- var newFocusKey = this.getNextFocusKey(focusKey);
1155
- this.log('setFocus', 'newFocusKey', newFocusKey);
1156
- this.setCurrentFocusedKey(newFocusKey, focusDetails);
1157
- this.updateParentsHasFocusedChild(newFocusKey, focusDetails);
1158
- this.updateParentsLastFocusedChild(newFocusKey);
1425
+ SpatialNavigationService.prototype.setFocus = function (focusKey_1) {
1426
+ return __awaiter(this, arguments, void 0, function (focusKey, focusDetails) {
1427
+ var newFocusKey;
1428
+ if (focusDetails === void 0) { focusDetails = {}; }
1429
+ return __generator(this, function (_a) {
1430
+ switch (_a.label) {
1431
+ case 0:
1432
+ // Cancel any pending auto-restore focus calls if we are setting focus manually
1433
+ this.setFocusDebounced.cancel();
1434
+ if (!this.enabled) {
1435
+ return [2 /*return*/];
1436
+ }
1437
+ this.log('setFocus', 'focusKey', focusKey);
1438
+ /**
1439
+ * When focusKey is not provided or is equal to `ROOT_FOCUS_KEY`, an attempt is made,
1440
+ * to force focus one of the Focusable Containers, that have "forceFocus" flag enabled.
1441
+ * A component closest to the top left viewport corner (0,0) is force-focused.
1442
+ */
1443
+ if (!focusKey || focusKey === ROOT_FOCUS_KEY) {
1444
+ // eslint-disable-next-line no-param-reassign
1445
+ focusKey = this.getForcedFocusKey();
1446
+ // If there is no force-focusable key either then we abort
1447
+ if (!focusKey) {
1448
+ this.log('setFocus', 'Aborted due to missing force-focusable key');
1449
+ return [2 /*return*/];
1450
+ }
1451
+ }
1452
+ return [4 /*yield*/, this.getNextFocusKey(focusKey)];
1453
+ case 1:
1454
+ newFocusKey = _a.sent();
1455
+ if (!newFocusKey) {
1456
+ this.log('setFocus', 'Aborted due to missing next focus key');
1457
+ return [2 /*return*/];
1458
+ }
1459
+ this.log('setFocus', 'newFocusKey', newFocusKey);
1460
+ this.setCurrentFocusedKey(newFocusKey, focusDetails);
1461
+ this.updateParentsHasFocusedChild(newFocusKey, focusDetails);
1462
+ this.updateParentsLastFocusedChild(newFocusKey);
1463
+ return [2 /*return*/];
1464
+ }
1465
+ });
1466
+ });
1159
1467
  };
1160
1468
  SpatialNavigationService.prototype.updateAllLayouts = function () {
1161
- var _this = this;
1162
- if (!this.enabled || this.nativeMode) {
1163
- return;
1164
- }
1165
- lodashEs.forOwn(this.focusableComponents, function (component, focusKey) {
1166
- _this.updateLayout(focusKey);
1469
+ return __awaiter(this, void 0, void 0, function () {
1470
+ var _this = this;
1471
+ return __generator(this, function (_a) {
1472
+ switch (_a.label) {
1473
+ case 0:
1474
+ if (!this.enabled) {
1475
+ return [2 /*return*/];
1476
+ }
1477
+ return [4 /*yield*/, Promise.all(Object.keys(this.focusableComponents).map(function (focusKey) {
1478
+ return _this.updateLayout(focusKey);
1479
+ }))];
1480
+ case 1:
1481
+ _a.sent();
1482
+ return [2 /*return*/];
1483
+ }
1484
+ });
1167
1485
  });
1168
1486
  };
1169
1487
  SpatialNavigationService.prototype.updateLayout = function (focusKey) {
1170
- var component = this.focusableComponents[focusKey];
1171
- if (!component || this.nativeMode || component.layoutUpdated) {
1172
- return;
1173
- }
1174
- var node = component.node;
1175
- var layout = this.useGetBoundingClientRect
1176
- ? getBoundingClientRect(node)
1177
- : measureLayout(node);
1178
- component.layout = __assign(__assign({}, layout), { node: node });
1488
+ return __awaiter(this, void 0, void 0, function () {
1489
+ var component, _a;
1490
+ return __generator(this, function (_b) {
1491
+ switch (_b.label) {
1492
+ case 0:
1493
+ component = this.focusableComponents[focusKey];
1494
+ if (!component) {
1495
+ return [2 /*return*/];
1496
+ }
1497
+ _a = component;
1498
+ return [4 /*yield*/, this.layoutAdapter.measureLayout(component)];
1499
+ case 1:
1500
+ _a.layout = _b.sent();
1501
+ component.layoutUpdatedAt = Date.now();
1502
+ return [2 /*return*/];
1503
+ }
1504
+ });
1505
+ });
1179
1506
  };
1180
1507
  SpatialNavigationService.prototype.updateFocusable = function (focusKey, _a) {
1181
1508
  var node = _a.node, preferredChildFocusKey = _a.preferredChildFocusKey, focusable = _a.focusable, isFocusBoundary = _a.isFocusBoundary, focusBoundaryDirections = _a.focusBoundaryDirections, onEnterPress = _a.onEnterPress, onEnterRelease = _a.onEnterRelease, onArrowPress = _a.onArrowPress, onFocus = _a.onFocus, onBlur = _a.onBlur;
1182
- if (this.nativeMode) {
1183
- return;
1184
- }
1185
1509
  var component = this.focusableComponents[focusKey];
1186
1510
  if (component) {
1187
1511
  component.preferredChildFocusKey = preferredChildFocusKey;
@@ -1193,14 +1517,13 @@ var SpatialNavigationService = /** @class */ (function () {
1193
1517
  component.onArrowPress = onArrowPress;
1194
1518
  component.onFocus = onFocus;
1195
1519
  component.onBlur = onBlur;
1520
+ // Reset layout updated at to force a layout update
1521
+ component.layoutUpdatedAt = 0;
1196
1522
  if (node) {
1197
1523
  component.node = node;
1198
1524
  }
1199
1525
  }
1200
1526
  };
1201
- SpatialNavigationService.prototype.isNativeMode = function () {
1202
- return this.nativeMode;
1203
- };
1204
1527
  SpatialNavigationService.prototype.doesFocusableExist = function (focusKey) {
1205
1528
  return !!this.focusableComponents[focusKey];
1206
1529
  };
@@ -1219,8 +1542,11 @@ var SpatialNavigationService = /** @class */ (function () {
1219
1542
  var SpatialNavigation = new SpatialNavigationService();
1220
1543
  var init = SpatialNavigation.init, setThrottle = SpatialNavigation.setThrottle, destroy = SpatialNavigation.destroy, setKeyMap = SpatialNavigation.setKeyMap, setFocus = SpatialNavigation.setFocus, navigateByDirection = SpatialNavigation.navigateByDirection, pause = SpatialNavigation.pause, resume = SpatialNavigation.resume, updateAllLayouts = SpatialNavigation.updateAllLayouts, getCurrentFocusKey = SpatialNavigation.getCurrentFocusKey, doesFocusableExist = SpatialNavigation.doesFocusableExist, updateRtl = SpatialNavigation.updateRtl;
1221
1544
 
1545
+ exports.BaseWebAdapter = BaseWebAdapter;
1546
+ exports.GetBoundingClientRectAdapter = GetBoundingClientRectAdapter;
1222
1547
  exports.ROOT_FOCUS_KEY = ROOT_FOCUS_KEY;
1223
1548
  exports.SpatialNavigation = SpatialNavigation;
1549
+ exports.SpatialNavigationService = SpatialNavigationService;
1224
1550
  exports.destroy = destroy;
1225
1551
  exports.doesFocusableExist = doesFocusableExist;
1226
1552
  exports.getCurrentFocusKey = getCurrentFocusKey;