Select.js code refactoring

This commit is contained in:
sairaj mote 2021-07-17 23:48:27 +05:30
parent ef03b3b554
commit 2d35ef8988
4 changed files with 98 additions and 139 deletions

View File

@ -42,7 +42,7 @@ smSelect.innerHTML = `
width: 1.5rem; width: 1.5rem;
fill: rgba(var(--text-color), 0.7); fill: rgba(var(--text-color), 0.7);
} }
.option-text{ .selected-option-text{
font-size: 0.9rem; font-size: 0.9rem;
overflow: hidden; overflow: hidden;
-o-text-overflow: ellipsis; -o-text-overflow: ellipsis;
@ -121,7 +121,7 @@ smSelect.innerHTML = `
</style> </style>
<div class="select" > <div class="select" >
<div class="selection"> <div class="selection">
<div class="option-text"></div> <div class="selected-option-text"></div>
<svg class="icon toggle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/></svg> <svg class="icon toggle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/></svg>
</div> </div>
<div part="options" class="options hide"> <div part="options" class="options hide">
@ -139,16 +139,13 @@ customElements.define('sm-select', class extends HTMLElement {
this.open = this.open.bind(this) this.open = this.open.bind(this)
this.collapse = this.collapse.bind(this) this.collapse = this.collapse.bind(this)
this.toggle = this.toggle.bind(this) this.toggle = this.toggle.bind(this)
this.handleSelectKeyDown = this.handleSelectKeyDown.bind(this) this.handleOptionsNavigation = this.handleOptionsNavigation.bind(this)
this.handleOptionsKeyDown = this.handleOptionsKeyDown.bind(this) this.handleOptionSelection = this.handleOptionSelection.bind(this)
this.handleOptionsKeyDown = this.handleOptionsKeyDown.bind(this) this.handleKeydown = this.handleKeydown.bind(this)
this.handleOptionSelected = this.handleOptionSelected.bind(this) this.handleClickOutside = this.handleClickOutside.bind(this)
this.availableOptions this.availableOptions
this.optionList = this.shadowRoot.querySelector('.options') this.previousOption
this.chevron = this.shadowRoot.querySelector('.toggle')
this.selection = this.shadowRoot.querySelector('.selection'),
this.previousOption
this.isOpen = false; this.isOpen = false;
this.slideDown = [{ this.slideDown = [{
transform: `translateY(-0.5rem)`, transform: `translateY(-0.5rem)`,
@ -173,6 +170,11 @@ customElements.define('sm-select', class extends HTMLElement {
fill: "forwards", fill: "forwards",
easing: 'ease' easing: 'ease'
} }
this.optionList = this.shadowRoot.querySelector('.options')
this.chevron = this.shadowRoot.querySelector('.toggle')
this.selection = this.shadowRoot.querySelector('.selection')
this.selectedOptionText = this.shadowRoot.querySelector('.selected-option-text')
} }
static get observedAttributes() { static get observedAttributes() {
return ['value', 'disabled'] return ['value', 'disabled']
@ -209,24 +211,19 @@ customElements.define('sm-select', class extends HTMLElement {
this.collapse() this.collapse()
} }
} }
handleSelectKeyDown(e) {
if (e.code === 'ArrowDown' || e.code === 'ArrowRight') { fireEvent() {
e.preventDefault() this.dispatchEvent(new CustomEvent('change', {
this.availableOptions[0].focus() bubbles: true,
} composed: true,
else if (e.code === 'Enter' || e.code === 'Space') { detail: {
if (!this.isOpen) { value: this.value
this.optionList.classList.remove('hide')
this.optionList.animate(this.slideDown, this.animationOptions)
this.chevron.classList.add('rotate')
this.isOpen = true
} else {
this.collapse()
} }
} }))
} }
handleOptionsKeyDown(e) {
if (e.code === 'ArrowUp' || e.code === 'ArrowRight') { handleOptionsNavigation(e) {
if (e.code === 'ArrowUp') {
e.preventDefault() e.preventDefault()
if (document.activeElement.previousElementSibling) { if (document.activeElement.previousElementSibling) {
document.activeElement.previousElementSibling.focus() document.activeElement.previousElementSibling.focus()
@ -234,7 +231,7 @@ customElements.define('sm-select', class extends HTMLElement {
this.availableOptions[this.availableOptions.length - 1].focus() this.availableOptions[this.availableOptions.length - 1].focus()
} }
} }
else if (e.code === 'ArrowDown' || e.code === 'ArrowLeft') { else if (e.code === 'ArrowDown') {
e.preventDefault() e.preventDefault()
if (document.activeElement.nextElementSibling) { if (document.activeElement.nextElementSibling) {
document.activeElement.nextElementSibling.focus() document.activeElement.nextElementSibling.focus()
@ -243,26 +240,52 @@ customElements.define('sm-select', class extends HTMLElement {
} }
} }
} }
handleOptionSelected(e) { handleOptionSelection(e) {
if (this.previousOption !== e.target) { if (this.previousOption !== document.activeElement) {
this.setAttribute('value', e.detail.value) this.value = document.activeElement.getAttribute('value')
this.shadowRoot.querySelector('.option-text').textContent = e.detail.text; this.selectedOptionText.textContent = this.value;
this.dispatchEvent(new CustomEvent('change', { this.fireEvent()
bubbles: true,
composed: true,
detail: {
value: e.detail.value
}
}))
if (this.previousOption) { if (this.previousOption) {
this.previousOption.classList.remove('check-selected') this.previousOption.classList.remove('check-selected')
} }
this.previousOption = e.target; document.activeElement.classList.add('check-selected')
this.previousOption = document.activeElement
} }
if (!e.detail.switching) }
handleClick(e) {
if (e.target === this) {
this.toggle()
}
else {
this.handleOptionSelection()
this.collapse() this.collapse()
}
e.target.classList.add('check-selected') }
handleKeydown(e) {
if (e.target === this) {
if (e.code === 'ArrowDown') {
e.preventDefault()
this.availableOptions[0].focus()
this.handleOptionSelection(e)
}
else if (e.code === 'Enter' || e.code === 'Space') {
e.preventDefault()
this.toggle()
}
}
else {
this.handleOptionsNavigation(e)
this.handleOptionSelection(e)
if (e.code === 'Enter' || e.code === 'Space') {
e.preventDefault()
this.collapse()
}
}
}
handleClickOutside(e) {
if (this.isOpen && !this.contains(e.target)) {
this.collapse()
}
} }
connectedCallback() { connectedCallback() {
this.setAttribute('role', 'listbox') this.setAttribute('role', 'listbox')
@ -276,37 +299,31 @@ customElements.define('sm-select', class extends HTMLElement {
let firstElement = this.availableOptions[0]; let firstElement = this.availableOptions[0];
this.previousOption = firstElement; this.previousOption = firstElement;
firstElement.classList.add('check-selected') firstElement.classList.add('check-selected')
this.setAttribute('value', firstElement.getAttribute('value')) this.value = firstElement.getAttribute('value')
this.shadowRoot.querySelector('.option-text').textContent = firstElement.textContent this.selectedOptionText.textContent = firstElement.textContent
this.availableOptions.forEach((element, index) => { this.availableOptions.forEach((element) => {
element.setAttribute('tabindex', "0"); element.setAttribute('tabindex', "0");
}) })
} }
}); });
this.selection.addEventListener('click', this.toggle) this.addEventListener('click', this.handleClick)
this.selection.addEventListener('keydown', this.handleSelectKeyDown) this.addEventListener('keydown', this.handleKeydown)
this.optionList.addEventListener('keydown', this.handleOptionsKeyDown) document.addEventListener('mousedown', this.handleClickOutside)
this.addEventListener('optionSelected', this.handleOptionSelected)
document.addEventListener('mousedown', e => {
if (this.isOpen && !this.contains(e.target)) {
this.collapse()
}
})
} }
attributeChangedCallback(name, oldVal, newVal) { disconnectedCallback() {
this.removeEventListener('click', this.toggle)
this.removeEventListener('keydown', this.handleKeydown)
document.removeEventListener('mousedown', this.handleClickOutside)
}
attributeChangedCallback(name) {
if (name === "disabled") { if (name === "disabled") {
if (this.hasAttribute('disabled')) { if (this.hasAttribute('disabled')) {
this.selection.removeAttribute('tabindex') this.selection.removeAttribute('tabindex')
}else { } else {
this.selection.setAttribute('tabindex', '0') this.selection.setAttribute('tabindex', '0')
} }
} }
} }
disconnectedCallback() {
this.selection.removeEventListener('click', this.toggle)
this.selection.removeEventListener('keydown', this.handleSelectKeyDown)
this.optionList.removeEventListener('keydown', this.handleOptionsKeyDown)
}
}) })
// option // option
@ -325,54 +342,47 @@ smOption.innerHTML = `
display: flex; display: flex;
} }
.option{ .option{
min-width: 100%;
padding: 0.8rem 1.2rem;
cursor: pointer;
overflow-wrap: break-word;
outline: none;
display: -webkit-box; display: -webkit-box;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
-webkit-box-align: center; -webkit-box-align: center;
-ms-flex-align: center; -ms-flex-align: center;
align-items: center; align-items: center;
min-width: 100%;
padding: 0.8rem 1.2rem;
cursor: pointer;
overflow-wrap: break-word;
outline: none;
user-select: none;
} }
:host(:focus){ :host(:focus){
outline: none; outline: none;
background: rgba(var(--text-color), 0.1); background: rgba(var(--text-color), 0.1);
} }
.icon {
opacity: 0;
height: 1.2rem;
width: 1.2rem;
margin-right: 0.5rem;
fill: rgba(var(--text-color), 0.8);
}
:host(:focus) .option .icon{ :host(:focus) .option .icon{
opacity: 0.4 opacity: 0.4
} }
:host(.check-selected) .icon{ :host(.check-selected) .icon{
opacity: 1 !important opacity: 1
}
.icon {
margin-right: 0.8rem;
fill: none;
height: 0.8rem;
width: 0.8rem;
stroke: rgba(var(--text-color), 0.7);
stroke-width: 10;
overflow: visible;
stroke-linecap: round;
border-radius: 1rem;
stroke-linejoin: round;
opacity: 0;
} }
@media (hover: hover){ @media (hover: hover){
.option:hover{ .option:hover{
background: rgba(var(--text-color), 0.1); background: rgba(var(--text-color), 0.1);
} }
.option:hover .icon{ :host(:not(.check-selected):hover) .icon{
opacity: 0.4 opacity: 0.4
} }
} }
</style> </style>
<div class="option"> <div class="option">
<svg class="icon" viewBox="0 0 64 64"> <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>
<polyline points="0.35 31.82 21.45 52.98 63.65 10.66"/>
</svg>
<slot></slot> <slot></slot>
</div>`; </div>`;
customElements.define('sm-option', class extends HTMLElement { customElements.define('sm-option', class extends HTMLElement {
@ -381,46 +391,9 @@ customElements.define('sm-option', class extends HTMLElement {
this.attachShadow({ this.attachShadow({
mode: 'open' mode: 'open'
}).append(smOption.content.cloneNode(true)) }).append(smOption.content.cloneNode(true))
this.sendDetails = this.sendDetails.bind(this)
}
sendDetails(switching) {
let optionSelected = new CustomEvent('optionSelected', {
bubbles: true,
composed: true,
detail: {
text: this.textContent,
value: this.getAttribute('value'),
switching
}
})
this.dispatchEvent(optionSelected)
} }
connectedCallback() { connectedCallback() {
this.setAttribute('role', 'option') this.setAttribute('role', 'option')
let validKey = [
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight'
]
this.addEventListener('click', this.sendDetails(false))
this.addEventListener('keydown', e => {
if (e.code === 'Enter' || e.code === 'Space') {
e.preventDefault()
this.sendDetails(false)
}
if (validKey.includes(e.code)) {
e.preventDefault()
this.sendDetails(true)
}
})
if (this.hasAttribute('default')) {
setTimeout(() => {
this.sendDetails()
}, 0);
}
} }
}) })

File diff suppressed because one or more lines are too long

View File

@ -871,7 +871,7 @@
also identical. also identical.
</p> </p>
<h2>Interactive demo</h2> <h2>Interactive demo</h2>
<sm-select> <sm-select id="my_select">
<sm-option value="1">option1</sm-option> <sm-option value="1">option1</sm-option>
<sm-option value="2">option2</sm-option> <sm-option value="2">option2</sm-option>
<sm-option value="3">option3</sm-option> <sm-option value="3">option3</sm-option>
@ -1023,20 +1023,6 @@
</div> </div>
</section> </section>
</section> </section>
<section id="select_page" class="page hide-completely">
<h1 class="page__title">Select</h1>
<p>
<span class="highlight">&lt;sm-select&gt;</span> is very similar to starndatd HTML5 select and it's
markup stucture is
also identical.
</p>
<sm-select>
<sm-option value="1">option1</sm-option>
<sm-option value="2">option2 something</sm-option>
<sm-option value="3">option3</sm-option>
</sm-select>
</section>
<section id="spinner_page" class="page hide-completely"> <section id="spinner_page" class="page hide-completely">
<h1 class="page__title">Spinner</h1> <h1 class="page__title">Spinner</h1>
<p> <p>

View File

@ -92,7 +92,7 @@
&lt;div&gt;Disabled switch&lt;/div&gt; &lt;div&gt;Disabled switch&lt;/div&gt;
&lt;/sm-radio&gt; &lt;/sm-radio&gt;
</code> </code>
</pre><h2>Attributes</h2><p>All the native HTML radio attributes are valid</p><section class="table"><div class="tr"><h4 class="table__heading">Attribute</h4><h4 class="table__heading">Description</h4></div><div class="tr"><div><span class="highlight">checked</span> (boolean)</div><p>If present, radio button is set to checked state as default.</p></div><div class="tr"><div><span class="highlight">disabled</span> (boolean)</div><p>If present radio button is set to disabled state. all the interactions are disabled</p></div><div class="tr"><div><span class="highlight">name</span> (string)</div><p>Can be used to group radio buttons with same name. only one radio button will be active in a group.</p></div><div class="tr"><div><span class="highlight">value</span> (string)</div><p>Sets value of radio button which can be accessed by value property with JS</p></div></section></section><section id="select_page" class="page hide-completely"><h1 class="page__title">Select</h1><p><span class="highlight">&lt;sm-select&gt;</span> is very similar to starndatd HTML5 select and it's markup stucture is also identical.</p><h2>Interactive demo</h2><sm-select><sm-option value="1">option1</sm-option><sm-option value="2">option2</sm-option><sm-option value="3">option3</sm-option></sm-select><pre> </pre><h2>Attributes</h2><p>All the native HTML radio attributes are valid</p><section class="table"><div class="tr"><h4 class="table__heading">Attribute</h4><h4 class="table__heading">Description</h4></div><div class="tr"><div><span class="highlight">checked</span> (boolean)</div><p>If present, radio button is set to checked state as default.</p></div><div class="tr"><div><span class="highlight">disabled</span> (boolean)</div><p>If present radio button is set to disabled state. all the interactions are disabled</p></div><div class="tr"><div><span class="highlight">name</span> (string)</div><p>Can be used to group radio buttons with same name. only one radio button will be active in a group.</p></div><div class="tr"><div><span class="highlight">value</span> (string)</div><p>Sets value of radio button which can be accessed by value property with JS</p></div></section></section><section id="select_page" class="page hide-completely"><h1 class="page__title">Select</h1><p><span class="highlight">&lt;sm-select&gt;</span> is very similar to starndatd HTML5 select and it's markup stucture is also identical.</p><h2>Interactive demo</h2><sm-select id="my_select"><sm-option value="1">option1</sm-option><sm-option value="2">option2</sm-option><sm-option value="3">option3</sm-option></sm-select><pre>
<code> <code>
&lt;sm-select&gt; &lt;sm-select&gt;
&lt;sm-option value="1"&gt;option1&lt;/sm-option&gt; &lt;sm-option value="1"&gt;option1&lt;/sm-option&gt;
@ -142,7 +142,7 @@
&lt;/div&gt; &lt;/div&gt;
&lt;/sm-switch&gt; &lt;/sm-switch&gt;
</code> </code>
</pre><h2>Attributes</h2><p>All the native HTML checkbox attributes are valid</p><section class="table"><div class="tr"><h4 class="table__heading">Attribute</h4><h4 class="table__heading">Description</h4></div><div class="tr"><div><span class="highlight">checked</span> (boolean)</div><p>If present, switch is set to checked state as default.</p></div><div class="tr"><div><span class="highlight">disabled</span> (boolean)</div><p>If present switch is set to disabled state. all the interactions are disabled</p></div><div class="tr"><div><span class="highlight">value</span> (string)</div><p>Sets value of switch which can be accessed by value property with JS</p></div></section></section><section id="select_page" class="page hide-completely"><h1 class="page__title">Select</h1><p><span class="highlight">&lt;sm-select&gt;</span> is very similar to starndatd HTML5 select and it's markup stucture is also identical.</p><sm-select><sm-option value="1">option1</sm-option><sm-option value="2">option2 something</sm-option><sm-option value="3">option3</sm-option></sm-select></section><section id="spinner_page" class="page hide-completely"><h1 class="page__title">Spinner</h1><p>Just drop the <span class="highlight">sm-spinner</span> in markup where you want to show the spinner</p><sm-spinner></sm-spinner><pre> </pre><h2>Attributes</h2><p>All the native HTML checkbox attributes are valid</p><section class="table"><div class="tr"><h4 class="table__heading">Attribute</h4><h4 class="table__heading">Description</h4></div><div class="tr"><div><span class="highlight">checked</span> (boolean)</div><p>If present, switch is set to checked state as default.</p></div><div class="tr"><div><span class="highlight">disabled</span> (boolean)</div><p>If present switch is set to disabled state. all the interactions are disabled</p></div><div class="tr"><div><span class="highlight">value</span> (string)</div><p>Sets value of switch which can be accessed by value property with JS</p></div></section></section><section id="spinner_page" class="page hide-completely"><h1 class="page__title">Spinner</h1><p>Just drop the <span class="highlight">sm-spinner</span> in markup where you want to show the spinner</p><sm-spinner></sm-spinner><pre>
<code> <code>
&lt;sm-spinner&gt;&lt;/sm-spinner&gt; &lt;sm-spinner&gt;&lt;/sm-spinner&gt;
</code> </code>