@lattice-ui/combobox 0.5.0-next.2 → 0.5.0-next.3
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/out/Combobox/ComboboxContent.luau +20 -8
- package/out/Combobox/ComboboxInput.luau +9 -1
- package/out/Combobox/ComboboxItem.luau +2 -6
- package/out/Combobox/ComboboxRoot.luau +24 -3
- package/out/Combobox/ComboboxTrigger.luau +5 -1
- package/out/Combobox/types.d.ts +4 -2
- package/package.json +7 -7
- package/src/Combobox/ComboboxContent.tsx +21 -10
- package/src/Combobox/ComboboxInput.tsx +7 -1
- package/src/Combobox/ComboboxItem.tsx +2 -2
- package/src/Combobox/ComboboxRoot.tsx +22 -2
- package/src/Combobox/ComboboxTrigger.tsx +1 -1
- package/src/Combobox/types.ts +4 -2
|
@@ -19,7 +19,11 @@ local function toGuiPropBag(value)
|
|
|
19
19
|
return if type(_value) == "table" then value else {}
|
|
20
20
|
end
|
|
21
21
|
local function toGuiObject(instance)
|
|
22
|
-
|
|
22
|
+
local _result = instance
|
|
23
|
+
if _result ~= nil then
|
|
24
|
+
_result = _result:IsA("GuiObject")
|
|
25
|
+
end
|
|
26
|
+
if not _result then
|
|
23
27
|
return nil
|
|
24
28
|
end
|
|
25
29
|
return instance
|
|
@@ -28,12 +32,14 @@ local function ComboboxContentImpl(props)
|
|
|
28
32
|
local comboboxContext = useComboboxContext()
|
|
29
33
|
local open = comboboxContext.open
|
|
30
34
|
local shouldMeasure = open or props.motionPresent or props.onExitComplete ~= nil
|
|
35
|
+
local contentBoundaryRef = React.useRef()
|
|
31
36
|
local popper = usePopper({
|
|
32
37
|
anchorRef = comboboxContext.anchorRef,
|
|
33
38
|
contentRef = comboboxContext.contentRef,
|
|
39
|
+
alignOffset = props.alignOffset,
|
|
40
|
+
collisionPadding = props.collisionPadding,
|
|
41
|
+
sideOffset = props.sideOffset,
|
|
34
42
|
placement = props.placement,
|
|
35
|
-
offset = props.offset,
|
|
36
|
-
padding = props.padding,
|
|
37
43
|
enabled = shouldMeasure,
|
|
38
44
|
})
|
|
39
45
|
local defaultTransition = React.useMemo(function()
|
|
@@ -50,6 +56,7 @@ local function ComboboxContentImpl(props)
|
|
|
50
56
|
local setContentRef = React.useCallback(function(instance)
|
|
51
57
|
local guiObject = toGuiObject(instance)
|
|
52
58
|
comboboxContext.contentRef.current = guiObject
|
|
59
|
+
contentBoundaryRef.current = guiObject
|
|
53
60
|
motion.ref.current = guiObject
|
|
54
61
|
end, { comboboxContext.contentRef, motion.ref })
|
|
55
62
|
local handleDismiss = React.useCallback(function()
|
|
@@ -58,6 +65,8 @@ local function ComboboxContentImpl(props)
|
|
|
58
65
|
local shouldRender = motion.mounted
|
|
59
66
|
local contentVisible = shouldRender and (motion.present or motion.phase ~= "exited")
|
|
60
67
|
local popperPosition = if popper.isPositioned then popper.position else HIDDEN_POSITION
|
|
68
|
+
local popperContentSize = popper.contentSize or Vector2.new(0, 0)
|
|
69
|
+
local popperWrapperSize = if popper.isPositioned then UDim2.fromOffset(popperContentSize.X, popperContentSize.Y) else UDim2.fromOffset(0, 0)
|
|
61
70
|
local contentNode = if props.asChild then ((function()
|
|
62
71
|
local child = props.children
|
|
63
72
|
if not React.isValidElement(child) then
|
|
@@ -94,12 +103,13 @@ local function ComboboxContentImpl(props)
|
|
|
94
103
|
onDismiss = handleDismiss,
|
|
95
104
|
onInteractOutside = props.onInteractOutside,
|
|
96
105
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
106
|
+
contentBoundaryRef = contentBoundaryRef,
|
|
97
107
|
}, React.createElement("frame", {
|
|
98
108
|
AnchorPoint = popper.anchorPoint,
|
|
99
109
|
BackgroundTransparency = 1,
|
|
100
110
|
BorderSizePixel = 0,
|
|
101
111
|
Position = popperPosition,
|
|
102
|
-
Size =
|
|
112
|
+
Size = popperWrapperSize,
|
|
103
113
|
Visible = shouldRender,
|
|
104
114
|
}, contentNode))
|
|
105
115
|
end
|
|
@@ -109,13 +119,14 @@ local function ComboboxContent(props)
|
|
|
109
119
|
if props.forceMount then
|
|
110
120
|
return React.createElement(ComboboxContentImpl, {
|
|
111
121
|
asChild = props.asChild,
|
|
122
|
+
alignOffset = props.alignOffset,
|
|
123
|
+
collisionPadding = props.collisionPadding,
|
|
112
124
|
forceMount = props.forceMount,
|
|
113
125
|
motionPresent = open,
|
|
114
|
-
offset = props.offset,
|
|
115
126
|
onInteractOutside = props.onInteractOutside,
|
|
116
127
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
117
|
-
padding = props.padding,
|
|
118
128
|
placement = props.placement,
|
|
129
|
+
sideOffset = props.sideOffset,
|
|
119
130
|
transition = props.transition,
|
|
120
131
|
}, props.children)
|
|
121
132
|
end
|
|
@@ -124,14 +135,15 @@ local function ComboboxContent(props)
|
|
|
124
135
|
render = function(state)
|
|
125
136
|
return React.createElement(ComboboxContentImpl, {
|
|
126
137
|
asChild = props.asChild,
|
|
138
|
+
alignOffset = props.alignOffset,
|
|
139
|
+
collisionPadding = props.collisionPadding,
|
|
127
140
|
forceMount = props.forceMount,
|
|
128
141
|
motionPresent = state.isPresent,
|
|
129
|
-
offset = props.offset,
|
|
130
142
|
onExitComplete = state.onExitComplete,
|
|
131
143
|
onInteractOutside = props.onInteractOutside,
|
|
132
144
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
133
|
-
padding = props.padding,
|
|
134
145
|
placement = props.placement,
|
|
146
|
+
sideOffset = props.sideOffset,
|
|
135
147
|
transition = props.transition,
|
|
136
148
|
}, props.children)
|
|
137
149
|
end,
|
|
@@ -4,8 +4,13 @@ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
|
|
|
4
4
|
local React = _core.React
|
|
5
5
|
local Slot = _core.Slot
|
|
6
6
|
local useComboboxContext = TS.import(script, script.Parent, "context").useComboboxContext
|
|
7
|
+
local UserInputService = game:GetService("UserInputService")
|
|
7
8
|
local function toTextBox(instance)
|
|
8
|
-
|
|
9
|
+
local _result = instance
|
|
10
|
+
if _result ~= nil then
|
|
11
|
+
_result = _result:IsA("TextBox")
|
|
12
|
+
end
|
|
13
|
+
if not _result then
|
|
9
14
|
return nil
|
|
10
15
|
end
|
|
11
16
|
return instance
|
|
@@ -32,6 +37,9 @@ local function ComboboxInput(props)
|
|
|
32
37
|
if textBox.Text == lastInputValueRef.current then
|
|
33
38
|
return nil
|
|
34
39
|
end
|
|
40
|
+
if UserInputService:GetFocusedTextBox() ~= textBox then
|
|
41
|
+
return nil
|
|
42
|
+
end
|
|
35
43
|
if disabled or readOnly then
|
|
36
44
|
if textBox.Text ~= lastInputValueRef.current then
|
|
37
45
|
textBox.Text = lastInputValueRef.current
|
|
@@ -12,14 +12,10 @@ local function ComboboxItem(props)
|
|
|
12
12
|
if _condition == nil then
|
|
13
13
|
_condition = props.value
|
|
14
14
|
end
|
|
15
|
-
local
|
|
15
|
+
local textValue = _condition
|
|
16
|
+
local itemQueryMatch = comboboxContext.filterFn(textValue, comboboxContext.queryValue)
|
|
16
17
|
local disabled = comboboxContext.disabled or props.disabled == true
|
|
17
18
|
local interactionDisabled = disabled or not itemQueryMatch
|
|
18
|
-
local _condition_1 = props.textValue
|
|
19
|
-
if _condition_1 == nil then
|
|
20
|
-
_condition_1 = props.value
|
|
21
|
-
end
|
|
22
|
-
local textValue = _condition_1
|
|
23
19
|
local disabledRef = React.useRef(disabled)
|
|
24
20
|
local textValueRef = React.useRef(textValue)
|
|
25
21
|
React.useEffect(function()
|
|
@@ -74,6 +74,7 @@ local function ComboboxRoot(props)
|
|
|
74
74
|
local _binding_2 = useControllableState(_object_1)
|
|
75
75
|
local inputValue = _binding_2[1]
|
|
76
76
|
local setInputValueState = _binding_2[2]
|
|
77
|
+
local visibleQueryValue, setVisibleQueryValue = React.useState(inputValue)
|
|
77
78
|
local disabled = props.disabled == true
|
|
78
79
|
local readOnly = props.readOnly == true
|
|
79
80
|
local required = props.required == true
|
|
@@ -84,6 +85,7 @@ local function ComboboxRoot(props)
|
|
|
84
85
|
local contentRef = React.useRef()
|
|
85
86
|
local itemEntriesRef = React.useRef({})
|
|
86
87
|
local itemTextCacheRef = React.useRef({})
|
|
88
|
+
local programmaticInputValueRef = React.useRef()
|
|
87
89
|
local registryRevision, setRegistryRevision = React.useState(0)
|
|
88
90
|
local registerItem = React.useCallback(function(item)
|
|
89
91
|
local _exp = itemEntriesRef.current
|
|
@@ -169,6 +171,7 @@ local function ComboboxRoot(props)
|
|
|
169
171
|
_result = ""
|
|
170
172
|
end
|
|
171
173
|
local nextInputValue = _result
|
|
174
|
+
programmaticInputValueRef.current = nextInputValue
|
|
172
175
|
setInputValueState(nextInputValue)
|
|
173
176
|
end, { getItemText, setInputValueState, value })
|
|
174
177
|
local setOpen = React.useCallback(function(nextOpen)
|
|
@@ -195,7 +198,11 @@ local function ComboboxRoot(props)
|
|
|
195
198
|
end
|
|
196
199
|
-- ▲ ReadonlyArray.find ▲
|
|
197
200
|
local selected = _result
|
|
198
|
-
|
|
201
|
+
local _result_1 = selected
|
|
202
|
+
if _result_1 ~= nil then
|
|
203
|
+
_result_1 = _result_1.getDisabled()
|
|
204
|
+
end
|
|
205
|
+
if _result_1 then
|
|
199
206
|
return nil
|
|
200
207
|
end
|
|
201
208
|
setValueState(nextValue)
|
|
@@ -204,12 +211,21 @@ local function ComboboxRoot(props)
|
|
|
204
211
|
_condition_2 = nextValue
|
|
205
212
|
end
|
|
206
213
|
local nextInputValue = _condition_2
|
|
214
|
+
programmaticInputValueRef.current = nextInputValue
|
|
207
215
|
setInputValueState(nextInputValue)
|
|
208
216
|
end, { disabled, getItemText, resolveOrderedItems, setInputValueState, setValueState })
|
|
209
217
|
local setInputValue = React.useCallback(function(nextInputValue)
|
|
210
218
|
if disabled or readOnly then
|
|
211
219
|
return nil
|
|
212
220
|
end
|
|
221
|
+
if programmaticInputValueRef.current ~= nil and nextInputValue == programmaticInputValueRef.current then
|
|
222
|
+
programmaticInputValueRef.current = nil
|
|
223
|
+
return nil
|
|
224
|
+
end
|
|
225
|
+
programmaticInputValueRef.current = nil
|
|
226
|
+
setVisibleQueryValue(function(currentQueryValue)
|
|
227
|
+
return if currentQueryValue == nextInputValue then currentQueryValue else nextInputValue
|
|
228
|
+
end)
|
|
213
229
|
if nextInputValue == inputValue then
|
|
214
230
|
return nil
|
|
215
231
|
end
|
|
@@ -234,7 +250,11 @@ local function ComboboxRoot(props)
|
|
|
234
250
|
return nil
|
|
235
251
|
end
|
|
236
252
|
syncInputFromValue()
|
|
237
|
-
|
|
253
|
+
setVisibleQueryValue(function(currentQueryValue)
|
|
254
|
+
return if currentQueryValue == inputValue then currentQueryValue else inputValue
|
|
255
|
+
end)
|
|
256
|
+
end, { inputValue, open, syncInputFromValue, value })
|
|
257
|
+
local queryValue = if open then visibleQueryValue else inputValue
|
|
238
258
|
local contextValue = React.useMemo(function()
|
|
239
259
|
return {
|
|
240
260
|
open = open,
|
|
@@ -242,6 +262,7 @@ local function ComboboxRoot(props)
|
|
|
242
262
|
value = value,
|
|
243
263
|
setValue = setValue,
|
|
244
264
|
inputValue = inputValue,
|
|
265
|
+
queryValue = queryValue,
|
|
245
266
|
setInputValue = setInputValue,
|
|
246
267
|
syncInputFromValue = syncInputFromValue,
|
|
247
268
|
disabled = disabled,
|
|
@@ -255,7 +276,7 @@ local function ComboboxRoot(props)
|
|
|
255
276
|
registerItem = registerItem,
|
|
256
277
|
getItemText = getItemText,
|
|
257
278
|
}
|
|
258
|
-
end, { disabled, filterFn, getItemText, inputValue, open, readOnly, registerItem, required, resolveOrderedItems, setInputValue, setOpen, setValue, syncInputFromValue, value })
|
|
279
|
+
end, { disabled, filterFn, getItemText, inputValue, open, queryValue, readOnly, registerItem, required, resolveOrderedItems, setInputValue, setOpen, setValue, syncInputFromValue, value, visibleQueryValue })
|
|
259
280
|
return React.createElement(ComboboxContextProvider, {
|
|
260
281
|
value = contextValue,
|
|
261
282
|
}, props.children)
|
|
@@ -5,7 +5,11 @@ local React = _core.React
|
|
|
5
5
|
local Slot = _core.Slot
|
|
6
6
|
local useComboboxContext = TS.import(script, script.Parent, "context").useComboboxContext
|
|
7
7
|
local function toGuiObject(instance)
|
|
8
|
-
|
|
8
|
+
local _result = instance
|
|
9
|
+
if _result ~= nil then
|
|
10
|
+
_result = _result:IsA("GuiObject")
|
|
11
|
+
end
|
|
12
|
+
if not _result then
|
|
9
13
|
return nil
|
|
10
14
|
end
|
|
11
15
|
return instance
|
package/out/Combobox/types.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type ComboboxContextValue = {
|
|
|
19
19
|
value?: string;
|
|
20
20
|
setValue: ComboboxSetValue;
|
|
21
21
|
inputValue: string;
|
|
22
|
+
queryValue: string;
|
|
22
23
|
setInputValue: ComboboxSetInputValue;
|
|
23
24
|
syncInputFromValue: () => void;
|
|
24
25
|
disabled: boolean;
|
|
@@ -75,8 +76,9 @@ export type ComboboxContentProps = {
|
|
|
75
76
|
asChild?: boolean;
|
|
76
77
|
forceMount?: boolean;
|
|
77
78
|
placement?: PopperPlacement;
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
sideOffset?: number;
|
|
80
|
+
alignOffset?: number;
|
|
81
|
+
collisionPadding?: number;
|
|
80
82
|
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
81
83
|
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
82
84
|
children?: React.ReactNode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lattice-ui/combobox",
|
|
3
|
-
"version": "0.5.0-next.
|
|
3
|
+
"version": "0.5.0-next.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "out/init.luau",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"url": "https://github.com/astra-void/lattice-ui.git"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@lattice-ui/core": "0.5.0-next.
|
|
20
|
-
"@lattice-ui/
|
|
21
|
-
"@lattice-ui/popper": "0.5.0-next.
|
|
22
|
-
"@lattice-ui/
|
|
19
|
+
"@lattice-ui/core": "0.5.0-next.3",
|
|
20
|
+
"@lattice-ui/layer": "0.5.0-next.3",
|
|
21
|
+
"@lattice-ui/popper": "0.5.0-next.3",
|
|
22
|
+
"@lattice-ui/motion": "0.5.0-next.3"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@rbxts/react": "17.3.7-ts.1",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "rbxtsc -p tsconfig.json",
|
|
34
|
-
"lint": "
|
|
35
|
-
"lint:fix": "
|
|
34
|
+
"lint": "biome check src",
|
|
35
|
+
"lint:fix": "biome check src --write --unsafe",
|
|
36
36
|
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
37
37
|
"watch": "rbxtsc -p tsconfig.json -w"
|
|
38
38
|
}
|
|
@@ -17,7 +17,7 @@ function toGuiPropBag(value: unknown): GuiPropBag {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function toGuiObject(instance: Instance | undefined) {
|
|
20
|
-
if (!instance
|
|
20
|
+
if (!instance?.IsA("GuiObject")) {
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
23
23
|
return instance;
|
|
@@ -27,8 +27,9 @@ function ComboboxContentImpl(props: {
|
|
|
27
27
|
motionPresent: boolean;
|
|
28
28
|
onExitComplete?: () => void;
|
|
29
29
|
placement?: PopperPlacement;
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
sideOffset?: number;
|
|
31
|
+
alignOffset?: number;
|
|
32
|
+
collisionPadding?: number;
|
|
32
33
|
forceMount?: boolean;
|
|
33
34
|
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
34
35
|
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
@@ -39,13 +40,15 @@ function ComboboxContentImpl(props: {
|
|
|
39
40
|
const comboboxContext = useComboboxContext();
|
|
40
41
|
const open = comboboxContext.open;
|
|
41
42
|
const shouldMeasure = open || props.motionPresent || props.onExitComplete !== undefined;
|
|
43
|
+
const contentBoundaryRef = React.useRef<GuiObject>();
|
|
42
44
|
|
|
43
45
|
const popper = usePopper({
|
|
44
46
|
anchorRef: comboboxContext.anchorRef,
|
|
45
47
|
contentRef: comboboxContext.contentRef,
|
|
48
|
+
alignOffset: props.alignOffset,
|
|
49
|
+
collisionPadding: props.collisionPadding,
|
|
50
|
+
sideOffset: props.sideOffset,
|
|
46
51
|
placement: props.placement,
|
|
47
|
-
offset: props.offset,
|
|
48
|
-
padding: props.padding,
|
|
49
52
|
enabled: shouldMeasure,
|
|
50
53
|
});
|
|
51
54
|
|
|
@@ -67,6 +70,7 @@ function ComboboxContentImpl(props: {
|
|
|
67
70
|
(instance: Instance | undefined) => {
|
|
68
71
|
const guiObject = toGuiObject(instance);
|
|
69
72
|
comboboxContext.contentRef.current = guiObject;
|
|
73
|
+
contentBoundaryRef.current = guiObject;
|
|
70
74
|
motion.ref.current = guiObject;
|
|
71
75
|
},
|
|
72
76
|
[comboboxContext.contentRef, motion.ref],
|
|
@@ -79,6 +83,10 @@ function ComboboxContentImpl(props: {
|
|
|
79
83
|
const shouldRender = motion.mounted;
|
|
80
84
|
const contentVisible = shouldRender && (motion.present || motion.phase !== "exited");
|
|
81
85
|
const popperPosition = popper.isPositioned ? popper.position : HIDDEN_POSITION;
|
|
86
|
+
const popperContentSize = (popper as { contentSize?: Vector2 }).contentSize ?? new Vector2(0, 0);
|
|
87
|
+
const popperWrapperSize = popper.isPositioned
|
|
88
|
+
? UDim2.fromOffset(popperContentSize.X, popperContentSize.Y)
|
|
89
|
+
: UDim2.fromOffset(0, 0);
|
|
82
90
|
|
|
83
91
|
const contentNode = props.asChild ? (
|
|
84
92
|
(() => {
|
|
@@ -129,13 +137,14 @@ function ComboboxContentImpl(props: {
|
|
|
129
137
|
onDismiss={handleDismiss}
|
|
130
138
|
onInteractOutside={props.onInteractOutside}
|
|
131
139
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
140
|
+
contentBoundaryRef={contentBoundaryRef}
|
|
132
141
|
>
|
|
133
142
|
<frame
|
|
134
143
|
AnchorPoint={popper.anchorPoint}
|
|
135
144
|
BackgroundTransparency={1}
|
|
136
145
|
BorderSizePixel={0}
|
|
137
146
|
Position={popperPosition}
|
|
138
|
-
Size={
|
|
147
|
+
Size={popperWrapperSize}
|
|
139
148
|
Visible={shouldRender}
|
|
140
149
|
>
|
|
141
150
|
{contentNode}
|
|
@@ -152,13 +161,14 @@ export function ComboboxContent(props: ComboboxContentProps) {
|
|
|
152
161
|
return (
|
|
153
162
|
<ComboboxContentImpl
|
|
154
163
|
asChild={props.asChild}
|
|
164
|
+
alignOffset={props.alignOffset}
|
|
165
|
+
collisionPadding={props.collisionPadding}
|
|
155
166
|
forceMount={props.forceMount}
|
|
156
167
|
motionPresent={open}
|
|
157
|
-
offset={props.offset}
|
|
158
168
|
onInteractOutside={props.onInteractOutside}
|
|
159
169
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
160
|
-
padding={props.padding}
|
|
161
170
|
placement={props.placement}
|
|
171
|
+
sideOffset={props.sideOffset}
|
|
162
172
|
transition={props.transition}
|
|
163
173
|
>
|
|
164
174
|
{props.children}
|
|
@@ -172,14 +182,15 @@ export function ComboboxContent(props: ComboboxContentProps) {
|
|
|
172
182
|
render={(state) => (
|
|
173
183
|
<ComboboxContentImpl
|
|
174
184
|
asChild={props.asChild}
|
|
185
|
+
alignOffset={props.alignOffset}
|
|
186
|
+
collisionPadding={props.collisionPadding}
|
|
175
187
|
forceMount={props.forceMount}
|
|
176
188
|
motionPresent={state.isPresent}
|
|
177
|
-
offset={props.offset}
|
|
178
189
|
onExitComplete={state.onExitComplete}
|
|
179
190
|
onInteractOutside={props.onInteractOutside}
|
|
180
191
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
181
|
-
padding={props.padding}
|
|
182
192
|
placement={props.placement}
|
|
193
|
+
sideOffset={props.sideOffset}
|
|
183
194
|
transition={props.transition}
|
|
184
195
|
>
|
|
185
196
|
{props.children}
|
|
@@ -2,8 +2,10 @@ import { React, Slot } from "@lattice-ui/core";
|
|
|
2
2
|
import { useComboboxContext } from "./context";
|
|
3
3
|
import type { ComboboxInputProps } from "./types";
|
|
4
4
|
|
|
5
|
+
const UserInputService = game.GetService("UserInputService");
|
|
6
|
+
|
|
5
7
|
function toTextBox(instance: Instance | undefined) {
|
|
6
|
-
if (!instance
|
|
8
|
+
if (!instance?.IsA("TextBox")) {
|
|
7
9
|
return undefined;
|
|
8
10
|
}
|
|
9
11
|
|
|
@@ -43,6 +45,10 @@ export function ComboboxInput(props: ComboboxInputProps) {
|
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
if (UserInputService.GetFocusedTextBox() !== textBox) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
if (disabled || readOnly) {
|
|
47
53
|
if (textBox.Text !== lastInputValueRef.current) {
|
|
48
54
|
textBox.Text = lastInputValueRef.current;
|
|
@@ -7,10 +7,10 @@ let nextItemOrder = 0;
|
|
|
7
7
|
|
|
8
8
|
export function ComboboxItem(props: ComboboxItemProps) {
|
|
9
9
|
const comboboxContext = useComboboxContext();
|
|
10
|
-
const
|
|
10
|
+
const textValue = props.textValue ?? props.value;
|
|
11
|
+
const itemQueryMatch = comboboxContext.filterFn(textValue, comboboxContext.queryValue);
|
|
11
12
|
const disabled = comboboxContext.disabled || props.disabled === true;
|
|
12
13
|
const interactionDisabled = disabled || !itemQueryMatch;
|
|
13
|
-
const textValue = props.textValue ?? props.value;
|
|
14
14
|
|
|
15
15
|
const disabledRef = React.useRef(disabled);
|
|
16
16
|
const textValueRef = React.useRef(textValue);
|
|
@@ -39,6 +39,7 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
39
39
|
defaultValue: props.defaultInputValue ?? "",
|
|
40
40
|
onChange: props.onInputValueChange,
|
|
41
41
|
});
|
|
42
|
+
const [visibleQueryValue, setVisibleQueryValue] = React.useState(inputValue);
|
|
42
43
|
|
|
43
44
|
const disabled = props.disabled === true;
|
|
44
45
|
const readOnly = props.readOnly === true;
|
|
@@ -52,6 +53,7 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
52
53
|
|
|
53
54
|
const itemEntriesRef = React.useRef<Array<ComboboxItemRegistration>>([]);
|
|
54
55
|
const itemTextCacheRef = React.useRef<Record<string, string>>({});
|
|
56
|
+
const programmaticInputValueRef = React.useRef<string | undefined>();
|
|
55
57
|
const [registryRevision, setRegistryRevision] = React.useState(0);
|
|
56
58
|
|
|
57
59
|
const registerItem = React.useCallback((item: ComboboxItemRegistration) => {
|
|
@@ -90,6 +92,7 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
90
92
|
|
|
91
93
|
const syncInputFromValue = React.useCallback(() => {
|
|
92
94
|
const nextInputValue = value !== undefined ? (getItemText(value) ?? "") : "";
|
|
95
|
+
programmaticInputValueRef.current = nextInputValue;
|
|
93
96
|
setInputValueState(nextInputValue);
|
|
94
97
|
}, [getItemText, setInputValueState, value]);
|
|
95
98
|
|
|
@@ -111,12 +114,13 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
const selected = resolveOrderedItems().find((item) => item.value === nextValue);
|
|
114
|
-
if (selected
|
|
117
|
+
if (selected?.getDisabled()) {
|
|
115
118
|
return;
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
setValueState(nextValue);
|
|
119
122
|
const nextInputValue = getItemText(nextValue) ?? nextValue;
|
|
123
|
+
programmaticInputValueRef.current = nextInputValue;
|
|
120
124
|
setInputValueState(nextInputValue);
|
|
121
125
|
},
|
|
122
126
|
[disabled, getItemText, resolveOrderedItems, setInputValueState, setValueState],
|
|
@@ -128,6 +132,16 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
128
132
|
return;
|
|
129
133
|
}
|
|
130
134
|
|
|
135
|
+
if (programmaticInputValueRef.current !== undefined && nextInputValue === programmaticInputValueRef.current) {
|
|
136
|
+
programmaticInputValueRef.current = undefined;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
programmaticInputValueRef.current = undefined;
|
|
141
|
+
setVisibleQueryValue((currentQueryValue) =>
|
|
142
|
+
currentQueryValue === nextInputValue ? currentQueryValue : nextInputValue,
|
|
143
|
+
);
|
|
144
|
+
|
|
131
145
|
if (nextInputValue === inputValue) {
|
|
132
146
|
return;
|
|
133
147
|
}
|
|
@@ -160,7 +174,10 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
syncInputFromValue();
|
|
163
|
-
|
|
177
|
+
setVisibleQueryValue((currentQueryValue) => (currentQueryValue === inputValue ? currentQueryValue : inputValue));
|
|
178
|
+
}, [inputValue, open, syncInputFromValue, value]);
|
|
179
|
+
|
|
180
|
+
const queryValue = open ? visibleQueryValue : inputValue;
|
|
164
181
|
|
|
165
182
|
const contextValue = React.useMemo(
|
|
166
183
|
() => ({
|
|
@@ -169,6 +186,7 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
169
186
|
value,
|
|
170
187
|
setValue,
|
|
171
188
|
inputValue,
|
|
189
|
+
queryValue,
|
|
172
190
|
setInputValue,
|
|
173
191
|
syncInputFromValue,
|
|
174
192
|
disabled,
|
|
@@ -188,6 +206,7 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
188
206
|
getItemText,
|
|
189
207
|
inputValue,
|
|
190
208
|
open,
|
|
209
|
+
queryValue,
|
|
191
210
|
readOnly,
|
|
192
211
|
registerItem,
|
|
193
212
|
required,
|
|
@@ -197,6 +216,7 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
197
216
|
setValue,
|
|
198
217
|
syncInputFromValue,
|
|
199
218
|
value,
|
|
219
|
+
visibleQueryValue,
|
|
200
220
|
],
|
|
201
221
|
);
|
|
202
222
|
|
|
@@ -3,7 +3,7 @@ import { useComboboxContext } from "./context";
|
|
|
3
3
|
import type { ComboboxTriggerProps } from "./types";
|
|
4
4
|
|
|
5
5
|
function toGuiObject(instance: Instance | undefined) {
|
|
6
|
-
if (!instance
|
|
6
|
+
if (!instance?.IsA("GuiObject")) {
|
|
7
7
|
return undefined;
|
|
8
8
|
}
|
|
9
9
|
|
package/src/Combobox/types.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type ComboboxContextValue = {
|
|
|
23
23
|
value?: string;
|
|
24
24
|
setValue: ComboboxSetValue;
|
|
25
25
|
inputValue: string;
|
|
26
|
+
queryValue: string;
|
|
26
27
|
setInputValue: ComboboxSetInputValue;
|
|
27
28
|
syncInputFromValue: () => void;
|
|
28
29
|
disabled: boolean;
|
|
@@ -85,8 +86,9 @@ export type ComboboxContentProps = {
|
|
|
85
86
|
asChild?: boolean;
|
|
86
87
|
forceMount?: boolean;
|
|
87
88
|
placement?: PopperPlacement;
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
sideOffset?: number;
|
|
90
|
+
alignOffset?: number;
|
|
91
|
+
collisionPadding?: number;
|
|
90
92
|
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
91
93
|
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
92
94
|
children?: React.ReactNode;
|