11<template >
22 <Transition name =" fade" >
33 <div
4- class =" fixed inset-0 z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm"
4+ class =" fixed inset-0 z-[1000] flex items-start justify-center bg-black/50 backdrop-blur-sm"
55 v-show =" show"
66 >
77 <div
8- class =" cmdbar-container flex items-center justify-center border border-border shadow-lg rounded-xl bg-bg p2 flex-col gap-2"
8+ class =" cmdbar-container flex items-center justify-center border border-border shadow-lg rounded-xl bg-bg p2 flex-col gap-2 mt-5rem "
99 >
1010 <label for =" command-input" class =" sr-only" >command-input</label >
1111
1212 <search class =" relative w-xl h-12 flex items-center" >
1313 <span class =" absolute inset-is-4 text-fg-subtle font-mono pointer-events-none" > > </span >
1414 <input
1515 type =" text"
16- label =" Enter command..."
1716 v-model =" inputVal"
1817 id =" command-input"
1918 ref =" inputRef"
2019 class =" w-full h-full px-4 pl-8 text-fg outline-none bg-bg-subtle border border-border rounded-md"
21- placeholder =" Enter command... "
20+ : placeholder =" placeholderText "
2221 @keydown =" handleKeydown"
2322 />
2423 </search >
2524
26- <div class =" w-xl h-lg overflow-auto" >
25+ <div class =" w-xl max- h-lg overflow-auto" v-if = " view.type != 'INPUT' " >
2726 <div
2827 v-for =" item in filteredCmdList"
2928 :key =" item.id"
3231 'bg-bg-subtle': item.id === selected,
3332 'trigger-anim': item.id === triggeringId,
3433 }"
35- @click =" triggerCommand (item.id)"
34+ @click =" onTrigger (item.id)"
3635 >
3736 <div class =" text-fg" >{{ item.name }}</div >
3837 <div class =" text-fg-subtle text-sm" >{{ item.description }}</div >
4443</template >
4544
4645<script setup lang="ts">
46+ const { t } = useI18n ()
47+
48+ type ViewState =
49+ | { type: ' ROOT' }
50+ | { type: ' INPUT' ; prompt: string ; resolve: (val : string ) => void }
51+ | { type: ' SELECT' ; prompt: string ; items: any []; resolve: (val : any ) => void }
52+ const view = ref <ViewState >({ type: ' ROOT' })
53+
54+ const cmdCtx: CommandContext = {
55+ async input(options ) {
56+ return new Promise (resolve => {
57+ view .value = { type: ' INPUT' , prompt: options .prompt , resolve }
58+ })
59+ },
60+ async select(options ) {
61+ return new Promise (resolve => {
62+ view .value = { type: ' SELECT' , prompt: options .prompt , items: options .items , resolve }
63+ })
64+ },
65+ }
66+
4767const { commands } = useCommandRegistry ()
4868
4969const selected = shallowRef (commands .value [0 ]?.id || ' ' )
@@ -54,13 +74,26 @@ const inputRef = useTemplateRef('inputRef')
5474
5575const { focused : inputFocused } = useFocus (inputRef )
5676
77+ const placeholderText = computed (() => {
78+ if (view .value .type === ' INPUT' || view .value .type === ' SELECT' ) {
79+ return view .value .prompt
80+ }
81+ return t (' command.placeholder' )
82+ })
83+
5784const filteredCmdList = computed (() => {
85+ if (view .value .type === ' INPUT' ) {
86+ return []
87+ }
88+
89+ const list = view .value .type === ' SELECT' ? view .value .items : commands .value
90+
5891 if (! inputVal .value ) {
59- return commands . value
92+ return list
6093 }
6194 const filter = inputVal .value .trim ().toLowerCase ()
62- return commands . value .filter (
63- item =>
95+ return list .filter (
96+ ( item : any ) =>
6497 item .name .toLowerCase ().includes (filter ) ||
6598 item .description ?.toLowerCase ().includes (filter ) ||
6699 item .id .includes (filter ),
@@ -84,6 +117,7 @@ function open() {
84117 inputVal .value = ' '
85118 selected .value = commands .value [0 ]?.id || ' '
86119 show .value = true
120+ view .value = { type: ' ROOT' }
87121 nextTick (focusInput )
88122}
89123
@@ -101,20 +135,36 @@ function toggle() {
101135 }
102136}
103137
104- function triggerCommand(id : string ) {
105- const selectedItem = filteredCmdList .value .find (item => item .id === id )
106- selectedItem ?.handler ?.({} as CommandContext )
138+ function onTrigger(id : string ) {
107139 triggeringId .value = id
108- setTimeout (() => {
140+
141+ if (view .value .type === ' ROOT' ) {
142+ const selectedItem = filteredCmdList .value .find ((item : any ) => item .id === id )
143+ selectedItem ?.handler ?.(cmdCtx )
144+ setTimeout (() => {
145+ triggeringId .value = ' '
146+ if (view .value .type === ' ROOT' ) {
147+ close ()
148+ }
149+ }, 100 )
150+ } else if (view .value .type === ' INPUT' ) {
151+ view .value .resolve (inputVal .value )
152+ close ()
153+ } else if (view .value .type === ' SELECT' ) {
154+ const selectedItem = filteredCmdList .value .find ((item : any ) => item .id === id )
155+ view .value .resolve (selectedItem )
109156 close ()
110- triggeringId .value = ' '
111- }, 100 )
157+ }
112158}
113159
114160const handleKeydown = useThrottleFn ((e : KeyboardEvent ) => {
115- if (! filteredCmdList .value .length ) return
161+ if (view .value .type === ' INPUT' && e .key === ' Enter' ) {
162+ e .preventDefault ()
163+ onTrigger (' ' ) // Trigger for input doesn't need ID
164+ return
165+ }
116166
117- const currentIndex = filteredCmdList .value .findIndex (item => item .id === selected .value )
167+ const currentIndex = filteredCmdList .value .findIndex (( item : any ) => item .id === selected .value )
118168
119169 if (e .key === ' ArrowDown' ) {
120170 e .preventDefault ()
@@ -127,7 +177,7 @@ const handleKeydown = useThrottleFn((e: KeyboardEvent) => {
127177 selected .value = filteredCmdList .value [prevIndex ]?.id || ' '
128178 } else if (e .key === ' Enter' ) {
129179 e .preventDefault ()
130- triggerCommand (selected .value )
180+ onTrigger (selected .value )
131181 } else if (e .key === ' Escape' ) {
132182 e .preventDefault ()
133183 close ()
0 commit comments