@loke/ui
Version:
2 lines (1 loc) • 18.6 kB
JavaScript
import{composeRefs}from"@loke/ui/compose-refs";import{createContextScope}from"@loke/ui/context";import{Primitive}from"@loke/ui/primitive";import{createSlot}from"@loke/ui/slot";import{useId}from"@loke/ui/use-id";import{useLayoutEffect}from"@loke/ui/use-layout-effect";import{forwardRef,useEffect,useMemo,useRef,useState,useSyncExternalStore}from"react";var IS_GAP_REGEXP=/[\\/_+.#"@[({&]/,COUNT_GAPS_REGEXP=/[\\/_+.#"@[({&]/g,IS_SPACE_REGEXP=/[\s-]/,COUNT_SPACE_REGEXP=/[\s-]/g;function commandScoreInner(string,abbreviation,lowerString,lowerAbbreviation,stringIndex,abbreviationIndex,memoizedResults){if(abbreviationIndex===abbreviation.length){if(stringIndex===string.length)return 1;return 0.99}let memoizeKey=`${stringIndex},${abbreviationIndex}`;if(memoizedResults[memoizeKey]!==void 0)return memoizedResults[memoizeKey];let abbreviationChar=lowerAbbreviation.charAt(abbreviationIndex),index=lowerString.indexOf(abbreviationChar,stringIndex),highScore=0,score,transposedScore,wordBreaks,spaceBreaks;while(index>=0){if(score=commandScoreInner(string,abbreviation,lowerString,lowerAbbreviation,index+1,abbreviationIndex+1,memoizedResults),score>highScore){if(index===stringIndex)score*=1;else if(IS_GAP_REGEXP.test(string.charAt(index-1))){if(score*=0.8,wordBreaks=string.slice(stringIndex,index-1).match(COUNT_GAPS_REGEXP),wordBreaks&&stringIndex>0)score*=0.999**wordBreaks.length}else if(IS_SPACE_REGEXP.test(string.charAt(index-1))){if(score*=0.9,spaceBreaks=string.slice(stringIndex,index-1).match(COUNT_SPACE_REGEXP),spaceBreaks&&stringIndex>0)score*=0.999**spaceBreaks.length}else if(score*=0.17,stringIndex>0)score*=0.999**(index-stringIndex);if(string.charAt(index)!==abbreviation.charAt(abbreviationIndex))score*=0.9999}if(score<0.1&&lowerString.charAt(index-1)===lowerAbbreviation.charAt(abbreviationIndex+1)||lowerAbbreviation.charAt(abbreviationIndex+1)===lowerAbbreviation.charAt(abbreviationIndex)&&lowerString.charAt(index-1)!==lowerAbbreviation.charAt(abbreviationIndex)){if(transposedScore=commandScoreInner(string,abbreviation,lowerString,lowerAbbreviation,index+1,abbreviationIndex+2,memoizedResults),transposedScore*0.1>score)score=transposedScore*0.1}if(score>highScore)highScore=score;index=lowerString.indexOf(abbreviationChar,index+1)}return memoizedResults[memoizeKey]=highScore,highScore}function formatInput(string){return string.toLowerCase().replace(COUNT_SPACE_REGEXP," ")}function commandScore(string,abbreviation,aliases){let stringToUse=aliases&&aliases.length>0?`${string} ${aliases.join(" ")}`:string;return commandScoreInner(stringToUse,abbreviation,formatInput(stringToUse),formatInput(abbreviation),0,0,{})}import{jsx,jsxs}from"react/jsx-runtime";var GROUP_SELECTOR='[loke-cmd-group=""]',GROUP_ITEMS_SELECTOR='[loke-cmd-group-items=""]',GROUP_HEADING_SELECTOR='[loke-cmd-group-heading=""]',ITEM_SELECTOR='[loke-cmd-item=""]',VALID_ITEM_SELECTOR=`${ITEM_SELECTOR}:not([aria-disabled="true"])`,SELECT_EVENT="loke-cmd-item-select",VALUE_ATTR="data-value",COMMAND_NAME="Command",defaultFilter=(value,search,keywords)=>commandScore(value,search,keywords),CommandSlot=createSlot("Command"),[createCommandContext,createCommandScope]=createContextScope(COMMAND_NAME),[CommandProvider,useCommand]=createCommandContext(COMMAND_NAME),[GroupProvider,useGroup]=createCommandContext(`${COMMAND_NAME}Group`);function useAsRef(data){let ref=useRef(data);return useLayoutEffect(()=>{ref.current=data}),ref}function useLazyRef(fn){let ref=useRef(void 0);if(ref.current===void 0)ref.current=fn();return ref}function useCmdk(selector,scope){let context=useCommand("useCmdk",scope);if(!context)throw Error("useCmdk must be used within a Command component");let cb=()=>selector(context.snapshot());return useSyncExternalStore(context.subscribe,cb,cb)}function useValue(id,ref,deps,aliases,scope){let valueRef=useRef(""),context=useCommand("useValue",scope);return useLayoutEffect(()=>{if(!context)return;let value=(()=>{for(let part of deps){if(typeof part==="string")return part.trim();if(typeof part==="object"&&"current"in part){if(part.current)return part.current.textContent?.trim();return valueRef.current}}})()??"",keywords=aliases.map((alias)=>alias.trim());context.value(id,value,keywords),ref.current?.setAttribute(VALUE_ATTR,value),valueRef.current=value}),valueRef}var useScheduleLayoutEffect=()=>{let[s,ss]=useState(),fns=useLazyRef(()=>new Map);return useLayoutEffect(()=>{for(let f of fns.current.values())f();fns.current=new Map},[s]),(id,cb)=>{fns.current.set(id,cb),ss({})}};function findNextSibling(el,selector){let sibling=el.nextElementSibling;while(sibling){if(sibling.matches(selector))return sibling;sibling=sibling.nextElementSibling}}function findPreviousSibling(el,selector){let sibling=el.previousElementSibling;while(sibling){if(sibling.matches(selector))return sibling;sibling=sibling.previousElementSibling}}function SlottableWithNestedChildren({asChild,children},render){if(asChild)return jsx(CommandSlot,{children:render(children)});return render(children)}var Command=forwardRef((props,forwardedRef)=>{let{__scopeCommand,label,value,disablePointerSelection:_,vimBindings=!0,className,...etc}=props,state=useLazyRef(()=>({filtered:{count:0,groups:new Set,items:new Map},search:"",selectedItemId:void 0,value:props.value??props.defaultValue??""})),allItems=useLazyRef(()=>new Set),allGroups=useLazyRef(()=>new Map),ids=useLazyRef(()=>new Map),listeners=useLazyRef(()=>new Set),propsRef=useAsRef(props),listId=useId(),labelId=useId(),inputId=useId(),listInnerRef=useRef(null),schedule=useScheduleLayoutEffect();useLayoutEffect(()=>{if(value!==void 0){let v=value.trim();state.current.value=v,context.emit()}},[value]),useLayoutEffect(()=>{schedule(6,scrollSelectedIntoView)},[]);function score(v,keywords){let filter=propsRef.current?.filter??defaultFilter;return v?filter(v,state.current.search,keywords):0}function sort(){if(!state.current.search||propsRef.current.shouldFilter===!1)return;let scores=state.current.filtered.items,groups=[];for(let groupValue of state.current.filtered.groups){let items=allGroups.current.get(groupValue),max=0;for(let item of items??[]){let maxScore=scores.get(item);max=Math.max(maxScore??0,max)}groups.push([groupValue,max])}let listInsertionElement=listInnerRef.current,validItems=getValidItems().sort((a,b)=>{let valueA=a.getAttribute("id"),valueB=b.getAttribute("id");if(valueA&&valueB)return(scores.get(valueB)??0)-(scores.get(valueA)??0);return 0});for(let item of validItems){let group=item.closest(GROUP_ITEMS_SELECTOR);if(group)group.appendChild(item.parentElement===group?item:item.closest(`${GROUP_ITEMS_SELECTOR} > *`));else listInsertionElement?.appendChild(item.parentElement===listInsertionElement?item:item.closest(`${GROUP_ITEMS_SELECTOR} > *`))}for(let group of groups.sort((a,b)=>b[1]-a[1])){let element=listInnerRef.current?.querySelector(`${GROUP_SELECTOR}[${VALUE_ATTR}="${encodeURIComponent(group[0])}"]`);element?.parentElement?.appendChild(element)}}function selectFirstItem(){let itemValue=getValidItems().find((validItem)=>validItem.getAttribute("aria-disabled")!=="true")?.getAttribute(VALUE_ATTR)??null;context.setState("value",itemValue)}function filterItems(){if(!state.current.search||propsRef.current.shouldFilter===!1){state.current.filtered.count=allItems.current.size;return}state.current.filtered.groups=new Set;let itemCount=0;for(let id of allItems.current){let itemValue=ids.current.get(id)?.value??"",keywords=ids.current.get(id)?.keywords??[],rank=score(itemValue,keywords);if(state.current.filtered.items.set(id,rank),rank>0)itemCount++}for(let[groupId,group]of allGroups.current)for(let itemId of group)if((state.current.filtered.items.get(itemId)??0)>0){state.current.filtered.groups.add(groupId);break}state.current.filtered.count=itemCount}function scrollSelectedIntoView(){let item=getSelectedItem();if(item){if(item.parentElement?.firstChild===item)item.closest(GROUP_SELECTOR)?.querySelector(GROUP_HEADING_SELECTOR)?.scrollIntoView?.({block:"nearest"});item.scrollIntoView?.({block:"nearest"})}}function getSelectedItem(){return listInnerRef.current?.querySelector(`${ITEM_SELECTOR}[aria-selected="true"]`)}function getValidItems(){return Array.from(listInnerRef.current?.querySelectorAll(VALID_ITEM_SELECTOR)||[])}function updateSelectedToIndex(index){let item=getValidItems()[index];if(item)context.setState("value",item.getAttribute(VALUE_ATTR)??null)}function updateSelectedByItem(change){let selected=getSelectedItem(),items=getValidItems(),index=items.indexOf(selected??items[0]),newSelected=items[index+change];if(propsRef.current?.loop){let newIndex=index+change;if(newIndex<0)newSelected=items.at(-1);else if(newIndex>=items.length)newSelected=items[0];else newSelected=items[newIndex]}if(newSelected)context.setState("value",newSelected.getAttribute(VALUE_ATTR)??null)}function updateSelectedByGroup(change){let group=getSelectedItem()?.closest(GROUP_SELECTOR),item=null;while(group&&!item)group=change>0?findNextSibling(group,GROUP_SELECTOR):findPreviousSibling(group,GROUP_SELECTOR),item=group?.querySelector(VALID_ITEM_SELECTOR)??null;if(item)context.setState("value",item.getAttribute(VALUE_ATTR)??null);else updateSelectedByItem(change)}let last=()=>updateSelectedToIndex(getValidItems().length-1),next=(e)=>{if(e.preventDefault(),e.metaKey)last();else if(e.altKey)updateSelectedByGroup(1);else updateSelectedByItem(1)},prev=(e)=>{if(e.preventDefault(),e.metaKey)updateSelectedToIndex(0);else if(e.altKey)updateSelectedByGroup(-1);else updateSelectedByItem(-1)},context=useMemo(()=>{return{emit:()=>{for(let listener of listeners.current)listener()},filter:()=>{return propsRef.current.shouldFilter!==!1},getDisablePointerSelection:()=>{return Boolean(propsRef.current.disablePointerSelection)},group:(id)=>{if(!allGroups.current.has(id))allGroups.current.set(id,new Set);return()=>{ids.current.delete(id),allGroups.current.delete(id)}},inputId,item:(id,groupId)=>{if(allItems.current.add(id),groupId)if(allGroups.current.has(groupId))allGroups.current.get(groupId)?.add(id);else allGroups.current.set(groupId,new Set([id]));return schedule(3,()=>{if(filterItems(),sort(),!state.current.value)selectFirstItem();context.emit()}),()=>{ids.current.delete(id),allItems.current.delete(id),state.current.filtered.items.delete(id);let selectedItem=getSelectedItem();schedule(4,()=>{if(filterItems(),selectedItem?.getAttribute("id")===id)selectFirstItem();context.emit()})}},label:label||props["aria-label"]||"Suggestions",labelId,listId,listInnerRef,setState:(key,prevValue,opts)=>{if(Object.is(state.current[key],prevValue))return;if(state.current[key]=prevValue,key==="search")filterItems(),sort(),schedule(1,selectFirstItem);else if(key==="value"){if(document.activeElement?.hasAttribute("loke-cmd-input")||document.activeElement?.hasAttribute("loke-cmd-root")){let input=document.getElementById(inputId);if(input)input.focus();else document.getElementById(listId)?.focus()}if(schedule(7,()=>{state.current.selectedItemId=getSelectedItem()?.id,context.emit()}),!opts)schedule(5,scrollSelectedIntoView);if(propsRef.current?.value!==void 0){let newValue=prevValue??"";propsRef.current.onValueChange?.(newValue);return}}context.emit()},snapshot:()=>{return state.current},subscribe:(cb)=>{return listeners.current.add(cb),()=>listeners.current.delete(cb)},value:(id,prevValue,keywords)=>{if(prevValue!==ids.current.get(id)?.value)ids.current.set(id,{keywords,value:prevValue}),state.current.filtered.items.set(id,score(prevValue,keywords)),schedule(2,()=>{sort(),context.emit()})}}},[]);return jsxs(Primitive.div,{ref:forwardedRef,tabIndex:-1,...etc,className,"loke-cmd-root":"",onKeyDown:(e)=>{etc.onKeyDown?.(e);let isComposing=e.nativeEvent.isComposing||e.keyCode===229;if(e.defaultPrevented||isComposing)return;switch(e.key){case"n":case"j":{if(vimBindings&&e.ctrlKey)next(e);break}case"ArrowDown":{next(e);break}case"p":case"k":{if(vimBindings&&e.ctrlKey)prev(e);break}case"ArrowUp":{prev(e);break}case"Home":{e.preventDefault(),updateSelectedToIndex(0);break}case"End":{e.preventDefault(),last();break}case"Enter":{e.preventDefault();let item=getSelectedItem();if(item){let event=new Event(SELECT_EVENT);item.dispatchEvent(event)}break}}},children:[jsx("label",{htmlFor:context.inputId,id:context.labelId,"loke-cmd-label":"",style:{borderWidth:0,clip:"rect(0, 0, 0, 0)",height:"1px",margin:"-1px",overflow:"hidden",padding:0,position:"absolute",whiteSpace:"nowrap",width:"1px"},children:context.label}),SlottableWithNestedChildren(props,(child)=>jsx(CommandProvider,{scope:__scopeCommand,...context,children:child}))]})}),CommandInput=forwardRef((props,forwardedRef)=>{let{__scopeCommand,onValueChange,...etc}=props,isControlled=props.value!=null,context=useCommand(`${COMMAND_NAME}Input`,__scopeCommand),search=useCmdk((state)=>state.search,__scopeCommand),selectedItemId=useCmdk((state)=>state.selectedItemId,__scopeCommand);return useEffect(()=>{if(props.value!=null)context.setState("search",props.value)},[context,props.value]),jsx(Primitive.input,{ref:forwardedRef,...etc,"aria-activedescendant":selectedItemId,"aria-autocomplete":"list","aria-controls":context.listId,"aria-expanded":!0,"aria-labelledby":context.labelId,autoComplete:"off",autoCorrect:"off",id:context.inputId,"loke-cmd-input":"",onChange:(e)=>{if(!isControlled)context.setState("search",e.target.value);onValueChange?.(e.target.value)},role:"combobox",spellCheck:!1,type:"text",value:isControlled?props.value:search})}),CommandList=forwardRef((props,forwardedRef)=>{let{__scopeCommand,children:_,label="Suggestions",className,...etc}=props,ref=useRef(null),height=useRef(null),selectedItemId=useCmdk((state)=>state.selectedItemId,__scopeCommand),context=useCommand(`${COMMAND_NAME}List`,__scopeCommand);return useEffect(()=>{if(height.current&&ref.current){let el=height.current,wrapper=ref.current,animationFrame,observer=new ResizeObserver(()=>{animationFrame=requestAnimationFrame(()=>{let offsetHeight=el.offsetHeight;wrapper.style.setProperty("--loke-cmd-list-height",`${offsetHeight.toFixed(1)}px`)})});return observer.observe(el),()=>{cancelAnimationFrame(animationFrame),observer.unobserve(el)}}},[]),jsx(Primitive.div,{ref:composeRefs(ref,forwardedRef),...etc,"aria-activedescendant":selectedItemId,"aria-label":label,className,id:context.listId,"loke-cmd-list":"",role:"listbox",tabIndex:-1,children:SlottableWithNestedChildren(props,(child)=>jsx("div",{"loke-cmd-list-sizer":"",ref:composeRefs(height,context.listInnerRef),children:child}))})}),CommandItem=forwardRef((props,forwardedRef)=>{let{__scopeCommand,className,...itemProps}=props,id=useId(),ref=useRef(null),groupContext=useGroup(`${COMMAND_NAME}Item`,__scopeCommand),context=useCommand(`${COMMAND_NAME}Item`,__scopeCommand),propsRef=useAsRef(props),forceMount=propsRef.current?.forceMount??groupContext?.forceMount;useLayoutEffect(()=>{if(!forceMount)return context.item(id,groupContext?.id)},[forceMount]);let value=useValue(id,ref,[props.value,props.children,ref],props.keywords??[],__scopeCommand),selected=useCmdk((state)=>state.value&&state.value===value.current,__scopeCommand),render=useCmdk((state)=>{if(forceMount)return!0;if(context.filter()===!1)return!0;if(!state.search)return!0;let score=state.filtered.items.get(id);return score!==void 0&&score>0},__scopeCommand);useEffect(()=>{let element=ref.current;if(!element||props.disabled)return;return element.addEventListener(SELECT_EVENT,onSelect),()=>element.removeEventListener(SELECT_EVENT,onSelect)},[render,props.onSelect,props.disabled]);function onSelect(){select(),propsRef.current.onSelect?.(value.current)}function select(){context.setState("value",value.current,!0)}if(!render)return null;let{disabled,value:_,onSelect:__,forceMount:___,keywords:____,...etc}=itemProps;return jsx(Primitive.div,{ref:composeRefs(ref,forwardedRef),...etc,"aria-disabled":Boolean(disabled),"aria-selected":Boolean(selected),className,"data-disabled":Boolean(disabled),"data-selected":Boolean(selected),"data-slot":"command-item",id,"loke-cmd-item":"",onClick:disabled?void 0:onSelect,onPointerMove:disabled||context.getDisablePointerSelection()?void 0:select,role:"option",children:props.children})}),CommandGroup=forwardRef((props,forwardedRef)=>{let{__scopeCommand,heading,children:_,forceMount,className,...etc}=props,id=useId(),ref=useRef(null),headingRef=useRef(null),headingId=useId(),context=useCommand(`${COMMAND_NAME}Group`,__scopeCommand),render=useCmdk((state)=>{if(forceMount)return!0;if(context.filter()===!1)return!0;if(!state.search)return!0;return state.filtered.groups.has(id)},__scopeCommand);useLayoutEffect(()=>{return context.group(id)},[]),useValue(id,ref,[props.value,props.heading,headingRef],[],__scopeCommand);let contextValue=useMemo(()=>({forceMount,id}),[forceMount]);return jsxs(Primitive.div,{className,"data-slot":"command-group",ref:composeRefs(ref,forwardedRef),...etc,hidden:render?void 0:!0,"loke-cmd-group":"",role:"presentation",children:[heading&&jsx("div",{"aria-hidden":!0,id:headingId,"loke-cmd-group-heading":"",ref:headingRef,children:heading}),SlottableWithNestedChildren(props,(child)=>jsx("fieldset",{"aria-labelledby":heading?headingId:void 0,"loke-cmd-group-items":"",children:jsx(GroupProvider,{scope:__scopeCommand,...contextValue,children:child})}))]})}),CommandSeparator=forwardRef((props,forwardedRef)=>{let{__scopeCommand,alwaysRender,className,...etc}=props,ref=useRef(null),render=useCmdk((state)=>!state.search,__scopeCommand);if(!(alwaysRender||render))return null;return jsx(Primitive.div,{ref:composeRefs(ref,forwardedRef),...etc,className,"data-slot":"command-separator","loke-cmd-separator":"",role:"separator"})}),CommandEmpty=forwardRef((props,forwardedRef)=>{let{__scopeCommand,className,...emptyProps}=props;if(!useCmdk((state)=>state.filtered.count===0,__scopeCommand))return null;return jsx(Primitive.div,{ref:forwardedRef,...emptyProps,className,"loke-cmd-empty":"",role:"presentation"})}),CommandLoading=forwardRef((props,forwardedRef)=>{let{__scopeCommand,progress,children:_,label="Loading...",...etc}=props;return jsx(Primitive.div,{ref:forwardedRef,...etc,"aria-label":label,"aria-valuemax":100,"aria-valuemin":0,"aria-valuenow":progress,"loke-cmd-loading":"",role:"progressbar",children:SlottableWithNestedChildren(props,(child)=>jsx("div",{"aria-hidden":!0,children:child}))})}),Root=Command,Input=CommandInput,List=CommandList,Item=CommandItem,Group=CommandGroup,Separator=CommandSeparator,Empty=CommandEmpty,Loading=CommandLoading;export{createCommandScope,Separator,Root,Loading,List,Item,Input,Group,Empty,CommandSeparator,CommandLoading,CommandList,CommandItem,CommandInput,CommandGroup,CommandEmpty,Command};