From 65bbfa94593f815aacb1449f361244fd5318ff67 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 14:10:09 +0100 Subject: [PATCH 01/21] Move source file in its source folder --- spatial_navigation.js => src/spatial_navigation.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spatial_navigation.js => src/spatial_navigation.js (100%) diff --git a/spatial_navigation.js b/src/spatial_navigation.js similarity index 100% rename from spatial_navigation.js rename to src/spatial_navigation.js From cb414758c2e2167bf922e8dc7b6715657d6fcdf6 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 14:14:56 +0100 Subject: [PATCH 02/21] Add NPM package configuration file To be able to add external dependencies like Grunt without adding those to the repository it's necessary to define a NPM package configuration file. --- package.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..89e648f --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "js-spatial-navigation", + "version": "1.0.0", + "description": "A javascript-based implementation of Spatial Navigation.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/exozet/js-spatial-navigation.git" + }, + "keywords": [ + "spatial", + "navigation", + "javascript" + ], + "author": "Luke Chang", + "license": "MPL-2.0", + "bugs": { + "url": "https://github.com/exozet/js-spatial-navigation/issues" + }, + "homepage": "https://github.com/exozet/js-spatial-navigation#readme" +} From 87f260042787904eb5bdf0ff0588fc3046d55d3d Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 14:29:41 +0100 Subject: [PATCH 03/21] Add gitignore file --- .gitignore | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc794c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,117 @@ + +# Created by https://www.gitignore.io/api/macos,windows,linux,node + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon +# Thumbnails +._* +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + + +### Node ### +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + + +# End of https://www.gitignore.io/api/macos,windows,linux,node From 11f4bd02ffcb3683739d22060b26c4a85682c9cc Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 14:30:03 +0100 Subject: [PATCH 04/21] Add grunt and the uglify plugin to the package configuration file --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 89e648f..ed0ed17 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,9 @@ "bugs": { "url": "https://github.com/exozet/js-spatial-navigation/issues" }, - "homepage": "https://github.com/exozet/js-spatial-navigation#readme" + "homepage": "https://github.com/exozet/js-spatial-navigation#readme", + "devDependencies": { + "grunt": "^1.0.1", + "grunt-contrib-uglify": "^2.0.0" + } } From 13235ac7f8132d095d810b802fa4bfb4b49e22be Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 15:04:44 +0100 Subject: [PATCH 05/21] Add jit-grunt dependancy to the package configuration file --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ed0ed17..317c7e3 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "homepage": "https://github.com/exozet/js-spatial-navigation#readme", "devDependencies": { "grunt": "^1.0.1", - "grunt-contrib-uglify": "^2.0.0" + "grunt-contrib-uglify": "^2.0.0", + "jit-grunt": "^0.10.0" } } From baca355b9e3d1191860b2cb2ee7355c7be2cfef2 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 15:06:01 +0100 Subject: [PATCH 06/21] Add Grunt configuration file --- gruntfile.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 gruntfile.js diff --git a/gruntfile.js b/gruntfile.js new file mode 100644 index 0000000..0bb4eab --- /dev/null +++ b/gruntfile.js @@ -0,0 +1,32 @@ +const fs = require('fs'); + +module.exports = function(grunt) +{ + require('jit-grunt')(grunt, { + ngtemplates: 'grunt-angular-templates' + }); + + grunt.initConfig({ + /** + * uglify. + */ + uglify: { + options: { + beautify: false, + preserveComments: 'some', + compress: { + drop_console: true + } + }, + release: { + files: { + 'dist/spatial_navigation.min.js': ['src/spatial_navigation.js'] + } + } + } + }); + + grunt.registerTask('default', [ + 'uglify' + ]); +}; \ No newline at end of file From e21873ad4f2b5d802e7d5e43929b82c88219e9a7 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 15:06:44 +0100 Subject: [PATCH 07/21] Change documentation header to retain it during the minification process --- src/spatial_navigation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spatial_navigation.js b/src/spatial_navigation.js index d2a6834..818d61a 100644 --- a/src/spatial_navigation.js +++ b/src/spatial_navigation.js @@ -1,10 +1,10 @@ -/* +/** * A javascript-based implementation of Spatial Navigation. * * Copyright (c) 2017 Luke Chang. * https://github.com/luke-chang/js-spatial-navigation * - * Licensed under the MPL 2.0. + * @license Licensed under the MPL 2.0. */ ;(function($) { 'use strict'; From 2c04ea514832c424e2e99704f8507793e82f2801 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 15:07:26 +0100 Subject: [PATCH 08/21] Add minified version I added the minified version to the repository to make it available for everyone who wants to download it without forcing them to clone the repository and install grunt and all the other dependancies to great the minified version by their own. --- dist/spatial_navigation.min.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 dist/spatial_navigation.min.js diff --git a/dist/spatial_navigation.min.js b/dist/spatial_navigation.min.js new file mode 100644 index 0000000..b7b309a --- /dev/null +++ b/dist/spatial_navigation.min.js @@ -0,0 +1,9 @@ +/** + * A javascript-based implementation of Spatial Navigation. + * + * Copyright (c) 2017 Luke Chang. + * https://github.com/luke-chang/js-spatial-navigation + * + * @license Licensed under the MPL 2.0. + */ +!function(a){"use strict";function b(a){var b=a.getBoundingClientRect(),c={left:b.left,top:b.top,right:b.right,bottom:b.bottom,width:b.width,height:b.height};return c.element=a,c.center={x:c.left+Math.floor(c.width/2),y:c.top+Math.floor(c.height/2)},c.center.left=c.center.right=c.center.x,c.center.top=c.center.bottom=c.center.y,c}function c(a,b,c){for(var d=[[],[],[],[],[],[],[],[],[]],e=0;e=b.left+b.width*k&&(0===h?d[1].push(i):6===h&&d[7].push(i)),i.top<=b.bottom-b.height*k&&(6===h?d[3].push(i):8===h&&d[5].push(i)),i.bottom>=b.top+b.height*k&&(0===h?d[3].push(i):2===h&&d[5].push(i))}}return d}function d(a){return{nearPlumbLineIsBetter:function(b){var c;return c=b.center.x=0:"object"==typeof c&&1===c.nodeType&&b===c}function j(){var a=document.activeElement;if(a&&a!==document.body)return a}function k(a){a=a||{};for(var b=1;b=0&&a.splice(c,1);return a}function m(a,b,c){if(!a||!b||!L[b]||L[b].disabled)return!1;if(a.offsetWidth<=0&&a.offsetHeight<=0||a.hasAttribute("disabled"))return!1;if(c&&!i(a,L[b].selector))return!1;if("function"==typeof L[b].navigableFilter){if(L[b].navigableFilter(a,b)===!1)return!1}else if("function"==typeof D.navigableFilter&&D.navigableFilter(a,b)===!1)return!1;return!0}function n(a){for(var b in L)if(!L[b].disabled&&i(a,L[b].selector))return b}function o(a){return h(L[a].selector).filter(function(b){return m(b,a)})}function p(b){var c=L[b].defaultElement;return c?("string"==typeof c?c=h(c)[0]:a&&c instanceof a&&(c=c.get(0)),m(c,b,!0)?c:null):null}function q(a){var b=L[a].lastFocusedElement;return m(b,a,!0)?b:null}function r(a,b,c,d){arguments.length<4&&(d=!0);var e=document.createEvent("CustomEvent");return e.initCustomEvent(G+b,!0,d,c),a.dispatchEvent(e)}function s(a,b,c){if(!a)return!1;var d=j(),e=function(){d&&d.blur(),a.focus(),t(a,b)};if(P)return e(),!0;if(P=!0,K)return e(),P=!1,!0;if(d){var f={nextElement:a,nextSectionId:b,direction:c,native:!1};if(!r(d,"willunfocus",f))return P=!1,!1;d.blur(),r(d,"unfocused",f,!1)}var g={previousElement:d,sectionId:b,direction:c,native:!1};return r(a,"willfocus",g)?(a.focus(),r(a,"focused",g,!1),P=!1,t(a,b),!0):(P=!1,!1)}function t(a,b){b||(b=n(a)),b&&(L[b].lastFocusedElement=a,O=b)}function u(a,b){if("@"==a.charAt(0)){if(1==a.length)return v();var c=a.substr(1);return v(c)}var d=h(a)[0];if(d){var e=n(d);if(m(d,e))return s(d,e,b)}return!1}function v(a){var b=[],c=function(a){a&&b.indexOf(a)<0&&L[a]&&!L[a].disabled&&b.push(a)};a?c(a):(c(N),c(O),Object.keys(L).map(c));for(var d=0;d=0},R={init:function(){J||(window.addEventListener("keydown",z),window.addEventListener("keyup",A),window.addEventListener("focus",B,!0),window.addEventListener("blur",C,!0),J=!0)},uninit:function(){window.removeEventListener("blur",C,!0),window.removeEventListener("focus",B,!0),window.removeEventListener("keyup",A),window.removeEventListener("keydown",z),R.clear(),I=0,J=!1},clear:function(){L={},M=0,N="",O="",P=!1},set:function(){var a,b;if("object"==typeof arguments[0])b=arguments[0];else{if("string"!=typeof arguments[0]||"object"!=typeof arguments[1])return;if(a=arguments[0],b=arguments[1],!L[a])throw new Error('Section "'+a+"\" doesn't exist!")}for(var c in b)void 0!==D[c]&&(a?L[a][c]=b[c]:void 0!==b[c]&&(D[c]=b[c]));a&&(L[a]=k({},L[a]))},add:function(){var a,b={};if("object"==typeof arguments[0]?b=arguments[0]:"string"==typeof arguments[0]&&"object"==typeof arguments[1]&&(a=arguments[0],b=arguments[1]),a||(a="string"==typeof b.id?b.id:g()),L[a])throw new Error('Section "'+a+'" has already existed!');return L[a]={},M++,R.set(a,b),a},remove:function(a){if(!a||"string"!=typeof a)throw new Error('Please assign the "sectionId"!');return!!L[a]&&(L[a]=void 0,L=k({},L),M--,!0)},disable:function(a){return!!L[a]&&(L[a].disabled=!0,!0)},enable:function(a){return!!L[a]&&(L[a].disabled=!1,!0)},pause:function(){K=!0},resume:function(){K=!1},focus:function(b,c){var d=!1;void 0===c&&"boolean"==typeof b&&(c=b,b=void 0);var e=!K&&c;if(e&&R.pause(),b)if("string"==typeof b)d=L[b]?v(b):u(b);else{a&&b instanceof a&&(b=b.get(0));var f=n(b);m(b,f)&&(d=s(b,f))}else d=v();return e&&R.resume(),d},move:function(a,b){if(a=a.toLowerCase(),!F[a])return!1;var c=b?h(b)[0]:j();if(!c)return!1;var d=n(c);if(!d)return!1;var e={direction:a,sectionId:d,cause:"api"};return!!r(c,"willmove",e)&&y(a,c,d)},makeFocusable:function(a){var b=function(a){var b=void 0!==a.tabIndexIgnoreList?a.tabIndexIgnoreList:D.tabIndexIgnoreList;h(a.selector).forEach(function(a){i(a,b)||a.getAttribute("tabindex")||a.setAttribute("tabindex","-1")})};if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");b(L[a])}else for(var c in L)b(L[c])},setDefaultSection:function(a){if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");N=a}else N=""}};window.SpatialNavigation=R,a&&(a.SpatialNavigation=function(){if(R.init(),arguments.length>0){if(a.isPlainObject(arguments[0]))return R.add(arguments[0]);if("string"===a.type(arguments[0])&&a.isFunction(R[arguments[0]]))return R[arguments[0]].apply(R,[].slice.call(arguments,1))}return a.extend({},R)},a.fn.SpatialNavigation=function(){var b;return b=a.isPlainObject(arguments[0])?arguments[0]:{id:arguments[0]},b.selector=this,R.init(),b.id&&R.remove(b.id),R.add(b),R.makeFocusable(b.id),this})}(window.jQuery); \ No newline at end of file From e65cf91f61ea841895057ee4bf11f247a28eae0d Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 16:11:07 +0100 Subject: [PATCH 09/21] Add new option to be able to ignore inner dimensions check When the library checks whether an element is navigable or not it checks whether the inner width and height values are greater than zero. There're cases where the inner width and height are zero but the element is still visible and therefore needs to be focusable. To fix it I have added a new option to disable that check for a particular section or globally. --- src/spatial_navigation.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/spatial_navigation.js b/src/spatial_navigation.js index 818d61a..c4e82c1 100644 --- a/src/spatial_navigation.js +++ b/src/spatial_navigation.js @@ -32,7 +32,8 @@ restrict: 'self-first', // 'self-first', 'self-only', 'none' tabIndexIgnoreList: 'a, input, select, textarea, button, iframe, [contentEditable=true]', - navigableFilter: null + navigableFilter: null, + ignoreInnerDimensionValidator: false }; /*********************/ @@ -518,7 +519,11 @@ !_sections[sectionId] || _sections[sectionId].disabled) { return false; } - if ((elem.offsetWidth <= 0 && elem.offsetHeight <= 0) || + + var ignoreInnerDimensionValidator = (typeof _sections[sectionId].ignoreInnerDimensionValidator === 'boolean' && _sections[sectionId].ignoreInnerDimensionValidator === true) || + (typeof GlobalConfig.ignoreInnerDimensionValidator === 'boolean' && GlobalConfig.ignoreInnerDimensionValidator === true); + + if ((!ignoreInnerDimensionValidator && elem.offsetWidth <= 0 && elem.offsetHeight <= 0) || elem.hasAttribute('disabled')) { return false; } From 32d6cd93962a487974882309316d8f6c300b4eaa Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 16:17:20 +0100 Subject: [PATCH 10/21] Update minified version with the latest build --- dist/spatial_navigation.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/spatial_navigation.min.js b/dist/spatial_navigation.min.js index b7b309a..7547ffb 100644 --- a/dist/spatial_navigation.min.js +++ b/dist/spatial_navigation.min.js @@ -6,4 +6,4 @@ * * @license Licensed under the MPL 2.0. */ -!function(a){"use strict";function b(a){var b=a.getBoundingClientRect(),c={left:b.left,top:b.top,right:b.right,bottom:b.bottom,width:b.width,height:b.height};return c.element=a,c.center={x:c.left+Math.floor(c.width/2),y:c.top+Math.floor(c.height/2)},c.center.left=c.center.right=c.center.x,c.center.top=c.center.bottom=c.center.y,c}function c(a,b,c){for(var d=[[],[],[],[],[],[],[],[],[]],e=0;e=b.left+b.width*k&&(0===h?d[1].push(i):6===h&&d[7].push(i)),i.top<=b.bottom-b.height*k&&(6===h?d[3].push(i):8===h&&d[5].push(i)),i.bottom>=b.top+b.height*k&&(0===h?d[3].push(i):2===h&&d[5].push(i))}}return d}function d(a){return{nearPlumbLineIsBetter:function(b){var c;return c=b.center.x=0:"object"==typeof c&&1===c.nodeType&&b===c}function j(){var a=document.activeElement;if(a&&a!==document.body)return a}function k(a){a=a||{};for(var b=1;b=0&&a.splice(c,1);return a}function m(a,b,c){if(!a||!b||!L[b]||L[b].disabled)return!1;if(a.offsetWidth<=0&&a.offsetHeight<=0||a.hasAttribute("disabled"))return!1;if(c&&!i(a,L[b].selector))return!1;if("function"==typeof L[b].navigableFilter){if(L[b].navigableFilter(a,b)===!1)return!1}else if("function"==typeof D.navigableFilter&&D.navigableFilter(a,b)===!1)return!1;return!0}function n(a){for(var b in L)if(!L[b].disabled&&i(a,L[b].selector))return b}function o(a){return h(L[a].selector).filter(function(b){return m(b,a)})}function p(b){var c=L[b].defaultElement;return c?("string"==typeof c?c=h(c)[0]:a&&c instanceof a&&(c=c.get(0)),m(c,b,!0)?c:null):null}function q(a){var b=L[a].lastFocusedElement;return m(b,a,!0)?b:null}function r(a,b,c,d){arguments.length<4&&(d=!0);var e=document.createEvent("CustomEvent");return e.initCustomEvent(G+b,!0,d,c),a.dispatchEvent(e)}function s(a,b,c){if(!a)return!1;var d=j(),e=function(){d&&d.blur(),a.focus(),t(a,b)};if(P)return e(),!0;if(P=!0,K)return e(),P=!1,!0;if(d){var f={nextElement:a,nextSectionId:b,direction:c,native:!1};if(!r(d,"willunfocus",f))return P=!1,!1;d.blur(),r(d,"unfocused",f,!1)}var g={previousElement:d,sectionId:b,direction:c,native:!1};return r(a,"willfocus",g)?(a.focus(),r(a,"focused",g,!1),P=!1,t(a,b),!0):(P=!1,!1)}function t(a,b){b||(b=n(a)),b&&(L[b].lastFocusedElement=a,O=b)}function u(a,b){if("@"==a.charAt(0)){if(1==a.length)return v();var c=a.substr(1);return v(c)}var d=h(a)[0];if(d){var e=n(d);if(m(d,e))return s(d,e,b)}return!1}function v(a){var b=[],c=function(a){a&&b.indexOf(a)<0&&L[a]&&!L[a].disabled&&b.push(a)};a?c(a):(c(N),c(O),Object.keys(L).map(c));for(var d=0;d=0},R={init:function(){J||(window.addEventListener("keydown",z),window.addEventListener("keyup",A),window.addEventListener("focus",B,!0),window.addEventListener("blur",C,!0),J=!0)},uninit:function(){window.removeEventListener("blur",C,!0),window.removeEventListener("focus",B,!0),window.removeEventListener("keyup",A),window.removeEventListener("keydown",z),R.clear(),I=0,J=!1},clear:function(){L={},M=0,N="",O="",P=!1},set:function(){var a,b;if("object"==typeof arguments[0])b=arguments[0];else{if("string"!=typeof arguments[0]||"object"!=typeof arguments[1])return;if(a=arguments[0],b=arguments[1],!L[a])throw new Error('Section "'+a+"\" doesn't exist!")}for(var c in b)void 0!==D[c]&&(a?L[a][c]=b[c]:void 0!==b[c]&&(D[c]=b[c]));a&&(L[a]=k({},L[a]))},add:function(){var a,b={};if("object"==typeof arguments[0]?b=arguments[0]:"string"==typeof arguments[0]&&"object"==typeof arguments[1]&&(a=arguments[0],b=arguments[1]),a||(a="string"==typeof b.id?b.id:g()),L[a])throw new Error('Section "'+a+'" has already existed!');return L[a]={},M++,R.set(a,b),a},remove:function(a){if(!a||"string"!=typeof a)throw new Error('Please assign the "sectionId"!');return!!L[a]&&(L[a]=void 0,L=k({},L),M--,!0)},disable:function(a){return!!L[a]&&(L[a].disabled=!0,!0)},enable:function(a){return!!L[a]&&(L[a].disabled=!1,!0)},pause:function(){K=!0},resume:function(){K=!1},focus:function(b,c){var d=!1;void 0===c&&"boolean"==typeof b&&(c=b,b=void 0);var e=!K&&c;if(e&&R.pause(),b)if("string"==typeof b)d=L[b]?v(b):u(b);else{a&&b instanceof a&&(b=b.get(0));var f=n(b);m(b,f)&&(d=s(b,f))}else d=v();return e&&R.resume(),d},move:function(a,b){if(a=a.toLowerCase(),!F[a])return!1;var c=b?h(b)[0]:j();if(!c)return!1;var d=n(c);if(!d)return!1;var e={direction:a,sectionId:d,cause:"api"};return!!r(c,"willmove",e)&&y(a,c,d)},makeFocusable:function(a){var b=function(a){var b=void 0!==a.tabIndexIgnoreList?a.tabIndexIgnoreList:D.tabIndexIgnoreList;h(a.selector).forEach(function(a){i(a,b)||a.getAttribute("tabindex")||a.setAttribute("tabindex","-1")})};if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");b(L[a])}else for(var c in L)b(L[c])},setDefaultSection:function(a){if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");N=a}else N=""}};window.SpatialNavigation=R,a&&(a.SpatialNavigation=function(){if(R.init(),arguments.length>0){if(a.isPlainObject(arguments[0]))return R.add(arguments[0]);if("string"===a.type(arguments[0])&&a.isFunction(R[arguments[0]]))return R[arguments[0]].apply(R,[].slice.call(arguments,1))}return a.extend({},R)},a.fn.SpatialNavigation=function(){var b;return b=a.isPlainObject(arguments[0])?arguments[0]:{id:arguments[0]},b.selector=this,R.init(),b.id&&R.remove(b.id),R.add(b),R.makeFocusable(b.id),this})}(window.jQuery); \ No newline at end of file +!function(a){"use strict";function b(a){var b=a.getBoundingClientRect(),c={left:b.left,top:b.top,right:b.right,bottom:b.bottom,width:b.width,height:b.height};return c.element=a,c.center={x:c.left+Math.floor(c.width/2),y:c.top+Math.floor(c.height/2)},c.center.left=c.center.right=c.center.x,c.center.top=c.center.bottom=c.center.y,c}function c(a,b,c){for(var d=[[],[],[],[],[],[],[],[],[]],e=0;e=b.left+b.width*k&&(0===h?d[1].push(i):6===h&&d[7].push(i)),i.top<=b.bottom-b.height*k&&(6===h?d[3].push(i):8===h&&d[5].push(i)),i.bottom>=b.top+b.height*k&&(0===h?d[3].push(i):2===h&&d[5].push(i))}}return d}function d(a){return{nearPlumbLineIsBetter:function(b){var c;return c=b.center.x=0:"object"==typeof c&&1===c.nodeType&&b===c}function j(){var a=document.activeElement;if(a&&a!==document.body)return a}function k(a){a=a||{};for(var b=1;b=0&&a.splice(c,1);return a}function m(a,b,c){if(!a||!b||!L[b]||L[b].disabled)return!1;var d="boolean"==typeof L[b].ignoreInnerDimensionValidator&&L[b].ignoreInnerDimensionValidator===!0||"boolean"==typeof D.ignoreInnerDimensionValidator&&D.ignoreInnerDimensionValidator===!0;if(!d&&a.offsetWidth<=0&&a.offsetHeight<=0||a.hasAttribute("disabled"))return!1;if(c&&!i(a,L[b].selector))return!1;if("function"==typeof L[b].navigableFilter){if(L[b].navigableFilter(a,b)===!1)return!1}else if("function"==typeof D.navigableFilter&&D.navigableFilter(a,b)===!1)return!1;return!0}function n(a){for(var b in L)if(!L[b].disabled&&i(a,L[b].selector))return b}function o(a){return h(L[a].selector).filter(function(b){return m(b,a)})}function p(b){var c=L[b].defaultElement;return c?("string"==typeof c?c=h(c)[0]:a&&c instanceof a&&(c=c.get(0)),m(c,b,!0)?c:null):null}function q(a){var b=L[a].lastFocusedElement;return m(b,a,!0)?b:null}function r(a,b,c,d){arguments.length<4&&(d=!0);var e=document.createEvent("CustomEvent");return e.initCustomEvent(G+b,!0,d,c),a.dispatchEvent(e)}function s(a,b,c){if(!a)return!1;var d=j(),e=function(){d&&d.blur(),a.focus(),t(a,b)};if(P)return e(),!0;if(P=!0,K)return e(),P=!1,!0;if(d){var f={nextElement:a,nextSectionId:b,direction:c,native:!1};if(!r(d,"willunfocus",f))return P=!1,!1;d.blur(),r(d,"unfocused",f,!1)}var g={previousElement:d,sectionId:b,direction:c,native:!1};return r(a,"willfocus",g)?(a.focus(),r(a,"focused",g,!1),P=!1,t(a,b),!0):(P=!1,!1)}function t(a,b){b||(b=n(a)),b&&(L[b].lastFocusedElement=a,O=b)}function u(a,b){if("@"==a.charAt(0)){if(1==a.length)return v();var c=a.substr(1);return v(c)}var d=h(a)[0];if(d){var e=n(d);if(m(d,e))return s(d,e,b)}return!1}function v(a){var b=[],c=function(a){a&&b.indexOf(a)<0&&L[a]&&!L[a].disabled&&b.push(a)};a?c(a):(c(N),c(O),Object.keys(L).map(c));for(var d=0;d=0},R={init:function(){J||(window.addEventListener("keydown",z),window.addEventListener("keyup",A),window.addEventListener("focus",B,!0),window.addEventListener("blur",C,!0),J=!0)},uninit:function(){window.removeEventListener("blur",C,!0),window.removeEventListener("focus",B,!0),window.removeEventListener("keyup",A),window.removeEventListener("keydown",z),R.clear(),I=0,J=!1},clear:function(){L={},M=0,N="",O="",P=!1},set:function(){var a,b;if("object"==typeof arguments[0])b=arguments[0];else{if("string"!=typeof arguments[0]||"object"!=typeof arguments[1])return;if(a=arguments[0],b=arguments[1],!L[a])throw new Error('Section "'+a+"\" doesn't exist!")}for(var c in b)void 0!==D[c]&&(a?L[a][c]=b[c]:void 0!==b[c]&&(D[c]=b[c]));a&&(L[a]=k({},L[a]))},add:function(){var a,b={};if("object"==typeof arguments[0]?b=arguments[0]:"string"==typeof arguments[0]&&"object"==typeof arguments[1]&&(a=arguments[0],b=arguments[1]),a||(a="string"==typeof b.id?b.id:g()),L[a])throw new Error('Section "'+a+'" has already existed!');return L[a]={},M++,R.set(a,b),a},remove:function(a){if(!a||"string"!=typeof a)throw new Error('Please assign the "sectionId"!');return!!L[a]&&(L[a]=void 0,L=k({},L),M--,!0)},disable:function(a){return!!L[a]&&(L[a].disabled=!0,!0)},enable:function(a){return!!L[a]&&(L[a].disabled=!1,!0)},pause:function(){K=!0},resume:function(){K=!1},focus:function(b,c){var d=!1;void 0===c&&"boolean"==typeof b&&(c=b,b=void 0);var e=!K&&c;if(e&&R.pause(),b)if("string"==typeof b)d=L[b]?v(b):u(b);else{a&&b instanceof a&&(b=b.get(0));var f=n(b);m(b,f)&&(d=s(b,f))}else d=v();return e&&R.resume(),d},move:function(a,b){if(a=a.toLowerCase(),!F[a])return!1;var c=b?h(b)[0]:j();if(!c)return!1;var d=n(c);if(!d)return!1;var e={direction:a,sectionId:d,cause:"api"};return!!r(c,"willmove",e)&&y(a,c,d)},makeFocusable:function(a){var b=function(a){var b=void 0!==a.tabIndexIgnoreList?a.tabIndexIgnoreList:D.tabIndexIgnoreList;h(a.selector).forEach(function(a){i(a,b)||a.getAttribute("tabindex")||a.setAttribute("tabindex","-1")})};if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");b(L[a])}else for(var c in L)b(L[c])},setDefaultSection:function(a){if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");N=a}else N=""}};window.SpatialNavigation=R,a&&(a.SpatialNavigation=function(){if(R.init(),arguments.length>0){if(a.isPlainObject(arguments[0]))return R.add(arguments[0]);if("string"===a.type(arguments[0])&&a.isFunction(R[arguments[0]]))return R[arguments[0]].apply(R,[].slice.call(arguments,1))}return a.extend({},R)},a.fn.SpatialNavigation=function(){var b;return b=a.isPlainObject(arguments[0])?arguments[0]:{id:arguments[0]},b.selector=this,R.init(),b.id&&R.remove(b.id),R.add(b),R.makeFocusable(b.id),this})}(window.jQuery); \ No newline at end of file From a1af9a2b39995c0269bf43ce6569aa92885e9818 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 16:22:15 +0100 Subject: [PATCH 11/21] Change ignoreInnerDimensionValidator to ignoreInnerDimensionValidation It seems that ignoreInnerDimensionValidation is a more appropriate name for that option and therefore changed it. --- src/spatial_navigation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spatial_navigation.js b/src/spatial_navigation.js index c4e82c1..1b72601 100644 --- a/src/spatial_navigation.js +++ b/src/spatial_navigation.js @@ -33,7 +33,7 @@ tabIndexIgnoreList: 'a, input, select, textarea, button, iframe, [contentEditable=true]', navigableFilter: null, - ignoreInnerDimensionValidator: false + ignoreInnerDimensionValidation: false }; /*********************/ @@ -520,10 +520,10 @@ return false; } - var ignoreInnerDimensionValidator = (typeof _sections[sectionId].ignoreInnerDimensionValidator === 'boolean' && _sections[sectionId].ignoreInnerDimensionValidator === true) || - (typeof GlobalConfig.ignoreInnerDimensionValidator === 'boolean' && GlobalConfig.ignoreInnerDimensionValidator === true); + var ignoreInnerDimensionValidation = (typeof _sections[sectionId].ignoreInnerDimensionValidation === 'boolean' && _sections[sectionId].ignoreInnerDimensionValidation === true) || + (typeof GlobalConfig.ignoreInnerDimensionValidation === 'boolean' && GlobalConfig.ignoreInnerDimensionValidation === true); - if ((!ignoreInnerDimensionValidator && elem.offsetWidth <= 0 && elem.offsetHeight <= 0) || + if ((!ignoreInnerDimensionValidation && elem.offsetWidth <= 0 && elem.offsetHeight <= 0) || elem.hasAttribute('disabled')) { return false; } From 2b5078371d4f6f32ff31bf1a44b2d11d378c5376 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 16:24:44 +0100 Subject: [PATCH 12/21] Add description for the new option ignoreInnerDimensionValidation --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c561b38..aa34628 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,8 @@ Following is an example with default values. leaveFor: null, restrict: 'self-first', tabIndexIgnoreList: 'a, input, select, textarea, button, iframe, [contentEditable=true]', - navigableFilter: null + navigableFilter: null, + ignoreInnerDimensionValidator: false } ``` @@ -293,6 +294,15 @@ A callback function that accepts a DOM element as the first argument. SpatialNavigation calls this function every time when it tries to traverse every single candidate. You can ignore arbitrary elements by returning `false`. +#### `ignoreInnerDimensionValidation` + + + Type: `'boolean'` + + Default: `false` + +When the library checks whether an element is navigable or not it checks whether the inner width and height values are greater than zero. There're +cases where the inner width and height are zero but the element is still visible and therefore needs to be focusable. To fix it I have added a new option +to disable that check for a particular section or globally. + ### Custom Attributes SpatialNavigation supports HTML `data-*` attributes as follows: From e5047ef16d74e41248fe3d18889533707a0ad0cd Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 16:28:50 +0100 Subject: [PATCH 13/21] Change option description --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index aa34628..53ee046 100644 --- a/README.md +++ b/README.md @@ -299,9 +299,9 @@ SpatialNavigation calls this function every time when it tries to traverse every + Type: `'boolean'` + Default: `false` -When the library checks whether an element is navigable or not it checks whether the inner width and height values are greater than zero. There're -cases where the inner width and height are zero but the element is still visible and therefore needs to be focusable. To fix it I have added a new option -to disable that check for a particular section or globally. +When the library checks whether an element is navigable or not, it validates the inner width and height values to be greater than zero +and only then there're navigable. There're also cases where the inner width and height are zero but the element is still visible and therefore needs to be focusable. +In such cases it's possible to disable that validator for a particular section or globally. ### Custom Attributes From 59231540219e9b38016f22601d8dfc975f11d84c Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 16:29:28 +0100 Subject: [PATCH 14/21] Update minified version with the latest build --- dist/spatial_navigation.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/spatial_navigation.min.js b/dist/spatial_navigation.min.js index 7547ffb..fdd7fdf 100644 --- a/dist/spatial_navigation.min.js +++ b/dist/spatial_navigation.min.js @@ -6,4 +6,4 @@ * * @license Licensed under the MPL 2.0. */ -!function(a){"use strict";function b(a){var b=a.getBoundingClientRect(),c={left:b.left,top:b.top,right:b.right,bottom:b.bottom,width:b.width,height:b.height};return c.element=a,c.center={x:c.left+Math.floor(c.width/2),y:c.top+Math.floor(c.height/2)},c.center.left=c.center.right=c.center.x,c.center.top=c.center.bottom=c.center.y,c}function c(a,b,c){for(var d=[[],[],[],[],[],[],[],[],[]],e=0;e=b.left+b.width*k&&(0===h?d[1].push(i):6===h&&d[7].push(i)),i.top<=b.bottom-b.height*k&&(6===h?d[3].push(i):8===h&&d[5].push(i)),i.bottom>=b.top+b.height*k&&(0===h?d[3].push(i):2===h&&d[5].push(i))}}return d}function d(a){return{nearPlumbLineIsBetter:function(b){var c;return c=b.center.x=0:"object"==typeof c&&1===c.nodeType&&b===c}function j(){var a=document.activeElement;if(a&&a!==document.body)return a}function k(a){a=a||{};for(var b=1;b=0&&a.splice(c,1);return a}function m(a,b,c){if(!a||!b||!L[b]||L[b].disabled)return!1;var d="boolean"==typeof L[b].ignoreInnerDimensionValidator&&L[b].ignoreInnerDimensionValidator===!0||"boolean"==typeof D.ignoreInnerDimensionValidator&&D.ignoreInnerDimensionValidator===!0;if(!d&&a.offsetWidth<=0&&a.offsetHeight<=0||a.hasAttribute("disabled"))return!1;if(c&&!i(a,L[b].selector))return!1;if("function"==typeof L[b].navigableFilter){if(L[b].navigableFilter(a,b)===!1)return!1}else if("function"==typeof D.navigableFilter&&D.navigableFilter(a,b)===!1)return!1;return!0}function n(a){for(var b in L)if(!L[b].disabled&&i(a,L[b].selector))return b}function o(a){return h(L[a].selector).filter(function(b){return m(b,a)})}function p(b){var c=L[b].defaultElement;return c?("string"==typeof c?c=h(c)[0]:a&&c instanceof a&&(c=c.get(0)),m(c,b,!0)?c:null):null}function q(a){var b=L[a].lastFocusedElement;return m(b,a,!0)?b:null}function r(a,b,c,d){arguments.length<4&&(d=!0);var e=document.createEvent("CustomEvent");return e.initCustomEvent(G+b,!0,d,c),a.dispatchEvent(e)}function s(a,b,c){if(!a)return!1;var d=j(),e=function(){d&&d.blur(),a.focus(),t(a,b)};if(P)return e(),!0;if(P=!0,K)return e(),P=!1,!0;if(d){var f={nextElement:a,nextSectionId:b,direction:c,native:!1};if(!r(d,"willunfocus",f))return P=!1,!1;d.blur(),r(d,"unfocused",f,!1)}var g={previousElement:d,sectionId:b,direction:c,native:!1};return r(a,"willfocus",g)?(a.focus(),r(a,"focused",g,!1),P=!1,t(a,b),!0):(P=!1,!1)}function t(a,b){b||(b=n(a)),b&&(L[b].lastFocusedElement=a,O=b)}function u(a,b){if("@"==a.charAt(0)){if(1==a.length)return v();var c=a.substr(1);return v(c)}var d=h(a)[0];if(d){var e=n(d);if(m(d,e))return s(d,e,b)}return!1}function v(a){var b=[],c=function(a){a&&b.indexOf(a)<0&&L[a]&&!L[a].disabled&&b.push(a)};a?c(a):(c(N),c(O),Object.keys(L).map(c));for(var d=0;d=0},R={init:function(){J||(window.addEventListener("keydown",z),window.addEventListener("keyup",A),window.addEventListener("focus",B,!0),window.addEventListener("blur",C,!0),J=!0)},uninit:function(){window.removeEventListener("blur",C,!0),window.removeEventListener("focus",B,!0),window.removeEventListener("keyup",A),window.removeEventListener("keydown",z),R.clear(),I=0,J=!1},clear:function(){L={},M=0,N="",O="",P=!1},set:function(){var a,b;if("object"==typeof arguments[0])b=arguments[0];else{if("string"!=typeof arguments[0]||"object"!=typeof arguments[1])return;if(a=arguments[0],b=arguments[1],!L[a])throw new Error('Section "'+a+"\" doesn't exist!")}for(var c in b)void 0!==D[c]&&(a?L[a][c]=b[c]:void 0!==b[c]&&(D[c]=b[c]));a&&(L[a]=k({},L[a]))},add:function(){var a,b={};if("object"==typeof arguments[0]?b=arguments[0]:"string"==typeof arguments[0]&&"object"==typeof arguments[1]&&(a=arguments[0],b=arguments[1]),a||(a="string"==typeof b.id?b.id:g()),L[a])throw new Error('Section "'+a+'" has already existed!');return L[a]={},M++,R.set(a,b),a},remove:function(a){if(!a||"string"!=typeof a)throw new Error('Please assign the "sectionId"!');return!!L[a]&&(L[a]=void 0,L=k({},L),M--,!0)},disable:function(a){return!!L[a]&&(L[a].disabled=!0,!0)},enable:function(a){return!!L[a]&&(L[a].disabled=!1,!0)},pause:function(){K=!0},resume:function(){K=!1},focus:function(b,c){var d=!1;void 0===c&&"boolean"==typeof b&&(c=b,b=void 0);var e=!K&&c;if(e&&R.pause(),b)if("string"==typeof b)d=L[b]?v(b):u(b);else{a&&b instanceof a&&(b=b.get(0));var f=n(b);m(b,f)&&(d=s(b,f))}else d=v();return e&&R.resume(),d},move:function(a,b){if(a=a.toLowerCase(),!F[a])return!1;var c=b?h(b)[0]:j();if(!c)return!1;var d=n(c);if(!d)return!1;var e={direction:a,sectionId:d,cause:"api"};return!!r(c,"willmove",e)&&y(a,c,d)},makeFocusable:function(a){var b=function(a){var b=void 0!==a.tabIndexIgnoreList?a.tabIndexIgnoreList:D.tabIndexIgnoreList;h(a.selector).forEach(function(a){i(a,b)||a.getAttribute("tabindex")||a.setAttribute("tabindex","-1")})};if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");b(L[a])}else for(var c in L)b(L[c])},setDefaultSection:function(a){if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");N=a}else N=""}};window.SpatialNavigation=R,a&&(a.SpatialNavigation=function(){if(R.init(),arguments.length>0){if(a.isPlainObject(arguments[0]))return R.add(arguments[0]);if("string"===a.type(arguments[0])&&a.isFunction(R[arguments[0]]))return R[arguments[0]].apply(R,[].slice.call(arguments,1))}return a.extend({},R)},a.fn.SpatialNavigation=function(){var b;return b=a.isPlainObject(arguments[0])?arguments[0]:{id:arguments[0]},b.selector=this,R.init(),b.id&&R.remove(b.id),R.add(b),R.makeFocusable(b.id),this})}(window.jQuery); \ No newline at end of file +!function(a){"use strict";function b(a){var b=a.getBoundingClientRect(),c={left:b.left,top:b.top,right:b.right,bottom:b.bottom,width:b.width,height:b.height};return c.element=a,c.center={x:c.left+Math.floor(c.width/2),y:c.top+Math.floor(c.height/2)},c.center.left=c.center.right=c.center.x,c.center.top=c.center.bottom=c.center.y,c}function c(a,b,c){for(var d=[[],[],[],[],[],[],[],[],[]],e=0;e=b.left+b.width*k&&(0===h?d[1].push(i):6===h&&d[7].push(i)),i.top<=b.bottom-b.height*k&&(6===h?d[3].push(i):8===h&&d[5].push(i)),i.bottom>=b.top+b.height*k&&(0===h?d[3].push(i):2===h&&d[5].push(i))}}return d}function d(a){return{nearPlumbLineIsBetter:function(b){var c;return c=b.center.x=0:"object"==typeof c&&1===c.nodeType&&b===c}function j(){var a=document.activeElement;if(a&&a!==document.body)return a}function k(a){a=a||{};for(var b=1;b=0&&a.splice(c,1);return a}function m(a,b,c){if(!a||!b||!L[b]||L[b].disabled)return!1;var d="boolean"==typeof L[b].ignoreInnerDimensionValidation&&L[b].ignoreInnerDimensionValidation===!0||"boolean"==typeof D.ignoreInnerDimensionValidation&&D.ignoreInnerDimensionValidation===!0;if(!d&&a.offsetWidth<=0&&a.offsetHeight<=0||a.hasAttribute("disabled"))return!1;if(c&&!i(a,L[b].selector))return!1;if("function"==typeof L[b].navigableFilter){if(L[b].navigableFilter(a,b)===!1)return!1}else if("function"==typeof D.navigableFilter&&D.navigableFilter(a,b)===!1)return!1;return!0}function n(a){for(var b in L)if(!L[b].disabled&&i(a,L[b].selector))return b}function o(a){return h(L[a].selector).filter(function(b){return m(b,a)})}function p(b){var c=L[b].defaultElement;return c?("string"==typeof c?c=h(c)[0]:a&&c instanceof a&&(c=c.get(0)),m(c,b,!0)?c:null):null}function q(a){var b=L[a].lastFocusedElement;return m(b,a,!0)?b:null}function r(a,b,c,d){arguments.length<4&&(d=!0);var e=document.createEvent("CustomEvent");return e.initCustomEvent(G+b,!0,d,c),a.dispatchEvent(e)}function s(a,b,c){if(!a)return!1;var d=j(),e=function(){d&&d.blur(),a.focus(),t(a,b)};if(P)return e(),!0;if(P=!0,K)return e(),P=!1,!0;if(d){var f={nextElement:a,nextSectionId:b,direction:c,native:!1};if(!r(d,"willunfocus",f))return P=!1,!1;d.blur(),r(d,"unfocused",f,!1)}var g={previousElement:d,sectionId:b,direction:c,native:!1};return r(a,"willfocus",g)?(a.focus(),r(a,"focused",g,!1),P=!1,t(a,b),!0):(P=!1,!1)}function t(a,b){b||(b=n(a)),b&&(L[b].lastFocusedElement=a,O=b)}function u(a,b){if("@"==a.charAt(0)){if(1==a.length)return v();var c=a.substr(1);return v(c)}var d=h(a)[0];if(d){var e=n(d);if(m(d,e))return s(d,e,b)}return!1}function v(a){var b=[],c=function(a){a&&b.indexOf(a)<0&&L[a]&&!L[a].disabled&&b.push(a)};a?c(a):(c(N),c(O),Object.keys(L).map(c));for(var d=0;d=0},R={init:function(){J||(window.addEventListener("keydown",z),window.addEventListener("keyup",A),window.addEventListener("focus",B,!0),window.addEventListener("blur",C,!0),J=!0)},uninit:function(){window.removeEventListener("blur",C,!0),window.removeEventListener("focus",B,!0),window.removeEventListener("keyup",A),window.removeEventListener("keydown",z),R.clear(),I=0,J=!1},clear:function(){L={},M=0,N="",O="",P=!1},set:function(){var a,b;if("object"==typeof arguments[0])b=arguments[0];else{if("string"!=typeof arguments[0]||"object"!=typeof arguments[1])return;if(a=arguments[0],b=arguments[1],!L[a])throw new Error('Section "'+a+"\" doesn't exist!")}for(var c in b)void 0!==D[c]&&(a?L[a][c]=b[c]:void 0!==b[c]&&(D[c]=b[c]));a&&(L[a]=k({},L[a]))},add:function(){var a,b={};if("object"==typeof arguments[0]?b=arguments[0]:"string"==typeof arguments[0]&&"object"==typeof arguments[1]&&(a=arguments[0],b=arguments[1]),a||(a="string"==typeof b.id?b.id:g()),L[a])throw new Error('Section "'+a+'" has already existed!');return L[a]={},M++,R.set(a,b),a},remove:function(a){if(!a||"string"!=typeof a)throw new Error('Please assign the "sectionId"!');return!!L[a]&&(L[a]=void 0,L=k({},L),M--,!0)},disable:function(a){return!!L[a]&&(L[a].disabled=!0,!0)},enable:function(a){return!!L[a]&&(L[a].disabled=!1,!0)},pause:function(){K=!0},resume:function(){K=!1},focus:function(b,c){var d=!1;void 0===c&&"boolean"==typeof b&&(c=b,b=void 0);var e=!K&&c;if(e&&R.pause(),b)if("string"==typeof b)d=L[b]?v(b):u(b);else{a&&b instanceof a&&(b=b.get(0));var f=n(b);m(b,f)&&(d=s(b,f))}else d=v();return e&&R.resume(),d},move:function(a,b){if(a=a.toLowerCase(),!F[a])return!1;var c=b?h(b)[0]:j();if(!c)return!1;var d=n(c);if(!d)return!1;var e={direction:a,sectionId:d,cause:"api"};return!!r(c,"willmove",e)&&y(a,c,d)},makeFocusable:function(a){var b=function(a){var b=void 0!==a.tabIndexIgnoreList?a.tabIndexIgnoreList:D.tabIndexIgnoreList;h(a.selector).forEach(function(a){i(a,b)||a.getAttribute("tabindex")||a.setAttribute("tabindex","-1")})};if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");b(L[a])}else for(var c in L)b(L[c])},setDefaultSection:function(a){if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");N=a}else N=""}};window.SpatialNavigation=R,a&&(a.SpatialNavigation=function(){if(R.init(),arguments.length>0){if(a.isPlainObject(arguments[0]))return R.add(arguments[0]);if("string"===a.type(arguments[0])&&a.isFunction(R[arguments[0]]))return R[arguments[0]].apply(R,[].slice.call(arguments,1))}return a.extend({},R)},a.fn.SpatialNavigation=function(){var b;return b=a.isPlainObject(arguments[0])?arguments[0]:{id:arguments[0]},b.selector=this,R.init(),b.id&&R.remove(b.id),R.add(b),R.makeFocusable(b.id),this})}(window.jQuery); \ No newline at end of file From abfc7d79a8bc8db5fccdb3510be52f544fd738d1 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 16:48:38 +0100 Subject: [PATCH 15/21] Add deployment documentation --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 53ee046..16dd140 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,22 @@ Although SpatialNavigation is a standalone (pure-javascript-based) library, it c + [Demonstrations](https://luke-chang.github.io/js-spatial-navigation/demo/) +Deployment +---------- + +After changing the source files you can create a new build by entering following line: + +```shell +grunt +``` + +This will create a new minified version of the library and saves it into the ``dist`` folder. +In case you haven't grunt available yet, just enter + +```shell +npm install +``` + Documentation ------------- From f3d86d517e23fe9beede593cd235b3c8a3463c15 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 30 Jan 2017 16:51:06 +0100 Subject: [PATCH 16/21] Update deployment description --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16dd140..9a62c8e 100644 --- a/README.md +++ b/README.md @@ -78,19 +78,22 @@ Although SpatialNavigation is a standalone (pure-javascript-based) library, it c Deployment ---------- -After changing the source files you can create a new build by entering following line: +After changing the source file you can create a new build by entering following line: ```shell grunt ``` This will create a new minified version of the library and saves it into the ``dist`` folder. -In case you haven't grunt available yet, just enter +In case you haven't Grunt available yet, just enter ```shell npm install ``` +and Grunt as well as all the needed plugins will be installed in your repository folder. Afterwards you +can perform the instruction from above to create a new minified version of the library. + Documentation ------------- From 40960378163af4774b577a82a8b13c494a9ebbd4 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Wed, 1 Feb 2017 11:52:38 +0100 Subject: [PATCH 17/21] Fix wrong option anme The option name "ignoreInnerDimensionValidation" was wrong because it doesn't disable the inner dimension validation but the validation of the offset dimensions. Therefore it was renamed to "ignoreOffsetDimensionValidation". --- src/spatial_navigation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spatial_navigation.js b/src/spatial_navigation.js index 1b72601..4b3fc2e 100644 --- a/src/spatial_navigation.js +++ b/src/spatial_navigation.js @@ -33,7 +33,7 @@ tabIndexIgnoreList: 'a, input, select, textarea, button, iframe, [contentEditable=true]', navigableFilter: null, - ignoreInnerDimensionValidation: false + ignoreOffsetDimensionValidation: false }; /*********************/ @@ -520,10 +520,10 @@ return false; } - var ignoreInnerDimensionValidation = (typeof _sections[sectionId].ignoreInnerDimensionValidation === 'boolean' && _sections[sectionId].ignoreInnerDimensionValidation === true) || - (typeof GlobalConfig.ignoreInnerDimensionValidation === 'boolean' && GlobalConfig.ignoreInnerDimensionValidation === true); + var ignoreOffsetDimensionValidation = (typeof _sections[sectionId].ignoreOffsetDimensionValidation === 'boolean' && _sections[sectionId].ignoreOffsetDimensionValidation === true) || + (typeof GlobalConfig.ignoreOffsetDimensionValidation === 'boolean' && GlobalConfig.ignoreOffsetDimensionValidation === true); - if ((!ignoreInnerDimensionValidation && elem.offsetWidth <= 0 && elem.offsetHeight <= 0) || + if ((!ignoreOffsetDimensionValidation && elem.offsetWidth <= 0 && elem.offsetHeight <= 0) || elem.hasAttribute('disabled')) { return false; } From ccb258a40f5a1b4cfe86d406b2b7fed61bf03b44 Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Wed, 1 Feb 2017 12:03:20 +0100 Subject: [PATCH 18/21] Add source code documentation for the new option --- src/spatial_navigation.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/spatial_navigation.js b/src/spatial_navigation.js index 4b3fc2e..d70899d 100644 --- a/src/spatial_navigation.js +++ b/src/spatial_navigation.js @@ -33,6 +33,18 @@ tabIndexIgnoreList: 'a, input, select, textarea, button, iframe, [contentEditable=true]', navigableFilter: null, + + /** + * Disables offset dimension validation. + * + * Usually before an element get focused it'll be checked + * whether it's navigable or not. To do so it checks, among other things, whether + * the offsetWidth or the offsetHeight is greater than null because then it means + * that element is visible. Some elements haven't any offsetWidth or offsetHeight + * defined even though they're visible to the viewer. To make those elements + * navigable this option can be set to true and the validation will be ignored. + * That option is available for a particular section or globally. + */ ignoreOffsetDimensionValidation: false }; From 49cbc049bfa3647eb7f9a8fcfc5f1e784703796c Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Wed, 1 Feb 2017 12:03:44 +0100 Subject: [PATCH 19/21] Fix wrong description about the new option in the README file --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9a62c8e..09958f9 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ Following is an example with default values. restrict: 'self-first', tabIndexIgnoreList: 'a, input, select, textarea, button, iframe, [contentEditable=true]', navigableFilter: null, - ignoreInnerDimensionValidator: false + ignoreOffsetDimensionValidation: false } ``` @@ -313,14 +313,14 @@ A callback function that accepts a DOM element as the first argument. SpatialNavigation calls this function every time when it tries to traverse every single candidate. You can ignore arbitrary elements by returning `false`. -#### `ignoreInnerDimensionValidation` +#### `ignoreOffsetDimensionValidation` + Type: `'boolean'` + Default: `false` -When the library checks whether an element is navigable or not, it validates the inner width and height values to be greater than zero -and only then there're navigable. There're also cases where the inner width and height are zero but the element is still visible and therefore needs to be focusable. -In such cases it's possible to disable that validator for a particular section or globally. +When the library checks whether an element is navigable or not, it validates the offset width and height values to be greater than zero +and only then the element is navigable. There're also cases where the offset width and height are zero but the element is still visible and therefore needs to be focusable. +In such cases it's possible to disable the validation at all for a particular section or globally. ### Custom Attributes From 5a91bc4cb6f7da0c0adbfaad3bfc1637e5266fdb Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Wed, 1 Feb 2017 12:28:57 +0100 Subject: [PATCH 20/21] Update minified version of the library to the latest build --- dist/spatial_navigation.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/spatial_navigation.min.js b/dist/spatial_navigation.min.js index fdd7fdf..00d407b 100644 --- a/dist/spatial_navigation.min.js +++ b/dist/spatial_navigation.min.js @@ -6,4 +6,4 @@ * * @license Licensed under the MPL 2.0. */ -!function(a){"use strict";function b(a){var b=a.getBoundingClientRect(),c={left:b.left,top:b.top,right:b.right,bottom:b.bottom,width:b.width,height:b.height};return c.element=a,c.center={x:c.left+Math.floor(c.width/2),y:c.top+Math.floor(c.height/2)},c.center.left=c.center.right=c.center.x,c.center.top=c.center.bottom=c.center.y,c}function c(a,b,c){for(var d=[[],[],[],[],[],[],[],[],[]],e=0;e=b.left+b.width*k&&(0===h?d[1].push(i):6===h&&d[7].push(i)),i.top<=b.bottom-b.height*k&&(6===h?d[3].push(i):8===h&&d[5].push(i)),i.bottom>=b.top+b.height*k&&(0===h?d[3].push(i):2===h&&d[5].push(i))}}return d}function d(a){return{nearPlumbLineIsBetter:function(b){var c;return c=b.center.x=0:"object"==typeof c&&1===c.nodeType&&b===c}function j(){var a=document.activeElement;if(a&&a!==document.body)return a}function k(a){a=a||{};for(var b=1;b=0&&a.splice(c,1);return a}function m(a,b,c){if(!a||!b||!L[b]||L[b].disabled)return!1;var d="boolean"==typeof L[b].ignoreInnerDimensionValidation&&L[b].ignoreInnerDimensionValidation===!0||"boolean"==typeof D.ignoreInnerDimensionValidation&&D.ignoreInnerDimensionValidation===!0;if(!d&&a.offsetWidth<=0&&a.offsetHeight<=0||a.hasAttribute("disabled"))return!1;if(c&&!i(a,L[b].selector))return!1;if("function"==typeof L[b].navigableFilter){if(L[b].navigableFilter(a,b)===!1)return!1}else if("function"==typeof D.navigableFilter&&D.navigableFilter(a,b)===!1)return!1;return!0}function n(a){for(var b in L)if(!L[b].disabled&&i(a,L[b].selector))return b}function o(a){return h(L[a].selector).filter(function(b){return m(b,a)})}function p(b){var c=L[b].defaultElement;return c?("string"==typeof c?c=h(c)[0]:a&&c instanceof a&&(c=c.get(0)),m(c,b,!0)?c:null):null}function q(a){var b=L[a].lastFocusedElement;return m(b,a,!0)?b:null}function r(a,b,c,d){arguments.length<4&&(d=!0);var e=document.createEvent("CustomEvent");return e.initCustomEvent(G+b,!0,d,c),a.dispatchEvent(e)}function s(a,b,c){if(!a)return!1;var d=j(),e=function(){d&&d.blur(),a.focus(),t(a,b)};if(P)return e(),!0;if(P=!0,K)return e(),P=!1,!0;if(d){var f={nextElement:a,nextSectionId:b,direction:c,native:!1};if(!r(d,"willunfocus",f))return P=!1,!1;d.blur(),r(d,"unfocused",f,!1)}var g={previousElement:d,sectionId:b,direction:c,native:!1};return r(a,"willfocus",g)?(a.focus(),r(a,"focused",g,!1),P=!1,t(a,b),!0):(P=!1,!1)}function t(a,b){b||(b=n(a)),b&&(L[b].lastFocusedElement=a,O=b)}function u(a,b){if("@"==a.charAt(0)){if(1==a.length)return v();var c=a.substr(1);return v(c)}var d=h(a)[0];if(d){var e=n(d);if(m(d,e))return s(d,e,b)}return!1}function v(a){var b=[],c=function(a){a&&b.indexOf(a)<0&&L[a]&&!L[a].disabled&&b.push(a)};a?c(a):(c(N),c(O),Object.keys(L).map(c));for(var d=0;d=0},R={init:function(){J||(window.addEventListener("keydown",z),window.addEventListener("keyup",A),window.addEventListener("focus",B,!0),window.addEventListener("blur",C,!0),J=!0)},uninit:function(){window.removeEventListener("blur",C,!0),window.removeEventListener("focus",B,!0),window.removeEventListener("keyup",A),window.removeEventListener("keydown",z),R.clear(),I=0,J=!1},clear:function(){L={},M=0,N="",O="",P=!1},set:function(){var a,b;if("object"==typeof arguments[0])b=arguments[0];else{if("string"!=typeof arguments[0]||"object"!=typeof arguments[1])return;if(a=arguments[0],b=arguments[1],!L[a])throw new Error('Section "'+a+"\" doesn't exist!")}for(var c in b)void 0!==D[c]&&(a?L[a][c]=b[c]:void 0!==b[c]&&(D[c]=b[c]));a&&(L[a]=k({},L[a]))},add:function(){var a,b={};if("object"==typeof arguments[0]?b=arguments[0]:"string"==typeof arguments[0]&&"object"==typeof arguments[1]&&(a=arguments[0],b=arguments[1]),a||(a="string"==typeof b.id?b.id:g()),L[a])throw new Error('Section "'+a+'" has already existed!');return L[a]={},M++,R.set(a,b),a},remove:function(a){if(!a||"string"!=typeof a)throw new Error('Please assign the "sectionId"!');return!!L[a]&&(L[a]=void 0,L=k({},L),M--,!0)},disable:function(a){return!!L[a]&&(L[a].disabled=!0,!0)},enable:function(a){return!!L[a]&&(L[a].disabled=!1,!0)},pause:function(){K=!0},resume:function(){K=!1},focus:function(b,c){var d=!1;void 0===c&&"boolean"==typeof b&&(c=b,b=void 0);var e=!K&&c;if(e&&R.pause(),b)if("string"==typeof b)d=L[b]?v(b):u(b);else{a&&b instanceof a&&(b=b.get(0));var f=n(b);m(b,f)&&(d=s(b,f))}else d=v();return e&&R.resume(),d},move:function(a,b){if(a=a.toLowerCase(),!F[a])return!1;var c=b?h(b)[0]:j();if(!c)return!1;var d=n(c);if(!d)return!1;var e={direction:a,sectionId:d,cause:"api"};return!!r(c,"willmove",e)&&y(a,c,d)},makeFocusable:function(a){var b=function(a){var b=void 0!==a.tabIndexIgnoreList?a.tabIndexIgnoreList:D.tabIndexIgnoreList;h(a.selector).forEach(function(a){i(a,b)||a.getAttribute("tabindex")||a.setAttribute("tabindex","-1")})};if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");b(L[a])}else for(var c in L)b(L[c])},setDefaultSection:function(a){if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");N=a}else N=""}};window.SpatialNavigation=R,a&&(a.SpatialNavigation=function(){if(R.init(),arguments.length>0){if(a.isPlainObject(arguments[0]))return R.add(arguments[0]);if("string"===a.type(arguments[0])&&a.isFunction(R[arguments[0]]))return R[arguments[0]].apply(R,[].slice.call(arguments,1))}return a.extend({},R)},a.fn.SpatialNavigation=function(){var b;return b=a.isPlainObject(arguments[0])?arguments[0]:{id:arguments[0]},b.selector=this,R.init(),b.id&&R.remove(b.id),R.add(b),R.makeFocusable(b.id),this})}(window.jQuery); \ No newline at end of file +!function(a){"use strict";function b(a){var b=a.getBoundingClientRect(),c={left:b.left,top:b.top,right:b.right,bottom:b.bottom,width:b.width,height:b.height};return c.element=a,c.center={x:c.left+Math.floor(c.width/2),y:c.top+Math.floor(c.height/2)},c.center.left=c.center.right=c.center.x,c.center.top=c.center.bottom=c.center.y,c}function c(a,b,c){for(var d=[[],[],[],[],[],[],[],[],[]],e=0;e=b.left+b.width*k&&(0===h?d[1].push(i):6===h&&d[7].push(i)),i.top<=b.bottom-b.height*k&&(6===h?d[3].push(i):8===h&&d[5].push(i)),i.bottom>=b.top+b.height*k&&(0===h?d[3].push(i):2===h&&d[5].push(i))}}return d}function d(a){return{nearPlumbLineIsBetter:function(b){var c;return c=b.center.x=0:"object"==typeof c&&1===c.nodeType&&b===c}function j(){var a=document.activeElement;if(a&&a!==document.body)return a}function k(a){a=a||{};for(var b=1;b=0&&a.splice(c,1);return a}function m(a,b,c){if(!a||!b||!L[b]||L[b].disabled)return!1;var d="boolean"==typeof L[b].ignoreOffsetDimensionValidation&&L[b].ignoreOffsetDimensionValidation===!0||"boolean"==typeof D.ignoreOffsetDimensionValidation&&D.ignoreOffsetDimensionValidation===!0;if(!d&&a.offsetWidth<=0&&a.offsetHeight<=0||a.hasAttribute("disabled"))return!1;if(c&&!i(a,L[b].selector))return!1;if("function"==typeof L[b].navigableFilter){if(L[b].navigableFilter(a,b)===!1)return!1}else if("function"==typeof D.navigableFilter&&D.navigableFilter(a,b)===!1)return!1;return!0}function n(a){for(var b in L)if(!L[b].disabled&&i(a,L[b].selector))return b}function o(a){return h(L[a].selector).filter(function(b){return m(b,a)})}function p(b){var c=L[b].defaultElement;return c?("string"==typeof c?c=h(c)[0]:a&&c instanceof a&&(c=c.get(0)),m(c,b,!0)?c:null):null}function q(a){var b=L[a].lastFocusedElement;return m(b,a,!0)?b:null}function r(a,b,c,d){arguments.length<4&&(d=!0);var e=document.createEvent("CustomEvent");return e.initCustomEvent(G+b,!0,d,c),a.dispatchEvent(e)}function s(a,b,c){if(!a)return!1;var d=j(),e=function(){d&&d.blur(),a.focus(),t(a,b)};if(P)return e(),!0;if(P=!0,K)return e(),P=!1,!0;if(d){var f={nextElement:a,nextSectionId:b,direction:c,native:!1};if(!r(d,"willunfocus",f))return P=!1,!1;d.blur(),r(d,"unfocused",f,!1)}var g={previousElement:d,sectionId:b,direction:c,native:!1};return r(a,"willfocus",g)?(a.focus(),r(a,"focused",g,!1),P=!1,t(a,b),!0):(P=!1,!1)}function t(a,b){b||(b=n(a)),b&&(L[b].lastFocusedElement=a,O=b)}function u(a,b){if("@"==a.charAt(0)){if(1==a.length)return v();var c=a.substr(1);return v(c)}var d=h(a)[0];if(d){var e=n(d);if(m(d,e))return s(d,e,b)}return!1}function v(a){var b=[],c=function(a){a&&b.indexOf(a)<0&&L[a]&&!L[a].disabled&&b.push(a)};a?c(a):(c(N),c(O),Object.keys(L).map(c));for(var d=0;d=0},R={init:function(){J||(window.addEventListener("keydown",z),window.addEventListener("keyup",A),window.addEventListener("focus",B,!0),window.addEventListener("blur",C,!0),J=!0)},uninit:function(){window.removeEventListener("blur",C,!0),window.removeEventListener("focus",B,!0),window.removeEventListener("keyup",A),window.removeEventListener("keydown",z),R.clear(),I=0,J=!1},clear:function(){L={},M=0,N="",O="",P=!1},set:function(){var a,b;if("object"==typeof arguments[0])b=arguments[0];else{if("string"!=typeof arguments[0]||"object"!=typeof arguments[1])return;if(a=arguments[0],b=arguments[1],!L[a])throw new Error('Section "'+a+"\" doesn't exist!")}for(var c in b)void 0!==D[c]&&(a?L[a][c]=b[c]:void 0!==b[c]&&(D[c]=b[c]));a&&(L[a]=k({},L[a]))},add:function(){var a,b={};if("object"==typeof arguments[0]?b=arguments[0]:"string"==typeof arguments[0]&&"object"==typeof arguments[1]&&(a=arguments[0],b=arguments[1]),a||(a="string"==typeof b.id?b.id:g()),L[a])throw new Error('Section "'+a+'" has already existed!');return L[a]={},M++,R.set(a,b),a},remove:function(a){if(!a||"string"!=typeof a)throw new Error('Please assign the "sectionId"!');return!!L[a]&&(L[a]=void 0,L=k({},L),M--,!0)},disable:function(a){return!!L[a]&&(L[a].disabled=!0,!0)},enable:function(a){return!!L[a]&&(L[a].disabled=!1,!0)},pause:function(){K=!0},resume:function(){K=!1},focus:function(b,c){var d=!1;void 0===c&&"boolean"==typeof b&&(c=b,b=void 0);var e=!K&&c;if(e&&R.pause(),b)if("string"==typeof b)d=L[b]?v(b):u(b);else{a&&b instanceof a&&(b=b.get(0));var f=n(b);m(b,f)&&(d=s(b,f))}else d=v();return e&&R.resume(),d},move:function(a,b){if(a=a.toLowerCase(),!F[a])return!1;var c=b?h(b)[0]:j();if(!c)return!1;var d=n(c);if(!d)return!1;var e={direction:a,sectionId:d,cause:"api"};return!!r(c,"willmove",e)&&y(a,c,d)},makeFocusable:function(a){var b=function(a){var b=void 0!==a.tabIndexIgnoreList?a.tabIndexIgnoreList:D.tabIndexIgnoreList;h(a.selector).forEach(function(a){i(a,b)||a.getAttribute("tabindex")||a.setAttribute("tabindex","-1")})};if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");b(L[a])}else for(var c in L)b(L[c])},setDefaultSection:function(a){if(a){if(!L[a])throw new Error('Section "'+a+"\" doesn't exist!");N=a}else N=""}};window.SpatialNavigation=R,a&&(a.SpatialNavigation=function(){if(R.init(),arguments.length>0){if(a.isPlainObject(arguments[0]))return R.add(arguments[0]);if("string"===a.type(arguments[0])&&a.isFunction(R[arguments[0]]))return R[arguments[0]].apply(R,[].slice.call(arguments,1))}return a.extend({},R)},a.fn.SpatialNavigation=function(){var b;return b=a.isPlainObject(arguments[0])?arguments[0]:{id:arguments[0]},b.selector=this,R.init(),b.id&&R.remove(b.id),R.add(b),R.makeFocusable(b.id),this})}(window.jQuery); \ No newline at end of file From e13da4661676123d55c8ab323948c824eab0eeed Mon Sep 17 00:00:00 2001 From: Andy Liebke Date: Mon, 6 Feb 2017 17:04:41 +0100 Subject: [PATCH 21/21] Add test case file for the offset dimensions offset issue A simple page that shows the different cases where the offset validation works and when not and how to fix it by using the new option or with CSS as an alternative way. --- test/offset-dimensions-test.html | 84 ++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 test/offset-dimensions-test.html diff --git a/test/offset-dimensions-test.html b/test/offset-dimensions-test.html new file mode 100644 index 0000000..eceb8ca --- /dev/null +++ b/test/offset-dimensions-test.html @@ -0,0 +1,84 @@ + + + + + + Spatial Navigation - Offset Dimensions Test + + + + + +
+

Spatial Navigation - Offset Dimensions Test

+ + +
+ + + + + \ No newline at end of file