@choice-ui/command 0.0.3 → 0.0.4
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/dist/index.d.ts +67 -6
- package/dist/index.js +278 -266
- package/package.json +13 -15
- package/dist/index.cjs +0 -1309
- package/dist/index.d.cts +0 -130
- package/src/command-score.ts +0 -171
- package/src/command.tsx +0 -482
- package/src/components/command-divider.tsx +0 -30
- package/src/components/command-empty.tsx +0 -30
- package/src/components/command-footer.tsx +0 -22
- package/src/components/command-group.tsx +0 -76
- package/src/components/command-input.tsx +0 -66
- package/src/components/command-item.tsx +0 -165
- package/src/components/command-list.tsx +0 -77
- package/src/components/command-loading.tsx +0 -30
- package/src/components/command-tabs.tsx +0 -20
- package/src/components/command-value.tsx +0 -23
- package/src/components/index.ts +0 -10
- package/src/context/command-context.ts +0 -5
- package/src/context/create-command-context.ts +0 -140
- package/src/context/index.ts +0 -2
- package/src/hooks/index.ts +0 -10
- package/src/hooks/use-as-ref.ts +0 -12
- package/src/hooks/use-command-state.ts +0 -18
- package/src/hooks/use-command.ts +0 -10
- package/src/hooks/use-schedule-layout-effect.ts +0 -19
- package/src/hooks/use-value.ts +0 -39
- package/src/index.ts +0 -31
- package/src/store/index.ts +0 -1
- package/src/tv.ts +0 -248
- package/src/types.ts +0 -84
- package/src/utils/constants.ts +0 -7
- package/src/utils/dom.ts +0 -19
- package/src/utils/helpers.ts +0 -45
- package/src/utils/index.ts +0 -3
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { tcx } from "@choice-ui/shared"
|
|
2
|
-
import { Kbd, type KbdKey } from "@choice-ui/kbd"
|
|
3
|
-
import React, { forwardRef, HTMLProps, ReactNode, useEffect, useMemo, useRef } from "react"
|
|
4
|
-
import { useEventCallback } from "usehooks-ts"
|
|
5
|
-
import { GroupContext, useCommand, useCommandState, useValue } from "../hooks"
|
|
6
|
-
import { commandItemTv } from "../tv"
|
|
7
|
-
import { SELECT_EVENT } from "../utils"
|
|
8
|
-
|
|
9
|
-
export interface CommandItemProps extends Omit<HTMLProps<HTMLDivElement>, "onSelect"> {
|
|
10
|
-
disabled?: boolean
|
|
11
|
-
forceMount?: boolean
|
|
12
|
-
keywords?: string[]
|
|
13
|
-
onSelect?: (value: string) => void
|
|
14
|
-
prefixElement?: ReactNode
|
|
15
|
-
shortcut?: {
|
|
16
|
-
keys?: ReactNode
|
|
17
|
-
modifier?: KbdKey | KbdKey[] | undefined
|
|
18
|
-
}
|
|
19
|
-
suffixElement?: ReactNode
|
|
20
|
-
value?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const CommandItem = forwardRef<HTMLDivElement, CommandItemProps>((props, forwardedRef) => {
|
|
24
|
-
const {
|
|
25
|
-
className,
|
|
26
|
-
disabled,
|
|
27
|
-
forceMount,
|
|
28
|
-
keywords,
|
|
29
|
-
onSelect,
|
|
30
|
-
value,
|
|
31
|
-
children,
|
|
32
|
-
prefixElement,
|
|
33
|
-
suffixElement,
|
|
34
|
-
shortcut,
|
|
35
|
-
...rest
|
|
36
|
-
} = props
|
|
37
|
-
|
|
38
|
-
const ref = useRef<HTMLDivElement | null>(null)
|
|
39
|
-
const id = React.useId()
|
|
40
|
-
const context = useCommand()
|
|
41
|
-
const groupContext = React.useContext(GroupContext)
|
|
42
|
-
|
|
43
|
-
const propsRef = useRef({
|
|
44
|
-
disabled,
|
|
45
|
-
forceMount: forceMount ?? groupContext?.forceMount,
|
|
46
|
-
keywords,
|
|
47
|
-
onSelect,
|
|
48
|
-
value,
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
propsRef.current = {
|
|
52
|
-
disabled,
|
|
53
|
-
forceMount: forceMount ?? groupContext?.forceMount,
|
|
54
|
-
keywords,
|
|
55
|
-
onSelect,
|
|
56
|
-
value,
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 注册item
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (!propsRef.current.forceMount) {
|
|
62
|
-
return context.item(id, groupContext?.id)
|
|
63
|
-
}
|
|
64
|
-
}, [context, groupContext?.id, id])
|
|
65
|
-
|
|
66
|
-
const valueDeps = useMemo(() => [value, children, ref], [value, children])
|
|
67
|
-
const stableKeywords = useMemo(() => keywords || [], [keywords])
|
|
68
|
-
const valueRef = useValue(id, ref, valueDeps, stableKeywords)
|
|
69
|
-
|
|
70
|
-
const store = context.store
|
|
71
|
-
|
|
72
|
-
const selected = useCommandState((state) =>
|
|
73
|
-
Boolean(state.value && state.value === valueRef?.current),
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
const render = useCommandState((state) =>
|
|
77
|
-
propsRef.current.forceMount
|
|
78
|
-
? true
|
|
79
|
-
: context.filter() === false
|
|
80
|
-
? true
|
|
81
|
-
: !state.search
|
|
82
|
-
? true
|
|
83
|
-
: (state.filtered.items.get(id) ?? 0) > 0,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
// 处理选择事件,当用户点击或按下Enter键时触发onSelect回调
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
const element = ref.current
|
|
89
|
-
if (!element || disabled) return
|
|
90
|
-
|
|
91
|
-
const handleSelect = () => {
|
|
92
|
-
select()
|
|
93
|
-
propsRef.current.onSelect?.(valueRef.current || "")
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
element.addEventListener(SELECT_EVENT, handleSelect)
|
|
97
|
-
return () => element.removeEventListener(SELECT_EVENT, handleSelect)
|
|
98
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
-
}, [render, disabled, valueRef])
|
|
100
|
-
|
|
101
|
-
const select = () => {
|
|
102
|
-
store.setState("value", valueRef.current || "", true)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const hasValidShortcut = shortcut && (shortcut.modifier || shortcut.keys)
|
|
106
|
-
|
|
107
|
-
const handlePointerMove = useEventCallback(() => {
|
|
108
|
-
if (disabled || context.getDisablePointerSelection()) return
|
|
109
|
-
select()
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const handleClick = useEventCallback(() => {
|
|
113
|
-
if (disabled) return
|
|
114
|
-
propsRef.current.onSelect?.(valueRef.current || "")
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const tv = commandItemTv({
|
|
118
|
-
selected,
|
|
119
|
-
disabled,
|
|
120
|
-
size: context.size,
|
|
121
|
-
hasPrefix: !!prefixElement,
|
|
122
|
-
hasSuffix: !!suffixElement,
|
|
123
|
-
variant: context.variant,
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
if (!render) return null
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<div
|
|
130
|
-
ref={(el) => {
|
|
131
|
-
ref.current = el
|
|
132
|
-
if (typeof forwardedRef === "function") forwardedRef(el)
|
|
133
|
-
else if (forwardedRef) forwardedRef.current = el
|
|
134
|
-
}}
|
|
135
|
-
{...rest}
|
|
136
|
-
id={id}
|
|
137
|
-
className={tcx(tv.root({ className }))}
|
|
138
|
-
role="option"
|
|
139
|
-
aria-disabled={disabled}
|
|
140
|
-
aria-selected={selected || undefined}
|
|
141
|
-
data-disabled={disabled}
|
|
142
|
-
data-selected={selected}
|
|
143
|
-
data-value={valueRef.current}
|
|
144
|
-
onPointerMove={handlePointerMove}
|
|
145
|
-
onClick={handleClick}
|
|
146
|
-
>
|
|
147
|
-
{prefixElement && <div className={tv.icon()}>{prefixElement}</div>}
|
|
148
|
-
|
|
149
|
-
{children}
|
|
150
|
-
|
|
151
|
-
{hasValidShortcut && (
|
|
152
|
-
<Kbd
|
|
153
|
-
className={tv.shortcut()}
|
|
154
|
-
keys={shortcut!.modifier}
|
|
155
|
-
>
|
|
156
|
-
{shortcut!.keys}
|
|
157
|
-
</Kbd>
|
|
158
|
-
)}
|
|
159
|
-
|
|
160
|
-
{suffixElement && <div className={tv.icon()}>{suffixElement}</div>}
|
|
161
|
-
</div>
|
|
162
|
-
)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
CommandItem.displayName = "CommandItem"
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { tcx } from "@choice-ui/shared"
|
|
2
|
-
import { ScrollArea, type ScrollAreaProps } from "@choice-ui/scroll-area"
|
|
3
|
-
import { forwardRef, useEffect, useRef } from "react"
|
|
4
|
-
import { useCommand, useCommandState } from "../hooks"
|
|
5
|
-
import { commandListTv } from "../tv"
|
|
6
|
-
|
|
7
|
-
export interface CommandListProps extends ScrollAreaProps {
|
|
8
|
-
children: React.ReactNode
|
|
9
|
-
className?: string
|
|
10
|
-
label?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const CommandList = forwardRef<HTMLDivElement, CommandListProps>((props, forwardedRef) => {
|
|
14
|
-
const { children, className, label = "Suggestions", hoverBoundary = "none", ...rest } = props
|
|
15
|
-
const ref = useRef<HTMLDivElement | null>(null)
|
|
16
|
-
const height = useRef<HTMLDivElement | null>(null)
|
|
17
|
-
const selectedItemId = useCommandState((state) => state.selectedItemId)
|
|
18
|
-
const context = useCommand()
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if (height.current && ref.current) {
|
|
22
|
-
const el = height.current
|
|
23
|
-
const wrapper = ref.current
|
|
24
|
-
let animationFrame: number
|
|
25
|
-
const observer = new ResizeObserver(() => {
|
|
26
|
-
animationFrame = requestAnimationFrame(() => {
|
|
27
|
-
const height = el.offsetHeight
|
|
28
|
-
wrapper.style.setProperty(`--cmdk-list-height`, height.toFixed(1) + "px")
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
observer.observe(el)
|
|
32
|
-
return () => {
|
|
33
|
-
cancelAnimationFrame(animationFrame)
|
|
34
|
-
observer.unobserve(el)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}, [])
|
|
38
|
-
|
|
39
|
-
const tv = commandListTv()
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<ScrollArea
|
|
43
|
-
variant={context.variant}
|
|
44
|
-
hoverBoundary={hoverBoundary}
|
|
45
|
-
{...rest}
|
|
46
|
-
>
|
|
47
|
-
<ScrollArea.Viewport
|
|
48
|
-
ref={(el) => {
|
|
49
|
-
ref.current = el
|
|
50
|
-
if (typeof forwardedRef === "function") forwardedRef(el)
|
|
51
|
-
else if (forwardedRef) forwardedRef.current = el
|
|
52
|
-
}}
|
|
53
|
-
{...rest}
|
|
54
|
-
className={tcx(tv.root({ className }))}
|
|
55
|
-
role="listbox"
|
|
56
|
-
tabIndex={-1}
|
|
57
|
-
aria-activedescendant={selectedItemId}
|
|
58
|
-
aria-label={label}
|
|
59
|
-
id={context.listId}
|
|
60
|
-
>
|
|
61
|
-
<ScrollArea.Content
|
|
62
|
-
className={tcx(tv.content())}
|
|
63
|
-
ref={(el) => {
|
|
64
|
-
height.current = el
|
|
65
|
-
if (context.listInnerRef) {
|
|
66
|
-
context.listInnerRef.current = el
|
|
67
|
-
}
|
|
68
|
-
}}
|
|
69
|
-
>
|
|
70
|
-
{children}
|
|
71
|
-
</ScrollArea.Content>
|
|
72
|
-
</ScrollArea.Viewport>
|
|
73
|
-
</ScrollArea>
|
|
74
|
-
)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
CommandList.displayName = "CommandList"
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { tcx } from "@choice-ui/shared"
|
|
2
|
-
import React, { forwardRef } from "react"
|
|
3
|
-
import { commandLoadingTv } from "../tv"
|
|
4
|
-
|
|
5
|
-
export interface CommandLoadingProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
-
label?: string
|
|
7
|
-
progress?: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const CommandLoading = forwardRef<HTMLDivElement, CommandLoadingProps>((props, ref) => {
|
|
11
|
-
const { className, children, label = "Loading...", progress, ...rest } = props
|
|
12
|
-
const tv = commandLoadingTv()
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<div
|
|
16
|
-
ref={ref}
|
|
17
|
-
{...props}
|
|
18
|
-
className={tcx(tv.root({ className }))}
|
|
19
|
-
role="progressbar"
|
|
20
|
-
aria-valuenow={progress}
|
|
21
|
-
aria-valuemin={0}
|
|
22
|
-
aria-valuemax={100}
|
|
23
|
-
aria-label={label}
|
|
24
|
-
>
|
|
25
|
-
<div aria-hidden>{children}</div>
|
|
26
|
-
</div>
|
|
27
|
-
)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
CommandLoading.displayName = "CommandLoading"
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Tabs, type TabsProps } from "@choice-ui/tabs"
|
|
2
|
-
import { forwardRef } from "react"
|
|
3
|
-
import { useCommand } from "../hooks/use-command"
|
|
4
|
-
import { commandTabsTv } from "../tv"
|
|
5
|
-
|
|
6
|
-
export const CommandTabs = forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
|
|
7
|
-
const context = useCommand()
|
|
8
|
-
const tv = commandTabsTv()
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<Tabs
|
|
12
|
-
ref={ref}
|
|
13
|
-
variant={props.variant || context.variant}
|
|
14
|
-
className={tv.tabs()}
|
|
15
|
-
{...props}
|
|
16
|
-
/>
|
|
17
|
-
)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
CommandTabs.displayName = "CommandTabs"
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { tcx } from "@choice-ui/shared"
|
|
2
|
-
import { forwardRef, HTMLProps } from "react"
|
|
3
|
-
import { useCommand } from "../hooks"
|
|
4
|
-
import { commandItemTv } from "../tv"
|
|
5
|
-
|
|
6
|
-
export const CommandValue = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>((props, ref) => {
|
|
7
|
-
const { className, children, ...rest } = props
|
|
8
|
-
const context = useCommand()
|
|
9
|
-
|
|
10
|
-
const tv = commandItemTv({ size: context.size })
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<div
|
|
14
|
-
ref={ref}
|
|
15
|
-
{...rest}
|
|
16
|
-
className={tcx(tv.value({ className }))}
|
|
17
|
-
>
|
|
18
|
-
{children}
|
|
19
|
-
</div>
|
|
20
|
-
)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
CommandValue.displayName = "CommandValue"
|
package/src/components/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export * from "./command-empty"
|
|
2
|
-
export * from "./command-footer"
|
|
3
|
-
export * from "./command-group"
|
|
4
|
-
export * from "./command-input"
|
|
5
|
-
export * from "./command-item"
|
|
6
|
-
export * from "./command-list"
|
|
7
|
-
export * from "./command-loading"
|
|
8
|
-
export * from "./command-divider"
|
|
9
|
-
export * from "./command-value"
|
|
10
|
-
export * from "./command-tabs"
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { commandScore } from "../command-score"
|
|
3
|
-
import type { CommandProps, Context, State, Store } from "../types"
|
|
4
|
-
|
|
5
|
-
interface CreateCommandContextOptions {
|
|
6
|
-
allGroups: React.MutableRefObject<Map<string, Set<string>>>
|
|
7
|
-
allItems: React.MutableRefObject<Set<string>>
|
|
8
|
-
filterItems: () => void
|
|
9
|
-
ids: React.MutableRefObject<Map<string, { keywords?: string[]; value: string }>>
|
|
10
|
-
inputId: string
|
|
11
|
-
label?: string
|
|
12
|
-
labelId: string
|
|
13
|
-
listId: string
|
|
14
|
-
listInnerRef: React.MutableRefObject<HTMLDivElement | null>
|
|
15
|
-
propsRef: React.MutableRefObject<CommandProps>
|
|
16
|
-
schedule: (id: string | number, cb: () => void) => void
|
|
17
|
-
selectFirstItem: () => void
|
|
18
|
-
size?: "default" | "large"
|
|
19
|
-
sort: () => void
|
|
20
|
-
state: React.MutableRefObject<State>
|
|
21
|
-
store: Store
|
|
22
|
-
variant?: "default" | "dark"
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function createCommandContext(options: CreateCommandContextOptions): Context {
|
|
26
|
-
const {
|
|
27
|
-
allGroups,
|
|
28
|
-
allItems,
|
|
29
|
-
filterItems,
|
|
30
|
-
ids,
|
|
31
|
-
inputId,
|
|
32
|
-
label,
|
|
33
|
-
labelId,
|
|
34
|
-
listId,
|
|
35
|
-
listInnerRef,
|
|
36
|
-
propsRef,
|
|
37
|
-
schedule,
|
|
38
|
-
selectFirstItem,
|
|
39
|
-
size,
|
|
40
|
-
sort,
|
|
41
|
-
state,
|
|
42
|
-
store,
|
|
43
|
-
variant,
|
|
44
|
-
} = options
|
|
45
|
-
|
|
46
|
-
function score(value: string, keywords?: string[]) {
|
|
47
|
-
const filter =
|
|
48
|
-
propsRef.current?.filter ??
|
|
49
|
-
((value: string, search: string, keywords?: string[]) =>
|
|
50
|
-
commandScore(value, search, keywords))
|
|
51
|
-
return value ? filter(value, state.current.search, keywords) : 0
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
// Keep id → {value, keywords} mapping up-to-date
|
|
56
|
-
value: (id, value, keywords) => {
|
|
57
|
-
if (value !== ids.current.get(id)?.value) {
|
|
58
|
-
ids.current.set(id, { value: value || "", keywords })
|
|
59
|
-
state.current.filtered.items.set(id, score(value || "", keywords))
|
|
60
|
-
schedule(2, () => {
|
|
61
|
-
sort()
|
|
62
|
-
store.emit()
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
// Track item lifecycle (mount, unmount)
|
|
67
|
-
item: (id, groupId) => {
|
|
68
|
-
allItems.current.add(id)
|
|
69
|
-
|
|
70
|
-
// Track this item within the group
|
|
71
|
-
if (groupId) {
|
|
72
|
-
if (!allGroups.current.has(groupId)) {
|
|
73
|
-
allGroups.current.set(groupId, new Set([id]))
|
|
74
|
-
} else {
|
|
75
|
-
allGroups.current.get(groupId)?.add(id)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Batch this, multiple items can mount in one pass
|
|
80
|
-
// and we should not be filtering/sorting/emitting each time
|
|
81
|
-
schedule(3, () => {
|
|
82
|
-
filterItems()
|
|
83
|
-
sort()
|
|
84
|
-
|
|
85
|
-
// Could be initial mount, select the first item if none already selected
|
|
86
|
-
if (!state.current.value) {
|
|
87
|
-
selectFirstItem()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
store.emit()
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
return () => {
|
|
94
|
-
ids.current.delete(id)
|
|
95
|
-
allItems.current.delete(id)
|
|
96
|
-
state.current.filtered.items.delete(id)
|
|
97
|
-
|
|
98
|
-
// Batch this, multiple items could be removed in one pass
|
|
99
|
-
schedule(4, () => {
|
|
100
|
-
filterItems()
|
|
101
|
-
|
|
102
|
-
// The item removed have been the selected one,
|
|
103
|
-
// so selection should be moved to the first
|
|
104
|
-
const ITEM_SELECTOR = `[role="option"]`
|
|
105
|
-
const selectedItem = listInnerRef.current?.querySelector(
|
|
106
|
-
`${ITEM_SELECTOR}[aria-selected="true"]`,
|
|
107
|
-
)
|
|
108
|
-
if (selectedItem?.getAttribute("id") === id) selectFirstItem()
|
|
109
|
-
|
|
110
|
-
store.emit()
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
// Track group lifecycle (mount, unmount)
|
|
115
|
-
group: (id) => {
|
|
116
|
-
if (!allGroups.current.has(id)) {
|
|
117
|
-
allGroups.current.set(id, new Set())
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return () => {
|
|
121
|
-
ids.current.delete(id)
|
|
122
|
-
allGroups.current.delete(id)
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
filter: () => {
|
|
126
|
-
return propsRef.current.shouldFilter !== false
|
|
127
|
-
},
|
|
128
|
-
label: label || propsRef.current["aria-label"],
|
|
129
|
-
getDisablePointerSelection: () => {
|
|
130
|
-
return propsRef.current.disablePointerSelection ?? false
|
|
131
|
-
},
|
|
132
|
-
listId,
|
|
133
|
-
inputId,
|
|
134
|
-
labelId,
|
|
135
|
-
listInnerRef,
|
|
136
|
-
store,
|
|
137
|
-
size,
|
|
138
|
-
variant,
|
|
139
|
-
}
|
|
140
|
-
}
|
package/src/context/index.ts
DELETED
package/src/hooks/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export * from "./use-as-ref"
|
|
2
|
-
export * from "./use-command"
|
|
3
|
-
export * from "./use-command-state"
|
|
4
|
-
export * from "./use-schedule-layout-effect"
|
|
5
|
-
export * from "./use-value"
|
|
6
|
-
|
|
7
|
-
import React from "react"
|
|
8
|
-
import { Group } from "../types"
|
|
9
|
-
|
|
10
|
-
export const GroupContext = React.createContext<Group | undefined>(undefined)
|
package/src/hooks/use-as-ref.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import React, { useRef } from "react"
|
|
2
|
-
import { useIsomorphicLayoutEffect } from "usehooks-ts"
|
|
3
|
-
|
|
4
|
-
export function useAsRef<T>(data: T): React.MutableRefObject<T> {
|
|
5
|
-
const ref = useRef<T>(data)
|
|
6
|
-
|
|
7
|
-
useIsomorphicLayoutEffect(() => {
|
|
8
|
-
ref.current = data
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
return ref
|
|
12
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { State } from "../types"
|
|
3
|
-
import { StoreContext } from "../context"
|
|
4
|
-
|
|
5
|
-
export const useStore = () => {
|
|
6
|
-
const store = React.useContext(StoreContext)
|
|
7
|
-
if (!store) {
|
|
8
|
-
throw new Error("useStore must be used within a Command component")
|
|
9
|
-
}
|
|
10
|
-
return store
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Run a selector against the store state. */
|
|
14
|
-
export function useCommandState<T>(selector: (state: State) => T): T {
|
|
15
|
-
const store = useStore()
|
|
16
|
-
const cb = () => selector(store.snapshot())
|
|
17
|
-
return React.useSyncExternalStore(store.subscribe, cb, cb)
|
|
18
|
-
}
|
package/src/hooks/use-command.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { CommandContext } from "../context"
|
|
3
|
-
|
|
4
|
-
export const useCommand = () => {
|
|
5
|
-
const context = React.useContext(CommandContext)
|
|
6
|
-
if (!context) {
|
|
7
|
-
throw new Error("useCommand must be used within a Command component")
|
|
8
|
-
}
|
|
9
|
-
return context
|
|
10
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useCallback, useState } from "react"
|
|
2
|
-
import { useIsomorphicLayoutEffect } from "usehooks-ts"
|
|
3
|
-
import { useLazyRef } from "@choice-ui/shared"
|
|
4
|
-
|
|
5
|
-
/** 在下一个 layout effect 周期内以命令式方式运行函数。 */
|
|
6
|
-
export const useScheduleLayoutEffect = () => {
|
|
7
|
-
const [updateCount, setUpdateCount] = useState(0)
|
|
8
|
-
const fns = useLazyRef(() => new Map<string | number, () => void>())
|
|
9
|
-
|
|
10
|
-
useIsomorphicLayoutEffect(() => {
|
|
11
|
-
fns.current.forEach((f) => f())
|
|
12
|
-
fns.current = new Map()
|
|
13
|
-
}, [updateCount])
|
|
14
|
-
|
|
15
|
-
return useCallback((id: string | number, cb: () => void) => {
|
|
16
|
-
fns.current.set(id, cb)
|
|
17
|
-
setUpdateCount((prev) => prev + 1)
|
|
18
|
-
}, [])
|
|
19
|
-
}
|
package/src/hooks/use-value.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import React, { useRef } from "react"
|
|
2
|
-
import { VALUE_ATTR, useLayoutEffect } from "../utils"
|
|
3
|
-
import { useCommand } from "./use-command"
|
|
4
|
-
|
|
5
|
-
export function useValue(
|
|
6
|
-
id: string,
|
|
7
|
-
ref: React.RefObject<HTMLElement>,
|
|
8
|
-
deps: (string | React.ReactNode | React.RefObject<HTMLElement>)[],
|
|
9
|
-
aliases: string[] = [],
|
|
10
|
-
) {
|
|
11
|
-
const valueRef = useRef<string>()
|
|
12
|
-
const context = useCommand()
|
|
13
|
-
|
|
14
|
-
useLayoutEffect(() => {
|
|
15
|
-
const value = (() => {
|
|
16
|
-
for (const part of deps) {
|
|
17
|
-
if (typeof part === "string") {
|
|
18
|
-
return part.trim()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (typeof part === "object" && part && "current" in part) {
|
|
22
|
-
if (part.current) {
|
|
23
|
-
return part.current.textContent?.trim()
|
|
24
|
-
}
|
|
25
|
-
return valueRef.current
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return undefined // 关键:和原始实现一致,找不到值时返回undefined
|
|
29
|
-
})()
|
|
30
|
-
|
|
31
|
-
const keywords = aliases.map((alias) => alias.trim())
|
|
32
|
-
|
|
33
|
-
context.value(id, value || "", keywords)
|
|
34
|
-
ref.current?.setAttribute(VALUE_ATTR, value || "")
|
|
35
|
-
valueRef.current = value
|
|
36
|
-
}) // 和原始实现一致:故意不使用依赖数组
|
|
37
|
-
|
|
38
|
-
return valueRef
|
|
39
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export { Command as CommandRoot, defaultFilter } from "./command"
|
|
2
|
-
export { useCommandState } from "./hooks"
|
|
3
|
-
|
|
4
|
-
import { Command as CommandRoot } from "./command"
|
|
5
|
-
import { TabItem } from "@choice-ui/tabs"
|
|
6
|
-
import {
|
|
7
|
-
CommandDivider,
|
|
8
|
-
CommandEmpty,
|
|
9
|
-
CommandFooter,
|
|
10
|
-
CommandGroup,
|
|
11
|
-
CommandInput,
|
|
12
|
-
CommandItem,
|
|
13
|
-
CommandList,
|
|
14
|
-
CommandLoading,
|
|
15
|
-
CommandTabs,
|
|
16
|
-
CommandValue,
|
|
17
|
-
} from "./components"
|
|
18
|
-
|
|
19
|
-
export const Command = Object.assign(CommandRoot, {
|
|
20
|
-
Empty: CommandEmpty,
|
|
21
|
-
Footer: CommandFooter,
|
|
22
|
-
Group: CommandGroup,
|
|
23
|
-
Input: CommandInput,
|
|
24
|
-
Item: CommandItem,
|
|
25
|
-
List: CommandList,
|
|
26
|
-
Loading: CommandLoading,
|
|
27
|
-
Divider: CommandDivider,
|
|
28
|
-
Value: CommandValue,
|
|
29
|
-
Tabs: CommandTabs,
|
|
30
|
-
TabItem: TabItem,
|
|
31
|
-
})
|
package/src/store/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// Store is now created inline in command.tsx, following original cmdk pattern
|