vue-command-kit 0.1.2 → 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 +62 -242
- package/dist/__tests__/highlight.test.d.ts +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/utils/highlight.d.ts +9 -0
- package/dist/utils/shortcut.d.ts +12 -0
- package/dist/vue-cmdk.js +322 -288
- package/dist/vue-cmdk.umd.cjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,33 +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
|
-
## ✨ Features
|
|
14
|
+
## Features
|
|
19
15
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- **🔄 Dynamic items** — Pass items as a reactive array, swap anytime
|
|
28
|
-
- **🛠 Custom filter** — Provide your own filter function
|
|
29
|
-
- **♿ Accessible** — ARIA attributes, focus trap, `aria-live` region
|
|
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`
|
|
30
23
|
|
|
31
|
-
##
|
|
24
|
+
## Installation
|
|
32
25
|
|
|
33
26
|
```bash
|
|
34
27
|
npm install vue-command-kit
|
|
@@ -36,7 +29,7 @@ npm install vue-command-kit
|
|
|
36
29
|
|
|
37
30
|
## Quick Start
|
|
38
31
|
|
|
39
|
-
###
|
|
32
|
+
### With items prop
|
|
40
33
|
|
|
41
34
|
```vue
|
|
42
35
|
<script setup lang="ts">
|
|
@@ -68,7 +61,7 @@ function onSelect(item: CommandItemData) {
|
|
|
68
61
|
</template>
|
|
69
62
|
```
|
|
70
63
|
|
|
71
|
-
###
|
|
64
|
+
### With custom slot content
|
|
72
65
|
|
|
73
66
|
```vue
|
|
74
67
|
<Command.Dialog :visible="visible" @update:visible="visible = $event">
|
|
@@ -85,87 +78,7 @@ function onSelect(item: CommandItemData) {
|
|
|
85
78
|
</Command.Dialog>
|
|
86
79
|
```
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
```vue
|
|
91
|
-
<script setup lang="ts">
|
|
92
|
-
import { Command } from 'vue-command-kit'
|
|
93
|
-
|
|
94
|
-
function myFilter(items: CommandItemData[], query: string) {
|
|
95
|
-
return items.filter((item) => item.label?.includes(query))
|
|
96
|
-
}
|
|
97
|
-
</script>
|
|
98
|
-
|
|
99
|
-
<template>
|
|
100
|
-
<Command.Dialog :filter="myFilter" ... />
|
|
101
|
-
</template>
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### With v-model:searchQuery
|
|
105
|
-
|
|
106
|
-
```vue
|
|
107
|
-
<script setup lang="ts">
|
|
108
|
-
import { ref } from 'vue'
|
|
109
|
-
import { Command } from 'vue-command-kit'
|
|
110
|
-
import type { CommandItemData } from 'vue-command-kit'
|
|
111
|
-
|
|
112
|
-
const visible = ref(false)
|
|
113
|
-
const query = ref('')
|
|
114
|
-
|
|
115
|
-
const items: CommandItemData[] = [
|
|
116
|
-
{ value: 'home', label: 'Home', keywords: ['dashboard'] },
|
|
117
|
-
{ value: 'settings', label: 'Settings' },
|
|
118
|
-
]
|
|
119
|
-
</script>
|
|
120
|
-
|
|
121
|
-
<template>
|
|
122
|
-
<Command.Dialog
|
|
123
|
-
:visible="visible"
|
|
124
|
-
:items="items"
|
|
125
|
-
v-model:searchQuery="query"
|
|
126
|
-
@update:visible="visible = $event"
|
|
127
|
-
/>
|
|
128
|
-
</template>
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Async data + loading
|
|
132
|
-
|
|
133
|
-
```vue
|
|
134
|
-
<script setup lang="ts">
|
|
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
|
-
})
|
|
155
|
-
</script>
|
|
156
|
-
|
|
157
|
-
<template>
|
|
158
|
-
<Command.Dialog
|
|
159
|
-
:visible="visible"
|
|
160
|
-
:items="items"
|
|
161
|
-
:loading="loading"
|
|
162
|
-
@update:visible="visible = $event"
|
|
163
|
-
@select="console.log('selected', $event)"
|
|
164
|
-
/>
|
|
165
|
-
</template>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## 📖 API
|
|
81
|
+
## API
|
|
169
82
|
|
|
170
83
|
### Components
|
|
171
84
|
|
|
@@ -177,26 +90,27 @@ watch(visible, async (v) => {
|
|
|
177
90
|
| `<Command.List>` | Scrollable list rendering grouped items |
|
|
178
91
|
| `<Command.Group>` | Group of items with heading |
|
|
179
92
|
| `<Command.Item>` | Single selectable command item |
|
|
93
|
+
| `<Command.Separator>` | Visual separator with optional `alwaysRender` |
|
|
180
94
|
| `<Command.Empty>` | Shown when no results match |
|
|
181
|
-
| `<Command.Separator>` | Visual separator |
|
|
182
95
|
| `<Command.Loading>` | Loading indicator |
|
|
183
96
|
|
|
184
97
|
### `<Command.Dialog>` Props
|
|
185
98
|
|
|
186
|
-
| Prop | Type
|
|
187
|
-
| --------------- |
|
|
188
|
-
| `visible` | `boolean`
|
|
189
|
-
| `items` | `CommandItemData[]`
|
|
190
|
-
| `searchQuery` | `string`
|
|
191
|
-
| `value` | `string`
|
|
192
|
-
| `placeholder` | `string`
|
|
193
|
-
| `filter` | `FilterFn`
|
|
194
|
-
| `loading` | `boolean`
|
|
195
|
-
| `autoFocus` | `boolean`
|
|
196
|
-
| `closeOnSelect` | `boolean`
|
|
197
|
-
| `shouldFilter` | `boolean`
|
|
198
|
-
| `loop` | `boolean`
|
|
199
|
-
| `label` | `string`
|
|
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 |
|
|
200
114
|
|
|
201
115
|
### `<Command.Dialog>` Events
|
|
202
116
|
|
|
@@ -211,155 +125,61 @@ watch(visible, async (v) => {
|
|
|
211
125
|
|
|
212
126
|
```ts
|
|
213
127
|
interface CommandItemData {
|
|
214
|
-
/** Unique value for this item */
|
|
215
128
|
value: string
|
|
216
|
-
/** Display label (falls back to value) */
|
|
217
129
|
label?: string
|
|
218
|
-
/** Optional keywords for search matching */
|
|
219
130
|
keywords?: string[]
|
|
220
|
-
/** Optional shortcut display (e.g. "⌘S") */
|
|
221
131
|
shortcut?: string
|
|
222
|
-
/** Group this item belongs to */
|
|
223
132
|
group?: string
|
|
224
|
-
/** Disabled state */
|
|
225
133
|
disabled?: boolean
|
|
226
|
-
|
|
134
|
+
forceMount?: boolean
|
|
227
135
|
icon?: Component | VNode | (() => VNode)
|
|
228
|
-
/** Callback when item is selected */
|
|
229
136
|
onSelect?: (item: CommandItemData) => void
|
|
230
137
|
}
|
|
231
138
|
```
|
|
232
139
|
|
|
233
|
-
### `useCommandMenu()`
|
|
140
|
+
### `useCommandMenu()`
|
|
234
141
|
|
|
235
|
-
|
|
142
|
+
The composable provides programmatic control outside of `Command.Dialog` / `Command.Menu`.
|
|
236
143
|
|
|
237
144
|
```ts
|
|
238
145
|
import { useCommandMenu } from 'vue-command-kit'
|
|
239
|
-
import type { UseCommandMenuReturn, FilterFn } from 'vue-command-kit'
|
|
240
146
|
|
|
241
|
-
const menu
|
|
147
|
+
const menu = useCommandMenu()
|
|
242
148
|
menu.items.value = [...]
|
|
243
149
|
menu.open()
|
|
244
150
|
menu.close()
|
|
245
151
|
menu.toggle()
|
|
246
152
|
```
|
|
247
153
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
| `
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
|
269
|
-
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
280
|
-
### Keyboard
|
|
281
|
-
|
|
282
|
-
| Key | Action |
|
|
283
|
-
| -------- | -------------------------------------- |
|
|
284
|
-
| `↑` `↓` | Navigate items (wraps around) |
|
|
285
|
-
| `Enter` | Select current item |
|
|
286
|
-
| `Escape` | Close dialog |
|
|
287
|
-
| `Tab` | Moves focus within dialog (focus trap) |
|
|
288
|
-
|
|
289
|
-
## 🤝 Contributing
|
|
290
|
-
|
|
291
|
-
### Prerequisites
|
|
292
|
-
|
|
293
|
-
- **Node.js** >= 22.13
|
|
294
|
-
- **pnpm** >= 11.x
|
|
295
|
-
|
|
296
|
-
### Setup
|
|
297
|
-
|
|
298
|
-
```bash
|
|
299
|
-
# Clone the repo
|
|
300
|
-
git clone https://github.com/yvng-jie/vue-cmdk.git
|
|
301
|
-
cd vue-cmdk
|
|
302
|
-
|
|
303
|
-
# Install dependencies
|
|
304
|
-
pnpm install
|
|
305
|
-
|
|
306
|
-
# Start demo dev server
|
|
307
|
-
pnpm dev
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Scripts
|
|
311
|
-
|
|
312
|
-
| Command | Description |
|
|
313
|
-
| ----------------- | ----------------------------------------- |
|
|
314
|
-
| `pnpm dev` | Start demo dev server at `localhost:5173` |
|
|
315
|
-
| `pnpm build` | Build the library + type declarations |
|
|
316
|
-
| `pnpm typecheck` | Run TypeScript type checking |
|
|
317
|
-
| `pnpm preview` | Preview production build |
|
|
318
|
-
| `pnpm build:demo` | Build demo site to `dist-demo/` |
|
|
319
|
-
|
|
320
|
-
### Project Structure
|
|
321
|
-
|
|
322
|
-
```
|
|
323
|
-
src/
|
|
324
|
-
├── useCommandMenu.ts # core composable (state, filter, shortcuts)
|
|
325
|
-
├── useCommandRoot.ts # shared composable (provide/inject wiring)
|
|
326
|
-
├── types.ts # TypeScript type definitions
|
|
327
|
-
├── injectionKeys.ts # provide/inject keys
|
|
328
|
-
├── utils/
|
|
329
|
-
│ └── injectStrict.ts # type-safe inject helper
|
|
330
|
-
├── CommandMenu.vue # inline command menu
|
|
331
|
-
├── CommandDialog.vue # modal dialog command palette
|
|
332
|
-
├── CommandInput.vue # search input
|
|
333
|
-
├── CommandList.vue # scrollable filtered list
|
|
334
|
-
├── CommandGroup.vue # group with heading
|
|
335
|
-
├── CommandItem.vue # single selectable item
|
|
336
|
-
├── CommandEmpty.vue # empty state
|
|
337
|
-
├── CommandSeparator.vue # visual separator
|
|
338
|
-
├── CommandLoading.vue # loading indicator
|
|
339
|
-
├── index.ts # barrel exports
|
|
340
|
-
└── env.d.ts # type shims
|
|
341
|
-
demo/
|
|
342
|
-
├── App.vue # demo application
|
|
343
|
-
├── main.ts # demo entry
|
|
344
|
-
└── style.css # demo styles
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### Pull Request Process
|
|
348
|
-
|
|
349
|
-
1. Fork the repo and create a feature branch from `main`
|
|
350
|
-
2. Make your changes and run `pnpm typecheck && pnpm build`
|
|
351
|
-
3. Test your changes with `pnpm dev` (demo app)
|
|
352
|
-
4. Update `CHANGELOG.md` with a description of your changes
|
|
353
|
-
5. Submit a PR with a clear description of what and why
|
|
354
|
-
|
|
355
|
-
PRs and issues are welcome!
|
|
356
|
-
|
|
357
|
-
## 📄 License
|
|
358
|
-
|
|
359
|
-
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)
|
|
360
180
|
|
|
361
181
|
---
|
|
362
182
|
|
|
363
183
|
<p align="center">
|
|
364
|
-
<
|
|
184
|
+
<a href="./CONTRIBUTING.md">Contributing Guide</a>
|
|
365
185
|
</p>
|
|
@@ -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
|
}
|
|
@@ -54,6 +56,8 @@ export interface CommandRootEmits {
|
|
|
54
56
|
export interface CommandDialogProps extends CommandRootProps {
|
|
55
57
|
/** Command items to display */
|
|
56
58
|
items?: CommandItemData[];
|
|
59
|
+
/** Teleport target for the dialog (defaults to 'body') */
|
|
60
|
+
container?: string | HTMLElement;
|
|
57
61
|
}
|
|
58
62
|
/** Group definition */
|
|
59
63
|
export interface CommandGroupData {
|
|
@@ -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;
|