vue-command-kit 0.1.1 โ†’ 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,87 +15,19 @@
15
15
  <img src="./demo.gif" alt="vue-cmdk demo" width="720">
16
16
  </p>
17
17
 
18
- ## ๐Ÿง  Inspiration
19
-
20
- This project is heavily inspired by two great projects:
21
-
22
- | Project | Author | Description |
23
- | --- | --- | --- |
24
- | [`vue-command-palette`](https://github.com/xiaoluoboding/vue-command-palette) | [@xiaoluoboding](https://github.com/xiaoluoboding) | The first composable command palette for Vue, with 590 โ˜… |
25
- | [`cmdk`](https://github.com/pacocoursey/cmdk) | [@pacocoursey](https://github.com/pacocoursey) | Fast, unstyled command menu React component (10k+ โ˜…) |
26
-
27
- ### Why another one?
28
-
29
- [`vue-command-palette`](https://github.com/xiaoluoboding/vue-command-palette) pioneered the โŒ˜K experience for Vue. Big thanks to [@xiaoluoboding](https://github.com/xiaoluoboding) for the original idea and work ๐Ÿ™Œ
30
-
31
- However, the project has been **inactive since September 2023** โ€” 9 issues remain unanswered, no dependencies have been updated in years, and the bundle size is 28 kB. The Vue ecosystem deserves a **well-maintained, lightweight alternative** that keeps up with modern standards.
32
-
33
- **vue-cmdk** is built to fill that gap: same compound component API, zero dependencies, half the size, with full TypeScript support and a commitment to ongoing maintenance.
34
-
35
- ### vs vue-command-palette (legacy reference)
36
-
37
- | Feature | `vue-command-palette` | `vue-cmdk` |
38
- | ---------------- | :----------------------: | :-----------: |
39
- | ๐Ÿ“ฆ Bundle (min) | 28.2 kB | **11.8 kB** |
40
- | ๐Ÿ“ฆ Bundle (gzip) | 9.6 kB | **3.4 kB** |
41
- | ๐Ÿ” Search | fuse.js (extra dep) | **Built-in** |
42
- | ๐Ÿ“‹ TypeScript | Partial | **Full** |
43
- | ๐Ÿ”„ Async items | โŒ | โœ… |
44
- | ๐Ÿงฉ Custom filter | โŒ | โœ… |
45
- | ๐Ÿ”’ Focus trap | โŒ | โœ… |
46
- | ๐Ÿงน Dependencies | 3 (fuse.js, nanoid, ...) | **0** |
47
- | ๐Ÿ”ง Maintenance | โŒ Inactive since 2023 | โœ… **Active** |
48
-
49
- ---
50
-
51
18
  ## โœจ Features
52
19
 
53
- - **๐Ÿงฉ Compound component API** โ€” `<Command.Dialog>`, `<Command.List>`, `<Command.Item>`, etc.
20
+ - **๐Ÿงฉ Compound component API** โ€” `<Command.Dialog>`, `<Command.Input>`, `<Command.Item>`, etc.
54
21
  - **๐Ÿ’„ Unstyled** โ€” Bring your own CSS, zero opinions, full design control
55
22
  - **๐Ÿ” Built-in search** โ€” Fast case-insensitive filtering with keyword matching
56
23
  - **โŒจ๏ธ Keyboard-first** โ€” Arrow keys, Enter, Escape โ€” all built-in, no config needed
24
+ - **๐Ÿ”Š Global shortcut** โ€” Register shortcuts on items (e.g. `โŒ˜S`) and they work globally
57
25
  - **๐Ÿ“ฆ Tiny** โ€” 3.4 kB gzipped, **zero runtime dependencies** (peer: `vue` only)
58
26
  - **๐ŸŽฏ TypeScript** โ€” Full type inference and declaration files
59
27
  - **๐Ÿ”„ Dynamic items** โ€” Pass items as a reactive array, swap anytime
60
28
  - **๐Ÿ›  Custom filter** โ€” Provide your own filter function
61
29
  - **โ™ฟ Accessible** โ€” ARIA attributes, focus trap, `aria-live` region
62
30
 
63
- ## ๐Ÿ“Š Comparison with React cmdk
64
-
65
- `vue-cmdk` is a Vue 3 port inspired by the excellent [`cmdk`](https://github.com/pacocoursey/cmdk) (React). Below is the current feature parity status:
66
-
67
- | # | Feature | React `cmdk` | `vue-cmdk` | Status |
68
- | --- | -------------------------------------------- | :-------------: | :--------------: | :--------: |
69
- | 1 | `Command` root `value` / `onValueChange` | โœ… | โŒ | ๐Ÿ“‹ Planned |
70
- | 2 | `Command` root `shouldFilter` | โœ… | โŒ | ๐Ÿ“‹ Planned |
71
- | 3 | `Command` root `loop` | โœ… | โŒ | ๐Ÿ“‹ Planned |
72
- | 4 | `Command` root `label` (aria-label) | โœ… | โŒ | ๐Ÿ“‹ Planned |
73
- | 5 | `Command.Dialog` `open` / `onOpenChange` | โœ… `open` | โœ… `visible` | โœ… |
74
- | 6 | `Command.Dialog` `container` (portal target) | โœ… | โŒ | ๐Ÿ’ก Future |
75
- | 7 | `Command.Input` `value` / `onValueChange` | โœ… | โœ… `searchQuery` | โœ… |
76
- | 8 | `Command.Item` `forceMount` | โœ… | โŒ | ๐Ÿ“‹ Planned |
77
- | 9 | `Command.Item` `keywords` | โœ… | โœ… | โœ… |
78
- | 10 | `Command.Item` `onSelect` | โœ… | โœ… | โœ… |
79
- | 11 | `Command.Item` auto value from textContent | โœ… | โŒ | ๐Ÿ“‹ Planned |
80
- | 12 | `Command.Group` `forceMount` | โœ… | โŒ | ๐Ÿ“‹ Planned |
81
- | 13 | `Command.Group` `heading` | โœ… | โœ… | โœ… |
82
- | 14 | `Command.Separator` `alwaysRender` | โœ… | โŒ | ๐Ÿ’ก Future |
83
- | 15 | `Command.Empty` | โœ… | โœ… | โœ… |
84
- | 16 | `Command.Loading` | โœ… | โœ… | โœ… |
85
- | 17 | `useCommandState()` state selector | โœ… | โŒ | ๐Ÿ“‹ Planned |
86
- | 18 | **Nested items / Pages** | โœ… (pattern) | โŒ | ๐Ÿ“‹ Planned |
87
- | 19 | **Built-in search / filtering** | โœ… | โœ… | โœ… |
88
- | 20 | **Custom filter function** | โœ… (rank-based) | โœ… (item-based) | โœ… |
89
- | 21 | **Global shortcut listener** | โŒ (manual) | โœ… (built-in) | โœ… Bonus |
90
- | 22 | **Keyboard navigation** | โœ… | โœ… | โœ… |
91
- | 23 | **Focus trap** | โœ… (Radix) | โœ… (custom) | โœ… |
92
- | 24 | **Zero dependencies** | โŒ (Radix UI) | โœ… (0 deps) | โœ… Better |
93
- | 25 | **TypeScript** | โœ… | โœ… | โœ… |
94
- | 26 | **Unstyled** | โœ… | โœ… | โœ… |
95
- | 27 | **Bundle size (gzip)** | ~7 kB | **3.4 kB** | โœ… Smaller |
96
-
97
- > **Legend** โ€” โœ… Done ยท ๐Ÿ“‹ High/Medium priority ยท ๐Ÿ’ก Low priority / Nice to have
98
-
99
31
  ## ๐Ÿš€ Install
100
32
 
101
33
  ```bash
@@ -108,20 +40,20 @@ npm install vue-command-kit
108
40
 
109
41
  ```vue
110
42
  <script setup lang="ts">
111
- import { ref } from 'vue'
112
- import { Command } from 'vue-command-kit'
113
- import type { CommandItemData } from 'vue-command-kit'
43
+ import { ref } from 'vue'
44
+ import { Command } from 'vue-command-kit'
45
+ import type { CommandItemData } from 'vue-command-kit'
114
46
 
115
- const visible = ref(false)
47
+ const visible = ref(false)
116
48
 
117
- const items: CommandItemData[] = [
118
- { value: 'settings', label: 'Open settings', shortcut: 'โŒ˜,' },
119
- { value: 'home', label: 'Go to home', shortcut: 'โŒ˜H' },
120
- ]
49
+ const items: CommandItemData[] = [
50
+ { value: 'settings', label: 'Open settings', shortcut: 'โŒ˜,' },
51
+ { value: 'home', label: 'Go to home', shortcut: 'โŒ˜H' },
52
+ ]
121
53
 
122
- function onSelect(item: CommandItemData) {
123
- console.log('selected:', item.value)
124
- }
54
+ function onSelect(item: CommandItemData) {
55
+ console.log('selected:', item.value)
56
+ }
125
57
  </script>
126
58
 
127
59
  <template>
@@ -157,19 +89,15 @@ npm install vue-command-kit
157
89
 
158
90
  ```vue
159
91
  <script setup lang="ts">
160
- import { Command } from 'vue-command-kit'
92
+ import { Command } from 'vue-command-kit'
161
93
 
162
- function myFilter(items: CommandItemData[], query: string) {
163
- // Return filtered items, or null to use default filter
164
- return items.filter((item) => item.label?.includes(query))
165
- }
94
+ function myFilter(items: CommandItemData[], query: string) {
95
+ return items.filter((item) => item.label?.includes(query))
96
+ }
166
97
  </script>
167
98
 
168
99
  <template>
169
- <Command.Dialog
170
- :filter="myFilter"
171
- ...
172
- />
100
+ <Command.Dialog :filter="myFilter" ... />
173
101
  </template>
174
102
  ```
175
103
 
@@ -177,17 +105,17 @@ npm install vue-command-kit
177
105
 
178
106
  ```vue
179
107
  <script setup lang="ts">
180
- import { ref } from 'vue'
181
- import { Command } from 'vue-command-kit'
182
- import type { CommandItemData } from 'vue-command-kit'
108
+ import { ref } from 'vue'
109
+ import { Command } from 'vue-command-kit'
110
+ import type { CommandItemData } from 'vue-command-kit'
183
111
 
184
- const visible = ref(false)
185
- const query = ref('')
112
+ const visible = ref(false)
113
+ const query = ref('')
186
114
 
187
- const items: CommandItemData[] = [
188
- { value: 'home', label: 'Home', keywords: ['dashboard'] },
189
- { value: 'settings', label: 'Settings' },
190
- ]
115
+ const items: CommandItemData[] = [
116
+ { value: 'home', label: 'Home', keywords: ['dashboard'] },
117
+ { value: 'settings', label: 'Settings' },
118
+ ]
191
119
  </script>
192
120
 
193
121
  <template>
@@ -204,26 +132,26 @@ npm install vue-command-kit
204
132
 
205
133
  ```vue
206
134
  <script setup lang="ts">
207
- import { ref, watch } from 'vue'
208
- import { Command } from 'vue-command-kit'
209
- import type { CommandItemData } from 'vue-command-kit'
210
-
211
- const visible = ref(false)
212
- const loading = ref(false)
213
- const items = ref<CommandItemData[]>([])
214
-
215
- watch(visible, async (v) => {
216
- if (v) {
217
- loading.value = true
218
- const data = await fetch('/api/commands').then((r) => r.json())
219
- items.value = data.map((d: any) => ({
220
- value: d.id,
221
- label: d.name,
222
- group: d.category,
223
- }))
224
- loading.value = false
225
- }
226
- })
135
+ import { ref, watch } from 'vue'
136
+ import { Command } from 'vue-command-kit'
137
+ import type { CommandItemData } from 'vue-command-kit'
138
+
139
+ const visible = ref(false)
140
+ const loading = ref(false)
141
+ const items = ref<CommandItemData[]>([])
142
+
143
+ watch(visible, async (v) => {
144
+ if (v) {
145
+ loading.value = true
146
+ const data = await fetch('/api/commands').then((r) => r.json())
147
+ items.value = data.map((d: any) => ({
148
+ value: d.id,
149
+ label: d.name,
150
+ group: d.category,
151
+ }))
152
+ loading.value = false
153
+ }
154
+ })
227
155
  </script>
228
156
 
229
157
  <template>
@@ -239,58 +167,73 @@ npm install vue-command-kit
239
167
 
240
168
  ## ๐Ÿ“– API
241
169
 
242
- ### `<Command.Dialog>` Props
243
-
244
- | Prop | Type | Default | Description |
245
- | --------------- | ------------------- | --------------------- | ------------------------------------ |
246
- | `visible` | `boolean` | `false` | Controlled open state |
247
- | `items` | `CommandItemData[]` | `[]` | Items to display |
248
- | `searchQuery` | `string` | `''` | Search query (`v-model:searchQuery`) |
249
- | `placeholder` | `string` | `'Type a command...'` | Input placeholder |
250
- | `filter` | `FilterFn` | โ€” | Custom filter function |
251
- | `loading` | `boolean` | `false` | Show loading state |
252
- | `autoFocus` | `boolean` | `true` | Auto-focus input on open |
253
- | `closeOnSelect` | `boolean` | `true` | Close dialog after selection |
254
-
255
- ### `<Command.Dialog>` Events
256
-
257
- | Event | Payload | Description |
258
- | -------------------- | ----------------- | --------------------------------- |
259
- | `update:visible` | `boolean` | Emitted when visibility changes |
260
- | `update:searchQuery` | `string` | Emitted when search query changes |
261
- | `select` | `CommandItemData` | Emitted when an item is selected |
262
-
263
170
  ### Components
264
171
 
265
172
  | Component | Description |
266
173
  | --------------------- | ---------------------------------------------- |
267
174
  | `<Command.Dialog>` | Modal dialog with mask, transition, focus trap |
268
- | `<Command.Menu>` | Inline command menu (non-modal) |
175
+ | `<Command.Menu>` | Inline command menu (non-modal) with slots |
269
176
  | `<Command.Input>` | Search input with keyboard navigation |
270
- | `<Command.List>` | Scrollable list rendering `groupedItems` |
177
+ | `<Command.List>` | Scrollable list rendering grouped items |
271
178
  | `<Command.Group>` | Group of items with heading |
272
179
  | `<Command.Item>` | Single selectable command item |
273
180
  | `<Command.Empty>` | Shown when no results match |
274
181
  | `<Command.Separator>` | Visual separator |
275
182
  | `<Command.Loading>` | Loading indicator |
276
183
 
184
+ ### `<Command.Dialog>` Props
185
+
186
+ | Prop | Type | Default | Description |
187
+ | --------------- | ------------------- | ------------------------------- | ---------------------------------------------- |
188
+ | `visible` | `boolean` | `false` | Controlled open state |
189
+ | `items` | `CommandItemData[]` | `[]` | Items to display |
190
+ | `searchQuery` | `string` | `''` | Search query (`v-model:searchQuery`) |
191
+ | `value` | `string` | โ€” | Selected item value (`v-model:value`) |
192
+ | `placeholder` | `string` | `'Type a command or search...'` | Input placeholder |
193
+ | `filter` | `FilterFn` | โ€” | Custom filter function |
194
+ | `loading` | `boolean` | `false` | Show loading state |
195
+ | `autoFocus` | `boolean` | `true` | Auto-focus input on open |
196
+ | `closeOnSelect` | `boolean` | `true` | Close dialog after selection |
197
+ | `shouldFilter` | `boolean` | `true` | When `false`, skip built-in filtering |
198
+ | `loop` | `boolean` | `true` | When `false`, keyboard nav stops at boundaries |
199
+ | `label` | `string` | `'Command menu'` | `aria-label` for the dialog |
200
+
201
+ ### `<Command.Dialog>` Events
202
+
203
+ | Event | Payload | Description |
204
+ | -------------------- | ----------------- | --------------------------------- |
205
+ | `update:visible` | `boolean` | Emitted when visibility changes |
206
+ | `update:searchQuery` | `string` | Emitted when search query changes |
207
+ | `update:value` | `string` | Emitted when an item is selected |
208
+ | `select` | `CommandItemData` | Emitted when an item is selected |
209
+
277
210
  ### `CommandItemData`
278
211
 
279
212
  ```ts
280
213
  interface CommandItemData {
214
+ /** Unique value for this item */
281
215
  value: string
216
+ /** Display label (falls back to value) */
282
217
  label?: string
218
+ /** Optional keywords for search matching */
283
219
  keywords?: string[]
220
+ /** Optional shortcut display (e.g. "โŒ˜S") */
284
221
  shortcut?: string
222
+ /** Group this item belongs to */
285
223
  group?: string
224
+ /** Disabled state */
286
225
  disabled?: boolean
287
- icon?: Component
226
+ /** Custom render icon or prefix */
227
+ icon?: Component | VNode | (() => VNode)
228
+ /** Callback when item is selected */
288
229
  onSelect?: (item: CommandItemData) => void
289
230
  }
290
231
  ```
291
232
 
292
233
  ### `useCommandMenu()` Composable
293
234
 
235
+ Use the composable for programmatic control outside of `Command.Dialog` / `Command.Menu`.
236
+
294
237
  ```ts
295
238
  import { useCommandMenu } from 'vue-command-kit'
296
239
  import type { UseCommandMenuReturn, FilterFn } from 'vue-command-kit'
@@ -302,6 +245,38 @@ menu.close()
302
245
  menu.toggle()
303
246
  ```
304
247
 
248
+ Returns:
249
+
250
+ | Return | Type | Description |
251
+ | ----------------- | ------------------------- | ------------------------------------------------ |
252
+ | `visible` | `Ref<boolean>` | Open state |
253
+ | `searchQuery` | `Ref<string>` | Current search query |
254
+ | `activeIndex` | `Ref<number>` | Currently highlighted item index |
255
+ | `items` | `Ref<CommandItemData[]>` | Raw item list |
256
+ | `filteredItems` | `ComputedRef<...>` | Items after filtering |
257
+ | `groupedItems` | `ComputedRef<...>` | Filtered items grouped by `group` field |
258
+ | `open()` | `() => void` | Open the menu |
259
+ | `close()` | `() => void` | Close and reset search |
260
+ | `toggle()` | `() => void` | Toggle open state |
261
+ | `selectNext()` | `() => void` | Move active index down |
262
+ | `selectPrev()` | `() => void` | Move active index up |
263
+ | `selectCurrent()` | `() => void` | Select currently active item |
264
+ | `defaultFilter()` | `(items, query) => items` | Default filter implementation (case-insensitive) |
265
+
266
+ ## ๐Ÿ“ฆ Bundle Size
267
+
268
+ | Format | Size |
269
+ | ------- | ---------- |
270
+ | ESM | 11.8 kB |
271
+ | UMD | 11.8 kB |
272
+ | Gzipped | **3.4 kB** |
273
+
274
+ Zero runtime dependencies. Peer dependency only on `vue ^3.4.0`.
275
+
276
+ ## ๐Ÿค Acknowledgements
277
+
278
+ vue-cmdk is inspired by [`cmdk`](https://github.com/pacocoursey/cmdk) by [@pacocoursey](https://github.com/pacocoursey) โ€” a fast, unstyled command menu for React.
279
+
305
280
  ### Keyboard
306
281
 
307
282
  | Key | Action |
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/dist/types.d.ts CHANGED
@@ -34,11 +34,20 @@ export interface CommandRootProps {
34
34
  closeOnSelect?: boolean;
35
35
  /** Whether the menu is loading */
36
36
  loading?: boolean;
37
+ /** aria-label for the dialog/menu */
38
+ label?: string;
39
+ /** When false, skip built-in filtering and show all items (custom filter still applies) */
40
+ shouldFilter?: boolean;
41
+ /** When false, keyboard navigation stops at boundaries instead of wrapping around */
42
+ loop?: boolean;
43
+ /** Controlled selected item value (v-model) */
44
+ value?: string;
37
45
  }
38
46
  /** Emitted events */
39
47
  export interface CommandRootEmits {
40
48
  (e: 'update:visible', value: boolean): void;
41
49
  (e: 'update:searchQuery', value: string): void;
50
+ (e: 'update:value', value: string): void;
42
51
  (e: 'select', item: CommandItemData): void;
43
52
  }
44
53
  /** Props for CommandDialog โ€” extends shared root props with items array */
@@ -1,10 +1,19 @@
1
1
  import { Ref, ComputedRef } from 'vue';
2
2
  import { CommandItemData, CommandGroupData } from './types';
3
3
  export type FilterFn = (items: CommandItemData[], query: string) => CommandItemData[] | null;
4
+ export interface UseCommandMenuOptions {
5
+ /** Custom filter function */
6
+ filter?: FilterFn;
7
+ /** When false, skip built-in filtering */
8
+ shouldFilter?: boolean;
9
+ /** When false, keyboard navigation stops at boundaries */
10
+ loop?: boolean;
11
+ }
4
12
  export interface UseCommandMenuReturn {
5
13
  visible: Ref<boolean>;
6
14
  searchQuery: Ref<string>;
7
15
  activeIndex: Ref<number>;
16
+ selectedValue: Ref<string | undefined>;
8
17
  items: Ref<CommandItemData[]>;
9
18
  toggle: () => void;
10
19
  open: () => void;
@@ -17,4 +26,4 @@ export interface UseCommandMenuReturn {
17
26
  selectPrev: () => void;
18
27
  selectCurrent: () => void;
19
28
  }
20
- export declare function useCommandMenu(customFilter?: FilterFn, onItemSelect?: (item: CommandItemData) => void): UseCommandMenuReturn;
29
+ export declare function useCommandMenu(optionsOrFilter?: UseCommandMenuOptions | FilterFn, onItemSelect?: (item: CommandItemData) => void): UseCommandMenuReturn;
@@ -4,6 +4,9 @@ export interface UseCommandRootOptions {
4
4
  filter?: FilterFn;
5
5
  loading?: boolean;
6
6
  closeOnSelect?: boolean;
7
+ shouldFilter?: boolean;
8
+ loop?: boolean;
9
+ value?: string;
7
10
  }
8
11
  export interface UseCommandRootReturn {
9
12
  state: ReturnType<typeof useCommandMenu>;