GithubHelp home page GithubHelp logo

nickdjm / accessible-menu Goto Github PK

View Code? Open in Web Editor NEW
33.0 5.0 6.0 3.15 MB

A JavaScript library to help you generate WAI-ARIA accessible menus in the DOM.

License: ISC License

JavaScript 99.65% Shell 0.02% HTML 0.33%
javascript wcag menu menubar treeview disclosure-menu hoverable hoverable-dropdown navigation accessibility

accessible-menu's People

Contributors

chriskyfung avatar dependabot[bot] avatar mandrasch avatar nickdjm avatar vf-telwing avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

accessible-menu's Issues

Tabs shouldn't get trapped

Technically there's nothing stating that tabs should function like up/down arrow keys.

We'll just remove them from the equation for a smoother flow.

Add github basics

Need the following:

  • LICENSE file
  • README file
  • Issue templates
  • PR templates
  • Code of conduct
  • Contributing guidelines

Implement base IE support without needing babel polyfill

Since Babel Polyfill is deprecated, I think it would make sense for the 3 polyfills needed to just be built into the compiled minified code.

Array.from(), Array.includes(), and Array.find() are all the polyfills needed for accessible-menu to work in IE11 (not sure about older versions, but 11 is all that's supported anyways).

Solution

Make rollup include the 3 polyfills needed.

Sub-Level Indicators Improvement

Very nice! May I suggest this additional CSS that changes the sub-level indicators a bit.

nav {
  justify-content: center;
}
.menu .menu-item.dropdown .menu-item > .dropdown-toggle::after {
  content: "►";
  margin-left: 0;
  float: right;
}
@media screen and (max-width: 1069px) {
  .menu .menu-item.dropdown > .dropdown-toggle::after {
    margin-left: 0;
    float: right;
  }
  .menu .menu-item.dropdown .menu-item > .dropdown-toggle::after {
    content: "▼";
  }
  .menu .menu-item.dropdown.show > .dropdown-toggle::after {
    content: "▲";
  }
}

Expanding Current Location

Hullo! So opening caveat, I'm a competent infrastructure/backend developer whose javascript and frontend are both much less polished.

That out of the way, I'm trying to use accessible-menu to build accessible disclosure navigation menus for both a site (with multiple "books") and the books via hugo. templating.

For site navigation, this is less of an issue, but for navigation within a book, it would be extremely useful to be able to apply some automatic styling to indicate where in the book's navigation you currently are - possibly just by defaulting that section to expanded?

I've been introspecting on the initialized AccessibleMenu.DisclosureMenu object but can't seem to figure out a way to discover the appropriate elements and toggle them. If I tab through things and introspect, I can use foo.currentMenuItem.menuElements.toggle.expand() but I'm not sure I understand how to do this programmatically on page load.

Is this a wild anti-pattern, to pre-expand a disclosure menu to the current page? If not, I'd love to understand how to do this with accessible-menu.

Thanks in advance (and it's okay if you don't have time/interest in answering this! 💜)

Left/Right arrows shouldn't enter submenus

When you're in a submenu and you hit the left/right arrows, the menu opens and focuses the first child.

The proper action to take for this would be the open the submenu, but keep focus on the root menu's menu item.

Feat: Sub-menus should use the parent menu's settings at all times

Allowing the menu to update on-the-fly based on changing props would be super useful (especially since we're using this in Vue projects quite a bit).

Things like changing hoverability or hover delay and having the menu/submenus update immediately without the need to re-initialize would not only be convenient, but also remove some strange bugs that happen with multiple events being triggered.

Feat: Focus clicked menu item

When you click an item inside of a menu, we should focus it at well so that if a user switches to a keyboard after clicking the flow isn't broken.

Bug: Up/down arrows move screen on initial opening of a submenu

Summary

When you have a root menu item targeted and you hit the up/down arrows to enter the sub-menu, it moves the screen once.

Steps

  1. Tab to a menu
  2. Enter the menu
  3. Navigate to a root menu item with a submenu using the arrow keys
  4. Hit the up or down arrow to enter the menu
  5. The screen moves up or down

Expected Behaviour

The screen shouldn't move when up/down is hit on a menu item containing a submenu.

Toggle ☰ with ×

<button href="" class="menu-toggle"><span>☰</span><span>×</span><span class="visually-hidden"> Toggle Menu</span></button>
#main-menu > .menu-toggle span:nth-child(2) { display: none; }
#main-menu > .menu-toggle[aria-expanded="true"] span:nth-child(1) {
  display: none;
}
#main-menu > .menu-toggle[aria-expanded="true"] span:nth-child(2) {
  display: inline;
}

