Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

aria-components

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aria-components - npm Package Compare versions

Comparing version 0.3.1 to 0.3.2-beta-1

18

CHANGELOG.md
# Change Log
This project adheres to [Semantic Versioning](http://semver.org/).
## 0.3.2-beta-1
**Changed**
- Loosens MenuBar and Menu components' menuitems' markup requirements (#48, 3385f2e)
- Dialog no longer re-queries for interactive child elements on every TAB keydown (167cc70)
**Added**
- Adds support for validating Menu & MenuBar menu items (#49)
- Adds support for refreshing elements tracked by components: MenuBar and Menu items, interactive child elements
within Dialog, Disclosure, MenuButton and Popup targets, and Listbox options (#50)
- MenuBar Popup state changes now update state in the MenuBar itself (2e1dcb1)
**Fixed**
- Scopes MenuBar Popup events to the controller (0374543)
## 0.3.1

@@ -5,0 +23,0 @@

2

package.json
{
"name": "aria-components",
"version": "0.3.1",
"version": "0.3.2-beta-1",
"description": "JavaScript classes to aid in accessible web development.",

@@ -5,0 +5,0 @@ "keywords": [

@@ -108,2 +108,3 @@ import AriaComponent from '../AriaComponent';

// Bind class methods
this.setInteractiveChildren = this.setInteractiveChildren.bind(this);
this.onPopupStateChange = this.onPopupStateChange.bind(this);

@@ -132,2 +133,17 @@ this.handleTargetKeydown = this.handleTargetKeydown.bind(this);

/**
* Collect the Dialog's interactive child elements.
*/
setInteractiveChildren() {
this.interactiveChildElements = interactiveChildren(this.target);
const [
firstInteractiveChild,
lastInteractiveChild,
] = getFirstAndLastItems(this.interactiveChildElements);
// Save as instance properties.
Object.assign(this, { firstInteractiveChild, lastInteractiveChild });
}
/**
* Set the component's DOM attributes and event listeners.

@@ -155,7 +171,7 @@ */

/*
* Collect the Dialog's interactive child elements. This is an initial pass
* to ensure values exists, but the interactive children will be collected
* each time the dialog opens, in case the dialog's contents change.
* This is an initial pass to ensure values exists, but the interactive
* children will be collected each time the dialog opens, in case the
* dialog's contents change.
*/
this.interactiveChildElements = interactiveChildren(this.target);
this.setInteractiveChildren();

@@ -203,3 +219,3 @@ // Add event listeners.

this.interactiveChildElements = interactiveChildren(this.target);
this.setInteractiveChildren();

@@ -247,8 +263,4 @@ if (expanded) {

const { activeElement } = document;
const [
firstInteractiveChild,
lastInteractiveChild,
] = getFirstAndLastItems(this.interactiveChildElements);
if (shiftKey && firstInteractiveChild === activeElement) {
if (shiftKey && this.firstInteractiveChild === activeElement) {
event.preventDefault();

@@ -259,4 +271,4 @@ /*

*/
lastInteractiveChild.focus();
} else if (! shiftKey && lastInteractiveChild === activeElement) {
this.lastInteractiveChild.focus();
} else if (! shiftKey && this.lastInteractiveChild === activeElement) {
event.preventDefault();

@@ -267,3 +279,3 @@ /*

*/
firstInteractiveChild.focus();
this.firstInteractiveChild.focus();
}

@@ -270,0 +282,0 @@ }

@@ -98,2 +98,7 @@ Dialog

/**
* Collect the Dialog's interactive child elements.
*/
setInteractiveChildren();
/**
* Destroy the Dialog and Popup.

@@ -100,0 +105,0 @@ */

@@ -93,2 +93,3 @@ import AriaComponent from '../AriaComponent';

// Bind class methods.
this.setInteractiveChildren = this.setInteractiveChildren.bind(this);
this.init = this.init.bind(this);

@@ -110,2 +111,30 @@ this.destroy = this.destroy.bind(this);

/**
* Collect the Disclosure's interactive child elements.
*/
setInteractiveChildren() {
const { expanded } = this.state;
/**
* Collect the target element's interactive child elements.
*
* @type {array}
*/
this.interactiveChildElements = interactiveChildren(this.target);
/**
* Allow or deny keyboard focus depending on component state.
*
* Prevent focus on interactive elements in the target when the target is
* hidden. This isn't such an issue when the target is hidden with
* `display:none`, but is necessary if the target is hidden by other means,
* such as minimized height or width.
*/
if (expanded) {
tabIndexAllow(this.interactiveChildElements);
} else {
tabIndexDeny(this.interactiveChildElements);
}
}
/**
* Add initial attributes, establish relationships, and listen for events

@@ -123,8 +152,2 @@ */

/**
* Collect the target element's interactive child elements.
* @type {array}
*/
this.interactiveChildElements = interactiveChildren(this.target);
// Ensure the target and controller each have an ID attribute.

@@ -181,9 +204,4 @@ [this.controller, this.target].forEach((element) => {

/*
* Prevent focus on interactive elements in the target when the target is
* hidden. This isn't such an issue when the target is hidden with
* `display:none`, but is necessary if the target is hidden by other means,
* such as minimized height or width.
*/
tabIndexDeny(this.interactiveChildElements);
// Collect the target element's interactive child elements.
this.setInteractiveChildren();

@@ -190,0 +208,0 @@ // Run {initCallback}

@@ -86,2 +86,7 @@ Disclosure

/**
* Collect the Disclosure's interactive child elements.
*/
setInteractiveChildren();
/**
* Remove all ARIA attributes added by this class.

@@ -88,0 +93,0 @@ */

@@ -76,2 +76,3 @@ import AriaComponent from '../AriaComponent';

// Bind class methods.
this.setListBoxOptions = this.setListBoxOptions.bind(this);
this.preventWindowScroll = this.preventWindowScroll.bind(this);

@@ -93,11 +94,5 @@ this.handleControllerKeyup = this.handleControllerKeyup.bind(this);

/**
* Set up the component's DOM attributes and event listeners.
* Collect and configure ListBox options.
*/
init() {
/*
* A reference to the class instance added to the controller and target
* elements to enable external interactions with this instance.
*/
super.setSelfReference([this.controller, this.target]);
setListBoxOptions() {
/**

@@ -108,10 +103,4 @@ * The target list items.

*/
this.options = Array.prototype.slice.call(this.target.children, 0);
this.options = Array.from(this.target.children);
/**
* Initialize search.
* @type {Search}
*/
this.search = new Search(this.options);
/*

@@ -131,2 +120,23 @@ * Set the `option` role for each list itme and ensure each has a unique ID.

/**
* Initialize search.
*
* @type {Search}
*/
this.search = new Search(this.options);
}
/**
* Set up the component's DOM attributes and event listeners.
*/
init() {
/*
* A reference to the class instance added to the controller and target
* elements to enable external interactions with this instance.
*/
super.setSelfReference([this.controller, this.target]);
// Set up initial ListBox options.
this.setListBoxOptions();
/**
* The initial default state.

@@ -133,0 +143,0 @@ *

@@ -71,2 +71,7 @@ Listbox

/**
* Collect and configure ListBox options.
*/
setListBoxOptions();
/**
* Destroy the Listbox and Popup.

@@ -73,0 +78,0 @@ */

@@ -71,2 +71,12 @@ import AriaComponent from '../AriaComponent';

/**
* Selector used to validate menu items.
*
* This can also be used to exclude items that would otherwise be given a
* "menuitem" role; e.g., `:not(.hidden)`.
*
* @type {string}
*/
itemMatches: '*',
/**
* Callback to run after the component initializes.

@@ -90,2 +100,3 @@ *

// Bind class methods
this.setMenuItems = this.setMenuItems.bind(this);
this.handleListKeydown = this.handleListKeydown.bind(this);

@@ -103,21 +114,9 @@ this.destroy = this.destroy.bind(this);

*/
init() {
/*
* A reference to the class instance added to the controller and target
* elements to enable external interactions with this instance.
*/
super.setSelfReference([this.list]);
/*
* Add the 'menu' role to signify a widget that offers a list of choices to
* the user, such as a set of actions or functions.
*/
this.list.setAttribute('role', 'menu');
setMenuItems() {
/**
* The list's child elements.
* The submenu Disclosures.
*
* @type {array}
*/
this.listItems = Array.prototype.slice.call(this.list.children);
this.disclosures = [];

@@ -129,6 +128,15 @@ /**

*/
this.menuItems = this.listItems.reduce((acc, item) => {
const itemLink = item.firstElementChild;
this.menuItems = Array.from(this.list.children).reduce((acc, item) => {
const [firstChild, ...theRest] = Array.from(item.children);
if (null !== itemLink && 'A' === itemLink.nodeName) {
// Try to use the first child of the menu item.
let itemLink = firstChild;
// If the first child isn't a link or button, find the first instance of either.
if (null === itemLink || ! itemLink.matches('a,button')) {
[itemLink] = Array.from(theRest)
.filter((child) => child.matches('a,button'));
}
if (undefined !== itemLink && itemLink.matches(this.itemMatches)) {
return [...acc, itemLink];

@@ -146,21 +154,2 @@ }

/**
* The number of menu items.
*
* @type {number}
*/
this.menuItemsLength = this.menuItems.length;
/**
* Listen for keydown events on the menu.
*/
this.list.addEventListener('keydown', this.handleListKeydown);
/**
* The submenu Disclosures.
*
* @type {array}
*/
this.disclosures = [];
/*

@@ -177,3 +166,3 @@ * Set menu link attributes and instantiate submenus.

// Add size and position attributes.
link.setAttribute('aria-setsize', this.menuItemsLength);
link.setAttribute('aria-setsize', this.menuItems.length);
link.setAttribute('aria-posinset', index + 1);

@@ -194,3 +183,7 @@

// Instantiate sub-Menus.
const subList = new Menu({ list: siblingList });
const subList = new Menu({
list: siblingList,
itemMatches: this.itemMatches,
});
// Save the list's previous sibling.

@@ -204,3 +197,28 @@ subList.previousSibling = link;

Object.assign(this, { firstItem, lastItem });
}
/**
* Initialize the Menu.
*/
init() {
/*
* A reference to the class instance added to the controller and target
* elements to enable external interactions with this instance.
*/
super.setSelfReference([this.list]);
/*
* Add the 'menu' role to signify a widget that offers a list of choices to
* the user, such as a set of actions or functions.
*/
this.list.setAttribute('role', 'menu');
// Set menuitem roles and attributes, including any submenu Disclosures.
this.setMenuItems();
/**
* Listen for keydown events on the menu.
*/
this.list.addEventListener('keydown', this.handleListKeydown);
// Run {initCallback}

@@ -207,0 +225,0 @@ this.onInit.call(this);

@@ -25,2 +25,12 @@ Menu

/**
* Selector used to validate menu items.
*
* This can also be used to exclude items that would otherwise be given a
* "menuitem" role; e.g., `:not(.hidden)`.
*
* @type {string}
*/
itemMatches: '*',
/**
* Callback to run after the component initializes.

@@ -48,2 +58,9 @@ *

/**
* Set menu items.
*
* Use this if your menu is dynamically updated.
*/
setMenuItems();
/**
* Destroy the Menu and any submenus.

@@ -50,0 +67,0 @@ */

@@ -77,2 +77,12 @@ import AriaComponent from '../AriaComponent';

/**
* Selector used to validate menu items.
*
* This can also be used to exclude items that would otherwise be given a
* "menuitem" role; e.g., `:not(.hidden)`.
*
* @type {string}
*/
itemMatches: '*',
/**
* Callback to run after the component initializes.

@@ -110,2 +120,4 @@ *

// Bind class methods.
this.setMenuBarItems = this.setMenuBarItems.bind(this);
this.setMenuBarSubMenuItems = this.setMenuBarSubMenuItems.bind(this);
this.handleMenuBarKeydown = this.handleMenuBarKeydown.bind(this);

@@ -115,2 +127,3 @@ this.handleMenuBarClick = this.handleMenuBarClick.bind(this);

this.stateWasUpdated = this.stateWasUpdated.bind(this);
this.onPopupStateChange = this.onPopupStateChange.bind(this);
this.destroy = this.destroy.bind(this);

@@ -127,20 +140,4 @@

*/
init() {
/*
* A reference to the class instance added to the controller and target
* elements to enable external interactions with this instance.
*/
super.setSelfReference([this.list]);
// Set the menu role.
this.list.setAttribute('role', 'menubar');
setMenuBarItems() {
/**
* The menubar's child elements.
*
* @type {array}
*/
this.menuBarChildren = Array.prototype.slice.call(this.list.children);
/**
* Collected menubar links.

@@ -150,6 +147,15 @@ *

*/
this.menuBarItems = this.menuBarChildren.reduce((acc, item) => {
const itemLink = item.firstElementChild;
this.menuBarItems = Array.from(this.list.children).reduce((acc, item) => {
const [firstChild, ...theRest] = Array.from(item.children);
if (null !== itemLink && 'A' === itemLink.nodeName) {
// Try to use the first child of the menu item.
let itemLink = firstChild;
// If the first child isn't a link or button, find the first instance of either.
if (null === itemLink || ! itemLink.matches('a,button')) {
[itemLink] = Array.from(theRest)
.filter((child) => child.matches('a,button'));
}
if (undefined !== itemLink && itemLink.matches(this.itemMatches)) {
return [...acc, itemLink];

@@ -163,2 +169,3 @@ }

* Initialize search.
*
* @type {Search}

@@ -168,9 +175,2 @@ */

/**
* The number of menubar items.
*
* @type {number}
*/
this.menuLength = this.menuBarItems.length;
/*

@@ -184,3 +184,3 @@ * Set menubar link attributes.

// Add size and position attributes.
link.setAttribute('aria-setsize', this.menuLength);
link.setAttribute('aria-setsize', this.menuBarItems.length);
link.setAttribute('aria-posinset', index + 1);

@@ -199,14 +199,17 @@

/**
* A mouse 'click' event.
*
* @type {MouseEvent}
/*
* Set up tabindex.
* The first item, by default, is "allowed", but if any of the menu items is
* active, it should be allowed.
*/
this.clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
});
const allowedItem = this.menuBarItems.includes(document.activeElement)
? document.activeElement
: this.firstItem;
rovingTabIndex(this.menuBarItems, allowedItem);
}
// Initialize popups for nested lists.
/**
* Initialize Menus and Popups for nested lists.
*/
setMenuBarSubMenuItems() {
const { popups, subMenus } = this.menuBarItems.reduce((acc, controller) => {

@@ -224,2 +227,3 @@ const target = controller.nextElementSibling;

onInit: this.onPopupInit,
onStateChange: this.onPopupStateChange,
type: 'menu',

@@ -241,3 +245,3 @@ });

// Initialize submenu Menus.
const subMenu = new Menu({ list });
const subMenu = new Menu({ list, itemMatches: this.itemMatches });
target.addEventListener('keydown', this.handleMenuItemKeydown);

@@ -254,4 +258,35 @@

Object.assign(this, { popups, subMenus });
}
/**
* Initialize the Menu.
*/
init() {
/*
* A reference to the class instance added to the controller and target
* elements to enable external interactions with this instance.
*/
super.setSelfReference([this.list]);
// Set the menu role.
this.list.setAttribute('role', 'menubar');
// Set menuitem roles and attributes.
this.setMenuBarItems();
/**
* A mouse 'click' event.
*
* @type {MouseEvent}
*/
this.clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
});
// Initialize Menus and Popups for nested lists.
this.setMenuBarSubMenuItems();
/**
* Set initial state.

@@ -267,5 +302,2 @@ *

// Set up initial tabindex.
rovingTabIndex(this.menuBarItems, this.firstItem);
// Run {initCallback}

@@ -293,2 +325,11 @@ this.onInit.call(this);

/**
* Track Popup state changes.
*
* @param {boolean} options.expanded The Popup state,
*/
onPopupStateChange({ expanded }) {
this.setState({ expanded });
}
/**
* Handle keydown events on the menuList element.

@@ -329,3 +370,3 @@ *

// Close the popup.
if (popup) {
if (false !== popup) {
popup.hide();

@@ -348,3 +389,3 @@ }

case DOWN: {
if (popup) {
if (false !== popup && event.target === popup.controller) {
event.stopPropagation();

@@ -351,0 +392,0 @@ event.preventDefault();

@@ -19,2 +19,12 @@ MenuBar

/**
* Selector used to validate menu items.
*
* This can also be used to exclude items that would otherwise be given a
* "menuitem" role; e.g., `:not(.hidden)`.
*
* @type {string}
*/
itemMatches: '*',
/**
* Callback to run after the component initializes.

@@ -63,2 +73,12 @@ *

/**
* Collect top-level menu items and set up event handlers.
*/
setMenuBarItems();
/**
* Initialize Menus and Popups for nested lists.
*/
setMenuBarSubMenuItems();
/**
* Destroy the MenuBar and any submenu Popups.

@@ -92,7 +112,10 @@ */

If a menu item has a sibling, that sibling will be turned into a "submenu popup".
If that element is not a UL element, the script will search the popup target with
`Element.querySelector('ul')` and use that as the `list` passed to the `Menu`
component.
The first anchor or button element found in each item will be used as the
`role="menuitem"`.
If a `menuitem` has a `nextElementSibling`, that element will be turned into a
"submenu popup". If a submenu popup's element is not a UL element, the script
will search the popup target with `Element.querySelector('ul')` and use that as
the `list` passed to the `Menu` component.
## Example

@@ -99,0 +122,0 @@

@@ -87,2 +87,3 @@ import AriaComponent from '../AriaComponent';

this.init = this.init.bind(this);
this.setInteractiveChildren = this.setInteractiveChildren.bind(this);
this.stateWasUpdated = this.stateWasUpdated.bind(this);

@@ -116,13 +117,8 @@ this.hide = this.hide.bind(this);

/**
* Set up the component's DOM attributes and event listeners.
* Collect and prepare the target element's interactive child elements.
*/
init() {
/*
* A reference to the class instance added to the controller and target
* elements to enable external interactions with this instance.
*/
super.setSelfReference([this.controller, this.target]);
setInteractiveChildren() {
const { expanded } = this.state;
/**
* Collect the target element's interactive child elements.
* The target element's interactive child elements.
*

@@ -133,4 +129,11 @@ * @type {array}

// Focusable content should initially have tabindex='-1'.
tabIndexDeny(this.interactiveChildElements);
/*
* Allow/deny tabbing to interactive children.
* Focusable content should have tabindex='-1' unless the popup is expanded.
*/
if (expanded) {
tabIndexAllow(this.interactiveChildElements);
} else {
tabIndexDeny(this.interactiveChildElements);
}

@@ -149,3 +152,17 @@ /*

}
}
/**
* Set up the component's DOM attributes and event listeners.
*/
init() {
/*
* A reference to the class instance added to the controller and target
* elements to enable external interactions with this instance.
*/
super.setSelfReference([this.controller, this.target]);
// Set up interactive child elements.
this.setInteractiveChildren();
// Add target attribute.

@@ -152,0 +169,0 @@ setUniqueId(this.target);

@@ -80,2 +80,7 @@ Popup

/**
* Collect and prepare the target element's interactive child elements.
*/
setInteractiveChildren();
/**
* Remove all attributes and event listeners added by this class.

@@ -82,0 +87,0 @@ */

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc