浏览代码

Improve tab navigation

Closes #396.
Thibaut Courouble 9 年之前
父节点
当前提交
39b9846d78

+ 1 - 1
assets/javascripts/templates/pages/about_tmpl.coffee

@@ -15,7 +15,7 @@ app.templates.aboutPage = -> """
   <ul>
     <li>Created and maintained by <a href="http://thibaut.me">Thibaut Courouble</a>
     <li>Free and <a href="https://github.com/Thibaut/devdocs">open source</a>
-        <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=Thibaut&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe>
+        <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=Thibaut&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20" tabindex="-1"></iframe>
   </ul>
   <p>To keep up-to-date with the latest news:
   <ul>

+ 2 - 2
assets/javascripts/templates/pages/root_tmpl.coffee.erb

@@ -3,7 +3,7 @@ app.templates.splash = """<div class="_splash-title">DevDocs</div>"""
 <% if App.development? %>
 app.templates.intro = """
   <div class="_intro"><div class="_intro-message">
-    <a class="_intro-hide" data-hide-intro>Stop showing this message</a>
+    <a href="#" class="_intro-hide" data-hide-intro>Stop showing this message</a>
     <h2 class="_intro-title">Hi there!</h2>
     <p>Thanks for downloading DevDocs. Here are a few things you should know:
     <ol class="_intro-list">
@@ -27,7 +27,7 @@ app.templates.intro = """
 <% else %>
 app.templates.intro = """
   <div class="_intro"><div class="_intro-message">
-    <a class="_intro-hide" data-hide-intro>Stop showing this message</a>
+    <a href="#" class="_intro-hide" data-hide-intro>Stop showing this message</a>
     <h2 class="_intro-title">Welcome!</h2>
     <p>DevDocs combines multiple API documentations in a fast, organized, and searchable interface.
        Here's what you should know before you start:

+ 9 - 9
assets/javascripts/templates/sidebar_tmpl.coffee

@@ -3,7 +3,7 @@ templates = app.templates
 templates.sidebarDoc = (doc, options = {}) ->
   link  = """<a href="#{doc.fullPath()}" class="_list-item _icon-#{doc.icon} """
   link += if options.disabled then '_list-disabled' else '_list-dir'
-  link += """" data-slug="#{doc.slug}" title="#{doc.fullName}">"""
+  link += """" data-slug="#{doc.slug}" title="#{doc.fullName}" tabindex="-1">"""
   if options.disabled
     link += """<span class="_list-enable" data-enable="#{doc.slug}">Enable</span>"""
   else
@@ -14,10 +14,10 @@ templates.sidebarDoc = (doc, options = {}) ->
   link + "</span></a>"
 
 templates.sidebarType = (type) ->
-  """<a href="#{type.fullPath()}" class="_list-item _list-dir" data-slug="#{type.slug}"><span class="_list-arrow"></span><span class="_list-count">#{type.count}</span><span class="_list-text">#{type.name}</span></a>"""
+  """<a href="#{type.fullPath()}" class="_list-item _list-dir" data-slug="#{type.slug}" tabindex="-1"><span class="_list-arrow"></span><span class="_list-count">#{type.count}</span><span class="_list-text">#{type.name}</span></a>"""
 
 templates.sidebarEntry = (entry) ->
-  """<a href="#{entry.fullPath()}" class="_list-item _list-hover">#{$.escape entry.name}</a>"""
+  """<a href="#{entry.fullPath()}" class="_list-item _list-hover" tabindex="-1">#{$.escape entry.name}</a>"""
 
 templates.sidebarResult = (entry) ->
   addons = if entry.isIndex() and app.disabledDocs.contains(entry.doc)
@@ -25,17 +25,17 @@ templates.sidebarResult = (entry) ->
   else
     """<span class="_list-reveal" data-reset-list title="Reveal in list"></span>"""
   addons += """<span class="_list-count">#{entry.doc.short_version}</span>""" if entry.doc.version and not entry.isIndex()
-  """<a href="#{entry.fullPath()}" class="_list-item _list-hover _list-result _icon-#{entry.doc.icon}">#{addons}<span class="_list-text">#{$.escape entry.name}</span></a>"""
+  """<a href="#{entry.fullPath()}" class="_list-item _list-hover _list-result _icon-#{entry.doc.icon}" tabindex="-1">#{addons}<span class="_list-text">#{$.escape entry.name}</span></a>"""
 
 templates.sidebarNoResults = ->
   html = """ <div class="_list-note">No results.</div> """
   html += """
-    <div class="_list-note">Note: documentations must be <a class="_list-note-link" data-pick-docs>enabled</a> to appear in the search.</div>
+    <div class="_list-note">Note: documentations must be <a href="#" class="_list-note-link" data-pick-docs>enabled</a> to appear in the search.</div>
   """ unless app.isSingleDoc() or app.disabledDocs.isEmpty()
   html
 
 templates.sidebarPageLink = (count) ->
