├── .DS_Store ├── .github ├── ISSUE_TEMPLATE └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .npmignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── blink_impl ├── heuristic_default_move.html ├── heuristic_scrollable.html ├── list.md ├── style.css └── testcase_style.css ├── demo ├── .DS_Store ├── blog │ ├── blog-style.css │ ├── blog-utils.js │ ├── images │ │ ├── feed │ │ │ ├── 1.jpg │ │ │ ├── 10.jpg │ │ │ ├── 11.jpg │ │ │ ├── 12.jpg │ │ │ ├── 13.jpg │ │ │ ├── 14.jpg │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ ├── 5.jpg │ │ │ ├── 6.jpg │ │ │ ├── 7.jpg │ │ │ ├── 8.jpg │ │ │ └── 9.jpg │ │ └── profile.png │ ├── index.html │ └── thumbnail.png ├── calendar │ ├── images │ │ └── pattern.png │ ├── index.html │ ├── style.css │ ├── thumbnail.png │ └── thumbnail_.png ├── favicon.png ├── index.html ├── infinite-scroller │ ├── index.html │ ├── scroller-style.css │ └── scroller-utils.js ├── link.png ├── mark.png ├── sample │ ├── api_focusableAreas.html │ ├── api_getSpatialNavigationContainer.html │ ├── api_navigate.html │ ├── api_navigate_event.html │ ├── api_prevent_navbeforefocus.html │ ├── api_prevent_navnotarget.html │ ├── api_spatialNavigationSearch.html │ ├── api_spatial_navigation_action.html │ ├── api_spatial_navigation_contain.html │ ├── api_spatial_navigation_empty_nested_container.html │ ├── api_spatial_navigation_function.html │ ├── api_spatial_navigation_nested_container.html │ ├── heurisitic_candidates_same_distance.html │ ├── heuristic_default_move.html │ ├── heuristic_distance_function_grid_001.html │ ├── heuristic_distance_function_grid_002.html │ ├── heuristic_distance_function_grid_003.html │ ├── heuristic_drawing_board.html │ ├── heuristic_grid_layout.html │ ├── heuristic_iframe.html │ ├── heuristic_iframe_sub.html │ ├── heuristic_inert_attribute.html │ ├── heuristic_input_elements.html │ ├── heuristic_non_scrollable_iframe.html │ ├── heuristic_non_scrollable_iframe_element.html │ ├── heuristic_overflow_regions.html │ ├── heuristic_overlapped_elements.html │ ├── heuristic_role_attribute.html │ ├── heuristic_scrollable.html │ ├── heuristic_scrollable_test.html │ ├── heuristic_starting_point.html │ ├── heuristic_text_type_elements.html │ ├── heuristic_textarea.html │ ├── images │ │ └── login_img.png │ ├── sample_focusout_event_order.html │ ├── sample_shadowDOM_SpatNav.html │ ├── sample_shadowDOM_SpatNav_iframe.html │ ├── spatnav-style.css │ └── spatnav-utils.js └── style.css ├── docs ├── Spatial Navigation in the Web (LG Electronics).pdf ├── images_of_spec.pptx ├── spatialNavigationSearch_explainer.md └── spatnav_processing_model_diagram.xml ├── images ├── calender1.png ├── calender2.png ├── exit-entry-point.JPG ├── nav-rule-example.png ├── patterns-navigational-transitions-parent-to-child-calendar-tablet-xhdpi-004.webm ├── pinterest.png └── scroll related terms.png ├── implStatus.md ├── package-lock.json ├── package.json ├── polyfill ├── LICENSE ├── README.md ├── changelog.md └── spatial-navigation-polyfill.js ├── resources ├── testharness.css ├── testharness.js └── testharnessreport.js ├── tests ├── README.md ├── internal │ ├── api-test.html │ ├── calendar.css │ ├── event-test.html │ ├── focusable-test.html │ ├── hostile_iframe1.html │ ├── hostile_iframe2.html │ ├── hostile_iframe3.html │ ├── hostile_iframe_test.html │ ├── iframe-offscreen-test.html │ ├── iframe-test.html │ ├── iframe_sub.html │ ├── iframe_sub2.html │ ├── isVisibleInScroller-test.html │ ├── out-of-viewport.html │ ├── overlapped-focusable-test.html │ ├── scrollable-container-test.html │ ├── search-origin-offscreen-test.html │ ├── search-origin-vanish-test.html │ ├── spatial-navigation-action-test.html │ ├── spatial-navigation-function-test.html │ ├── spatialNavSearch-test.html │ ├── test.css │ └── test.js ├── spatnav-focus-001.html ├── spatnav-scroll-001.html └── ux │ ├── README.md │ ├── list.md │ ├── result.md │ ├── spatnav-algorithm-test.js │ ├── spatnav-distance-function-fragments-001.html │ ├── spatnav-distance-function-grid-001.html │ ├── spatnav-distance-function-grid-002.html │ ├── spatnav-distance-function-grid-003.html │ ├── spatnav-distance-function-grid-004.html │ ├── spatnav-distance-function-grid-align-001.html │ ├── spatnav-distance-function-grid-align-002.html │ ├── spatnav-distance-function-grid-align-003.html │ ├── spatnav-distance-function-grid-align-004.html │ ├── spatnav-distance-function-intersected-001.html │ ├── spatnav-distance-function-intersected-002.html │ ├── spatnav-distance-function-transformed-001.html │ ├── spatnav-drowing-board.html │ └── spatnav-simple-test.html ├── tools └── chrome-extension │ ├── LICENSE │ ├── README.md │ ├── icon.png │ ├── img1.png │ ├── img2.gif │ ├── img3.png │ ├── manifest.json │ ├── package-lock.json │ ├── package.json │ ├── polyfill │ └── spatial-navigation-polyfill.js │ └── src │ ├── .eslintrc.js │ ├── Panel.html │ ├── background.js │ ├── content_script.css │ ├── content_script.js │ ├── detector │ ├── detector.js │ ├── graph.js │ ├── isolation_elements_detector.js │ ├── loop_elements_detector.js │ ├── trap_elements_detector.js │ └── unreachable_elements_detector.js │ ├── devtool.html │ ├── devtool.js │ ├── panel.css │ ├── panel.js │ ├── popup.css │ ├── popup.html │ └── popup.js └── w3c.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | The specification has been moved to https://drafts.csswg.org/css-nav-1/ 2 | If you want to report issues about the specification, please report them at: 3 | https://github.com/w3c/csswg-drafts/labels/css-nav-1 4 | 5 | Issues about the polyfill, or tests, or demos can be posted here. 6 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '35 6 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | blink_impl 2 | docs 3 | dist 4 | node_modules 5 | .gitignore 6 | .eslintrc.js 7 | .eslintrc.yml 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .npmignore 3 | .gitignore 4 | .eslintrc.js 5 | .eslintrc.yml 6 | implStatus.md 7 | CODE_OF_CONDUCT.md 8 | CONTRIBUTING.md 9 | LICENSE.md 10 | package-lock.json 11 | blink_impl/ 12 | docs/ 13 | images/ 14 | resources/ 15 | tests/ 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | As a W3C Community Group, the WICG operates under the W3C's [Code of Ethics and Professional Conduct](http://www.w3.org/Consortium/cepc/). 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Web Platform Incubator Community Group 2 | 3 | This repository is being used for work in the W3C Web Platform Incubator 4 | Community Group, governed by the [W3C Community License Agreement 5 | (CLA)](http://www.w3.org/community/about/agreements/cla/). To make substantive 6 | contributions, you must join the CG. 7 | 8 | If you are not the sole contributor to a contribution (pull request), please 9 | identify all contributors in the pull request comment. 10 | 11 | To add a contributor (other than yourself, that's automatic), mark them one per 12 | line as follows: 13 | 14 | ``` 15 | +@github_username 16 | ``` 17 | 18 | If you added a contributor by mistake, you can remove them in a comment with: 19 | 20 | ``` 21 | -@github_username 22 | ``` 23 | 24 | If you are making a pull request on behalf of someone else but you had no part 25 | in designing the feature, you can remove yourself with the above syntax. 26 | 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors under the 2 | [W3C Software and Document License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). 3 | 4 | Contributions to Specifications are made under the 5 | [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 6 | 7 | -------------------------------------------------------------------------------- /blink_impl/heuristic_default_move.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 |

Heuristic Spatial Navigation Test Cases

16 |
17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 41 |
42 | 43 | 44 | 45 | 46 |
47 | 48 |
49 | 50 | 51 |
52 | 53 |
54 | 55 | 56 |
57 | 58 |
59 | 60 | 61 |
62 | 63 |
64 | 65 | 66 | 67 |
68 | 69 |
70 | 71 |
72 | Editable Box 73 |
74 | 75 | 76 |
77 | 78 |
79 | 80 | 81 | 82 | 83 |
84 | 85 |
86 |
87 | 88 |
89 | 90 |
91 |
92 | 93 | 94 | -------------------------------------------------------------------------------- /blink_impl/heuristic_scrollable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 |

Spatial Navigation Test Cases

14 |
15 |

Scrollable region

16 | 17 |

18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |

