@anypoint-web-components/anypoint-radio-button
Advanced tools
Comparing version 0.1.1 to 0.1.2
import { LitElement, html, css } from 'lit-element'; | ||
import { CheckedElementMixin } from '@anypoint-web-components/anypoint-form-mixins/anypoint-form-mixins.js'; | ||
import '@anypoint-web-components/anypoint-styles/colors.js'; | ||
/** | ||
@@ -67,2 +68,3 @@ * `anypoint-radio-button` | ||
cursor: pointer; | ||
vertical-align: middle; | ||
} | ||
@@ -190,4 +192,5 @@ | ||
:host([disabled]) #radioLabel { | ||
opacity: 0.65; | ||
:host([disabled]) .radioLabel { | ||
pointer-events: none; | ||
color: var(--anypoint-radio-button-disabled-color, #a8a8a8); | ||
} | ||
@@ -291,2 +294,5 @@ `; | ||
_asyncClick() { | ||
if (this.disabled) { | ||
return; | ||
} | ||
setTimeout(() => this.click(), 1); | ||
@@ -293,0 +299,0 @@ } |
@@ -0,1 +1,3 @@ | ||
import { LitElement } from 'lit-element'; | ||
import { AnypointMenuMixin } from '@anypoint-web-components/anypoint-menu-mixin/anypoint-menu-mixin.js'; | ||
/** | ||
@@ -36,8 +38,5 @@ * A web component that groups custom radio buttons and handles selection inside | ||
*/ | ||
class AnypointRadioGroup extends HTMLElement { | ||
/** | ||
* @return {Node|undefined} Currently selected radio button. | ||
*/ | ||
get selected() { | ||
return this._selected; | ||
class AnypointRadioGroup extends AnypointMenuMixin(LitElement) { | ||
createRenderRoot() { | ||
return this; | ||
} | ||
@@ -53,57 +52,19 @@ /** | ||
super(); | ||
this._nodesChanged = this._nodesChanged.bind(this); | ||
this._radioAction = this._radioAction.bind(this); | ||
this.multi = false; | ||
} | ||
connectedCallback() { | ||
if (super.connectedCallback) { | ||
super.connectedCallback(); | ||
} | ||
this.style.display = 'inline-block'; | ||
this.style.verticalAlign = 'middle'; | ||
this.setAttribute('role', 'radiogroup'); | ||
const config = { | ||
attributes: true, | ||
childList: true, | ||
subtree: true | ||
}; | ||
this._observer = new MutationObserver(this._nodesChanged); | ||
this._observer.observe(this, config); | ||
this._discoverNodes(); | ||
} | ||
disconnectedCallback() { | ||
this._observer.disconnect(); | ||
this._observer = null; | ||
this._removeListeners(); | ||
} | ||
/** | ||
* Processes mutations to the light DOM of this element. | ||
* Processes added and removed nodes and changes to attributes. | ||
* @param {Array<MutationRecord>} mutationsList List of changes discovered by | ||
* `MutationObserver` | ||
*/ | ||
_nodesChanged(mutationsList) { | ||
for (const mutation of mutationsList) { | ||
switch (mutation.type) { | ||
case 'attributes': | ||
this._processNodeAttributeChange(mutation); | ||
break; | ||
case 'childList': | ||
this._processAddedNodes(mutation.addedNodes); | ||
this._processRemovedNodes(mutation.removedNodes); | ||
this._manageNodesSelection(mutation.addedNodes); | ||
break; | ||
} | ||
this.selectable = '[role=radio],input[type=radio]'; | ||
this._ensureSingleSelection(); | ||
if (this.disabled) { | ||
this._disabledChanged(this.disabled); | ||
} | ||
} | ||
/** | ||
* This to be run when element is inserted into the DOM. | ||
* It discovers radio buttons in light DOM and manages the sate. | ||
*/ | ||
_discoverNodes() { | ||
const nodes = this.elements; | ||
if (nodes.length) { | ||
this._processAddedNodes(nodes); | ||
this._manageNodesSelection(nodes); | ||
} | ||
} | ||
/** | ||
* Function that manages attribute change. | ||
@@ -120,2 +81,5 @@ * If the changed attribute is `role` with value `radio` then the node is processed | ||
const target = record.target; | ||
if (target === this) { | ||
return; | ||
} | ||
if (target.getAttribute('role') === 'radio') { | ||
@@ -128,32 +92,2 @@ this._processAddedNodes([target]); | ||
/** | ||
* Processes new and existing nodes and makes single selection from multiple selected | ||
* radio buttons. If arriving `nodes` has selected nodes the last selected node in | ||
* the selection keeps the selection. | ||
* | ||
* @param {NodeList?} nodes Optional list of newly added nodes to the light DOM. | ||
*/ | ||
_manageNodesSelection(nodes) { | ||
let selected = this._lastSelected(nodes); | ||
const domNodes = this.elements; | ||
if (!selected) { | ||
selected = this._lastSelected(domNodes); | ||
} | ||
if (!selected) { | ||
return; | ||
} | ||
this._selected = selected; | ||
for (let i = domNodes.length - 1; i >= 0; i--) { | ||
const node = domNodes[i]; | ||
if (!this._isRadioButton(node)) { | ||
continue; | ||
} | ||
if (node.disabled || !node.checked) { | ||
continue; | ||
} | ||
if (node !== selected && node.checked) { | ||
node.checked = false; | ||
} | ||
} | ||
} | ||
/** | ||
* Tests if given node is a radio button. | ||
@@ -174,21 +108,2 @@ * @param {Node} node A node to test | ||
/** | ||
* @param {NodeList?} nodes Optional list of nodes to check for selection. | ||
* @return {Node} Last selected node or undefined when no selection is detected. | ||
*/ | ||
_lastSelected(nodes) { | ||
if (!nodes || !nodes.length) { | ||
return; | ||
} | ||
for (let i = nodes.length - 1; i >= 0; i--) { | ||
const node = nodes[i]; | ||
if (!this._isRadioButton(node)) { | ||
continue; | ||
} | ||
if (node.disabled || !node.checked) { | ||
continue; | ||
} | ||
return node; | ||
} | ||
} | ||
/** | ||
* Adds `change` event listener to detected radio buttons. | ||
@@ -202,9 +117,6 @@ * A button is considered as a radio button when its `role` is `radio`. | ||
const node = nodes[i]; | ||
if (!this._isRadioButton(node)) { | ||
if (node === this || !this._isRadioButton(node)) { | ||
continue; | ||
} | ||
// The event may have been already added when the attribute has been set | ||
// with the same name again. | ||
node.removeEventListener('change', this._radioAction); | ||
node.addEventListener('change', this._radioAction); | ||
node.setAttribute('tabindex', '-1'); | ||
} | ||
@@ -220,3 +132,3 @@ } | ||
const node = nodes[i]; | ||
if (!this._isRadioButton(node)) { | ||
if (node === this || !this._isRadioButton(node)) { | ||
continue; | ||
@@ -234,42 +146,96 @@ } | ||
_nodeRemoved(node) { | ||
node.removeEventListener('change', this._radioAction); | ||
if (node === this._selected) { | ||
this._selected = undefined; | ||
const { selected } = this; | ||
if ((selected || selected === 0) && this._valueForItem(node) === selected) { | ||
this.selected = undefined; | ||
} | ||
} | ||
/** | ||
* Remove listeners from all current `elements`. | ||
* Overrides `AnypointMenuMixin._onKeydown`. Adds right / left arrows support. | ||
* @param {KeyboardEvent} e | ||
*/ | ||
_removeListeners() { | ||
const nodes = this.elements; | ||
for (let i = 0, len = nodes.length; i < len; i++) { | ||
nodes[i].removeEventListener('change', this._radioAction); | ||
_onKeydown(e) { | ||
if (e.key === 'ArrowRight') { | ||
this._onDownKey(e); | ||
e.stopPropagation(); | ||
} else if (e.key === 'ArrowLeft') { | ||
this._onUpKey(e); | ||
e.stopPropagation(); | ||
} else { | ||
super._onKeydown(e); | ||
} | ||
} | ||
/** | ||
* Handler for radio's `change` event. | ||
* @param {Event} e | ||
* Overrides `AnypointSelectableMixin._applySelection` to manage item's checked | ||
* state. | ||
* @param {Node} item Selected / deselected item. | ||
* @param {Boolean} isSelected True if the item is selected | ||
*/ | ||
_radioAction(e) { | ||
const target = e.target; | ||
if (!target.checked) { | ||
return; | ||
_applySelection(item, isSelected) { | ||
super._applySelection(item, isSelected); | ||
item.checked = isSelected; | ||
} | ||
/** | ||
* Ensures that the last child element is checked in the group. | ||
*/ | ||
_ensureSingleSelection() { | ||
const nodes = this._items; | ||
let checked = false; | ||
for (let i = nodes.length - 1; i >= 0; i--) { | ||
const currentChecked = nodes[i].checked; | ||
if (currentChecked && !checked) { | ||
checked = true; | ||
if (this.attrForSelected) { | ||
const value = this._valueForItem(nodes[i]); | ||
this.select(value); | ||
} else { | ||
this.select(i); | ||
} | ||
} else if (currentChecked && checked) { | ||
this._applySelection(nodes[i], false); | ||
} | ||
} | ||
this._selected = target; | ||
const name = target.name; | ||
if (!name) { | ||
return; | ||
} | ||
// Normally you would use querySelectorAll with [name="${name}"] | ||
// but the name can be anything, including invalid selectors causing | ||
// error. This queries for all checkboxes and compares names manually. | ||
const nodes = this.elements; | ||
for (let i = 0, len = nodes.length; i < len; i++) { | ||
const node = nodes[i]; | ||
if (node.name !== name && node !== target && node.checked) { | ||
node.checked = false; | ||
} | ||
/** | ||
* Overrides `AnypointSelectableMixin._mutationHandler`. | ||
* Processes dynamically added nodes and updates selection if needed. | ||
* @param {Array<MutationRecord>} mutationsList A list of changes record | ||
*/ | ||
_mutationHandler(mutationsList) { | ||
for (const mutation of mutationsList) { | ||
if (mutation.type === 'attributes') { | ||
this._processNodeAttributeChange(mutation); | ||
} else if (mutation.type === 'childList') { | ||
if (mutation.addedNodes && mutation.addedNodes.length) { | ||
this._ensureSingleSelection(); | ||
} | ||
if (mutation.removedNodes && mutation.removedNodes.length) { | ||
this._processRemovedNodes(mutation.removedNodes); | ||
} | ||
} | ||
} | ||
super._mutationHandler(mutationsList); | ||
} | ||
/** | ||
* Overrides `AnypointSelectableMixin._observeItems` to include subtree. | ||
* @return {MutationObserver} | ||
*/ | ||
_observeItems() { | ||
const config = { | ||
attributes: true, | ||
childList: true, | ||
subtree: true | ||
}; | ||
const observer = new MutationObserver(this._mutationHandler); | ||
observer.observe(this, config); | ||
return observer; | ||
} | ||
/** | ||
* Disables children when disabled state changes | ||
* @param {Boolean} disabled | ||
*/ | ||
_disabledChanged(disabled) { | ||
super._disabledChanged(disabled); | ||
this.items.forEach((node) => node.disabled = disabled); | ||
} | ||
} | ||
window.customElements.define('anypoint-radio-group', AnypointRadioGroup); |
{ | ||
"name": "@anypoint-web-components/anypoint-radio-button", | ||
"description": "Anypoint styled radio button", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"license": "Apache-2.0", | ||
@@ -33,18 +33,20 @@ "main": "anypoint-radio-button.js", | ||
"@anypoint-web-components/anypoint-form-mixins": "^1.0.1", | ||
"@anypoint-web-components/anypoint-menu-mixin": "^1.0.0", | ||
"@anypoint-web-components/anypoint-styles": "^1.0.0-preview.1", | ||
"lit-element": "^2.0.1" | ||
}, | ||
"devDependencies": { | ||
"@advanced-rest-client/arc-demo-helper": "^1.0.9", | ||
"@advanced-rest-client/arc-demo-helper": "^1.0.12", | ||
"@advanced-rest-client/eslint-config": "^1.0.6", | ||
"@advanced-rest-client/prettier-config": "^0.1.0", | ||
"@advanced-rest-client/testing-karma-sl": "^1.0.3", | ||
"@anypoint-web-components/anypoint-styles": "^1.0.0-preview.1", | ||
"@anypoint-web-components/anypoint-checkbox": "^1.0.0", | ||
"@commitlint/cli": "^8.1.0", | ||
"@commitlint/config-conventional": "^7.0.0", | ||
"@open-wc/testing": "^2.2.1", | ||
"@open-wc/testing-karma": "^3.1.5", | ||
"@open-wc/testing-karma": "^3.1.17", | ||
"@polymer/gen-typescript-declarations": "^1.6.2", | ||
"@polymer/iron-test-helpers": "^3.0.1", | ||
"deepmerge": "^4.0.0", | ||
"es-dev-server": "^1.8.3", | ||
"es-dev-server": "^1.11.1", | ||
"husky": "^1.0.0", | ||
@@ -51,0 +53,0 @@ "karma": "^4.2.0", |
@@ -7,6 +7,13 @@ [![Published on NPM](https://img.shields.io/npm/v/@anypoint-web-components/anypoint-radio-button.svg)](https://www.npmjs.com/package/@anypoint-web-components/anypoint-radio-button) | ||
Accessible radio button and radio buttons group for Anypoint platform | ||
Accessible radio button and radio button group for Anypoint platform. | ||
Radio buttons are used to select one of predefined options. | ||
Radio buttons should be used when the user must see all available options. Consider using a dropdown list if the options can be collapsed. | ||
See [Radio buttons](https://material.io/design/components/selection-controls.html#radio-buttons) documentation in Material Design documentation for principles and anatomy of radio buttons. | ||
## Usage | ||
### Installation | ||
``` | ||
@@ -13,0 +20,0 @@ npm i --save @anypoint-web-components/anypoint-radio-button |
61
25494
4
665
+ Added@anypoint-web-components/anypoint-styles@^1.0.0-preview.1
+ Added@anypoint-web-components/anypoint-menu-mixin@1.1.9(transitive)
+ Added@anypoint-web-components/anypoint-selector@1.1.8(transitive)
+ Added@anypoint-web-components/anypoint-styles@1.0.4(transitive)