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;
fill: rgba(var(--text-color), 0.7);
}
.option-text{
.selected-option-text{
font-size: 0.9rem;
overflow: hidden;
-o-text-overflow: ellipsis;
@ -121,7 +121,7 @@ smSelect.innerHTML = `
</style>
<div class="select" >
<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>
</div>
<div part="options" class="options hide">
@ -139,16 +139,13 @@ customElements.define('sm-select', class extends HTMLElement {
this.open = this.open.bind(this)
this.collapse = this.collapse.bind(this)
this.toggle = this.toggle.bind(this)
this.handleSelectKeyDown = this.handleSelectKeyDown.bind(this)
this.handleOptionsKeyDown = this.handleOptionsKeyDown.bind(this)
this.handleOptionsKeyDown = this.handleOptionsKeyDown.bind(this)
this.handleOptionSelected = this.handleOptionSelected.bind(this)
this.handleOptionsNavigation = this.handleOptionsNavigation.bind(this)
this.handleOptionSelection = this.handleOptionSelection.bind(this)
this.handleKeydown = this.handleKeydown.bind(this)
this.handleClickOutside = this.handleClickOutside.bind(this)
this.availableOptions
this.optionList = this.shadowRoot.querySelector('.options')
this.chevron = this.shadowRoot.querySelector('.toggle')
this.selection = this.shadowRoot.querySelector('.selection'),
this.previousOption
this.previousOption
this.isOpen = false;
this.slideDown = [{
transform: `translateY(-0.5rem)`,
@ -173,6 +170,11 @@ customElements.define('sm-select', class extends HTMLElement {
fill: "forwards",
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() {
return ['value', 'disabled']
@ -209,24 +211,19 @@ customElements.define('sm-select', class extends HTMLElement {
this.collapse()
}
}
handleSelectKeyDown(e) {
if (e.code === 'ArrowDown' || e.code === 'ArrowRight') {
e.preventDefault()
this.availableOptions[0].focus()
}
else if (e.code === 'Enter' || e.code === 'Space') {
if (!this.isOpen) {
this.optionList.classList.remove('hide')
this.optionList.animate(this.slideDown, this.animationOptions)
this.chevron.classList.add('rotate')
this.isOpen = true
} else {
this.collapse()
fireEvent() {
this.dispatchEvent(new CustomEvent('change', {
bubbles: true,
composed: true,
detail: {
value: this.value
}
}
}))
}
handleOptionsKeyDown(e) {
if (e.code === 'ArrowUp' || e.code === 'ArrowRight') {
handleOptionsNavigation(e) {
if (e.code === 'ArrowUp') {
e.preventDefault()
if (document.activeElement.previousElementSibling) {
document.activeElement.previousElementSibling.focus()
@ -234,7 +231,7 @@ customElements.define('sm-select', class extends HTMLElement {
this.availableOptions[this.availableOptions.length - 1].focus()
}
}
else if (e.code === 'ArrowDown' || e.code === 'ArrowLeft') {
else if (e.code === 'ArrowDown') {
e.preventDefault()
if (document.activeElement.nextElementSibling) {
document.activeElement.nextElementSibling.focus()
@ -243,26 +240,52 @@ customElements.define('sm-select', class extends HTMLElement {
}
}
}
handleOptionSelected(e) {
if (this.previousOption !== e.target) {
this.setAttribute('value', e.detail.value)
this.shadowRoot.querySelector('.option-text').textContent = e.detail.text;
this.dispatchEvent(new CustomEvent('change', {
bubbles: true,
composed: true,
detail: {
value: e.detail.value
}
}))
handleOptionSelection(e) {
if (this.previousOption !== document.activeElement) {
this.value = document.activeElement.getAttribute('value')
this.selectedOptionText.textContent = this.value;
this.fireEvent()
if (this.previousOption) {
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()
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() {
this.setAttribute('role', 'listbox')
@ -276,37 +299,31 @@ customElements.define('sm-select', class extends HTMLElement {
let firstElement = this.availableOptions[0];
this.previousOption = firstElement;
firstElement.classList.add('check-selected')
this.setAttribute('value', firstElement.getAttribute('value'))
this.shadowRoot.querySelector('.option-text').textContent = firstElement.textContent
this.availableOptions.forEach((element, index) => {
this.value = firstElement.getAttribute('value')
this.selectedOptionText.textContent = firstElement.textContent
this.availableOptions.forEach((element) => {
element.setAttribute('tabindex', "0");
})
}
});
this.selection.addEventListener('click', this.toggle)
this.selection.addEventListener('keydown', this.handleSelectKeyDown)
this.optionList.addEventListener('keydown', this.handleOptionsKeyDown)
this.addEventListener('optionSelected', this.handleOptionSelected)
document.addEventListener('mousedown', e => {
if (this.isOpen && !this.contains(e.target)) {
this.collapse()
}
})
this.addEventListener('click', this.handleClick)
this.addEventListener('keydown', this.handleKeydown)
document.addEventListener('mousedown', this.handleClickOutside)
}
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 (this.hasAttribute('disabled')) {
this.selection.removeAttribute('tabindex')
}else {
} else {
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
@ -325,54 +342,47 @@ smOption.innerHTML = `
display: flex;
}
.option{
min-width: 100%;
padding: 0.8rem 1.2rem;
cursor: pointer;
overflow-wrap: break-word;
outline: none;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: 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){
outline: none;
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{
opacity: 0.4
}
:host(.check-selected) .icon{
opacity: 1 !important
}
.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;
opacity: 1
}
@media (hover: hover){
.option:hover{
background: rgba(var(--text-color), 0.1);
}
.option:hover .icon{
:host(:not(.check-selected):hover) .icon{
opacity: 0.4
}
}
</style>
<div class="option">
<svg class="icon" viewBox="0 0 64 64">
<polyline points="0.35 31.82 21.45 52.98 63.65 10.66"/>
</svg>
<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>
<slot></slot>
</div>`;
customElements.define('sm-option', class extends HTMLElement {
@ -381,46 +391,9 @@ customElements.define('sm-option', class extends HTMLElement {
this.attachShadow({
mode: 'open'
}).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() {
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.
</p>
<h2>Interactive demo</h2>
<sm-select>
<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>
@ -1023,20 +1023,6 @@
</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>

View File

@ -92,7 +92,7 @@
&lt;div&gt;Disabled switch&lt;/div&gt;
&lt;/sm-radio&gt;
</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>
&lt;sm-select&gt;
&lt;sm-option value="1"&gt;option1&lt;/sm-option&gt;
@ -142,7 +142,7 @@
&lt;/div&gt;
&lt;/sm-switch&gt;
</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>
&lt;sm-spinner&gt;&lt;/sm-spinner&gt;
</code>