Feat: Provide option for disclosure menus

As per recommendation from David MacDonald a lot of blind users are used to using Disclosure menus instead of Menubars, so having an option (perhaps even the default option) for the Accessible Menu to be a Disclosure menu would be beneficial.

Spec of Disclosures: https://www.w3.org/TR/2019/NOTE-wai-aria-practices-1.1-20190814/#disclosure

Example of Disclosure menu: https://www.w3.org/TR/2019/NOTE-wai-aria-practices-1.1-20190814/examples/disclosure/disclosure-navigation.html

This is going to be a pretty big overhaul.

  • Implement BaseMenu class
    • Split out current functionality into Menubar class
    • Create DisclosureMenu class
  • Ensure tests can handle both Menubars and DisclosureMenus
  • Update documentation
  • Ensure IE functionality in both menus

Feat: Add bundle for drop-in usage

Right now this package will only work with JS module-based projects (anything that can import/export files).

For developers using more static development (like Drupal theming) we'll need to have a bundled version of the code that can just be included on a page and run.

Going to look into Rollup for resolve this.

Bug: Menubars lose keyboard functionality when menu is expanded

Summary

Submenus within Menubars no longer respond to key events in Safari.

I suspect this is actually a change in Safari because I've gone back from v1.1.0 through dev and nothing works.

Steps

  1. Go to any of the existing Menubar tests
  2. Tab to a Menubar
  3. Use left/right arrows to get to a submenu
  4. Hit enter to open the submenu
  5. Try to navigate through the submenu using keys

Expected Behaviour

You should be able to enter/navigate through submenus using key bindings.

Accessibility: Roles are incorrect on menu items

Roles on menu items should be:

<li role="none">
  <a href="#" roles="menuitem">...</a>
</li>

They are currently:

<li role="menuitem">
  <a href="#">...</a>
</li>

Just need to change the initializer for menu items to add the correct roles to both the li and the a elements.

Bug: Top-level dropdowns don't close when clicked away

Summary

When you open a top-level dropdown menu and click away from it, the menu stays open.

Steps

What are the steps to reproduce this issue?

  1. Expand a top-level dropdown menu
  2. Click anywhere outside of the menu
  3. The menu stays open.

Expected Behaviour

The menu should close when you click outside of it.

Bug: Circular dependencies

I noticed you recently merged browser module support into next to help resolve issues with circular dependencies, but they are still present. There are loops between BaseMenu/BaseMenuToggle and validate.js because of the isMenu() and isMenuToggle() implementations.

One solution could be using Symbol to act as the unique identifier for a class. I quickly tested this idea by creating a file containing the symbols:

// _baseTypes.js
export const baseMenu = Symbol('BaseMenu');
export const baseMenuToggle = Symbol('BaseMenuToggle');

Then, adding a Symbol member to each respective class:

// _baseMenu.js
import { baseMenu } from "./_baseTypes.js";

class BaseMenu {
    // ...
    get [baseMenu]() {
        return true;
    }
}

It would then be possible to rely on the Symbol for validation rather than the class name (thus avoiding the circular import):

// validate.js
import { baseMenu } from "./_baseTypes.js";

function isMenu(element) {
    // ...
    if (!(element[baseMenu])) {
        // ...
    }
}

I realize this is probably not ideal and an extra Symbol polyfill would be required for IE, but I wanted to at least try and provide a solution.

Feat: Emit event when switching between visible and hidden state

Hi

First off: Huge thank you for this awesome package! I love how much it adds to our websites in terms of accessibility.

One thing I would like to do is bind the state of other elements to the state of a menu. For example: When a Menubar shows, I'd like to also show a backdrop obscuring everything but the menu to reduce distractions from the rest of the website; when the MenuBar hides again, I'd like to also hide the backdrop. I believe, one way to achieve such a thing would be to let MenuToggle extend EvenTarget and have it emit an Event in e.g. the open, close or toggle methods. This way, one could do something like this:

const myMenu = AccessibleMenu.Menubar({
  // Options
})
myMenu.dom.controller.addEventListener('stateChange', evt => {
  // do something depending on the menu's state, e.g. show or hide backdrop
})

Change hoverability after initialization

Hi Nick,
First of all, thanks for the great work! I'm trying to change the isHoverable setting after initialization, depending on screen size, because my menu changes to a hamburger menu below a certain screen width. At that point I would like to disable hoverability.

