diff --git a/README.md b/README.md index 6d0ec9b..f7ab515 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,67 @@ return [ ## Usage +### Basic Usage + ```php +use Prvious\Filament\Combobox\Components\Combobox; + +Combobox::make('status') + ->options([ + 'draft' => 'Draft', + 'published' => 'Published', + 'archived' => 'Archived', + ]) +``` + +### Setting a Search Query +You can set a search query that will be displayed in the search input when the component loads: + +```php +Combobox::make('product') + ->searchable() + ->options([ + // your options + ]) + ->searchQuery('electronics') ``` +This will populate the search input with "electronics" but will not trigger a search automatically. + +### Auto-Triggering a Search on Load + +If you want to perform a search automatically when the component boots, use the `autoSearch()` method along with `searchQuery()`: + +```php +Combobox::make('product') + ->searchable() + ->getSearchResultsUsing(function (string $search) { + return Product::where('name', 'like', "%{$search}%") + ->limit(50) + ->pluck('name', 'id') + ->toArray(); + }) + ->searchQuery('electronics') + ->autoSearch() +``` + +This is useful when you want to pre-filter options based on context, such as: +- Showing products from a specific category +- Filtering items based on user permissions +- Displaying search results based on a related field value + +You can also conditionally enable auto-search: + +```php +Combobox::make('product') + ->searchable() + ->searchQuery($categoryName) + ->autoSearch(filled($categoryName)) +``` + +**Note:** The `autoSearch()` method requires that the component is searchable and has a search handler configured. + ## Testing ```bash diff --git a/resources/dist/components/combobox.js b/resources/dist/components/combobox.js index 886ba3e..8acf4c4 100644 --- a/resources/dist/components/combobox.js +++ b/resources/dist/components/combobox.js @@ -1 +1 @@ -function d(c){return c==null||c===""||typeof c=="string"&&c.trim()===""}function l(c){return!d(c)}var N=class{constructor({element:t,options:e,placeholder:s,state:i,canOptionLabelsWrap:o=!0,canSelectPlaceholder:n=!0,initialOptionLabel:a=null,initialOptionLabels:r=null,initialState:h=null,isHtmlAllowed:p=!1,isAutofocused:f=!1,isDisabled:m=!1,isMultiple:g=!1,isSearchable:y=!1,getOptionLabelUsing:L=null,getOptionLabelsUsing:O=null,getOptionsUsing:x=null,getSearchResultsUsing:S=null,hasDynamicOptions:I=!1,hasDynamicSearchResults:C=!0,searchPrompt:A="Search...",searchDebounce:v=1e3,loadingMessage:w="Loading...",searchingMessage:D="Searching...",noSearchResultsMessage:E="No results found",maxItems:R=null,maxItemsMessage:M="Maximum number of items selected",optionsLimit:k=null,searchableOptionFields:b=["label"],livewireId:u=null,statePath:T=null,onStateChange:V=()=>{}}){this.element=t,this.options=e,this.originalOptions=JSON.parse(JSON.stringify(e)),this.placeholder=s,this.state=i,this.canOptionLabelsWrap=o,this.canSelectPlaceholder=n,this.initialOptionLabel=a,this.initialOptionLabels=r,this.initialState=h,this.isHtmlAllowed=p,this.isAutofocused=f,this.isDisabled=m,this.isMultiple=g,this.isSearchable=y,this.getOptionLabelUsing=L,this.getOptionLabelsUsing=O,this.getOptionsUsing=x,this.getSearchResultsUsing=S,this.hasDynamicOptions=I,this.hasDynamicSearchResults=C,this.searchPrompt=A,this.searchDebounce=v,this.loadingMessage=w,this.searchingMessage=D,this.noSearchResultsMessage=E,this.maxItems=R,this.maxItemsMessage=M,this.optionsLimit=k,this.searchableOptionFields=Array.isArray(b)?b:["label"],this.livewireId=u,this.statePath=T,this.onStateChange=V,this.labelRepository={},this.selectedIndex=-1,this.searchQuery="",this.searchTimeout=null,this.isSearching=!1,this.selectedDisplayVersion=0,this.render(),this.setUpEventListeners(),this.isAutofocused&&this.searchInput&&this.searchInput.focus()}populateLabelRepositoryFromOptions(t){if(!(!t||!Array.isArray(t)))for(let e of t)e.options&&Array.isArray(e.options)?this.populateLabelRepositoryFromOptions(e.options):e.value!==void 0&&e.label!==void 0&&(this.labelRepository[e.value]=e.label)}render(){this.populateLabelRepositoryFromOptions(this.options),this.container=document.createElement("div"),this.container.className="prvious-combobox-input-ctn",this.canOptionLabelsWrap||this.container.classList.add("prvious-combobox-input-ctn-option-labels-not-wrapped"),this.selectedDisplay=document.createElement("div"),this.selectedDisplay.className="prvious-combobox-input-value-ctn",this.updateSelectedDisplay(),this.isSearchable&&(this.searchContainer=document.createElement("div"),this.searchContainer.className="prvious-combobox-input-search-ctn",this.searchInput=document.createElement("input"),this.searchInput.className="fi-input",this.searchInput.type="text",this.searchInput.placeholder=this.searchPrompt,this.searchInput.setAttribute("aria-label","Search"),this.searchContainer.appendChild(this.searchInput)),this.optionsContainer=document.createElement("div"),this.optionsContainer.className="prvious-combobox-options-wrapper max-h-60 overflow-y-auto",this.optionsContainer.setAttribute("role","listbox"),this.optionsContainer.setAttribute("tabindex","-1"),this.isMultiple&&this.optionsContainer.setAttribute("aria-multiselectable","true"),this.optionsList=document.createElement("ul"),this.renderOptions(),this.optionsContainer.appendChild(this.optionsList),this.container.appendChild(this.selectedDisplay),this.isSearchable&&this.container.appendChild(this.searchContainer),this.container.appendChild(this.optionsContainer),this.element.appendChild(this.container),this.applyDisabledState(),this.hasDynamicOptions&&this.getOptionsUsing&&this.loadDynamicOptions()}async loadDynamicOptions(){this.showLoadingState(!1);try{let t=await this.getOptionsUsing(),e=Array.isArray(t)?t:t&&Array.isArray(t.options)?t.options:[];this.options=e,this.originalOptions=JSON.parse(JSON.stringify(e)),this.populateLabelRepositoryFromOptions(e),this.renderOptions()}catch(t){console.error("Error fetching options:",t),this.hideLoadingState()}}renderOptions(){this.optionsList.innerHTML="";let t=0,e=this.options,s=0,i=!1;this.options.forEach(a=>{a.options&&Array.isArray(a.options)?(s+=a.options.length,i=!0):s++}),i?this.optionsList.className="prvious-combobox-input-options-ctn":s>0&&(this.optionsList.className="fi-dropdown-list");let o=i?null:this.optionsList,n=0;for(let a of e){if(this.optionsLimit&&n>=this.optionsLimit)break;if(a.options&&Array.isArray(a.options)){let r=a.options;if(this.isMultiple&&Array.isArray(this.state)&&this.state.length>0&&(r=a.options.filter(h=>!this.state.includes(h.value))),r.length>0){if(this.optionsLimit){let h=this.optionsLimit-n;h{let a=this.createOptionElement(n.value,n);o.appendChild(a)}),s.appendChild(i),s.appendChild(o),this.optionsList.appendChild(s)}createOptionElement(t,e){let s=t,i=e,o=!1;typeof e=="object"&&e!==null&&"label"in e&&"value"in e&&(s=e.value,i=e.label,o=e.isDisabled||!1);let n=document.createElement("li");n.className="fi-dropdown-list-item prvious-combobox-input-option",o&&n.classList.add("fi-disabled");let a=`prvious-combobox-input-option-${Math.random().toString(36).substring(2,11)}`;if(n.id=a,n.setAttribute("role","option"),n.setAttribute("data-value",s),n.setAttribute("tabindex","0"),o&&n.setAttribute("aria-disabled","true"),this.isHtmlAllowed&&typeof i=="string"){let p=document.createElement("div");p.innerHTML=i;let f=p.textContent||p.innerText||i;n.setAttribute("aria-label",f)}let r=this.isMultiple?Array.isArray(this.state)&&this.state.includes(s):this.state===s;n.setAttribute("aria-selected",r?"true":"false"),r&&n.classList.add("fi-selected");let h=document.createElement("span");return this.isHtmlAllowed?h.innerHTML=i:h.textContent=i,n.appendChild(h),o||n.addEventListener("click",p=>{p.preventDefault(),p.stopPropagation(),this.selectOption(s),this.isMultiple&&(this.isSearchable&&this.searchInput?setTimeout(()=>{this.searchInput.focus()},0):setTimeout(()=>{n.focus()},0))}),n}async updateSelectedDisplay(){this.selectedDisplayVersion=this.selectedDisplayVersion+1;let t=this.selectedDisplayVersion,e=document.createDocumentFragment();if(this.isMultiple){if(!Array.isArray(this.state)||this.state.length===0){let i=document.createElement("span");i.textContent=this.placeholder,i.classList.add("prvious-combobox-input-placeholder"),e.appendChild(i)}else{let i=await this.getLabelsForMultipleSelection();if(t!==this.selectedDisplayVersion)return;this.addBadgesForSelectedOptions(i,e)}t===this.selectedDisplayVersion&&this.selectedDisplay.replaceChildren(e);return}if(this.state===null||this.state===""){let i=document.createElement("span");i.textContent=this.placeholder,i.classList.add("prvious-combobox-input-placeholder"),e.appendChild(i),t===this.selectedDisplayVersion&&this.selectedDisplay.replaceChildren(e);return}let s=await this.getLabelForSingleSelection();t===this.selectedDisplayVersion&&(this.addSingleSelectionDisplay(s,e),t===this.selectedDisplayVersion&&this.selectedDisplay.replaceChildren(e))}async getLabelsForMultipleSelection(){let t=this.getSelectedOptionLabels(),e=[];if(Array.isArray(this.state)){for(let i of this.state)if(!l(this.labelRepository[i])){if(l(t[i])){this.labelRepository[i]=t[i];continue}e.push(i.toString())}}if(e.length>0&&l(this.initialOptionLabels)&&JSON.stringify(this.state)===JSON.stringify(this.initialState)){if(Array.isArray(this.initialOptionLabels))for(let i of this.initialOptionLabels)l(i)&&i.value!==void 0&&i.label!==void 0&&e.includes(i.value)&&(this.labelRepository[i.value]=i.label)}else if(e.length>0&&this.getOptionLabelsUsing)try{let i=await this.getOptionLabelsUsing();for(let o of i)l(o)&&o.value!==void 0&&o.label!==void 0&&(this.labelRepository[o.value]=o.label)}catch(i){console.error("Error fetching option labels:",i)}let s=[];if(Array.isArray(this.state))for(let i of this.state)l(this.labelRepository[i])?s.push(this.labelRepository[i]):l(t[i])?s.push(t[i]):s.push(i);return s}createBadgeElement(t,e){let s=document.createElement("span");s.className="fi-badge fi-size-md fi-color fi-color-primary fi-text-color-600 dark:fi-text-color-200",l(t)&&s.setAttribute("data-value",t);let i=document.createElement("span");i.className="fi-badge-label-ctn";let o=document.createElement("span");o.className="fi-badge-label",this.canOptionLabelsWrap&&o.classList.add("fi-wrapped"),this.isHtmlAllowed?o.innerHTML=e:o.textContent=e,i.appendChild(o),s.appendChild(i);let n=this.createRemoveButton(t,e);return s.appendChild(n),s}createRemoveButton(t,e){let s=document.createElement("button");return s.type="button",s.className="fi-badge-delete-btn",s.innerHTML='',s.setAttribute("aria-label","Remove "+(this.isHtmlAllowed?e.replace(/<[^>]*>/g,""):e)),s.addEventListener("click",i=>{i.stopPropagation(),l(t)&&this.selectOption(t)}),s.addEventListener("keydown",i=>{(i.key===" "||i.key==="Enter")&&(i.preventDefault(),i.stopPropagation(),l(t)&&this.selectOption(t))}),s}addBadgesForSelectedOptions(t,e=this.selectedDisplay){let s=document.createElement("div");s.className="prvious-combobox-input-value-badges-ctn",t.forEach((i,o)=>{let n=Array.isArray(this.state)?this.state[o]:null,a=this.createBadgeElement(n,i);s.appendChild(a)}),e.appendChild(s)}async getLabelForSingleSelection(){let t=this.labelRepository[this.state];if(d(t)&&(t=this.getSelectedOptionLabel(this.state)),d(t)&&l(this.initialOptionLabel)&&this.state===this.initialState)t=this.initialOptionLabel,l(this.state)&&(this.labelRepository[this.state]=t);else if(d(t)&&this.getOptionLabelUsing)try{t=await this.getOptionLabelUsing(),l(t)&&l(this.state)&&(this.labelRepository[this.state]=t)}catch(e){console.error("Error fetching option label:",e),t=this.state}else d(t)&&(t=this.state);return t}addSingleSelectionDisplay(t,e=this.selectedDisplay){let s=document.createElement("span");if(s.className="prvious-combobox-input-value-label",this.isHtmlAllowed?s.innerHTML=t:s.textContent=t,e.appendChild(s),!this.canSelectPlaceholder)return;let i=document.createElement("button");i.type="button",i.className="prvious-combobox-input-value-remove-btn",i.innerHTML='',i.setAttribute("aria-label","Clear selection"),i.addEventListener("click",o=>{o.stopPropagation(),this.selectOption("")}),i.addEventListener("keydown",o=>{(o.key===" "||o.key==="Enter")&&(o.preventDefault(),o.stopPropagation(),this.selectOption(""))}),e.appendChild(i)}getSelectedOptionLabel(t){if(l(this.labelRepository[t]))return this.labelRepository[t];let e="";for(let s of this.options)if(s.options&&Array.isArray(s.options)){for(let i of s.options)if(i.value===t){e=i.label,this.labelRepository[t]=e;break}}else if(s.value===t){e=s.label,this.labelRepository[t]=e;break}return e}setUpEventListeners(){this.isSearchable&&this.searchInput&&(this.searchInput.addEventListener("input",t=>{this.isDisabled||this.handleSearch(t)}),this.searchInput.addEventListener("keydown",t=>{if(!this.isDisabled){if(t.key==="Tab"){t.preventDefault();let e=this.getVisibleOptions();if(e.length===0)return;t.shiftKey?this.selectedIndex=e.length-1:this.selectedIndex=0,e.forEach(s=>{s.classList.remove("fi-selected")}),e[this.selectedIndex].classList.add("fi-selected"),e[this.selectedIndex].focus()}else if(t.key==="ArrowDown"){if(t.preventDefault(),t.stopPropagation(),this.getVisibleOptions().length===0)return;this.selectedIndex=-1,this.searchInput.blur(),this.focusNextOption()}else if(t.key==="ArrowUp"){t.preventDefault(),t.stopPropagation();let e=this.getVisibleOptions();if(e.length===0)return;this.selectedIndex=e.length-1,this.searchInput.blur(),e[this.selectedIndex].classList.add("fi-selected"),e[this.selectedIndex].focus(),e[this.selectedIndex].id&&this.optionsContainer.setAttribute("aria-activedescendant",e[this.selectedIndex].id),this.scrollOptionIntoView(e[this.selectedIndex])}else if(t.key==="Enter"){if(t.preventDefault(),t.stopPropagation(),this.isSearching)return;let e=this.getVisibleOptions();if(e.length===0)return;let s=e.find(o=>{let n=o.getAttribute("aria-disabled")==="true",a=o.classList.contains("fi-disabled"),r=o.offsetParent===null;return!(n||a||r)});if(!s)return;let i=s.getAttribute("data-value");if(i===null)return;this.selectOption(i)}}})),this.optionsContainerKeydownListener=t=>{this.isDisabled||this.isSearchable&&document.activeElement===this.searchInput&&!["Tab","Escape"].includes(t.key)||this.handleOptionsKeydown(t)},this.optionsContainer.addEventListener("keydown",this.optionsContainerKeydownListener),!this.isMultiple&&this.livewireId&&this.statePath&&this.getOptionLabelUsing&&(this.refreshOptionLabelListener=async t=>{if(t.detail.livewireId===this.livewireId&&t.detail.statePath===this.statePath&&l(this.state))try{delete this.labelRepository[this.state];let e=await this.getOptionLabelUsing();l(e)&&(this.labelRepository[this.state]=e);let s=this.selectedDisplay.querySelector(".prvious-combobox-input-value-label");l(s)&&(this.isHtmlAllowed?s.innerHTML=e:s.textContent=e),this.updateOptionLabelInList(this.state,e)}catch(e){console.error("Error refreshing option label:",e)}},window.addEventListener("filament-forms::select.refreshSelectedOptionLabel",this.refreshOptionLabelListener))}updateOptionLabelInList(t,e){this.labelRepository[t]=e;let s=this.getVisibleOptions();for(let i of s)if(i.getAttribute("data-value")===String(t)){if(i.innerHTML="",this.isHtmlAllowed){let o=document.createElement("span");o.innerHTML=e,i.appendChild(o)}else i.appendChild(document.createTextNode(e));break}for(let i of this.options)if(i.options&&Array.isArray(i.options)){for(let o of i.options)if(o.value===t){o.label=e;break}}else if(i.value===t){i.label=e;break}for(let i of this.originalOptions)if(i.options&&Array.isArray(i.options)){for(let o of i.options)if(o.value===t){o.label=e;break}}else if(i.value===t){i.label=e;break}}handleOptionsKeydown(t){switch(t.key){case"ArrowDown":t.preventDefault(),t.stopPropagation(),this.focusNextOption();break;case"ArrowUp":t.preventDefault(),t.stopPropagation(),this.focusPreviousOption();break;case" ":if(t.preventDefault(),this.selectedIndex>=0){let e=this.getVisibleOptions()[this.selectedIndex];e&&e.click()}break;case"Enter":if(t.preventDefault(),this.selectedIndex>=0){let e=this.getVisibleOptions()[this.selectedIndex];e&&e.click()}break;case"Tab":break;default:if(this.isSearchable&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&typeof t.key=="string"&&t.key.length===1){t.preventDefault();let e=t.key;this.searchInput&&(this.searchInput.focus(),this.searchInput.value=(this.searchInput.value||"")+e,this.searchInput.dispatchEvent(new Event("input",{bubbles:!0})))}break}}focusNextOption(){let t=this.getVisibleOptions();if(t.length!==0){if(this.selectedIndex>=0&&this.selectedIndex=0&&this.selectedIndexe.bottom?this.optionsContainer.scrollTop+=s.bottom-e.bottom:s.top li[role="option"]')):t=Array.from(this.optionsList.querySelectorAll(':scope > ul.fi-dropdown-list > li[role="option"]'));let e=Array.from(this.optionsList.querySelectorAll('li.prvious-combobox-input-option-group > ul > li[role="option"]'));return[...t,...e]}getSelectedOptionLabels(){if(!Array.isArray(this.state)||this.state.length===0)return{};let t={};for(let e of this.state){let s=!1;for(let i of this.options)if(i.options&&Array.isArray(i.options)){for(let o of i.options)if(o.value===e){t[e]=o.label,s=!0;break}if(s)break}else if(i.value===e){t[e]=i.label,s=!0;break}}return t}handleSearch(t){let e=t.target.value.trim().toLowerCase();if(this.searchQuery=e,this.searchTimeout&&clearTimeout(this.searchTimeout),e===""){this.options=JSON.parse(JSON.stringify(this.originalOptions)),this.renderOptions();return}if(!this.getSearchResultsUsing||typeof this.getSearchResultsUsing!="function"||!this.hasDynamicSearchResults){this.filterOptions(e);return}this.searchTimeout=setTimeout(async()=>{this.searchTimeout=null,this.isSearching=!0;try{this.showLoadingState(!0);let s=await this.getSearchResultsUsing(e),i=Array.isArray(s)?s:s&&Array.isArray(s.options)?s.options:[];this.options=i,this.populateLabelRepositoryFromOptions(i),this.hideLoadingState(),this.renderOptions(),this.options.length===0&&this.showNoResultsMessage()}catch(s){console.error("Error fetching search results:",s),this.hideLoadingState(),this.options=JSON.parse(JSON.stringify(this.originalOptions)),this.renderOptions()}finally{this.isSearching=!1}},this.searchDebounce)}showLoadingState(t=!1){this.optionsList.parentNode===this.optionsContainer&&this.optionsContainer.removeChild(this.optionsList),this.hideLoadingState();let e=document.createElement("div");e.className="prvious-combobox-input-message",e.textContent=t?this.searchingMessage:this.loadingMessage,this.optionsContainer.appendChild(e)}hideLoadingState(){let t=this.optionsContainer.querySelector(".prvious-combobox-input-message");t&&t.remove()}showNoResultsMessage(){this.optionsList.parentNode===this.optionsContainer&&this.optionsContainer.removeChild(this.optionsList),this.hideLoadingState();let t=document.createElement("div");t.className="prvious-combobox-input-message",t.textContent=this.noSearchResultsMessage,this.optionsContainer.appendChild(t)}filterOptions(t){let e=this.searchableOptionFields.includes("label"),s=this.searchableOptionFields.includes("value"),i=[];for(let o of this.originalOptions)if(o.options&&Array.isArray(o.options)){let n=o.options.filter(a=>e&&a.label.toLowerCase().includes(t)||s&&String(a.value).toLowerCase().includes(t));n.length>0&&i.push({label:o.label,options:n})}else(e&&o.label.toLowerCase().includes(t)||s&&String(o.value).toLowerCase().includes(t))&&i.push(o);this.options=i,this.renderOptions(),this.options.length===0&&this.showNoResultsMessage()}selectOption(t){if(this.isDisabled)return;if(!this.isMultiple){this.state=t,this.updateSelectedDisplay(),this.renderOptions(),this.onStateChange(this.state);return}let e=Array.isArray(this.state)?[...this.state]:[];if(e.includes(t)){let i=this.selectedDisplay.querySelector(`[data-value="${t}"]`);if(l(i)){let o=i.parentElement;l(o)&&o.children.length===1?(e=e.filter(n=>n!==t),this.state=e,this.updateSelectedDisplay()):(i.remove(),e=e.filter(n=>n!==t),this.state=e)}else e=e.filter(o=>o!==t),this.state=e,this.updateSelectedDisplay();this.renderOptions(),this.maintainFocusInMultipleMode(),this.onStateChange(this.state);return}if(this.maxItems&&e.length>=this.maxItems){this.maxItemsMessage&&alert(this.maxItemsMessage);return}e.push(t),this.state=e;let s=this.selectedDisplay.querySelector(".prvious-combobox-input-value-badges-ctn");d(s)?this.updateSelectedDisplay():this.addSingleBadge(t,s),this.renderOptions(),this.maintainFocusInMultipleMode(),this.onStateChange(this.state)}async addSingleBadge(t,e){let s=this.labelRepository[t];if(d(s)&&(s=this.getSelectedOptionLabel(t),l(s)&&(this.labelRepository[t]=s)),d(s)&&this.getOptionLabelsUsing)try{let o=await this.getOptionLabelsUsing();for(let n of o)if(l(n)&&n.value===t&&n.label!==void 0){s=n.label,this.labelRepository[t]=s;break}}catch(o){console.error("Error fetching option label:",o)}d(s)&&(s=t);let i=this.createBadgeElement(t,s);e.appendChild(i)}maintainFocusInMultipleMode(){if(this.isSearchable&&this.searchInput){this.searchInput.focus();return}let t=this.getVisibleOptions();if(t.length!==0){if(this.selectedIndex=-1,Array.isArray(this.state)&&this.state.length>0){for(let e=0;e{e.setAttribute("disabled","disabled"),e.classList.add("fi-disabled")}),!this.isMultiple&&this.canSelectPlaceholder){let t=this.container.querySelector(".prvious-combobox-input-value-remove-btn");t&&(t.setAttribute("disabled","disabled"),t.classList.add("fi-disabled"))}this.isSearchable&&this.searchInput&&(this.searchInput.setAttribute("disabled","disabled"),this.searchInput.classList.add("fi-disabled"))}else{if(this.container.classList.remove("fi-disabled"),this.isMultiple&&this.container.querySelectorAll(".fi-badge-delete-btn").forEach(e=>{e.removeAttribute("disabled"),e.classList.remove("fi-disabled")}),!this.isMultiple&&this.canSelectPlaceholder){let t=this.container.querySelector(".prvious-combobox-input-value-remove-btn");t&&(t.removeAttribute("disabled"),t.classList.remove("fi-disabled"))}this.isSearchable&&this.searchInput&&(this.searchInput.removeAttribute("disabled"),this.searchInput.classList.remove("fi-disabled"))}}destroy(){this.searchInput&&this.searchInput.removeEventListener("input",this.handleSearch),this.optionsContainer&&this.optionsContainerKeydownListener&&this.optionsContainer.removeEventListener("keydown",this.optionsContainerKeydownListener),this.refreshOptionLabelListener&&window.removeEventListener("filament-forms::select.refreshSelectedOptionLabel",this.refreshOptionLabelListener),this.searchTimeout&&(clearTimeout(this.searchTimeout),this.searchTimeout=null),this.container&&this.container.remove()}};function B({canOptionLabelsWrap:c,canSelectPlaceholder:t,isHtmlAllowed:e,getOptionLabelUsing:s,getOptionLabelsUsing:i,getOptionsUsing:o,getSearchResultsUsing:n,initialOptionLabel:a,initialOptionLabels:r,initialState:h,isAutofocused:p,isDisabled:f,isMultiple:m,isSearchable:g,hasDynamicOptions:y,hasDynamicSearchResults:L,livewireId:O,loadingMessage:x,maxItems:S,maxItemsMessage:I,noSearchResultsMessage:C,options:A,optionsLimit:v,placeholder:w,searchDebounce:D,searchingMessage:E,searchPrompt:R,searchableOptionFields:M,state:k,statePath:b}){return{combobox:null,state:k,init(){this.combobox=new N({element:this.$refs.combobox,options:A,placeholder:w,state:this.state,canOptionLabelsWrap:c,canSelectPlaceholder:t,initialOptionLabel:a,initialOptionLabels:r,initialState:h,isHtmlAllowed:e,isAutofocused:p,isDisabled:f,isMultiple:m,isSearchable:g,getOptionLabelUsing:s,getOptionLabelsUsing:i,getOptionsUsing:o,getSearchResultsUsing:n,hasDynamicOptions:y,hasDynamicSearchResults:L,searchPrompt:R,searchDebounce:D,loadingMessage:x,searchingMessage:E,noSearchResultsMessage:C,maxItems:S,maxItemsMessage:I,optionsLimit:v,searchableOptionFields:M,livewireId:O,statePath:b,onStateChange:u=>{this.state=u}}),this.$watch("state",u=>{this.combobox&&this.combobox.state!==u&&(this.combobox.state=u,this.combobox.updateSelectedDisplay(),this.combobox.renderOptions())})},destroy(){this.combobox&&(this.combobox.destroy(),this.combobox=null)}}}export{B as default}; +function d(c){return c==null||c===""||typeof c=="string"&&c.trim()===""}function r(c){return!d(c)}var V=class{constructor({element:t,options:e,placeholder:s,state:i,canOptionLabelsWrap:o=!0,canSelectPlaceholder:n=!0,initialOptionLabel:a=null,initialOptionLabels:l=null,initialState:h=null,isHtmlAllowed:p=!1,isAutofocused:f=!1,isDisabled:g=!1,isMultiple:y=!1,isSearchable:L=!1,getOptionLabelUsing:O=null,getOptionLabelsUsing:S=null,getOptionsUsing:x=null,getSearchResultsUsing:I=null,hasDynamicOptions:C=!1,hasDynamicSearchResults:A=!0,searchPrompt:v="Search...",searchDebounce:w=1e3,loadingMessage:D="Loading...",searchingMessage:E="Searching...",noSearchResultsMessage:R="No results found",maxItems:M=null,maxItemsMessage:N="Maximum number of items selected",optionsLimit:k=null,searchableOptionFields:b=["label"],searchQuery:m=null,autoSearch:T=!1,livewireId:u=null,statePath:U=null,onStateChange:B=()=>{}}){this.element=t,this.options=e,this.originalOptions=JSON.parse(JSON.stringify(e)),this.placeholder=s,this.state=i,this.canOptionLabelsWrap=o,this.canSelectPlaceholder=n,this.initialOptionLabel=a,this.initialOptionLabels=l,this.initialState=h,this.isHtmlAllowed=p,this.isAutofocused=f,this.isDisabled=g,this.isMultiple=y,this.isSearchable=L,this.getOptionLabelUsing=O,this.getOptionLabelsUsing=S,this.getOptionsUsing=x,this.getSearchResultsUsing=I,this.hasDynamicOptions=C,this.hasDynamicSearchResults=A,this.searchPrompt=v,this.searchDebounce=w,this.loadingMessage=D,this.searchingMessage=E,this.noSearchResultsMessage=R,this.maxItems=M,this.maxItemsMessage=N,this.optionsLimit=k,this.searchableOptionFields=Array.isArray(b)?b:["label"],this.initialSearchQuery=m,this.autoSearch=T,this.livewireId=u,this.statePath=U,this.onStateChange=B,this.labelRepository={},this.selectedIndex=-1,this.searchQuery=m||"",this.searchTimeout=null,this.isSearching=!1,this.selectedDisplayVersion=0,this.render(),this.setUpEventListeners(),this.isAutofocused&&this.searchInput&&this.searchInput.focus(),this.autoSearch&&r(this.initialSearchQuery)&&this.performInitialSearch()}populateLabelRepositoryFromOptions(t){if(!(!t||!Array.isArray(t)))for(let e of t)e.options&&Array.isArray(e.options)?this.populateLabelRepositoryFromOptions(e.options):e.value!==void 0&&e.label!==void 0&&(this.labelRepository[e.value]=e.label)}render(){this.populateLabelRepositoryFromOptions(this.options),this.container=document.createElement("div"),this.container.className="prvious-combobox-input-ctn",this.canOptionLabelsWrap||this.container.classList.add("prvious-combobox-input-ctn-option-labels-not-wrapped"),this.selectedDisplay=document.createElement("div"),this.selectedDisplay.className="prvious-combobox-input-value-ctn",this.updateSelectedDisplay(),this.isSearchable&&(this.searchContainer=document.createElement("div"),this.searchContainer.className="prvious-combobox-input-search-ctn",this.searchInput=document.createElement("input"),this.searchInput.className="fi-input",this.searchInput.type="text",this.searchInput.placeholder=this.searchPrompt,this.searchInput.setAttribute("aria-label","Search"),this.searchQuery&&(this.searchInput.value=this.searchQuery),this.searchContainer.appendChild(this.searchInput)),this.optionsContainer=document.createElement("div"),this.optionsContainer.className="prvious-combobox-options-wrapper max-h-60 overflow-y-auto",this.optionsContainer.setAttribute("role","listbox"),this.optionsContainer.setAttribute("tabindex","-1"),this.isMultiple&&this.optionsContainer.setAttribute("aria-multiselectable","true"),this.optionsList=document.createElement("ul"),this.renderOptions(),this.optionsContainer.appendChild(this.optionsList),this.container.appendChild(this.selectedDisplay),this.isSearchable&&this.container.appendChild(this.searchContainer),this.container.appendChild(this.optionsContainer),this.element.appendChild(this.container),this.applyDisabledState(),this.hasDynamicOptions&&this.getOptionsUsing&&this.loadDynamicOptions()}async loadDynamicOptions(){this.showLoadingState(!1);try{let t=await this.getOptionsUsing(),e=Array.isArray(t)?t:t&&Array.isArray(t.options)?t.options:[];this.options=e,this.originalOptions=JSON.parse(JSON.stringify(e)),this.populateLabelRepositoryFromOptions(e),this.renderOptions()}catch(t){console.error("Error fetching options:",t),this.hideLoadingState()}}async performInitialSearch(){if(!this.isSearchable||!this.searchInput)return;let t=this.initialSearchQuery;if(r(t)){if(this.searchQuery=t,this.searchInput.value=t,!this.getSearchResultsUsing||typeof this.getSearchResultsUsing!="function"||!this.hasDynamicSearchResults){this.filterOptions(t.trim().toLowerCase());return}this.isSearching=!0;try{this.showLoadingState(!0);let e=await this.getSearchResultsUsing(t),s=Array.isArray(e)?e:e&&Array.isArray(e.options)?e.options:[];this.options=s,this.populateLabelRepositoryFromOptions(s),this.hideLoadingState(),this.renderOptions(),this.options.length===0&&this.showNoResultsMessage()}catch(e){console.error("Error fetching initial search results:",e),this.hideLoadingState(),this.options=JSON.parse(JSON.stringify(this.originalOptions)),this.renderOptions()}finally{this.isSearching=!1}}}renderOptions(){this.optionsList.innerHTML="";let t=0,e=this.options,s=0,i=!1;this.options.forEach(a=>{a.options&&Array.isArray(a.options)?(s+=a.options.length,i=!0):s++}),i?this.optionsList.className="prvious-combobox-input-options-ctn":s>0&&(this.optionsList.className="fi-dropdown-list");let o=i?null:this.optionsList,n=0;for(let a of e){if(this.optionsLimit&&n>=this.optionsLimit)break;if(a.options&&Array.isArray(a.options)){let l=a.options;if(this.isMultiple&&Array.isArray(this.state)&&this.state.length>0&&(l=a.options.filter(h=>!this.state.includes(h.value))),l.length>0){if(this.optionsLimit){let h=this.optionsLimit-n;h{let a=this.createOptionElement(n.value,n);o.appendChild(a)}),s.appendChild(i),s.appendChild(o),this.optionsList.appendChild(s)}createOptionElement(t,e){let s=t,i=e,o=!1;typeof e=="object"&&e!==null&&"label"in e&&"value"in e&&(s=e.value,i=e.label,o=e.isDisabled||!1);let n=document.createElement("li");n.className="fi-dropdown-list-item prvious-combobox-input-option",o&&n.classList.add("fi-disabled");let a=`prvious-combobox-input-option-${Math.random().toString(36).substring(2,11)}`;if(n.id=a,n.setAttribute("role","option"),n.setAttribute("data-value",s),n.setAttribute("tabindex","0"),o&&n.setAttribute("aria-disabled","true"),this.isHtmlAllowed&&typeof i=="string"){let p=document.createElement("div");p.innerHTML=i;let f=p.textContent||p.innerText||i;n.setAttribute("aria-label",f)}let l=this.isMultiple?Array.isArray(this.state)&&this.state.includes(s):this.state===s;n.setAttribute("aria-selected",l?"true":"false"),l&&n.classList.add("fi-selected");let h=document.createElement("span");return this.isHtmlAllowed?h.innerHTML=i:h.textContent=i,n.appendChild(h),o||n.addEventListener("click",p=>{p.preventDefault(),p.stopPropagation(),this.selectOption(s),this.isMultiple&&(this.isSearchable&&this.searchInput?setTimeout(()=>{this.searchInput.focus()},0):setTimeout(()=>{n.focus()},0))}),n}async updateSelectedDisplay(){this.selectedDisplayVersion=this.selectedDisplayVersion+1;let t=this.selectedDisplayVersion,e=document.createDocumentFragment();if(this.isMultiple){if(!Array.isArray(this.state)||this.state.length===0){let i=document.createElement("span");i.textContent=this.placeholder,i.classList.add("prvious-combobox-input-placeholder"),e.appendChild(i)}else{let i=await this.getLabelsForMultipleSelection();if(t!==this.selectedDisplayVersion)return;this.addBadgesForSelectedOptions(i,e)}t===this.selectedDisplayVersion&&this.selectedDisplay.replaceChildren(e);return}if(this.state===null||this.state===""){let i=document.createElement("span");i.textContent=this.placeholder,i.classList.add("prvious-combobox-input-placeholder"),e.appendChild(i),t===this.selectedDisplayVersion&&this.selectedDisplay.replaceChildren(e);return}let s=await this.getLabelForSingleSelection();t===this.selectedDisplayVersion&&(this.addSingleSelectionDisplay(s,e),t===this.selectedDisplayVersion&&this.selectedDisplay.replaceChildren(e))}async getLabelsForMultipleSelection(){let t=this.getSelectedOptionLabels(),e=[];if(Array.isArray(this.state)){for(let i of this.state)if(!r(this.labelRepository[i])){if(r(t[i])){this.labelRepository[i]=t[i];continue}e.push(i.toString())}}if(e.length>0&&r(this.initialOptionLabels)&&JSON.stringify(this.state)===JSON.stringify(this.initialState)){if(Array.isArray(this.initialOptionLabels))for(let i of this.initialOptionLabels)r(i)&&i.value!==void 0&&i.label!==void 0&&e.includes(i.value)&&(this.labelRepository[i.value]=i.label)}else if(e.length>0&&this.getOptionLabelsUsing)try{let i=await this.getOptionLabelsUsing();for(let o of i)r(o)&&o.value!==void 0&&o.label!==void 0&&(this.labelRepository[o.value]=o.label)}catch(i){console.error("Error fetching option labels:",i)}let s=[];if(Array.isArray(this.state))for(let i of this.state)r(this.labelRepository[i])?s.push(this.labelRepository[i]):r(t[i])?s.push(t[i]):s.push(i);return s}createBadgeElement(t,e){let s=document.createElement("span");s.className="fi-badge fi-size-md fi-color fi-color-primary fi-text-color-600 dark:fi-text-color-200",r(t)&&s.setAttribute("data-value",t);let i=document.createElement("span");i.className="fi-badge-label-ctn";let o=document.createElement("span");o.className="fi-badge-label",this.canOptionLabelsWrap&&o.classList.add("fi-wrapped"),this.isHtmlAllowed?o.innerHTML=e:o.textContent=e,i.appendChild(o),s.appendChild(i);let n=this.createRemoveButton(t,e);return s.appendChild(n),s}createRemoveButton(t,e){let s=document.createElement("button");return s.type="button",s.className="fi-badge-delete-btn",s.innerHTML='',s.setAttribute("aria-label","Remove "+(this.isHtmlAllowed?e.replace(/<[^>]*>/g,""):e)),s.addEventListener("click",i=>{i.stopPropagation(),r(t)&&this.selectOption(t)}),s.addEventListener("keydown",i=>{(i.key===" "||i.key==="Enter")&&(i.preventDefault(),i.stopPropagation(),r(t)&&this.selectOption(t))}),s}addBadgesForSelectedOptions(t,e=this.selectedDisplay){let s=document.createElement("div");s.className="prvious-combobox-input-value-badges-ctn",t.forEach((i,o)=>{let n=Array.isArray(this.state)?this.state[o]:null,a=this.createBadgeElement(n,i);s.appendChild(a)}),e.appendChild(s)}async getLabelForSingleSelection(){let t=this.labelRepository[this.state];if(d(t)&&(t=this.getSelectedOptionLabel(this.state)),d(t)&&r(this.initialOptionLabel)&&this.state===this.initialState)t=this.initialOptionLabel,r(this.state)&&(this.labelRepository[this.state]=t);else if(d(t)&&this.getOptionLabelUsing)try{t=await this.getOptionLabelUsing(),r(t)&&r(this.state)&&(this.labelRepository[this.state]=t)}catch(e){console.error("Error fetching option label:",e),t=this.state}else d(t)&&(t=this.state);return t}addSingleSelectionDisplay(t,e=this.selectedDisplay){let s=document.createElement("span");if(s.className="prvious-combobox-input-value-label",this.isHtmlAllowed?s.innerHTML=t:s.textContent=t,e.appendChild(s),!this.canSelectPlaceholder)return;let i=document.createElement("button");i.type="button",i.className="prvious-combobox-input-value-remove-btn",i.innerHTML='',i.setAttribute("aria-label","Clear selection"),i.addEventListener("click",o=>{o.stopPropagation(),this.selectOption("")}),i.addEventListener("keydown",o=>{(o.key===" "||o.key==="Enter")&&(o.preventDefault(),o.stopPropagation(),this.selectOption(""))}),e.appendChild(i)}getSelectedOptionLabel(t){if(r(this.labelRepository[t]))return this.labelRepository[t];let e="";for(let s of this.options)if(s.options&&Array.isArray(s.options)){for(let i of s.options)if(i.value===t){e=i.label,this.labelRepository[t]=e;break}}else if(s.value===t){e=s.label,this.labelRepository[t]=e;break}return e}setUpEventListeners(){this.isSearchable&&this.searchInput&&(this.searchInput.addEventListener("input",t=>{this.isDisabled||this.handleSearch(t)}),this.searchInput.addEventListener("keydown",t=>{if(!this.isDisabled){if(t.key==="Tab"){t.preventDefault();let e=this.getVisibleOptions();if(e.length===0)return;t.shiftKey?this.selectedIndex=e.length-1:this.selectedIndex=0,e.forEach(s=>{s.classList.remove("fi-selected")}),e[this.selectedIndex].classList.add("fi-selected"),e[this.selectedIndex].focus()}else if(t.key==="ArrowDown"){if(t.preventDefault(),t.stopPropagation(),this.getVisibleOptions().length===0)return;this.selectedIndex=-1,this.searchInput.blur(),this.focusNextOption()}else if(t.key==="ArrowUp"){t.preventDefault(),t.stopPropagation();let e=this.getVisibleOptions();if(e.length===0)return;this.selectedIndex=e.length-1,this.searchInput.blur(),e[this.selectedIndex].classList.add("fi-selected"),e[this.selectedIndex].focus(),e[this.selectedIndex].id&&this.optionsContainer.setAttribute("aria-activedescendant",e[this.selectedIndex].id),this.scrollOptionIntoView(e[this.selectedIndex])}else if(t.key==="Enter"){if(t.preventDefault(),t.stopPropagation(),this.isSearching)return;let e=this.getVisibleOptions();if(e.length===0)return;let s=e.find(o=>{let n=o.getAttribute("aria-disabled")==="true",a=o.classList.contains("fi-disabled"),l=o.offsetParent===null;return!(n||a||l)});if(!s)return;let i=s.getAttribute("data-value");if(i===null)return;this.selectOption(i)}}})),this.optionsContainerKeydownListener=t=>{this.isDisabled||this.isSearchable&&document.activeElement===this.searchInput&&!["Tab","Escape"].includes(t.key)||this.handleOptionsKeydown(t)},this.optionsContainer.addEventListener("keydown",this.optionsContainerKeydownListener),!this.isMultiple&&this.livewireId&&this.statePath&&this.getOptionLabelUsing&&(this.refreshOptionLabelListener=async t=>{if(t.detail.livewireId===this.livewireId&&t.detail.statePath===this.statePath&&r(this.state))try{delete this.labelRepository[this.state];let e=await this.getOptionLabelUsing();r(e)&&(this.labelRepository[this.state]=e);let s=this.selectedDisplay.querySelector(".prvious-combobox-input-value-label");r(s)&&(this.isHtmlAllowed?s.innerHTML=e:s.textContent=e),this.updateOptionLabelInList(this.state,e)}catch(e){console.error("Error refreshing option label:",e)}},window.addEventListener("filament-forms::select.refreshSelectedOptionLabel",this.refreshOptionLabelListener))}updateOptionLabelInList(t,e){this.labelRepository[t]=e;let s=this.getVisibleOptions();for(let i of s)if(i.getAttribute("data-value")===String(t)){if(i.innerHTML="",this.isHtmlAllowed){let o=document.createElement("span");o.innerHTML=e,i.appendChild(o)}else i.appendChild(document.createTextNode(e));break}for(let i of this.options)if(i.options&&Array.isArray(i.options)){for(let o of i.options)if(o.value===t){o.label=e;break}}else if(i.value===t){i.label=e;break}for(let i of this.originalOptions)if(i.options&&Array.isArray(i.options)){for(let o of i.options)if(o.value===t){o.label=e;break}}else if(i.value===t){i.label=e;break}}handleOptionsKeydown(t){switch(t.key){case"ArrowDown":t.preventDefault(),t.stopPropagation(),this.focusNextOption();break;case"ArrowUp":t.preventDefault(),t.stopPropagation(),this.focusPreviousOption();break;case" ":if(t.preventDefault(),this.selectedIndex>=0){let e=this.getVisibleOptions()[this.selectedIndex];e&&e.click()}break;case"Enter":if(t.preventDefault(),this.selectedIndex>=0){let e=this.getVisibleOptions()[this.selectedIndex];e&&e.click()}break;case"Tab":break;default:if(this.isSearchable&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&typeof t.key=="string"&&t.key.length===1){t.preventDefault();let e=t.key;this.searchInput&&(this.searchInput.focus(),this.searchInput.value=(this.searchInput.value||"")+e,this.searchInput.dispatchEvent(new Event("input",{bubbles:!0})))}break}}focusNextOption(){let t=this.getVisibleOptions();if(t.length!==0){if(this.selectedIndex>=0&&this.selectedIndex=0&&this.selectedIndexe.bottom?this.optionsContainer.scrollTop+=s.bottom-e.bottom:s.top li[role="option"]')):t=Array.from(this.optionsList.querySelectorAll(':scope > ul.fi-dropdown-list > li[role="option"]'));let e=Array.from(this.optionsList.querySelectorAll('li.prvious-combobox-input-option-group > ul > li[role="option"]'));return[...t,...e]}getSelectedOptionLabels(){if(!Array.isArray(this.state)||this.state.length===0)return{};let t={};for(let e of this.state){let s=!1;for(let i of this.options)if(i.options&&Array.isArray(i.options)){for(let o of i.options)if(o.value===e){t[e]=o.label,s=!0;break}if(s)break}else if(i.value===e){t[e]=i.label,s=!0;break}}return t}handleSearch(t){let e=t.target.value.trim().toLowerCase();if(this.searchQuery=e,this.searchTimeout&&clearTimeout(this.searchTimeout),e===""){this.options=JSON.parse(JSON.stringify(this.originalOptions)),this.renderOptions();return}if(!this.getSearchResultsUsing||typeof this.getSearchResultsUsing!="function"||!this.hasDynamicSearchResults){this.filterOptions(e);return}this.searchTimeout=setTimeout(async()=>{this.searchTimeout=null,this.isSearching=!0;try{this.showLoadingState(!0);let s=await this.getSearchResultsUsing(e),i=Array.isArray(s)?s:s&&Array.isArray(s.options)?s.options:[];this.options=i,this.populateLabelRepositoryFromOptions(i),this.hideLoadingState(),this.renderOptions(),this.options.length===0&&this.showNoResultsMessage()}catch(s){console.error("Error fetching search results:",s),this.hideLoadingState(),this.options=JSON.parse(JSON.stringify(this.originalOptions)),this.renderOptions()}finally{this.isSearching=!1}},this.searchDebounce)}showLoadingState(t=!1){this.optionsList.parentNode===this.optionsContainer&&this.optionsContainer.removeChild(this.optionsList),this.hideLoadingState();let e=document.createElement("div");e.className="prvious-combobox-input-message",e.textContent=t?this.searchingMessage:this.loadingMessage,this.optionsContainer.appendChild(e)}hideLoadingState(){let t=this.optionsContainer.querySelector(".prvious-combobox-input-message");t&&t.remove()}showNoResultsMessage(){this.optionsList.parentNode===this.optionsContainer&&this.optionsContainer.removeChild(this.optionsList),this.hideLoadingState();let t=document.createElement("div");t.className="prvious-combobox-input-message",t.textContent=this.noSearchResultsMessage,this.optionsContainer.appendChild(t)}filterOptions(t){let e=this.searchableOptionFields.includes("label"),s=this.searchableOptionFields.includes("value"),i=[];for(let o of this.originalOptions)if(o.options&&Array.isArray(o.options)){let n=o.options.filter(a=>e&&a.label.toLowerCase().includes(t)||s&&String(a.value).toLowerCase().includes(t));n.length>0&&i.push({label:o.label,options:n})}else(e&&o.label.toLowerCase().includes(t)||s&&String(o.value).toLowerCase().includes(t))&&i.push(o);this.options=i,this.renderOptions(),this.options.length===0&&this.showNoResultsMessage()}selectOption(t){if(this.isDisabled)return;if(!this.isMultiple){this.state=t,this.updateSelectedDisplay(),this.renderOptions(),this.onStateChange(this.state);return}let e=Array.isArray(this.state)?[...this.state]:[];if(e.includes(t)){let i=this.selectedDisplay.querySelector(`[data-value="${t}"]`);if(r(i)){let o=i.parentElement;r(o)&&o.children.length===1?(e=e.filter(n=>n!==t),this.state=e,this.updateSelectedDisplay()):(i.remove(),e=e.filter(n=>n!==t),this.state=e)}else e=e.filter(o=>o!==t),this.state=e,this.updateSelectedDisplay();this.renderOptions(),this.maintainFocusInMultipleMode(),this.onStateChange(this.state);return}if(this.maxItems&&e.length>=this.maxItems){this.maxItemsMessage&&alert(this.maxItemsMessage);return}e.push(t),this.state=e;let s=this.selectedDisplay.querySelector(".prvious-combobox-input-value-badges-ctn");d(s)?this.updateSelectedDisplay():this.addSingleBadge(t,s),this.renderOptions(),this.maintainFocusInMultipleMode(),this.onStateChange(this.state)}async addSingleBadge(t,e){let s=this.labelRepository[t];if(d(s)&&(s=this.getSelectedOptionLabel(t),r(s)&&(this.labelRepository[t]=s)),d(s)&&this.getOptionLabelsUsing)try{let o=await this.getOptionLabelsUsing();for(let n of o)if(r(n)&&n.value===t&&n.label!==void 0){s=n.label,this.labelRepository[t]=s;break}}catch(o){console.error("Error fetching option label:",o)}d(s)&&(s=t);let i=this.createBadgeElement(t,s);e.appendChild(i)}maintainFocusInMultipleMode(){if(this.isSearchable&&this.searchInput){this.searchInput.focus();return}let t=this.getVisibleOptions();if(t.length!==0){if(this.selectedIndex=-1,Array.isArray(this.state)&&this.state.length>0){for(let e=0;e{e.setAttribute("disabled","disabled"),e.classList.add("fi-disabled")}),!this.isMultiple&&this.canSelectPlaceholder){let t=this.container.querySelector(".prvious-combobox-input-value-remove-btn");t&&(t.setAttribute("disabled","disabled"),t.classList.add("fi-disabled"))}this.isSearchable&&this.searchInput&&(this.searchInput.setAttribute("disabled","disabled"),this.searchInput.classList.add("fi-disabled"))}else{if(this.container.classList.remove("fi-disabled"),this.isMultiple&&this.container.querySelectorAll(".fi-badge-delete-btn").forEach(e=>{e.removeAttribute("disabled"),e.classList.remove("fi-disabled")}),!this.isMultiple&&this.canSelectPlaceholder){let t=this.container.querySelector(".prvious-combobox-input-value-remove-btn");t&&(t.removeAttribute("disabled"),t.classList.remove("fi-disabled"))}this.isSearchable&&this.searchInput&&(this.searchInput.removeAttribute("disabled"),this.searchInput.classList.remove("fi-disabled"))}}destroy(){this.searchInput&&this.searchInput.removeEventListener("input",this.handleSearch),this.optionsContainer&&this.optionsContainerKeydownListener&&this.optionsContainer.removeEventListener("keydown",this.optionsContainerKeydownListener),this.refreshOptionLabelListener&&window.removeEventListener("filament-forms::select.refreshSelectedOptionLabel",this.refreshOptionLabelListener),this.searchTimeout&&(clearTimeout(this.searchTimeout),this.searchTimeout=null),this.container&&this.container.remove()}};function P({canOptionLabelsWrap:c,canSelectPlaceholder:t,isHtmlAllowed:e,getOptionLabelUsing:s,getOptionLabelsUsing:i,getOptionsUsing:o,getSearchResultsUsing:n,initialOptionLabel:a,initialOptionLabels:l,initialState:h,isAutofocused:p,isDisabled:f,isMultiple:g,isSearchable:y,hasDynamicOptions:L,hasDynamicSearchResults:O,livewireId:S,loadingMessage:x,maxItems:I,maxItemsMessage:C,noSearchResultsMessage:A,options:v,optionsLimit:w,placeholder:D,searchDebounce:E,searchingMessage:R,searchPrompt:M,searchableOptionFields:N,searchQuery:k,autoSearch:b,state:m,statePath:T}){return{combobox:null,state:m,init(){this.combobox=new V({element:this.$refs.combobox,options:v,placeholder:D,state:this.state,canOptionLabelsWrap:c,canSelectPlaceholder:t,initialOptionLabel:a,initialOptionLabels:l,initialState:h,isHtmlAllowed:e,isAutofocused:p,isDisabled:f,isMultiple:g,isSearchable:y,getOptionLabelUsing:s,getOptionLabelsUsing:i,getOptionsUsing:o,getSearchResultsUsing:n,hasDynamicOptions:L,hasDynamicSearchResults:O,searchPrompt:M,searchDebounce:E,loadingMessage:x,searchingMessage:R,noSearchResultsMessage:A,maxItems:I,maxItemsMessage:C,optionsLimit:w,searchableOptionFields:N,searchQuery:k,autoSearch:b,livewireId:S,statePath:T,onStateChange:u=>{this.state=u}}),this.$watch("state",u=>{this.combobox&&this.combobox.state!==u&&(this.combobox.state=u,this.combobox.updateSelectedDisplay(),this.combobox.renderOptions())})},destroy(){this.combobox&&(this.combobox.destroy(),this.combobox=null)}}}export{P as default}; diff --git a/resources/js/components/combobox.js b/resources/js/components/combobox.js index 9be197d..b7ac9cc 100644 --- a/resources/js/components/combobox.js +++ b/resources/js/components/combobox.js @@ -42,6 +42,8 @@ class Combobox { maxItemsMessage = 'Maximum number of items selected', optionsLimit = null, searchableOptionFields = ['label'], + searchQuery = null, + autoSearch = false, livewireId = null, statePath = null, onStateChange = () => {}, @@ -78,13 +80,15 @@ class Combobox { this.searchableOptionFields = Array.isArray(searchableOptionFields) ? searchableOptionFields : ['label'] + this.initialSearchQuery = searchQuery + this.autoSearch = autoSearch this.livewireId = livewireId this.statePath = statePath this.onStateChange = onStateChange this.labelRepository = {} this.selectedIndex = -1 - this.searchQuery = '' + this.searchQuery = searchQuery || '' this.searchTimeout = null this.isSearching = false this.selectedDisplayVersion = 0 @@ -95,6 +99,10 @@ class Combobox { if (this.isAutofocused && this.searchInput) { this.searchInput.focus() } + + if (this.autoSearch && filled(this.initialSearchQuery)) { + this.performInitialSearch() + } } populateLabelRepositoryFromOptions(options) { @@ -139,6 +147,10 @@ class Combobox { this.searchInput.type = 'text' this.searchInput.placeholder = this.searchPrompt this.searchInput.setAttribute('aria-label', 'Search') + + if (this.searchQuery) { + this.searchInput.value = this.searchQuery + } this.searchContainer.appendChild(this.searchInput) } @@ -197,6 +209,63 @@ class Combobox { } } + async performInitialSearch() { + if (!this.isSearchable || !this.searchInput) { + return + } + + const query = this.initialSearchQuery + + if (!filled(query)) { + return + } + + this.searchQuery = query + this.searchInput.value = query + + if ( + !this.getSearchResultsUsing || + typeof this.getSearchResultsUsing !== 'function' || + !this.hasDynamicSearchResults + ) { + this.filterOptions(query.trim().toLowerCase()) + return + } + + this.isSearching = true + + try { + this.showLoadingState(true) + + const results = await this.getSearchResultsUsing(query) + + const normalizedResults = Array.isArray(results) + ? results + : results && Array.isArray(results.options) + ? results.options + : [] + + this.options = normalizedResults + + this.populateLabelRepositoryFromOptions(normalizedResults) + + this.hideLoadingState() + this.renderOptions() + + if (this.options.length === 0) { + this.showNoResultsMessage() + } + } catch (error) { + console.error('Error fetching initial search results:', error) + + this.hideLoadingState() + this.options = JSON.parse(JSON.stringify(this.originalOptions)) + this.renderOptions() + } finally { + this.isSearching = false + } + } + renderOptions() { this.optionsList.innerHTML = '' @@ -1538,6 +1607,8 @@ export default function comboboxFormComponent({ searchingMessage, searchPrompt, searchableOptionFields, + searchQuery, + autoSearch, state, statePath, }) { @@ -1577,6 +1648,8 @@ export default function comboboxFormComponent({ maxItemsMessage, optionsLimit, searchableOptionFields, + searchQuery, + autoSearch, livewireId, statePath, onStateChange: (newState) => { diff --git a/resources/views/components/combobox.blade.php b/resources/views/components/combobox.blade.php index ada75f1..4851d7e 100644 --- a/resources/views/components/combobox.blade.php +++ b/resources/views/components/combobox.blade.php @@ -177,6 +177,8 @@ class="fi-hidden" searchingMessage: @js($getSearchingMessage()), searchPrompt: @js($getSearchPrompt()), searchableOptionFields: @js($getSearchableOptionFields()), + searchQuery: @js($getSearchQuery()), + autoSearch: @js($shouldAutoSearch()), state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }}, statePath: @js($statePath), })" diff --git a/src/Components/Combobox.php b/src/Components/Combobox.php index d5f9ec9..f56df0c 100644 --- a/src/Components/Combobox.php +++ b/src/Components/Combobox.php @@ -2,6 +2,7 @@ namespace Prvious\Filament\Combobox\Components; +use Closure; use Filament\Forms\Components\Select; class Combobox extends Select @@ -10,4 +11,32 @@ class Combobox extends Select * @var view-string */ protected string $view = 'prvious-combobox::components.combobox'; + + protected string | Closure | null $searchQueryValue = null; + + protected bool $shouldAutoSearch = false; + + public function searchQuery(string | Closure | null $query): static + { + $this->searchQueryValue = $query; + + return $this; + } + + public function getSearchQuery(): ?string + { + return $this->evaluate($this->searchQueryValue); + } + + public function autoSearch(bool | Closure $condition = true): static + { + $this->shouldAutoSearch = $condition; + + return $this; + } + + public function shouldAutoSearch(): bool + { + return $this->evaluate($this->shouldAutoSearch); + } }