makeup-expander
Advanced tools
Comparing version 0.7.3 to 0.8.0
264
index.js
@@ -19,3 +19,3 @@ 'use strict'; | ||
alwaysDoFocusManagement: false, | ||
// in the case we want mouse click to move focus (e.g. menu buttons) | ||
ariaControls: true, | ||
autoCollapse: false, | ||
@@ -33,30 +33,50 @@ collapseOnFocusOut: false, | ||
simulateSpacebarClick: false | ||
}; // when options.expandOnClick is true, we set a flag if spacebar or enter are pressed | ||
// the idea being that this flag is set BEFORE the click event | ||
}; | ||
function _onKeyDown(e) { | ||
var keyCode = e.keyCode; | ||
function onHostKeyDown(e) { | ||
if (e.keyCode === 13 || e.keyCode === 32) { | ||
this._keyboardClickFlag = true; | ||
} // if host element does not naturally trigger a click event on spacebar, we can force one to trigger here. | ||
// careful! if host already triggers click events naturally, we end up with a "double-click". | ||
if (keyCode === 13 || keyCode === 32) { | ||
this.keyDownFlag = true; // if host element does not naturally trigger a click event on spacebar, we can force one to trigger here. | ||
// careful! if host already triggers click events naturally, we end up with a "double-click". | ||
if (keyCode === 32 && this.options.simulateSpacebarClick === true) { | ||
this.hostEl.click(); | ||
} | ||
if (e.keyCode === 32 && this.options.simulateSpacebarClick === true) { | ||
this.hostEl.click(); | ||
} | ||
} | ||
function processDocumentClick(event, el) { | ||
if (el.contains(event.target) === false) { | ||
el.dispatchEvent(new CustomEvent('clickOut', { | ||
bubbles: false | ||
})); | ||
} | ||
function onHostMouseDown() { | ||
this._mouseClickFlag = true; | ||
} | ||
function _onDocumentClick(e) { | ||
processDocumentClick(e, this.el); | ||
function onHostClick() { | ||
this._expandWasKeyboardClickActivated = this._keyboardClickFlag; | ||
this._expandWasMouseClickActivated = this._mouseClickFlag; | ||
this.expanded = !this.expanded; | ||
} | ||
function onHostFocus() { | ||
this._expandWasFocusActivated = true; | ||
this.expanded = true; | ||
} | ||
function onHostHover() { | ||
this._expandWasHoverActivated = true; | ||
this.expanded = true; | ||
} | ||
function onFocusExit() { | ||
this.expanded = false; | ||
} | ||
function onMouseLeave() { | ||
this.expanded = false; | ||
} | ||
function _onDocumentClick() { | ||
if (this.el.contains(event.target) === false) { | ||
this.expanded = false; | ||
} | ||
} | ||
function _onDocumentTouchStart() { | ||
@@ -70,9 +90,29 @@ this.documentClick = true; | ||
function _onDocumentTouchEnd(e) { | ||
if (this.documentClick) { | ||
function _onDocumentTouchEnd() { | ||
if (this.documentClick === true) { | ||
this.documentClick = false; | ||
processDocumentClick(e, this.el); | ||
if (this.el.contains(event.target) === false) { | ||
this.expanded = false; | ||
} | ||
} | ||
} | ||
function manageFocus(focusManagement, contentEl) { | ||
if (focusManagement === 'content') { | ||
contentEl.setAttribute('tabindex', '-1'); | ||
contentEl.focus(); | ||
} else if (focusManagement === 'focusable') { | ||
focusables(contentEl)[0].focus(); | ||
} else if (focusManagement === 'interactive') { | ||
focusables(contentEl, true)[0].focus(); | ||
} else if (focusManagement !== null) { | ||
var el = contentEl.querySelector("#".concat(focusManagement)); | ||
if (el) { | ||
el.focus(); | ||
} | ||
} | ||
} | ||
module.exports = | ||
@@ -88,10 +128,6 @@ /*#__PURE__*/ | ||
this.expandeeEl = el.querySelector(this.options.contentSelector); | ||
this.keyDownFlag = false; | ||
this.documentClick = false; // ensure the widget and expandee have an id | ||
nextID(this.el, 'expander'); | ||
nextID(this.expandeeEl, "".concat(this.el.id, "-content")); | ||
this.contentEl = el.querySelector(this.options.contentSelector); | ||
ExitEmitter.addFocusExit(this.el); | ||
this._hostKeyDownListener = _onKeyDown.bind(this); | ||
this._hostKeyDownListener = onHostKeyDown.bind(this); | ||
this._hostMouseDownListener = onHostMouseDown.bind(this); | ||
this._documentClickListener = _onDocumentClick.bind(this); | ||
@@ -101,8 +137,7 @@ this._documentTouchStartListener = _onDocumentTouchStart.bind(this); | ||
this._documentTouchEndListener = _onDocumentTouchEnd.bind(this); | ||
this._hostClickListener = this.toggle.bind(this); | ||
this._hostFocusListener = this.expand.bind(this); | ||
this._hostHoverListener = this.expand.bind(this); | ||
this._focusExitListener = this.collapse.bind(this); | ||
this._mouseLeaveListener = this.collapse.bind(this); | ||
this._clickOutListener = this.collapse.bind(this); | ||
this._hostClickListener = onHostClick.bind(this); | ||
this._hostFocusListener = onHostFocus.bind(this); | ||
this._hostHoverListener = onHostHover.bind(this); | ||
this._focusExitListener = onFocusExit.bind(this); | ||
this._mouseLeaveListener = onMouseLeave.bind(this); | ||
@@ -113,3 +148,9 @@ if (this.hostEl.getAttribute('aria-expanded') === null) { | ||
this.hostEl.setAttribute('aria-controls', this.expandeeEl.id); | ||
if (this.options.ariaControls === true) { | ||
// ensure the widget has an id | ||
nextID(this.el, 'expander'); | ||
this.contentEl.id = this.contentEl.id || "".concat(this.el.id, "-content"); | ||
this.hostEl.setAttribute('aria-controls', this.contentEl.id); | ||
} | ||
this.expandOnClick = this.options.expandOnClick; | ||
@@ -127,79 +168,59 @@ this.expandOnFocus = this.options.expandOnFocus; | ||
_createClass(_class, [{ | ||
key: "isExpanded", | ||
value: function isExpanded() { | ||
return this.hostEl.getAttribute('aria-expanded') === 'true'; | ||
key: "sleep", | ||
value: function sleep() { | ||
if (this._destroyed !== true) { | ||
this.expandOnClick = false; | ||
this.expandOnFocus = false; | ||
this.expandOnHover = false; | ||
this.collapseOnClickOut = false; | ||
this.collapseOnFocusOut = false; | ||
this.collapseOnMouseOut = false; | ||
} | ||
} | ||
}, { | ||
key: "collapse", | ||
value: function collapse() { | ||
if (this.isExpanded() === true) { | ||
this.hostEl.setAttribute('aria-expanded', 'false'); | ||
key: "destroy", | ||
value: function destroy() { | ||
this.sleep(); | ||
this._destroyed = true; | ||
this._hostKeyDownListener = null; | ||
this._hostMouseDownListener = null; | ||
this._documentClickListener = null; | ||
this._documentTouchStartListener = null; | ||
this._documentTouchMoveListener = null; | ||
this._documentTouchEndListener = null; | ||
this._hostClickListener = null; | ||
this._hostFocusListener = null; | ||
this._hostHoverListener = null; | ||
this._focusExitListener = null; | ||
this._mouseLeaveListener = null; | ||
} // DEPRECATED (remove in v1.0.0) | ||
if (this.options.expandedClass) { | ||
this.el.classList.remove(this.options.expandedClass); | ||
} | ||
}, { | ||
key: "isExpanded", | ||
value: function isExpanded() { | ||
return this.expanded; | ||
} // DEPRECATED (remove in v1.0.0) | ||
this.el.dispatchEvent(new CustomEvent('expander-collapse', { | ||
bubbles: true, | ||
detail: this.expandeeEl | ||
})); | ||
} | ||
} // todo: refactor to remove "isKeyboard" param | ||
}, { | ||
key: "expand", | ||
value: function expand(isKeyboard) { | ||
if (this.isExpanded() === false) { | ||
this.hostEl.setAttribute('aria-expanded', 'true'); | ||
value: function expand() { | ||
this.expanded = true; | ||
} // DEPRECATED (remove in v1.0.0) | ||
if (this.options.expandedClass) { | ||
this.el.classList.add(this.options.expandedClass); | ||
} // todo: refactor focus management. We could run into a bad situation where mouse hover moves focus. | ||
}, { | ||
key: "collapse", | ||
value: function collapse() { | ||
this.expanded = false; | ||
} // DEPRECATED (remove in v1.0.0) | ||
if (isKeyboard === true || this.options.alwaysDoFocusManagement === true) { | ||
var focusManagement = this.options.focusManagement; | ||
if (focusManagement === 'content') { | ||
this.expandeeEl.setAttribute('tabindex', '-1'); | ||
this.expandeeEl.focus(); | ||
} else if (focusManagement === 'focusable') { | ||
focusables(this.expandeeEl)[0].focus(); | ||
} else if (focusManagement === 'interactive') { | ||
focusables(this.expandeeEl, true)[0].focus(); | ||
} else if (focusManagement !== null) { | ||
var el = this.expandeeEl.querySelector("#".concat(focusManagement)); | ||
if (el) { | ||
el.focus(); | ||
} | ||
} | ||
} | ||
this.el.dispatchEvent(new CustomEvent('expander-expand', { | ||
bubbles: true, | ||
detail: this.expandeeEl | ||
})); | ||
} | ||
} | ||
}, { | ||
key: "toggle", | ||
value: function toggle() { | ||
if (this.isExpanded() === true) { | ||
this.collapse(); | ||
} else { | ||
this.expand(this.keyDownFlag); | ||
} | ||
this.expanded = !this.expanded; | ||
} // DEPRECATED (remove in v1.0.0) | ||
this.keyDownFlag = false; | ||
} | ||
}, { | ||
key: "cancelAsync", | ||
value: function cancelAsync() { | ||
this.expandOnClick = false; | ||
this.expandOnFocus = false; | ||
this.expandOnHover = false; | ||
this.collapseOnClickOut = false; | ||
this.collapseOnFocusOut = false; | ||
this.collapseOnMouseOut = false; | ||
this.sleep(); | ||
} | ||
@@ -211,2 +232,3 @@ }, { | ||
this.hostEl.addEventListener('keydown', this._hostKeyDownListener); | ||
this.hostEl.addEventListener('mousedown', this._hostMouseDownListener); | ||
this.hostEl.addEventListener('click', this._hostClickListener); | ||
@@ -220,2 +242,3 @@ | ||
this.hostEl.removeEventListener('click', this._hostClickListener); | ||
this.hostEl.removeEventListener('mousedown', this._hostMouseDownListener); | ||
this.hostEl.removeEventListener('keydown', this._hostKeyDownListener); | ||
@@ -259,5 +282,3 @@ } | ||
document.addEventListener('touchend', this._documentTouchEndListener); | ||
this.el.addEventListener('clickOut', this._clickOutListener); | ||
} else { | ||
this.el.removeEventListener('clickOut', this._clickOutListener); | ||
document.removeEventListener('click', this._documentClickListener); | ||
@@ -287,2 +308,45 @@ document.removeEventListener('touchstart', this._documentTouchStartListener); | ||
} | ||
}, { | ||
key: "expanded", | ||
get: function get() { | ||
return this.hostEl.getAttribute('aria-expanded') === 'true'; | ||
}, | ||
set: function set(bool) { | ||
if (bool === true && this.expanded === false) { | ||
this.hostEl.setAttribute('aria-expanded', 'true'); | ||
if (this.options.expandedClass) { | ||
this.el.classList.add(this.options.expandedClass); | ||
} | ||
if (this._expandWasKeyboardClickActivated || this._expandWasMouseClickActivated && this.options.alwaysDoFocusManagement) { | ||
manageFocus(this.options.focusManagement, this.contentEl); | ||
} | ||
this.el.dispatchEvent(new CustomEvent('expander-expand', { | ||
bubbles: true, | ||
detail: this.contentEl | ||
})); | ||
} | ||
if (bool === false && this.expanded === true) { | ||
this.hostEl.setAttribute('aria-expanded', 'false'); | ||
if (this.options.expandedClass) { | ||
this.el.classList.remove(this.options.expandedClass); | ||
} | ||
this.el.dispatchEvent(new CustomEvent('expander-collapse', { | ||
bubbles: true, | ||
detail: this.contentEl | ||
})); | ||
} | ||
this._expandWasKeyboardClickActivated = false; | ||
this._expandWasMouseClickActivated = false; | ||
this._expandWasFocusActivated = false; | ||
this._expandWasHoverActivated = false; | ||
this._keyboardClickFlag = false; | ||
this._mouseClickFlag = false; | ||
} | ||
}]); | ||
@@ -289,0 +353,0 @@ |
{ | ||
"name": "makeup-expander", | ||
"description": "Creates the basic interactivity for an element that expands and collapses another element.", | ||
"version": "0.7.3", | ||
"version": "0.8.0", | ||
"main": "index.js", | ||
@@ -51,3 +51,3 @@ "repository": "https://github.com/makeup-js/makeup-expander.git", | ||
"nodelist-foreach-polyfill": "^1", | ||
"onchange": "^5", | ||
"onchange": "^6", | ||
"parallelshell": "^3", | ||
@@ -59,5 +59,5 @@ "puppeteer": "^1", | ||
"custom-event-polyfill": "^1", | ||
"makeup-exit-emitter": "~0.1.1", | ||
"makeup-exit-emitter": "~0.2.0", | ||
"makeup-focusables": "~0.0.4", | ||
"makeup-next-id": "~0.0.3" | ||
"makeup-next-id": "~0.1.1" | ||
}, | ||
@@ -64,0 +64,0 @@ "files": [ |
@@ -26,4 +26,6 @@ # makeup-expander | ||
## Example | ||
## Example 1: Requires aria-expanded only | ||
In the first example, our expanded content is adjacent to the host element. | ||
```html | ||
@@ -70,6 +72,44 @@ <div class="expander"> | ||
## Example 2: Requires aria-expanded and a class | ||
In this second example, our expanded content is not adjacent to the host element. | ||
```html | ||
<div class="expander"> | ||
<span> | ||
<input class="expander__host" type="text"> | ||
</span> | ||
<div class="expander__content"> | ||
<p>Any kind of HTML control can go inside...</p> | ||
<p>A link: <a id="foo" href="http://www.ebay.com">www.ebay.com</a></p> | ||
<p>A button: <button>Click Me</button></p> | ||
<p>An input: <input type="text" aria-label="Dummy textbox"></p> | ||
<p>A checkbox: <input type="checkbox" aria-label="Dummy checkbox"></p> | ||
</div> | ||
</div> | ||
``` | ||
A CSS classname is required as our styling hook. This can be passed in via the options. | ||
```js | ||
// options | ||
const options = { | ||
expandedClass: 'expander--expanded', | ||
expandOnFocus: true | ||
}; | ||
``` | ||
Setting focus on the host (a text input) sets it's aria-expanded state *and* add adds the chosen class to the root. CSS can be used to display the content accordingly, for example: | ||
```css | ||
.expander--expanded .expander__content { | ||
display: block; | ||
} | ||
``` | ||
## Params | ||
* `el`: the root widget el | ||
* `options.alwaysDoFocusManagement`: honour `focusManagement` setting even when non-keyboard device is used (default: false) | ||
* `options.alwaysDoFocusManagement`: whether `focusManagement` option (see below) should apply for mouse click | ||
* `options.ariaControls`: specify whether `aria-controls` relationship should be created between host and overlay (default: true) | ||
* `options.autoCollapse`: applies a collapse behavior (`collapseOnClick`, `collapseOnFocusOut`, `collapseOnMouseOut`) based on expand behaviour (default: false) | ||
@@ -83,3 +123,3 @@ * `options.collapseOnClickOut`: whether the content should collapse when clicking outside of content (default: false) | ||
* `options.expandOnHover`: whether the host should be hover activated (default: false) | ||
* `options.focusManagement`: where keyboard focus should go (null, 'content', 'focusable', 'interactive', or ID reference) after expanded (default: null) | ||
* `options.focusManagement`: where keyboard focus should go (null, 'content', 'focusable', 'interactive', or ID reference) when expanded via `ENTER` or `SPACEBAR` (default: null) | ||
* `options.hostSelector`: the query selector for the host element in relation to the widget (default: '.expander__host') | ||
@@ -95,2 +135,3 @@ * `options.expandedClass`: the class which will be used on the root element to signify expanded state. **Example:** `foo--expanded`; this mirrors the `aria-expanded="true"` setting on the host element | ||
* `collapseOnMouseOut` | ||
* `expanded` | ||
* `expandOnClick` | ||
@@ -102,6 +143,6 @@ * `expandOnFocus` | ||
* `collapse()`: set state to collapsed | ||
* `expand()`: set state to expanded | ||
* `isExpanded()`: returns expanded state | ||
* `toggle()`: toggle expanded/collapsed state | ||
* `collapse()`: set state to collapsed (DEPRECATED) | ||
* `expand()`: set state to expanded (DEPRECATED) | ||
* `isExpanded()`: returns expanded state (DEPRECATED) | ||
* `toggle()`: toggle expanded/collapsed state (DEPRECATED) | ||
@@ -108,0 +149,0 @@ ## Events |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
291130
304
183
+ Addedcustom-event@1.0.1(transitive)
+ Addedmakeup-exit-emitter@0.2.6(transitive)
+ Addedmakeup-next-id@0.1.3(transitive)
+ Addednanoid@2.1.11(transitive)
- Removedmakeup-exit-emitter@0.1.1(transitive)
- Removedmakeup-next-id@0.0.3(transitive)
Updatedmakeup-exit-emitter@~0.2.0
Updatedmakeup-next-id@~0.1.1