From 2d35ef8988b2796dcd01b511d8200b08ff6ff978 Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Sat, 17 Jul 2021 23:48:27 +0530 Subject: [PATCH] Select.js code refactoring --- components/dist/select.js | 215 +++++++++++++++------------------- components/dist/select.min.js | 2 +- components/index.html | 16 +-- components/index.min.html | 4 +- 4 files changed, 98 insertions(+), 139 deletions(-) diff --git a/components/dist/select.js b/components/dist/select.js index 2a79ac7..cc5566c 100644 --- a/components/dist/select.js +++ b/components/dist/select.js @@ -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 = `
-
+
@@ -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 } }
- - - +
`; 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); - } } }) diff --git a/components/dist/select.min.js b/components/dist/select.min.js index 24d6784..7b3ab27 100644 --- a/components/dist/select.min.js +++ b/components/dist/select.min.js @@ -1 +1 @@ -const smSelect=document.createElement("template");smSelect.innerHTML='\n\n
\n
\n
\n \n
\n
\n \n
\n
',customElements.define("sm-select",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smSelect.content.cloneNode(!0)),this.reset=this.reset.bind(this),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.availableOptions,this.optionList=this.shadowRoot.querySelector(".options"),this.chevron=this.shadowRoot.querySelector(".toggle"),this.selection=this.shadowRoot.querySelector(".selection"),this.previousOption,this.isOpen=!1,this.slideDown=[{transform:"translateY(-0.5rem)",opacity:0},{transform:"translateY(0)",opacity:1}],this.slideUp=[{transform:"translateY(0)",opacity:1},{transform:"translateY(-0.5rem)",opacity:0}],this.animationOptions={duration:300,fill:"forwards",easing:"ease"}}static get observedAttributes(){return["value","disabled"]}get value(){return this.getAttribute("value")}set value(e){this.setAttribute("value",e)}reset(){}open(){this.optionList.classList.remove("hide"),this.optionList.animate(this.slideDown,this.animationOptions),this.chevron.classList.add("rotate"),this.isOpen=!0}collapse(){this.chevron.classList.remove("rotate"),this.optionList.animate(this.slideUp,this.animationOptions).onfinish=(()=>{this.optionList.classList.add("hide"),this.isOpen=!1})}toggle(){this.isOpen||this.hasAttribute("disabled")?this.collapse():this.open()}handleSelectKeyDown(e){"ArrowDown"===e.code||"ArrowRight"===e.code?(e.preventDefault(),this.availableOptions[0].focus()):"Enter"!==e.code&&"Space"!==e.code||(this.isOpen?this.collapse():(this.optionList.classList.remove("hide"),this.optionList.animate(this.slideDown,this.animationOptions),this.chevron.classList.add("rotate"),this.isOpen=!0))}handleOptionsKeyDown(e){"ArrowUp"===e.code||"ArrowRight"===e.code?(e.preventDefault(),document.activeElement.previousElementSibling?document.activeElement.previousElementSibling.focus():this.availableOptions[this.availableOptions.length-1].focus()):"ArrowDown"!==e.code&&"ArrowLeft"!==e.code||(e.preventDefault(),document.activeElement.nextElementSibling?document.activeElement.nextElementSibling.focus():this.availableOptions[0].focus())}handleOptionSelected(e){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:!0,composed:!0,detail:{value:e.detail.value}})),this.previousOption&&this.previousOption.classList.remove("check-selected"),this.previousOption=e.target),e.detail.switching||this.collapse(),e.target.classList.add("check-selected")}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.selection.setAttribute("tabindex","0");let e=this.shadowRoot.querySelector("slot");e.addEventListener("slotchange",t=>{if(this.availableOptions=e.assignedElements(),this.availableOptions[0]){let e=this.availableOptions[0];this.previousOption=e,e.classList.add("check-selected"),this.setAttribute("value",e.getAttribute("value")),this.shadowRoot.querySelector(".option-text").textContent=e.textContent,this.availableOptions.forEach((e,t)=>{e.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=>{this.isOpen&&!this.contains(e.target)&&this.collapse()})}attributeChangedCallback(e,t,n){"disabled"===e&&(this.hasAttribute("disabled")?this.selection.removeAttribute("tabindex"):this.selection.setAttribute("tabindex","0"))}disconnectedCallback(){this.selection.removeEventListener("click",this.toggle),this.selection.removeEventListener("keydown",this.handleSelectKeyDown),this.optionList.removeEventListener("keydown",this.handleOptionsKeyDown)}});const smOption=document.createElement("template");smOption.innerHTML='\n\n
\n \n \n \n \n
',customElements.define("sm-option",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smOption.content.cloneNode(!0)),this.sendDetails=this.sendDetails.bind(this)}sendDetails(e){let t=new CustomEvent("optionSelected",{bubbles:!0,composed:!0,detail:{text:this.textContent,value:this.getAttribute("value"),switching:e}});this.dispatchEvent(t)}connectedCallback(){this.setAttribute("role","option");let e=["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"];this.addEventListener("click",this.sendDetails(!1)),this.addEventListener("keydown",t=>{"Enter"!==t.code&&"Space"!==t.code||(t.preventDefault(),this.sendDetails(!1)),e.includes(t.code)&&(t.preventDefault(),this.sendDetails(!0))}),this.hasAttribute("default")&&setTimeout(()=>{this.sendDetails()},0)}}); \ No newline at end of file +const smSelect=document.createElement("template");smSelect.innerHTML='\n\n
\n
\n
\n \n
\n
\n \n
\n
',customElements.define("sm-select",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smSelect.content.cloneNode(!0)),this.reset=this.reset.bind(this),this.open=this.open.bind(this),this.collapse=this.collapse.bind(this),this.toggle=this.toggle.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.previousOption,this.isOpen=!1,this.slideDown=[{transform:"translateY(-0.5rem)",opacity:0},{transform:"translateY(0)",opacity:1}],this.slideUp=[{transform:"translateY(0)",opacity:1},{transform:"translateY(-0.5rem)",opacity:0}],this.animationOptions={duration:300,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"]}get value(){return this.getAttribute("value")}set value(e){this.setAttribute("value",e)}reset(){}open(){this.optionList.classList.remove("hide"),this.optionList.animate(this.slideDown,this.animationOptions),this.chevron.classList.add("rotate"),this.isOpen=!0}collapse(){this.chevron.classList.remove("rotate"),this.optionList.animate(this.slideUp,this.animationOptions).onfinish=(()=>{this.optionList.classList.add("hide"),this.isOpen=!1})}toggle(){this.isOpen||this.hasAttribute("disabled")?this.collapse():this.open()}fireEvent(){this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{value:this.value}}))}handleOptionsNavigation(e){"ArrowUp"===e.code?(e.preventDefault(),document.activeElement.previousElementSibling?document.activeElement.previousElementSibling.focus():this.availableOptions[this.availableOptions.length-1].focus()):"ArrowDown"===e.code&&(e.preventDefault(),document.activeElement.nextElementSibling?document.activeElement.nextElementSibling.focus():this.availableOptions[0].focus())}handleOptionSelection(e){this.previousOption!==document.activeElement&&(this.value=document.activeElement.getAttribute("value"),this.selectedOptionText.textContent=this.value,this.fireEvent(),this.previousOption&&this.previousOption.classList.remove("check-selected"),document.activeElement.classList.add("check-selected"),this.previousOption=document.activeElement)}handleClick(e){e.target===this?this.toggle():(this.handleOptionSelection(),this.collapse())}handleKeydown(e){e.target===this?"ArrowDown"===e.code?(e.preventDefault(),this.availableOptions[0].focus(),this.handleOptionSelection(e)):"Enter"!==e.code&&"Space"!==e.code||(e.preventDefault(),this.toggle()):(this.handleOptionsNavigation(e),this.handleOptionSelection(e),"Enter"!==e.code&&"Space"!==e.code||(e.preventDefault(),this.collapse()))}handleClickOutside(e){this.isOpen&&!this.contains(e.target)&&this.collapse()}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.selection.setAttribute("tabindex","0");let e=this.shadowRoot.querySelector("slot");e.addEventListener("slotchange",t=>{if(this.availableOptions=e.assignedElements(),this.availableOptions[0]){let e=this.availableOptions[0];this.previousOption=e,e.classList.add("check-selected"),this.value=e.getAttribute("value"),this.selectedOptionText.textContent=e.textContent,this.availableOptions.forEach(e=>{e.setAttribute("tabindex","0")})}}),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeydown),document.addEventListener("mousedown",this.handleClickOutside)}disconnectedCallback(){this.removeEventListener("click",this.toggle),this.removeEventListener("keydown",this.handleKeydown),document.removeEventListener("mousedown",this.handleClickOutside)}attributeChangedCallback(e){"disabled"===e&&(this.hasAttribute("disabled")?this.selection.removeAttribute("tabindex"):this.selection.setAttribute("tabindex","0"))}});const smOption=document.createElement("template");smOption.innerHTML='\n\n
\n \n \n
',customElements.define("sm-option",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smOption.content.cloneNode(!0))}connectedCallback(){this.setAttribute("role","option")}}); \ No newline at end of file diff --git a/components/index.html b/components/index.html index 57a11dd..9c5afb9 100644 --- a/components/index.html +++ b/components/index.html @@ -871,7 +871,7 @@ also identical.