I setup a DisclosureMenu like this:

let mainMenu = new AccessibleMenu.DisclosureMenu({
      menuElement: document.querySelector('ul.nav'),
      menuItemSelector: '.menu-item',
      menuLinkSelector: '.menu-item > a',
      submenuItemSelector: '.menu-item-has-children',
      submenuToggleSelector: '.menu-item-has-children > a',
      submenuSelector: '.sub-menu',
      openClass: 'open',
      isHoverable: true,
});

And I change the isHoverable setting to false when the window is below a certain width (using jQuery):

$(window).on('load resize', function(){
    if($(window).width() <= 860) {
        mainMenu.isHoverable = false;
    }
});

When I console.log the mainMenu after that, I can see that the hoverable setting is indeed false, but the mouseenter and mouseleave events are still working.

I tried to create a new DisclosureMenu, but the events are still there from the 'old' one.
I tried to run the initialize() method on the mainMenu after changing isHoverable().

I think that there is currently no way to remove the event listeners on the submenu toggles, unless I do it manually. Or to destroy (and by that I mean destroy all event listeners) a DisclosureMenu. I hope you understand what I'm trying to do and hopefully can point me in the right direction.

Minify dist

Should provide a minified version of the accessibleMenu.js file (along with a non-minified version).

Feat: Add dynamic hovering state

I was reading some of the aria practices elements and I took a look at the Editor Menubar Example where it mentioned some hover functionality for menubars:

  1. In general, moving focus in response to mouse hover is avoided in accessible widgets; it causes unexpected context changes for keyboard users. However, like desktop menubars, there are two conditions in this example menubar where focus moves in response to hover in order to help maintain context for users who use both keyboard and mouse:
    1. After a parent menu item in the menubar has been activated and the user hovers over a different parent item in the menubar, focus will follow hover.
    2. When a submenu is open and the user hovers over an item in the submenu, focus follows hover.

It would be super cool to add this functionality to menubars (and possibly disclosure menus).

Use proper keybinds

Using the following spec: https://www.w3.org/WAI/tutorials/menus/application-menus/

We'll need to fix keybindings to match.

  • Left/Right arrows should move between top-level menus
  • Up/down arrows should move between sub-level menus

The only catch here would be multi-level dropdowns, through those aren't technically accessible anyways, so we may just cascade the arrow keys down...

If you're in a proper menu:

  • Left/Right will move top level items
  • Up/Down will move current sub level items

If you're in a menu with too many dropdowns

  • Left/Right will move the parent's menu items
  • Up/Down will move current level items

This isn't ideal, but there isn't a great way to handle people who love their mega menus.

Accessibility: Space does not open root dropdowns

Summary

Root-level dropdown items do not open when you hit space. There is not event handler for them.

Steps

  1. Tab into a menu with a dropdown item inside of it's root level,
  2. use arrows to navigate to the dropdown item,
  3. hit space, and
  4. the menu doesn't open

Expected Behaviour

The dropdown menu should open if space is hit.

Bug: Home/End keys don't work on some keyboards

Summary

In MacOS, some keyboards have their Home/End keys actually bound to the Meta + Left/Right Arrows.

Steps

  1. Go to a page using accessible-menu in any browser
  2. Enter a menu
  3. Hit the Home or End key
  4. The next/previous menu item is selected

Expected Behaviour

The Home key should select the first menu item in the menu.

The End key should select the last menu item in the menu.

Other notes

  • I haven't tested this in Linux/Windows yet
  • I'm not 100% sure this will be fixed as Meta + Arrow Keys do other things in some browsers- I don't know if we want to hijack that functionality.

Advanced documentation

The project needs more in-depth documentation.

Stuff like basic menus, sub menus, top-level dropdown menus, what each class expects as argument, how things are validated, etc.

Feat: A-Z support

As-per WAI-ARIA specifications, when a user is inside of a menu, typing any letter from A-Z:

  • Moves focus to the next menu item with a label that starts with the typed character if such an menu item exists.
  • Otherwise, focus does not move.

This isn't implemented within the current classes, so we'll need to do that.

Upcoming 2.x and 1.x release todos

I believe accessible-menu is ready for a new release, but there are a couple chores to do before hand since there have been a handful of bug fixes (particularly for 2.x).

1.x