-  """<span class="_list-item _list-pagelink">Show more\u2026 (#{count})</span>"""
+  """<span role="link" class="_list-item _list-pagelink">Show more\u2026 (#{count})</span>"""
 
 templates.sidebarLabel = (doc, options = {}) ->
   label = """<label class="_list-item"""
@@ -47,7 +47,7 @@ templates.sidebarLabel = (doc, options = {}) ->
 templates.sidebarVersionedDoc = (doc, versions, options = {}) ->
   html = """<div class="_list-item _list-dir _list-rdir _icon-#{doc.icon}"""
   html += " open" if options.open
-  html + """"><span class="_list-arrow"></span>#{doc.name}</div><div class="_list _list-sub">#{versions}</div>"""
+  html + """" tabindex="0"><span class="_list-arrow"></span>#{doc.name}</div><div class="_list _list-sub">#{versions}</div>"""
 
 templates.sidebarDisabled = (options) ->
   """<h6 class="_list-title"><span class="_list-arrow"></span>Disabled (#{options.count})</h6>"""
@@ -56,7 +56,7 @@ templates.sidebarDisabledList = (html) ->
   """<div class="_disabled-list">#{html}</div>"""
 
 templates.sidebarDisabledVersionedDoc = (doc, versions) ->
-  """<a class="_list-item _list-dir _icon-#{doc.icon} _list-disabled" data-slug="#{doc.slug_without_version}"><span class="_list-arrow"></span>#{doc.name}</a><div class="_list _list-sub">#{versions}</div>"""
+  """<a class="_list-item _list-dir _icon-#{doc.icon} _list-disabled" data-slug="#{doc.slug_without_version}" tabindex="-1"><span class="_list-arrow"></span>#{doc.name}</a><div class="_list _list-sub">#{versions}</div>"""
 
 templates.sidebarPickerNote = """
   <div class="_list-note">Tip: for faster and better search results, select only the docs you need.</div>
@@ -67,9 +67,9 @@ sidebarFooter = (html) -> """<div class="_sidebar-footer">#{html}</div>"""
 
 templates.sidebarSettings = ->
   sidebarFooter """
+    <button type="button" class="_sidebar-footer-link _sidebar-footer-edit" data-pick-docs>Select documentation</button>
     <button type="button" class="_sidebar-footer-link _sidebar-footer-light" title="Toggle light" data-light>Toggle light</button>
     <button type="button" class="_sidebar-footer-link _sidebar-footer-layout" title="Toggle layout" data-layout>Toggle layout</button>
-    <a href="#" class="_sidebar-footer-link _sidebar-footer-edit" data-pick-docs>Select documentation</a>
   """
 
 templates.sidebarSave = ->

+ 2 - 0
assets/javascripts/views/layout/nav.coffee

@@ -9,11 +9,13 @@ class app.views.Nav extends app.View
     @deselect()
     if @current = @find "a[href='#{href}']"
       @current.classList.add @constructor.activeClass
+      @current.setAttribute 'tabindex', '-1'
     return
 
   deselect: ->
     if @current
       @current.classList.remove @constructor.activeClass
+      @current.removeAttribute 'tabindex'
       @current = null
     return
 

+ 5 - 4
assets/javascripts/views/search/search_scope.coffee

@@ -84,10 +84,11 @@ class app.views.SearchScope extends app.View
       if @doc and not @input.value
         $.stopEvent(event)
         @reset()
-    else if event.which is 9 or # tab
-            event.which is 32 and (app.isMobile() or $.isTouchScreen()) # space
-      $.stopEvent(event)
-      @search @input.value[0...@input.selectionStart]
+    else if not @doc and @input.value
+      if event.which is 9 or # tab
+         event.which is 32 and (app.isMobile() or $.isTouchScreen()) # space
+        @search @input.value[0...@input.selectionStart]
+        $.stopEvent(event) if @doc
     return
 
   extractHashValue: ->

+ 17 - 4
assets/javascripts/views/sidebar/doc_picker.coffee

@@ -21,14 +21,14 @@ class app.views.DocPicker extends app.View
       @render()
       @findByTag('input')?.focus()
       app.appCache?.on 'progress', @onAppCacheProgress
