@@ -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
',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
',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
',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
',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.
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