26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /blink_impl/list.md: -------------------------------------------------------------------------------- 1 | ## Real page lists 2 | 3 | * [Amazon](https://www.amazon.com) 4 | * [MDN Docs](https://developer.mozilla.org/en-US/docs/Web) 5 | * [Youtube](https://www.youtube.com/) 6 | -------------------------------------------------------------------------------- /blink_impl/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Saira'); 2 | 3 | h1 { 4 | text-align: center; 5 | font-family: 'Saira', sans-serif; 6 | } 7 | h1:after { 8 | background: #F67280; 9 | content: ''; 10 | display: block; 11 | height: 6px; 12 | margin: 30px; 13 | } 14 | 15 | h2 { 16 | font-family: 'Saira', sans-serif; 17 | } 18 | 19 | .examples { 20 | margin: 30px; 21 | } 22 | 23 | .elm { 24 | position: relative; 25 | width: 50px; 26 | height: 50px; 27 | border: 1px #000 solid; 28 | } 29 | .elm:focus { 30 | box-sizing: border-box; 31 | border: 3px #F73 solid; 32 | } 33 | 34 | .scroll { 35 | position: relative; 36 | border: 1px #888 solid; 37 | } 38 | 39 | .content { 40 | display: block; 41 | margin: 0 auto; 42 | width: 70%; 43 | height: 100%; 44 | } 45 | 46 | .root { 47 | position: relative; 48 | margin: 30px; 49 | height: 200px; 50 | width: 1000px; 51 | background-color: #A8E6CE; 52 | } 53 | 54 | .unreachable { 55 | position: relative; 56 | margin: 30px; 57 | height: 300px; 58 | width: 1000px; 59 | background-color: #CC9E9F; 60 | } 61 | 62 | .scrollarea { 63 | position: relative; 64 | height: 150px; 65 | width: 1000px; 66 | background-color: #FC9D9A; 67 | } 68 | 69 | .tricky{ 70 | position: relative; 71 | margin: 30px; 72 | height: 300px; 73 | width: 1000px; 74 | background-color: #FC9D9A; 75 | } 76 | 77 | .group { 78 | height: 800px; 79 | } 80 | 81 | button { 82 | position: absolute; 83 | margin: 10px; 84 | width: 50px; 85 | height: 50px; 86 | border-radius: 0.5em; 87 | font-size: 20px; 88 | font-family: 'Saira', sans-serif; 89 | text-align: center; 90 | border: 1px solid black; 91 | box-shadow: 2px 2px gray; 92 | } 93 | 94 | button:focus, .box:focus{ 95 | background-color: #9457EB; 96 | color: #fff; 97 | outline: none; 98 | } 99 | 100 | .box { 101 | position: absolute; 102 | margin: 10px; 103 | width: 150px; 104 | height: 70px; 105 | border-radius: 0.5em; 106 | font-size: 20px; 107 | font-family: 'Saira', sans-serif; 108 | text-align: center; 109 | } 110 | -------------------------------------------------------------------------------- /blink_impl/testcase_style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Saira'); 2 | 3 | h1 { 4 | text-align: center; 5 | font-family: 'Saira', sans-serif; 6 | } 7 | h1:after { 8 | background: #F67280; 9 | content: ''; 10 | display: block; 11 | height: 6px; 12 | margin: 30px; 13 | } 14 | 15 | .content { 16 | display: block; 17 | margin: 0 auto; 18 | width: 70%; 19 | height: 100%; 20 | } 21 | 22 | .root { 23 | position: relative; 24 | margin: 30px; 25 | height: 200px; 26 | width: 1000px; 27 | background-color: #A8E6CE; 28 | } 29 | 30 | .unreachable { 31 | position: relative; 32 | margin: 30px; 33 | height: 300px; 34 | width: 1000px; 35 | background-color: #CC9E9F; 36 | } 37 | 38 | .scrollarea { 39 | position: relative; 40 | height: 150px; 41 | width: 1000px; 42 | background-color: #FC9D9A; 43 | } 44 | 45 | .tricky{ 46 | position: relative; 47 | margin: 30px; 48 | height: 300px; 49 | width: 1000px; 50 | background-color: #FC9D9A; 51 | } 52 | 53 | .group { 54 | height: 800px; 55 | } 56 | 57 | button { 58 | position: absolute; 59 | margin: 10px; 60 | width: 50px; 61 | height: 50px; 62 | border-radius: 0.5em; 63 | font-size: 20px; 64 | font-family: 'Saira', sans-serif; 65 | text-align: center; 66 | border: 1px solid black; 67 | box-shadow: 2px 2px gray; 68 | } 69 | 70 | button:focus, .box:focus{ 71 | background-color: #9457EB; 72 | color: #fff; 73 | outline: none; 74 | } 75 | 76 | .box { 77 | position: absolute; 78 | margin: 10px; 79 | width: 150px; 80 | height: 70px; 81 | border-radius: 0.5em; 82 | font-size: 20px; 83 | font-family: 'Saira', sans-serif; 84 | text-align: center; 85 | } -------------------------------------------------------------------------------- /demo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/.DS_Store -------------------------------------------------------------------------------- /demo/blog/blog-utils.js: -------------------------------------------------------------------------------- 1 | /* JS Lib for Blog Demo 2 | * 3 | * Copyright 2018 LG Electronics Inc. All rights reserved. 4 | * 5 | */ 6 | function swapTabContents(evt, tabName) { 7 | // Declare all variables 8 | var i, tabcontent, tablink; 9 | 10 | // Get all elements with class="tabcontent" and hide them 11 | tabcontent = document.getElementsByClassName("tabcontent"); 12 | for (i = 0; i < tabcontent.length; i++) { 13 | tabcontent[i].style.display = "none"; 14 | 15 | } 16 | 17 | // Get all elements with class="tablink" and remove the class "active" 18 | tablink = document.getElementsByClassName("tablink"); 19 | for (i = 0; i < tablink.length; i++) { 20 | tablink[i].children[0].style.fontWeight = ""; 21 | } 22 | 23 | // Show the current tab, and add an "active" class to the button that opened the tab 24 | document.getElementById(tabName).style.display = "block"; 25 | } 26 | -------------------------------------------------------------------------------- /demo/blog/images/feed/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/1.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/10.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/11.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/12.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/13.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/14.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/2.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/3.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/4.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/5.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/6.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/7.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/8.jpg -------------------------------------------------------------------------------- /demo/blog/images/feed/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/feed/9.jpg -------------------------------------------------------------------------------- /demo/blog/images/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/images/profile.png -------------------------------------------------------------------------------- /demo/blog/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/blog/thumbnail.png -------------------------------------------------------------------------------- /demo/calendar/images/pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/calendar/images/pattern.png -------------------------------------------------------------------------------- /demo/calendar/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/calendar/thumbnail.png -------------------------------------------------------------------------------- /demo/calendar/thumbnail_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/calendar/thumbnail_.png -------------------------------------------------------------------------------- /demo/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/favicon.png -------------------------------------------------------------------------------- /demo/infinite-scroller/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | Infinite Scroller Demo 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Infinite Scroller in Spatial Navigation

18 |
19 |

Demo

20 |

This demo shows the infinite scroll in Spatial Navigation.

21 |
22 | 23 |
24 |
25 |
26 |

Initial number of elements :

27 |

Current number of elements :

28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 | 37 |
38 | 39 | 40 |
41 |

42 | Related Github Issue 43 |