-      $.on @el, 'focus', @onFocus, true
+      $.on @el, 'focus', @onDOMFocus, true
     return
 
   deactivate: ->
     if super
       @empty()
       app.appCache?.off 'progress', @onAppCacheProgress
-      $.off @el, 'focus', @onFocus, true
+      $.off @el, 'focus', @onDOMFocus, true
     return
 
   render: ->
@@ -84,14 +84,27 @@ class app.views.DocPicker extends app.View
       input.name
 
   onClick: (event) =>
+    if @focusTimeout
+      clearTimeout @focusTimeout
+      @focusTimeout = null
     return if event.which isnt 1
     if event.target is @saveLink
       $.stopEvent(event)
       @save()
     return
 
-  onFocus: (event) ->
-    $.scrollTo event.target.parentNode, null, 'continuous', bottomGap: 2
+  onDOMFocus: (event) =>
+    target = event.target
+    if target.tagName is 'INPUT'
+      $.scrollTo target.parentNode, null, 'continuous', bottomGap: 2
+    else if target.classList.contains(app.views.ListFold.targetClass)
+      target.blur()
+      @focusTimeout = setTimeout =>
+        @listFold.open(target) unless target.classList.contains(app.views.ListFold.activeClass)
+        $('input', target.nextElementSibling).focus()
+        @focusTimeout = null
+      , 10
+    return
 
   onEnter: =>
     @save()

+ 2 - 0
assets/javascripts/views/sidebar/sidebar.coffee

@@ -101,9 +101,11 @@ class app.views.Sidebar extends app.View
       @reset()
     else if event.target.hasAttribute? 'data-light'
       $.stopEvent(event)
+      document.activeElement?.blur()
       app.document.toggleLight()
     else if event.target.hasAttribute? 'data-layout'
       $.stopEvent(event)
+      document.activeElement?.blur()
       app.document.toggleLayout()
     return
 

+ 2 - 2
assets/stylesheets/components/_header.scss

@@ -13,8 +13,6 @@
   background: $headerBackground;
   border-bottom: 1px solid $headerBorder;
   @extend %user-select-none;
-
-  a:focus { outline: 0; }
 }
 
 //
@@ -58,6 +56,8 @@
 }
 
 ._nav-current {
+  outline: 0;
+
   &:before, &:after { content: ''; }
 }
 

+ 4 - 2
assets/stylesheets/components/_sidebar.scss

@@ -32,8 +32,6 @@
     }
   }
 
-  a:focus { outline: 0; }
-
   ._sidebar-hidden & {
     display: none;
   }
@@ -88,6 +86,8 @@
     min-height: 100%;
     padding-bottom: 3.5rem;
   }
+
+  a:focus { outline: 0; }
 }
 
 ._list-title {
@@ -422,6 +422,8 @@
 }
 
 ._sidebar-footer-edit {
+  display: inline-block;
+
   @if $style == 'dark' {
     &:before { @extend %icon-settings-white; }
   } @else {

+ 7 - 0
assets/stylesheets/global/_base.scss

@@ -159,6 +159,8 @@ td {
   > pre:last-child, > p:last-child, > ul:last-child, > ol:last-child { margin-bottom: 0; }
 }
 
+section, main { outline: 0; }
+
 input, button {
   margin: 0;
   font-family: inherit;
@@ -179,6 +181,11 @@ button, input[type="search"] {
      -moz-appearance: none;
 }
 
+button:focus {
+  outline: 1px dotted;
+  outline: -webkit-focus-ring-color auto 5px;
+}
+
 input[type="search"]::-webkit-search-cancel-button,
 input[type="search"]::-webkit-search-decoration {
   -webkit-appearance: none;

+ 1 - 1
assets/stylesheets/global/_classes.scss

@@ -22,7 +22,7 @@
 %hide-text {
   white-space: nowrap;
   overflow: hidden;
-  color: transparent !important;
+  text-indent: -1000px;
   @extend %user-select-none;
 }
 

+ 2 - 2
views/app.erb

@@ -19,7 +19,7 @@
       <a href="/help" class="_nav-link">Tips</a>
     </nav>
   </header>
-  <section class="_sidebar">
+  <section class="_sidebar" tabindex="-1">
     <div class="_list" role="navigation">
       <% unless @doc %>
       <% App.docs.each do |slug, doc| %>
@@ -29,7 +29,7 @@
     </div>
   </section>
   <div class="_container" role="document">
-    <main class="_content _content-loading" role="main"></main>
+    <main class="_content _content-loading" role="main" tabindex="1"></main>
   </div>
 </div>
 <style data-size="<%= app_size %>" data-resizer>