UNPKG

@bw-ui/command-palette

Version:

Beautiful, accessible command palette for the web. Zero dependencies.

18 lines (17 loc) 18.3 kB
var BWCommandPalette=(()=>{var _=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var P=(t,e)=>{for(var s in e)_(t,s,{get:e[s],enumerable:!0})},z=(t,e,s,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of B(e))!$.call(t,n)&&n!==s&&_(t,n,{get:()=>e[n],enumerable:!(r=K(e,n))||r.enumerable});return t};var F=t=>z(_({},"__esModule",{value:!0}),t);var V={};P(V,{BWCommandPalette:()=>L,createRecentHistory:()=>E,formatHotkey:()=>C,fuzzyMatch:()=>p,highlightMatches:()=>k,matchesHotkey:()=>x,parseHotkey:()=>f,searchCommands:()=>w});function p(t,e){if(!t)return{score:1,matches:[]};let s=t.toLowerCase(),r=e.toLowerCase(),n=[],i=0,a=0,c=-1,l=0;for(let o=0;o<e.length&&i<t.length;o++)if(r[o]===s[i]){n.push(o);let d=1;c===o-1?(l+=.5,d+=l):l=0,(o===0||/[\s\-_.]/.test(e[o-1]))&&(d+=2),e[o]===t[i]&&(d+=.2),a+=d,c=o,i++}return i!==t.length?null:(a=a/Math.sqrt(e.length),r===s&&(a+=10),r.startsWith(s)&&(a+=5),{score:a,matches:n})}function w(t,e,s=50){if(!t.trim())return e.filter(n=>!n.hidden).slice(0,s).map(n=>({command:n,score:0,matches:[]}));let r=[];for(let n of e){if(n.hidden)continue;let i=p(t,n.label),a="label";if(n.keywords)for(let c of n.keywords){let l=p(t,c);l&&(!i||l.score>i.score)&&(i=l,a="keyword")}if(n.description){let c=p(t,n.description);c&&(c.score*=.5,(!i||c.score>i.score)&&(i=c,a="description"))}i&&r.push({command:n,score:i.score,matches:a==="label"?i.matches:[]})}return r.sort((n,i)=>i.score-n.score),r.slice(0,s)}function k(t,e){if(!e.length)return y(t);let s="",r=0;for(let n of e)s+=y(t.slice(r,n)),s+=`<mark class="bw-cmd-highlight">${y(t[n])}</mark>`,r=n+1;return s+=y(t.slice(r)),s}function y(t){let e=document.createElement("div");return e.textContent=t,e.innerHTML}var m=typeof navigator<"u"&&/Mac|iPod|iPhone|iPad/.test(navigator.platform);function f(t){let e=t.toLowerCase().split("+"),s=e.pop()||"";return{key:j(s),ctrl:e.includes("ctrl"),meta:e.includes("meta")||e.includes("cmd"),alt:e.includes("alt")||e.includes("option"),shift:e.includes("shift"),mod:e.includes("mod")}}function j(t){return{esc:"escape",return:"enter",space:" ",up:"arrowup",down:"arrowdown",left:"arrowleft",right:"arrowright"}[t]||t}function x(t,e){if(t.key.toLowerCase()!==e.key)return!1;if(e.mod){if(!(m?t.metaKey:t.ctrlKey))return!1}else if(e.ctrl&&!t.ctrlKey||e.meta&&!t.metaKey)return!1;return!(e.alt&&!t.altKey||e.shift&&!t.shiftKey)}function C(t){let e=f(t),s=[];e.mod&&s.push(m?"\u2318":"Ctrl"),e.ctrl&&!e.mod&&s.push(m?"\u2303":"Ctrl"),e.alt&&s.push(m?"\u2325":"Alt"),e.shift&&s.push(m?"\u21E7":"Shift"),e.meta&&!e.mod&&s.push(m?"\u2318":"Win");let r=q(e.key);return s.push(r),s.join(m?"":"+")}function q(t){return{arrowup:"\u2191",arrowdown:"\u2193",arrowleft:"\u2190",arrowright:"\u2192",enter:"\u21B5",escape:"Esc",backspace:"\u232B",delete:"\u2326",tab:"\u21E5"," ":"Space"}[t]||t.toUpperCase()}function I(t){return{handleKeyDown:s=>{let{getItemCount:r,getActiveIndex:n,setActiveIndex:i,onSelect:a,onClose:c,onBack:l}=t,o=r(),d=n();switch(s.key){case"ArrowDown":s.preventDefault(),i(o>0?(d+1)%o:0);break;case"ArrowUp":s.preventDefault(),i(o>0?(d-1+o)%o:0);break;case"Enter":s.preventDefault(),o>0&&a();break;case"Escape":s.preventDefault(),c();break;case"Backspace":l&&s.target.value===""&&(s.preventDefault(),l());break;case"Tab":s.preventDefault();break}},destroy:()=>{}}}var G="bw-command-palette-recent";function E(t={}){let{maxRecent:e=5,storageKey:s=G,persist:r=!0}=t,n=[];if(r&&typeof localStorage<"u")try{let a=localStorage.getItem(s);a&&(n=JSON.parse(a))}catch{}function i(){if(r&&typeof localStorage<"u")try{localStorage.setItem(s,JSON.stringify(n))}catch{}}return{add(a){n=n.filter(c=>c!==a),n.unshift(a),n=n.slice(0,e),i()},get(){return[...n]},clear(){n=[],i()},getScore(a){let c=n.indexOf(a);return c===-1?0:(e-c)/e}}}function T(){let t=document.createElement("div");t.setAttribute("role","status"),t.setAttribute("aria-live","polite"),t.setAttribute("aria-atomic","true"),t.className="bw-cmd-sr-only",document.body.appendChild(t);let e=null;return{announce(s,r="polite"){t.setAttribute("aria-live",r),t.textContent="",e&&clearTimeout(e),e=setTimeout(()=>{t.textContent=s},50)},destroy(){e&&clearTimeout(e),t.remove()}}}function M(t){let e=null,s=!1,r=()=>{let i=["input:not([disabled])","button:not([disabled])",'[tabindex]:not([tabindex="-1"])',"a[href]","select:not([disabled])","textarea:not([disabled])"].join(",");return Array.from(t.querySelectorAll(i))},n=i=>{if(!s||i.key!=="Tab")return;let a=r();if(a.length===0)return;let c=a[0],l=a[a.length-1];i.shiftKey&&document.activeElement===c?(i.preventDefault(),l.focus()):!i.shiftKey&&document.activeElement===l&&(i.preventDefault(),c.focus())};return{activate(){if(s)return;s=!0,e=document.activeElement;let i=r();i.length>0&&i[0].focus(),document.addEventListener("keydown",n)},deactivate(){s&&(s=!1,document.removeEventListener("keydown",n),e&&e.focus&&e.focus())},destroy(){this.deactivate()}}}var U=0;function S(t="bw-cmd"){return`${t}-${++U}`}function H(t,e){return e?t===0?`No commands found for "${e}"`:`${t} result${t!==1?"s":""} for "${e}"`:`${t} command${t!==1?"s":""} available`}function N(t){let e=S("bw-cmd-listbox"),s=S("bw-cmd-input"),r=document.createElement("div");r.className="bw-cmd-root",r.setAttribute("data-theme",t.theme||"system"),t.zIndex&&(r.style.zIndex=String(t.zIndex));let n=document.createElement("div");n.className="bw-cmd-backdrop";let i=document.createElement("div");i.className="bw-cmd-dialog",i.setAttribute("role","dialog"),i.setAttribute("aria-modal","true"),i.setAttribute("aria-label","Command palette"),t.width&&i.style.setProperty("--bw-cmd-width",t.width),t.maxHeight&&i.style.setProperty("--bw-cmd-max-height",t.maxHeight);let a=document.createElement("div");a.className="bw-cmd-header";let c=document.createElement("div");c.className="bw-cmd-search-icon",c.innerHTML='<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>';let l=document.createElement("div");l.className="bw-cmd-breadcrumb",l.setAttribute("aria-hidden","true");let o=document.createElement("input");o.type="text",o.id=s,o.className="bw-cmd-input",o.placeholder=t.placeholder||"Search commands...",o.setAttribute("role","combobox"),o.setAttribute("aria-expanded","true"),o.setAttribute("aria-controls",e),o.setAttribute("aria-autocomplete","list"),o.setAttribute("aria-activedescendant",""),o.setAttribute("autocomplete","off"),o.setAttribute("autocorrect","off"),o.setAttribute("autocapitalize","off"),o.setAttribute("spellcheck","false");let d=document.createElement("button");d.type="button",d.className="bw-cmd-clear",d.setAttribute("aria-label","Clear search"),d.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',a.appendChild(c),a.appendChild(l),a.appendChild(o),a.appendChild(d);let h=document.createElement("div");h.className="bw-cmd-content";let u=document.createElement("div");u.id=e,u.className="bw-cmd-list",u.setAttribute("role","listbox"),u.setAttribute("aria-label","Commands");let b=document.createElement("div");b.className="bw-cmd-empty",b.innerHTML=` <div class="bw-cmd-empty-icon"> <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/><path d="M8 8h6"/></svg> </div> <div class="bw-cmd-empty-text">${t.emptyMessage||"No commands found"}</div> <div class="bw-cmd-empty-hint">Try a different search term</div> `;let g=document.createElement("div");g.className="bw-cmd-loading",g.innerHTML=` <div class="bw-cmd-spinner"></div> <div class="bw-cmd-loading-text">${t.loadingMessage||"Searching..."}</div> `,h.appendChild(u),h.appendChild(b),h.appendChild(g);let v=document.createElement("div");return v.className="bw-cmd-footer",v.innerHTML=` <div class="bw-cmd-hint"> <kbd>\u2191\u2193</kbd> navigate <kbd>\u21B5</kbd> select <kbd>esc</kbd> close </div> `,i.appendChild(a),i.appendChild(h),i.appendChild(v),r.appendChild(n),r.appendChild(i),{root:r,backdrop:n,dialog:i,header:a,input:o,breadcrumb:l,content:h,list:u,empty:b,loading:g,footer:v,listboxId:e}}function D(t,e,s,r){let n=W(e);t.innerHTML="";let i=0;for(let a of n){if(a.name){let c=document.createElement("div");c.className="bw-cmd-group-header",c.textContent=a.name,c.setAttribute("role","presentation"),t.appendChild(c)}for(let c of a.items){let l=J(c,i,s===i),o=i;l.addEventListener("click",d=>{d.preventDefault(),r.onSelect(c.command,o)}),l.addEventListener("mouseenter",()=>{r.onHover(o)}),t.appendChild(l),i++}}}function W(t){let e=new Map;for(let r of t){let n=r.command.group||null;e.has(n)||e.set(n,[]),e.get(n).push(r)}let s=[];e.has(null)&&(s.push({name:null,items:e.get(null)}),e.delete(null));for(let[r,n]of e)s.push({name:r,items:n});return s}function J(t,e,s){let{command:r,matches:n}=t,i=document.createElement("div");if(i.className=`bw-cmd-item${s?" bw-cmd-item--active":""}${r.disabled?" bw-cmd-item--disabled":""}`,i.id=`bw-cmd-item-${e}`,i.setAttribute("role","option"),i.setAttribute("aria-selected",String(s)),i.setAttribute("aria-disabled",String(!!r.disabled)),i.setAttribute("data-index",String(e)),r.icon){let o=document.createElement("div");o.className="bw-cmd-item-icon",typeof r.icon=="string"?o.innerHTML=r.icon:o.appendChild(r.icon.cloneNode(!0)),i.appendChild(o)}let a=document.createElement("div");a.className="bw-cmd-item-content";let c=document.createElement("div");if(c.className="bw-cmd-item-label",c.innerHTML=k(r.label,n),a.appendChild(c),r.description){let o=document.createElement("div");o.className="bw-cmd-item-description",o.textContent=r.description,a.appendChild(o)}i.appendChild(a);let l=document.createElement("div");if(l.className="bw-cmd-item-right",r.children&&r.children.length>0)l.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>',l.className+=" bw-cmd-item-arrow";else if(r.shortcut){let o=document.createElement("kbd");o.className="bw-cmd-kbd",o.textContent=C(r.shortcut),l.appendChild(o)}return i.appendChild(l),i}function A(t,e,s){if(t.innerHTML="",e.length===0){t.style.display="none";return}t.style.display="flex",e.forEach((r,n)=>{if(n>0){let a=document.createElement("span");a.className="bw-cmd-breadcrumb-sep",a.textContent="/",t.appendChild(a)}let i=document.createElement("button");i.type="button",i.className="bw-cmd-breadcrumb-item",i.textContent=r.label,i.addEventListener("click",()=>s(n)),t.appendChild(i)})}function O(t,e){let{list:s,empty:r,loading:n}=t;s.style.display=e.isLoading||e.isEmpty?"none":"block",r.style.display=e.isLoading?"none":e.isEmpty?"flex":"none",n.style.display=e.isLoading?"flex":"none",e.activeIndex>=0&&!e.isEmpty&&!e.isLoading?t.input.setAttribute("aria-activedescendant",`bw-cmd-item-${e.activeIndex}`):t.input.removeAttribute("aria-activedescendant")}function R(t,e){let s=t.querySelector(`[data-index="${e}"]`);s&&s.scrollIntoView({block:"nearest",behavior:"smooth"})}var Q={trigger:"mod+k",placeholder:"Search commands...",emptyMessage:"No commands found",loadingMessage:"Searching...",closeOnSelect:!0,closeOnClickOutside:!0,closeOnEscape:!0,maxResults:50,debounceMs:150,rememberRecent:!0,maxRecent:5,theme:"system",animationDuration:150},L=class{constructor(e={}){this.options={...Q,...e},this.elements=null,this.state={isOpen:!1,query:"",activeIndex:0,isLoading:!1,results:[],breadcrumb:[]},this.commands=[],this.commandProvider=null,e.commands&&(typeof e.commands=="function"?this.commandProvider=e.commands:this.commands=e.commands),this.mountTarget=null,this.history=E(),this.liveRegion=T(),this.focusTrap=null,this.navigationHandler=null,this.debounceTimer=null,this.eventListeners=new Map,this._boundHandleGlobalKeyDown=this._handleGlobalKeyDown.bind(this),this.options.trigger&&document.addEventListener("keydown",this._boundHandleGlobalKeyDown)}mount(e){return typeof e=="string"?this.mountTarget=document.querySelector(e):this.mountTarget=e,this.mountTarget?(this._createDOM(),this):(console.warn("[BWCommandPalette] Mount target not found"),this)}open(){return this.state.isOpen?this:(this.elements||this._createDOM(),this.state.isOpen=!0,this.state.query="",this.state.activeIndex=0,this.state.breadcrumb=[],this.elements&&!this.elements.root.parentElement&&document.body.appendChild(this.elements.root),requestAnimationFrame(()=>{this.elements&&(this.elements.root.classList.add("bw-cmd-root--open"),this.elements.input.value="",this.elements.input.focus(),this.focusTrap?.activate())}),this._performSearch(""),this._emit("open",void 0),this.options.onOpen?.(),this)}close(){return this.state.isOpen?(this.state.isOpen=!1,this.focusTrap?.deactivate(),this.elements&&(this.elements.root.classList.remove("bw-cmd-root--open"),setTimeout(()=>{this.elements?.root.parentElement&&!this.state.isOpen&&this.elements.root.remove()},this.options.animationDuration)),this._emit("close",void 0),this.options.onClose?.(),this):this}toggle(){return this.state.isOpen?this.close():this.open()}setCommands(e){return this.commands=e,this.commandProvider=null,this.state.isOpen&&this._performSearch(this.state.query),this}addCommand(e){return this.commands.push(e),this.state.isOpen&&this._performSearch(this.state.query),this}removeCommand(e){return this.commands=this.commands.filter(s=>s.id!==e),this.state.isOpen&&this._performSearch(this.state.query),this}on(e,s){return this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(s),this}off(e,s){return this.eventListeners.get(e)?.delete(s),this}destroy(){document.removeEventListener("keydown",this._boundHandleGlobalKeyDown),this.focusTrap?.destroy(),this.liveRegion.destroy(),this.elements?.root.remove(),this.elements=null,this.eventListeners.clear(),this.debounceTimer&&clearTimeout(this.debounceTimer)}_createDOM(){this.elements=N(this.options),this.focusTrap=M(this.elements.dialog),this.navigationHandler=I({getItemCount:()=>this.state.results.length,getActiveIndex:()=>this.state.activeIndex,setActiveIndex:s=>this._setActiveIndex(s),onSelect:()=>this._selectActive(),onClose:()=>this.close(),onBack:()=>this._navigateBack()}),this.elements.input.addEventListener("input",this._handleInput.bind(this)),this.elements.input.addEventListener("keydown",this._handleInputKeyDown.bind(this)),this.elements.header.querySelector(".bw-cmd-clear")?.addEventListener("click",()=>{this.elements&&(this.elements.input.value="",this.elements.input.focus(),this._handleInput())}),this.options.closeOnClickOutside&&this.elements.backdrop.addEventListener("click",()=>this.close())}_handleGlobalKeyDown(e){if(!this.options.trigger)return;let s=f(this.options.trigger);x(e,s)&&(e.preventDefault(),this.toggle())}_handleInputKeyDown(e){this.navigationHandler?.handleKeyDown(e)}_handleInput(){let e=this.elements?.input.value||"",s=this.elements?.header.querySelector(".bw-cmd-clear");s&&(s.style.opacity=e?"1":"0",s.style.pointerEvents=e?"auto":"none"),this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>{this._performSearch(e)},this.options.debounceMs),this._emit("query",e),this.options.onQueryChange?.(e)}async _performSearch(e){this.state.query=e;let s;if(this.commandProvider&&typeof this.commandProvider=="function"){this.state.isLoading=!0,this._updateUI();try{s=await this.commandProvider(e)}catch(n){console.error("[BWCommandPalette] Command provider error:",n),s=[]}this.state.isLoading=!1}else this.state.breadcrumb.length>0?s=this.state.breadcrumb[this.state.breadcrumb.length-1].children||[]:s=this.commands;let r=w(e,s,this.options.maxResults);!e&&this.options.rememberRecent&&(r=this._boostRecent(r)),this.state.results=r,this.state.activeIndex=0,this._updateUI(),this.liveRegion.announce(H(r.length,e))}_boostRecent(e){return e.sort((s,r)=>{let n=this.history.getScore(s.command.id),i=this.history.getScore(r.command.id);return n&&!i?-1:i&&!n?1:n&&i?i-n:r.score-s.score})}_setActiveIndex(e){let s=this.state.activeIndex;if(this.state.activeIndex=e,this.elements){let n=this.elements.list.querySelector(`[data-index="${s}"]`),i=this.elements.list.querySelector(`[data-index="${e}"]`);n&&(n.classList.remove("bw-cmd-item--active"),n.setAttribute("aria-selected","false")),i&&(i.classList.add("bw-cmd-item--active"),i.setAttribute("aria-selected","true")),R(this.elements.list,e),this.elements.input.setAttribute("aria-activedescendant",`bw-cmd-item-${e}`)}let r=this.state.results[e]?.command||null;this._emit("navigate",{index:e,command:r})}_selectActive(){let e=this.state.results[this.state.activeIndex];!e||e.command.disabled||this._selectCommand(e.command)}_selectCommand(e){if(e.children&&e.children.length>0){this.state.breadcrumb.push(e),this._performSearch(""),this.elements&&(this.elements.input.value="",this.elements.input.focus(),A(this.elements.breadcrumb,this.state.breadcrumb,s=>this._navigateToBreadcrumb(s)));return}this.options.rememberRecent&&this.history.add(e.id),e.action?.(e),this._emit("select",e),this.options.onSelect?.(e),this.options.closeOnSelect&&this.close()}_navigateBack(){this.state.breadcrumb.length!==0&&(this.state.breadcrumb.pop(),this._performSearch(""),this.elements&&A(this.elements.breadcrumb,this.state.breadcrumb,e=>this._navigateToBreadcrumb(e)))}_navigateToBreadcrumb(e){this.state.breadcrumb=this.state.breadcrumb.slice(0,e+1),this._performSearch(""),this.elements&&(this.elements.input.value="",this.elements.input.focus(),A(this.elements.breadcrumb,this.state.breadcrumb,s=>this._navigateToBreadcrumb(s)))}_updateUI(){if(!this.elements)return;let{results:e,activeIndex:s,isLoading:r}=this.state;O(this.elements,{isEmpty:e.length===0,isLoading:r,activeIndex:s}),!r&&e.length>0&&D(this.elements.list,e,s,{onSelect:n=>this._selectCommand(n),onHover:n=>this._setActiveIndex(n)})}_emit(e,s){let r=this.eventListeners.get(e);r&&r.forEach(n=>n(s))}};return F(V);})(); BWCommandPalette=BWCommandPalette.BWCommandPalette;