@lattice-ui/combobox 0.3.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 +23 -0
- package/out/Combobox/ComboboxContent.d.ts +3 -0
- package/out/Combobox/ComboboxContent.luau +110 -0
- package/out/Combobox/ComboboxGroup.d.ts +3 -0
- package/out/Combobox/ComboboxGroup.luau +22 -0
- package/out/Combobox/ComboboxInput.d.ts +3 -0
- package/out/Combobox/ComboboxInput.luau +88 -0
- package/out/Combobox/ComboboxItem.d.ts +3 -0
- package/out/Combobox/ComboboxItem.luau +131 -0
- package/out/Combobox/ComboboxLabel.d.ts +3 -0
- package/out/Combobox/ComboboxLabel.luau +26 -0
- package/out/Combobox/ComboboxPortal.d.ts +3 -0
- package/out/Combobox/ComboboxPortal.luau +33 -0
- package/out/Combobox/ComboboxRoot.d.ts +4 -0
- package/out/Combobox/ComboboxRoot.luau +256 -0
- package/out/Combobox/ComboboxSeparator.d.ts +3 -0
- package/out/Combobox/ComboboxSeparator.luau +22 -0
- package/out/Combobox/ComboboxTrigger.d.ts +3 -0
- package/out/Combobox/ComboboxTrigger.luau +72 -0
- package/out/Combobox/ComboboxValue.d.ts +3 -0
- package/out/Combobox/ComboboxValue.luau +47 -0
- package/out/Combobox/context.d.ts +3 -0
- package/out/Combobox/context.luau +10 -0
- package/out/Combobox/logic.d.ts +10 -0
- package/out/Combobox/logic.luau +103 -0
- package/out/Combobox/types.d.ts +105 -0
- package/out/Combobox/types.luau +2 -0
- package/out/index.d.ts +24 -0
- package/out/init.luau +32 -0
- package/package.json +26 -0
- package/src/Combobox/ComboboxContent.tsx +142 -0
- package/src/Combobox/ComboboxGroup.tsx +19 -0
- package/src/Combobox/ComboboxInput.tsx +98 -0
- package/src/Combobox/ComboboxItem.tsx +134 -0
- package/src/Combobox/ComboboxLabel.tsx +27 -0
- package/src/Combobox/ComboboxPortal.tsx +28 -0
- package/src/Combobox/ComboboxRoot.tsx +202 -0
- package/src/Combobox/ComboboxSeparator.tsx +19 -0
- package/src/Combobox/ComboboxTrigger.tsx +89 -0
- package/src/Combobox/ComboboxValue.tsx +42 -0
- package/src/Combobox/context.ts +6 -0
- package/src/Combobox/logic.ts +56 -0
- package/src/Combobox/types.ts +119 -0
- package/src/index.ts +48 -0
- package/tsconfig.json +16 -0
- package/tsconfig.typecheck.json +35 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { React, useControllableState } from "@lattice-ui/core";
|
|
2
|
+
import { ComboboxContextProvider } from "./context";
|
|
3
|
+
import {
|
|
4
|
+
type ComboboxOption,
|
|
5
|
+
defaultComboboxFilter,
|
|
6
|
+
resolveComboboxInputValue,
|
|
7
|
+
resolveForcedComboboxValue,
|
|
8
|
+
} from "./logic";
|
|
9
|
+
import type { ComboboxItemRegistration, ComboboxProps } from "./types";
|
|
10
|
+
|
|
11
|
+
function getOrderedItems(items: Array<ComboboxItemRegistration>) {
|
|
12
|
+
const ordered = [...items];
|
|
13
|
+
ordered.sort((left, right) => left.order < right.order);
|
|
14
|
+
return ordered;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function toOptions(items: Array<ComboboxItemRegistration>): Array<ComboboxOption> {
|
|
18
|
+
return items.map((item) => ({
|
|
19
|
+
value: item.value,
|
|
20
|
+
disabled: item.getDisabled(),
|
|
21
|
+
textValue: item.getTextValue(),
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ComboboxRoot(props: ComboboxProps) {
|
|
26
|
+
const [open, setOpenState] = useControllableState<boolean>({
|
|
27
|
+
value: props.open,
|
|
28
|
+
defaultValue: props.defaultOpen ?? false,
|
|
29
|
+
onChange: props.onOpenChange,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const [value, setValueState] = useControllableState<string | undefined>({
|
|
33
|
+
value: props.value,
|
|
34
|
+
defaultValue: props.defaultValue,
|
|
35
|
+
onChange: (nextValue) => {
|
|
36
|
+
if (nextValue !== undefined) {
|
|
37
|
+
props.onValueChange?.(nextValue);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const [inputValue, setInputValueState] = useControllableState<string>({
|
|
43
|
+
value: props.inputValue,
|
|
44
|
+
defaultValue: props.defaultInputValue ?? "",
|
|
45
|
+
onChange: props.onInputValueChange,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const disabled = props.disabled === true;
|
|
49
|
+
const readOnly = props.readOnly === true;
|
|
50
|
+
const required = props.required === true;
|
|
51
|
+
const loop = props.loop ?? true;
|
|
52
|
+
const filterFn = props.filterFn ?? defaultComboboxFilter;
|
|
53
|
+
|
|
54
|
+
const triggerRef = React.useRef<GuiObject>();
|
|
55
|
+
const inputRef = React.useRef<TextBox>();
|
|
56
|
+
const contentRef = React.useRef<GuiObject>();
|
|
57
|
+
|
|
58
|
+
const itemEntriesRef = React.useRef<Array<ComboboxItemRegistration>>([]);
|
|
59
|
+
const [registryRevision, setRegistryRevision] = React.useState(0);
|
|
60
|
+
|
|
61
|
+
const registerItem = React.useCallback((item: ComboboxItemRegistration) => {
|
|
62
|
+
itemEntriesRef.current.push(item);
|
|
63
|
+
setRegistryRevision((revision) => revision + 1);
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
const index = itemEntriesRef.current.findIndex((entry) => entry.id === item.id);
|
|
67
|
+
if (index >= 0) {
|
|
68
|
+
itemEntriesRef.current.remove(index);
|
|
69
|
+
setRegistryRevision((revision) => revision + 1);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
const resolveOrderedItems = React.useCallback(() => {
|
|
75
|
+
return getOrderedItems(itemEntriesRef.current);
|
|
76
|
+
}, [registryRevision]);
|
|
77
|
+
|
|
78
|
+
const getItemText = React.useCallback(
|
|
79
|
+
(candidateValue: string) => {
|
|
80
|
+
const selected = resolveOrderedItems().find((item) => item.value === candidateValue);
|
|
81
|
+
return selected?.getTextValue();
|
|
82
|
+
},
|
|
83
|
+
[resolveOrderedItems],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const syncInputFromValue = React.useCallback(() => {
|
|
87
|
+
const nextInputValue = resolveComboboxInputValue(value, toOptions(resolveOrderedItems()), "");
|
|
88
|
+
setInputValueState(nextInputValue);
|
|
89
|
+
}, [resolveOrderedItems, setInputValueState, value]);
|
|
90
|
+
|
|
91
|
+
const setOpen = React.useCallback(
|
|
92
|
+
(nextOpen: boolean) => {
|
|
93
|
+
if (disabled && nextOpen) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setOpenState(nextOpen);
|
|
98
|
+
|
|
99
|
+
if (!nextOpen) {
|
|
100
|
+
syncInputFromValue();
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
[disabled, setOpenState, syncInputFromValue],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const setValue = React.useCallback(
|
|
107
|
+
(nextValue: string) => {
|
|
108
|
+
if (disabled) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const selected = resolveOrderedItems().find((item) => item.value === nextValue);
|
|
113
|
+
if (selected && selected.getDisabled()) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setValueState(nextValue);
|
|
118
|
+
const nextInputValue = getItemText(nextValue) ?? nextValue;
|
|
119
|
+
setInputValueState(nextInputValue);
|
|
120
|
+
},
|
|
121
|
+
[disabled, getItemText, resolveOrderedItems, setInputValueState, setValueState],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const setInputValue = React.useCallback(
|
|
125
|
+
(nextInputValue: string) => {
|
|
126
|
+
if (disabled || readOnly) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setInputValueState(nextInputValue);
|
|
131
|
+
setOpenState(true);
|
|
132
|
+
},
|
|
133
|
+
[disabled, readOnly, setInputValueState, setOpenState],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const getFilteredItems = React.useCallback(() => {
|
|
137
|
+
const query = inputValue;
|
|
138
|
+
return resolveOrderedItems().filter((item) => filterFn(item.getTextValue(), query));
|
|
139
|
+
}, [filterFn, inputValue, resolveOrderedItems]);
|
|
140
|
+
|
|
141
|
+
React.useEffect(() => {
|
|
142
|
+
const orderedItems = resolveOrderedItems();
|
|
143
|
+
const nextValue = resolveForcedComboboxValue(value, toOptions(orderedItems));
|
|
144
|
+
if (nextValue !== value) {
|
|
145
|
+
setValueState(nextValue);
|
|
146
|
+
}
|
|
147
|
+
}, [registryRevision, resolveOrderedItems, setValueState, value]);
|
|
148
|
+
|
|
149
|
+
React.useEffect(() => {
|
|
150
|
+
if (open) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
syncInputFromValue();
|
|
155
|
+
}, [open, syncInputFromValue, value]);
|
|
156
|
+
|
|
157
|
+
const contextValue = React.useMemo(
|
|
158
|
+
() => ({
|
|
159
|
+
open,
|
|
160
|
+
setOpen,
|
|
161
|
+
value,
|
|
162
|
+
setValue,
|
|
163
|
+
inputValue,
|
|
164
|
+
setInputValue,
|
|
165
|
+
syncInputFromValue,
|
|
166
|
+
disabled,
|
|
167
|
+
readOnly,
|
|
168
|
+
required,
|
|
169
|
+
loop,
|
|
170
|
+
filterFn,
|
|
171
|
+
triggerRef,
|
|
172
|
+
inputRef,
|
|
173
|
+
contentRef,
|
|
174
|
+
registerItem,
|
|
175
|
+
getOrderedItems: resolveOrderedItems,
|
|
176
|
+
getFilteredItems,
|
|
177
|
+
getItemText,
|
|
178
|
+
}),
|
|
179
|
+
[
|
|
180
|
+
disabled,
|
|
181
|
+
filterFn,
|
|
182
|
+
getFilteredItems,
|
|
183
|
+
getItemText,
|
|
184
|
+
inputValue,
|
|
185
|
+
loop,
|
|
186
|
+
open,
|
|
187
|
+
readOnly,
|
|
188
|
+
registerItem,
|
|
189
|
+
required,
|
|
190
|
+
resolveOrderedItems,
|
|
191
|
+
setInputValue,
|
|
192
|
+
setOpen,
|
|
193
|
+
setValue,
|
|
194
|
+
syncInputFromValue,
|
|
195
|
+
value,
|
|
196
|
+
],
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
return <ComboboxContextProvider value={contextValue}>{props.children}</ComboboxContextProvider>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export { ComboboxRoot as Combobox };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import type { ComboboxSeparatorProps } from "./types";
|
|
3
|
+
|
|
4
|
+
export function ComboboxSeparator(props: ComboboxSeparatorProps) {
|
|
5
|
+
if (props.asChild) {
|
|
6
|
+
const child = props.children;
|
|
7
|
+
if (!child) {
|
|
8
|
+
error("[ComboboxSeparator] `asChild` requires a child element.");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return <Slot>{child}</Slot>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<frame BackgroundColor3={Color3.fromRGB(78, 86, 104)} BorderSizePixel={0} Size={UDim2.fromOffset(220, 1)}>
|
|
16
|
+
{props.children}
|
|
17
|
+
</frame>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { useComboboxContext } from "./context";
|
|
3
|
+
import type { ComboboxTriggerProps } from "./types";
|
|
4
|
+
|
|
5
|
+
function toGuiObject(instance: Instance | undefined) {
|
|
6
|
+
if (!instance || !instance.IsA("GuiObject")) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return instance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ComboboxTrigger(props: ComboboxTriggerProps) {
|
|
14
|
+
const comboboxContext = useComboboxContext();
|
|
15
|
+
const disabled = comboboxContext.disabled || props.disabled === true;
|
|
16
|
+
|
|
17
|
+
const setTriggerRef = React.useCallback(
|
|
18
|
+
(instance: Instance | undefined) => {
|
|
19
|
+
comboboxContext.triggerRef.current = toGuiObject(instance);
|
|
20
|
+
},
|
|
21
|
+
[comboboxContext.triggerRef],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const handleActivated = React.useCallback(() => {
|
|
25
|
+
if (disabled) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
comboboxContext.setOpen(!comboboxContext.open);
|
|
30
|
+
}, [comboboxContext, disabled]);
|
|
31
|
+
|
|
32
|
+
const handleInputBegan = React.useCallback(
|
|
33
|
+
(_rbx: GuiObject, inputObject: InputObject) => {
|
|
34
|
+
if (disabled) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const keyCode = inputObject.KeyCode;
|
|
39
|
+
if (keyCode === Enum.KeyCode.Return || keyCode === Enum.KeyCode.Space) {
|
|
40
|
+
comboboxContext.setOpen(!comboboxContext.open);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (keyCode === Enum.KeyCode.Down || keyCode === Enum.KeyCode.Up) {
|
|
45
|
+
comboboxContext.setOpen(true);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
[comboboxContext, disabled],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const eventHandlers = React.useMemo(
|
|
52
|
+
() => ({
|
|
53
|
+
Activated: handleActivated,
|
|
54
|
+
InputBegan: handleInputBegan,
|
|
55
|
+
}),
|
|
56
|
+
[handleActivated, handleInputBegan],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (props.asChild) {
|
|
60
|
+
const child = props.children;
|
|
61
|
+
if (!child) {
|
|
62
|
+
error("[ComboboxTrigger] `asChild` requires a child element.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Slot Active={!disabled} Event={eventHandlers} Selectable={!disabled} ref={setTriggerRef}>
|
|
67
|
+
{child}
|
|
68
|
+
</Slot>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<textbutton
|
|
74
|
+
Active={!disabled}
|
|
75
|
+
AutoButtonColor={false}
|
|
76
|
+
BackgroundColor3={Color3.fromRGB(41, 48, 63)}
|
|
77
|
+
BorderSizePixel={0}
|
|
78
|
+
Event={eventHandlers}
|
|
79
|
+
Selectable={!disabled}
|
|
80
|
+
Size={UDim2.fromOffset(220, 36)}
|
|
81
|
+
Text="Combobox"
|
|
82
|
+
TextColor3={disabled ? Color3.fromRGB(140, 148, 164) : Color3.fromRGB(235, 241, 248)}
|
|
83
|
+
TextSize={15}
|
|
84
|
+
ref={setTriggerRef}
|
|
85
|
+
>
|
|
86
|
+
{props.children}
|
|
87
|
+
</textbutton>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { useComboboxContext } from "./context";
|
|
3
|
+
import type { ComboboxValueProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function ComboboxValue(props: ComboboxValueProps) {
|
|
6
|
+
const comboboxContext = useComboboxContext();
|
|
7
|
+
const selectedValue = comboboxContext.value;
|
|
8
|
+
const hasValue = selectedValue !== undefined;
|
|
9
|
+
|
|
10
|
+
const resolvedText = React.useMemo(() => {
|
|
11
|
+
if (!hasValue || selectedValue === undefined) {
|
|
12
|
+
return props.placeholder ?? "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return comboboxContext.getItemText(selectedValue) ?? selectedValue;
|
|
16
|
+
}, [comboboxContext, hasValue, props.placeholder, selectedValue]);
|
|
17
|
+
|
|
18
|
+
if (props.asChild) {
|
|
19
|
+
const child = props.children;
|
|
20
|
+
if (!child) {
|
|
21
|
+
error("[ComboboxValue] `asChild` requires a child element.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Slot Name="ComboboxValue" Text={resolvedText}>
|
|
26
|
+
{child}
|
|
27
|
+
</Slot>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<textlabel
|
|
33
|
+
BackgroundTransparency={1}
|
|
34
|
+
BorderSizePixel={0}
|
|
35
|
+
Size={UDim2.fromOffset(200, 20)}
|
|
36
|
+
Text={resolvedText}
|
|
37
|
+
TextColor3={hasValue ? Color3.fromRGB(235, 240, 248) : Color3.fromRGB(153, 161, 177)}
|
|
38
|
+
TextSize={14}
|
|
39
|
+
TextXAlignment={Enum.TextXAlignment.Left}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createStrictContext } from "@lattice-ui/core";
|
|
2
|
+
import type { ComboboxContextValue } from "./types";
|
|
3
|
+
|
|
4
|
+
const [ComboboxContextProvider, useComboboxContext] = createStrictContext<ComboboxContextValue>("Combobox");
|
|
5
|
+
|
|
6
|
+
export { ComboboxContextProvider, useComboboxContext };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type ComboboxOption = {
|
|
2
|
+
value: string;
|
|
3
|
+
disabled: boolean;
|
|
4
|
+
textValue: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type ComboboxFilterFn = (itemText: string, query: string) => boolean;
|
|
8
|
+
|
|
9
|
+
export function defaultComboboxFilter(itemText: string, query: string) {
|
|
10
|
+
const normalizedItemText = string.lower(itemText);
|
|
11
|
+
const normalizedQuery = string.lower(query);
|
|
12
|
+
return string.find(normalizedItemText, normalizedQuery, 1, true) !== undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveForcedComboboxValue(currentValue: string | undefined, options: Array<ComboboxOption>) {
|
|
16
|
+
const enabled = options.filter((option) => !option.disabled);
|
|
17
|
+
if (enabled.size() === 0) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (currentValue === undefined) {
|
|
22
|
+
return enabled[0]?.value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const selected = enabled.find((option) => option.value === currentValue);
|
|
26
|
+
if (selected) {
|
|
27
|
+
return selected.value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return enabled[0]?.value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveComboboxInputValue(
|
|
34
|
+
selectedValue: string | undefined,
|
|
35
|
+
options: Array<ComboboxOption>,
|
|
36
|
+
placeholder = "",
|
|
37
|
+
) {
|
|
38
|
+
if (selectedValue === undefined) {
|
|
39
|
+
return placeholder;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const selected = options.find((option) => option.value === selectedValue && !option.disabled);
|
|
43
|
+
if (!selected) {
|
|
44
|
+
return placeholder;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return selected.textValue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function filterComboboxOptions(
|
|
51
|
+
options: Array<ComboboxOption>,
|
|
52
|
+
query: string,
|
|
53
|
+
filterFn: ComboboxFilterFn = defaultComboboxFilter,
|
|
54
|
+
) {
|
|
55
|
+
return options.filter((option) => filterFn(option.textValue, query));
|
|
56
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { LayerInteractEvent } from "@lattice-ui/layer";
|
|
2
|
+
import type { PopperPlacement } from "@lattice-ui/popper";
|
|
3
|
+
import type React from "@rbxts/react";
|
|
4
|
+
|
|
5
|
+
export type ComboboxFilterFn = (itemText: string, query: string) => boolean;
|
|
6
|
+
|
|
7
|
+
export type ComboboxSetOpen = (open: boolean) => void;
|
|
8
|
+
export type ComboboxSetValue = (value: string) => void;
|
|
9
|
+
export type ComboboxSetInputValue = (inputValue: string) => void;
|
|
10
|
+
|
|
11
|
+
export type ComboboxItemRegistration = {
|
|
12
|
+
id: number;
|
|
13
|
+
value: string;
|
|
14
|
+
order: number;
|
|
15
|
+
getNode: () => GuiObject | undefined;
|
|
16
|
+
getDisabled: () => boolean;
|
|
17
|
+
getTextValue: () => string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ComboboxContextValue = {
|
|
21
|
+
open: boolean;
|
|
22
|
+
setOpen: ComboboxSetOpen;
|
|
23
|
+
value?: string;
|
|
24
|
+
setValue: ComboboxSetValue;
|
|
25
|
+
inputValue: string;
|
|
26
|
+
setInputValue: ComboboxSetInputValue;
|
|
27
|
+
syncInputFromValue: () => void;
|
|
28
|
+
disabled: boolean;
|
|
29
|
+
readOnly: boolean;
|
|
30
|
+
required: boolean;
|
|
31
|
+
loop: boolean;
|
|
32
|
+
filterFn: ComboboxFilterFn;
|
|
33
|
+
triggerRef: React.MutableRefObject<GuiObject | undefined>;
|
|
34
|
+
inputRef: React.MutableRefObject<TextBox | undefined>;
|
|
35
|
+
contentRef: React.MutableRefObject<GuiObject | undefined>;
|
|
36
|
+
registerItem: (item: ComboboxItemRegistration) => () => void;
|
|
37
|
+
getOrderedItems: () => Array<ComboboxItemRegistration>;
|
|
38
|
+
getFilteredItems: () => Array<ComboboxItemRegistration>;
|
|
39
|
+
getItemText: (value: string) => string | undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ComboboxProps = {
|
|
43
|
+
value?: string;
|
|
44
|
+
defaultValue?: string;
|
|
45
|
+
onValueChange?: (value: string) => void;
|
|
46
|
+
inputValue?: string;
|
|
47
|
+
defaultInputValue?: string;
|
|
48
|
+
onInputValueChange?: (inputValue: string) => void;
|
|
49
|
+
open?: boolean;
|
|
50
|
+
defaultOpen?: boolean;
|
|
51
|
+
onOpenChange?: (open: boolean) => void;
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
readOnly?: boolean;
|
|
54
|
+
required?: boolean;
|
|
55
|
+
loop?: boolean;
|
|
56
|
+
filterFn?: ComboboxFilterFn;
|
|
57
|
+
children?: React.ReactNode;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type ComboboxTriggerProps = {
|
|
61
|
+
asChild?: boolean;
|
|
62
|
+
disabled?: boolean;
|
|
63
|
+
children?: React.ReactElement;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type ComboboxInputProps = {
|
|
67
|
+
asChild?: boolean;
|
|
68
|
+
disabled?: boolean;
|
|
69
|
+
readOnly?: boolean;
|
|
70
|
+
placeholder?: string;
|
|
71
|
+
children?: React.ReactElement;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type ComboboxValueProps = {
|
|
75
|
+
asChild?: boolean;
|
|
76
|
+
placeholder?: string;
|
|
77
|
+
children?: React.ReactElement;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type ComboboxPortalProps = {
|
|
81
|
+
container?: BasePlayerGui;
|
|
82
|
+
displayOrderBase?: number;
|
|
83
|
+
children?: React.ReactNode;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type ComboboxContentProps = {
|
|
87
|
+
asChild?: boolean;
|
|
88
|
+
forceMount?: boolean;
|
|
89
|
+
placement?: PopperPlacement;
|
|
90
|
+
offset?: Vector2;
|
|
91
|
+
padding?: number;
|
|
92
|
+
onEscapeKeyDown?: (event: LayerInteractEvent) => void;
|
|
93
|
+
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
94
|
+
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
95
|
+
children?: React.ReactNode;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type ComboboxItemProps = {
|
|
99
|
+
value: string;
|
|
100
|
+
textValue?: string;
|
|
101
|
+
disabled?: boolean;
|
|
102
|
+
asChild?: boolean;
|
|
103
|
+
children?: React.ReactElement;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export type ComboboxSeparatorProps = {
|
|
107
|
+
asChild?: boolean;
|
|
108
|
+
children?: React.ReactElement;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export type ComboboxGroupProps = {
|
|
112
|
+
asChild?: boolean;
|
|
113
|
+
children?: React.ReactElement;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type ComboboxLabelProps = {
|
|
117
|
+
asChild?: boolean;
|
|
118
|
+
children?: React.ReactElement;
|
|
119
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ComboboxContent } from "./Combobox/ComboboxContent";
|
|
2
|
+
import { ComboboxGroup } from "./Combobox/ComboboxGroup";
|
|
3
|
+
import { ComboboxInput } from "./Combobox/ComboboxInput";
|
|
4
|
+
import { ComboboxItem } from "./Combobox/ComboboxItem";
|
|
5
|
+
import { ComboboxLabel } from "./Combobox/ComboboxLabel";
|
|
6
|
+
import { ComboboxPortal } from "./Combobox/ComboboxPortal";
|
|
7
|
+
import { ComboboxRoot } from "./Combobox/ComboboxRoot";
|
|
8
|
+
import { ComboboxSeparator } from "./Combobox/ComboboxSeparator";
|
|
9
|
+
import { ComboboxTrigger } from "./Combobox/ComboboxTrigger";
|
|
10
|
+
import { ComboboxValue } from "./Combobox/ComboboxValue";
|
|
11
|
+
|
|
12
|
+
export const Combobox = {
|
|
13
|
+
Root: ComboboxRoot,
|
|
14
|
+
Trigger: ComboboxTrigger,
|
|
15
|
+
Input: ComboboxInput,
|
|
16
|
+
Value: ComboboxValue,
|
|
17
|
+
Portal: ComboboxPortal,
|
|
18
|
+
Content: ComboboxContent,
|
|
19
|
+
Item: ComboboxItem,
|
|
20
|
+
Group: ComboboxGroup,
|
|
21
|
+
Label: ComboboxLabel,
|
|
22
|
+
Separator: ComboboxSeparator,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
defaultComboboxFilter,
|
|
27
|
+
filterComboboxOptions,
|
|
28
|
+
resolveComboboxInputValue,
|
|
29
|
+
resolveForcedComboboxValue,
|
|
30
|
+
} from "./Combobox/logic";
|
|
31
|
+
export type {
|
|
32
|
+
ComboboxContentProps,
|
|
33
|
+
ComboboxContextValue,
|
|
34
|
+
ComboboxFilterFn,
|
|
35
|
+
ComboboxGroupProps,
|
|
36
|
+
ComboboxInputProps,
|
|
37
|
+
ComboboxItemProps,
|
|
38
|
+
ComboboxItemRegistration,
|
|
39
|
+
ComboboxLabelProps,
|
|
40
|
+
ComboboxPortalProps,
|
|
41
|
+
ComboboxProps,
|
|
42
|
+
ComboboxSeparatorProps,
|
|
43
|
+
ComboboxSetInputValue,
|
|
44
|
+
ComboboxSetOpen,
|
|
45
|
+
ComboboxSetValue,
|
|
46
|
+
ComboboxTriggerProps,
|
|
47
|
+
ComboboxValueProps,
|
|
48
|
+
} from "./Combobox/types";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "out",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"typeRoots": [
|
|
8
|
+
"./node_modules/@rbxts",
|
|
9
|
+
"../../node_modules/@rbxts",
|
|
10
|
+
"./node_modules/@lattice-ui",
|
|
11
|
+
"../../node_modules/@lattice-ui"
|
|
12
|
+
],
|
|
13
|
+
"types": ["types", "compiler-types"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": true,
|
|
5
|
+
"baseUrl": "..",
|
|
6
|
+
"rootDir": "..",
|
|
7
|
+
"paths": {
|
|
8
|
+
"@lattice-ui/accordion": ["accordion/src/index.ts"],
|
|
9
|
+
"@lattice-ui/avatar": ["avatar/src/index.ts"],
|
|
10
|
+
"@lattice-ui/checkbox": ["checkbox/src/index.ts"],
|
|
11
|
+
"@lattice-ui/combobox": ["combobox/src/index.ts"],
|
|
12
|
+
"@lattice-ui/core": ["core/src/index.ts"],
|
|
13
|
+
"@lattice-ui/dialog": ["dialog/src/index.ts"],
|
|
14
|
+
"@lattice-ui/focus": ["focus/src/index.ts"],
|
|
15
|
+
"@lattice-ui/layer": ["layer/src/index.ts"],
|
|
16
|
+
"@lattice-ui/menu": ["menu/src/index.ts"],
|
|
17
|
+
"@lattice-ui/popover": ["popover/src/index.ts"],
|
|
18
|
+
"@lattice-ui/popper": ["popper/src/index.ts"],
|
|
19
|
+
"@lattice-ui/progress": ["progress/src/index.ts"],
|
|
20
|
+
"@lattice-ui/radio-group": ["radio-group/src/index.ts"],
|
|
21
|
+
"@lattice-ui/scroll-area": ["scroll-area/src/index.ts"],
|
|
22
|
+
"@lattice-ui/select": ["select/src/index.ts"],
|
|
23
|
+
"@lattice-ui/slider": ["slider/src/index.ts"],
|
|
24
|
+
"@lattice-ui/style": ["style/src/index.ts"],
|
|
25
|
+
"@lattice-ui/switch": ["switch/src/index.ts"],
|
|
26
|
+
"@lattice-ui/system": ["system/src/index.ts"],
|
|
27
|
+
"@lattice-ui/tabs": ["tabs/src/index.ts"],
|
|
28
|
+
"@lattice-ui/text-field": ["text-field/src/index.ts"],
|
|
29
|
+
"@lattice-ui/textarea": ["textarea/src/index.ts"],
|
|
30
|
+
"@lattice-ui/toast": ["toast/src/index.ts"],
|
|
31
|
+
"@lattice-ui/toggle-group": ["toggle-group/src/index.ts"],
|
|
32
|
+
"@lattice-ui/tooltip": ["tooltip/src/index.ts"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|