vue-command-kit 0.1.1 → 0.2.0
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 +87 -292
- package/dist/__tests__/CommandDialog.test.d.ts +1 -0
- package/dist/__tests__/CommandInput.test.d.ts +1 -0
- package/dist/__tests__/CommandItem.test.d.ts +1 -0
- package/dist/__tests__/CommandList.test.d.ts +1 -0
- package/dist/__tests__/highlight.test.d.ts +1 -0
- package/dist/__tests__/injectStrict.test.d.ts +1 -0
- package/dist/__tests__/parseShortcut.test.d.ts +1 -0
- package/dist/__tests__/useCommandMenu.test.d.ts +1 -0
- package/dist/__tests__/useCommandRoot.test.d.ts +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/useCommandMenu.d.ts +10 -1
- package/dist/useCommandRoot.d.ts +3 -0
- package/dist/utils/highlight.d.ts +9 -0
- package/dist/utils/shortcut.d.ts +12 -0
- package/dist/vue-cmdk.js +415 -339
- package/dist/vue-cmdk.umd.cjs +1 -1
- package/package.json +28 -1
package/README.md
CHANGED
|
@@ -2,101 +2,26 @@
|
|
|
2
2
|
<img src="https://img.shields.io/npm/v/vue-command-kit?color=blue&label=version" alt="npm">
|
|
3
3
|
<img src="https://img.shields.io/badge/vue-3.4%2B-brightgreen" alt="vue">
|
|
4
4
|
<img src="https://img.shields.io/badge/license-MIT-blue" alt="license">
|
|
5
|
-
<img src="https://img.shields.io/badge/bundle-
|
|
5
|
+
<img src="https://img.shields.io/badge/bundle-5.0kB_gzip-green" alt="size">
|
|
6
|
+
<img src="https://img.shields.io/badge/tests-117_passed-brightgreen" alt="tests">
|
|
6
7
|
</p>
|
|
7
8
|
|
|
8
9
|
<h1 align="center">⌘K — vue-cmdk</h1>
|
|
9
10
|
<p align="center">
|
|
10
|
-
|
|
11
|
-
Press <kbd>⌘K</kbd> and take control.
|
|
11
|
+
A fast, composable, unstyled command palette for Vue 3.
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
<img src="./demo.gif" alt="vue-cmdk demo" width="720">
|
|
16
|
-
</p>
|
|
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?
|
|
14
|
+
## Features
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
---
|
|
16
|
+
- **Compound component API** — Declarative composition with `<Command.Dialog>`, `<Command.Input>`, `<Command.Item>`, and more
|
|
17
|
+
- **Built-in search** — Case-insensitive filtering with keyword matching and result highlighting
|
|
18
|
+
- **Force render** — `forceMount` prop keeps items in the DOM when filtered out (for animations)
|
|
19
|
+
- **Keyboard-first** — Arrow key navigation, Enter to select, Escape to close, global shortcuts
|
|
20
|
+
- **Unstyled** — Zero CSS opinions; bring your own styles
|
|
21
|
+
- **TypeScript** — Full type inference, exported types, and declaration files
|
|
22
|
+
- **Zero runtime dependencies** — Peer dependency only on `vue ^3.4.0`
|
|
50
23
|
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
- **🧩 Compound component API** — `<Command.Dialog>`, `<Command.List>`, `<Command.Item>`, etc.
|
|
54
|
-
- **💄 Unstyled** — Bring your own CSS, zero opinions, full design control
|
|
55
|
-
- **🔍 Built-in search** — Fast case-insensitive filtering with keyword matching
|
|
56
|
-
- **⌨️ Keyboard-first** — Arrow keys, Enter, Escape — all built-in, no config needed
|
|
57
|
-
- **📦 Tiny** — 3.4 kB gzipped, **zero runtime dependencies** (peer: `vue` only)
|
|
58
|
-
- **🎯 TypeScript** — Full type inference and declaration files
|
|
59
|
-
- **🔄 Dynamic items** — Pass items as a reactive array, swap anytime
|
|
60
|
-
- **🛠 Custom filter** — Provide your own filter function
|
|
61
|
-
- **♿ Accessible** — ARIA attributes, focus trap, `aria-live` region
|
|
62
|
-
|
|
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
|
-
## 🚀 Install
|
|
24
|
+
## Installation
|
|
100
25
|
|
|
101
26
|
```bash
|
|
102
27
|
npm install vue-command-kit
|
|
@@ -104,24 +29,24 @@ npm install vue-command-kit
|
|
|
104
29
|
|
|
105
30
|
## Quick Start
|
|
106
31
|
|
|
107
|
-
###
|
|
32
|
+
### With items prop
|
|
108
33
|
|
|
109
34
|
```vue
|
|
110
35
|
<script setup lang="ts">
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
36
|
+
import { ref } from 'vue'
|
|
37
|
+
import { Command } from 'vue-command-kit'
|
|
38
|
+
import type { CommandItemData } from 'vue-command-kit'
|
|
114
39
|
|
|
115
|
-
|
|
40
|
+
const visible = ref(false)
|
|
116
41
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
42
|
+
const items: CommandItemData[] = [
|
|
43
|
+
{ value: 'settings', label: 'Open settings', shortcut: '⌘,' },
|
|
44
|
+
{ value: 'home', label: 'Go to home', shortcut: '⌘H' },
|
|
45
|
+
]
|
|
121
46
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
47
|
+
function onSelect(item: CommandItemData) {
|
|
48
|
+
console.log('selected:', item.value)
|
|
49
|
+
}
|
|
125
50
|
</script>
|
|
126
51
|
|
|
127
52
|
<template>
|
|
@@ -136,7 +61,7 @@ npm install vue-command-kit
|
|
|
136
61
|
</template>
|
|
137
62
|
```
|
|
138
63
|
|
|
139
|
-
###
|
|
64
|
+
### With custom slot content
|
|
140
65
|
|
|
141
66
|
```vue
|
|
142
67
|
<Command.Dialog :visible="visible" @update:visible="visible = $event">
|
|
@@ -153,104 +78,39 @@ npm install vue-command-kit
|
|
|
153
78
|
</Command.Dialog>
|
|
154
79
|
```
|
|
155
80
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
```vue
|
|
159
|
-
<script setup lang="ts">
|
|
160
|
-
import { Command } from 'vue-command-kit'
|
|
161
|
-
|
|
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
|
-
}
|
|
166
|
-
</script>
|
|
167
|
-
|
|
168
|
-
<template>
|
|
169
|
-
<Command.Dialog
|
|
170
|
-
:filter="myFilter"
|
|
171
|
-
...
|
|
172
|
-
/>
|
|
173
|
-
</template>
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### With v-model:searchQuery
|
|
177
|
-
|
|
178
|
-
```vue
|
|
179
|
-
<script setup lang="ts">
|
|
180
|
-
import { ref } from 'vue'
|
|
181
|
-
import { Command } from 'vue-command-kit'
|
|
182
|
-
import type { CommandItemData } from 'vue-command-kit'
|
|
183
|
-
|
|
184
|
-
const visible = ref(false)
|
|
185
|
-
const query = ref('')
|
|
186
|
-
|
|
187
|
-
const items: CommandItemData[] = [
|
|
188
|
-
{ value: 'home', label: 'Home', keywords: ['dashboard'] },
|
|
189
|
-
{ value: 'settings', label: 'Settings' },
|
|
190
|
-
]
|
|
191
|
-
</script>
|
|
192
|
-
|
|
193
|
-
<template>
|
|
194
|
-
<Command.Dialog
|
|
195
|
-
:visible="visible"
|
|
196
|
-
:items="items"
|
|
197
|
-
v-model:searchQuery="query"
|
|
198
|
-
@update:visible="visible = $event"
|
|
199
|
-
/>
|
|
200
|
-
</template>
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Async data + loading
|
|
204
|
-
|
|
205
|
-
```vue
|
|
206
|
-
<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
|
-
})
|
|
227
|
-
</script>
|
|
81
|
+
## API
|
|
228
82
|
|
|
229
|
-
|
|
230
|
-
<Command.Dialog
|
|
231
|
-
:visible="visible"
|
|
232
|
-
:items="items"
|
|
233
|
-
:loading="loading"
|
|
234
|
-
@update:visible="visible = $event"
|
|
235
|
-
@select="console.log('selected', $event)"
|
|
236
|
-
/>
|
|
237
|
-
</template>
|
|
238
|
-
```
|
|
83
|
+
### Components
|
|
239
84
|
|
|
240
|
-
|
|
85
|
+
| Component | Description |
|
|
86
|
+
| --------------------- | ---------------------------------------------- |
|
|
87
|
+
| `<Command.Dialog>` | Modal dialog with mask, transition, focus trap |
|
|
88
|
+
| `<Command.Menu>` | Inline command menu (non-modal) with slots |
|
|
89
|
+
| `<Command.Input>` | Search input with keyboard navigation |
|
|
90
|
+
| `<Command.List>` | Scrollable list rendering grouped items |
|
|
91
|
+
| `<Command.Group>` | Group of items with heading |
|
|
92
|
+
| `<Command.Item>` | Single selectable command item |
|
|
93
|
+
| `<Command.Separator>` | Visual separator with optional `alwaysRender` |
|
|
94
|
+
| `<Command.Empty>` | Shown when no results match |
|
|
95
|
+
| `<Command.Loading>` | Loading indicator |
|
|
241
96
|
|
|
242
97
|
### `<Command.Dialog>` Props
|
|
243
98
|
|
|
244
|
-
| Prop | Type
|
|
245
|
-
| --------------- |
|
|
246
|
-
| `visible` | `boolean`
|
|
247
|
-
| `items` | `CommandItemData[]`
|
|
248
|
-
| `searchQuery` | `string`
|
|
249
|
-
| `
|
|
250
|
-
| `
|
|
251
|
-
| `
|
|
252
|
-
| `
|
|
253
|
-
| `
|
|
99
|
+
| Prop | Type | Default | Description |
|
|
100
|
+
| --------------- | ----------------------- | ------------------------------- | ---------------------------------------------- |
|
|
101
|
+
| `visible` | `boolean` | `false` | Controlled open state |
|
|
102
|
+
| `items` | `CommandItemData[]` | `[]` | Items to display |
|
|
103
|
+
| `searchQuery` | `string` | — | Search query (`v-model:searchQuery`) |
|
|
104
|
+
| `value` | `string` | — | Selected item value (`v-model:value`) |
|
|
105
|
+
| `placeholder` | `string` | `'Type a command or search...'` | Input placeholder |
|
|
106
|
+
| `filter` | `FilterFn` | — | Custom filter function |
|
|
107
|
+
| `loading` | `boolean` | `false` | Show loading state |
|
|
108
|
+
| `autoFocus` | `boolean` | `true` | Auto-focus input on open |
|
|
109
|
+
| `closeOnSelect` | `boolean` | `true` | Close dialog after selection |
|
|
110
|
+
| `shouldFilter` | `boolean` | `true` | When `false`, skip built-in filtering |
|
|
111
|
+
| `loop` | `boolean` | `true` | When `false`, keyboard nav stops at boundaries |
|
|
112
|
+
| `label` | `string` | `'Command menu'` | `aria-label` for the dialog |
|
|
113
|
+
| `container` | `string \| HTMLElement` | `'body'` | Teleport target for the dialog |
|
|
254
114
|
|
|
255
115
|
### `<Command.Dialog>` Events
|
|
256
116
|
|
|
@@ -258,22 +118,9 @@ npm install vue-command-kit
|
|
|
258
118
|
| -------------------- | ----------------- | --------------------------------- |
|
|
259
119
|
| `update:visible` | `boolean` | Emitted when visibility changes |
|
|
260
120
|
| `update:searchQuery` | `string` | Emitted when search query changes |
|
|
121
|
+
| `update:value` | `string` | Emitted when an item is selected |
|
|
261
122
|
| `select` | `CommandItemData` | Emitted when an item is selected |
|
|
262
123
|
|
|
263
|
-
### Components
|
|
264
|
-
|
|
265
|
-
| Component | Description |
|
|
266
|
-
| --------------------- | ---------------------------------------------- |
|
|
267
|
-
| `<Command.Dialog>` | Modal dialog with mask, transition, focus trap |
|
|
268
|
-
| `<Command.Menu>` | Inline command menu (non-modal) |
|
|
269
|
-
| `<Command.Input>` | Search input with keyboard navigation |
|
|
270
|
-
| `<Command.List>` | Scrollable list rendering `groupedItems` |
|
|
271
|
-
| `<Command.Group>` | Group of items with heading |
|
|
272
|
-
| `<Command.Item>` | Single selectable command item |
|
|
273
|
-
| `<Command.Empty>` | Shown when no results match |
|
|
274
|
-
| `<Command.Separator>` | Visual separator |
|
|
275
|
-
| `<Command.Loading>` | Loading indicator |
|
|
276
|
-
|
|
277
124
|
### `CommandItemData`
|
|
278
125
|
|
|
279
126
|
```ts
|
|
@@ -284,107 +131,55 @@ interface CommandItemData {
|
|
|
284
131
|
shortcut?: string
|
|
285
132
|
group?: string
|
|
286
133
|
disabled?: boolean
|
|
287
|
-
|
|
134
|
+
forceMount?: boolean
|
|
135
|
+
icon?: Component | VNode | (() => VNode)
|
|
288
136
|
onSelect?: (item: CommandItemData) => void
|
|
289
137
|
}
|
|
290
138
|
```
|
|
291
139
|
|
|
292
|
-
### `useCommandMenu()`
|
|
140
|
+
### `useCommandMenu()`
|
|
141
|
+
|
|
142
|
+
The composable provides programmatic control outside of `Command.Dialog` / `Command.Menu`.
|
|
293
143
|
|
|
294
144
|
```ts
|
|
295
145
|
import { useCommandMenu } from 'vue-command-kit'
|
|
296
|
-
import type { UseCommandMenuReturn, FilterFn } from 'vue-command-kit'
|
|
297
146
|
|
|
298
|
-
const menu
|
|
147
|
+
const menu = useCommandMenu()
|
|
299
148
|
menu.items.value = [...]
|
|
300
149
|
menu.open()
|
|
301
150
|
menu.close()
|
|
302
151
|
menu.toggle()
|
|
303
152
|
```
|
|
304
153
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
|
308
|
-
|
|
|
309
|
-
|
|
|
310
|
-
| `
|
|
311
|
-
| `
|
|
312
|
-
| `
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
# Start demo dev server
|
|
332
|
-
pnpm dev
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### Scripts
|
|
336
|
-
|
|
337
|
-
| Command | Description |
|
|
338
|
-
| ----------------- | ----------------------------------------- |
|
|
339
|
-
| `pnpm dev` | Start demo dev server at `localhost:5173` |
|
|
340
|
-
| `pnpm build` | Build the library + type declarations |
|
|
341
|
-
| `pnpm typecheck` | Run TypeScript type checking |
|
|
342
|
-
| `pnpm preview` | Preview production build |
|
|
343
|
-
| `pnpm build:demo` | Build demo site to `dist-demo/` |
|
|
344
|
-
|
|
345
|
-
### Project Structure
|
|
346
|
-
|
|
347
|
-
```
|
|
348
|
-
src/
|
|
349
|
-
├── useCommandMenu.ts # core composable (state, filter, shortcuts)
|
|
350
|
-
├── useCommandRoot.ts # shared composable (provide/inject wiring)
|
|
351
|
-
├── types.ts # TypeScript type definitions
|
|
352
|
-
├── injectionKeys.ts # provide/inject keys
|
|
353
|
-
├── utils/
|
|
354
|
-
│ └── injectStrict.ts # type-safe inject helper
|
|
355
|
-
├── CommandMenu.vue # inline command menu
|
|
356
|
-
├── CommandDialog.vue # modal dialog command palette
|
|
357
|
-
├── CommandInput.vue # search input
|
|
358
|
-
├── CommandList.vue # scrollable filtered list
|
|
359
|
-
├── CommandGroup.vue # group with heading
|
|
360
|
-
├── CommandItem.vue # single selectable item
|
|
361
|
-
├── CommandEmpty.vue # empty state
|
|
362
|
-
├── CommandSeparator.vue # visual separator
|
|
363
|
-
├── CommandLoading.vue # loading indicator
|
|
364
|
-
├── index.ts # barrel exports
|
|
365
|
-
└── env.d.ts # type shims
|
|
366
|
-
demo/
|
|
367
|
-
├── App.vue # demo application
|
|
368
|
-
├── main.ts # demo entry
|
|
369
|
-
└── style.css # demo styles
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### Pull Request Process
|
|
373
|
-
|
|
374
|
-
1. Fork the repo and create a feature branch from `main`
|
|
375
|
-
2. Make your changes and run `pnpm typecheck && pnpm build`
|
|
376
|
-
3. Test your changes with `pnpm dev` (demo app)
|
|
377
|
-
4. Update `CHANGELOG.md` with a description of your changes
|
|
378
|
-
5. Submit a PR with a clear description of what and why
|
|
379
|
-
|
|
380
|
-
PRs and issues are welcome!
|
|
381
|
-
|
|
382
|
-
## 📄 License
|
|
383
|
-
|
|
384
|
-
MIT © [yvng-jie](https://github.com/yvng-jie)
|
|
154
|
+
| Return | Type | Description |
|
|
155
|
+
| ----------------- | ------------------------ | --------------------------------------- |
|
|
156
|
+
| `visible` | `Ref<boolean>` | Open state |
|
|
157
|
+
| `searchQuery` | `Ref<string>` | Current search query |
|
|
158
|
+
| `activeIndex` | `Ref<number>` | Currently highlighted item index |
|
|
159
|
+
| `items` | `Ref<CommandItemData[]>` | Raw item list |
|
|
160
|
+
| `filteredItems` | `ComputedRef<...>` | Items after filtering |
|
|
161
|
+
| `groupedItems` | `ComputedRef<...>` | Filtered items grouped by `group` field |
|
|
162
|
+
| `open()` | `() => void` | Open the menu |
|
|
163
|
+
| `close()` | `() => void` | Close and reset search |
|
|
164
|
+
| `toggle()` | `() => void` | Toggle open state |
|
|
165
|
+
| `selectNext()` | `() => void` | Move active index down |
|
|
166
|
+
| `selectPrev()` | `() => void` | Move active index up |
|
|
167
|
+
| `selectCurrent()` | `() => void` | Select currently active item |
|
|
168
|
+
|
|
169
|
+
## Bundle Size
|
|
170
|
+
|
|
171
|
+
| Format | Size |
|
|
172
|
+
| ------- | ------- |
|
|
173
|
+
| ESM | 17.0 kB |
|
|
174
|
+
| UMD | 13.5 kB |
|
|
175
|
+
| Gzipped | 5.0 kB |
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT © [yvng-jie](https://github.com/yvng-jie)
|
|
385
180
|
|
|
386
181
|
---
|
|
387
182
|
|
|
388
183
|
<p align="center">
|
|
389
|
-
<
|
|
184
|
+
<a href="./CONTRIBUTING.md">Contributing Guide</a>
|
|
390
185
|
</p>
|
|
@@ -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 {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface CommandItemData {
|
|
|
15
15
|
disabled?: boolean;
|
|
16
16
|
/** Custom render icon or prefix */
|
|
17
17
|
icon?: Component | VNode | (() => VNode);
|
|
18
|
+
/** Whether to force this item to always appear in filtered results */
|
|
19
|
+
forceMount?: boolean;
|
|
18
20
|
/** Callback when item is selected */
|
|
19
21
|
onSelect?: (item: CommandItemData) => void;
|
|
20
22
|
}
|
|
@@ -34,17 +36,28 @@ export interface CommandRootProps {
|
|
|
34
36
|
closeOnSelect?: boolean;
|
|
35
37
|
/** Whether the menu is loading */
|
|
36
38
|
loading?: boolean;
|
|
39
|
+
/** aria-label for the dialog/menu */
|
|
40
|
+
label?: string;
|
|
41
|
+
/** When false, skip built-in filtering and show all items (custom filter still applies) */
|
|
42
|
+
shouldFilter?: boolean;
|
|
43
|
+
/** When false, keyboard navigation stops at boundaries instead of wrapping around */
|
|
44
|
+
loop?: boolean;
|
|
45
|
+
/** Controlled selected item value (v-model) */
|
|
46
|
+
value?: string;
|
|
37
47
|
}
|
|
38
48
|
/** Emitted events */
|
|
39
49
|
export interface CommandRootEmits {
|
|
40
50
|
(e: 'update:visible', value: boolean): void;
|
|
41
51
|
(e: 'update:searchQuery', value: string): void;
|
|
52
|
+
(e: 'update:value', value: string): void;
|
|
42
53
|
(e: 'select', item: CommandItemData): void;
|
|
43
54
|
}
|
|
44
55
|
/** Props for CommandDialog — extends shared root props with items array */
|
|
45
56
|
export interface CommandDialogProps extends CommandRootProps {
|
|
46
57
|
/** Command items to display */
|
|
47
58
|
items?: CommandItemData[];
|
|
59
|
+
/** Teleport target for the dialog (defaults to 'body') */
|
|
60
|
+
container?: string | HTMLElement;
|
|
48
61
|
}
|
|
49
62
|
/** Group definition */
|
|
50
63
|
export interface CommandGroupData {
|
package/dist/useCommandMenu.d.ts
CHANGED
|
@@ -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(
|
|
29
|
+
export declare function useCommandMenu(optionsOrFilter?: UseCommandMenuOptions | FilterFn, onItemSelect?: (item: CommandItemData) => void): UseCommandMenuReturn;
|
package/dist/useCommandRoot.d.ts
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface HighlightSegment {
|
|
2
|
+
text: string;
|
|
3
|
+
highlighted: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Split text into segments, marking parts that match the query.
|
|
7
|
+
* Matching is case-insensitive.
|
|
8
|
+
*/
|
|
9
|
+
export declare function highlightText(text: string, query: string): HighlightSegment[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Parsed shortcut descriptor */
|
|
2
|
+
export interface ShortcutDescriptor {
|
|
3
|
+
key: string;
|
|
4
|
+
metaKey?: boolean;
|
|
5
|
+
ctrlKey?: boolean;
|
|
6
|
+
altKey?: boolean;
|
|
7
|
+
shiftKey?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Parse a shortcut string like "⌘S" or "⌘⇧F" into a key+modifiers descriptor */
|
|
10
|
+
export declare function parseShortcut(shortcut: string): ShortcutDescriptor | null;
|
|
11
|
+
/** Check if a KeyboardEvent matches a parsed shortcut descriptor */
|
|
12
|
+
export declare function eventMatchesShortcut(e: KeyboardEvent, desc: ShortcutDescriptor | null): boolean;
|