ajo-ui 0.1.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/LICENSE +15 -0
- package/README.md +126 -0
- package/dist/accordion.js +130 -0
- package/dist/avatar.js +54 -0
- package/dist/calendar.js +2 -0
- package/dist/carousel.js +239 -0
- package/dist/chart.js +790 -0
- package/dist/checkbox-group.js +70 -0
- package/dist/checkbox.js +77 -0
- package/dist/chunks/bar-CVafh6C1.js +99 -0
- package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
- package/dist/chunks/collection-DtRB63U4.js +111 -0
- package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
- package/dist/chunks/menu-B45IyHHC.js +704 -0
- package/dist/chunks/native-BJdhd9XJ.js +20 -0
- package/dist/chunks/popup-C8Bb3l_g.js +459 -0
- package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
- package/dist/chunks/position-D6_i_SRn.js +434 -0
- package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
- package/dist/collapsible.js +106 -0
- package/dist/command.js +263 -0
- package/dist/context-menu.js +112 -0
- package/dist/data-table.js +5 -0
- package/dist/dialog.js +207 -0
- package/dist/direction.js +22 -0
- package/dist/drawer.js +139 -0
- package/dist/field.js +26 -0
- package/dist/index.js +38 -0
- package/dist/input-date.js +994 -0
- package/dist/input-group.js +52 -0
- package/dist/input-otp.js +179 -0
- package/dist/menu.js +2 -0
- package/dist/menubar.js +236 -0
- package/dist/message-scroller.js +446 -0
- package/dist/navigation-menu.js +330 -0
- package/dist/popover.js +307 -0
- package/dist/progress.js +39 -0
- package/dist/radio-group.js +107 -0
- package/dist/resizable.js +172 -0
- package/dist/select.js +961 -0
- package/dist/sidebar.js +343 -0
- package/dist/slider.js +259 -0
- package/dist/switch.js +53 -0
- package/dist/tabs.js +182 -0
- package/dist/toast.js +492 -0
- package/dist/toggle-group.js +111 -0
- package/dist/toggle.js +52 -0
- package/dist/toolbar.js +127 -0
- package/dist/tooltip.js +196 -0
- package/dist/utils.js +104 -0
- package/dist/virtual-list.js +2 -0
- package/package.json +250 -0
- package/src/accordion.tsx +261 -0
- package/src/availability.ts +261 -0
- package/src/avatar.tsx +99 -0
- package/src/bar.ts +156 -0
- package/src/calendar.tsx +1441 -0
- package/src/carousel.tsx +424 -0
- package/src/chart.tsx +1194 -0
- package/src/checkbox-group.tsx +132 -0
- package/src/checkbox.tsx +130 -0
- package/src/collapsible.tsx +188 -0
- package/src/collection.ts +154 -0
- package/src/command.tsx +511 -0
- package/src/context-menu.tsx +233 -0
- package/src/data-table-contract.ts +143 -0
- package/src/data-table-model.ts +760 -0
- package/src/data-table.tsx +475 -0
- package/src/dialog.tsx +393 -0
- package/src/direction.tsx +45 -0
- package/src/drawer.tsx +251 -0
- package/src/field.tsx +61 -0
- package/src/index.ts +37 -0
- package/src/input-date.tsx +1539 -0
- package/src/input-group.tsx +142 -0
- package/src/input-otp.tsx +324 -0
- package/src/menu-cluster.ts +124 -0
- package/src/menu.tsx +1095 -0
- package/src/menubar.tsx +459 -0
- package/src/message-scroller.tsx +732 -0
- package/src/native.ts +26 -0
- package/src/navigation-menu.tsx +578 -0
- package/src/popover.tsx +519 -0
- package/src/popup-surface.tsx +31 -0
- package/src/popup.ts +569 -0
- package/src/position.ts +523 -0
- package/src/progress.tsx +70 -0
- package/src/radio-group.tsx +186 -0
- package/src/resizable.tsx +310 -0
- package/src/segments.ts +922 -0
- package/src/select.tsx +1501 -0
- package/src/sidebar.tsx +683 -0
- package/src/slider.tsx +424 -0
- package/src/switch.tsx +104 -0
- package/src/tabs.tsx +314 -0
- package/src/toast.tsx +923 -0
- package/src/toggle-group.tsx +249 -0
- package/src/toggle.tsx +91 -0
- package/src/toolbar.tsx +212 -0
- package/src/tooltip.tsx +359 -0
- package/src/utils.ts +204 -0
- package/src/virtual-list.tsx +205 -0
- package/src/virtual.ts +385 -0
package/src/command.tsx
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import type { IntrinsicElements, Stateful, Stateless, WithChildren } from 'ajo'
|
|
2
|
+
import { announce, callHandler, controlled, dom, id, listen, roving, statefulRootAttrs as rootAttrs } from 'ajo-cloves'
|
|
3
|
+
import { context } from 'ajo/context'
|
|
4
|
+
import type { FixedArgs, OmitArg } from './utils'
|
|
5
|
+
import { defaultResultsLabel, flag, matchesTokens, resolveFilter, text } from './utils'
|
|
6
|
+
import { collection } from './collection'
|
|
7
|
+
import {
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogClose,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
type DialogContentArgs,
|
|
14
|
+
} from './dialog'
|
|
15
|
+
|
|
16
|
+
/** Predicate used to match a command item against the current search. */
|
|
17
|
+
export type CommandFilter = (value: string, search: string, keywords: string[]) => boolean
|
|
18
|
+
|
|
19
|
+
/** Arguments for the searchable Command collection root. */
|
|
20
|
+
export type CommandArgs = WithChildren<OmitArg<IntrinsicElements['div'], 'onchange'> & {
|
|
21
|
+
/** Controlled selected item value. */
|
|
22
|
+
value?: string
|
|
23
|
+
/** Initial selected item value for uncontrolled usage. */
|
|
24
|
+
defaultValue?: string
|
|
25
|
+
/** Controlled search query. */
|
|
26
|
+
search?: string
|
|
27
|
+
/** Initial search query for uncontrolled usage. */
|
|
28
|
+
defaultSearch?: string
|
|
29
|
+
/** Disable every command item and input. */
|
|
30
|
+
disabled?: boolean
|
|
31
|
+
/** Item filter; null disables internal filtering for externally driven lists. */
|
|
32
|
+
filter?: CommandFilter | null
|
|
33
|
+
/** Wrap keyboard navigation at the ends. Default: true. */
|
|
34
|
+
loop?: boolean
|
|
35
|
+
/** Called whenever the selected item value changes. */
|
|
36
|
+
onValueChange?: (value: string, event?: Event) => void
|
|
37
|
+
/** Called whenever the search query changes. */
|
|
38
|
+
onSearchChange?: (search: string, event?: Event) => void
|
|
39
|
+
/** Screen-reader message for filtered result counts. */
|
|
40
|
+
resultsLabel?: (count: number) => string
|
|
41
|
+
/** Additional UnoCSS classes. */
|
|
42
|
+
class?: string
|
|
43
|
+
}> & FixedArgs<'onchange'>
|
|
44
|
+
|
|
45
|
+
/** Arguments for a Dialog that owns a Command surface. */
|
|
46
|
+
export type CommandDialogArgs = WithChildren<OmitArg<IntrinsicElements['dialog'], 'open'> & {
|
|
47
|
+
/** Controlled dialog open state. */
|
|
48
|
+
open?: boolean
|
|
49
|
+
/** Initial open state for uncontrolled usage. */
|
|
50
|
+
defaultOpen?: boolean
|
|
51
|
+
/** Called whenever the dialog opens or closes. */
|
|
52
|
+
onOpenChange?: (open: boolean, event?: Event) => void
|
|
53
|
+
/** Called when Escape requests dialog close. Prevent default to keep it open. */
|
|
54
|
+
onEscapeKeyDown?: DialogContentArgs['onEscapeKeyDown']
|
|
55
|
+
/** Called when the native backdrop is clicked. Prevent default to keep it open. */
|
|
56
|
+
onPointerDownOutside?: DialogContentArgs['onPointerDownOutside']
|
|
57
|
+
/** Accessible dialog title. */
|
|
58
|
+
title?: string
|
|
59
|
+
/** Accessible dialog description. */
|
|
60
|
+
description?: string
|
|
61
|
+
/** Show the default close button. */
|
|
62
|
+
showCloseButton?: boolean
|
|
63
|
+
/** Accessible label for the default close button. */
|
|
64
|
+
closeLabel?: string
|
|
65
|
+
/** Additional UnoCSS classes for dialog content. */
|
|
66
|
+
class?: string
|
|
67
|
+
commandClass?: string
|
|
68
|
+
closeClass?: string
|
|
69
|
+
closeIconClass?: string
|
|
70
|
+
descriptionClass?: string
|
|
71
|
+
titleClass?: string
|
|
72
|
+
}>
|
|
73
|
+
|
|
74
|
+
/** Arguments for the search input bound to a Command root. */
|
|
75
|
+
export type CommandInputArgs = OmitArg<IntrinsicElements['input'], 'onchange'> & {
|
|
76
|
+
/** Controlled input value. Prefer Command `search` for root-level control. */
|
|
77
|
+
value?: string
|
|
78
|
+
/** Called whenever the input value changes. */
|
|
79
|
+
onValueChange?: (value: string, event: Event) => void
|
|
80
|
+
/** Additional UnoCSS classes. */
|
|
81
|
+
class?: string
|
|
82
|
+
iconClass?: string
|
|
83
|
+
wrapperClass?: string
|
|
84
|
+
} & FixedArgs<'onchange'>
|
|
85
|
+
|
|
86
|
+
/** Arguments for the Command listbox container. */
|
|
87
|
+
export type CommandListArgs = WithChildren<IntrinsicElements['div'] & {
|
|
88
|
+
/** Additional UnoCSS classes. */
|
|
89
|
+
class?: string
|
|
90
|
+
}>
|
|
91
|
+
|
|
92
|
+
/** Arguments for content shown when no command item matches. */
|
|
93
|
+
export type CommandEmptyArgs = WithChildren<IntrinsicElements['div'] & {
|
|
94
|
+
/** Additional UnoCSS classes. */
|
|
95
|
+
class?: string
|
|
96
|
+
}>
|
|
97
|
+
|
|
98
|
+
/** Arguments for a labelled group of command items. */
|
|
99
|
+
export type CommandGroupArgs = WithChildren<IntrinsicElements['div'] & {
|
|
100
|
+
/** Group heading. */
|
|
101
|
+
heading?: string
|
|
102
|
+
/** Keep the group mounted and visible even when all child items are filtered out. */
|
|
103
|
+
forceMount?: boolean
|
|
104
|
+
/** Additional UnoCSS classes. */
|
|
105
|
+
class?: string
|
|
106
|
+
headingClass?: string
|
|
107
|
+
}>
|
|
108
|
+
|
|
109
|
+
/** Arguments for a visual separator between command groups. */
|
|
110
|
+
export type CommandSeparatorArgs = IntrinsicElements['div'] & {
|
|
111
|
+
/** Additional UnoCSS classes. */
|
|
112
|
+
class?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Arguments for one selectable and filterable command item. */
|
|
116
|
+
export type CommandItemArgs = WithChildren<OmitArg<IntrinsicElements['div'], 'value'> & {
|
|
117
|
+
/** Stable value used for filtering, selection, and onSelect. */
|
|
118
|
+
value?: string
|
|
119
|
+
/** Extra searchable terms. */
|
|
120
|
+
keywords?: string[]
|
|
121
|
+
/** Keep mounted and visible even when it does not match the search query. */
|
|
122
|
+
forceMount?: boolean
|
|
123
|
+
/** Disable activation. */
|
|
124
|
+
disabled?: boolean
|
|
125
|
+
/** Called when this command item is selected by click or Enter. */
|
|
126
|
+
onSelect?: (value: string, event: Event) => void
|
|
127
|
+
/** Additional UnoCSS classes. */
|
|
128
|
+
class?: string
|
|
129
|
+
}>
|
|
130
|
+
|
|
131
|
+
/** Arguments for shortcut text displayed beside a command item. */
|
|
132
|
+
export type CommandShortcutArgs = WithChildren<IntrinsicElements['span'] & {
|
|
133
|
+
/** Additional UnoCSS classes. */
|
|
134
|
+
class?: string
|
|
135
|
+
}>
|
|
136
|
+
|
|
137
|
+
type CommandContextValue = {
|
|
138
|
+
activeId: string
|
|
139
|
+
disabled: boolean
|
|
140
|
+
itemId: (value: string) => string
|
|
141
|
+
listId: string
|
|
142
|
+
matches: (value: string, keywords?: string[]) => boolean
|
|
143
|
+
search: string
|
|
144
|
+
setValue: (value: string, event?: Event) => void
|
|
145
|
+
setSearch: (search: string, event: Event) => void
|
|
146
|
+
value: string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const CommandContext = context<CommandContextValue | null>(null)
|
|
150
|
+
|
|
151
|
+
const defaultFilter: CommandFilter = (value, search, keywords) =>
|
|
152
|
+
matchesTokens(search, [value, ...keywords].join(' '))
|
|
153
|
+
|
|
154
|
+
const commandItems = collection('command')
|
|
155
|
+
|
|
156
|
+
const CommandRoot: Stateful<CommandArgs> = function* ({ defaultSearch, defaultValue, search, value }) {
|
|
157
|
+
const commandId = id('command')
|
|
158
|
+
let announceResults = false
|
|
159
|
+
let disabled = false
|
|
160
|
+
let lastResultCount = -1
|
|
161
|
+
let loop = true
|
|
162
|
+
let onSearchChange: CommandArgs['onSearchChange']
|
|
163
|
+
let onValueChange: CommandArgs['onValueChange']
|
|
164
|
+
let resultsLabel: CommandArgs['resultsLabel']
|
|
165
|
+
const valueState = controlled<string>(this, {
|
|
166
|
+
fallback: String(value ?? defaultValue ?? ''),
|
|
167
|
+
onChange: (next, event) => onValueChange?.(next, event),
|
|
168
|
+
})
|
|
169
|
+
const searchState = controlled<string>(this, {
|
|
170
|
+
fallback: String(search ?? defaultSearch ?? ''),
|
|
171
|
+
onChange: (next, event) => onSearchChange?.(next, event),
|
|
172
|
+
})
|
|
173
|
+
const live = announce(this)
|
|
174
|
+
|
|
175
|
+
const itemId = (itemValue: string) => `${commandId}-item-${encodeURIComponent(itemValue)}`
|
|
176
|
+
|
|
177
|
+
const setValue = (next: string, event?: Event) => {
|
|
178
|
+
if (next === valueState.value) return
|
|
179
|
+
valueState.set(next, event)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const setSearch = (next: string, event: Event) => {
|
|
183
|
+
if (next === searchState.value) return
|
|
184
|
+
searchState.set(next, event)
|
|
185
|
+
announceResults = true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const nav = roving(this, {
|
|
189
|
+
items: () => commandItems.items(this),
|
|
190
|
+
loop: () => loop,
|
|
191
|
+
current: () => commandItems.items(this).find(item => item.dataset.value === valueState.value),
|
|
192
|
+
onMove: (target, event) => {
|
|
193
|
+
setValue(target.dataset.value ?? '', event)
|
|
194
|
+
target.scrollIntoView({ block: 'nearest' })
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
listen(this, 'keydown', (event: KeyboardEvent) => {
|
|
199
|
+
const target = event.target as HTMLElement | null
|
|
200
|
+
if (!target?.closest('[data-slot="command"]')) return
|
|
201
|
+
|
|
202
|
+
if (nav.handle(event)) return
|
|
203
|
+
|
|
204
|
+
if (event.key === 'Enter') {
|
|
205
|
+
// Visible items only: a highlight the filter hid must never commit.
|
|
206
|
+
const item = commandItems.items(this).find(candidate => candidate.dataset.value === valueState.value)
|
|
207
|
+
if (!item) return
|
|
208
|
+
event.preventDefault()
|
|
209
|
+
item.click()
|
|
210
|
+
} else if (event.key === 'Escape') {
|
|
211
|
+
if (!searchState.value) return
|
|
212
|
+
event.preventDefault()
|
|
213
|
+
setSearch('', event)
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
for (const args of this) {
|
|
218
|
+
disabled = Boolean(args.disabled)
|
|
219
|
+
loop = args.loop !== false
|
|
220
|
+
onSearchChange = args.onSearchChange
|
|
221
|
+
onValueChange = args.onValueChange
|
|
222
|
+
resultsLabel = args.resultsLabel
|
|
223
|
+
searchState.sync(args.search != null ? String(args.search) : undefined)
|
|
224
|
+
valueState.sync(args.value != null ? String(args.value) : undefined)
|
|
225
|
+
|
|
226
|
+
const filter = resolveFilter(args.filter, defaultFilter)
|
|
227
|
+
const matches = (itemValue: string, keywords: string[] = []) =>
|
|
228
|
+
filter(itemValue, searchState.value, keywords)
|
|
229
|
+
|
|
230
|
+
CommandContext({
|
|
231
|
+
activeId: valueState.value ? itemId(valueState.value) : '',
|
|
232
|
+
disabled,
|
|
233
|
+
itemId,
|
|
234
|
+
listId: `${commandId}-list`,
|
|
235
|
+
matches,
|
|
236
|
+
search: searchState.value,
|
|
237
|
+
setValue,
|
|
238
|
+
setSearch,
|
|
239
|
+
value: valueState.value,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// The render owns highlight attrs; this microtask only sweeps structure
|
|
243
|
+
// and clears staleness the filter introduced.
|
|
244
|
+
if (dom(this)) queueMicrotask(() => {
|
|
245
|
+
const visible = commandItems.sweep(this)
|
|
246
|
+
|
|
247
|
+
// Selection follows highlight: an uncontrolled value the filter hid
|
|
248
|
+
// re-seats on the first visible item.
|
|
249
|
+
if (!valueState.controlled) {
|
|
250
|
+
const next = visible.find(item => item.dataset.value === valueState.value) ?? visible[0]
|
|
251
|
+
const nextValue = next?.dataset.value ?? ''
|
|
252
|
+
if (nextValue !== valueState.value) valueState.init(nextValue)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (announceResults && visible.length !== lastResultCount) {
|
|
256
|
+
live.polite((resultsLabel ?? defaultResultsLabel)(visible.length))
|
|
257
|
+
}
|
|
258
|
+
lastResultCount = visible.length
|
|
259
|
+
announceResults = false
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
yield <>{args.children}</>
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
/** Searchable command menu. */
|
|
268
|
+
const Command: Stateless<CommandArgs> = ({
|
|
269
|
+
children,
|
|
270
|
+
class: classes,
|
|
271
|
+
defaultSearch,
|
|
272
|
+
defaultValue,
|
|
273
|
+
disabled,
|
|
274
|
+
filter,
|
|
275
|
+
loop,
|
|
276
|
+
onSearchChange,
|
|
277
|
+
onValueChange,
|
|
278
|
+
resultsLabel,
|
|
279
|
+
search,
|
|
280
|
+
value,
|
|
281
|
+
...attrs
|
|
282
|
+
}) => (
|
|
283
|
+
<CommandRoot
|
|
284
|
+
{...rootAttrs(attrs)}
|
|
285
|
+
defaultSearch={defaultSearch}
|
|
286
|
+
defaultValue={defaultValue}
|
|
287
|
+
disabled={disabled}
|
|
288
|
+
filter={filter}
|
|
289
|
+
loop={loop}
|
|
290
|
+
onSearchChange={onSearchChange}
|
|
291
|
+
onValueChange={onValueChange}
|
|
292
|
+
resultsLabel={resultsLabel}
|
|
293
|
+
search={search}
|
|
294
|
+
value={value}
|
|
295
|
+
attr:class={classes}
|
|
296
|
+
attr:data-slot="command"
|
|
297
|
+
>
|
|
298
|
+
{children}
|
|
299
|
+
</CommandRoot>
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
/** Native dialog wrapper for a Command palette. */
|
|
303
|
+
const CommandDialog: Stateless<CommandDialogArgs> = ({
|
|
304
|
+
children,
|
|
305
|
+
class: classes,
|
|
306
|
+
defaultOpen,
|
|
307
|
+
description,
|
|
308
|
+
commandClass,
|
|
309
|
+
onOpenChange,
|
|
310
|
+
open,
|
|
311
|
+
showCloseButton,
|
|
312
|
+
closeClass,
|
|
313
|
+
closeIconClass,
|
|
314
|
+
closeLabel = 'Close',
|
|
315
|
+
descriptionClass,
|
|
316
|
+
onEscapeKeyDown,
|
|
317
|
+
title,
|
|
318
|
+
titleClass,
|
|
319
|
+
onPointerDownOutside,
|
|
320
|
+
...attrs
|
|
321
|
+
}) => (
|
|
322
|
+
<Dialog
|
|
323
|
+
defaultOpen={defaultOpen}
|
|
324
|
+
onOpenChange={onOpenChange}
|
|
325
|
+
open={open}
|
|
326
|
+
class="contents"
|
|
327
|
+
>
|
|
328
|
+
<DialogContent
|
|
329
|
+
{...attrs}
|
|
330
|
+
class={classes}
|
|
331
|
+
data-slot="command-dialog"
|
|
332
|
+
onEscapeKeyDown={onEscapeKeyDown}
|
|
333
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
334
|
+
>
|
|
335
|
+
<div class={titleClass}>
|
|
336
|
+
<DialogTitle>{title ?? 'Command Palette'}</DialogTitle>
|
|
337
|
+
<DialogDescription class={descriptionClass}>
|
|
338
|
+
{description ?? 'Search for a command to run...'}
|
|
339
|
+
</DialogDescription>
|
|
340
|
+
</div>
|
|
341
|
+
{showCloseButton !== false ? (
|
|
342
|
+
<DialogClose aria-label={closeLabel} class={closeClass}>
|
|
343
|
+
<span aria-hidden="true" class={closeIconClass} />
|
|
344
|
+
</DialogClose>
|
|
345
|
+
) : null}
|
|
346
|
+
<Command class={commandClass}>
|
|
347
|
+
{children}
|
|
348
|
+
</Command>
|
|
349
|
+
</DialogContent>
|
|
350
|
+
</Dialog>
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
/** Search input for a Command menu. */
|
|
354
|
+
const CommandInput: Stateless<CommandInputArgs> = ({
|
|
355
|
+
class: classes,
|
|
356
|
+
disabled,
|
|
357
|
+
iconClass,
|
|
358
|
+
onValueChange,
|
|
359
|
+
placeholder = 'Type a command or search...',
|
|
360
|
+
type: _type,
|
|
361
|
+
value,
|
|
362
|
+
wrapperClass,
|
|
363
|
+
'set:oninput': onInput,
|
|
364
|
+
...attrs
|
|
365
|
+
}) => {
|
|
366
|
+
const command = CommandContext()
|
|
367
|
+
const disabledFlag = Boolean(disabled ?? command?.disabled)
|
|
368
|
+
const shown = value == null ? command?.search ?? '' : String(value)
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<div class={wrapperClass} data-slot="command-input-wrapper">
|
|
372
|
+
<span aria-hidden="true" class={iconClass} />
|
|
373
|
+
<input
|
|
374
|
+
{...attrs}
|
|
375
|
+
aria-activedescendant={command?.activeId || undefined}
|
|
376
|
+
aria-autocomplete="list"
|
|
377
|
+
aria-controls={command?.listId}
|
|
378
|
+
aria-expanded="true"
|
|
379
|
+
class={classes}
|
|
380
|
+
data-slot="command-input"
|
|
381
|
+
disabled={disabledFlag}
|
|
382
|
+
placeholder={placeholder}
|
|
383
|
+
role="combobox"
|
|
384
|
+
set:oninput={(event: Event) => {
|
|
385
|
+
const next = (event.target as HTMLInputElement).value
|
|
386
|
+
callHandler(onInput, event)
|
|
387
|
+
if (event.defaultPrevented) return
|
|
388
|
+
onValueChange?.(next, event)
|
|
389
|
+
command?.setSearch(next, event)
|
|
390
|
+
}}
|
|
391
|
+
set:value={shown}
|
|
392
|
+
type="search"
|
|
393
|
+
/>
|
|
394
|
+
</div>
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Scrollable list for command options. */
|
|
399
|
+
const CommandList: Stateless<CommandListArgs> = ({ children, class: classes, ...attrs }) => {
|
|
400
|
+
const command = CommandContext()
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<div
|
|
404
|
+
{...attrs}
|
|
405
|
+
class={classes}
|
|
406
|
+
data-slot="command-list"
|
|
407
|
+
id={command?.listId}
|
|
408
|
+
role="listbox"
|
|
409
|
+
>
|
|
410
|
+
{children}
|
|
411
|
+
</div>
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Empty state shown when filtering hides every command item. */
|
|
416
|
+
const CommandEmpty: Stateless<CommandEmptyArgs> = ({ children, class: classes, ...attrs }) => (
|
|
417
|
+
<div {...attrs} class={classes} data-slot="command-empty">
|
|
418
|
+
{children}
|
|
419
|
+
</div>
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
/** Group of related command items. */
|
|
423
|
+
const CommandGroup: Stateless<CommandGroupArgs> = ({
|
|
424
|
+
children,
|
|
425
|
+
class: classes,
|
|
426
|
+
forceMount,
|
|
427
|
+
heading,
|
|
428
|
+
headingClass,
|
|
429
|
+
...attrs
|
|
430
|
+
}) => (
|
|
431
|
+
<div
|
|
432
|
+
{...attrs}
|
|
433
|
+
class={classes}
|
|
434
|
+
data-force-mount={flag(forceMount)}
|
|
435
|
+
data-slot="command-group"
|
|
436
|
+
role="group"
|
|
437
|
+
>
|
|
438
|
+
{heading ? <div class={headingClass} data-slot="command-group-heading">{heading}</div> : null}
|
|
439
|
+
{children}
|
|
440
|
+
</div>
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
/** Visual separator between command groups. */
|
|
444
|
+
const CommandSeparator: Stateless<CommandSeparatorArgs> = ({ class: classes, ...attrs }) => (
|
|
445
|
+
<div {...attrs} class={classes} data-slot="command-separator" role="separator" />
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
/** Selectable command option. */
|
|
449
|
+
const CommandItem: Stateless<CommandItemArgs> = ({
|
|
450
|
+
children,
|
|
451
|
+
class: classes,
|
|
452
|
+
disabled,
|
|
453
|
+
forceMount,
|
|
454
|
+
id: idArg,
|
|
455
|
+
keywords = [],
|
|
456
|
+
onSelect,
|
|
457
|
+
value,
|
|
458
|
+
...attrs
|
|
459
|
+
}) => {
|
|
460
|
+
const command = CommandContext()
|
|
461
|
+
const itemValue = String(value ?? text(children))
|
|
462
|
+
const disabledFlag = Boolean(disabled ?? command?.disabled)
|
|
463
|
+
const hidden = !forceMount && command ? !command.matches(itemValue, keywords) : false
|
|
464
|
+
const highlighted = command?.value === itemValue
|
|
465
|
+
|
|
466
|
+
return (
|
|
467
|
+
<div
|
|
468
|
+
{...attrs}
|
|
469
|
+
{...commandItems.attrs({ disabled: disabledFlag, value: itemValue })}
|
|
470
|
+
aria-disabled={flag(disabledFlag)}
|
|
471
|
+
aria-selected={highlighted ? 'true' : 'false'}
|
|
472
|
+
class={classes}
|
|
473
|
+
data-highlighted={flag(highlighted)}
|
|
474
|
+
data-slot="command-item"
|
|
475
|
+
hidden={hidden || undefined}
|
|
476
|
+
id={idArg ?? command?.itemId(itemValue)}
|
|
477
|
+
role="option"
|
|
478
|
+
set:onclick={(event: Event) => {
|
|
479
|
+
if (disabledFlag) return
|
|
480
|
+
onSelect?.(itemValue, event)
|
|
481
|
+
if (event.defaultPrevented) return
|
|
482
|
+
command?.setValue(itemValue, event)
|
|
483
|
+
}}
|
|
484
|
+
set:onpointermove={(event: Event) => {
|
|
485
|
+
if (disabledFlag) return
|
|
486
|
+
command?.setValue(itemValue, event)
|
|
487
|
+
}}
|
|
488
|
+
>
|
|
489
|
+
{children}
|
|
490
|
+
</div>
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Right-aligned shortcut hint inside a CommandItem. */
|
|
495
|
+
const CommandShortcut: Stateless<CommandShortcutArgs> = ({ children, class: classes, ...attrs }) => (
|
|
496
|
+
<span {...attrs} class={classes} data-slot="command-shortcut">
|
|
497
|
+
{children}
|
|
498
|
+
</span>
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
export {
|
|
502
|
+
Command,
|
|
503
|
+
CommandDialog,
|
|
504
|
+
CommandEmpty,
|
|
505
|
+
CommandGroup,
|
|
506
|
+
CommandInput,
|
|
507
|
+
CommandItem,
|
|
508
|
+
CommandList,
|
|
509
|
+
CommandSeparator,
|
|
510
|
+
CommandShortcut,
|
|
511
|
+
}
|