Interactive demo

- + option1 option2 option3 @@ -1023,20 +1023,6 @@
- -
-

Select

-

- <sm-select> is very similar to starndatd HTML5 select and it's - markup stucture is - also identical. -

- - option1 - option2 something - option3 - -

Spinner

diff --git a/components/index.min.html b/components/index.min.html index 054837b..20db38c 100644 --- a/components/index.min.html +++ b/components/index.min.html @@ -92,7 +92,7 @@ <div>Disabled switch</div> </sm-radio> -

Attributes

All the native HTML radio attributes are valid

Attribute

Description

checked (boolean)

If present, radio button is set to checked state as default.

disabled (boolean)

If present radio button is set to disabled state. all the interactions are disabled

name (string)

Can be used to group radio buttons with same name. only one radio button will be active in a group.

value (string)

Sets value of radio button which can be accessed by value property with JS

Select

<sm-select> is very similar to starndatd HTML5 select and it's markup stucture is also identical.

Interactive demo

option1option2option3
+

Attributes

All the native HTML radio attributes are valid

Attribute

Description

checked (boolean)

If present, radio button is set to checked state as default.

disabled (boolean)

If present radio button is set to disabled state. all the interactions are disabled

name (string)

Can be used to group radio buttons with same name. only one radio button will be active in a group.

value (string)

Sets value of radio button which can be accessed by value property with JS

Select

<sm-select> is very similar to starndatd HTML5 select and it's markup stucture is also identical.

Interactive demo

option1option2option3
 
 <sm-select>
     <sm-option value="1">option1</sm-option>
@@ -142,7 +142,7 @@
     </div>
 </sm-switch>    
 
-

Attributes

All the native HTML checkbox attributes are valid

Attribute

Description

checked (boolean)

If present, switch is set to checked state as default.

disabled (boolean)

If present switch is set to disabled state. all the interactions are disabled

value (string)

Sets value of switch which can be accessed by value property with JS

Select

<sm-select> is very similar to starndatd HTML5 select and it's markup stucture is also identical.

option1option2 somethingoption3

Spinner

Just drop the sm-spinner in markup where you want to show the spinner

+

Attributes

All the native HTML checkbox attributes are valid

Attribute

Description

checked (boolean)

If present, switch is set to checked state as default.

disabled (boolean)

If present switch is set to disabled state. all the interactions are disabled

value (string)

Sets value of switch which can be accessed by value property with JS

Spinner

Just drop the sm-spinner in markup where you want to show the spinner

 
 <sm-spinner></sm-spinner>