The 1.x release is going to be very few and far between. The only reason this is getting an update is because of the Safari issue in #99 since this actually breaks all previous versions of accessible-menu.

  • Ensure dependencies are as up-to-date as possible
  • Make sure build system works
  • Test in Safari (desktop/iOS)
  • Test in IE
  • Test in Chrome (desktop/Android)
  • Test in Firefox (desktop/Android)
  • Test in Edge (desktop/Android)
  • Test in Opera (desktop/Android)

2.x

  • Ensure dependencies are as up-to-date as possible
  • Make sure build system works
  • Test in Safari (desktop/iOS)
  • Test in IE
  • Test in Chrome (desktop/Android)
  • Test in Firefox (desktop/Android)
  • Test in Edge (desktop/Android)
  • Test in Opera (desktop/Android)

Once all the above tasks are done a new release for 1.x an 2.x will be made.

Browser compatability

I want to make sure this works with at least the latest 2 versions of most browsers.

Once we can confirm browser support, we can use this to generate something to put in the readmen.

Feat: Add additional sub-menu functionality

See https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-1/menubar-1.html

If inside of a submenu...

The right arrow should do the following:

  • If focus is on an item with a submenu, opens the submenu and places focus on the first item.
  • If focus is on an item that does not have a submenu:
    • Closes submenu.
    • Moves focus to next item in the menubar.
    • Opens submenu of newly focused menubar item, keeping focus on that parent menubar item.

And the left arrow should do the following:

  • Closes submenu and moves focus to parent menu item.
  • If parent menu item is in the menubar, also:
    • moves focus to previous item in the menubar.
    • Opens submenu of newly focused menubar item, keeping focus on that parent menubar item.

Feat: Handle top-level dropdown menus

Issue

Right now, there isn't a great way to handle the root menu being a dropdown menu (or submenu).

Solution

I think the best way to go about fixing this would be to change the logic of how the classes work.

Instead of having: Menu; MenuItem; and SubmenuItem, we should have: Menu; Submenu; and MenuItem.

This would allow us to initialize the root menu as a Submenu (Maybe rename this to DropdownMenu to make a little more sense for a root-menu).

This would be a breaking change, but also I think it would mark a good beta release point.

Remove need to initialize

There isn't really a point to force users to have to call initialize() for everything to work.

The menu should just initialize itself once its constructed.

Feat: Upgrade guide

Since there are going to be many breaking changes and new features in v3.x it would be good to have an upgrade guide to go from v2.x to v3.x.

Some notes:

  • File structure change in source directory
  • DisclosureMenu no longer having the optional key board support by default

Accessibility: Auto-generated IDs can start with a "-"

Summary

What an ID is generated through a MenuToggle, it can lead to accessibility violations.

If the innerText of a toggle starts with a space is it replaced with a -, which is technically an accessibility violation. The same issue can also cause many dashes to be added to the IDs if there are spacing issues with the DOM.

Another side-effect of the current way IDs are generated is if a developer is using aria-label instead of innerText nothing is added to the ID string, which also causes it to start with a dash since the the string is essentially: [innerText]-[randomString]-[menu/menu-button].

Expected Behaviour

I think the best way to go about fixing this will be to do a couple things:

  1. If there is no innerText, see if there's an aria-label that can be used,
  2. Replace any amount of spaces with a single -,
  3. Make sure the string generated by innerText/aria-label does not start or end with a -, and
  4. Make the final generated ID check to see if the generated string is empty before adding it to the beginning of the rest of the ID. Should fall-back to just [randomString]-[menu/menu-button]

Screenshots

You can see the dash being added here because there is no innerText

<button class="navbar-toggler" type="button" aria-expanded="false" aria-label="Toggle navigation" aria-haspopup="true" id="-qefmxrqg-menu-button" aria-controls="-qefmxrqg-menu">
  <span class="navbar-toggler-icon"></span>
</button>

Here's a Bootstrap 3/Drupal example where the generated markup is causing a load of extras dashes.

<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse" aria-haspopup="true" aria-expanded="false" role="button" id="-------------toggle-navigation---------------------------------------------------uikbq-menu-button" aria-controls="-------------toggle-navigation---------------------------------------------------uikbq-menu">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>

Spec

Feat: Insert aria-controls on submenu toggles

As per WAI-ARIA specifications menu toggle buttons should have an aria-controls attributes with the ID of the menu it controls.

To implement this we'll first need to ensure all menus have IDs (we'll need to find a unique way of generating them if they don't exist), and set that in the initialization()method of SubmenuItems.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.