kmenu
Version:
The perfect ⌘K menu
2 lines • 12.6 kB
JavaScript
var u=class{currentState;transitions=new Map;listeners=new Map;constructor(t="idle"){this.currentState=t;}defineTransition(t,e){this.transitions.set(t,e);}transition(t){let e=this.transitions.get(t);if(!e||!(Array.isArray(e.from)?e.from:[e.from]).includes(this.currentState)||e.guard&&!e.guard())return false;let n=this.currentState;return this.currentState=e.to,this.notifyListeners(n),this.notifyListeners(this.currentState),true}getState(){return this.currentState}onStateEnter(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),()=>{this.listeners.get(t)?.delete(e);}}notifyListeners(t){this.listeners.get(t)?.forEach(e=>e());}};function f(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g,"").replace(/\s+/g,"-").replace(/^-+|-+$/g,"").substring(0,50)}var m=class{stateMachine;state;listeners=new Map;filter;keydownHandler;globalKeyHandler;inputElement;listElement;optionElements=new Map;destroyed=false;lastScrollTime=0;constructor(t={}){this.stateMachine=new u("idle"),this.state={open:false,input:"",activeId:void 0,activeIndex:-1,filtered:[],options:[],groups:new Map,menuStack:[],currentLevel:0,breadcrumbs:[],allOptions:[],currentOptions:[]},this.filter=t.filter||this.defaultFilter,this.setupStateMachine(),this.setupGlobalKeyboardShortcut(),t.onOpen&&this.on("open",t.onOpen),t.onClose&&this.on("close",t.onClose),t.onChange&&this.on("change",e=>e.type==="change"&&t.onChange(e.input)),t.onSelect&&this.on("select",e=>e.type==="select"&&t.onSelect(e.option));}setupStateMachine(){this.stateMachine.defineTransition("open",{from:"idle",to:"open"}),this.stateMachine.defineTransition("startNavigating",{from:["open","filtering"],to:"navigating"}),this.stateMachine.defineTransition("startFiltering",{from:["open","navigating"],to:"filtering"}),this.stateMachine.defineTransition("select",{from:["navigating","filtering"],to:"selected"}),this.stateMachine.defineTransition("close",{from:["open","navigating","filtering","selected"],to:"idle"}),this.stateMachine.onStateEnter("open",()=>{this.state.open=true,this.emit({type:"open"}),this.updateFiltered(),this.selectFirstAvailableOption(),requestAnimationFrame(()=>this.inputElement?.focus());}),this.stateMachine.onStateEnter("idle",()=>{this.state.open=false,this.state.input="",this.state.activeId=void 0,this.state.activeIndex=-1,this.emit({type:"close"});}),this.stateMachine.onStateEnter("selected",()=>{this.stateMachine.transition("close");});}setupGlobalKeyboardShortcut(){this.globalKeyHandler=t=>{(t.metaKey||t.ctrlKey)&&t.key==="k"&&(t.preventDefault(),this.toggle());},typeof window<"u"&&window.addEventListener("keydown",this.globalKeyHandler);}defaultFilter=(t,e)=>{if(!e)return t;let i=e.toLowerCase();return t.filter(n=>{if(n.disabled)return false;let s=n.label.toLowerCase().includes(i),r=n.keywords?.some(a=>a.toLowerCase().includes(i));return s||r})};open(){this.destroyed||this.stateMachine.transition("open");}close(){this.destroyed||this.stateMachine.transition("close");}toggle(){this.destroyed||(this.state.open?this.close():this.open());}setInput(t){this.destroyed||(this.state.input=t,this.stateMachine.transition("startFiltering"),this.updateFiltered(),this.emit({type:"change",input:t}));}registerOptions(t){if(this.destroyed)return;let e=this.processOptionsWithIds(t);this.state.options=[...e],this.state.groups.clear(),this.state.allOptions=this.flattenOptions(e),this.state.currentOptions=[...e],this.state.menuStack=[],this.state.currentLevel=0,this.state.breadcrumbs=[],this.rebuildGroups(),this.updateFiltered();}processOptionsWithIds(t){let e=new Set,i=n=>{let s=n.id;if(!s){let a=f(n.label);s=this.ensureUniqueId(a,e);}e.add(s);let r=n.children?n.children.map(a=>i(a)):void 0;return {...n,id:s,children:r}};return t.map(n=>i(n))}ensureUniqueId(t,e){if(!e.has(t))return t;let i=1,n=`${t}-${i}`;for(;e.has(n);)i++,n=`${t}-${i}`;return n}rebuildGroups(){this.state.groups.clear(),this.state.currentOptions.forEach(t=>{t.group&&(this.state.groups.has(t.group)||this.state.groups.set(t.group,[]),this.state.groups.get(t.group).push(t));});}reorderByGroups(t){if(this.state.groups.size===0)return t;let e=[],i=t.filter(s=>!s.group);e.push(...i);let n=[];for(let s of this.state.currentOptions)s.group&&!n.includes(s.group)&&n.push(s.group);for(let s of n){let r=t.filter(a=>a.group===s);e.push(...r);}return e}flattenOptions(t,e){let i=[];return t.forEach(n=>{if(e&&(n.parent=e),i.push(n),n.children&&n.children.length>0){let s=this.flattenOptions(n.children,n.id);i.push(...s);}}),i}setActiveByIndex(t,e){if(this.destroyed)return;let i=this.state.filtered[t];!i||i.disabled||(this.state.activeIndex=t,this.state.activeId=i.id,this.stateMachine.transition("startNavigating"),this.emit({type:"navigate",activeId:i.id,activeIndex:t}),this.updateAriaActiveDescendant(),this.scrollActiveIntoView(e));}scrollActiveIntoView(t){if(!this.state.activeId||!this.listElement)return;let e=this.optionElements.get(this.state.activeId);if(!e)return;let i=this.listElement.getBoundingClientRect(),n=e.getBoundingClientRect(),s=Date.now(),a=s-this.lastScrollTime<100?"auto":"smooth";this.lastScrollTime=s;let h=8;if(t==="up"){let l=i.top+i.height*.25;if(n.top<l){let c=n.height,p=this.listElement.scrollTop-c*2-h;this.listElement.scrollTo({top:Math.max(0,p),behavior:a});}}else if(t==="down"){if(n.bottom>i.bottom){let l=e.offsetTop,c=e.offsetHeight,d=this.listElement.clientHeight,p=l-d+c+h;this.listElement.scrollTo({top:Math.max(0,p),behavior:a});}}else if(n.top<i.top){let c=e.offsetTop-h;this.listElement.scrollTo({top:Math.max(0,c),behavior:a});}else if(n.bottom>i.bottom){let l=e.offsetTop,c=e.offsetHeight,d=this.listElement.clientHeight,p=l-d+c+h;this.listElement.scrollTo({top:Math.max(0,p),behavior:a});}}setActiveById(t){if(this.destroyed)return;let e=this.state.filtered.findIndex(i=>i.id===t);e>=0&&this.setActiveByIndex(e);}navigateUp(){if(this.destroyed)return;let t=this.state.activeIndex-1;for(;t>=0&&this.state.filtered[t]?.disabled;)t--;t>=0&&this.setActiveByIndex(t,"up");}navigateDown(){if(this.destroyed)return;let t=this.state.activeIndex+1;for(;t<this.state.filtered.length&&this.state.filtered[t]?.disabled;)t++;t<this.state.filtered.length&&this.setActiveByIndex(t,"down");}on(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),()=>{this.listeners.get(t)?.delete(e);}}getState(){return {...this.state}}emit(t){this.listeners.get(t.type)?.forEach(i=>i(t));}updateFiltered(){let t;this.state.input.trim()?t=this.state.currentLevel===0?this.state.allOptions:this.state.currentOptions:t=this.state.currentOptions;let e=this.filter(t,this.state.input);this.state.filtered=this.reorderByGroups(e),this.state.activeId&&!this.state.filtered.some(i=>i.id===this.state.activeId)?(this.state.activeId=void 0,this.state.activeIndex=-1,this.selectFirstAvailableOption()):this.state.activeId?this.state.activeIndex=this.state.filtered.findIndex(i=>i.id===this.state.activeId):this.state.open&&this.selectFirstAvailableOption();}selectFirstAvailableOption(){let t=this.state.filtered.findIndex(e=>!e.disabled);t>=0&&this.setActiveByIndex(t);}enterSubmenu(t){this.destroyed||!t.children||t.children.length===0||(this.state.menuStack.push(t.id),this.state.currentLevel++,this.state.breadcrumbs.push({id:t.id,label:t.group||t.label}),this.state.currentOptions=[...t.children],this.rebuildGroups(),this.state.input="",this.state.activeId=void 0,this.state.activeIndex=-1,this.updateFiltered(),this.selectFirstAvailableOption(),this.emit({type:"submenu",option:t,level:this.state.currentLevel}),this.emit({type:"change",input:this.state.input}));}goBack(){if(this.destroyed||this.state.menuStack.length===0)return false;if(this.state.menuStack.pop(),this.state.currentLevel--,this.state.breadcrumbs.pop(),this.state.currentLevel===0)this.state.currentOptions=[...this.state.options];else {let t=this.state.menuStack[this.state.menuStack.length-1];if(t){let e=this.findOptionById(t);e&&e.children?this.state.currentOptions=[...e.children]:(this.state.currentOptions=[...this.state.options],this.state.menuStack=[],this.state.currentLevel=0,this.state.breadcrumbs=[]);}else this.state.currentOptions=[...this.state.options],this.state.menuStack=[],this.state.currentLevel=0,this.state.breadcrumbs=[];}return this.rebuildGroups(),this.state.input="",this.state.activeId=void 0,this.state.activeIndex=-1,this.updateFiltered(),this.emit({type:"change",input:this.state.input}),this.emit({type:"back",level:this.state.currentLevel}),true}findOptionById(t){return this.state.allOptions.find(e=>e.id===t)}updateAriaActiveDescendant(){this.inputElement&&this.state.activeId?this.inputElement.setAttribute("aria-activedescendant",`kmenu-option-${this.state.activeId}`):this.inputElement&&this.inputElement.removeAttribute("aria-activedescendant");}getComboboxProps(){return {role:"combobox","aria-expanded":this.state.open,"aria-haspopup":"listbox","aria-controls":"kmenu-listbox"}}getInputProps(){return {ref:t=>{t&&(this.inputElement=t);},role:"combobox","aria-autocomplete":"list","aria-expanded":this.state.open,"aria-controls":"kmenu-listbox","aria-activedescendant":this.state.activeId?`kmenu-option-${this.state.activeId}`:void 0,value:this.state.input,onInput:t=>{let e=t.target;this.setInput(e.value);},onKeyDown:t=>{this.handleKeyDown(t);}}}getListboxProps(){return {ref:t=>{t&&(this.listElement=t);},id:"kmenu-listbox",role:"listbox","aria-label":"Commands",tabIndex:-1}}getOptionProps(t){let e=this.state.allOptions.find(s=>s.id===t),i=this.state.activeId===t,n=this.state.filtered.findIndex(s=>s.id===t);return {ref:s=>{s?this.optionElements.set(t,s):this.optionElements.delete(t);},id:`kmenu-option-${t}`,role:"option","aria-selected":i,"aria-disabled":e?.disabled,tabIndex:-1,onClick:()=>{e&&!e.disabled&&(this.state.activeId=t,this.state.activeIndex=n,this.selectActive());},onMouseEnter:()=>{e&&!e.disabled&&n>=0&&this.setActiveByIndex(n);}}}handleKeyDown(t){switch(t.key){case "ArrowUp":t.preventDefault(),this.navigateUp();break;case "ArrowDown":t.preventDefault(),this.navigateDown();break;case "Tab":t.preventDefault(),t.shiftKey?this.navigateUp():this.navigateDown();break;case "Enter":t.preventDefault(),this.selectActive();break;case "Escape":t.preventDefault(),this.close();break;case "Backspace":this.state.input===""&&this.state.currentLevel>0&&(t.preventDefault(),this.goBack());break;case "Home":if(t.ctrlKey||t.metaKey){t.preventDefault();let e=this.state.filtered.findIndex(i=>!i.disabled);e>=0&&this.setActiveByIndex(e);}break;case "End":if(t.ctrlKey||t.metaKey){t.preventDefault();for(let e=this.state.filtered.length-1;e>=0;e--)if(!this.state.filtered[e]?.disabled){this.setActiveByIndex(e);break}}break}}selectActive(){if(this.destroyed||!this.state.activeId)return;let t=this.state.filtered.find(e=>e.id===this.state.activeId);if(!(!t||t.disabled)){if(t.children&&t.children.length>0)this.enterSubmenu(t);else if(this.emit({type:"select",option:t}),this.stateMachine.transition("select"),t.action){let e=t.action();if(e instanceof Promise)e.catch(console.error);else if(Array.isArray(e)){t.children=e,this.enterSubmenu(t);return}}}}destroy(){this.destroyed||(this.destroyed=true,this.close(),this.globalKeyHandler&&typeof window<"u"&&window.removeEventListener("keydown",this.globalKeyHandler),this.listeners.clear(),this.optionElements.clear(),this.inputElement=void 0,this.listElement=void 0);}};var v=(o,t)=>{if(!t)return o;let e=t.toLowerCase();return o.filter(i=>{if(i.disabled)return false;let n=i.label.toLowerCase().includes(e),s=i.keywords?.some(r=>r.toLowerCase().includes(e));return n||s})},g=(o,t)=>{if(!t)return o;let e=t.toLowerCase();return o.filter(i=>{if(i.disabled)return false;let n=i.label.toLowerCase().startsWith(e),s=i.keywords?.some(r=>r.toLowerCase().startsWith(e));return n||s})},b=(o,t)=>{if(!t)return o;let i=t.toLowerCase().split("");return o.filter(s=>!s.disabled).map(s=>{let r=`${s.label} ${s.keywords?.join(" ")||""}`.toLowerCase(),a=0,h=-1,l=0;for(let c of i){let d=r.indexOf(c,h+1);if(d===-1)return null;d===h+1?(l++,a+=10*l):(l=0,a+=1),(d===0||r[d-1]===" ")&&(a+=5),h=d;}return a-=r.length*.1,{option:s,score:a}}).filter(s=>s!==null).sort((s,r)=>r.score-s.score).map(s=>s.option)};function y(o,t){return (e,i)=>{if(!i)return e;try{let n=new RegExp(o.replace("{{query}}",i),t);return e.filter(s=>{if(s.disabled)return !1;let r=`${s.label} ${s.keywords?.join(" ")||""}`;return n.test(r)})}catch{return []}}}export{m as CommandCore,u as StateMachine,y as createRegexFilter,b as fuzzyFilter,v as simpleFilter,g as startsWithFilter};//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map