├── .gitignore
├── README.md
├── index.js
├── lib
├── index.js
└── spatial_navigation.js
├── package.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-js-spatial-navigation [](https://npmjs.org/package/vue-js-spatial-navigation "View this project on npm")
2 |
3 | Vue directive of [js-spatial-navigation](https://github.com/luke-chang/js-spatial-navigation);
4 |
5 | ## Installation
6 |
7 | ### NPM
8 |
9 | ```shell
10 | npm install vue-js-spatial-navigation
11 | ```
12 |
13 | ## Getting Started
14 |
15 | ```javascript
16 | import Vue from "vue";
17 | import vjsn from "vue-js-spatial-navigation";
18 |
19 | Vue.use(vjsn);
20 | ```
21 |
22 | #### Optional global [Configuration](https://github.com/luke-chang/js-spatial-navigation#configuration)
23 |
24 | #### Additional configuration `scrollOptions`:
25 |
26 | - The page will auto scroll to the focus element by using [`scrollIntoView`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView).
27 |
28 | - You can set this `scrollOptions` for the `scrollIntoViewOptions`.
29 |
30 | - The page will not scroll to the focus element when setting `scrollOptions` to `""` or `null`.
31 |
32 | ```javascript
33 | const config = {
34 | straightOnly: false,
35 | straightOverlapThreshold: 0.5,
36 | rememberSource: false,
37 | disabled: false,
38 | defaultElement: "",
39 | enterTo: "",
40 | leaveFor: null,
41 | restrict: "self-first",
42 | tabIndexIgnoreList: "a, input, select, textarea, button, iframe, [contentEditable=true]",
43 | navigableFilter: null,
44 | scrollOptions: { behavior: "smooth", block: "center" }
45 | };
46 | Vue.use(vjsn, config);
47 | ```
48 |
49 | ## Documentation
50 |
51 | ### `$SpatialNavigation`
52 |
53 | A global Vue instance property for [SpatialNavigation](https://github.com/luke-chang/js-spatial-navigation#api-reference);
54 |
55 | ```javascript
56 | // you can access SpatialNavigation in every instance
57 | this.$SpatialNavigation;
58 | ```
59 |
60 | ### `v-focus`
61 |
62 | A directive that make the element focusable.
63 |
64 | The element with `v-focus` must under the element with `v-focus-section`, see [v-focus-section](#v-focus-section)
65 |
66 | ```html
67 |
70 | ```
71 |
72 | #### dynamic control
73 |
74 | ```html
75 |
78 |
79 |
93 | ```
94 |
95 | ### `v-focus-section`
96 |
97 | A directive that define a focus [Section](https://github.com/luke-chang/js-spatial-navigation#spatialnavigationaddsectionid-config)
98 |
99 | ```html
100 |
108 | ```
109 |
110 | #### Pass a specified section id
111 |
112 | ```html
113 |
114 |
117 | ```
118 |
119 | #### Set configuration
120 |
121 | ```html
122 |
123 |
126 | ```
127 |
128 | #### Set default section
129 |
130 | ```html
131 |
132 |
135 | ```
136 |
137 | ### `v-disable-focus-section`
138 |
139 | This directive will make the conponemt unnavigable.
140 | See [SpatialNavigation.disable()](https://github.com/luke-chang/js-spatial-navigation#spatialnavigationdisablesectionid),
141 | [SpatialNavigation.enable()](https://github.com/luke-chang/js-spatial-navigation#spatialnavigationenablesectionid).
142 |
143 | ```html
144 |
147 |
148 |
162 | ```
163 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib')
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import SpatialNavigation from "./spatial_navigation.js";
2 | import "focus-options-polyfill";
3 | import "scroll-behavior-polyfill";
4 |
5 | const vueSpatialNavigation = {
6 | install(Vue, config) {
7 | const globalConfig = {
8 | selector: `[data-focusable=true]`
9 | };
10 | Object.assign(globalConfig, config);
11 | SpatialNavigation.init();
12 | SpatialNavigation.set(globalConfig);
13 | Vue.prototype.$SpatialNavigation = SpatialNavigation;
14 |
15 | const assignConfig = (sectionId, config) => {
16 | let sectionConfig = Object.assign({}, globalConfig);
17 | if (config) {
18 | Object.assign(sectionConfig, config);
19 | }
20 | sectionConfig.selector = `[data-section-id="${sectionId}"] [data-focusable=true]`;
21 | return sectionConfig;
22 | };
23 |
24 | // focus section directive
25 | Vue.directive("focus-section", {
26 | bind(el, binding) {
27 | let sectionId = null;
28 | if (binding.arg) {
29 | sectionId = binding.arg;
30 | try {
31 | SpatialNavigation.add(sectionId, {});
32 | } catch (error) {}
33 | } else {
34 | sectionId = SpatialNavigation.add({});
35 | }
36 |
37 | // set sectionid to data set for removing when unbinding
38 | el.dataset.sectionId = sectionId;
39 | SpatialNavigation.set(sectionId, assignConfig(sectionId, binding.value));
40 | // set default section
41 | if (binding.modifiers.default) {
42 | SpatialNavigation.setDefaultSection(sectionId);
43 | }
44 | },
45 | update(el, binding) {
46 | let sectionId = el.dataset.sectionId;
47 | if (binding.arg && sectionId != binding.arg) {
48 | sectionId = binding.arg;
49 | el.dataset.sectionId = sectionId;
50 | }
51 | if (binding.value) {
52 | try {
53 | SpatialNavigation.set(sectionId, binding.value);
54 | } catch (error) {
55 | SpatialNavigation.add(sectionId, assignConfig(sectionId, binding.value));
56 | }
57 | }
58 | },
59 | unbind(el) {
60 | SpatialNavigation.remove(el.dataset.sectionId);
61 | }
62 | });
63 |
64 | const disableSection = (sectionId, disable) => {
65 | if (disable == false) {
66 | SpatialNavigation.enable(sectionId);
67 | } else {
68 | SpatialNavigation.disable(sectionId);
69 | }
70 | };
71 | // diasble focus section directive
72 | Vue.directive("disable-focus-section", {
73 | bind(el, binding) {
74 | disableSection(el.dataset.sectionId, binding.value);
75 | },
76 | update(el, binding) {
77 | disableSection(el.dataset.sectionId, binding.value);
78 | }
79 | });
80 |
81 | const disableElement = (el, focusable) => {
82 | focusable = focusable == false ? false : true;
83 | if (!el.dataset.focusable || el.dataset.focusable != focusable + "") {
84 | el.dataset.focusable = focusable;
85 | if (focusable) el.tabIndex = -1;
86 | }
87 | };
88 | // focusable directive
89 | Vue.directive("focus", {
90 | bind(el, binding) {
91 | disableElement(el, binding.value);
92 | },
93 | update(el, binding) {
94 | disableElement(el, binding.value);
95 | },
96 | unbind(el) {
97 | el.removeAttribute("data-focusable");
98 | }
99 | });
100 | }
101 | };
102 |
103 | export default vueSpatialNavigation;
104 |
--------------------------------------------------------------------------------
/lib/spatial_navigation.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * A javascript-based implementation of Spatial Navigation.
4 | *
5 | * Copyright (c) 2017 Luke Chang.
6 | * https://github.com/luke-chang/js-spatial-navigation
7 | *
8 | * Licensed under the MPL 2.0.
9 | */
10 | ;(function($) {
11 | 'use strict';
12 |
13 | /************************/
14 | /* Global Configuration */
15 | /************************/
16 | // Note: an can be one of following types:
17 | // - a valid selector string for "querySelectorAll" or jQuery (if it exists)
18 | // - a NodeList or an array containing DOM elements
19 | // - a single DOM element
20 | // - a jQuery object
21 | // - a string "@" to indicate the specified section
22 | // - a string "@" to indicate the default section
23 | var GlobalConfig = {
24 | selector: "", // can be a valid except "@" syntax.
25 | straightOnly: false,
26 | straightOverlapThreshold: 0.5,
27 | rememberSource: false,
28 | disabled: false,
29 | defaultElement: "", // except "@" syntax.
30 | enterTo: "", // '', 'last-focused', 'default-element'
31 | leaveFor: null, // {left: , right: ,
32 | // up: , down: }
33 | restrict: "self-first", // 'self-first', 'self-only', 'none'
34 | tabIndexIgnoreList: "a, input, select, textarea, button, iframe, [contentEditable=true]",
35 | navigableFilter: null,
36 | scrollOptions: null // scrollIntoViewOptions https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
37 | };
38 |
39 | /*********************/
40 | /* Constant Variable */
41 | /*********************/
42 | var KEYMAPPING = {
43 | '37': 'left',
44 | '38': 'up',
45 | '39': 'right',
46 | '40': 'down'
47 | };
48 |
49 | var REVERSE = {
50 | 'left': 'right',
51 | 'up': 'down',
52 | 'right': 'left',
53 | 'down': 'up'
54 | };
55 |
56 | var EVENT_PREFIX = 'sn:';
57 | var ID_POOL_PREFIX = 'section-';
58 |
59 | /********************/
60 | /* Private Variable */
61 | /********************/
62 | var _idPool = 0;
63 | var _ready = false;
64 | var _pause = false;
65 | var _sections = {};
66 | var _sectionCount = 0;
67 | var _defaultSectionId = '';
68 | var _lastSectionId = '';
69 | var _duringFocusChange = false;
70 |
71 | /************/
72 | /* Polyfill */
73 | /************/
74 | var elementMatchesSelector =
75 | Element.prototype.matches ||
76 | Element.prototype.matchesSelector ||
77 | Element.prototype.mozMatchesSelector ||
78 | Element.prototype.webkitMatchesSelector ||
79 | Element.prototype.msMatchesSelector ||
80 | Element.prototype.oMatchesSelector ||
81 | function (selector) {
82 | var matchedNodes =
83 | (this.parentNode || this.document).querySelectorAll(selector);
84 | return [].slice.call(matchedNodes).indexOf(this) >= 0;
85 | };
86 |
87 | /*****************/
88 | /* Core Function */
89 | /*****************/
90 | function getRect(elem) {
91 | var cr = elem.getBoundingClientRect();
92 | var rect = {
93 | left: cr.left,
94 | top: cr.top,
95 | right: cr.right,
96 | bottom: cr.bottom,
97 | width: cr.width,
98 | height: cr.height
99 | };
100 | rect.element = elem;
101 | rect.center = {
102 | x: rect.left + Math.floor(rect.width / 2),
103 | y: rect.top + Math.floor(rect.height / 2)
104 | };
105 | rect.center.left = rect.center.right = rect.center.x;
106 | rect.center.top = rect.center.bottom = rect.center.y;
107 | return rect;
108 | }
109 |
110 | function partition(rects, targetRect, straightOverlapThreshold) {
111 | var groups = [[], [], [], [], [], [], [], [], []];
112 |
113 | for (var i = 0; i < rects.length; i++) {
114 | var rect = rects[i];
115 | var center = rect.center;
116 | var x, y, groupId;
117 |
118 | if (center.x < targetRect.left) {
119 | x = 0;
120 | } else if (center.x <= targetRect.right) {
121 | x = 1;
122 | } else {
123 | x = 2;
124 | }
125 |
126 | if (center.y < targetRect.top) {
127 | y = 0;
128 | } else if (center.y <= targetRect.bottom) {
129 | y = 1;
130 | } else {
131 | y = 2;
132 | }
133 |
134 | groupId = y * 3 + x;
135 | groups[groupId].push(rect);
136 |
137 | if ([0, 2, 6, 8].indexOf(groupId) !== -1) {
138 | var threshold = straightOverlapThreshold;
139 |
140 | if (rect.left <= targetRect.right - targetRect.width * threshold) {
141 | if (groupId === 2) {
142 | groups[1].push(rect);
143 | } else if (groupId === 8) {
144 | groups[7].push(rect);
145 | }
146 | }
147 |
148 | if (rect.right >= targetRect.left + targetRect.width * threshold) {
149 | if (groupId === 0) {
150 | groups[1].push(rect);
151 | } else if (groupId === 6) {
152 | groups[7].push(rect);
153 | }
154 | }
155 |
156 | if (rect.top <= targetRect.bottom - targetRect.height * threshold) {
157 | if (groupId === 6) {
158 | groups[3].push(rect);
159 | } else if (groupId === 8) {
160 | groups[5].push(rect);
161 | }
162 | }
163 |
164 | if (rect.bottom >= targetRect.top + targetRect.height * threshold) {
165 | if (groupId === 0) {
166 | groups[3].push(rect);
167 | } else if (groupId === 2) {
168 | groups[5].push(rect);
169 | }
170 | }
171 | }
172 | }
173 |
174 | return groups;
175 | }
176 |
177 | function generateDistanceFunction(targetRect) {
178 | return {
179 | nearPlumbLineIsBetter: function(rect) {
180 | var d;
181 | if (rect.center.x < targetRect.center.x) {
182 | d = targetRect.center.x - rect.right;
183 | } else {
184 | d = rect.left - targetRect.center.x;
185 | }
186 | return d < 0 ? 0 : d;
187 | },
188 | nearHorizonIsBetter: function(rect) {
189 | var d;
190 | if (rect.center.y < targetRect.center.y) {
191 | d = targetRect.center.y - rect.bottom;
192 | } else {
193 | d = rect.top - targetRect.center.y;
194 | }
195 | return d < 0 ? 0 : d;
196 | },
197 | nearTargetLeftIsBetter: function(rect) {
198 | var d;
199 | if (rect.center.x < targetRect.center.x) {
200 | d = targetRect.left - rect.right;
201 | } else {
202 | d = rect.left - targetRect.left;
203 | }
204 | return d < 0 ? 0 : d;
205 | },
206 | nearTargetTopIsBetter: function(rect) {
207 | var d;
208 | if (rect.center.y < targetRect.center.y) {
209 | d = targetRect.top - rect.bottom;
210 | } else {
211 | d = rect.top - targetRect.top;
212 | }
213 | return d < 0 ? 0 : d;
214 | },
215 | topIsBetter: function(rect) {
216 | return rect.top;
217 | },
218 | bottomIsBetter: function(rect) {
219 | return -1 * rect.bottom;
220 | },
221 | leftIsBetter: function(rect) {
222 | return rect.left;
223 | },
224 | rightIsBetter: function(rect) {
225 | return -1 * rect.right;
226 | }
227 | };
228 | }
229 |
230 | function prioritize(priorities) {
231 | var destPriority = null;
232 | for (var i = 0; i < priorities.length; i++) {
233 | if (priorities[i].group.length) {
234 | destPriority = priorities[i];
235 | break;
236 | }
237 | }
238 |
239 | if (!destPriority) {
240 | return null;
241 | }
242 |
243 | var destDistance = destPriority.distance;
244 |
245 | destPriority.group.sort(function(a, b) {
246 | for (var i = 0; i < destDistance.length; i++) {
247 | var distance = destDistance[i];
248 | var delta = distance(a) - distance(b);
249 | if (delta) {
250 | return delta;
251 | }
252 | }
253 | return 0;
254 | });
255 |
256 | return destPriority.group;
257 | }
258 |
259 | function navigate(target, direction, candidates, config) {
260 | if (!target || !direction || !candidates || !candidates.length) {
261 | return null;
262 | }
263 |
264 | var rects = [];
265 | for (var i = 0; i < candidates.length; i++) {
266 | var rect = getRect(candidates[i]);
267 | if (rect) {
268 | rects.push(rect);
269 | }
270 | }
271 | if (!rects.length) {
272 | return null;
273 | }
274 |
275 | var targetRect = getRect(target);
276 | if (!targetRect) {
277 | return null;
278 | }
279 |
280 | var distanceFunction = generateDistanceFunction(targetRect);
281 |
282 | var groups = partition(
283 | rects,
284 | targetRect,
285 | config.straightOverlapThreshold
286 | );
287 |
288 | var internalGroups = partition(
289 | groups[4],
290 | targetRect.center,
291 | config.straightOverlapThreshold
292 | );
293 |
294 | var priorities;
295 |
296 | switch (direction) {
297 | case 'left':
298 | priorities = [
299 | {
300 | group: internalGroups[0].concat(internalGroups[3])
301 | .concat(internalGroups[6]),
302 | distance: [
303 | distanceFunction.nearPlumbLineIsBetter,
304 | distanceFunction.topIsBetter
305 | ]
306 | },
307 | {
308 | group: groups[3],
309 | distance: [
310 | distanceFunction.nearPlumbLineIsBetter,
311 | distanceFunction.topIsBetter
312 | ]
313 | },
314 | {
315 | group: groups[0].concat(groups[6]),
316 | distance: [
317 | distanceFunction.nearHorizonIsBetter,
318 | distanceFunction.rightIsBetter,
319 | distanceFunction.nearTargetTopIsBetter
320 | ]
321 | }
322 | ];
323 | break;
324 | case 'right':
325 | priorities = [
326 | {
327 | group: internalGroups[2].concat(internalGroups[5])
328 | .concat(internalGroups[8]),
329 | distance: [
330 | distanceFunction.nearPlumbLineIsBetter,
331 | distanceFunction.topIsBetter
332 | ]
333 | },
334 | {
335 | group: groups[5],
336 | distance: [
337 | distanceFunction.nearPlumbLineIsBetter,
338 | distanceFunction.topIsBetter
339 | ]
340 | },
341 | {
342 | group: groups[2].concat(groups[8]),
343 | distance: [
344 | distanceFunction.nearHorizonIsBetter,
345 | distanceFunction.leftIsBetter,
346 | distanceFunction.nearTargetTopIsBetter
347 | ]
348 | }
349 | ];
350 | break;
351 | case 'up':
352 | priorities = [
353 | {
354 | group: internalGroups[0].concat(internalGroups[1])
355 | .concat(internalGroups[2]),
356 | distance: [
357 | distanceFunction.nearHorizonIsBetter,
358 | distanceFunction.leftIsBetter
359 | ]
360 | },
361 | {
362 | group: groups[1],
363 | distance: [
364 | distanceFunction.nearHorizonIsBetter,
365 | distanceFunction.leftIsBetter
366 | ]
367 | },
368 | {
369 | group: groups[0].concat(groups[2]),
370 | distance: [
371 | distanceFunction.nearPlumbLineIsBetter,
372 | distanceFunction.bottomIsBetter,
373 | distanceFunction.nearTargetLeftIsBetter
374 | ]
375 | }
376 | ];
377 | break;
378 | case 'down':
379 | priorities = [
380 | {
381 | group: internalGroups[6].concat(internalGroups[7])
382 | .concat(internalGroups[8]),
383 | distance: [
384 | distanceFunction.nearHorizonIsBetter,
385 | distanceFunction.leftIsBetter
386 | ]
387 | },
388 | {
389 | group: groups[7],
390 | distance: [
391 | distanceFunction.nearHorizonIsBetter,
392 | distanceFunction.leftIsBetter
393 | ]
394 | },
395 | {
396 | group: groups[6].concat(groups[8]),
397 | distance: [
398 | distanceFunction.nearPlumbLineIsBetter,
399 | distanceFunction.topIsBetter,
400 | distanceFunction.nearTargetLeftIsBetter
401 | ]
402 | }
403 | ];
404 | break;
405 | default:
406 | return null;
407 | }
408 |
409 | if (config.straightOnly) {
410 | priorities.pop();
411 | }
412 |
413 | var destGroup = prioritize(priorities);
414 | if (!destGroup) {
415 | return null;
416 | }
417 |
418 | var dest = null;
419 | if (config.rememberSource &&
420 | config.previous &&
421 | config.previous.destination === target &&
422 | config.previous.reverse === direction) {
423 | for (var j = 0; j < destGroup.length; j++) {
424 | if (destGroup[j].element === config.previous.target) {
425 | dest = destGroup[j].element;
426 | break;
427 | }
428 | }
429 | }
430 |
431 | if (!dest) {
432 | dest = destGroup[0].element;
433 | }
434 |
435 | return dest;
436 | }
437 |
438 | /********************/
439 | /* Private Function */
440 | /********************/
441 | function generateId() {
442 | var id;
443 | while(true) {
444 | id = ID_POOL_PREFIX + String(++_idPool);
445 | if (!_sections[id]) {
446 | break;
447 | }
448 | }
449 | return id;
450 | }
451 |
452 | function parseSelector(selector) {
453 | var result;
454 | if ($) {
455 | result = $(selector).get();
456 | } else if (typeof selector === 'string') {
457 | result = [].slice.call(document.querySelectorAll(selector));
458 | } else if (typeof selector === 'object' && selector.length) {
459 | result = [].slice.call(selector);
460 | } else if (typeof selector === 'object' && selector.nodeType === 1) {
461 | result = [selector];
462 | } else {
463 | result = [];
464 | }
465 | return result;
466 | }
467 |
468 | function matchSelector(elem, selector) {
469 | if ($) {
470 | return $(elem).is(selector);
471 | } else if (typeof selector === 'string') {
472 | return elementMatchesSelector.call(elem, selector);
473 | } else if (typeof selector === 'object' && selector.length) {
474 | return selector.indexOf(elem) >= 0;
475 | } else if (typeof selector === 'object' && selector.nodeType === 1) {
476 | return elem === selector;
477 | }
478 | return false;
479 | }
480 |
481 | function getCurrentFocusedElement() {
482 | var activeElement = document.activeElement;
483 | if (activeElement && activeElement !== document.body) {
484 | return activeElement;
485 | }
486 | }
487 |
488 | function extend(out) {
489 | out = out || {};
490 | for (var i = 1; i < arguments.length; i++) {
491 | if (!arguments[i]) {
492 | continue;
493 | }
494 | for (var key in arguments[i]) {
495 | if (arguments[i].hasOwnProperty(key) &&
496 | arguments[i][key] !== undefined) {
497 | out[key] = arguments[i][key];
498 | }
499 | }
500 | }
501 | return out;
502 | }
503 |
504 | function exclude(elemList, excludedElem) {
505 | if (!Array.isArray(excludedElem)) {
506 | excludedElem = [excludedElem];
507 | }
508 | for (var i = 0, index; i < excludedElem.length; i++) {
509 | index = elemList.indexOf(excludedElem[i]);
510 | if (index >= 0) {
511 | elemList.splice(index, 1);
512 | }
513 | }
514 | return elemList;
515 | }
516 |
517 | function isNavigable(elem, sectionId, verifySectionSelector) {
518 | if (! elem || !sectionId ||
519 | !_sections[sectionId] || _sections[sectionId].disabled) {
520 | return false;
521 | }
522 | if ((elem.offsetWidth <= 0 && elem.offsetHeight <= 0) ||
523 | elem.hasAttribute('disabled')) {
524 | return false;
525 | }
526 | if (verifySectionSelector &&
527 | !matchSelector(elem, _sections[sectionId].selector)) {
528 | return false;
529 | }
530 | if (typeof _sections[sectionId].navigableFilter === 'function') {
531 | if (_sections[sectionId].navigableFilter(elem, sectionId) === false) {
532 | return false;
533 | }
534 | } else if (typeof GlobalConfig.navigableFilter === 'function') {
535 | if (GlobalConfig.navigableFilter(elem, sectionId) === false) {
536 | return false;
537 | }
538 | }
539 | return true;
540 | }
541 |
542 | function getSectionId(elem) {
543 | for (var id in _sections) {
544 | if (!_sections[id].disabled &&
545 | matchSelector(elem, _sections[id].selector)) {
546 | return id;
547 | }
548 | }
549 | }
550 |
551 | function getSectionNavigableElements(sectionId) {
552 | return parseSelector(_sections[sectionId].selector).filter(function(elem) {
553 | return isNavigable(elem, sectionId);
554 | });
555 | }
556 |
557 | function getSectionDefaultElement(sectionId) {
558 | var defaultElement = _sections[sectionId].defaultElement;
559 | if (!defaultElement) {
560 | return null;
561 | }
562 | if (typeof defaultElement === 'string') {
563 | // defaultElement = parseSelector(defaultElement)[0]; // bug !
564 | var elements = parseSelector(defaultElement);
565 | // check each element to see if it's navigable and stop when one has been found
566 | for (var element of elements) {
567 | if (isNavigable(element, sectionId, true)) {
568 | return element;
569 | }
570 | }
571 | return null;
572 | } else if ($ && defaultElement instanceof $) {
573 | defaultElement = defaultElement.get(0);
574 | if (isNavigable(defaultElement, sectionId, true)) {
575 | return defaultElement;
576 | }
577 | }
578 | return null;
579 | }
580 |
581 | function getSectionLastFocusedElement(sectionId) {
582 | var lastFocusedElement = _sections[sectionId].lastFocusedElement;
583 | if (!isNavigable(lastFocusedElement, sectionId, true)) {
584 | return null;
585 | }
586 | return lastFocusedElement;
587 | }
588 |
589 | function fireEvent(elem, type, details, cancelable) {
590 | if (arguments.length < 4) {
591 | cancelable = true;
592 | }
593 | var evt = document.createEvent('CustomEvent');
594 | evt.initCustomEvent(EVENT_PREFIX + type, true, cancelable, details);
595 | return elem.dispatchEvent(evt);
596 | }
597 |
598 | function focusElement(elem, sectionId, direction) {
599 | if (!elem) {
600 | return false;
601 | }
602 |
603 | var currentFocusedElement = getCurrentFocusedElement();
604 |
605 | var focusNscroll = function () {
606 | if (_sections[sectionId].scrollOptions !== undefined && _sections[sectionId].scrollOptions !== "") {
607 | elem.focus({ preventScroll: true });
608 | elem.scrollIntoView(_sections[sectionId].scrollOptions);
609 | } else if (GlobalConfig.scrollOptions !== undefined && GlobalConfig.scrollOptions !== "") {
610 | elem.focus({ preventScroll: true });
611 | elem.scrollIntoView(GlobalConfig.scrollOptions);
612 | } else {
613 | elem.focus();
614 | }
615 | };
616 |
617 | var silentFocus = function() {
618 | if (currentFocusedElement) {
619 | currentFocusedElement.blur();
620 | }
621 |
622 | focusNscroll(elem);
623 | focusChanged(elem, sectionId);
624 | };
625 |
626 | if (_duringFocusChange) {
627 | silentFocus();
628 | return true;
629 | }
630 |
631 | _duringFocusChange = true;
632 |
633 | if (_pause) {
634 | silentFocus();
635 | _duringFocusChange = false;
636 | return true;
637 | }
638 |
639 | if (currentFocusedElement) {
640 | var unfocusProperties = {
641 | nextElement: elem,
642 | nextSectionId: sectionId,
643 | direction: direction,
644 | native: false
645 | };
646 | if (!fireEvent(currentFocusedElement, 'willunfocus', unfocusProperties)) {
647 | _duringFocusChange = false;
648 | return false;
649 | }
650 | currentFocusedElement.blur();
651 | fireEvent(currentFocusedElement, 'unfocused', unfocusProperties, false);
652 | }
653 |
654 | var focusProperties = {
655 | previousElement: currentFocusedElement,
656 | sectionId: sectionId,
657 | direction: direction,
658 | native: false
659 | };
660 | if (!fireEvent(elem, 'willfocus', focusProperties)) {
661 | _duringFocusChange = false;
662 | return false;
663 | }
664 | focusNscroll(elem);
665 | fireEvent(elem, 'focused', focusProperties, false);
666 |
667 | _duringFocusChange = false;
668 |
669 | focusChanged(elem, sectionId);
670 | return true;
671 | }
672 |
673 | function focusChanged(elem, sectionId) {
674 | if (!sectionId) {
675 | sectionId = getSectionId(elem);
676 | }
677 | if (sectionId) {
678 | _sections[sectionId].lastFocusedElement = elem;
679 | _lastSectionId = sectionId;
680 | }
681 | }
682 |
683 | function focusExtendedSelector(selector, direction) {
684 | if (selector.charAt(0) == '@') {
685 | if (selector.length == 1) {
686 | return focusSection();
687 | } else {
688 | var sectionId = selector.substr(1);
689 | return focusSection(sectionId);
690 | }
691 | } else {
692 | var next = parseSelector(selector)[0];
693 | if (next) {
694 | var nextSectionId = getSectionId(next);
695 | if (isNavigable(next, nextSectionId)) {
696 | return focusElement(next, nextSectionId, direction);
697 | }
698 | }
699 | }
700 | return false;
701 | }
702 |
703 | function focusSection(sectionId) {
704 | var range = [];
705 | var addRange = function(id) {
706 | if (id && range.indexOf(id) < 0 &&
707 | _sections[id] && !_sections[id].disabled) {
708 | range.push(id);
709 | }
710 | };
711 |
712 | if (sectionId) {
713 | addRange(sectionId);
714 | } else {
715 | addRange(_defaultSectionId);
716 | addRange(_lastSectionId);
717 | Object.keys(_sections).map(addRange);
718 | }
719 |
720 | for (var i = 0; i < range.length; i++) {
721 | var id = range[i];
722 | var next;
723 |
724 | if (_sections[id].enterTo == 'last-focused') {
725 | next = getSectionLastFocusedElement(id) ||
726 | getSectionDefaultElement(id) ||
727 | getSectionNavigableElements(id)[0];
728 | } else {
729 | next = getSectionDefaultElement(id) ||
730 | getSectionLastFocusedElement(id) ||
731 | getSectionNavigableElements(id)[0];
732 | }
733 |
734 | if (next) {
735 | return focusElement(next, id);
736 | }
737 | }
738 |
739 | return false;
740 | }
741 |
742 | function fireNavigatefailed(elem, direction) {
743 | fireEvent(elem, 'navigatefailed', {
744 | direction: direction
745 | }, false);
746 | }
747 |
748 | function gotoLeaveFor(sectionId, direction) {
749 | if (_sections[sectionId].leaveFor &&
750 | _sections[sectionId].leaveFor[direction] !== undefined) {
751 | var next = _sections[sectionId].leaveFor[direction];
752 |
753 | if (typeof next === 'string') {
754 | if (next === '') {
755 | return null;
756 | }
757 | return focusExtendedSelector(next, direction);
758 | }
759 |
760 | if ($ && next instanceof $) {
761 | next = next.get(0);
762 | }
763 |
764 | var nextSectionId = getSectionId(next);
765 | if (isNavigable(next, nextSectionId)) {
766 | return focusElement(next, nextSectionId, direction);
767 | }
768 | }
769 | return false;
770 | }
771 |
772 | function focusNext(direction, currentFocusedElement, currentSectionId) {
773 | var extSelector =
774 | currentFocusedElement.getAttribute('data-sn-' + direction);
775 | if (typeof extSelector === 'string') {
776 | if (extSelector === '' ||
777 | !focusExtendedSelector(extSelector, direction)) {
778 | fireNavigatefailed(currentFocusedElement, direction);
779 | return false;
780 | }
781 | return true;
782 | }
783 |
784 | var sectionNavigableElements = {};
785 | var allNavigableElements = [];
786 | for (var id in _sections) {
787 | sectionNavigableElements[id] = getSectionNavigableElements(id);
788 | allNavigableElements =
789 | allNavigableElements.concat(sectionNavigableElements[id]);
790 | }
791 |
792 | var config = extend({}, GlobalConfig, _sections[currentSectionId]);
793 | var next;
794 |
795 | if (config.restrict == 'self-only' || config.restrict == 'self-first') {
796 | var currentSectionNavigableElements =
797 | sectionNavigableElements[currentSectionId];
798 |
799 | next = navigate(
800 | currentFocusedElement,
801 | direction,
802 | exclude(currentSectionNavigableElements, currentFocusedElement),
803 | config
804 | );
805 |
806 | if (!next && config.restrict == 'self-first') {
807 | next = navigate(
808 | currentFocusedElement,
809 | direction,
810 | exclude(allNavigableElements, currentSectionNavigableElements),
811 | config
812 | );
813 | }
814 | } else {
815 | next = navigate(
816 | currentFocusedElement,
817 | direction,
818 | exclude(allNavigableElements, currentFocusedElement),
819 | config
820 | );
821 | }
822 |
823 | if (next) {
824 | _sections[currentSectionId].previous = {
825 | target: currentFocusedElement,
826 | destination: next,
827 | reverse: REVERSE[direction]
828 | };
829 |
830 | var nextSectionId = getSectionId(next);
831 |
832 | if (currentSectionId != nextSectionId) {
833 | var result = gotoLeaveFor(currentSectionId, direction);
834 | if (result) {
835 | return true;
836 | } else if (result === null) {
837 | fireNavigatefailed(currentFocusedElement, direction);
838 | return false;
839 | }
840 |
841 | var enterToElement;
842 | switch (_sections[nextSectionId].enterTo) {
843 | case 'last-focused':
844 | enterToElement = getSectionLastFocusedElement(nextSectionId) ||
845 | getSectionDefaultElement(nextSectionId);
846 | break;
847 | case 'default-element':
848 | enterToElement = getSectionDefaultElement(nextSectionId);
849 | break;
850 | }
851 | if (enterToElement) {
852 | next = enterToElement;
853 | }
854 | }
855 |
856 | return focusElement(next, nextSectionId, direction);
857 | } else if (gotoLeaveFor(currentSectionId, direction)) {
858 | return true;
859 | }
860 |
861 | fireNavigatefailed(currentFocusedElement, direction);
862 | return false;
863 | }
864 |
865 | function onKeyDown(evt) {
866 | if (!_sectionCount || _pause ||
867 | evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {
868 | return;
869 | }
870 |
871 | var currentFocusedElement;
872 | var preventDefault = function() {
873 | evt.preventDefault();
874 | evt.stopPropagation();
875 | return false;
876 | };
877 |
878 | var direction = KEYMAPPING[evt.keyCode];
879 | if (!direction) {
880 | if (evt.keyCode == 13) {
881 | currentFocusedElement = getCurrentFocusedElement();
882 | if (currentFocusedElement && getSectionId(currentFocusedElement)) {
883 | if (!fireEvent(currentFocusedElement, 'enter-down')) {
884 | return preventDefault();
885 | }
886 | }
887 | }
888 | return;
889 | }
890 |
891 | currentFocusedElement = getCurrentFocusedElement();
892 |
893 | if (!currentFocusedElement) {
894 | if (_lastSectionId) {
895 | currentFocusedElement = getSectionLastFocusedElement(_lastSectionId);
896 | }
897 | if (!currentFocusedElement) {
898 | focusSection();
899 | return preventDefault();
900 | }
901 | }
902 |
903 | var currentSectionId = getSectionId(currentFocusedElement);
904 | if (!currentSectionId) {
905 | return;
906 | }
907 |
908 | var willmoveProperties = {
909 | direction: direction,
910 | sectionId: currentSectionId,
911 | cause: 'keydown'
912 | };
913 |
914 | if (fireEvent(currentFocusedElement, 'willmove', willmoveProperties)) {
915 | focusNext(direction, currentFocusedElement, currentSectionId);
916 | }
917 |
918 | return preventDefault();
919 | }
920 |
921 | function onKeyUp(evt) {
922 | if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {
923 | return
924 | }
925 | if (!_pause && _sectionCount && evt.keyCode == 13) {
926 | var currentFocusedElement = getCurrentFocusedElement();
927 | if (currentFocusedElement && getSectionId(currentFocusedElement)) {
928 | if (!fireEvent(currentFocusedElement, 'enter-up')) {
929 | evt.preventDefault();
930 | evt.stopPropagation();
931 | }
932 | }
933 | }
934 | }
935 |
936 | function onFocus(evt) {
937 | var target = evt.target;
938 | if (target !== window && target !== document &&
939 | _sectionCount && !_duringFocusChange) {
940 | var sectionId = getSectionId(target);
941 | if (sectionId) {
942 | if (_pause) {
943 | focusChanged(target, sectionId);
944 | return;
945 | }
946 |
947 | var focusProperties = {
948 | sectionId: sectionId,
949 | native: true
950 | };
951 |
952 | if (!fireEvent(target, 'willfocus', focusProperties)) {
953 | _duringFocusChange = true;
954 | target.blur();
955 | _duringFocusChange = false;
956 | } else {
957 | fireEvent(target, 'focused', focusProperties, false);
958 | focusChanged(target, sectionId);
959 | }
960 | }
961 | }
962 | }
963 |
964 | function onBlur(evt) {
965 | var target = evt.target;
966 | if (target !== window && target !== document && !_pause &&
967 | _sectionCount && !_duringFocusChange && getSectionId(target)) {
968 | var unfocusProperties = {
969 | native: true
970 | };
971 | if (!fireEvent(target, 'willunfocus', unfocusProperties)) {
972 | _duringFocusChange = true;
973 | setTimeout(function() {
974 | target.focus();
975 | _duringFocusChange = false;
976 | });
977 | } else {
978 | fireEvent(target, 'unfocused', unfocusProperties, false);
979 | }
980 | }
981 | }
982 |
983 | function onBodyClick(){
984 | if (document.activeElement === document.body && _lastSectionId
985 | && _sections[_lastSectionId].lastFocusedElement) {
986 | focusElement(_sections[_lastSectionId].lastFocusedElement, _lastSectionId);
987 | }
988 | }
989 |
990 | /*******************/
991 | /* Public Function */
992 | /*******************/
993 | var SpatialNavigation = {
994 | init: function() {
995 | if (!_ready) {
996 | window.addEventListener('keydown', onKeyDown);
997 | window.addEventListener('keyup', onKeyUp);
998 | window.addEventListener('focus', onFocus, true);
999 | window.addEventListener('blur', onBlur, true);
1000 | document.body.addEventListener("click", onBodyClick);
1001 | _ready = true;
1002 | }
1003 | },
1004 |
1005 | uninit: function() {
1006 | window.removeEventListener('blur', onBlur, true);
1007 | window.removeEventListener('focus', onFocus, true);
1008 | window.removeEventListener('keyup', onKeyUp);
1009 | window.removeEventListener('keydown', onKeyDown);
1010 | document.body.removeEventListener("click", onBodyClick);
1011 | SpatialNavigation.clear();
1012 | _idPool = 0;
1013 | _ready = false;
1014 | },
1015 |
1016 | clear: function() {
1017 | _sections = {};
1018 | _sectionCount = 0;
1019 | _defaultSectionId = '';
1020 | _lastSectionId = '';
1021 | _duringFocusChange = false;
1022 | },
1023 |
1024 | reset: function(sectionId) {
1025 | if (sectionId) {
1026 | _sections[sectionId].lastFocusedElement = null;
1027 | _sections[sectionId].previous = null;
1028 | } else {
1029 | for (const id in _sections) {
1030 | const section = _sections[id];
1031 | section.lastFocusedElement = null;
1032 | section.previous = null;
1033 | }
1034 | }
1035 | },
1036 |
1037 | // set();
1038 | // set(, );
1039 | set: function() {
1040 | var sectionId, config;
1041 |
1042 | if (typeof arguments[0] === 'object') {
1043 | config = arguments[0];
1044 | } else if (typeof arguments[0] === 'string' &&
1045 | typeof arguments[1] === 'object') {
1046 | sectionId = arguments[0];
1047 | config = arguments[1];
1048 | if (!_sections[sectionId]) {
1049 | throw new Error('Section "' + sectionId + '" doesn\'t exist!');
1050 | }
1051 | } else {
1052 | return;
1053 | }
1054 |
1055 | for (var key in config) {
1056 | if (GlobalConfig[key] !== undefined) {
1057 | if (sectionId) {
1058 | _sections[sectionId][key] = config[key];
1059 | } else if (config[key] !== undefined) {
1060 | GlobalConfig[key] = config[key];
1061 | }
1062 | }
1063 | }
1064 |
1065 | if (sectionId) {
1066 | // remove "undefined" items
1067 | _sections[sectionId] = extend({}, _sections[sectionId]);
1068 | }
1069 | },
1070 |
1071 | // add();
1072 | // add(, );
1073 | add: function() {
1074 | var sectionId;
1075 | var config = {};
1076 |
1077 | if (typeof arguments[0] === 'object') {
1078 | config = arguments[0];
1079 | } else if (typeof arguments[0] === 'string' &&
1080 | typeof arguments[1] === 'object') {
1081 | sectionId = arguments[0];
1082 | config = arguments[1];
1083 | }
1084 |
1085 | if (!sectionId) {
1086 | sectionId = (typeof config.id === 'string') ? config.id : generateId();
1087 | }
1088 |
1089 | if (_sections[sectionId]) {
1090 | throw new Error('Section "' + sectionId + '" has already existed!');
1091 | }
1092 |
1093 | _sections[sectionId] = {};
1094 | _sectionCount++;
1095 |
1096 | SpatialNavigation.set(sectionId, config);
1097 |
1098 | return sectionId;
1099 | },
1100 |
1101 | remove: function(sectionId) {
1102 | if (!sectionId || typeof sectionId !== 'string') {
1103 | throw new Error('Please assign the "sectionId"!');
1104 | }
1105 | if (_sections[sectionId]) {
1106 | _sections[sectionId] = undefined;
1107 | _sections = extend({}, _sections);
1108 | _sectionCount--;
1109 | if (_lastSectionId === sectionId) {
1110 | _lastSectionId = '';
1111 | }
1112 | return true;
1113 | }
1114 | return false;
1115 | },
1116 |
1117 | disable: function(sectionId) {
1118 | if (_sections[sectionId]) {
1119 | _sections[sectionId].disabled = true;
1120 | return true;
1121 | }
1122 | return false;
1123 | },
1124 |
1125 | enable: function(sectionId) {
1126 | if (_sections[sectionId]) {
1127 | _sections[sectionId].disabled = false;
1128 | return true;
1129 | }
1130 | return false;
1131 | },
1132 |
1133 | pause: function() {
1134 | _pause = true;
1135 | },
1136 |
1137 | resume: function() {
1138 | _pause = false;
1139 | },
1140 |
1141 | // focus([silent])
1142 | // focus(, [silent])
1143 | // focus(, [silent])
1144 | // Note: "silent" is optional and default to false
1145 | focus: function(elem, silent) {
1146 | var result = false;
1147 |
1148 | if (silent === undefined && typeof elem === 'boolean') {
1149 | silent = elem;
1150 | elem = undefined;
1151 | }
1152 |
1153 | var autoPause = !_pause && silent;
1154 |
1155 | if (autoPause) {
1156 | SpatialNavigation.pause();
1157 | }
1158 |
1159 | if (!elem) {
1160 | result = focusSection();
1161 | } else {
1162 | if (typeof elem === 'string') {
1163 | if (_sections[elem]) {
1164 | result = focusSection(elem);
1165 | } else {
1166 | result = focusExtendedSelector(elem);
1167 | }
1168 | } else {
1169 | if ($ && elem instanceof $) {
1170 | elem = elem.get(0);
1171 | }
1172 |
1173 | var nextSectionId = getSectionId(elem);
1174 | if (isNavigable(elem, nextSectionId)) {
1175 | result = focusElement(elem, nextSectionId);
1176 | }
1177 | }
1178 | }
1179 |
1180 | if (autoPause) {
1181 | SpatialNavigation.resume();
1182 | }
1183 |
1184 | return result;
1185 | },
1186 |
1187 | // move()
1188 | // move(, )
1189 | move: function(direction, selector) {
1190 | direction = direction.toLowerCase();
1191 | if (!REVERSE[direction]) {
1192 | return false;
1193 | }
1194 |
1195 | var elem = selector ?
1196 | parseSelector(selector)[0] : getCurrentFocusedElement();
1197 | if (!elem) {
1198 | return false;
1199 | }
1200 |
1201 | var sectionId = getSectionId(elem);
1202 | if (!sectionId) {
1203 | return false;
1204 | }
1205 |
1206 | var willmoveProperties = {
1207 | direction: direction,
1208 | sectionId: sectionId,
1209 | cause: 'api'
1210 | };
1211 |
1212 | if (!fireEvent(elem, 'willmove', willmoveProperties)) {
1213 | return false;
1214 | }
1215 |
1216 | return focusNext(direction, elem, sectionId);
1217 | },
1218 |
1219 | // makeFocusable()
1220 | // makeFocusable()
1221 | makeFocusable: function(sectionId) {
1222 | var doMakeFocusable = function(section) {
1223 | var tabIndexIgnoreList = section.tabIndexIgnoreList !== undefined ?
1224 | section.tabIndexIgnoreList : GlobalConfig.tabIndexIgnoreList;
1225 | parseSelector(section.selector).forEach(function(elem) {
1226 | if (!matchSelector(elem, tabIndexIgnoreList)) {
1227 | if (!elem.getAttribute('tabindex')) {
1228 | elem.setAttribute('tabindex', '-1');
1229 | }
1230 | }
1231 | });
1232 | };
1233 |
1234 | if (sectionId) {
1235 | if (_sections[sectionId]) {
1236 | doMakeFocusable(_sections[sectionId]);
1237 | } else {
1238 | throw new Error('Section "' + sectionId + '" doesn\'t exist!');
1239 | }
1240 | } else {
1241 | for (var id in _sections) {
1242 | doMakeFocusable(_sections[id]);
1243 | }
1244 | }
1245 | },
1246 |
1247 | setDefaultSection: function(sectionId) {
1248 | if (!sectionId) {
1249 | _defaultSectionId = '';
1250 | } else if (!_sections[sectionId]) {
1251 | throw new Error('Section "' + sectionId + '" doesn\'t exist!');
1252 | } else {
1253 | _defaultSectionId = sectionId;
1254 | }
1255 | }
1256 | };
1257 |
1258 | window.SpatialNavigation = SpatialNavigation;
1259 |
1260 | /**********************/
1261 | /* CommonJS Interface */
1262 | /**********************/
1263 | if (typeof module === 'object') {
1264 | module.exports = SpatialNavigation;
1265 | }
1266 |
1267 | /********************/
1268 | /* jQuery Interface */
1269 | /********************/
1270 | if ($) {
1271 | $.SpatialNavigation = function() {
1272 | SpatialNavigation.init();
1273 |
1274 | if (arguments.length > 0) {
1275 | if ($.isPlainObject(arguments[0])) {
1276 | return SpatialNavigation.add(arguments[0]);
1277 | } else if ($.type(arguments[0]) === 'string' &&
1278 | $.isFunction(SpatialNavigation[arguments[0]])) {
1279 | return SpatialNavigation[arguments[0]]
1280 | .apply(SpatialNavigation, [].slice.call(arguments, 1));
1281 | }
1282 | }
1283 |
1284 | return $.extend({}, SpatialNavigation);
1285 | };
1286 |
1287 | $.fn.SpatialNavigation = function() {
1288 | var config;
1289 |
1290 | if ($.isPlainObject(arguments[0])) {
1291 | config = arguments[0];
1292 | } else {
1293 | config = {
1294 | id: arguments[0]
1295 | };
1296 | }
1297 |
1298 | config.selector = this;
1299 |
1300 | SpatialNavigation.init();
1301 | if (config.id) {
1302 | SpatialNavigation.remove(config.id);
1303 | }
1304 | SpatialNavigation.add(config);
1305 | SpatialNavigation.makeFocusable(config.id);
1306 |
1307 | return this;
1308 | };
1309 | }
1310 | })(window.jQuery);
1311 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-js-spatial-navigation",
3 | "version": "2.0.14",
4 | "description": "Vue directive of js-spatial-navigation",
5 | "main": "index.js",
6 | "directories": {
7 | "lib": "lib"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/spacerefugee/vue-js-spatial-navigation.git"
15 | },
16 | "keywords": [
17 | "vue",
18 | "spatial-navigation",
19 | "smart-tv",
20 | "key-navigation"
21 | ],
22 | "author": "Brandon.gh.yang",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/spacerefugee/vue-js-spatial-navigation/issues"
26 | },
27 | "homepage": "https://github.com/spacerefugee/vue-js-spatial-navigation#readme",
28 | "dependencies": {
29 | "focus-options-polyfill": "^1.6.0",
30 | "scroll-behavior-polyfill": "^2.0.13"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | focus-options-polyfill@^1.6.0:
6 | version "1.6.0"
7 | resolved "https://registry.yarnpkg.com/focus-options-polyfill/-/focus-options-polyfill-1.6.0.tgz#12ef3081dae9801d32dfcffd7b69259ca8f57198"
8 | integrity sha512-uyrAmLZrPnUItQY5wTdg31TO9GGZRGsh/jmohUg9oLmLi/sw5y7LlTV/mwyd6rvbxIOGwmRiv6LcTS8w7Bk9NQ==
9 |
10 | scroll-behavior-polyfill@^2.0.13:
11 | version "2.0.13"
12 | resolved "https://registry.yarnpkg.com/scroll-behavior-polyfill/-/scroll-behavior-polyfill-2.0.13.tgz#f6f4db9eecdb94d5744b85b653440602fcf3b24b"
13 | integrity sha512-X1AC6+k0++Y3XOT8Likr5Dh4XMmwvbTolr3Mp2g1jYPpOQ3++wff/JPfPEK6zqPCkZ88Ac0/OMRW/Uwh16U+HQ==
14 |
--------------------------------------------------------------------------------