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.2.0 to 0.3.0

src/lib/getFirstAndLastItems.js

42

CHANGELOG.md
# Change Log
This project adheres to [Semantic Versioning](http://semver.org/).
## 0.3.0
**Changed**
- Uses `aria-hidden="false"` rather than removing the attribute (#28)
- Uses documented methods for nested classes (4e58d45)
**Added**
- Menu submenus can be instantiated as Disclosures by passing `collapse: true` (#27)
- Uses the `hidden` attribute where `aria-hidden="true"` (#29)
- Documents additional class properties (#34)
- Adds a helper function for getting the first and last item from an Array or NodeList (#35)
**Removed**
- Menu and MenuBar components no longer require the `aria-describedby` help text (#33)
**Fixed**
- Updates NPM dependencies (#25)
- Corrects issues with the reliability of `destroy` methods (#26 & #31)
- Updates NPM dependencies (...again) (#36)
**BREAKING CHANGES**
- MenuBar no longer tracks internal Popup state (51ab17c)
- Corrects ambiguity with native DOM `firstChild` and `lastChild` properties (4795b2a, 1312a99)
## 0.2.0

@@ -8,7 +37,2 @@

- BREAKING: Moves helper functions to `utils/` (#17)
- BREAKING: Deprecates the Menu and MenuBar `menu` config property in favor of `list` (#20)
- BREAKING: Deprecates the Tablist `tablist` config property in favor of `tabs` (#20)
- BREAKING: Updates the way the `componentName` and self references are managed (#21)
- BREAKING: Deprecates MenuBar `onPopupStateChange` and `onPopupDestroy` callbacks (#22)
- Improves tracking of internal Popup state (#22)

@@ -25,2 +49,10 @@

**BREAKING CHANGES**
- Moves helper functions to `utils/` (#17)
- Deprecates the Menu and MenuBar `menu` config property in favor of `list` (#20)
- Deprecates the Tablist `tablist` config property in favor of `tabs` (#20)
- Updates the way the `componentName` and self references are managed (#21)
- Deprecates MenuBar `onPopupStateChange` and `onPopupDestroy` callbacks (#22)
## 0.1.2

@@ -27,0 +59,0 @@

18

package.json
{
"name": "aria-components",
"version": "0.2.0",
"version": "0.3.0",
"description": "JavaScript classes to aid in accessible web development.",

@@ -23,3 +23,3 @@ "keywords": [

"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-jest": "^25.5.1",
"babel-loader": "^8.0.4",

@@ -32,3 +32,3 @@ "babel-minify-webpack-plugin": "^0.3.1",

"clean-webpack-plugin": "^1.0.0",
"concurrently": "^4.1.0",
"concurrently": "^5.2.0",
"css-loader": "^1.0.1",

@@ -44,6 +44,6 @@ "eslint": "^5.16.0",

"file-loader": "^2.0.0",
"jest": "^24.7.1",
"jest-cli": "^24.7.1",
"jest": "^25.5.3",
"jest-cli": "^25.5.3",
"mini-css-extract-plugin": "^0.4.5",
"node-sass": "^4.11.0",
"node-sass": "^4.14.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",

@@ -59,8 +59,8 @@ "postcss": "^7.0.6",

"style-loader": "^0.23.1",
"stylelint": "^9.8.0",
"stylelint": "^13.3.3",
"stylelint-webpack-plugin": "^1.0.2",
"url-loader": "^1.1.2",
"webpack": "^4.41.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2",
"webpack-dev-server": "^3.10.3",
"webpack-livereload-plugin": "^2.1.1",

@@ -67,0 +67,0 @@ "webpack-stats-plugin": "^0.2.1",

@@ -31,21 +31,2 @@ [![npm version][npmjs-img]][npmjs] [![Build Status][ci-img]][ci]

## Help text elements
> Elements used for keyboard navigation description and referenced on the
element via `aria-labelledby` need to exist in the DOM.
The Menu and MenuBar components reference such elements. As a result, authors
will need to manually add the elements to their page(s).
Examples can be found in the docs directory:
- [docs/\_includes/help-text.html](docs/_includes/help-text.html)
- [docs/\_includes/help-text.php](docs/_includes/help-text.php)
See also the [Menu](src/Menu/) and [Menubar](src/MenuBar/) components' README
Aside from the help text examples above, authors are responsible for adding all
necessary `aria-labelledby`, `aria-label` and `aria-describedby` to the revelant
elements.
**Note**:

@@ -52,0 +33,0 @@ <!-- @todo is this still true? -->

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

import keyCodes from '../lib/keyCodes';
import getFirstAndLastItems from '../lib/getFirstAndLastItems';

@@ -142,2 +143,3 @@ /**

* The Popup instance controlling the Dialog.
*
* @type {Popup}

@@ -157,3 +159,3 @@ */

*/
this.interactiveChildren = interactiveChildren(this.target);
this.interactiveChildElements = interactiveChildren(this.target);

@@ -201,10 +203,12 @@ // Add event listeners.

this.interactiveChildren = interactiveChildren(this.target);
this.interactiveChildElements = interactiveChildren(this.target);
if (expanded) {
this.content.setAttribute('aria-hidden', 'true');
this.content.setAttribute('hidden', '');
document.body.addEventListener('keydown', this.handleKeydownEsc);
this.close.focus();
} else {
this.content.removeAttribute('aria-hidden');
this.content.setAttribute('aria-hidden', 'false');
this.content.removeAttribute('hidden');
document.body.removeEventListener('keydown', this.handleKeydownEsc);

@@ -224,3 +228,3 @@ this.controller.focus();

outsideClick(event) {
const { expanded } = this.popup.getState();
const { expanded } = this.state;

@@ -240,10 +244,12 @@ if (expanded && ! this.target.contains(event.target)) {

const { keyCode, shiftKey } = event;
const { expanded } = this.state;
if (this.popup.getState().expanded && keyCode === TAB) {
if (expanded && keyCode === TAB) {
const { activeElement } = document;
const lastIndex = this.interactiveChildren.length - 1;
const [firstChild] = this.interactiveChildren;
const lastChild = this.interactiveChildren[lastIndex];
const [
firstInteractiveChild,
lastInteractiveChild,
] = getFirstAndLastItems(this.interactiveChildElements);
if (shiftKey && firstChild === activeElement) {
if (shiftKey && firstInteractiveChild === activeElement) {
event.preventDefault();

@@ -254,4 +260,4 @@ /*

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

@@ -262,3 +268,3 @@ /*

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

@@ -294,5 +300,9 @@ }

// Remove the `aria-hidden` attribute from the content wrapper.
this.content.removeAttribute('aria-hidden');
// Remove event listeners.
this.close.removeEventListener('click', this.hide);
this.target.removeEventListener('keydown', this.handleTargetKeydown);
document.body.removeEventListener('keydown', this.handleKeydownEsc);

@@ -299,0 +309,0 @@ /* Run {destroyCallback} */

@@ -109,2 +109,4 @@ Dialog

* The config.controller property.
*
* @type {HTMLButtonElement}
*/

@@ -117,2 +119,4 @@ Dialog.controller

* The config.target property.
*
* @type {HTMLElement}
*/

@@ -125,2 +129,4 @@ Dialog.target

* The config.content property.
*
* @type {HTMLElement}
*/

@@ -132,2 +138,11 @@ Dialog.content

/**
* The config.close property, or the button created in its absence.
*
* @type {HTMLButtonElement}
*/
Dialog.close
```
```javascript
/**
* The Popup instance controlling the Dialog.

@@ -134,0 +149,0 @@ *

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

this.target.setAttribute('aria-hidden', 'true');
this.target.setAttribute('hidden', '');
}

@@ -209,5 +210,7 @@

if (expanded) {
this.target.removeAttribute('aria-hidden');
this.target.setAttribute('aria-hidden', 'false');
this.target.removeAttribute('hidden');
} else {
this.target.setAttribute('aria-hidden', 'true');
this.target.setAttribute('hidden', '');
}

@@ -281,2 +284,9 @@

// Remove IDs set by this class.
[this.controller, this.target].forEach((element) => {
if (element.getAttribute('id').includes('id_')) {
element.removeAttribute('id');
}
});
// Remove controller attributes.

@@ -288,7 +298,16 @@ this.controller.removeAttribute('aria-expanded');

if ('BUTTON' !== this.controller.nodeName) {
this.controller.removeAttribute('role');
}
// Remove target attributes.
this.target.removeAttribute('aria-hidden');
this.target.removeAttribute('hidden');
// Remove tabindex attributes.
tabIndexAllow(this.interactiveChildElements);
// Remove event listeners.
this.controller.removeEventListener('click', this.toggleExpandedState);
this.controller.removeEventListener('keydown', this.handleControllerKeydown);
document.body.removeEventListener('click', this.closeOnOutsideClick);

@@ -295,0 +314,0 @@

@@ -97,2 +97,4 @@ Disclosure

* The config.controller property.
*
* @type {HTMLButtonElement}
*/

@@ -105,2 +107,4 @@ Disclosure.controller

* The config.target property.
*
* @type {HTMLElement}
*/

@@ -107,0 +111,0 @@ Disclosure.target

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

import Search from '../lib/Search';
import getFirstAndLastItems from '../lib/getFirstAndLastItems';

@@ -123,17 +124,4 @@ /**

/**
* First [role="option"]
*
* @type {HTMLElement}
*/
const [firstOption] = this.options;
/**
* Last [role="option"]
*
* @type {HTMLElement}
*/
const lastOption = this.options[this.options.length - 1];
// Save first and last option as properties.
const [ firstOption, lastOption ] = getFirstAndLastItems(this.options);
Object.assign(this, { firstOption, lastOption });

@@ -407,2 +395,3 @@

handleTargetBlur() {
// Use Popup state here, since the Popup drives the Listbox state.
if (this.popup.getState().expanded) {

@@ -445,2 +434,8 @@ this.hide();

listItem.removeAttribute('role');
listItem.removeAttribute('aria-selected');
// Remove IDs set by this class.
if (listItem.getAttribute('id').includes('id_')) {
listItem.removeAttribute('id');
}
});

@@ -454,2 +449,3 @@

this.target.removeAttribute('tabindex');
this.target.removeAttribute('aria-activedescendant');

@@ -456,0 +452,0 @@ // Remove event listeners.

@@ -82,2 +82,4 @@ Listbox

* The config.controller property.
*
* @type {HTMLButtonElement}
*/

@@ -90,2 +92,4 @@ ListBox.controller

* The config.target property.
*
* @type {HTMLUListElement}
*/

@@ -97,2 +101,29 @@ ListBox.target

/**
* The target list items.
*
* @type {array}
*/
Listbox.options
```
```javascript
/**
* The first Listbox option.
*
* @type {HTMLLIElement}
*/
ListBox.firstOption
```
```javascript
/**
* The last Listbox option.
*
* @type {HTMLLIElement}
*/
ListBox.lastOption
```
```javascript
/**
* The Popup instance controlling the ListBox.

@@ -99,0 +130,0 @@ *

import AriaComponent from '../AriaComponent';
import Disclosure from '../Disclosure';
import keyCodes from '../lib/keyCodes';
import isInstanceOf from '../lib/isInstanceOf';
import { nextPreviousFromUpDown } from '../lib/nextPrevious';
import { missingDescribedByWarning } from '../lib/ariaDescribedbyElementsFound';
import Search from '../lib/Search';
import getFirstAndLastItems from '../lib/getFirstAndLastItems';

@@ -15,16 +16,2 @@ /**

/**
* HTML IDs for elements containing help text.
*
* @return {array}
*/
static getHelpIds() {
return [
'#ac-describe-submenu-help',
'#ac-describe-esc-help',
'#ac-describe-submenu-explore',
'#ac-describe-submenu-back',
];
}
/**
* Test for a list as the next sibling element.

@@ -78,2 +65,9 @@ *

/**
* Instantiate submenus as Disclosures.
*
* @type {Boolean}
*/
collapse: false,
/**
* Callback to run after the component initializes.

@@ -162,8 +156,8 @@ *

/*
* Warn if aria-decribedby elements are not found.
* Without these elements, the references will be broken and potentially
* confusing to users.
/**
* The submenu Disclosures.
*
* @type {array}
*/
missingDescribedByWarning(Menu.getHelpIds());
this.disclosures = [];

@@ -180,8 +174,2 @@ /*

link.setAttribute(
'aria-describedby',
// eslint-disable-next-line max-len
'ac-describe-submenu-explore ac-describe-submenu-help ac-describe-submenu-back ac-describe-esc-help'
);
// Add size and position attributes.

@@ -193,2 +181,13 @@ link.setAttribute('aria-setsize', this.menuItemsLength);

if (siblingList) {
// Instantiate submenu Disclosures
if (this.collapse) {
const disclosure = new Disclosure({
controller: link,
target: siblingList,
});
this.disclosures.push(disclosure);
}
// Instantiate sub-Menus.
const subList = new Menu({ list: siblingList });

@@ -201,4 +200,3 @@ // Save the list's previous sibling.

// Save the menu's first and last items.
const [firstItem] = this.menuItems;
const lastItem = this.menuItems[this.menuItems.length - 1];
const [ firstItem, lastItem ] = getFirstAndLastItems(this.menuItems);
Object.assign(this, { firstItem, lastItem });

@@ -285,2 +283,7 @@

// Open the submenu Disclosure.
if (isInstanceOf(activeDescendant.disclosure, Disclosure)) {
activeDescendant.disclosure.open();
}
const { menu } = siblingElement;

@@ -304,2 +307,8 @@ menu.firstItem.focus();

event.stopPropagation();
// Close the submenu Disclosure.
if (isInstanceOf(this.previousSibling.disclosure, Disclosure)) {
this.previousSibling.disclosure.close();
}
this.previousSibling.focus();

@@ -343,2 +352,8 @@ }

// Remove the list's role attritbute.
this.list.removeAttribute('role');
// Remove event listener.
this.list.removeEventListener('keydown', this.handleListKeydown);
this.menuItems.forEach((link) => {

@@ -350,9 +365,5 @@ // Remove list item role.

link.removeAttribute('role');
link.removeAttribute('aria-describedby');
link.removeAttribute('aria-setsize');
link.removeAttribute('aria-posinset');
// Remove event listeners.
link.removeEventListener('keydown', this.handleListKeydown);
// Destroy nested Menus.

@@ -365,2 +376,7 @@ const siblingList = this.constructor.nextElementIsUl(link);

// Destroy inner Disclosure(s).
this.disclosures.forEach((disclosure) => {
disclosure.destroy();
});
// Run {destroyCallback}

@@ -367,0 +383,0 @@ this.onDestroy.call(this);

@@ -18,2 +18,9 @@ Menu

/**
* Instantiate submenus as Disclosures.
*
* @type {Boolean}
*/
collapse: false,
/**
* Callback to run after the component initializes.

@@ -56,2 +63,9 @@ *

Menu.menu
/**
* The submenu Disclosures.
*
* @type {array}
*/
Menu.disclosures
```

@@ -82,14 +96,2 @@

</ul>
<!--
These elements are required by this component, but must be added manually.
Feel free to update the text how you see fit, but make sure it's helpful and
the elements have the correct `id` attribute.
-->
<div class="screen-reader-only">
<span id="ac-describe-submenu-help">Use right arrow key to move into submenus.</span>
<span id="ac-describe-esc-help">Use escape to exit the menu.</span>
<span id="ac-describe-submenu-explore">Use up and down arrow keys to explore.</span>
<span id="ac-describe-submenu-back">Use left arrow key to move back to the parent list.</span>
</div>
```

@@ -104,2 +106,3 @@

list,
collapse: true,
onInit: () => {

@@ -106,0 +109,0 @@ console.log('Menu initialized.');

@@ -8,4 +8,4 @@ import AriaComponent from '../AriaComponent';

import isInstanceOf from '../lib/isInstanceOf';
import { missingDescribedByWarning } from '../lib/ariaDescribedbyElementsFound';
import Search from '../lib/Search';
import getFirstAndLastItems from '../lib/getFirstAndLastItems';

@@ -33,15 +33,2 @@ /**

/**
* HTML IDs for elements containing help text
*
* @return {array}
*/
static getHelpIds() {
return [
'#ac-describe-top-level-help',
'#ac-describe-submenu-help',
'#ac-describe-esc-help',
];
}
/**
* Create a MenuBar.

@@ -128,3 +115,2 @@ * @constructor

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

@@ -187,9 +173,2 @@

/*
* Warn if aria-decribedby elements are not found.
* Without these elements, the references will be broken and potentially
* confusing to users.
*/
missingDescribedByWarning(MenuBar.getHelpIds());
/*
* Set menubar link attributes.

@@ -201,9 +180,2 @@ */

// Add a reference to the help text.
link.setAttribute(
'aria-describedby',
// eslint-disable-next-line max-len
'ac-describe-top-level-help ac-describe-submenu-help ac-describe-esc-help'
);
// Add size and position attributes.

@@ -220,8 +192,5 @@ link.setAttribute('aria-setsize', this.menuLength);

/**
* The index of the last menubar item
*
* @type {number}
*/
this.lastIndex = (this.menuLength - 1);
// Collect first and last MenuBar items and merge them in as instance properties.
const [ firstItem, lastItem ] = getFirstAndLastItems(this.menuBarItems);
Object.assign(this, { firstItem, lastItem });

@@ -248,3 +217,2 @@ /**

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

@@ -271,6 +239,5 @@ });

*/
const [menubarItem] = this.menuBarItems;
this.state = {
menubarItem,
popup: this.constructor.getPopupFromMenubarItem(menubarItem),
menubarItem: this.firstItem,
popup: this.constructor.getPopupFromMenubarItem(this.firstItem),
expanded: false,

@@ -280,3 +247,3 @@ };

// Set up initial tabindex.
rovingTabIndex(this.menuBarItems, menubarItem);
rovingTabIndex(this.menuBarItems, this.firstItem);

@@ -288,23 +255,2 @@ // Run {initCallback}

/**
* Refresh component state when Popup state is updated.
*
* @param {object} state The Popup state.
*/
trackPopupState(state = {}) {
const { menubarItem } = this.state;
const popup = this.constructor.getPopupFromMenubarItem(menubarItem);
/*
* Use the current MenuBar state if there's no popup or if an expanded state
* was passed in, otherwise make sure to use the current popup's state.
*/
const expanded = (
false === popup
|| Object.prototype.hasOwnProperty.call(state, 'expanded')
) ? state.expanded : popup.getState();
// Add the Popup state to this component's state.
this.state = Object.assign({ menubarItem, popup, expanded });
}
/**
* Manage menubar state.

@@ -317,5 +263,2 @@ *

// Make sure we're tracking the Popup state along with this.
this.trackPopupState();
// Prevent tabbing to all but the currently-active menubar item.

@@ -346,3 +289,4 @@ rovingTabIndex(this.menuBarItems, menubarItem);

const { keyCode } = event;
const { menubarItem, popup } = this.state;
const { menubarItem } = this.state;
const popup = this.constructor.getPopupFromMenubarItem(menubarItem);

@@ -367,3 +311,3 @@ switch (keyCode) {

if (popup) {
popup.setState({ expanded: false });
popup.hide();
}

@@ -390,6 +334,6 @@

if (! popup.state.expanded) {
popup.setState({ expanded: true });
popup.show();
}
popup.firstChild.focus();
popup.firstInteractiveChild.focus();
}

@@ -419,3 +363,3 @@

this.setState({
menubarItem: this.menuBarItems[this.lastIndex],
menubarItem: this.lastItem,
});

@@ -484,5 +428,2 @@

// Remove reference to the help text.
link.removeAttribute('aria-describedby');
// Remove size and position attributes.

@@ -501,2 +442,5 @@ link.removeAttribute('aria-setsize');

// Remove tabindex attribute.
tabIndexAllow(this.menuBarItems);
// Destroy nested components.

@@ -507,8 +451,7 @@ this.popups.forEach((popup) => {

}
popup.target.removeEventListener('keydown', this.handleMenuItemKeydown);
popup.destroy();
});
// Revert tabindex attributes.
tabIndexAllow(this.menuBarItems);
// Run {destroyCallback}

@@ -515,0 +458,0 @@ this.onDestroy.call(this);

@@ -79,2 +79,11 @@ MenuBar

```javascript
/**
* Collected menubar links.
*
* @type {array}
*/
MenuBar.menuBarItems
```
## Example

@@ -105,13 +114,2 @@

</ul>
<!--
These elements are required by this component, but must be added manually.
Feel free to update the text how you see fit, but make sure it's helpful and
the elements have the correct `id` attribute.
-->
<div class="screen-reader-only">
<span id="ac-describe-top-level-help">Use left and right arrow keys to navigate between menu items.</span>
<span id="ac-describe-submenu-help">Use right arrow key to move into submenus.</span>
<span id="ac-describe-esc-help">Use escape to exit the menu.</span>
</div>
```

@@ -118,0 +116,0 @@

@@ -208,2 +208,5 @@ import AriaComponent from '../AriaComponent';

// Remove event listener.
this.controller.removeEventListener('keydown', this.handleControllerKeydown);
// Run {destroyCallback}

@@ -210,0 +213,0 @@ this.onDestroy.call(this);

@@ -90,2 +90,4 @@ MenuButton

* The config.controller property.
*
* @type {HTMLButtonElement}
*/

@@ -98,2 +100,4 @@ MenuButton.controller

* The config.target property.
*
* @type {HTMLElement}
*/

@@ -106,2 +110,4 @@ MenuButton.target

* The config.list property.
*
* @type {HTMLUListElement}
*/

@@ -108,0 +114,0 @@ MenuButton.list

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

import { setUniqueId } from '../lib/uniqueId';
import getFirstAndLastItems from '../lib/getFirstAndLastItems';

@@ -97,2 +98,13 @@ /**

/**
* Check if the controller is a button, but only if it doesn't already have
* a role attribute, since we'll be adding the role and allowing focus.
*
* @type {bool}
*/
this.controllerIsNotAButton = (
'BUTTON' !== this.controller.nodeName
&& null === this.controller.getAttribute('role')
);
// Check for a valid controller and target before initializing.

@@ -129,8 +141,8 @@ if (null !== this.controller && null !== this.target) {

if (0 < this.interactiveChildElements.length) {
const [firstChild] = this.interactiveChildElements;
const lastChild = (
this.interactiveChildElements[this.interactiveChildElements.length - 1]
);
const [
firstInteractiveChild,
lastInteractiveChild,
] = getFirstAndLastItems(this.interactiveChildElements);
Object.assign(this, { firstChild, lastChild });
Object.assign(this, { firstInteractiveChild, lastInteractiveChild });
}

@@ -149,8 +161,4 @@

* Use the button role on non-button elements.
* But only if it doesn't already have a role attribute.
*/
if (
'BUTTON' !== this.controller.nodeName
&& null === this.controller.getAttribute('role')
) {
if (this.controllerIsNotAButton) {
// https://www.w3.org/TR/wai-aria-1.1/#button

@@ -175,2 +183,3 @@ this.controller.setAttribute('role', 'button');

this.target.setAttribute('aria-hidden', 'true');
this.target.setAttribute('hidden', '');

@@ -198,10 +207,7 @@ // Add event listeners

/*
* https://developer.paciellogroup.com/blog/2016/01/the-state-of-hidden-content-support-in-2016/
*
* > In some browser and screen reader combinations aria-hidden=false on an
* element that is hidden using the hidden attribute or CSS display:none
* results in the content being unhidden.
* Update Popup and interactive children's attributes.
*/
if (expanded) {
this.target.removeAttribute('aria-hidden');
this.target.setAttribute('aria-hidden', 'false');
this.target.removeAttribute('hidden');

@@ -211,2 +217,3 @@ tabIndexAllow(this.interactiveChildElements);

this.target.setAttribute('aria-hidden', 'true');
this.target.setAttribute('hidden', '');

@@ -261,3 +268,3 @@ // Focusable content should have tabindex='-1' or be removed from the DOM.

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

@@ -294,3 +301,3 @@ }

if (shiftKey) {
if ([this.firstChild, this.target].includes(activeElement)) {
if ([this.firstInteractiveChild, this.target].includes(activeElement)) {
event.preventDefault();

@@ -304,3 +311,3 @@ /*

}
} else if (this.lastChild === activeElement) {
} else if (this.lastInteractiveChild === activeElement) {
/*

@@ -368,2 +375,9 @@ * Close the Popup when tabbing from the last child.

// Remove IDs set by this class.
[this.controller, this.target].forEach((element) => {
if (element.getAttribute('id').includes('id_')) {
element.removeAttribute('id');
}
});
// Remove controller attributes.

@@ -375,5 +389,15 @@ this.controller.removeAttribute('aria-haspopup');

// Remove role and tabindex added to a link controller.
if (this.controllerIsNotAButton) {
this.controller.removeAttribute('role');
this.controller.removeAttribute('tabindex');
}
// Remove target attributes.
this.target.removeAttribute('aria-hidden');
this.target.removeAttribute('hidden');
// Remove tabindex attribute.
tabIndexAllow(this.interactiveChildElements);
// Remove event listeners.

@@ -380,0 +404,0 @@ this.controller.removeEventListener('click', this.controllerClickHandler);

@@ -14,3 +14,3 @@ Popup

*
* @type {HTMLElement}
* @type {HTMLButtonElement}
*/

@@ -92,2 +92,4 @@ controller: null,

* The config.controller property.
*
* @type {HTMLButtonElement}
*/

@@ -100,4 +102,20 @@ Popup.controller

* The config.target property.
*
* @type {HTMLElement}
*/
Popup.target
/**
* The target's first interactive child element.
*
* @type {HTMLElement}
*/
Popup.firstInteractiveChild
/**
* The target's last interactive child element.
*
* @type {HTMLElement}
*/
Popup.lastInteractiveChild
```

@@ -104,0 +122,0 @@

@@ -202,4 +202,7 @@ import AriaComponent from '../AriaComponent';

panel.setAttribute('tabindex', '0');
panel.setAttribute('aria-hidden', 'false');
panel.removeAttribute('hidden');
} else {
panel.setAttribute('aria-hidden', 'true');
panel.setAttribute('hidden', '');
}

@@ -212,3 +215,3 @@

// Save the active panel's interactive children.
this.interactiveChildren = interactiveChildren(this.panels[activeIndex]);
this.interactiveChildElements = interactiveChildren(this.panels[activeIndex]);

@@ -239,2 +242,3 @@ // Run {initCallback}

this.panels[deactiveIndex].setAttribute('aria-hidden', 'true');
this.panels[deactiveIndex].setAttribute('hidden', '');
this.panels[deactiveIndex].removeAttribute('tabindex');

@@ -249,8 +253,9 @@

this.tabLinks[activeIndex].setAttribute('aria-selected', 'true');
this.panels[activeIndex].removeAttribute('aria-hidden');
this.panels[activeIndex].setAttribute('aria-hidden', 'false');
this.panels[activeIndex].removeAttribute('hidden');
this.panels[activeIndex].setAttribute('tabindex', '0');
// Allow tabbing to the newly-active panel.
this.interactiveChildren = interactiveChildren(this.panels[activeIndex]);
tabIndexAllow(this.interactiveChildren);
this.interactiveChildElements = interactiveChildren(this.panels[activeIndex]);
tabIndexAllow(this.interactiveChildElements);

@@ -271,3 +276,3 @@ // Run {stateChangeCallback}

const { activeElement } = document;
const [firstChild] = this.interactiveChildren;
const [firstInteractiveChild] = this.interactiveChildElements;

@@ -278,3 +283,3 @@ if (keyCode === TAB && shiftKey) {

this.tabLinks[activeIndex].focus();
} else if (activeElement === firstChild) {
} else if (activeElement === firstInteractiveChild) {
/*

@@ -441,3 +446,5 @@ * Ensure navigating with Shift-TAB from the first interactive child of

panel.removeAttribute('aria-hidden');
panel.removeAttribute('hidden');
panel.removeAttribute('tabindex');
panel.removeAttribute('aria-labelledby');

@@ -444,0 +451,0 @@ // Make sure to allow tabbing to all children of all panels.

@@ -14,3 +14,3 @@ Tablist

*
* @type {HTMLElement}
* @type {HTMLUListElement}
*/

@@ -80,5 +80,7 @@ tabs: null,

/**
* The config.tablist property.
* The config.tabs property.
*
* @type {HTMLUListElement}
*/
Tablist.tablist
Tablist.tabs
```

@@ -89,2 +91,4 @@

* The config.panels property.
*
* @type {array}
*/

@@ -94,2 +98,10 @@ Tablist.panels

```javascript
/**
* Collected anchors from inside of each list items.
*
* @type {array}
*/
Tablist.tabLinks
```

@@ -96,0 +108,0 @@ ## Example

@@ -9,2 +9,3 @@ /**

import Search from '../src/lib/Search';
import getFirstAndLastItems from '../src/lib/getFirstAndLastItems';

@@ -20,2 +21,3 @@ export {

Search,
getFirstAndLastItems,
};

@@ -69,3 +69,3 @@ src/lib/

const newId = getUniqueId(button); // 'id_9y0541qs1tk'
button.id = getUniqueId(); // 'id_9y0541qs1tk'
```

@@ -92,1 +92,16 @@

```
## `getFirstAndLastItems`
```javascript
import { getFirstAndLastItems } from 'aria-components/utils';
const listItems = document.querySelectorAll('li');
// Pass a NodeList.
const [firstItem, lastItem] = getFirstAndLastItems(listItems);
// Pass an Array.
const listItemsArray = Array.from(listItems);
const [firstItem, lastItem] = getFirstAndLastItems(listItemsArray);
```
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