2023-05-24 05:29:02 -07:00
|
|
|
<div x-data="{
|
2023-06-20 18:15:31 -07:00
|
|
|
selectOpen: false,
|
2023-05-27 09:19:35 -07:00
|
|
|
selectedItem: '',
|
2023-05-24 05:29:02 -07:00
|
|
|
selectableItems: [
|
|
|
|
{
|
|
|
|
title: 'Milk',
|
|
|
|
value: 'milk',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Eggs',
|
|
|
|
value: 'eggs',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Cheese',
|
|
|
|
value: 'cheese',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Bread',
|
|
|
|
value: 'bread',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Apples',
|
|
|
|
value: 'apples',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Bananas',
|
|
|
|
value: 'bananas',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Yogurt',
|
|
|
|
value: 'yogurt',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Sugar',
|
|
|
|
value: 'sugar',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Salt',
|
|
|
|
value: 'salt',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Coffee',
|
|
|
|
value: 'coffee',
|
|
|
|
disabled: false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Tea',
|
|
|
|
value: 'tea',
|
|
|
|
disabled: false
|
|
|
|
}
|
|
|
|
],
|
2023-05-27 09:19:35 -07:00
|
|
|
selectableItemActive: null,
|
|
|
|
selectId: $id('select'),
|
2023-06-07 07:19:52 -07:00
|
|
|
selectKeydownValue: '',
|
|
|
|
selectKeydownTimeout: 1000,
|
2023-06-20 18:15:31 -07:00
|
|
|
selectKeydownClearTimeout: null,
|
2023-06-07 07:19:52 -07:00
|
|
|
selectDropdownPosition: 'bottom',
|
2023-05-27 09:19:35 -07:00
|
|
|
selectableItemIsActive(item) {
|
|
|
|
return this.selectableItemActive && this.selectableItemActive.value==item.value;
|
|
|
|
},
|
|
|
|
selectableItemActiveNext(){
|
|
|
|
let index = this.selectableItems.indexOf(this.selectableItemActive);
|
|
|
|
if(index < this.selectableItems.length-1){
|
|
|
|
this.selectableItemActive = this.selectableItems[index+1];
|
|
|
|
this.selectScrollToActiveItem();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
selectableItemActivePrevious(){
|
|
|
|
let index = this.selectableItems.indexOf(this.selectableItemActive);
|
|
|
|
if(index > 0){
|
|
|
|
this.selectableItemActive = this.selectableItems[index-1];
|
|
|
|
this.selectScrollToActiveItem();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
selectScrollToActiveItem(){
|
|
|
|
if(this.selectableItemActive){
|
|
|
|
activeElement = document.getElementById(this.selectableItemActive.value + '-' + this.selectId)
|
|
|
|
newScrollPos = (activeElement.offsetTop + activeElement.offsetHeight) - this.$refs.selectableItemsList.offsetHeight;
|
|
|
|
if(newScrollPos > 0){
|
|
|
|
this.$refs.selectableItemsList.scrollTop=newScrollPos;
|
|
|
|
} else {
|
|
|
|
this.$refs.selectableItemsList.scrollTop=0;
|
|
|
|
}
|
|
|
|
}
|
2023-06-07 07:19:52 -07:00
|
|
|
},
|
|
|
|
selectKeydown(event){
|
|
|
|
if (event.keyCode >= 65 && event.keyCode <= 90) {
|
|
|
|
|
|
|
|
this.selectKeydownValue += event.key;
|
2023-06-20 18:15:31 -07:00
|
|
|
selectedItemBestMatch = this.selectItemsFindBestMatch();
|
2023-06-07 07:19:52 -07:00
|
|
|
if(selectedItemBestMatch){
|
2023-06-20 18:15:31 -07:00
|
|
|
if(this.selectOpen){
|
2023-06-07 07:19:52 -07:00
|
|
|
this.selectableItemActive = selectedItemBestMatch;
|
|
|
|
this.selectScrollToActiveItem();
|
|
|
|
} else {
|
|
|
|
this.selectedItem = this.selectableItemActive = selectedItemBestMatch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(this.selectKeydownValue != ''){
|
|
|
|
clearTimeout(this.selectKeydownClearTimeout);
|
|
|
|
this.selectKeydownClearTimeout = setTimeout(() => {
|
|
|
|
this.selectKeydownValue = '';
|
|
|
|
}, this.selectKeydownTimeout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2023-06-20 18:15:31 -07:00
|
|
|
selectItemsFindBestMatch(){
|
2023-06-07 07:19:52 -07:00
|
|
|
typedValue = this.selectKeydownValue.toLowerCase();
|
|
|
|
var bestMatch = null;
|
|
|
|
var bestMatchIndex = -1;
|
|
|
|
for (var i = 0; i < this.selectableItems.length; i++) {
|
|
|
|
var title = this.selectableItems[i].title.toLowerCase();
|
|
|
|
var index = title.indexOf(typedValue);
|
2023-06-20 18:15:31 -07:00
|
|
|
if (index > -1 && (bestMatchIndex == -1 || index < bestMatchIndex) && !this.selectableItems[i].disabled) {
|
2023-06-07 07:19:52 -07:00
|
|
|
bestMatch = this.selectableItems[i];
|
|
|
|
bestMatchIndex = index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bestMatch;
|
|
|
|
},
|
|
|
|
selectPositionUpdate(){
|
|
|
|
selectDropdownBottomPos = this.$refs.selectButton.getBoundingClientRect().top + this.$refs.selectButton.offsetHeight + parseInt(window.getComputedStyle(this.$refs.selectableItemsList).maxHeight);
|
|
|
|
if(window.innerHeight < selectDropdownBottomPos){
|
|
|
|
this.selectDropdownPosition = 'top';
|
|
|
|
} else {
|
|
|
|
this.selectDropdownPosition = 'bottom';
|
|
|
|
}
|
2023-05-27 09:19:35 -07:00
|
|
|
}
|
2023-05-24 05:29:02 -07:00
|
|
|
}"
|
2023-05-27 09:19:35 -07:00
|
|
|
x-init="
|
2023-06-20 18:15:31 -07:00
|
|
|
$watch('selectOpen', function(){
|
2023-05-27 09:19:35 -07:00
|
|
|
if(!selectedItem){
|
2023-06-07 07:19:52 -07:00
|
|
|
selectableItemActive=selectableItems[0];
|
2023-05-27 09:19:35 -07:00
|
|
|
} else {
|
|
|
|
selectableItemActive=selectedItem;
|
|
|
|
}
|
2023-06-07 07:19:52 -07:00
|
|
|
setTimeout(function(){
|
|
|
|
selectScrollToActiveItem();
|
|
|
|
}, 10);
|
|
|
|
selectPositionUpdate();
|
|
|
|
window.addEventListener('resize', (event) => { selectPositionUpdate(); });
|
2023-05-27 09:19:35 -07:00
|
|
|
});
|
|
|
|
"
|
2023-06-20 18:15:31 -07:00
|
|
|
@keydown.escape="if(selectOpen){ selectOpen=false; }"
|
|
|
|
@keydown.down="if(selectOpen){ selectableItemActiveNext(); } else { selectOpen=true; } event.preventDefault();"
|
|
|
|
@keydown.up="if(selectOpen){ selectableItemActivePrevious(); } else { selectOpen=true; } event.preventDefault();"
|
|
|
|
@keydown.enter="selectedItem=selectableItemActive; selectOpen=false;"
|
2023-06-07 07:19:52 -07:00
|
|
|
@keydown="selectKeydown($event);"
|
2023-05-24 05:29:02 -07:00
|
|
|
class="relative w-64">
|
|
|
|
|
2023-06-20 18:15:31 -07:00
|
|
|
<button x-ref="selectButton" @click="selectOpen=!selectOpen"
|
|
|
|
:class="{ 'focus:ring-2 focus:ring-offset-2 focus:ring-neutral-400' : !selectOpen }"
|
|
|
|
class="relative min-h-[38px] flex items-center justify-between w-full py-2 pl-3 pr-10 text-left bg-white border rounded-md shadow-sm cursor-default border-neutral-200/70 focus:outline-none text-sm">
|
2023-06-07 07:19:52 -07:00
|
|
|
<span x-text="selectedItem ? selectedItem.title : 'Select Item'" class="truncate">Select Item</span>
|
2023-05-24 05:29:02 -07:00
|
|
|
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="w-5 h-5 text-gray-400"><path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd"></path></svg>
|
|
|
|
</span>
|
|
|
|
</button>
|
|
|
|
|
2023-06-20 18:15:31 -07:00
|
|
|
<ul x-show="selectOpen"
|
2023-05-27 09:19:35 -07:00
|
|
|
x-ref="selectableItemsList"
|
2023-06-20 18:15:31 -07:00
|
|
|
@click.away="selectOpen = false"
|
2023-05-24 05:29:02 -07:00
|
|
|
x-transition:enter="transition ease-out duration-50"
|
|
|
|
x-transition:enter-start="opacity-0 -translate-y-1"
|
|
|
|
x-transition:enter-end="opacity-100"
|
2023-06-07 07:19:52 -07:00
|
|
|
:class="{ 'bottom-0 mb-10' : selectDropdownPosition == 'top', 'top-0 mt-10' : selectDropdownPosition == 'bottom' }"
|
2023-06-20 18:15:31 -07:00
|
|
|
class="absolute w-full py-1 mt-1 overflow-auto text-sm bg-white rounded-md shadow-md max-h-56 ring-1 ring-black ring-opacity-5 focus:outline-none"
|
2023-05-24 05:29:02 -07:00
|
|
|
x-cloak>
|
|
|
|
|
|
|
|
<template x-for="item in selectableItems" :key="item.value">
|
|
|
|
<li
|
2023-06-20 18:15:31 -07:00
|
|
|
@click="selectedItem=item; selectOpen=false; $refs.selectButton.focus();"
|
2023-05-27 09:19:35 -07:00
|
|
|
:id="item.value + '-' + selectId"
|
2023-06-20 18:15:31 -07:00
|
|
|
:data-disabled="item.disabled"
|
2023-05-27 09:19:35 -07:00
|
|
|
:class="{ 'bg-neutral-100 text-gray-900' : selectableItemIsActive(item), '' : !selectableItemIsActive(item) }"
|
|
|
|
@mousemove="selectableItemActive=item"
|
2023-06-20 18:15:31 -07:00
|
|
|
class="relative flex items-center h-full py-2 pl-8 text-gray-700 cursor-default select-none data-[disabled]:opacity-50 data-[disabled]:pointer-events-none">
|
2023-05-27 09:19:35 -07:00
|
|
|
<svg x-show="selectedItem.value==item.value" class="absolute left-0 w-4 h-4 ml-2 stroke-current text-neutral-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
2023-05-24 05:29:02 -07:00
|
|
|
<span class="block font-medium truncate" x-text="item.title"></span>
|
|
|
|
</li>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
</div>
|