@lattice-ui/combobox 0.3.2 → 0.4.1
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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/out/Combobox/ComboboxContent.luau +3 -11
- package/out/Combobox/ComboboxInput.luau +14 -19
- package/out/Combobox/ComboboxItem.luau +15 -36
- package/out/Combobox/ComboboxRoot.luau +39 -45
- package/out/Combobox/ComboboxTrigger.luau +16 -8
- package/out/Combobox/types.d.ts +1 -6
- package/package.json +4 -5
- package/src/Combobox/ComboboxContent.tsx +4 -9
- package/src/Combobox/ComboboxInput.tsx +18 -25
- package/src/Combobox/ComboboxItem.tsx +26 -46
- package/src/Combobox/ComboboxRoot.tsx +30 -28
- package/src/Combobox/ComboboxTrigger.tsx +20 -9
- package/src/Combobox/types.ts +1 -6
|
@@ -3,7 +3,6 @@ local TS = _G[script]
|
|
|
3
3
|
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
|
-
local RovingFocusGroup = TS.import(script, TS.getModule(script, "@lattice-ui", "focus").out).RovingFocusGroup
|
|
7
6
|
local _layer = TS.import(script, TS.getModule(script, "@lattice-ui", "layer").out)
|
|
8
7
|
local DismissableLayer = _layer.DismissableLayer
|
|
9
8
|
local Presence = _layer.Presence
|
|
@@ -18,7 +17,7 @@ end
|
|
|
18
17
|
local function ComboboxContentImpl(props)
|
|
19
18
|
local comboboxContext = useComboboxContext()
|
|
20
19
|
local popper = usePopper({
|
|
21
|
-
anchorRef = comboboxContext.
|
|
20
|
+
anchorRef = comboboxContext.anchorRef,
|
|
22
21
|
contentRef = comboboxContext.contentRef,
|
|
23
22
|
placement = props.placement,
|
|
24
23
|
offset = props.offset,
|
|
@@ -50,17 +49,12 @@ local function ComboboxContentImpl(props)
|
|
|
50
49
|
}, props.children))
|
|
51
50
|
return React.createElement(DismissableLayer, {
|
|
52
51
|
enabled = props.enabled,
|
|
52
|
+
insideRefs = { comboboxContext.triggerRef, comboboxContext.inputRef },
|
|
53
53
|
modal = false,
|
|
54
54
|
onDismiss = props.onDismiss,
|
|
55
|
-
onEscapeKeyDown = props.onEscapeKeyDown,
|
|
56
55
|
onInteractOutside = props.onInteractOutside,
|
|
57
56
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
58
|
-
},
|
|
59
|
-
active = props.enabled,
|
|
60
|
-
autoFocus = "first",
|
|
61
|
-
loop = comboboxContext.loop,
|
|
62
|
-
orientation = "vertical",
|
|
63
|
-
}, contentNode))
|
|
57
|
+
}, contentNode)
|
|
64
58
|
end
|
|
65
59
|
local function ComboboxContent(props)
|
|
66
60
|
local comboboxContext = useComboboxContext()
|
|
@@ -78,7 +72,6 @@ local function ComboboxContent(props)
|
|
|
78
72
|
enabled = open,
|
|
79
73
|
offset = props.offset,
|
|
80
74
|
onDismiss = handleDismiss,
|
|
81
|
-
onEscapeKeyDown = props.onEscapeKeyDown,
|
|
82
75
|
onInteractOutside = props.onInteractOutside,
|
|
83
76
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
84
77
|
padding = props.padding,
|
|
@@ -95,7 +88,6 @@ local function ComboboxContent(props)
|
|
|
95
88
|
enabled = state.isPresent,
|
|
96
89
|
offset = props.offset,
|
|
97
90
|
onDismiss = handleDismiss,
|
|
98
|
-
onEscapeKeyDown = props.onEscapeKeyDown,
|
|
99
91
|
onInteractOutside = props.onInteractOutside,
|
|
100
92
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
101
93
|
padding = props.padding,
|
|
@@ -15,9 +15,21 @@ local function ComboboxInput(props)
|
|
|
15
15
|
local disabled = comboboxContext.disabled or props.disabled == true
|
|
16
16
|
local readOnly = comboboxContext.readOnly or props.readOnly == true
|
|
17
17
|
local setInputRef = React.useCallback(function(instance)
|
|
18
|
-
comboboxContext.inputRef.current
|
|
19
|
-
|
|
18
|
+
local previousInput = comboboxContext.inputRef.current
|
|
19
|
+
local nextInput = toTextBox(instance)
|
|
20
|
+
comboboxContext.inputRef.current = nextInput
|
|
21
|
+
if nextInput then
|
|
22
|
+
comboboxContext.anchorRef.current = nextInput
|
|
23
|
+
return nil
|
|
24
|
+
end
|
|
25
|
+
if comboboxContext.anchorRef.current == previousInput then
|
|
26
|
+
comboboxContext.anchorRef.current = comboboxContext.triggerRef.current
|
|
27
|
+
end
|
|
28
|
+
end, { comboboxContext.anchorRef, comboboxContext.inputRef, comboboxContext.triggerRef })
|
|
20
29
|
local handleTextChanged = React.useCallback(function(textBox)
|
|
30
|
+
if textBox.Text == comboboxContext.inputValue then
|
|
31
|
+
return nil
|
|
32
|
+
end
|
|
21
33
|
if disabled or readOnly then
|
|
22
34
|
if textBox.Text ~= comboboxContext.inputValue then
|
|
23
35
|
textBox.Text = comboboxContext.inputValue
|
|
@@ -26,19 +38,6 @@ local function ComboboxInput(props)
|
|
|
26
38
|
end
|
|
27
39
|
comboboxContext.setInputValue(textBox.Text)
|
|
28
40
|
end, { comboboxContext, disabled, readOnly })
|
|
29
|
-
local handleFocusLost = React.useCallback(function()
|
|
30
|
-
comboboxContext.setOpen(false)
|
|
31
|
-
comboboxContext.syncInputFromValue()
|
|
32
|
-
end, { comboboxContext })
|
|
33
|
-
local handleInputBegan = React.useCallback(function(_rbx, inputObject)
|
|
34
|
-
if disabled then
|
|
35
|
-
return nil
|
|
36
|
-
end
|
|
37
|
-
local keyCode = inputObject.KeyCode
|
|
38
|
-
if keyCode == Enum.KeyCode.Down or keyCode == Enum.KeyCode.Up then
|
|
39
|
-
comboboxContext.setOpen(true)
|
|
40
|
-
end
|
|
41
|
-
end, { comboboxContext, disabled })
|
|
42
41
|
local _object = {
|
|
43
42
|
Active = not disabled,
|
|
44
43
|
ClearTextOnFocus = false,
|
|
@@ -55,10 +54,6 @@ local function ComboboxInput(props)
|
|
|
55
54
|
_object.Change = {
|
|
56
55
|
Text = handleTextChanged,
|
|
57
56
|
}
|
|
58
|
-
_object.Event = {
|
|
59
|
-
FocusLost = handleFocusLost,
|
|
60
|
-
InputBegan = handleInputBegan,
|
|
61
|
-
}
|
|
62
57
|
_object.ref = setInputRef
|
|
63
58
|
local sharedProps = _object
|
|
64
59
|
if props.asChild then
|
|
@@ -3,25 +3,18 @@ local TS = _G[script]
|
|
|
3
3
|
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
|
-
local RovingFocusItem = TS.import(script, TS.getModule(script, "@lattice-ui", "focus").out).RovingFocusItem
|
|
7
6
|
local useComboboxContext = TS.import(script, script.Parent, "context").useComboboxContext
|
|
8
7
|
local nextItemId = 0
|
|
9
8
|
local nextItemOrder = 0
|
|
10
|
-
local function toGuiObject(instance)
|
|
11
|
-
if not instance or not instance:IsA("GuiObject") then
|
|
12
|
-
return nil
|
|
13
|
-
end
|
|
14
|
-
return instance
|
|
15
|
-
end
|
|
16
9
|
local function ComboboxItem(props)
|
|
17
10
|
local comboboxContext = useComboboxContext()
|
|
18
|
-
local itemRef = React.useRef()
|
|
19
11
|
local _condition = props.textValue
|
|
20
12
|
if _condition == nil then
|
|
21
13
|
_condition = props.value
|
|
22
14
|
end
|
|
23
15
|
local itemQueryMatch = comboboxContext.filterFn(_condition, comboboxContext.inputValue)
|
|
24
|
-
local disabled = comboboxContext.disabled or props.disabled == true
|
|
16
|
+
local disabled = comboboxContext.disabled or props.disabled == true
|
|
17
|
+
local interactionDisabled = disabled or not itemQueryMatch
|
|
25
18
|
local _condition_1 = props.textValue
|
|
26
19
|
if _condition_1 == nil then
|
|
27
20
|
_condition_1 = props.value
|
|
@@ -50,9 +43,6 @@ local function ComboboxItem(props)
|
|
|
50
43
|
id = itemIdRef.current,
|
|
51
44
|
value = props.value,
|
|
52
45
|
order = itemOrderRef.current,
|
|
53
|
-
getNode = function()
|
|
54
|
-
return itemRef.current
|
|
55
|
-
end,
|
|
56
46
|
getDisabled = function()
|
|
57
47
|
return disabledRef.current
|
|
58
48
|
end,
|
|
@@ -61,18 +51,15 @@ local function ComboboxItem(props)
|
|
|
61
51
|
end,
|
|
62
52
|
})
|
|
63
53
|
end, { comboboxContext, props.value })
|
|
64
|
-
local setItemRef = React.useCallback(function(instance)
|
|
65
|
-
itemRef.current = toGuiObject(instance)
|
|
66
|
-
end, {})
|
|
67
54
|
local handleSelect = React.useCallback(function()
|
|
68
|
-
if
|
|
55
|
+
if interactionDisabled then
|
|
69
56
|
return nil
|
|
70
57
|
end
|
|
71
58
|
comboboxContext.setValue(props.value)
|
|
72
59
|
comboboxContext.setOpen(false)
|
|
73
|
-
end, { comboboxContext,
|
|
60
|
+
end, { comboboxContext, interactionDisabled, props.value })
|
|
74
61
|
local handleInputBegan = React.useCallback(function(_rbx, inputObject)
|
|
75
|
-
if
|
|
62
|
+
if interactionDisabled then
|
|
76
63
|
return nil
|
|
77
64
|
end
|
|
78
65
|
local keyCode = inputObject.KeyCode
|
|
@@ -81,7 +68,7 @@ local function ComboboxItem(props)
|
|
|
81
68
|
end
|
|
82
69
|
comboboxContext.setValue(props.value)
|
|
83
70
|
comboboxContext.setOpen(false)
|
|
84
|
-
end, { comboboxContext,
|
|
71
|
+
end, { comboboxContext, interactionDisabled, props.value })
|
|
85
72
|
local eventHandlers = React.useMemo(function()
|
|
86
73
|
return {
|
|
87
74
|
Activated = handleSelect,
|
|
@@ -93,38 +80,30 @@ local function ComboboxItem(props)
|
|
|
93
80
|
if not child then
|
|
94
81
|
error("[ComboboxItem] `asChild` requires a child element.")
|
|
95
82
|
end
|
|
96
|
-
return React.createElement(
|
|
97
|
-
|
|
98
|
-
disabled = disabled,
|
|
99
|
-
}, React.createElement(Slot, {
|
|
100
|
-
Active = not disabled,
|
|
83
|
+
return React.createElement(Slot, {
|
|
84
|
+
Active = not interactionDisabled,
|
|
101
85
|
Event = eventHandlers,
|
|
102
|
-
Selectable =
|
|
86
|
+
Selectable = false,
|
|
103
87
|
Visible = itemQueryMatch,
|
|
104
|
-
|
|
105
|
-
}, child))
|
|
88
|
+
}, child)
|
|
106
89
|
end
|
|
107
|
-
return React.createElement(
|
|
108
|
-
|
|
109
|
-
disabled = disabled,
|
|
110
|
-
}, React.createElement("textbutton", {
|
|
111
|
-
Active = not disabled,
|
|
90
|
+
return React.createElement("textbutton", {
|
|
91
|
+
Active = not interactionDisabled,
|
|
112
92
|
AutoButtonColor = false,
|
|
113
93
|
BackgroundColor3 = Color3.fromRGB(47, 53, 68),
|
|
114
94
|
BorderSizePixel = 0,
|
|
115
95
|
Event = eventHandlers,
|
|
116
|
-
Selectable =
|
|
96
|
+
Selectable = false,
|
|
117
97
|
Size = UDim2.fromOffset(220, 32),
|
|
118
98
|
Text = textValue,
|
|
119
|
-
TextColor3 = if
|
|
99
|
+
TextColor3 = if interactionDisabled then Color3.fromRGB(134, 141, 156) else Color3.fromRGB(234, 239, 247),
|
|
120
100
|
TextSize = 15,
|
|
121
101
|
TextXAlignment = Enum.TextXAlignment.Left,
|
|
122
102
|
Visible = itemQueryMatch,
|
|
123
|
-
ref = setItemRef,
|
|
124
103
|
}, React.createElement("uipadding", {
|
|
125
104
|
PaddingLeft = UDim.new(0, 10),
|
|
126
105
|
PaddingRight = UDim.new(0, 10),
|
|
127
|
-
}), props.children)
|
|
106
|
+
}), props.children)
|
|
128
107
|
end
|
|
129
108
|
return {
|
|
130
109
|
ComboboxItem = ComboboxItem,
|
|
@@ -6,7 +6,6 @@ local useControllableState = _core.useControllableState
|
|
|
6
6
|
local ComboboxContextProvider = TS.import(script, script.Parent, "context").ComboboxContextProvider
|
|
7
7
|
local _logic = TS.import(script, script.Parent, "logic")
|
|
8
8
|
local defaultComboboxFilter = _logic.defaultComboboxFilter
|
|
9
|
-
local resolveComboboxInputValue = _logic.resolveComboboxInputValue
|
|
10
9
|
local resolveForcedComboboxValue = _logic.resolveForcedComboboxValue
|
|
11
10
|
local function getOrderedItems(items)
|
|
12
11
|
local _array = {}
|
|
@@ -78,21 +77,19 @@ local function ComboboxRoot(props)
|
|
|
78
77
|
local disabled = props.disabled == true
|
|
79
78
|
local readOnly = props.readOnly == true
|
|
80
79
|
local required = props.required == true
|
|
81
|
-
local _condition_2 = props.loop
|
|
82
|
-
if _condition_2 == nil then
|
|
83
|
-
_condition_2 = true
|
|
84
|
-
end
|
|
85
|
-
local loop = _condition_2
|
|
86
80
|
local filterFn = props.filterFn or defaultComboboxFilter
|
|
81
|
+
local anchorRef = React.useRef()
|
|
87
82
|
local triggerRef = React.useRef()
|
|
88
83
|
local inputRef = React.useRef()
|
|
89
84
|
local contentRef = React.useRef()
|
|
90
85
|
local itemEntriesRef = React.useRef({})
|
|
86
|
+
local itemTextCacheRef = React.useRef({})
|
|
91
87
|
local registryRevision, setRegistryRevision = React.useState(0)
|
|
92
88
|
local registerItem = React.useCallback(function(item)
|
|
93
89
|
local _current = itemEntriesRef.current
|
|
94
90
|
local _item = item
|
|
95
91
|
table.insert(_current, _item)
|
|
92
|
+
itemTextCacheRef.current[item.value] = item.getTextValue()
|
|
96
93
|
setRegistryRevision(function(revision)
|
|
97
94
|
return revision + 1
|
|
98
95
|
end)
|
|
@@ -137,25 +134,33 @@ local function ComboboxRoot(props)
|
|
|
137
134
|
end
|
|
138
135
|
-- ▲ ReadonlyArray.find ▲
|
|
139
136
|
local selected = _result
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
if selected then
|
|
138
|
+
local textValue = selected.getTextValue()
|
|
139
|
+
itemTextCacheRef.current[candidateValue] = textValue
|
|
140
|
+
return textValue
|
|
143
141
|
end
|
|
144
|
-
return
|
|
142
|
+
return itemTextCacheRef.current[candidateValue]
|
|
145
143
|
end, { resolveOrderedItems })
|
|
146
144
|
local syncInputFromValue = React.useCallback(function()
|
|
147
|
-
local
|
|
145
|
+
local _result
|
|
146
|
+
if value ~= nil then
|
|
147
|
+
local _condition_2 = getItemText(value)
|
|
148
|
+
if _condition_2 == nil then
|
|
149
|
+
_condition_2 = ""
|
|
150
|
+
end
|
|
151
|
+
_result = _condition_2
|
|
152
|
+
else
|
|
153
|
+
_result = ""
|
|
154
|
+
end
|
|
155
|
+
local nextInputValue = _result
|
|
148
156
|
setInputValueState(nextInputValue)
|
|
149
|
-
end, {
|
|
157
|
+
end, { getItemText, setInputValueState, value })
|
|
150
158
|
local setOpen = React.useCallback(function(nextOpen)
|
|
151
159
|
if disabled and nextOpen then
|
|
152
160
|
return nil
|
|
153
161
|
end
|
|
154
162
|
setOpenState(nextOpen)
|
|
155
|
-
|
|
156
|
-
syncInputFromValue()
|
|
157
|
-
end
|
|
158
|
-
end, { disabled, setOpenState, syncInputFromValue })
|
|
163
|
+
end, { disabled, setOpenState })
|
|
159
164
|
local setValue = React.useCallback(function(nextValue)
|
|
160
165
|
if disabled then
|
|
161
166
|
return nil
|
|
@@ -178,45 +183,36 @@ local function ComboboxRoot(props)
|
|
|
178
183
|
return nil
|
|
179
184
|
end
|
|
180
185
|
setValueState(nextValue)
|
|
181
|
-
local
|
|
182
|
-
if
|
|
183
|
-
|
|
186
|
+
local _condition_2 = getItemText(nextValue)
|
|
187
|
+
if _condition_2 == nil then
|
|
188
|
+
_condition_2 = nextValue
|
|
184
189
|
end
|
|
185
|
-
local nextInputValue =
|
|
190
|
+
local nextInputValue = _condition_2
|
|
186
191
|
setInputValueState(nextInputValue)
|
|
187
192
|
end, { disabled, getItemText, resolveOrderedItems, setInputValueState, setValueState })
|
|
188
193
|
local setInputValue = React.useCallback(function(nextInputValue)
|
|
189
194
|
if disabled or readOnly then
|
|
190
195
|
return nil
|
|
191
196
|
end
|
|
197
|
+
if nextInputValue == inputValue then
|
|
198
|
+
return nil
|
|
199
|
+
end
|
|
192
200
|
setInputValueState(nextInputValue)
|
|
193
201
|
setOpenState(true)
|
|
194
|
-
end, { disabled, readOnly, setInputValueState, setOpenState })
|
|
195
|
-
local getFilteredItems = React.useCallback(function()
|
|
196
|
-
local query = inputValue
|
|
197
|
-
local _exp = resolveOrderedItems()
|
|
198
|
-
-- ▼ ReadonlyArray.filter ▼
|
|
199
|
-
local _newValue = {}
|
|
200
|
-
local _callback = function(item)
|
|
201
|
-
return filterFn(item.getTextValue(), query)
|
|
202
|
-
end
|
|
203
|
-
local _length = 0
|
|
204
|
-
for _k, _v in _exp do
|
|
205
|
-
if _callback(_v, _k - 1, _exp) == true then
|
|
206
|
-
_length += 1
|
|
207
|
-
_newValue[_length] = _v
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
-- ▲ ReadonlyArray.filter ▲
|
|
211
|
-
return _newValue
|
|
212
|
-
end, { filterFn, inputValue, resolveOrderedItems })
|
|
202
|
+
end, { disabled, inputValue, readOnly, setInputValueState, setOpenState })
|
|
213
203
|
React.useEffect(function()
|
|
204
|
+
if not open then
|
|
205
|
+
return nil
|
|
206
|
+
end
|
|
214
207
|
local orderedItems = resolveOrderedItems()
|
|
208
|
+
if #orderedItems == 0 then
|
|
209
|
+
return nil
|
|
210
|
+
end
|
|
215
211
|
local nextValue = resolveForcedComboboxValue(value, toOptions(orderedItems))
|
|
216
|
-
if nextValue ~= value then
|
|
212
|
+
if nextValue ~= nil and nextValue ~= value then
|
|
217
213
|
setValueState(nextValue)
|
|
218
214
|
end
|
|
219
|
-
end, { registryRevision, resolveOrderedItems, setValueState, value })
|
|
215
|
+
end, { open, registryRevision, resolveOrderedItems, setValueState, value })
|
|
220
216
|
React.useEffect(function()
|
|
221
217
|
if open then
|
|
222
218
|
return nil
|
|
@@ -235,17 +231,15 @@ local function ComboboxRoot(props)
|
|
|
235
231
|
disabled = disabled,
|
|
236
232
|
readOnly = readOnly,
|
|
237
233
|
required = required,
|
|
238
|
-
loop = loop,
|
|
239
234
|
filterFn = filterFn,
|
|
235
|
+
anchorRef = anchorRef,
|
|
240
236
|
triggerRef = triggerRef,
|
|
241
237
|
inputRef = inputRef,
|
|
242
238
|
contentRef = contentRef,
|
|
243
239
|
registerItem = registerItem,
|
|
244
|
-
getOrderedItems = resolveOrderedItems,
|
|
245
|
-
getFilteredItems = getFilteredItems,
|
|
246
240
|
getItemText = getItemText,
|
|
247
241
|
}
|
|
248
|
-
end, { disabled, filterFn,
|
|
242
|
+
end, { disabled, filterFn, getItemText, inputValue, open, readOnly, registerItem, required, resolveOrderedItems, setInputValue, setOpen, setValue, syncInputFromValue, value })
|
|
249
243
|
return React.createElement(ComboboxContextProvider, {
|
|
250
244
|
value = contextValue,
|
|
251
245
|
}, props.children)
|
|
@@ -14,8 +14,20 @@ local function ComboboxTrigger(props)
|
|
|
14
14
|
local comboboxContext = useComboboxContext()
|
|
15
15
|
local disabled = comboboxContext.disabled or props.disabled == true
|
|
16
16
|
local setTriggerRef = React.useCallback(function(instance)
|
|
17
|
-
comboboxContext.triggerRef.current
|
|
18
|
-
|
|
17
|
+
local previousTrigger = comboboxContext.triggerRef.current
|
|
18
|
+
local nextTrigger = toGuiObject(instance)
|
|
19
|
+
comboboxContext.triggerRef.current = nextTrigger
|
|
20
|
+
if comboboxContext.inputRef.current then
|
|
21
|
+
return nil
|
|
22
|
+
end
|
|
23
|
+
if nextTrigger then
|
|
24
|
+
comboboxContext.anchorRef.current = nextTrigger
|
|
25
|
+
return nil
|
|
26
|
+
end
|
|
27
|
+
if comboboxContext.anchorRef.current == previousTrigger then
|
|
28
|
+
comboboxContext.anchorRef.current = nil
|
|
29
|
+
end
|
|
30
|
+
end, { comboboxContext.anchorRef, comboboxContext.inputRef, comboboxContext.triggerRef })
|
|
19
31
|
local handleActivated = React.useCallback(function()
|
|
20
32
|
if disabled then
|
|
21
33
|
return nil
|
|
@@ -29,10 +41,6 @@ local function ComboboxTrigger(props)
|
|
|
29
41
|
local keyCode = inputObject.KeyCode
|
|
30
42
|
if keyCode == Enum.KeyCode.Return or keyCode == Enum.KeyCode.Space then
|
|
31
43
|
comboboxContext.setOpen(not comboboxContext.open)
|
|
32
|
-
return nil
|
|
33
|
-
end
|
|
34
|
-
if keyCode == Enum.KeyCode.Down or keyCode == Enum.KeyCode.Up then
|
|
35
|
-
comboboxContext.setOpen(true)
|
|
36
44
|
end
|
|
37
45
|
end, { comboboxContext, disabled })
|
|
38
46
|
local eventHandlers = React.useMemo(function()
|
|
@@ -49,7 +57,7 @@ local function ComboboxTrigger(props)
|
|
|
49
57
|
return React.createElement(Slot, {
|
|
50
58
|
Active = not disabled,
|
|
51
59
|
Event = eventHandlers,
|
|
52
|
-
Selectable =
|
|
60
|
+
Selectable = false,
|
|
53
61
|
ref = setTriggerRef,
|
|
54
62
|
}, child)
|
|
55
63
|
end
|
|
@@ -59,7 +67,7 @@ local function ComboboxTrigger(props)
|
|
|
59
67
|
BackgroundColor3 = Color3.fromRGB(41, 48, 63),
|
|
60
68
|
BorderSizePixel = 0,
|
|
61
69
|
Event = eventHandlers,
|
|
62
|
-
Selectable =
|
|
70
|
+
Selectable = false,
|
|
63
71
|
Size = UDim2.fromOffset(220, 36),
|
|
64
72
|
Text = "Combobox",
|
|
65
73
|
TextColor3 = if disabled then Color3.fromRGB(140, 148, 164) else Color3.fromRGB(235, 241, 248),
|
package/out/Combobox/types.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ export type ComboboxItemRegistration = {
|
|
|
9
9
|
id: number;
|
|
10
10
|
value: string;
|
|
11
11
|
order: number;
|
|
12
|
-
getNode: () => GuiObject | undefined;
|
|
13
12
|
getDisabled: () => boolean;
|
|
14
13
|
getTextValue: () => string;
|
|
15
14
|
};
|
|
@@ -24,14 +23,12 @@ export type ComboboxContextValue = {
|
|
|
24
23
|
disabled: boolean;
|
|
25
24
|
readOnly: boolean;
|
|
26
25
|
required: boolean;
|
|
27
|
-
loop: boolean;
|
|
28
26
|
filterFn: ComboboxFilterFn;
|
|
27
|
+
anchorRef: React.MutableRefObject<GuiObject | undefined>;
|
|
29
28
|
triggerRef: React.MutableRefObject<GuiObject | undefined>;
|
|
30
29
|
inputRef: React.MutableRefObject<TextBox | undefined>;
|
|
31
30
|
contentRef: React.MutableRefObject<GuiObject | undefined>;
|
|
32
31
|
registerItem: (item: ComboboxItemRegistration) => () => void;
|
|
33
|
-
getOrderedItems: () => Array<ComboboxItemRegistration>;
|
|
34
|
-
getFilteredItems: () => Array<ComboboxItemRegistration>;
|
|
35
32
|
getItemText: (value: string) => string | undefined;
|
|
36
33
|
};
|
|
37
34
|
export type ComboboxProps = {
|
|
@@ -47,7 +44,6 @@ export type ComboboxProps = {
|
|
|
47
44
|
disabled?: boolean;
|
|
48
45
|
readOnly?: boolean;
|
|
49
46
|
required?: boolean;
|
|
50
|
-
loop?: boolean;
|
|
51
47
|
filterFn?: ComboboxFilterFn;
|
|
52
48
|
children?: React.ReactNode;
|
|
53
49
|
};
|
|
@@ -79,7 +75,6 @@ export type ComboboxContentProps = {
|
|
|
79
75
|
placement?: PopperPlacement;
|
|
80
76
|
offset?: Vector2;
|
|
81
77
|
padding?: number;
|
|
82
|
-
onEscapeKeyDown?: (event: LayerInteractEvent) => void;
|
|
83
78
|
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
84
79
|
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
85
80
|
children?: React.ReactNode;
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lattice-ui/combobox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "out/init.luau",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@lattice-ui/core": "0.
|
|
9
|
-
"@lattice-ui/layer": "0.
|
|
10
|
-
"@lattice-ui/
|
|
11
|
-
"@lattice-ui/popper": "0.3.2"
|
|
8
|
+
"@lattice-ui/core": "0.4.1",
|
|
9
|
+
"@lattice-ui/layer": "0.4.1",
|
|
10
|
+
"@lattice-ui/popper": "0.4.1"
|
|
12
11
|
},
|
|
13
12
|
"devDependencies": {
|
|
14
13
|
"@rbxts/react": "17.3.7-ts.1",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
-
import { RovingFocusGroup } from "@lattice-ui/focus";
|
|
3
2
|
import { DismissableLayer, Presence } from "@lattice-ui/layer";
|
|
4
3
|
import { usePopper } from "@lattice-ui/popper";
|
|
5
4
|
import { useComboboxContext } from "./context";
|
|
@@ -13,7 +12,7 @@ type ComboboxContentImplProps = {
|
|
|
13
12
|
placement?: ComboboxContentProps["placement"];
|
|
14
13
|
offset?: ComboboxContentProps["offset"];
|
|
15
14
|
padding?: ComboboxContentProps["padding"];
|
|
16
|
-
} & Pick<ComboboxContentProps, "children" | "
|
|
15
|
+
} & Pick<ComboboxContentProps, "children" | "onInteractOutside" | "onPointerDownOutside">;
|
|
17
16
|
|
|
18
17
|
function toGuiObject(instance: Instance | undefined) {
|
|
19
18
|
if (!instance || !instance.IsA("GuiObject")) {
|
|
@@ -27,7 +26,7 @@ function ComboboxContentImpl(props: ComboboxContentImplProps) {
|
|
|
27
26
|
const comboboxContext = useComboboxContext();
|
|
28
27
|
|
|
29
28
|
const popper = usePopper({
|
|
30
|
-
anchorRef: comboboxContext.
|
|
29
|
+
anchorRef: comboboxContext.anchorRef,
|
|
31
30
|
contentRef: comboboxContext.contentRef,
|
|
32
31
|
placement: props.placement,
|
|
33
32
|
offset: props.offset,
|
|
@@ -72,15 +71,13 @@ function ComboboxContentImpl(props: ComboboxContentImplProps) {
|
|
|
72
71
|
return (
|
|
73
72
|
<DismissableLayer
|
|
74
73
|
enabled={props.enabled}
|
|
74
|
+
insideRefs={[comboboxContext.triggerRef, comboboxContext.inputRef]}
|
|
75
75
|
modal={false}
|
|
76
76
|
onDismiss={props.onDismiss}
|
|
77
|
-
onEscapeKeyDown={props.onEscapeKeyDown}
|
|
78
77
|
onInteractOutside={props.onInteractOutside}
|
|
79
78
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
80
79
|
>
|
|
81
|
-
|
|
82
|
-
{contentNode}
|
|
83
|
-
</RovingFocusGroup>
|
|
80
|
+
{contentNode}
|
|
84
81
|
</DismissableLayer>
|
|
85
82
|
);
|
|
86
83
|
}
|
|
@@ -105,7 +102,6 @@ export function ComboboxContent(props: ComboboxContentProps) {
|
|
|
105
102
|
enabled={open}
|
|
106
103
|
offset={props.offset}
|
|
107
104
|
onDismiss={handleDismiss}
|
|
108
|
-
onEscapeKeyDown={props.onEscapeKeyDown}
|
|
109
105
|
onInteractOutside={props.onInteractOutside}
|
|
110
106
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
111
107
|
padding={props.padding}
|
|
@@ -127,7 +123,6 @@ export function ComboboxContent(props: ComboboxContentProps) {
|
|
|
127
123
|
enabled={state.isPresent}
|
|
128
124
|
offset={props.offset}
|
|
129
125
|
onDismiss={handleDismiss}
|
|
130
|
-
onEscapeKeyDown={props.onEscapeKeyDown}
|
|
131
126
|
onInteractOutside={props.onInteractOutside}
|
|
132
127
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
133
128
|
padding={props.padding}
|
|
@@ -17,13 +17,29 @@ export function ComboboxInput(props: ComboboxInputProps) {
|
|
|
17
17
|
|
|
18
18
|
const setInputRef = React.useCallback(
|
|
19
19
|
(instance: Instance | undefined) => {
|
|
20
|
-
comboboxContext.inputRef.current
|
|
20
|
+
const previousInput = comboboxContext.inputRef.current;
|
|
21
|
+
const nextInput = toTextBox(instance);
|
|
22
|
+
|
|
23
|
+
comboboxContext.inputRef.current = nextInput;
|
|
24
|
+
|
|
25
|
+
if (nextInput) {
|
|
26
|
+
comboboxContext.anchorRef.current = nextInput;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (comboboxContext.anchorRef.current === previousInput) {
|
|
31
|
+
comboboxContext.anchorRef.current = comboboxContext.triggerRef.current;
|
|
32
|
+
}
|
|
21
33
|
},
|
|
22
|
-
[comboboxContext.inputRef],
|
|
34
|
+
[comboboxContext.anchorRef, comboboxContext.inputRef, comboboxContext.triggerRef],
|
|
23
35
|
);
|
|
24
36
|
|
|
25
37
|
const handleTextChanged = React.useCallback(
|
|
26
38
|
(textBox: TextBox) => {
|
|
39
|
+
if (textBox.Text === comboboxContext.inputValue) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
if (disabled || readOnly) {
|
|
28
44
|
if (textBox.Text !== comboboxContext.inputValue) {
|
|
29
45
|
textBox.Text = comboboxContext.inputValue;
|
|
@@ -37,25 +53,6 @@ export function ComboboxInput(props: ComboboxInputProps) {
|
|
|
37
53
|
[comboboxContext, disabled, readOnly],
|
|
38
54
|
);
|
|
39
55
|
|
|
40
|
-
const handleFocusLost = React.useCallback(() => {
|
|
41
|
-
comboboxContext.setOpen(false);
|
|
42
|
-
comboboxContext.syncInputFromValue();
|
|
43
|
-
}, [comboboxContext]);
|
|
44
|
-
|
|
45
|
-
const handleInputBegan = React.useCallback(
|
|
46
|
-
(_rbx: GuiObject, inputObject: InputObject) => {
|
|
47
|
-
if (disabled) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const keyCode = inputObject.KeyCode;
|
|
52
|
-
if (keyCode === Enum.KeyCode.Down || keyCode === Enum.KeyCode.Up) {
|
|
53
|
-
comboboxContext.setOpen(true);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
[comboboxContext, disabled],
|
|
57
|
-
);
|
|
58
|
-
|
|
59
56
|
const sharedProps = {
|
|
60
57
|
Active: !disabled,
|
|
61
58
|
ClearTextOnFocus: false,
|
|
@@ -66,10 +63,6 @@ export function ComboboxInput(props: ComboboxInputProps) {
|
|
|
66
63
|
Change: {
|
|
67
64
|
Text: handleTextChanged,
|
|
68
65
|
},
|
|
69
|
-
Event: {
|
|
70
|
-
FocusLost: handleFocusLost,
|
|
71
|
-
InputBegan: handleInputBegan,
|
|
72
|
-
},
|
|
73
66
|
ref: setInputRef,
|
|
74
67
|
};
|
|
75
68
|
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
-
import { RovingFocusItem } from "@lattice-ui/focus";
|
|
3
2
|
import { useComboboxContext } from "./context";
|
|
4
3
|
import type { ComboboxItemProps } from "./types";
|
|
5
4
|
|
|
6
5
|
let nextItemId = 0;
|
|
7
6
|
let nextItemOrder = 0;
|
|
8
7
|
|
|
9
|
-
function toGuiObject(instance: Instance | undefined) {
|
|
10
|
-
if (!instance || !instance.IsA("GuiObject")) {
|
|
11
|
-
return undefined;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return instance;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
8
|
export function ComboboxItem(props: ComboboxItemProps) {
|
|
18
9
|
const comboboxContext = useComboboxContext();
|
|
19
|
-
const itemRef = React.useRef<GuiObject>();
|
|
20
|
-
|
|
21
10
|
const itemQueryMatch = comboboxContext.filterFn(props.textValue ?? props.value, comboboxContext.inputValue);
|
|
22
|
-
const disabled = comboboxContext.disabled || props.disabled === true
|
|
11
|
+
const disabled = comboboxContext.disabled || props.disabled === true;
|
|
12
|
+
const interactionDisabled = disabled || !itemQueryMatch;
|
|
23
13
|
const textValue = props.textValue ?? props.value;
|
|
24
14
|
|
|
25
15
|
const disabledRef = React.useRef(disabled);
|
|
@@ -50,28 +40,23 @@ export function ComboboxItem(props: ComboboxItemProps) {
|
|
|
50
40
|
id: itemIdRef.current,
|
|
51
41
|
value: props.value,
|
|
52
42
|
order: itemOrderRef.current,
|
|
53
|
-
getNode: () => itemRef.current,
|
|
54
43
|
getDisabled: () => disabledRef.current,
|
|
55
44
|
getTextValue: () => textValueRef.current,
|
|
56
45
|
});
|
|
57
46
|
}, [comboboxContext, props.value]);
|
|
58
47
|
|
|
59
|
-
const setItemRef = React.useCallback((instance: Instance | undefined) => {
|
|
60
|
-
itemRef.current = toGuiObject(instance);
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
48
|
const handleSelect = React.useCallback(() => {
|
|
64
|
-
if (
|
|
49
|
+
if (interactionDisabled) {
|
|
65
50
|
return;
|
|
66
51
|
}
|
|
67
52
|
|
|
68
53
|
comboboxContext.setValue(props.value);
|
|
69
54
|
comboboxContext.setOpen(false);
|
|
70
|
-
}, [comboboxContext,
|
|
55
|
+
}, [comboboxContext, interactionDisabled, props.value]);
|
|
71
56
|
|
|
72
57
|
const handleInputBegan = React.useCallback(
|
|
73
58
|
(_rbx: GuiObject, inputObject: InputObject) => {
|
|
74
|
-
if (
|
|
59
|
+
if (interactionDisabled) {
|
|
75
60
|
return;
|
|
76
61
|
}
|
|
77
62
|
|
|
@@ -83,7 +68,7 @@ export function ComboboxItem(props: ComboboxItemProps) {
|
|
|
83
68
|
comboboxContext.setValue(props.value);
|
|
84
69
|
comboboxContext.setOpen(false);
|
|
85
70
|
},
|
|
86
|
-
[comboboxContext,
|
|
71
|
+
[comboboxContext, interactionDisabled, props.value],
|
|
87
72
|
);
|
|
88
73
|
|
|
89
74
|
const eventHandlers = React.useMemo(
|
|
@@ -101,34 +86,29 @@ export function ComboboxItem(props: ComboboxItemProps) {
|
|
|
101
86
|
}
|
|
102
87
|
|
|
103
88
|
return (
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</Slot>
|
|
108
|
-
</RovingFocusItem>
|
|
89
|
+
<Slot Active={!interactionDisabled} Event={eventHandlers} Selectable={false} Visible={itemQueryMatch}>
|
|
90
|
+
{child}
|
|
91
|
+
</Slot>
|
|
109
92
|
);
|
|
110
93
|
}
|
|
111
94
|
|
|
112
95
|
return (
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
{props.children}
|
|
131
|
-
</textbutton>
|
|
132
|
-
</RovingFocusItem>
|
|
96
|
+
<textbutton
|
|
97
|
+
Active={!interactionDisabled}
|
|
98
|
+
AutoButtonColor={false}
|
|
99
|
+
BackgroundColor3={Color3.fromRGB(47, 53, 68)}
|
|
100
|
+
BorderSizePixel={0}
|
|
101
|
+
Event={eventHandlers}
|
|
102
|
+
Selectable={false}
|
|
103
|
+
Size={UDim2.fromOffset(220, 32)}
|
|
104
|
+
Text={textValue}
|
|
105
|
+
TextColor3={interactionDisabled ? Color3.fromRGB(134, 141, 156) : Color3.fromRGB(234, 239, 247)}
|
|
106
|
+
TextSize={15}
|
|
107
|
+
TextXAlignment={Enum.TextXAlignment.Left}
|
|
108
|
+
Visible={itemQueryMatch}
|
|
109
|
+
>
|
|
110
|
+
<uipadding PaddingLeft={new UDim(0, 10)} PaddingRight={new UDim(0, 10)} />
|
|
111
|
+
{props.children}
|
|
112
|
+
</textbutton>
|
|
133
113
|
);
|
|
134
114
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { React, useControllableState } from "@lattice-ui/core";
|
|
2
2
|
import { ComboboxContextProvider } from "./context";
|
|
3
|
-
import {
|
|
4
|
-
type ComboboxOption,
|
|
5
|
-
defaultComboboxFilter,
|
|
6
|
-
resolveComboboxInputValue,
|
|
7
|
-
resolveForcedComboboxValue,
|
|
8
|
-
} from "./logic";
|
|
3
|
+
import { type ComboboxOption, defaultComboboxFilter, resolveForcedComboboxValue } from "./logic";
|
|
9
4
|
import type { ComboboxItemRegistration, ComboboxProps } from "./types";
|
|
10
5
|
|
|
11
6
|
function getOrderedItems(items: Array<ComboboxItemRegistration>) {
|
|
@@ -48,18 +43,20 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
48
43
|
const disabled = props.disabled === true;
|
|
49
44
|
const readOnly = props.readOnly === true;
|
|
50
45
|
const required = props.required === true;
|
|
51
|
-
const loop = props.loop ?? true;
|
|
52
46
|
const filterFn = props.filterFn ?? defaultComboboxFilter;
|
|
53
47
|
|
|
48
|
+
const anchorRef = React.useRef<GuiObject>();
|
|
54
49
|
const triggerRef = React.useRef<GuiObject>();
|
|
55
50
|
const inputRef = React.useRef<TextBox>();
|
|
56
51
|
const contentRef = React.useRef<GuiObject>();
|
|
57
52
|
|
|
58
53
|
const itemEntriesRef = React.useRef<Array<ComboboxItemRegistration>>([]);
|
|
54
|
+
const itemTextCacheRef = React.useRef<Record<string, string>>({});
|
|
59
55
|
const [registryRevision, setRegistryRevision] = React.useState(0);
|
|
60
56
|
|
|
61
57
|
const registerItem = React.useCallback((item: ComboboxItemRegistration) => {
|
|
62
58
|
itemEntriesRef.current.push(item);
|
|
59
|
+
itemTextCacheRef.current[item.value] = item.getTextValue();
|
|
63
60
|
setRegistryRevision((revision) => revision + 1);
|
|
64
61
|
|
|
65
62
|
return () => {
|
|
@@ -78,15 +75,21 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
78
75
|
const getItemText = React.useCallback(
|
|
79
76
|
(candidateValue: string) => {
|
|
80
77
|
const selected = resolveOrderedItems().find((item) => item.value === candidateValue);
|
|
81
|
-
|
|
78
|
+
if (selected) {
|
|
79
|
+
const textValue = selected.getTextValue();
|
|
80
|
+
itemTextCacheRef.current[candidateValue] = textValue;
|
|
81
|
+
return textValue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return itemTextCacheRef.current[candidateValue];
|
|
82
85
|
},
|
|
83
86
|
[resolveOrderedItems],
|
|
84
87
|
);
|
|
85
88
|
|
|
86
89
|
const syncInputFromValue = React.useCallback(() => {
|
|
87
|
-
const nextInputValue =
|
|
90
|
+
const nextInputValue = value !== undefined ? (getItemText(value) ?? "") : "";
|
|
88
91
|
setInputValueState(nextInputValue);
|
|
89
|
-
}, [
|
|
92
|
+
}, [getItemText, setInputValueState, value]);
|
|
90
93
|
|
|
91
94
|
const setOpen = React.useCallback(
|
|
92
95
|
(nextOpen: boolean) => {
|
|
@@ -95,12 +98,8 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
setOpenState(nextOpen);
|
|
98
|
-
|
|
99
|
-
if (!nextOpen) {
|
|
100
|
-
syncInputFromValue();
|
|
101
|
-
}
|
|
102
101
|
},
|
|
103
|
-
[disabled, setOpenState
|
|
102
|
+
[disabled, setOpenState],
|
|
104
103
|
);
|
|
105
104
|
|
|
106
105
|
const setValue = React.useCallback(
|
|
@@ -127,24 +126,31 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
127
126
|
return;
|
|
128
127
|
}
|
|
129
128
|
|
|
129
|
+
if (nextInputValue === inputValue) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
130
133
|
setInputValueState(nextInputValue);
|
|
131
134
|
setOpenState(true);
|
|
132
135
|
},
|
|
133
|
-
[disabled, readOnly, setInputValueState, setOpenState],
|
|
136
|
+
[disabled, inputValue, readOnly, setInputValueState, setOpenState],
|
|
134
137
|
);
|
|
135
138
|
|
|
136
|
-
const getFilteredItems = React.useCallback(() => {
|
|
137
|
-
const query = inputValue;
|
|
138
|
-
return resolveOrderedItems().filter((item) => filterFn(item.getTextValue(), query));
|
|
139
|
-
}, [filterFn, inputValue, resolveOrderedItems]);
|
|
140
|
-
|
|
141
139
|
React.useEffect(() => {
|
|
140
|
+
if (!open) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
142
144
|
const orderedItems = resolveOrderedItems();
|
|
145
|
+
if (orderedItems.size() === 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
143
149
|
const nextValue = resolveForcedComboboxValue(value, toOptions(orderedItems));
|
|
144
|
-
if (nextValue !== value) {
|
|
150
|
+
if (nextValue !== undefined && nextValue !== value) {
|
|
145
151
|
setValueState(nextValue);
|
|
146
152
|
}
|
|
147
|
-
}, [registryRevision, resolveOrderedItems, setValueState, value]);
|
|
153
|
+
}, [open, registryRevision, resolveOrderedItems, setValueState, value]);
|
|
148
154
|
|
|
149
155
|
React.useEffect(() => {
|
|
150
156
|
if (open) {
|
|
@@ -166,23 +172,19 @@ export function ComboboxRoot(props: ComboboxProps) {
|
|
|
166
172
|
disabled,
|
|
167
173
|
readOnly,
|
|
168
174
|
required,
|
|
169
|
-
loop,
|
|
170
175
|
filterFn,
|
|
176
|
+
anchorRef,
|
|
171
177
|
triggerRef,
|
|
172
178
|
inputRef,
|
|
173
179
|
contentRef,
|
|
174
180
|
registerItem,
|
|
175
|
-
getOrderedItems: resolveOrderedItems,
|
|
176
|
-
getFilteredItems,
|
|
177
181
|
getItemText,
|
|
178
182
|
}),
|
|
179
183
|
[
|
|
180
184
|
disabled,
|
|
181
185
|
filterFn,
|
|
182
|
-
getFilteredItems,
|
|
183
186
|
getItemText,
|
|
184
187
|
inputValue,
|
|
185
|
-
loop,
|
|
186
188
|
open,
|
|
187
189
|
readOnly,
|
|
188
190
|
registerItem,
|
|
@@ -16,9 +16,25 @@ export function ComboboxTrigger(props: ComboboxTriggerProps) {
|
|
|
16
16
|
|
|
17
17
|
const setTriggerRef = React.useCallback(
|
|
18
18
|
(instance: Instance | undefined) => {
|
|
19
|
-
comboboxContext.triggerRef.current
|
|
19
|
+
const previousTrigger = comboboxContext.triggerRef.current;
|
|
20
|
+
const nextTrigger = toGuiObject(instance);
|
|
21
|
+
|
|
22
|
+
comboboxContext.triggerRef.current = nextTrigger;
|
|
23
|
+
|
|
24
|
+
if (comboboxContext.inputRef.current) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (nextTrigger) {
|
|
29
|
+
comboboxContext.anchorRef.current = nextTrigger;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (comboboxContext.anchorRef.current === previousTrigger) {
|
|
34
|
+
comboboxContext.anchorRef.current = undefined;
|
|
35
|
+
}
|
|
20
36
|
},
|
|
21
|
-
[comboboxContext.triggerRef],
|
|
37
|
+
[comboboxContext.anchorRef, comboboxContext.inputRef, comboboxContext.triggerRef],
|
|
22
38
|
);
|
|
23
39
|
|
|
24
40
|
const handleActivated = React.useCallback(() => {
|
|
@@ -38,11 +54,6 @@ export function ComboboxTrigger(props: ComboboxTriggerProps) {
|
|
|
38
54
|
const keyCode = inputObject.KeyCode;
|
|
39
55
|
if (keyCode === Enum.KeyCode.Return || keyCode === Enum.KeyCode.Space) {
|
|
40
56
|
comboboxContext.setOpen(!comboboxContext.open);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (keyCode === Enum.KeyCode.Down || keyCode === Enum.KeyCode.Up) {
|
|
45
|
-
comboboxContext.setOpen(true);
|
|
46
57
|
}
|
|
47
58
|
},
|
|
48
59
|
[comboboxContext, disabled],
|
|
@@ -63,7 +74,7 @@ export function ComboboxTrigger(props: ComboboxTriggerProps) {
|
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
return (
|
|
66
|
-
<Slot Active={!disabled} Event={eventHandlers} Selectable={
|
|
77
|
+
<Slot Active={!disabled} Event={eventHandlers} Selectable={false} ref={setTriggerRef}>
|
|
67
78
|
{child}
|
|
68
79
|
</Slot>
|
|
69
80
|
);
|
|
@@ -76,7 +87,7 @@ export function ComboboxTrigger(props: ComboboxTriggerProps) {
|
|
|
76
87
|
BackgroundColor3={Color3.fromRGB(41, 48, 63)}
|
|
77
88
|
BorderSizePixel={0}
|
|
78
89
|
Event={eventHandlers}
|
|
79
|
-
Selectable={
|
|
90
|
+
Selectable={false}
|
|
80
91
|
Size={UDim2.fromOffset(220, 36)}
|
|
81
92
|
Text="Combobox"
|
|
82
93
|
TextColor3={disabled ? Color3.fromRGB(140, 148, 164) : Color3.fromRGB(235, 241, 248)}
|
package/src/Combobox/types.ts
CHANGED
|
@@ -12,7 +12,6 @@ export type ComboboxItemRegistration = {
|
|
|
12
12
|
id: number;
|
|
13
13
|
value: string;
|
|
14
14
|
order: number;
|
|
15
|
-
getNode: () => GuiObject | undefined;
|
|
16
15
|
getDisabled: () => boolean;
|
|
17
16
|
getTextValue: () => string;
|
|
18
17
|
};
|
|
@@ -28,14 +27,12 @@ export type ComboboxContextValue = {
|
|
|
28
27
|
disabled: boolean;
|
|
29
28
|
readOnly: boolean;
|
|
30
29
|
required: boolean;
|
|
31
|
-
loop: boolean;
|
|
32
30
|
filterFn: ComboboxFilterFn;
|
|
31
|
+
anchorRef: React.MutableRefObject<GuiObject | undefined>;
|
|
33
32
|
triggerRef: React.MutableRefObject<GuiObject | undefined>;
|
|
34
33
|
inputRef: React.MutableRefObject<TextBox | undefined>;
|
|
35
34
|
contentRef: React.MutableRefObject<GuiObject | undefined>;
|
|
36
35
|
registerItem: (item: ComboboxItemRegistration) => () => void;
|
|
37
|
-
getOrderedItems: () => Array<ComboboxItemRegistration>;
|
|
38
|
-
getFilteredItems: () => Array<ComboboxItemRegistration>;
|
|
39
36
|
getItemText: (value: string) => string | undefined;
|
|
40
37
|
};
|
|
41
38
|
|
|
@@ -52,7 +49,6 @@ export type ComboboxProps = {
|
|
|
52
49
|
disabled?: boolean;
|
|
53
50
|
readOnly?: boolean;
|
|
54
51
|
required?: boolean;
|
|
55
|
-
loop?: boolean;
|
|
56
52
|
filterFn?: ComboboxFilterFn;
|
|
57
53
|
children?: React.ReactNode;
|
|
58
54
|
};
|
|
@@ -89,7 +85,6 @@ export type ComboboxContentProps = {
|
|
|
89
85
|
placement?: PopperPlacement;
|
|
90
86
|
offset?: Vector2;
|
|
91
87
|
padding?: number;
|
|
92
|
-
onEscapeKeyDown?: (event: LayerInteractEvent) => void;
|
|
93
88
|
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
94
89
|
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
95
90
|
children?: React.ReactNode;
|