@mdxui/terminal 2.0.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 +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal Command Palette Renderer
|
|
3
|
+
*
|
|
4
|
+
* Multi-tier renderer for command palette navigation component.
|
|
5
|
+
* Supports all 6 tiers: text, markdown, ascii, unicode, ansi, interactive.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { UINode, RenderContext, RenderTier } from '../core/types'
|
|
9
|
+
import { type RouterAdapter } from './utils'
|
|
10
|
+
|
|
11
|
+
// ANSI constants
|
|
12
|
+
const RESET = '\x1b[0m'
|
|
13
|
+
const BOLD = '\x1b[1m'
|
|
14
|
+
const DIM = '\x1b[2m'
|
|
15
|
+
const UNDERLINE = '\x1b[4m'
|
|
16
|
+
const INVERSE = '\x1b[7m'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
interface Command {
|
|
23
|
+
id: string
|
|
24
|
+
label: string
|
|
25
|
+
description?: string
|
|
26
|
+
shortcut?: string
|
|
27
|
+
icon?: string
|
|
28
|
+
category?: string
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
keywords?: string[]
|
|
31
|
+
/** Path for route-based navigation commands */
|
|
32
|
+
path?: string
|
|
33
|
+
/** Action type: 'navigate' triggers router navigation, 'action' uses callback */
|
|
34
|
+
actionType?: 'navigate' | 'action'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface CommandGroup {
|
|
38
|
+
id: string
|
|
39
|
+
label: string
|
|
40
|
+
commands: Command[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface CommandPaletteProps {
|
|
44
|
+
commands: Command[]
|
|
45
|
+
groups?: CommandGroup[]
|
|
46
|
+
searchQuery?: string
|
|
47
|
+
selectedIndex?: number
|
|
48
|
+
placeholder?: string
|
|
49
|
+
maxResults?: number
|
|
50
|
+
showShortcuts?: boolean
|
|
51
|
+
onSelect?: (commandId: string) => void
|
|
52
|
+
onQueryChange?: (query: string) => void
|
|
53
|
+
onClose?: () => void
|
|
54
|
+
/** Router adapter for navigation commands */
|
|
55
|
+
router?: RouterAdapter
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface CommandPaletteState {
|
|
59
|
+
commands: Command[]
|
|
60
|
+
groups?: CommandGroup[]
|
|
61
|
+
searchQuery: string
|
|
62
|
+
selectedIndex: number
|
|
63
|
+
wrapNavigation?: boolean
|
|
64
|
+
onSelect?: (commandId: string) => void
|
|
65
|
+
onQueryChange?: (query: string) => void
|
|
66
|
+
onClose?: () => void
|
|
67
|
+
/** Router adapter for navigation commands */
|
|
68
|
+
router?: RouterAdapter
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Main Render Function
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
export function renderCommandPalette(node: UINode, ctx: RenderContext): string {
|
|
76
|
+
const props = node.props as unknown as CommandPaletteProps
|
|
77
|
+
const {
|
|
78
|
+
searchQuery = '',
|
|
79
|
+
selectedIndex = 0,
|
|
80
|
+
placeholder = 'Search commands...',
|
|
81
|
+
maxResults = 10,
|
|
82
|
+
showShortcuts = true,
|
|
83
|
+
} = props
|
|
84
|
+
|
|
85
|
+
// Extract commands from node or props
|
|
86
|
+
const { commands, groups } = extractCommandsAndGroups(node, props)
|
|
87
|
+
|
|
88
|
+
const tier = ctx.tier
|
|
89
|
+
const maxWidth = ctx.width || 80
|
|
90
|
+
const lines: string[] = []
|
|
91
|
+
|
|
92
|
+
// Render the input box
|
|
93
|
+
lines.push(renderInputBox(searchQuery, placeholder, tier, ctx, maxWidth))
|
|
94
|
+
|
|
95
|
+
// Filter commands based on search
|
|
96
|
+
let filteredCommands = commands
|
|
97
|
+
let filteredGroups = groups
|
|
98
|
+
|
|
99
|
+
if (searchQuery) {
|
|
100
|
+
if (groups && groups.length > 0) {
|
|
101
|
+
filteredGroups = filterGroupedCommands(groups, searchQuery)
|
|
102
|
+
filteredCommands = []
|
|
103
|
+
} else {
|
|
104
|
+
filteredCommands = filterCommands(commands, searchQuery, { maxResults })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Limit results
|
|
109
|
+
if (!groups && filteredCommands.length > maxResults) {
|
|
110
|
+
filteredCommands = filteredCommands.slice(0, maxResults)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check if no results
|
|
114
|
+
const hasResults = filteredGroups?.some(g => g.commands.length > 0) || filteredCommands.length > 0
|
|
115
|
+
|
|
116
|
+
if (!hasResults && searchQuery) {
|
|
117
|
+
lines.push(renderNoResults(tier, ctx))
|
|
118
|
+
} else if (filteredGroups && filteredGroups.length > 0) {
|
|
119
|
+
// Render grouped commands
|
|
120
|
+
let globalIndex = 0
|
|
121
|
+
for (const group of filteredGroups) {
|
|
122
|
+
if (group.commands.length === 0) continue
|
|
123
|
+
lines.push(renderGroupHeader(group.label, tier, ctx))
|
|
124
|
+
for (const cmd of group.commands) {
|
|
125
|
+
const isSelected = globalIndex === selectedIndex
|
|
126
|
+
lines.push(renderCommandItem(cmd, isSelected, showShortcuts, tier, ctx, maxWidth))
|
|
127
|
+
globalIndex++
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
// Render flat command list
|
|
132
|
+
for (let i = 0; i < filteredCommands.length; i++) {
|
|
133
|
+
const cmd = filteredCommands[i]
|
|
134
|
+
const isSelected = i === selectedIndex
|
|
135
|
+
lines.push(renderCommandItem(cmd, isSelected, showShortcuts, tier, ctx, maxWidth))
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Wrap in box for better tiers
|
|
140
|
+
return wrapInBox(lines, tier, ctx, maxWidth)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// Command Extraction
|
|
145
|
+
// ============================================================================
|
|
146
|
+
|
|
147
|
+
function extractCommandsAndGroups(node: UINode, props: CommandPaletteProps): {
|
|
148
|
+
commands: Command[]
|
|
149
|
+
groups?: CommandGroup[]
|
|
150
|
+
} {
|
|
151
|
+
if (props.commands && props.commands.length > 0) {
|
|
152
|
+
return { commands: props.commands, groups: props.groups }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Extract from children
|
|
156
|
+
const commands: Command[] = []
|
|
157
|
+
const groups: CommandGroup[] = []
|
|
158
|
+
const rawChildren = node.children
|
|
159
|
+
const children: UINode[] = Array.isArray(rawChildren) ? rawChildren : []
|
|
160
|
+
|
|
161
|
+
for (const child of children) {
|
|
162
|
+
if (child.type === 'command-palette-list') {
|
|
163
|
+
const rawListChildren = child.children
|
|
164
|
+
const listChildren: UINode[] = Array.isArray(rawListChildren) ? rawListChildren : []
|
|
165
|
+
for (const item of listChildren) {
|
|
166
|
+
if (item.type === 'command-group') {
|
|
167
|
+
const groupCommands: Command[] = []
|
|
168
|
+
const rawGroupChildren = item.children
|
|
169
|
+
const groupChildren: UINode[] = Array.isArray(rawGroupChildren) ? rawGroupChildren : []
|
|
170
|
+
for (const cmdItem of groupChildren) {
|
|
171
|
+
if (cmdItem.type === 'command-item') {
|
|
172
|
+
groupCommands.push(extractCommand(cmdItem))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const itemProps = item.props || {}
|
|
176
|
+
groups.push({
|
|
177
|
+
id: itemProps.id as string,
|
|
178
|
+
label: itemProps.label as string,
|
|
179
|
+
commands: groupCommands,
|
|
180
|
+
})
|
|
181
|
+
} else if (item.type === 'command-item') {
|
|
182
|
+
commands.push(extractCommand(item))
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { commands, groups: groups.length > 0 ? groups : undefined }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function extractCommand(node: UINode): Command {
|
|
192
|
+
const props = node.props || {}
|
|
193
|
+
return {
|
|
194
|
+
id: props.id as string,
|
|
195
|
+
label: props.label as string,
|
|
196
|
+
description: props.description as string | undefined,
|
|
197
|
+
shortcut: props.shortcut as string | undefined,
|
|
198
|
+
icon: props.icon as string | undefined,
|
|
199
|
+
category: props.category as string | undefined,
|
|
200
|
+
disabled: props.disabled as boolean | undefined,
|
|
201
|
+
keywords: props.keywords as string[] | undefined,
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Search/Filter Functions
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
export function filterCommands(
|
|
210
|
+
commands: Command[],
|
|
211
|
+
query: string,
|
|
212
|
+
options?: { maxResults?: number; recentIds?: string[] }
|
|
213
|
+
): Command[] {
|
|
214
|
+
const { maxResults, recentIds } = options || {}
|
|
215
|
+
const lowerQuery = query.toLowerCase()
|
|
216
|
+
|
|
217
|
+
// Score each command
|
|
218
|
+
const scored = commands.map((cmd, originalIndex) => {
|
|
219
|
+
let score = 0
|
|
220
|
+
let recentRank = -1
|
|
221
|
+
|
|
222
|
+
// Boost recent commands and track their order
|
|
223
|
+
if (recentIds?.includes(cmd.id)) {
|
|
224
|
+
score += 100
|
|
225
|
+
recentRank = recentIds.indexOf(cmd.id)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// For empty query, include all commands (but give base score to non-recent)
|
|
229
|
+
if (!query) {
|
|
230
|
+
if (recentRank < 0) {
|
|
231
|
+
score += 1 // Base score so they're included
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
// Match label
|
|
235
|
+
if (cmd.label.toLowerCase().includes(lowerQuery)) {
|
|
236
|
+
score += 50
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Fuzzy match label
|
|
240
|
+
if (fuzzyMatch(cmd.label, query)) {
|
|
241
|
+
score += 30
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Match keywords
|
|
245
|
+
if (cmd.keywords?.some(k => k.toLowerCase().includes(lowerQuery))) {
|
|
246
|
+
score += 40
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Match description
|
|
250
|
+
if (cmd.description?.toLowerCase().includes(lowerQuery)) {
|
|
251
|
+
score += 20
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { cmd, score, recentRank, originalIndex }
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Filter and sort
|
|
259
|
+
let results = scored
|
|
260
|
+
.filter(s => s.score > 0)
|
|
261
|
+
.sort((a, b) => {
|
|
262
|
+
// Primary sort by score
|
|
263
|
+
if (b.score !== a.score) return b.score - a.score
|
|
264
|
+
// Secondary sort by recent rank (lower is better, -1 means not recent)
|
|
265
|
+
if (a.recentRank >= 0 && b.recentRank >= 0) return a.recentRank - b.recentRank
|
|
266
|
+
if (a.recentRank >= 0) return -1
|
|
267
|
+
if (b.recentRank >= 0) return 1
|
|
268
|
+
// Tertiary sort by original order
|
|
269
|
+
return a.originalIndex - b.originalIndex
|
|
270
|
+
})
|
|
271
|
+
.map(s => s.cmd)
|
|
272
|
+
|
|
273
|
+
// Apply max results
|
|
274
|
+
if (maxResults && results.length > maxResults) {
|
|
275
|
+
results = results.slice(0, maxResults)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return results
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function filterGroupedCommands(groups: CommandGroup[], query: string): CommandGroup[] {
|
|
282
|
+
return groups
|
|
283
|
+
.map(group => ({
|
|
284
|
+
...group,
|
|
285
|
+
commands: filterCommands(group.commands, query),
|
|
286
|
+
}))
|
|
287
|
+
.filter(group => group.commands.length > 0)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function fuzzyMatch(text: string, pattern: string): boolean {
|
|
291
|
+
const textLower = text.toLowerCase()
|
|
292
|
+
const patternLower = pattern.toLowerCase()
|
|
293
|
+
|
|
294
|
+
let textIndex = 0
|
|
295
|
+
let patternIndex = 0
|
|
296
|
+
|
|
297
|
+
while (textIndex < textLower.length && patternIndex < patternLower.length) {
|
|
298
|
+
if (textLower[textIndex] === patternLower[patternIndex]) {
|
|
299
|
+
patternIndex++
|
|
300
|
+
}
|
|
301
|
+
textIndex++
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return patternIndex === patternLower.length
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// Render Components
|
|
309
|
+
// ============================================================================
|
|
310
|
+
|
|
311
|
+
function renderInputBox(
|
|
312
|
+
query: string,
|
|
313
|
+
placeholder: string,
|
|
314
|
+
tier: RenderTier,
|
|
315
|
+
ctx: RenderContext,
|
|
316
|
+
maxWidth: number
|
|
317
|
+
): string {
|
|
318
|
+
const displayText = query || placeholder
|
|
319
|
+
const isPlaceholder = !query
|
|
320
|
+
const cursorChar = tier === 'interactive' ? '\u2588' : ''
|
|
321
|
+
|
|
322
|
+
let content = displayText
|
|
323
|
+
if (tier === 'interactive') {
|
|
324
|
+
content = query + cursorChar
|
|
325
|
+
if (!query) {
|
|
326
|
+
content = ctx.theme.muted + placeholder + RESET
|
|
327
|
+
}
|
|
328
|
+
} else if (isPlaceholder) {
|
|
329
|
+
if (tier === 'ansi') {
|
|
330
|
+
content = ctx.theme.muted + placeholder + RESET
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const prefix = tier === 'unicode' || tier === 'ansi' || tier === 'interactive' ? '\u2315 ' : '> '
|
|
335
|
+
return prefix + content
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function renderGroupHeader(label: string, tier: RenderTier, ctx: RenderContext): string {
|
|
339
|
+
if (tier === 'ansi' || tier === 'interactive') {
|
|
340
|
+
return ctx.theme.muted + label + RESET
|
|
341
|
+
}
|
|
342
|
+
if (tier === 'markdown') {
|
|
343
|
+
return `### ${label}`
|
|
344
|
+
}
|
|
345
|
+
return label
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function renderNoResults(tier: RenderTier, ctx: RenderContext): string {
|
|
349
|
+
const message = 'No results found'
|
|
350
|
+
if (tier === 'ansi' || tier === 'interactive') {
|
|
351
|
+
return ctx.theme.muted + message + RESET
|
|
352
|
+
}
|
|
353
|
+
return message
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function renderCommandItem(
|
|
357
|
+
cmd: Command,
|
|
358
|
+
isSelected: boolean,
|
|
359
|
+
showShortcuts: boolean,
|
|
360
|
+
tier: RenderTier,
|
|
361
|
+
ctx: RenderContext,
|
|
362
|
+
maxWidth: number
|
|
363
|
+
): string {
|
|
364
|
+
const indent = ' '
|
|
365
|
+
let prefix = isSelected ? '> ' : ' '
|
|
366
|
+
|
|
367
|
+
// Build label with icon
|
|
368
|
+
let label = cmd.label
|
|
369
|
+
if (cmd.icon && (tier === 'unicode' || tier === 'ansi' || tier === 'interactive')) {
|
|
370
|
+
label = cmd.icon + ' ' + label
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Add description
|
|
374
|
+
let description = ''
|
|
375
|
+
if (cmd.description) {
|
|
376
|
+
if (tier === 'ansi' || tier === 'interactive') {
|
|
377
|
+
description = ' ' + ctx.theme.muted + cmd.description + RESET
|
|
378
|
+
} else {
|
|
379
|
+
description = ' - ' + cmd.description
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Add shortcut
|
|
384
|
+
let shortcut = ''
|
|
385
|
+
if (showShortcuts && cmd.shortcut) {
|
|
386
|
+
if (tier === 'markdown') {
|
|
387
|
+
shortcut = ' `' + cmd.shortcut + '`'
|
|
388
|
+
} else if (tier === 'ansi' || tier === 'interactive') {
|
|
389
|
+
shortcut = ' ' + ctx.theme.muted + cmd.shortcut + RESET
|
|
390
|
+
} else {
|
|
391
|
+
shortcut = ' ' + cmd.shortcut
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Build the line
|
|
396
|
+
let line = indent + prefix + label + description + shortcut
|
|
397
|
+
|
|
398
|
+
// Apply styling
|
|
399
|
+
if (tier === 'text') {
|
|
400
|
+
if (isSelected) {
|
|
401
|
+
line = indent + '> ' + label + description + shortcut
|
|
402
|
+
}
|
|
403
|
+
if (cmd.disabled) {
|
|
404
|
+
line += ' (disabled)'
|
|
405
|
+
}
|
|
406
|
+
} else if (tier === 'ansi' || tier === 'interactive') {
|
|
407
|
+
if (cmd.disabled) {
|
|
408
|
+
return indent + ' ' + ctx.theme.muted + label + RESET
|
|
409
|
+
}
|
|
410
|
+
if (isSelected) {
|
|
411
|
+
return INVERSE + indent + prefix + label + RESET + description + shortcut
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return line
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function wrapInBox(lines: string[], tier: RenderTier, ctx: RenderContext, maxWidth: number): string {
|
|
419
|
+
if (tier === 'text' || tier === 'markdown') {
|
|
420
|
+
return lines.join('\n')
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Add box border
|
|
424
|
+
const horizontal = tier === 'ascii' ? '-' : '\u2500'
|
|
425
|
+
const vertical = tier === 'ascii' ? '|' : '\u2502'
|
|
426
|
+
const topLeft = tier === 'ascii' ? '+' : '\u250C'
|
|
427
|
+
const topRight = tier === 'ascii' ? '+' : '\u2510'
|
|
428
|
+
const bottomLeft = tier === 'ascii' ? '+' : '\u2514'
|
|
429
|
+
const bottomRight = tier === 'ascii' ? '+' : '\u2518'
|
|
430
|
+
|
|
431
|
+
// Calculate content width - account for border (4 chars: "| " + " |")
|
|
432
|
+
const maxContentWidth = maxWidth - 4
|
|
433
|
+
const contentWidth = Math.min(maxContentWidth, Math.max(...lines.map(l => stripAnsi(l).length)))
|
|
434
|
+
// Ensure minimum width of 20, but not exceeding maxWidth - 4
|
|
435
|
+
const innerWidth = Math.min(Math.max(contentWidth, 20), maxContentWidth)
|
|
436
|
+
|
|
437
|
+
const boxLines: string[] = []
|
|
438
|
+
boxLines.push(topLeft + horizontal.repeat(innerWidth + 2) + topRight)
|
|
439
|
+
|
|
440
|
+
for (const line of lines) {
|
|
441
|
+
let truncatedLine = line
|
|
442
|
+
const stripped = stripAnsi(line)
|
|
443
|
+
|
|
444
|
+
// Truncate line if it exceeds inner width
|
|
445
|
+
if (stripped.length > innerWidth) {
|
|
446
|
+
truncatedLine = truncateLineToWidth(line, innerWidth, tier)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const truncatedStripped = stripAnsi(truncatedLine)
|
|
450
|
+
const padding = innerWidth - truncatedStripped.length
|
|
451
|
+
boxLines.push(vertical + ' ' + truncatedLine + ' '.repeat(Math.max(0, padding)) + ' ' + vertical)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
boxLines.push(bottomLeft + horizontal.repeat(innerWidth + 2) + bottomRight)
|
|
455
|
+
|
|
456
|
+
return boxLines.join('\n')
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function truncateLineToWidth(line: string, maxWidth: number, tier: RenderTier): string {
|
|
460
|
+
const stripped = stripAnsi(line)
|
|
461
|
+
if (stripped.length <= maxWidth) return line
|
|
462
|
+
|
|
463
|
+
const ellipsis = tier === 'unicode' || tier === 'ansi' || tier === 'interactive' ? '\u2026' : '...'
|
|
464
|
+
const ellipsisLen = tier === 'unicode' || tier === 'ansi' || tier === 'interactive' ? 1 : 3
|
|
465
|
+
const RESET = '\x1b[0m'
|
|
466
|
+
|
|
467
|
+
let visibleLen = 0
|
|
468
|
+
let result = ''
|
|
469
|
+
let inEscape = false
|
|
470
|
+
|
|
471
|
+
for (let i = 0; i < line.length; i++) {
|
|
472
|
+
const char = line[i]
|
|
473
|
+
|
|
474
|
+
if (char === '\x1b') {
|
|
475
|
+
inEscape = true
|
|
476
|
+
result += char
|
|
477
|
+
continue
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (inEscape) {
|
|
481
|
+
result += char
|
|
482
|
+
if (char === 'm') {
|
|
483
|
+
inEscape = false
|
|
484
|
+
}
|
|
485
|
+
continue
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (visibleLen >= maxWidth - ellipsisLen) {
|
|
489
|
+
result += ellipsis + RESET
|
|
490
|
+
break
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
result += char
|
|
494
|
+
visibleLen++
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return result
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ============================================================================
|
|
501
|
+
// Keyboard Bindings
|
|
502
|
+
// ============================================================================
|
|
503
|
+
|
|
504
|
+
export function getCommandPaletteKeyBindings(): Record<string, string> {
|
|
505
|
+
return {
|
|
506
|
+
up: 'select-prev',
|
|
507
|
+
down: 'select-next',
|
|
508
|
+
k: 'select-prev',
|
|
509
|
+
j: 'select-next',
|
|
510
|
+
enter: 'execute',
|
|
511
|
+
escape: 'close',
|
|
512
|
+
'ctrl+n': 'select-next',
|
|
513
|
+
'ctrl+p': 'select-prev',
|
|
514
|
+
'ctrl+u': 'clear-query',
|
|
515
|
+
home: 'select-first',
|
|
516
|
+
end: 'select-last',
|
|
517
|
+
tab: 'select-next',
|
|
518
|
+
'shift+tab': 'select-prev',
|
|
519
|
+
backspace: 'delete-char',
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ============================================================================
|
|
524
|
+
// State Management
|
|
525
|
+
// ============================================================================
|
|
526
|
+
|
|
527
|
+
export function createCommandPaletteState(config: {
|
|
528
|
+
commands: Command[]
|
|
529
|
+
groups?: CommandGroup[]
|
|
530
|
+
searchQuery?: string
|
|
531
|
+
selectedIndex?: number
|
|
532
|
+
wrapNavigation?: boolean
|
|
533
|
+
onSelect?: (commandId: string) => void
|
|
534
|
+
onQueryChange?: (query: string) => void
|
|
535
|
+
onClose?: () => void
|
|
536
|
+
/** Router adapter for navigation commands */
|
|
537
|
+
router?: RouterAdapter
|
|
538
|
+
}): CommandPaletteState {
|
|
539
|
+
return {
|
|
540
|
+
commands: config.commands,
|
|
541
|
+
groups: config.groups,
|
|
542
|
+
searchQuery: config.searchQuery || '',
|
|
543
|
+
selectedIndex: config.selectedIndex || 0,
|
|
544
|
+
wrapNavigation: config.wrapNavigation,
|
|
545
|
+
onSelect: config.onSelect,
|
|
546
|
+
onQueryChange: config.onQueryChange,
|
|
547
|
+
onClose: config.onClose,
|
|
548
|
+
router: config.router,
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function handleCommandPaletteKey(state: CommandPaletteState, key: string): CommandPaletteState {
|
|
553
|
+
const { commands, groups, searchQuery, selectedIndex, wrapNavigation, onSelect, onQueryChange, onClose, router } =
|
|
554
|
+
state
|
|
555
|
+
|
|
556
|
+
// Helper to execute a command (with router support)
|
|
557
|
+
const executeCommand = (cmd: Command): void => {
|
|
558
|
+
if (cmd.disabled) return
|
|
559
|
+
|
|
560
|
+
// Check if this is a navigation command
|
|
561
|
+
const isNavigationCommand = cmd.actionType === 'navigate' || (cmd.path && !cmd.actionType)
|
|
562
|
+
|
|
563
|
+
if (isNavigationCommand && cmd.path && router) {
|
|
564
|
+
// Use router for navigation commands
|
|
565
|
+
router.navigate(cmd.path)
|
|
566
|
+
} else if (onSelect) {
|
|
567
|
+
// Use callback for action commands or when no router available
|
|
568
|
+
onSelect(cmd.id)
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Get all commands (maintaining original order and indices)
|
|
573
|
+
let allCommands: Command[]
|
|
574
|
+
if (groups && groups.length > 0) {
|
|
575
|
+
allCommands = groups.flatMap(g => g.commands)
|
|
576
|
+
} else {
|
|
577
|
+
allCommands = commands
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Filter by search if needed
|
|
581
|
+
if (searchQuery) {
|
|
582
|
+
allCommands = filterCommands(allCommands, searchQuery)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const maxIndex = allCommands.length - 1
|
|
586
|
+
|
|
587
|
+
// Helper to find next non-disabled index
|
|
588
|
+
function findNextEnabled(fromIndex: number, direction: 1 | -1): number {
|
|
589
|
+
let newIndex = fromIndex + direction
|
|
590
|
+
|
|
591
|
+
// Keep moving in direction until we find a non-disabled command
|
|
592
|
+
while (newIndex >= 0 && newIndex <= maxIndex && allCommands[newIndex]?.disabled) {
|
|
593
|
+
newIndex += direction
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Handle boundary cases
|
|
597
|
+
if (newIndex < 0) {
|
|
598
|
+
if (wrapNavigation) {
|
|
599
|
+
// Wrap to end and search backwards for enabled
|
|
600
|
+
newIndex = maxIndex
|
|
601
|
+
while (newIndex > fromIndex && allCommands[newIndex]?.disabled) {
|
|
602
|
+
newIndex--
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
newIndex = 0
|
|
606
|
+
// Find first non-disabled from start
|
|
607
|
+
while (newIndex <= maxIndex && allCommands[newIndex]?.disabled) {
|
|
608
|
+
newIndex++
|
|
609
|
+
}
|
|
610
|
+
if (newIndex > maxIndex) newIndex = fromIndex
|
|
611
|
+
}
|
|
612
|
+
} else if (newIndex > maxIndex) {
|
|
613
|
+
if (wrapNavigation) {
|
|
614
|
+
// Wrap to start and search forward for enabled
|
|
615
|
+
newIndex = 0
|
|
616
|
+
while (newIndex < fromIndex && allCommands[newIndex]?.disabled) {
|
|
617
|
+
newIndex++
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
newIndex = maxIndex
|
|
621
|
+
// Find last non-disabled from end
|
|
622
|
+
while (newIndex >= 0 && allCommands[newIndex]?.disabled) {
|
|
623
|
+
newIndex--
|
|
624
|
+
}
|
|
625
|
+
if (newIndex < 0) newIndex = fromIndex
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return newIndex
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
switch (key) {
|
|
633
|
+
case 'down':
|
|
634
|
+
case 'j':
|
|
635
|
+
case 'ctrl+n':
|
|
636
|
+
case 'tab': {
|
|
637
|
+
const newIndex = findNextEnabled(selectedIndex, 1)
|
|
638
|
+
return { ...state, selectedIndex: newIndex }
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
case 'up':
|
|
642
|
+
case 'k':
|
|
643
|
+
case 'ctrl+p':
|
|
644
|
+
case 'shift+tab': {
|
|
645
|
+
const newIndex = findNextEnabled(selectedIndex, -1)
|
|
646
|
+
return { ...state, selectedIndex: newIndex }
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
case 'home': {
|
|
650
|
+
// Find first non-disabled
|
|
651
|
+
let newIndex = 0
|
|
652
|
+
while (newIndex <= maxIndex && allCommands[newIndex]?.disabled) {
|
|
653
|
+
newIndex++
|
|
654
|
+
}
|
|
655
|
+
return { ...state, selectedIndex: newIndex > maxIndex ? 0 : newIndex }
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
case 'end': {
|
|
659
|
+
// Find last non-disabled
|
|
660
|
+
let newIndex = maxIndex
|
|
661
|
+
while (newIndex >= 0 && allCommands[newIndex]?.disabled) {
|
|
662
|
+
newIndex--
|
|
663
|
+
}
|
|
664
|
+
return { ...state, selectedIndex: newIndex < 0 ? 0 : newIndex }
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
case 'enter': {
|
|
668
|
+
const selectedCmd = allCommands[selectedIndex]
|
|
669
|
+
if (selectedCmd && !selectedCmd.disabled) {
|
|
670
|
+
executeCommand(selectedCmd)
|
|
671
|
+
}
|
|
672
|
+
return state
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
case 'escape': {
|
|
676
|
+
if (onClose) {
|
|
677
|
+
onClose()
|
|
678
|
+
}
|
|
679
|
+
return state
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
case 'backspace': {
|
|
683
|
+
if (searchQuery && onQueryChange) {
|
|
684
|
+
onQueryChange(searchQuery.slice(0, -1))
|
|
685
|
+
}
|
|
686
|
+
return { ...state, searchQuery: searchQuery.slice(0, -1), selectedIndex: 0 }
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
case 'ctrl+u': {
|
|
690
|
+
if (onQueryChange) {
|
|
691
|
+
onQueryChange('')
|
|
692
|
+
}
|
|
693
|
+
return { ...state, searchQuery: '', selectedIndex: 0 }
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
default: {
|
|
697
|
+
// Typing a character
|
|
698
|
+
if (key.length === 1 && /[a-zA-Z0-9 ]/.test(key)) {
|
|
699
|
+
const newQuery = searchQuery + key
|
|
700
|
+
if (onQueryChange) {
|
|
701
|
+
onQueryChange(newQuery)
|
|
702
|
+
}
|
|
703
|
+
return { ...state, searchQuery: newQuery, selectedIndex: 0 }
|
|
704
|
+
}
|
|
705
|
+
return state
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// ============================================================================
|
|
711
|
+
// Utility Functions
|
|
712
|
+
// ============================================================================
|
|
713
|
+
|
|
714
|
+
function stripAnsi(str: string): string {
|
|
715
|
+
return str.replace(/\x1b\[[\d;]*m/g, '')
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ============================================================================
|
|
719
|
+
// Router Integration Functions
|
|
720
|
+
// ============================================================================
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Creates navigation commands from route definitions.
|
|
724
|
+
* Useful for generating commands from a list of routes/pages.
|
|
725
|
+
*
|
|
726
|
+
* @param routes - Array of route definitions
|
|
727
|
+
* @returns Array of Command objects with navigation action type
|
|
728
|
+
*/
|
|
729
|
+
export function navigationCommandsFromRoutes(
|
|
730
|
+
routes: Array<{
|
|
731
|
+
path: string
|
|
732
|
+
label: string
|
|
733
|
+
description?: string
|
|
734
|
+
icon?: string
|
|
735
|
+
shortcut?: string
|
|
736
|
+
keywords?: string[]
|
|
737
|
+
category?: string
|
|
738
|
+
}>
|
|
739
|
+
): Command[] {
|
|
740
|
+
return routes.map((route) => ({
|
|
741
|
+
id: `nav:${route.path}`,
|
|
742
|
+
label: route.label,
|
|
743
|
+
description: route.description,
|
|
744
|
+
icon: route.icon,
|
|
745
|
+
shortcut: route.shortcut,
|
|
746
|
+
keywords: route.keywords,
|
|
747
|
+
category: route.category || 'Navigation',
|
|
748
|
+
path: route.path,
|
|
749
|
+
actionType: 'navigate' as const,
|
|
750
|
+
}))
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Creates a command execution handler that integrates with a router adapter.
|
|
755
|
+
* Navigation commands will be handled by the router, action commands by the callback.
|
|
756
|
+
*
|
|
757
|
+
* @param commands - Array of commands (needed to look up command details by ID)
|
|
758
|
+
* @param router - Router adapter instance
|
|
759
|
+
* @param onAction - Callback for non-navigation actions
|
|
760
|
+
* @returns Command selection handler function
|
|
761
|
+
*/
|
|
762
|
+
export function createRouterCommandHandler(
|
|
763
|
+
commands: Command[],
|
|
764
|
+
router: RouterAdapter,
|
|
765
|
+
onAction?: (commandId: string, command: Command) => void
|
|
766
|
+
): (commandId: string) => void {
|
|
767
|
+
return (commandId: string) => {
|
|
768
|
+
const command = commands.find((c) => c.id === commandId)
|
|
769
|
+
if (!command) return
|
|
770
|
+
|
|
771
|
+
// Check if this is a navigation command
|
|
772
|
+
const isNavigationCommand = command.actionType === 'navigate' || (command.path && !command.actionType)
|
|
773
|
+
|
|
774
|
+
if (isNavigationCommand && command.path) {
|
|
775
|
+
router.navigate(command.path)
|
|
776
|
+
} else if (onAction) {
|
|
777
|
+
onAction(commandId, command)
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Filters commands to only include navigation commands.
|
|
784
|
+
* Useful for creating a page navigation palette.
|
|
785
|
+
*
|
|
786
|
+
* @param commands - Array of commands
|
|
787
|
+
* @returns Array of navigation-only commands
|
|
788
|
+
*/
|
|
789
|
+
export function getNavigationCommands(commands: Command[]): Command[] {
|
|
790
|
+
return commands.filter((cmd) => cmd.actionType === 'navigate' || cmd.path)
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Filters commands to only include action commands.
|
|
795
|
+
* Useful for creating an actions palette.
|
|
796
|
+
*
|
|
797
|
+
* @param commands - Array of commands
|
|
798
|
+
* @returns Array of action-only commands
|
|
799
|
+
*/
|
|
800
|
+
export function getActionCommands(commands: Command[]): Command[] {
|
|
801
|
+
return commands.filter((cmd) => cmd.actionType === 'action' || (!cmd.path && !cmd.actionType))
|
|
802
|
+
}
|