44 | 45 | 82 | 83 | -------------------------------------------------------------------------------- /demo/infinite-scroller/scroller-style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(https://themes.googleusercontent.com/static/fonts/roboto/v11/2UX7WLTfW3W8TclTUvlFyQ.woff) 6 | } 7 | 8 | body { 9 | margin: 20px; 10 | font-family: 'Roboto'; 11 | text-align: center; 12 | } 13 | 14 | h1 { 15 | text-align: center; 16 | } 17 | 18 | .description, .option { 19 | margin: 20px auto; 20 | text-align: justify; 21 | } 22 | .description { 23 | width: 80%; 24 | } 25 | .option { 26 | width: 750px; 27 | } 28 | 29 | .cssSyntax { 30 | background-color: lightsteelblue; 31 | border: 2px solid steelblue; 32 | padding: 20px; 33 | width: 500px; 34 | } 35 | 36 | #feed { 37 | display: grid; 38 | overflow-y: scroll; 39 | grid-template-columns: repeat(2, 1fr); 40 | background-color: #fff; 41 | color: #444; 42 | height: 500px; 43 | width: 500px; 44 | margin: auto auto; 45 | border: 3px solid lightgrey; 46 | } 47 | 48 | .item { 49 | height: 150px; 50 | width: 150px; 51 | margin: 60px 20px; 52 | justify-self: center; 53 | background-color: mediumseagreen; 54 | } 55 | 56 | .item p { 57 | vertical-align: middle; 58 | font-size: 36px; 59 | } 60 | 61 | :focus { 62 | outline: 5px solid #428bca; 63 | } 64 | -------------------------------------------------------------------------------- /demo/infinite-scroller/scroller-utils.js: -------------------------------------------------------------------------------- 1 | function addBoxes(root, amount, dir) { 2 | const cnt = root.childElementCount; 3 | let initCnt = 0; 4 | let endCnt = 0; 5 | 6 | if (cnt) { 7 | initCnt = parseInt(root.firstChild.innerText); 8 | endCnt = parseInt(root.lastChild.innerText); 9 | } 10 | 11 | for (let i = 1; i <= amount; i++) { 12 | let temp = document.createElement('div'); 13 | temp.setAttribute('class', 'item'); 14 | temp.setAttribute('tabindex', 0); 15 | temp.style.setProperty('--spatial-navigation-action', 'focus'); 16 | let para = document.createElement('p'); 17 | temp.appendChild(para); 18 | 19 | if (dir === 'up') { 20 | para.appendChild(document.createTextNode(`${initCnt - i}`)); 21 | root.prepend(temp); 22 | } else { 23 | para.appendChild(document.createTextNode(`${endCnt + i}`)); 24 | root.append(temp); 25 | } 26 | } 27 | 28 | return new Promise(function (resolve) { 29 | resolve(cnt + amount); 30 | }); 31 | } 32 | 33 | function updateBoxes(root, amount) { 34 | const childNum = root.childElementCount; 35 | 36 | if (childNum > amount) { 37 | for (let i = 0; i < childNum - amount; i++) { 38 | root.removeChild(root.lastChild); 39 | } 40 | } 41 | else { 42 | addBoxes(root, amount - childNum); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /demo/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/link.png -------------------------------------------------------------------------------- /demo/mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/mark.png -------------------------------------------------------------------------------- /demo/sample/api_focusableAreas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 56 | 57 | 58 |
59 |

60 | spatnav container 1(Document) > 61 | spatnav container 2 > 62 | spatnav container 3 63 |

64 | 65 | 66 |
67 | 68 |
69 | 70 | 71 |
72 | 73 | 74 | 75 | 76 |
77 | 78 | 79 |
80 | 81 |
82 | focusableAreasOptions : 83 | 87 |
88 |
89 |       
90 |     
91 | 92 | 93 | -------------------------------------------------------------------------------- /demo/sample/api_getSpatialNavigationContainer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 43 | 44 | 45 |
46 |

47 | spatnav container 1(Document) > 48 | spatnav container 2 > 49 | spatnav container 3 50 |

51 | 52 |
53 | 54 |
55 | 56 | 57 |
58 | 59 | 60 |
61 | 62 | 63 |
64 |
65 |       
66 |         focusable.addEventListener('focus', function(e) {
67 |           let spatnavContainer = e.target.getSpatialNavigationContainer();
68 |           spatnavContainer.style.background = '#F0808099';
69 |           spatnavContainer.style.outline = '5px red solid';
70 |           console.log(spatnavContainer);
71 |         });
72 |     
73 |   
74 | 75 | 76 | -------------------------------------------------------------------------------- /demo/sample/api_navigate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 32 | 33 | 34 |
35 |

36 | spatnav container 1(Document) > 37 | spatnav container 2 > 38 | spatnav container 3 39 |

40 | 41 | 42 |
43 | 44 |
45 | 46 | 47 |
48 | 49 | 50 |
51 | 52 | 53 |
54 | 55 |
56 |     
57 |       const redContainer = document.getElementsByClassName("container c1")[0];
58 |       redContainer.addEventListener('keydown', function(e) {
59 |         const dir = WASD_KEY_CODE[e.keyCode];
60 |         if(window.navigate && dir) {
61 |           window.navigate(dir);
62 |           e.preventDefault();
63 |         }
64 |       }, true);
65 |     
66 |   
67 | 68 | 69 | -------------------------------------------------------------------------------- /demo/sample/api_navigate_event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 | Document > 21 | Scroll container > 22 | Scroll container > 23 | Scroll container 24 |

25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 | 73 | 74 | -------------------------------------------------------------------------------- /demo/sample/api_prevent_navbeforefocus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 |
30 | 31 | 66 | 67 | -------------------------------------------------------------------------------- /demo/sample/api_prevent_navnotarget.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 |
29 | 30 | 66 | 67 | -------------------------------------------------------------------------------- /demo/sample/api_spatial_navigation_action.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | Option 17 |
18 | Initial number of elements: 19 | 20 | spatial-navigation-action : 21 | 26 | 27 |
28 |
29 |
30 |
31 | 32 | 33 |
34 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut 35 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 36 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit 37 | esse cillum dolore eu fugiat nulla pariatur.

38 |
39 | 40 | 41 |
42 | 43 | 76 | 77 | -------------------------------------------------------------------------------- /demo/sample/api_spatial_navigation_contain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | Document 18 | Scroll container 19 | Container with 'spatial-navigation-contain: contain' 20 |

21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /demo/sample/api_spatial_navigation_empty_nested_container.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

18 | Document > 19 | Scroll container > 20 | Scroll container > 21 | Scroll container 22 |

23 | 24 |
25 |
26 |
27 | Lorem ipsum dolor sit amet, consectetur adipiscing elit.... 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /demo/sample/api_spatial_navigation_function.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 86 | 87 |
88 |
89 | Option 90 |
91 | 92 | spatial-navigation-function : 93 | 97 | 98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | 114 | 121 | 122 | -------------------------------------------------------------------------------- /demo/sample/api_spatial_navigation_nested_container.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | Document > 18 | Scroll container > 19 | Scroll container > 20 | Scroll container 21 |

22 | 23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /demo/sample/heurisitic_candidates_same_distance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | -------------------------------------------------------------------------------- /demo/sample/heuristic_default_move.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 |
36 |
37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /demo/sample/heuristic_distance_function_grid_001.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 23 | 24 | 25 |
26 |
27 |
28 |
Initial Focus
29 |
30 |
31 |
non-focusable
32 |
33 |
34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /demo/sample/heuristic_distance_function_grid_002.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 127 | 128 | 129 |
130 |
131 | 132 | 133 | 134 | 135 | 136 |
137 | 138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /demo/sample/heuristic_distance_function_grid_003.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | 30 | 31 | 32 |
33 |
34 |
Box 1
35 |
Box 2
36 |
37 | 38 | -------------------------------------------------------------------------------- /demo/sample/heuristic_grid_layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /demo/sample/heuristic_iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /demo/sample/heuristic_iframe_sub.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/sample/heuristic_inert_attribute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | Check this to edit the forms below: 18 | 19 |

20 |
21 |
22 | radio1 23 | radio2 24 | radio3 25 |

26 | 30 |
31 |
32 | 33 | 54 | 55 | -------------------------------------------------------------------------------- /demo/sample/heuristic_input_elements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

button

18 |

checkbox

19 |
20 | checkbox1 21 | checkbox2 22 | checkbox3 23 |
24 | checkbox4 25 | checkbox5 26 | checkbox6 27 |
28 |
29 |

color

30 |

file

31 |

password

32 |
33 |

radio

34 |
35 | radio1 36 | radio2 37 | radio3 38 |
39 | radio4 40 | radio5 41 | radio6 42 |
43 |
44 |

reset

45 |

submit

46 |

text

47 |

text area

48 |

search

49 |

tel

50 |

url

51 |

hidden

52 |

date

53 |

email

54 |

image

55 |

month

56 |

number

57 |

range

58 |

time

59 |

week

60 |

datalist

61 |
62 | 63 | 64 | 70 |
71 |
72 |

select

73 | 80 |
81 |

optgroup

82 | 92 |
93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /demo/sample/heuristic_non_scrollable_iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /demo/sample/heuristic_non_scrollable_iframe_element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /demo/sample/heuristic_overflow_regions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | overflow-y: scroll 19 | 20 | 21 |
22 | 23 |
24 | overflow-y: visible 25 | 26 | 27 |
28 | 29 |
30 | overflow-y: hidden 31 | 32 | 33 |
34 | 35 |
36 | overflow-y: scroll, offscreen test 37 | 38 | 39 |
40 |
41 | Can't scroll up back when container DIV tabIndex is less than 0 42 |
43 | 44 |
45 | 46 |
47 | overflow-y: scroll
48 |
49 | overflow: unset 50 | 51 | 52 |
53 |
54 | 55 |
56 | overflow-y: scroll
57 |

58 | <p> tag container
overflow: unset 59 | 60 | 61 |

62 | 63 |
64 | 65 |

This is a paragraph tabIndex=1.
This is a paragraph tabIndex=1.
This is a paragraph tabIndex=1.
This is a paragraph tabIndex=1.
This is a paragraph tabIndex=1.
This is a paragraph tabIndex=1.
This is a paragraph tabIndex=1.
This is a paragraph tabIndex=1.

66 | 67 |

Can't scroll when scrollable area is tabIndex<0
This is a paragraph no tabIndex.
This is a paragraph no tabIndex.
This is a paragraph no tabIndex.
This is a paragraph no tabIndex.
This is a paragraph no tabIndex.
This is a paragraph no tabIndex.
This is a paragraph no tabIndex.
This is a paragraph no tabIndex.
This is a paragraph no tabIndex.

68 |
69 |
Can't scroll up
This is a DIV tabIndex=1 without delegation.
This is a DIV tabIndex=1 without delegation.
This is a DIV tabIndex=1 without delegation.
This is a DIV tabIndex=1 without delegation.
This is a DIV tabIndex=1 without delegation.
This is a DIV tabIndex=1 without delegation.
70 | 71 |
Can't scroll when scrollable area is tabIndex<0
This is a DIV no tabIndex, no delegation.
This is a DIV no tabIndex, no delegation.
This is a DIV no tabIndex, no delegation.
This is a DIV no tabIndex, no delegation.
This is a DIV no tabIndex, no delegation.
This is a DIV no tabIndex, no delegation.
This is a DIV no tabIndex, no delegation.
72 | 73 | 74 | -------------------------------------------------------------------------------- /demo/sample/heuristic_overlapped_elements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | Completely embedded elements can not be reached with the arrow keys. 18 |
19 |
Near element
20 |
Unreachable element
21 |

22 | 23 | Among the candidates of the same distance, the smallest height is the first. 24 |
25 |
Second order by 'down key'
First order by 'up key'
26 |
First order by 'down key'
Unreachable by 'up key'
27 |
28 | 29 |

30 |
31 |
32 |
33 |
34 | 35 |

36 | 37 |
38 |
39 |
40 |
41 |





42 |

43 | 44 | -------------------------------------------------------------------------------- /demo/sample/heuristic_role_attribute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 |

Nils Frahm is a German musician, composer and record producer based in Berlin. He is known for combining classical and electronic music and for an unconventional approach to the piano in which he mixes a grand piano, upright piano, Roland Juno-60, Rhodes piano, drum machine, and Moog Taurus.

23 |
24 | 25 | 28 | 29 | 33 |
34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /demo/sample/heuristic_scrollable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/sample/heuristic_scrollable_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/sample/heuristic_starting_point.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 48 | 49 | 65 | 66 | 67 |
68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /demo/sample/heuristic_text_type_elements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

Spatial Navigation

19 |
20 | This is a repository for making the Web excellently embrace the 21 | spatial navigation's features so that 22 | the Web technology can be propagated into several industries such as TV, IVI, game console, 23 | and upcoming smart devices as well as PC and mobile for a11y. 24 | 25 |

Details

26 |
    27 |
  1. Read the Explainer
  2. 28 |
  3. Read the Spec
  4. 29 |
  5. See the Implementation Status
  6. 30 |
  7. Try the Demo
  8. 31 |
  9. Give feedback on issues
  10. 32 |
33 | 34 |

Overview

35 | 36 | The spatial navigation is the ability to navigate between focusable elements based on their position 37 | within a structured document. Spatial navigation is often called 'directional navigation' 38 | which enables four(4) directional navigation. Users are usually familiar with the 2-way navigation 39 | using tab key for the forward direction and shift+tab key for the backward direction, 40 | but not familiar with the 4-way navigation using arrow keys. 41 | Regarding TV remote control, game console pad, IVI jog dial with 4-way keys, and 42 | Web accessibility, the spatial navigation has been a rising important input mechanism in several industries. 43 | If the Web can embrace the spatial navigation and efficiently support the functionalities in Web engines and W3C APIs, 44 | it will be more promising technology for existing products as mentioned above and various upcoming smart devices. 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /demo/sample/heuristic_textarea.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

text area

18 |

text area

19 |

tel

20 |

url

21 |

hidden

22 |

date

23 |

email

24 |

image

25 |

month

26 |

number

27 |

range

28 |

time

29 |

week

30 |

datalist

31 |
32 | 33 | 34 | 40 |
41 |
42 |

select

43 | 50 |
51 |

optgroup

52 | 62 |
63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /demo/sample/images/login_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/demo/sample/images/login_img.png -------------------------------------------------------------------------------- /demo/sample/sample_focusout_event_order.html: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 |
Instruction: Click the button 1 and press the tab key or the right arrow key.
29 |
button1 and 2 are the children of the following red container, but button3 is not.
30 |
31 | 32 | 33 |
34 | 35 |
36 |
If you want to test it via SpatNav, you need to enable a runtime flag (chrome.exe --enable-spatial-navigation).
37 | 38 | 76 | -------------------------------------------------------------------------------- /demo/sample/sample_shadowDOM_SpatNav.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
document.activeElement =

5 | 1. General
6 |
7 |
2. ShadowDOM
8 |
9 |
3. General
10 |
11 |
4. Iframe
12 |
13 |
5. General
14 |
15 | 16 | 35 | -------------------------------------------------------------------------------- /demo/sample/sample_shadowDOM_SpatNav_iframe.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /demo/sample/spatnav-utils.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("load", function() { 2 | let list = document.querySelectorAll("meta"); 3 | let application_name, application_author, application_description; 4 | // get the data such as name, author, and description 5 | for (let item of list) { 6 | if (item.name === "application-name") { 7 | application_name = item.content; 8 | } else if (item.name === "author") { 9 | application_author = item.content; 10 | } else if (item.name === "description") { 11 | application_description = item.content; 12 | } 13 | } 14 | if (typeof application_name !== "undefined") { 15 | // show the information panel 16 | let info_panel = document.createElement("div"); 17 | info_panel.id = "info_panel"; 18 | info_panel.innerHTML = application_name; 19 | // show the application author panel 20 | if (typeof application_author !== "undefined") { 21 | let author_panel = document.createElement("div"); 22 | author_panel.id = "author_panel"; 23 | author_panel.innerHTML = "written by " + application_author; 24 | info_panel.appendChild(author_panel); 25 | } 26 | // show the application description panel 27 | if (typeof application_description !== "undefined") { 28 | let description_panel = document.createElement("div"); 29 | description_panel.id = "description_panel"; 30 | description_panel.innerHTML = application_description; 31 | info_panel.appendChild(description_panel); 32 | } 33 | // show the folder figure 34 | let folder_btn = document.createElement("div"); 35 | folder_btn.id = "folder_btn"; 36 | folder_btn.innerText = ">"; 37 | folder_btn.addEventListener("click", function() { 38 | if (folder_btn.innerText === ">") { 39 | folder_btn.innerText = "<"; 40 | info_panel.setAttribute("style", "display: none;"); 41 | } else { 42 | folder_btn.innerText = ">"; 43 | info_panel.setAttribute("style", "display: block;"); 44 | } 45 | }); 46 | // attach the elements to body 47 | document.body.appendChild(info_panel); 48 | document.body.appendChild(folder_btn); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /demo/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(https://themes.googleusercontent.com/static/fonts/roboto/v11/2UX7WLTfW3W8TclTUvlFyQ.woff) 6 | } 7 | 8 | body { 9 | font-family: 'Roboto'; 10 | margin: 0; 11 | padding: 0; 12 | word-break: break-word; 13 | } 14 | 15 | :focus { 16 | outline: 2px solid #1E90FF; 17 | } 18 | 19 | /* style for side menu area */ 20 | nav { 21 | font-size: 21px; 22 | width: 270px; 23 | min-width: 270px; 24 | border-right: 1px solid lightgray; 25 | } 26 | 27 | nav .menu { 28 | position: -webkit-sticky; 29 | position: sticky; 30 | top: 0px; 31 | } 32 | 33 | nav .menu a { 34 | display: block; 35 | padding: 12px 6px 6px 10px; 36 | font-weight: 600; 37 | color: #2c366f; 38 | } 39 | 40 | nav .menu a:hover { 41 | background: #d6c4f6; 42 | } 43 | 44 | nav .menu a.submenu { 45 | padding: 6px 0 6px 30px; 46 | font-size: 14px; 47 | font-weight: 600; 48 | } 49 | 50 | /* style for header content */ 51 | header .title{ 52 | width: 100%; 53 | height: 81px; 54 | line-height: 81px; 55 | background: #5e568b; 56 | color: #FFF; 57 | font-size: 36px; 58 | font-weight: 600; 59 | } 60 | 61 | #icon-image { 62 | margin-bottom: -12px; 63 | margin-right: 10px; 64 | } 65 | 66 | header div{ 67 | font-size: 13px; 68 | text-align: center; 69 | color: #FFF; 70 | background: #8170d7; 71 | width: 100%; 72 | height: 25px; 73 | } 74 | header div.deactivate { 75 | background: gray; 76 | } 77 | 78 | header div:not(.deactivate) .deactivate-description{ 79 | display: none; 80 | } 81 | 82 | header div.deactivate .activate-description{ 83 | display: none; 84 | } 85 | 86 | header div span { 87 | display: inline-block; 88 | width: 550px; 89 | } 90 | 91 | .switch { 92 | position: relative; 93 | display: inline-block; 94 | width: 45px; 95 | height: 17px; 96 | margin-left: 10px; 97 | top: 3px; 98 | } 99 | 100 | .switch input { 101 | display:none; 102 | } 103 | 104 | .slider { 105 | width: 100%; 106 | height: 100%; 107 | border-radius: 34px; 108 | background-color: #ccc; 109 | -webkit-transition: .2s; 110 | transition: .2s; 111 | } 112 | 113 | .slider:before { 114 | position: absolute; 115 | content: ""; 116 | height: 13px; 117 | width: 19px; 118 | left: 3px; 119 | bottom: 2px; 120 | background-color: white; 121 | border-radius: 40%; 122 | -webkit-transition: .2s; 123 | transition: .2s; 124 | } 125 | 126 | input:checked + .slider { 127 | background-color: #5a1875; 128 | } 129 | 130 | input:focus + .slider { 131 | box-shadow: 0 0 1px #2196F3; 132 | } 133 | 134 | input:checked + .slider:before { 135 | -webkit-transform: translateX(19px); 136 | -ms-transform: translateX(19px); 137 | transform: translateX(19px); 138 | } 139 | 140 | 141 | /* style for footer content */ 142 | footer { 143 | font-size: 18px; 144 | background: #5e568b; 145 | color: white; 146 | } 147 | 148 | footer div:first-child { 149 | margin-bottom: 10px; 150 | } 151 | 152 | footer a { 153 | margin: 0 10px; 154 | font-weight: 400; 155 | color: white; 156 | } 157 | 158 | /* style for main content */ 159 | main { 160 | flex: 1; 161 | } 162 | 163 | main h2 { 164 | color: #673AB7; 165 | 166 | } 167 | 168 | h3 { 169 | margin-top: 40px; 170 | padding-left: 10px; 171 | color: #679; 172 | font-size: 20px; 173 | } 174 | 175 | .iframe-description { 176 | width: 800px; 177 | } 178 | 179 | iframe { 180 | border: 1px solid gray; 181 | border-radius: 7px; 182 | height: 500px; 183 | width: 800px; 184 | } 185 | 186 | iframe #info_panel, #folder_btn{ 187 | display: none; 188 | } 189 | 190 | iframe:focus { 191 | outline: 5px solid #1E90FF; 192 | } 193 | 194 | iframe:focus-within { 195 | outline: 5px solid #1E90FF; 196 | } 197 | 198 | a { 199 | color: #0049bf; 200 | text-decoration: none; 201 | } 202 | 203 | a:hover { 204 | text-decoration: underline; 205 | } 206 | 207 | /* style for common */ 208 | .text-align { 209 | text-align: center; 210 | } 211 | 212 | .basic-padding{ 213 | padding : 20px; 214 | } 215 | 216 | .flex-box { 217 | display: flex; 218 | } 219 | 220 | .card-view { 221 | margin-top : 50px; 222 | margin-left: 20px; 223 | padding: 20px 30px; 224 | padding-right: 0; 225 | background: white; 226 | border: 1px solid lightgray; 227 | border-left: 15px solid #3F51B5; 228 | max-width: 850px; 229 | -webkit-box-shadow: 0 10px 6px -6px #777; 230 | -moz-box-shadow: 0 10px 6px -6px #777; 231 | box-shadow: 0 10px 6px -6px #777; 232 | } 233 | 234 | .demo-thumbnail { 235 | display: inline-block; 236 | text-align: center; 237 | } 238 | 239 | .demo-thumbnail img{ 240 | border: 1px solid gray; 241 | -webkit-box-shadow: 0 10px 6px -6px #777; 242 | -moz-box-shadow: 0 10px 6px -6px #777; 243 | box-shadow: 0 10px 6px -6px #777; 244 | } 245 | -------------------------------------------------------------------------------- /docs/Spatial Navigation in the Web (LG Electronics).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/docs/Spatial Navigation in the Web (LG Electronics).pdf -------------------------------------------------------------------------------- /docs/images_of_spec.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/docs/images_of_spec.pptx -------------------------------------------------------------------------------- /images/calender1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/images/calender1.png -------------------------------------------------------------------------------- /images/calender2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/images/calender2.png -------------------------------------------------------------------------------- /images/exit-entry-point.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/images/exit-entry-point.JPG -------------------------------------------------------------------------------- /images/nav-rule-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/images/nav-rule-example.png -------------------------------------------------------------------------------- /images/patterns-navigational-transitions-parent-to-child-calendar-tablet-xhdpi-004.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/images/patterns-navigational-transitions-parent-to-child-calendar-tablet-xhdpi-004.webm -------------------------------------------------------------------------------- /images/pinterest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/images/pinterest.png -------------------------------------------------------------------------------- /images/scroll related terms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/images/scroll related terms.png -------------------------------------------------------------------------------- /implStatus.md: -------------------------------------------------------------------------------- 1 | ## The implementation status of spatial navigation in Web engines 2 | To support the heuristic algorithm in the Web, we need to implement the codes into Web engines. 3 | 4 | ## Blink 5 | - Spatial Navigation issue list in Chromium Bugs 6 | - https://bugs.chromium.org/p/chromium/issues/list?can=1&q=spatial%20navigation 7 | 8 | ## WebKit 9 | - Spatial Navigation issue list in WebKit Bugzilla 10 | - http://bit.ly/2z0QlDl 11 | - [webkit-dev] RFC: Spatial Navigation feature for WebCore (2010/01/15) 12 | - https://lists.webkit.org/pipermail/webkit-dev/2010-January/011339.html 13 | - Bug 18662 - Extend keyboard navigation to allow directional navigation (2008/04/21) 14 | - https://bugs.webkit.org/show_bug.cgi?id=18662 15 | 16 | ## Gecko 17 | - Interacting with TV remote controls (2016/06/24) 18 | - https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS_for_TV/Interacting_with_TV_remote_controls 19 | - The relevant codes in Gecko 20 | - https://dxr.mozilla.org/mozilla-central/search?q=spatial+navigation&redirect=false 21 | - Does Spatial Navigation Exist for firefox? 22 | - https://support.mozilla.org/ko/questions/955214 (2013/04/03) 23 | - Keyboard navigation proposal for Mozilla (2014/09/15) 24 | - https://lists.w3.org/Archives/Public/w3c-wai-ua/2004JulSep/0040.html 25 | - Firefox - hook snav up to browser (2013/08/02) 26 | - https://bugzilla.mozilla.org/show_bug.cgi?id=443043 27 | 28 | ## edgeHTML 29 | - Focus navigation with keyboard, gamepad, and accessibility tools (2017/08/02) 30 | - https://docs.microsoft.com/en-us/windows/uwp/input-and-devices/managing-focus-navigation 31 | - Custom keyboard interactions (2017/08/24) 32 | - https://docs.microsoft.com/en-us/windows/uwp/input-and-devices/custom-keyboard-interactions 33 | - TVJS API (2017/07/12) 34 | - https://github.com/Microsoft/TVHelpers/wiki 35 | - Using DirectionalNavigation (2016/06/25) 36 | - https://github.com/Microsoft/TVHelpers/wiki/Using-DirectionalNavigation 37 | 38 | ## Presto (Vewd, aka OperaTV) 39 | - Tweaking Spatial Navigation for TV Browsing (2017/09/14) 40 | - https://developer.vewd.com/display/OTV/Tweaking+Spatial+Navigation+for+TV+Browsing 41 | - Spatial Navigation and Opera (2008/02/13) 42 | - https://blog.codinghorror.com/spatial-navigation-and-opera/ 43 | 44 | ## Web frameworks 45 | - react-spatial-tv-navigation (2017/04/11) 46 | - https://github.com/SatadruBhattacharjee/react-spatial-tv-navigation 47 | 48 | ## etc. 49 | - Creating TV Navigation (Android) 50 | - https://developer.android.com/training/tv/start/navigation.html 51 | - The Focus Engine Controls Focus (Apple) 52 | - https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/WorkingwiththeAppleTVRemote.html 53 | - Tizen (2015/10/28) 54 | - https://bugs.tizen.org/browse/TT-211?jql=text%20~%20%22spatial%20navigation%22 55 | - QT WebKit (2013/02/08) 56 | - https://bugreports.qt.io/browse/QTWEBKIT-271?jql=text%20~%20%22spatial%20navigation%22 57 | - The summary of Spatial Navigation (2015/04/02, Florian) 58 | - https://florian.rivoal.net/blog/2015/04/controlling-spatial-navigation/ 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatial-navigation-polyfill", 3 | "version": "1.3.1", 4 | "description": "A polyfill for the spatial navigation", 5 | "main": "polyfill/spatial-navigation-polyfill.js", 6 | "directories": { 7 | "doc": "docs", 8 | "test": "tests" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/WICG/spatial-navigation.git" 16 | }, 17 | "keywords": [ 18 | "spatial-navigation", 19 | "polyfill", 20 | "web" 21 | ], 22 | "author": "Jihye Hong ", 23 | "contributors": [ 24 | "Hyojin Song ", 25 | "Jeonghee Ahn ", 26 | "Yeonjae Koo " 27 | ], 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/WICG/spatial-navigation/issues" 31 | }, 32 | "homepage": "https://wicg.github.io/spatial-navigation/polyfill/" 33 | } 34 | -------------------------------------------------------------------------------- /polyfill/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 LG Electronics Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /polyfill/README.md: -------------------------------------------------------------------------------- 1 | This polyfill implementation follows the latest [Spatial Navigation Spec](https://drafts.csswg.org/css-nav-1/) 2 | -------------------------------------------------------------------------------- /polyfill/changelog.md: -------------------------------------------------------------------------------- 1 | ## 1.3.1 (2019-11-29) 2 | 3 | * Align the polyfill features with the [new Working Draft of CSS Spatial Navigation Level 1](https://www.w3.org/TR/2019/WD-css-nav-1-20191126/) 4 | * Fix a bug for spatial-navigation-action: auto by [commit](https://github.com/WICG/spatial-navigation/commit/bceff39b5ca059303193c0ec85c066aa9254d252) 5 | * Modify the spatial-navigation-function implementation by [commit](https://github.com/WICG/spatial-navigation/commit/408ff1607abd8c07085a8de1bbe8bd448e188b58) 6 | * Drop `inside` attribute of `spatialNavigationSearch` API [(#211)](https://github.com/WICG/spatial-navigation/pull/211) 7 | * Add and modify the sample demos 8 | 9 | ## 1.3.0 (2019-10-31) 10 | 11 | * Add chrome extension [(#197)](https://github.com/WICG/spatial-navigation/pull/197) 12 | * Add spatial-navigation-function CSS Property by [commit](https://github.com/WICG/spatial-navigation/commit/14654aa651a6ee06106ab0823046b3a4e59d6156) 13 | * Make `spatialNavigationSearch` API takes inside option by [commit](https://github.com/WICG/spatial-navigation/commit/a5603e51f2417f37e38e1fcab37706486fa46ae3) depending the [spec](https://github.com/w3c/csswg-drafts/issues/3743) 14 | * Modify the condition of scroll container [(#200)](https://github.com/WICG/spatial-navigation/pull/200) 15 | * Fix the focus movement properly inside iframe element 16 | * Add the behavior of moving focus to inside overlapped elements by [commit](https://github.com/WICG/spatial-navigation/commit/9131a38349e6c9109b02e33500f0fa63bf6a4233) 17 | * Add resetting the search origin [(#205)](https://github.com/WICG/spatial-navigation/pull/205) 18 | 19 | ## 1.2.0 (2019-04-18) 20 | 21 | * Add UX test cases for verifying the distance function [(#154)](https://github.com/WICG/spatial-navigation/pull/154) 22 | * Improve the distance function depending on [the spec update](https://github.com/w3c/csswg-drafts/pull/3755) [(#165)](https://github.com/WICG/spatial-navigation/pull/165) 23 | * Fix the bug for preventing navigation events 24 | * Remove `navbeforescroll` event 25 | * Add `spatial-navigation-action` CSS Property and its demo [(#171)](https://github.com/WICG/spatial-navigation/pull/171) 26 | * Add internal test cases for overall polyfill behavior 27 | * Add the feature about navigating from the spatial navigation starting point [(#169)](https://github.com/WICG/spatial-navigation/pull/169) 28 | * Optimize `navigate()` [(#172)](https://github.com/WICG/spatial-navigation/pull/172) 29 | * Fix the bug of handling non-focusable elements [(#174)](https://github.com/WICG/spatial-navigation/pull/174) 30 | 31 | ## 1.1.0 (2019-01-04) 32 | 33 | * Change the polyfill file name from spatnav-heuristic.js to spatial-navigation-polyfill.js 34 | * Fix the bug of isFocusable() (added more conditions for focusable elements) [(#147)](https://github.com/WICG/spatial-navigation/pull/147) 35 | * Fix the bug of getSpatialNavigationContainer [(jihyerish#5)](https://github.com/jihyerish/spatial-navigation/pull/5) 36 | * Add enableExperimentalAPIs function 37 | * Add the drowing board for the demo center [(#148)](https://github.com/WICG/spatial-navigation/pull/148) 38 | * Improve the overall performance [(#150)](https://github.com/WICG/spatial-navigation/pull/150) 39 | * Add the polyfill annotation with JSDoc format 40 | 41 | ## 1.0.0 (2018-09-07) 42 | 43 | * Initial version 44 | -------------------------------------------------------------------------------- /resources/testharness.css: -------------------------------------------------------------------------------- 1 | 2 | body { font-family: sans-serif; } 3 | 4 | :focus { 5 | outline: 4px #3582ff solid; 6 | } 7 | 8 | .container, #container{ 9 | width: 720px; 10 | margin: 10px; 11 | border: black solid 1px; 12 | } 13 | 14 | .select { 15 | width: 720px; 16 | height: 120px; 17 | padding: 10px; 18 | margin-top: 10px; 19 | color: white; 20 | border-radius: 10px 10px 0 0; 21 | background: gray; 22 | line-height : 20px; 23 | } 24 | 25 | .options { 26 | display: inline; 27 | } 28 | 29 | #initial_focus { 30 | background: #F08080; 31 | } 32 | 33 | #get_result { 34 | font-weight: bold; 35 | } 36 | 37 | .btn { 38 | border: 2px solid purple; 39 | border-radius: 10px; 40 | background-color: white; 41 | color: purple; 42 | margin: 5px 250px; 43 | padding: 10px 15px; 44 | font-size: 14px; 45 | cursor: pointer; 46 | } 47 | 48 | .btn:hover, .btn:focus { 49 | background: purple; 50 | color: white; 51 | } -------------------------------------------------------------------------------- /resources/testharnessreport.js: -------------------------------------------------------------------------------- 1 | /* global add_completion_callback */ 2 | /* global setup */ 3 | 4 | /* 5 | * This file is intended for vendors to implement code needed to integrate 6 | * testharness.js tests with their own test systems. 7 | * 8 | * Typically test system integration will attach callbacks when each test has 9 | * run, using add_result_callback(callback(test)), or when the whole test file 10 | * has completed, using 11 | * add_completion_callback(callback(tests, harness_status)). 12 | * 13 | * For more documentation about the callback functions and the 14 | * parameters they are called with see testharness.js 15 | */ 16 | 17 | function dump_test_results(tests, status) { 18 | var results_element = document.createElement("script"); 19 | results_element.type = "text/json"; 20 | results_element.id = "__testharness__results__"; 21 | var test_results = tests.map(function(x) { 22 | return {name:x.name, status:x.status, message:x.message, stack:x.stack} 23 | }); 24 | var data = {test:window.location.href, 25 | tests:test_results, 26 | status: status.status, 27 | message: status.message, 28 | stack: status.stack}; 29 | results_element.textContent = JSON.stringify(data); 30 | 31 | // To avoid a HierarchyRequestError with XML documents, ensure that 'results_element' 32 | // is inserted at a location that results in a valid document. 33 | var parent = document.body 34 | ? document.body // is required in XHTML documents 35 | : document.documentElement; // fallback for optional in HTML5, SVG, etc. 36 | 37 | parent.appendChild(results_element); 38 | } 39 | 40 | add_completion_callback(dump_test_results); 41 | 42 | /* If the parent window has a testharness_properties object, 43 | * we use this to provide the test settings. This is used by the 44 | * default in-browser runner to configure the timeout and the 45 | * rendering of results 46 | */ 47 | try { 48 | if (window.opener && "testharness_properties" in window.opener) { 49 | /* If we pass the testharness_properties object as-is here without 50 | * JSON stringifying and reparsing it, IE fails & emits the message 51 | * "Could not complete the operation due to error 80700019". 52 | */ 53 | setup(JSON.parse(JSON.stringify(window.opener.testharness_properties))); 54 | } 55 | } catch (e) { 56 | } 57 | // vim: set expandtab shiftwidth=4 tabstop=4: 58 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | Maintaining the test case of Spatial Navigation. The test cases are categorized by testing purposes. 3 | 4 | # Contents 5 | ## Web Platform Test 6 | The test cases under `/test` aims for building a cross-browser testsuite. 7 | The test cases would be moved to the https://github.com/web-platform-tests/wpt later. 8 | 9 | ## Polyfill Test 10 | The test cases under `/test/internal` aims to validate: 11 | * If there are bugs in polyfill 12 | * If the polyfill and specification are synchronized 13 | 14 | ## UX Test 15 | The test cases under `/test/ux` show what would be desireable spatial navigation behavior from a user point of view. 16 | The result of test case may not matchs with the current spatial navigation spec. 17 | -------------------------------------------------------------------------------- /tests/internal/hostile_iframe1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | 32 | 33 | -------------------------------------------------------------------------------- /tests/internal/hostile_iframe2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | 32 | 33 | -------------------------------------------------------------------------------- /tests/internal/hostile_iframe3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | 32 | 33 | -------------------------------------------------------------------------------- /tests/internal/hostile_iframe_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hostile iframe test 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Hostile iframe Test

18 |

Focus Trap by preventDefault 'navnotarget' Event

19 | 20 |

Focus Trap by preventDefault 'navbeforefocus' Event

21 | 22 |

Focus Trap by preventDefault 'keydown'(LRUD) Event

23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/internal/iframe-offscreen-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spatnav sanity check 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 41 | 42 | -------------------------------------------------------------------------------- /tests/internal/iframe-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spatnav sanity check 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 | 65 | 66 | -------------------------------------------------------------------------------- /tests/internal/iframe_sub.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/internal/iframe_sub2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/internal/isVisibleInScroller-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spatnav sanity check 5 | 6 | 7 | 8 | 9 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 63 | 64 | -------------------------------------------------------------------------------- /tests/internal/out-of-viewport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spatnav sanity check 5 | 6 | 7 | 8 | 9 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 76 | 77 | -------------------------------------------------------------------------------- /tests/internal/search-origin-offscreen-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spatnav sanity check 5 | 6 | 7 | 8 | 9 | 21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 | 36 |
37 | 38 | 39 | 40 |
41 | 42 | 71 | 72 | -------------------------------------------------------------------------------- /tests/internal/test.css: -------------------------------------------------------------------------------- 1 | #result { 2 | margin: 20px; 3 | } 4 | 5 | #result div span { 6 | margin: 10px; 7 | display: inline-block; 8 | } 9 | #result div span.error { 10 | display: block; 11 | margin-top: 0; 12 | margin-left: 110px; 13 | color: gray; 14 | } 15 | #result div span:first-child { 16 | width: 80px; 17 | font-weight: bold; 18 | color: blue; 19 | } 20 | #result div span:first-child.fail { 21 | color: red; 22 | } -------------------------------------------------------------------------------- /tests/internal/test.js: -------------------------------------------------------------------------------- 1 | let resultIndex = 1; 2 | 3 | function testInit() { 4 | const resultNode = document.createElement('div'); 5 | resultNode.id = 'result'; 6 | document.body.appendChild(resultNode); 7 | } 8 | 9 | function testRun(func, description) { 10 | try { 11 | func(); 12 | addResult ('SUCCESS', description); 13 | } catch (error) { 14 | addResult ('FAIL', description, error); 15 | } 16 | } 17 | 18 | function addResult (success, description, error) { 19 | const resultNode = document.querySelector('#result'); 20 | const testResult = document.createElement('div'); 21 | const testDes1 = document.createElement('span'); 22 | const testDes2 = document.createElement('span'); 23 | const testDes3 = document.createElement('span'); 24 | testDes1.innerText = resultIndex++ + '.' + success; 25 | testDes2.innerText = description; 26 | 27 | testResult.appendChild(testDes1); 28 | testResult.appendChild(testDes2); 29 | if (error) { 30 | testDes1.className = 'fail'; 31 | testDes3.innerText = error; 32 | testDes3.className = 'error'; 33 | testResult.appendChild(testDes3); 34 | } 35 | resultNode.appendChild(testResult); 36 | } 37 | 38 | function getString (obj) { 39 | if(!obj || typeof obj !== "object") 40 | return obj; 41 | else if ("nodeType" in obj && "nodeName" in obj && "nodeValue" in obj && "childNodes" in obj) 42 | return obj.outerHTML.substr(0,60) + '...'; 43 | return obj.toString(); 44 | } 45 | 46 | function assert_equals(actual, expected) { 47 | if (actual !== expected) 48 | throw `assert_equals: expected ${getString(expected)} but got ${getString(actual)}`; 49 | } 50 | 51 | function assert_not_equals(actual, expected) { 52 | if (actual === expected) 53 | throw `assert_not_equals: dose not expected ${getString(expected)} but got ${getString(actual)}`; 54 | } 55 | -------------------------------------------------------------------------------- /tests/spatnav-focus-001.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spatnav sanity check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 20 | 21 | 37 | -------------------------------------------------------------------------------- /tests/spatnav-scroll-001.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spatnav sanity check 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras lacinia nulla justo, et lobortis odio condimentum facilisis. Cras tempor pulvinar ligula finibus vulputate. Vivamus posuere accumsan metus, eget consectetur diam. Donec convallis, ipsum sed semper tempus, elit quam luctus tortor, in molestie felis ex eu leo. Suspendisse pellentesque non diam sit amet dictum. Cras nibh ex, ultricies non lorem vitae, placerat euismod sem. Quisque nec luctus risus, vel tincidunt eros. Mauris tempor enim et mi sollicitudin blandit.
25 | 26 | 27 | 36 | -------------------------------------------------------------------------------- /tests/ux/README.md: -------------------------------------------------------------------------------- 1 | The tests in this directory **do not** attempt to match the spec. 2 | 3 | Instead, they attempt to cover intuition would suggest should be the correct 4 | target for spatial navigation in various scenario. 5 | 6 | This is certainly subjective, and may evolve over time as our understanding 7 | of the problem space improves. 8 | 9 | It is expected that this will be used to influence the specification, 10 | but not necessarily that there will be a perfect match. 11 | 12 | See the test results in [here](result.md) 13 | -------------------------------------------------------------------------------- /tests/ux/spatnav-distance-function-grid-align-002.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spatnav sanity check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 |
34 |
35 | Points : 36 | 42 |
43 | 44 |
45 | Function : 46 | 51 |
52 | 53 |
54 | Orthogonal Weight (X-axis, Y-axis) : 55 | , 56 | 57 |
58 |
59 | Align Weight : 60 | 61 |
62 | 63 | 64 |
65 |
66 |
67 |
Box 1
68 |
Box 2
69 |
70 |
71 |
72 |
73 | 74 | 98 | 118 | -------------------------------------------------------------------------------- /tests/ux/spatnav-distance-function-grid-align-003.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spatnav sanity check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 |
34 |
35 | Points : 36 | 42 |
43 | 44 |
45 | Function : 46 | 51 |
52 | 53 |
54 | Orthogonal Weight (X-axis, Y-axis) : 55 | , 56 | 57 |
58 |
59 | Align Weight : 60 | 61 |
62 | 63 | 64 |
65 |
66 |
67 |
Box 1
68 |
Box 2
69 |
70 |
71 |
72 |
73 | 74 | 98 | 118 | -------------------------------------------------------------------------------- /tests/ux/spatnav-distance-function-grid-align-004.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spatnav sanity check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 |
34 |
35 | Points : 36 | 42 |
43 | 44 |
45 | Function : 46 | 51 |
52 | 53 |
54 | Orthogonal Weight (X-axis, Y-axis) : 55 | , 56 | 57 |
58 |
59 | Align Weight : 60 | 61 |
62 | 63 | 64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 | 98 | 118 | -------------------------------------------------------------------------------- /tests/ux/spatnav-distance-function-intersected-001.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spatnav sanity check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 |
34 |
35 | Points : 36 | 42 |
43 | 44 |
45 | Function : 46 | 51 |
52 | 53 |
54 | Orthogonal Weight (X-axis, Y-axis) : 55 | , 56 | 57 |
58 |
59 | Align Weight : 60 | 61 |
62 | 63 | 64 |
65 |
66 |
67 |
Box 1
68 |
Box 2
69 |
70 |
71 |
72 |
73 | 74 | 98 | 118 | 119 | -------------------------------------------------------------------------------- /tests/ux/spatnav-distance-function-intersected-002.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spatnav sanity check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 |
34 |
35 | Points : 36 | 42 |
43 | 44 |
45 | Function : 46 | 51 |
52 | 53 |
54 | Orthogonal Weight (X-axis, Y-axis) : 55 | , 56 | 57 |
58 |
59 | Align Weight : 60 | 61 |
62 | 63 | 64 |
65 |
66 |
67 |
Box 1
68 |
Box 2
69 |
70 |
71 |
72 |
73 | 74 | 98 | 118 | 119 | -------------------------------------------------------------------------------- /tests/ux/spatnav-distance-function-transformed-001.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spatnav sanity check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 30 | 31 |
32 |
33 | Points : 34 | 40 |
41 | 42 |
43 | Function : 44 | 49 |
50 | 51 |
52 | Orthogonal Weight (X-axis) : 53 | 54 | 55 | Orthogonal Weight (Y-axis) : 56 | 57 |
58 | 59 | 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 92 | 111 | -------------------------------------------------------------------------------- /tests/ux/spatnav-simple-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spatnav sanity check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 |
41 |
42 | 43 | 44 | 69 | 86 | -------------------------------------------------------------------------------- /tools/chrome-extension/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 LG Electronics Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tools/chrome-extension/README.md: -------------------------------------------------------------------------------- 1 | 2 | # spatial-navigation-chrome-extension 3 | 4 | **Written**: 2019-08-29, **Updated**: 2019-08-29 5 | 6 | **Spatial-navigation-chrome-extension** enables spatial navigation on any websites using [spatial-navigation-polyfill](https://github.com/WICG/spatial-navigation/tree/master/polyfill) 7 | ![spatial navigation on any websites](./img2.gif) 8 | 9 | **Spatial Navigation** provides a processing model and standards APIs for directional(top/left/bottom/right) focus navigation using arrow keys. ([Learn More](https://wicg.github.io/spatial-navigation/)) 10 | 11 | ``` 12 | Note: 13 | This tool is based on spatial-navigation-polyfill which was forked from the develop branch on August 2019. 14 | It means the polyfill includes a few non-standards features. 15 | ``` 16 | 17 | ## Features 18 | ### 1. Spatial Navigation 19 | - Enable Spatial Navigation 20 | - Show Spatial Navigation Info in DevTool (F12 / ⌥ +⌘+I) 21 | - Change settings in popup menu 22 | 23 | ### 2. Popup 24 | 1. On / Off Spatial Navigation 25 | 2. Change Key mode (ARROW / SHIFTARROW) 26 | 3. Choose whether or not to show candidates in 4 directions 27 | 28 | - ![Popup menu](./img1.png) 29 | 30 | ### 3. Devtool 31 | 1. Focusable Element 32 | : Show number of all focusable element inside whole page 33 | : Use checkbox, you can see (1) whole focusable element inside web page (2) candidate elements of spatial navigation from current focused element to all and each 4 directions 34 | 2. Is it container? 35 | : Whether current element is container or not 36 | 3. Next Spatnav Search 37 | : Show optimal candidate of 4 directions from current element (in specific container) 38 | 4. Next Target 39 | : Show optimal candidate of 4 directions from current element (show undefined if scrollable) 40 | 5. Focusable Areas 41 | : Show focusable child elements from current element 42 | 6. Next Candidates 43 | : Show all candidates list of 4 directions from current element (show current element if scrollable) 44 | 7. List of Containers 45 | : List of containers from parent of current element to topmost container 46 | 47 | - ![Devtool](./img3.png) 48 | 49 | ## How to install 50 | 1. Clone the repository `git clone https://github.com/WICG/spatial-navigation.git`. 51 | 2. In chrome, navigate to `chrome://extensions`. 52 | 3. Turn on the **Developer mode**. 53 | 4. Click **LOAD UNPACKED** and select the `tools/chrome-extension` directory. 54 | 55 | ## Manual 56 | ### 1. PopUp 57 | 1. Click the toolbar icon 58 | 59 | ### 2. DevTool 60 | 1. Click the SpatialNavigation tab of developer's tool(F12 / ⌥ +⌘+I). 61 | 2. You can see the information of Spatial Navigation on the tab. Also, the information is changed as the focus moves. 62 | 63 | 64 | ## Acknowledgement 65 | 66 | We would like to express appreciation to below students who initially developed this extension. 67 | 68 | - jisk984@snu.ac.kr 69 | - amorphysics@snu.ac.kr 70 | - aeridh@naver.com 71 | - maheu@snu.ac.kr 72 | - sunnyday0208@naver.com 73 | 74 | ### Initial repository 75 | - https://github.com/lgewst/snu-chrome-extension-1 76 | - https://github.com/lgewst/snu-chrome-extension-2 77 | -------------------------------------------------------------------------------- /tools/chrome-extension/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/tools/chrome-extension/icon.png -------------------------------------------------------------------------------- /tools/chrome-extension/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/tools/chrome-extension/img1.png -------------------------------------------------------------------------------- /tools/chrome-extension/img2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/tools/chrome-extension/img2.gif -------------------------------------------------------------------------------- /tools/chrome-extension/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/spatial-navigation/11ffdb06637c0f86cb8eb5f430bad0a26b547184/tools/chrome-extension/img3.png -------------------------------------------------------------------------------- /tools/chrome-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SpatialNavigation", 3 | "version": "0.6", 4 | "description": "extension for spatial navigation", 5 | "manifest_version": 2, 6 | "devtools_page": "src/devtool.html", 7 | "browser_action": { 8 | "default_icon": "icon.png", 9 | "default_popup": "src/popup.html" 10 | }, 11 | "content_scripts": [ 12 | { 13 | "matches": ["http://*/*", "https://*/*", ""], 14 | "js": ["polyfill/spatial-navigation-polyfill.js", "src/content_script.js", "src/detector/graph.js", "src/detector/detector.js"], 15 | "css": ["src/content_script.css"], 16 | "all_frames": true 17 | } 18 | ], 19 | "permissions": [ 20 | "activeTab", "tabs", "", "storage" 21 | ], 22 | "background": { 23 | "scripts": ["src/background.js"], 24 | "persistent": true 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tools/chrome-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snu-chrome-extension-1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "devtool.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "eslint": "^5.7.0", 9 | "eslint-plugin-import": "^2.14.0" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+ssh://git@github.com/lgewst/snu-chrome-extension-1.git" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/lgewst/snu-chrome-extension-1/issues" 22 | }, 23 | "homepage": "https://github.com/lgewst/snu-chrome-extension-1#readme" 24 | } 25 | -------------------------------------------------------------------------------- /tools/chrome-extension/src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "webextensions": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "rules": { 9 | "indent": [ 10 | "error", 11 | 4 12 | ], 13 | "linebreak-style": [ 14 | "error", 15 | "unix" 16 | ], 17 | "quotes": [ 18 | "error", 19 | "single" 20 | ], 21 | "semi": [ 22 | "error", 23 | "always" 24 | ], 25 | "no-console": 0, 26 | "no-undef": 0, 27 | "space-before-blocks": ["error", { "functions": "always", "keywords": "always", "classes": "always" }], 28 | "space-infix-ops": "error", 29 | "no-control-regex": 0, 30 | "no-debugger": 1, 31 | "no-empty": 0, 32 | "no-negated-in-lhs": 2, 33 | "no-regex-spaces": 0, 34 | "block-scoped-var": 1, 35 | "no-caller": 2, 36 | "no-div-regex": 1, 37 | "no-eval": 1, 38 | "no-extra-bind": 1, 39 | "no-implied-eval": 1, 40 | "no-labels": 2, 41 | "no-native-reassign": 2, 42 | "no-new-func": 2, 43 | "no-new-wrappers": 2, 44 | "no-new": 1, 45 | "no-octal-escape": 1, 46 | "no-redeclare": [2, {builtinGlobals: true}], 47 | "no-return-assign": [2, "except-parens"], 48 | "no-self-compare": 2, 49 | "no-sequences": 2, 50 | "no-throw-literal": 2, 51 | "no-unused-expressions": [1, {allowShortCircuit: true, allowTernary: true}], 52 | "no-useless-call": 2, 53 | "no-useless-return": 1, 54 | "no-with": 2, 55 | "no-catch-shadow": 2, 56 | "no-label-var": 2, 57 | "no-shadow-restricted-names": 2, 58 | "no-shadow": [2, {builtinGlobals: true, hoist: "all", allow: ["context"]}], 59 | "no-use-before-define": [2, {functions: false}], 60 | "linebreak-style": 1, 61 | "new-cap": [2, {newIsCap: true, capIsNew: false}], 62 | "no-array-constructor": 2, 63 | "no-lonely-if": 1, 64 | "no-new-object": 1, 65 | "no-unneeded-ternary": 1, 66 | "spaced-comment": [1, "always", {markers: ["*"]}], 67 | "require-yield": 0, 68 | "no-var": 1, 69 | "prefer-const": 1 70 | }, 71 | "parserOptions": { 72 | "sourceType": "module" 73 | } 74 | }; -------------------------------------------------------------------------------- /tools/chrome-extension/src/background.js: -------------------------------------------------------------------------------- 1 | /** 2 | * restore option when page is loaded or refreshed. 3 | */ 4 | function setOption(tabId) { 5 | chrome.storage.local.get({ 6 | keyMode: 'ARROW', 7 | isOn: true 8 | }, (items) => { 9 | items.keyMode = items.isOn ? items.keyMode : 'NONE'; 10 | console.log(items.keyMode); 11 | const codeString = `window.__spatialNavigation__.keyMode = '${items.keyMode}'`; 12 | chrome.tabs.executeScript(tabId, { 13 | code: codeString 14 | }, (err) => { 15 | const e = chrome.runtime.lastError; 16 | if (e !== undefined) { 17 | console.log(tabId, err, e); 18 | } 19 | }); 20 | 21 | }); 22 | } 23 | 24 | chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { 25 | console.log(changeInfo); 26 | /// alert(changeInfo); 27 | if (changeInfo.status == 'complete') { 28 | setOption(tabId); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /tools/chrome-extension/src/content_script.css: -------------------------------------------------------------------------------- 1 | :focus { 2 | /* outline: 5px solid #428bca;*/ 3 | } 4 | 5 | .next-target-highlight { 6 | background-color: #e1def8; 7 | color: black; 8 | } 9 | 10 | .active-element-highlight { 11 | background-color: #eca1dc; 12 | outline: 1px solid #c973c4 !important; 13 | color: black; 14 | } 15 | 16 | .hover-element-highlight { 17 | background-color: #FCADAB !important; 18 | outline: 3px solid red !important; 19 | box-sizing: border-box !important; 20 | } 21 | 22 | .focusable-element-highlight { 23 | outline: 2px solid #B0C4DE !important; 24 | } 25 | 26 | /* tooltip */ 27 | /* https://codepen.io/yjose/pen/KWEqMg */ 28 | 29 | [spatNavTooltip]{ 30 | position:relative; 31 | /* display:inline-block;*/ 32 | } 33 | 34 | [spatNavTooltip]::before { 35 | content: ""; 36 | position: absolute; 37 | top:-6px; 38 | left:50%; 39 | transform: translateX(-50%); 40 | border-width: 4px 6px 0 6px; 41 | border-style: solid; 42 | border-color: rgba(0,0,0,0.3) transparent transparent transparent; 43 | z-index: 1000; 44 | opacity:1; 45 | } 46 | 47 | [spatNavTooltip-position='left']::before{ 48 | left:0%; 49 | top:50%; 50 | margin-left:-12px; 51 | transform:translatey(-50%) rotate(-90deg) 52 | } 53 | 54 | [spatNavTooltip-position='top']::before{ 55 | left:50%; 56 | } 57 | 58 | [spatNavTooltip-position='buttom']::before{ 59 | top:100%; 60 | margin-top:8px; 61 | transform: translateX(-50%) translatey(-100%) rotate(-180deg) 62 | } 63 | 64 | [spatNavTooltip-position='right']::before{ 65 | left:100%; 66 | top:50%; 67 | margin-left:1px; 68 | transform:translatey(-50%) rotate(90deg) 69 | } 70 | 71 | [spatNavTooltip]::after { 72 | content: attr(spatNavTooltip); 73 | position: absolute; 74 | left:50%; 75 | top:-6px; 76 | transform: translateX(-50%) translateY(-100%); 77 | background: rgba(0,0,0,0.3); 78 | text-align: center; 79 | color: #fff; 80 | padding:4px 2px; 81 | font-size: 12px; 82 | min-width: 50px; 83 | border-radius: 5px; 84 | pointer-events: none; 85 | padding: 4px 4px; 86 | z-index:1000; 87 | opacity:1; 88 | } 89 | 90 | [spatNavTooltip-position='left']::after{ 91 | left:0%; 92 | top:50%; 93 | margin-left:-8px; 94 | transform: translateX(-100%) translateY(-50%); 95 | } 96 | 97 | [spatNavTooltip-position='top']::after{ 98 | left:50%; 99 | } 100 | 101 | [spatNavTooltip-position='buttom']::after{ 102 | top:100%; 103 | margin-top:8px; 104 | transform: translateX(-50%) translateY(0%); 105 | } 106 | 107 | [spatNavTooltip-position='right']::after{ 108 | left:100%; 109 | top:50%; 110 | margin-left:8px; 111 | transform: translateX(0%) translateY(-50%); 112 | } 113 | /* 114 | [spatNavTooltip]:hover::after,[spatNavTooltip]:hover::before { 115 | opacity:1 116 | } */ 117 | -------------------------------------------------------------------------------- /tools/chrome-extension/src/content_script.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function blurHappened() { 4 | chrome.runtime.sendMessage({ 5 | request: 'reload' 6 | }); 7 | } 8 | 9 | const NEXT_TARGET_HIGHLIGHT = 'next-target-highlight'; 10 | const ACTIVE_ELEMENT_HIGHLIGHT = 'active-element-highlight'; 11 | 12 | let actElement; 13 | let highlightedElement = { 14 | left: null, 15 | right: null, 16 | up: null, 17 | down: null 18 | }; 19 | /** 20 | * Event handler for initializing. 21 | */ 22 | window.addEventListener('load', function () { 23 | window.__spatialNavigation__ && window.__spatialNavigation__.enableExperimentalAPIs(); 24 | }); 25 | 26 | /** 27 | * Event handler for highlight and tooltip. 28 | */ 29 | 30 | window.addEventListener('blur', () => { 31 | // Current frame lost focus 32 | if (actElement != null) { 33 | actElement.classList.remove(ACTIVE_ELEMENT_HIGHLIGHT); 34 | actElement = null; 35 | } 36 | for (label in highlightedElement) { 37 | if (highlightedElement[label]) { 38 | highlightedElement[label].classList.remove(NEXT_TARGET_HIGHLIGHT); 39 | 40 | if (highlightedElement[label].getAttribute('spatNavTooltip')) { 41 | highlightedElement[label].removeAttribute('spatNavTooltip'); 42 | } 43 | highlightedElement[label] = null; 44 | } 45 | } 46 | }); 47 | 48 | document.addEventListener('keyup', (e) => { 49 | const keyCode = e.which || e.keyCode; 50 | 51 | // get keyMode settings. 52 | if (chrome.storage.local) { 53 | chrome.storage.local.get({ 54 | keyMode: 'ARROW', 55 | isOn: true, 56 | isVisible: false, 57 | CurrentOn: true 58 | }, (items) => { 59 | if (document.activeElement != undefined) { 60 | // enable spatial navigation experimental APIs. 61 | if(!window.__spatialNavigation__.findNextTarget) { 62 | window.__spatialNavigation__ && window.__spatialNavigation__.enableExperimentalAPIs(); 63 | } 64 | 65 | // Check whether pressed key is arrow key or tab key. 66 | if ([9, 37, 38, 39, 40].includes(keyCode)) { 67 | blurHappened(); 68 | 69 | // remove highlight and tooltip. 70 | if (actElement != null) { 71 | actElement.classList.remove(ACTIVE_ELEMENT_HIGHLIGHT); 72 | actElement = null; 73 | } 74 | for (label in highlightedElement) { 75 | if (highlightedElement[label]) { 76 | highlightedElement[label].classList.remove(NEXT_TARGET_HIGHLIGHT); 77 | 78 | if (highlightedElement[label].getAttribute('spatNavTooltip')) { 79 | highlightedElement[label].removeAttribute('spatNavTooltip'); 80 | } 81 | highlightedElement[label] = null; 82 | } 83 | } 84 | 85 | // Add only spatNav and visible option are turned on. 86 | if (items.isOn && items.isVisible) { 87 | actElement = document.activeElement; 88 | actElement.classList.add(ACTIVE_ELEMENT_HIGHLIGHT); 89 | 90 | // Add highlight and tooltip. 91 | for (label in highlightedElement) { 92 | highlightedElement[label] = window.__spatialNavigation__.findNextTarget(actElement, label); 93 | 94 | if (highlightedElement[label]) { 95 | highlightedElement[label].classList.add(NEXT_TARGET_HIGHLIGHT); 96 | highlightedElement[label].setAttribute('spatNavTooltip', label); 97 | } 98 | } 99 | } else if (items.isOn && items.CurrentOn) { 100 | actElement = document.activeElement; 101 | actElement.classList.add(ACTIVE_ELEMENT_HIGHLIGHT); 102 | } 103 | } 104 | } 105 | }); 106 | } 107 | }, false); 108 | -------------------------------------------------------------------------------- /tools/chrome-extension/src/detector/isolation_elements_detector.js: -------------------------------------------------------------------------------- 1 | var ret_val = Array(); 2 | var res = isolation_detector(); 3 | if (res < 0) ret_val = -res; 4 | else { 5 | //var res = document.body.focusableAreas({'mode': 'all'}); 6 | for (i = 0; i < res.length; i++) { 7 | ret_val[i] = res[i].outerHTML; 8 | } 9 | } 10 | ret_val; -------------------------------------------------------------------------------- /tools/chrome-extension/src/detector/loop_elements_detector.js: -------------------------------------------------------------------------------- 1 | var ret_val = Array(); 2 | var res = loop_detector(); 3 | 4 | if (res < 0) ret_val = res; 5 | else { 6 | 7 | direction = ["Loop elments in Up direction", "Loop elments in down direction", "Loop elments in left direction", "Loop elments in right direction"]; 8 | 9 | for (j = 0; j < 4; j++) { 10 | ret_val.push("" + "

" + direction[j] + "


" + ""); 11 | 12 | var each_direction_loop_result = res[j]; 13 | 14 | for (i = 0; i < each_direction_loop_result.length; i++) { 15 | ret_val.push(each_direction_loop_result[i].outerHTML); 16 | } 17 | } 18 | 19 | } 20 | ret_val; -------------------------------------------------------------------------------- /tools/chrome-extension/src/detector/trap_elements_detector.js: -------------------------------------------------------------------------------- 1 | var ret_val = Array(); 2 | var res = trap_detector(); 3 | if (res < 0) ret_val = res; 4 | else { 5 | for (i = 0; i < res.length; i++) { 6 | ret_val[i] = res[i].outerHTML; 7 | } 8 | } 9 | ret_val; -------------------------------------------------------------------------------- /tools/chrome-extension/src/detector/unreachable_elements_detector.js: -------------------------------------------------------------------------------- 1 | var ret_val = Array(); 2 | var res = unreachable_detector(); 3 | if (res < 0) ret_val = res; 4 | else { 5 | for (i = 0; i < res.length; i++) { 6 | ret_val[i] = res[i].outerHTML; 7 | } 8 | } 9 | ret_val; -------------------------------------------------------------------------------- /tools/chrome-extension/src/devtool.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="UTF-8"> 5 | <title>SpatialNavigation</title> 6 | </head> 7 | <body> 8 | <h1>Hello!</h1> 9 | <script src="devtool.js"></script> 10 | </body> 11 | </html> -------------------------------------------------------------------------------- /tools/chrome-extension/src/devtool.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create('SpatialNavigation', 2 | 'icon', 3 | 'src/Panel.html', 4 | function(panel) { 5 | // code invoked on panel creation 6 | } 7 | ); 8 | -------------------------------------------------------------------------------- /tools/chrome-extension/src/popup.html: -------------------------------------------------------------------------------- 1 | <html> 2 | 3 | <head> 4 | <title>Spatial Navigation Settings</title> 5 | <link rel="stylesheet" href="popup.css"> 6 | </head> 7 | 8 | <body> 9 | <div class="align-center"> 10 | <input type="checkbox" id="switch"> 11 | <label for="switch" class="switch-label"> 12 | <span class="toggle--on">SpatialNavigation On</span> 13 | <span class="toggle--off">SpatialNavigation Off</span> 14 | </label> 15 | </div> 16 | <div id="optionArea"> 17 | <label class="popup-label">KeyMode</label> 18 | <div class="select aling "> 19 | <select id="keyMode" class="select-text"> 20 | <option value="ARROW">ARROW Key</option> 21 | <option value="SHIFTARROW">SHIFT + ARROW Key</option> 22 | </select> 23 | <span class="select-bar"></span> 24 | </div> 25 | <label class="popup-label">Screen Overlay</label> 26 | <div> 27 | <div class="md-radio"> 28 | <input id="visActive" type="radio" name="options" checked> 29 | <label for="visActive">Visible Active Only</label> 30 | </div> 31 | <div class="md-radio"> 32 | <input id="visNextTarget" type="radio" name="options"> 33 | <label for="visNextTarget">Visible Next Target</label> 34 | </div> 35 | <div class="md-radio"> 36 | <input id="visNone" type="radio" name="options"> 37 | <label for="visNone"> Visible Nothing </label> 38 | </div> 39 | </div> 40 | </div> 41 | <div id="status"></div> 42 | </body> 43 | <script src="popup.js"></script> 44 | </html> 45 | -------------------------------------------------------------------------------- /tools/chrome-extension/src/popup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * set keyMode 3 | * @param {string} mode keyMode string 4 | */ 5 | function setKeyOption(mode) { 6 | chrome.tabs.query({}, (tabs) => { 7 | const setCode = `window.__spatialNavigation__.keyMode = '${mode}'`; 8 | for (let i = 0; i < tabs.length; i++) { 9 | chrome.tabs.executeScript(tabs[i].id, { 10 | code: setCode 11 | }, (err) => { 12 | const e = chrome.runtime.lastError; 13 | if (e !== undefined) { 14 | console.log(tabs[i].id, err, e); 15 | } 16 | }); 17 | } 18 | }); 19 | } 20 | 21 | /** 22 | * save keyMode options. 23 | */ 24 | function onChangeOptions() { 25 | const isOn = document.getElementById('switch').checked; 26 | const mode = document.getElementById('keyMode').value; 27 | const isVisible = document.getElementById('visNextTarget').checked; 28 | const CurrentOn = document.getElementById('visActive').checked; 29 | const VisNone = document.getElementById('visNone').checked; 30 | const optionAreaDiv = document.getElementById('optionArea'); 31 | chrome.storage.local.set({ 32 | keyMode: mode, 33 | isOn, 34 | isVisible, 35 | CurrentOn, 36 | VisNone 37 | }, () => { 38 | // Update status to let user know options were saved. 39 | const status = document.getElementById('status'); 40 | if (isOn) { 41 | setKeyOption(keyMode.value); 42 | optionAreaDiv.style.display = 'block'; 43 | } else { 44 | setKeyOption('NONE'); 45 | optionAreaDiv.style.display = 'NONE'; 46 | } 47 | 48 | status.textContent = 'Options saved.'; 49 | setTimeout(() => { 50 | status.textContent = ''; 51 | }, 750); 52 | }); 53 | } 54 | 55 | /** 56 | * restore keyMode options. 57 | */ 58 | function restoreOptions() { 59 | // Use default value color = 'ARROW' and isOn = true. 60 | chrome.storage.local.get({ 61 | keyMode: 'ARROW', 62 | isOn: true, 63 | isVisible: false, 64 | CurrentOn: true, 65 | VisNone : false 66 | }, (items) => { 67 | document.getElementById('keyMode').value = items.keyMode; 68 | document.getElementById('switch').checked = items.isOn; 69 | document.getElementById('visNextTarget').checked = items.isVisible; 70 | document.getElementById('visActive').checked = items.CurrentOn; 71 | document.getElementById('visNone').checked = items.VisNone; 72 | 73 | const optionAreaDiv = document.getElementById('optionArea'); 74 | if (items.isOn) { 75 | setKeyOption(items.keyMode.value); 76 | optionAreaDiv.style.display = 'block'; 77 | } else { 78 | setKeyOption('NONE'); 79 | optionAreaDiv.style.display = 'NONE'; 80 | } 81 | }); 82 | } 83 | 84 | window.onload = restoreOptions; 85 | document.getElementById('switch').addEventListener('change', onChangeOptions); 86 | document.getElementById('keyMode').addEventListener('change', onChangeOptions); 87 | document.getElementById('visNextTarget').addEventListener('change', onChangeOptions); 88 | document.getElementById('visActive').addEventListener('change', onChangeOptions); 89 | document.getElementById('visNone').addEventListener('change', onChangeOptions); 90 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": [80485] 3 | , "contacts": ["marcoscaceres"] 4 | , "repo-type": "cg-report" 5 | } 6 | --------------------------------------------------------------------------------