@lattice-ui/select 0.3.2 → 0.4.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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-typecheck.log +5 -0
- package/README.md +2 -2
- package/out/Select/SelectContent.luau +69 -159
- package/out/Select/SelectItem.luau +6 -28
- package/out/Select/SelectRoot.luau +1 -8
- package/out/Select/SelectTrigger.luau +5 -8
- package/out/Select/types.d.ts +0 -5
- package/out/index.d.ts +1 -1
- package/package.json +5 -5
- package/src/Select/SelectContent.tsx +78 -143
- package/src/Select/SelectItem.tsx +19 -40
- package/src/Select/SelectRoot.tsx +1 -4
- package/src/Select/SelectTrigger.tsx +5 -9
- package/src/Select/types.ts +0 -5
- package/src/index.ts +11 -12
package/README.md
CHANGED
|
@@ -19,5 +19,5 @@ Headless single-select primitives built for Roblox UI.
|
|
|
19
19
|
|
|
20
20
|
- Single value only in this release.
|
|
21
21
|
- Supports controlled/uncontrolled `value` and `open`.
|
|
22
|
-
- Content uses dismissable-layer semantics (outside pointer
|
|
23
|
-
-
|
|
22
|
+
- Content uses dismissable-layer semantics (outside pointer dismiss).
|
|
23
|
+
- No built-in Roblox native selection or directional keyboard navigation.
|
|
@@ -3,112 +3,24 @@ 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
|
|
6
|
+
local FocusScope = TS.import(script, TS.getModule(script, "@lattice-ui", "focus").out).FocusScope
|
|
7
7
|
local _layer = TS.import(script, TS.getModule(script, "@lattice-ui", "layer").out)
|
|
8
8
|
local DismissableLayer = _layer.DismissableLayer
|
|
9
9
|
local Presence = _layer.Presence
|
|
10
10
|
local usePopper = TS.import(script, TS.getModule(script, "@lattice-ui", "popper").out).usePopper
|
|
11
11
|
local useSelectContext = TS.import(script, script.Parent, "context").useSelectContext
|
|
12
|
-
local
|
|
13
|
-
local
|
|
14
|
-
local
|
|
15
|
-
|
|
16
|
-
One = "1",
|
|
17
|
-
Two = "2",
|
|
18
|
-
Three = "3",
|
|
19
|
-
Four = "4",
|
|
20
|
-
Five = "5",
|
|
21
|
-
Six = "6",
|
|
22
|
-
Seven = "7",
|
|
23
|
-
Eight = "8",
|
|
24
|
-
Nine = "9",
|
|
25
|
-
}
|
|
12
|
+
local TweenService = game:GetService("TweenService")
|
|
13
|
+
local OPEN_TWEEN_INFO = TweenInfo.new(0.12, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
|
|
14
|
+
local CLOSE_TWEEN_INFO = TweenInfo.new(0.09, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
|
|
15
|
+
local CONTENT_OPEN_Y_OFFSET = 6
|
|
26
16
|
local function toGuiObject(instance)
|
|
27
17
|
if not instance or not instance:IsA("GuiObject") then
|
|
28
18
|
return nil
|
|
29
19
|
end
|
|
30
20
|
return instance
|
|
31
21
|
end
|
|
32
|
-
local function
|
|
33
|
-
|
|
34
|
-
return " "
|
|
35
|
-
end
|
|
36
|
-
local digitCharacter = digitKeyMap[keyCode.Name]
|
|
37
|
-
if digitCharacter ~= nil then
|
|
38
|
-
return digitCharacter
|
|
39
|
-
end
|
|
40
|
-
if #keyCode.Name == 1 then
|
|
41
|
-
return string.lower(keyCode.Name)
|
|
42
|
-
end
|
|
43
|
-
return nil
|
|
44
|
-
end
|
|
45
|
-
local function startsWithIgnoreCase(value, query)
|
|
46
|
-
if #query == 0 then
|
|
47
|
-
return false
|
|
48
|
-
end
|
|
49
|
-
local normalizedValue = string.lower(value)
|
|
50
|
-
return string.sub(normalizedValue, 1, #query) == query
|
|
51
|
-
end
|
|
52
|
-
local function findCurrentIndex(items, selectedObject)
|
|
53
|
-
if not selectedObject then
|
|
54
|
-
return -1
|
|
55
|
-
end
|
|
56
|
-
-- ▼ ReadonlyArray.findIndex ▼
|
|
57
|
-
local _callback = function(item)
|
|
58
|
-
local node = item.getNode()
|
|
59
|
-
if not node then
|
|
60
|
-
return false
|
|
61
|
-
end
|
|
62
|
-
return selectedObject == node or selectedObject:IsDescendantOf(node)
|
|
63
|
-
end
|
|
64
|
-
local _result = -1
|
|
65
|
-
for _i, _v in items do
|
|
66
|
-
if _callback(_v, _i - 1, items) == true then
|
|
67
|
-
_result = _i - 1
|
|
68
|
-
break
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
-- ▲ ReadonlyArray.findIndex ▲
|
|
72
|
-
return _result
|
|
73
|
-
end
|
|
74
|
-
local function findTypeaheadMatch(items, query, startIndex)
|
|
75
|
-
local itemCount = #items
|
|
76
|
-
if itemCount == 0 then
|
|
77
|
-
return -1
|
|
78
|
-
end
|
|
79
|
-
do
|
|
80
|
-
local attempts = 0
|
|
81
|
-
local _shouldIncrement = false
|
|
82
|
-
while true do
|
|
83
|
-
if _shouldIncrement then
|
|
84
|
-
attempts += 1
|
|
85
|
-
else
|
|
86
|
-
_shouldIncrement = true
|
|
87
|
-
end
|
|
88
|
-
if not (attempts < itemCount) then
|
|
89
|
-
break
|
|
90
|
-
end
|
|
91
|
-
local candidateIndex = (startIndex + attempts) % itemCount
|
|
92
|
-
local candidate = items[candidateIndex + 1]
|
|
93
|
-
if not candidate or candidate.getDisabled() then
|
|
94
|
-
continue
|
|
95
|
-
end
|
|
96
|
-
if startsWithIgnoreCase(candidate.getTextValue(), query) then
|
|
97
|
-
return candidateIndex
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
return -1
|
|
102
|
-
end
|
|
103
|
-
local function focusItem(item)
|
|
104
|
-
if not item then
|
|
105
|
-
return nil
|
|
106
|
-
end
|
|
107
|
-
local node = item.getNode()
|
|
108
|
-
if not node or not node.Selectable then
|
|
109
|
-
return nil
|
|
110
|
-
end
|
|
111
|
-
GuiService.SelectedObject = node
|
|
22
|
+
local function withVerticalOffset(position, offset)
|
|
23
|
+
return UDim2.new(position.X.Scale, position.X.Offset, position.Y.Scale, position.Y.Offset + offset)
|
|
112
24
|
end
|
|
113
25
|
local function SelectContentImpl(props)
|
|
114
26
|
local selectContext = useSelectContext()
|
|
@@ -123,69 +35,68 @@ local function SelectContentImpl(props)
|
|
|
123
35
|
local setContentRef = React.useCallback(function(instance)
|
|
124
36
|
selectContext.contentRef.current = toGuiObject(instance)
|
|
125
37
|
end, { selectContext.contentRef })
|
|
126
|
-
local
|
|
127
|
-
local
|
|
128
|
-
React.
|
|
129
|
-
|
|
130
|
-
|
|
38
|
+
local positionTweenRef = React.useRef()
|
|
39
|
+
local tweenCompletedConnectionRef = React.useRef()
|
|
40
|
+
local previousVisibleRef = React.useRef(props.visible)
|
|
41
|
+
local previousExitingRef = React.useRef(props.exiting)
|
|
42
|
+
local clearTween = React.useCallback(function()
|
|
43
|
+
local tween = positionTweenRef.current
|
|
44
|
+
if tween then
|
|
45
|
+
tween:Cancel()
|
|
46
|
+
positionTweenRef.current = nil
|
|
131
47
|
end
|
|
132
|
-
local
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
48
|
+
local completedConnection = tweenCompletedConnectionRef.current
|
|
49
|
+
if completedConnection then
|
|
50
|
+
completedConnection:Disconnect()
|
|
51
|
+
tweenCompletedConnectionRef.current = nil
|
|
136
52
|
end
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
break
|
|
142
|
-
end
|
|
53
|
+
end, {})
|
|
54
|
+
React.useEffect(function()
|
|
55
|
+
return function()
|
|
56
|
+
clearTween()
|
|
143
57
|
end
|
|
144
|
-
|
|
145
|
-
local selectedItem = _result
|
|
146
|
-
focusItem(selectedItem)
|
|
147
|
-
end, { props.enabled, selectContext, selectContext.value })
|
|
58
|
+
end, { clearTween })
|
|
148
59
|
React.useEffect(function()
|
|
149
|
-
|
|
60
|
+
local contentNode = selectContext.contentRef.current
|
|
61
|
+
if not contentNode then
|
|
150
62
|
return nil
|
|
151
63
|
end
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if inputObject.UserInputType ~= Enum.UserInputType.Keyboard then
|
|
64
|
+
local wasVisible = previousVisibleRef.current
|
|
65
|
+
local wasExiting = previousExitingRef.current
|
|
66
|
+
previousVisibleRef.current = props.visible
|
|
67
|
+
previousExitingRef.current = props.exiting
|
|
68
|
+
if props.exiting then
|
|
69
|
+
if wasExiting then
|
|
159
70
|
return nil
|
|
160
71
|
end
|
|
161
|
-
|
|
162
|
-
local
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
connection:Disconnect()
|
|
72
|
+
clearTween()
|
|
73
|
+
local tween = TweenService:Create(contentNode, CLOSE_TWEEN_INFO, {
|
|
74
|
+
Position = withVerticalOffset(popper.position, CONTENT_OPEN_Y_OFFSET),
|
|
75
|
+
})
|
|
76
|
+
positionTweenRef.current = tween
|
|
77
|
+
tweenCompletedConnectionRef.current = tween.Completed:Connect(function(playbackState)
|
|
78
|
+
if playbackState == Enum.PlaybackState.Completed then
|
|
79
|
+
local _result = props.onExitComplete
|
|
80
|
+
if _result ~= nil then
|
|
81
|
+
_result()
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end)
|
|
85
|
+
tween:Play()
|
|
86
|
+
return nil
|
|
87
|
+
end
|
|
88
|
+
clearTween()
|
|
89
|
+
if props.visible and not wasVisible then
|
|
90
|
+
contentNode.Position = withVerticalOffset(popper.position, CONTENT_OPEN_Y_OFFSET)
|
|
91
|
+
local tween = TweenService:Create(contentNode, OPEN_TWEEN_INFO, {
|
|
92
|
+
Position = popper.position,
|
|
93
|
+
})
|
|
94
|
+
positionTweenRef.current = tween
|
|
95
|
+
tween:Play()
|
|
96
|
+
return nil
|
|
187
97
|
end
|
|
188
|
-
|
|
98
|
+
contentNode.Position = popper.position
|
|
99
|
+
end, { clearTween, popper.position, props.exiting, props.onExitComplete, props.visible, selectContext.contentRef })
|
|
189
100
|
local contentNode = if props.asChild then ((function()
|
|
190
101
|
local child = props.children
|
|
191
102
|
if not React.isValidElement(child) then
|
|
@@ -194,7 +105,7 @@ local function SelectContentImpl(props)
|
|
|
194
105
|
return React.createElement(Slot, {
|
|
195
106
|
AnchorPoint = popper.anchorPoint,
|
|
196
107
|
Position = popper.position,
|
|
197
|
-
Visible = props.visible,
|
|
108
|
+
Visible = props.visible or props.exiting,
|
|
198
109
|
ref = setContentRef,
|
|
199
110
|
}, child)
|
|
200
111
|
end)()) else (React.createElement("frame", {
|
|
@@ -203,21 +114,19 @@ local function SelectContentImpl(props)
|
|
|
203
114
|
BorderSizePixel = 0,
|
|
204
115
|
Position = popper.position,
|
|
205
116
|
Size = UDim2.fromOffset(0, 0),
|
|
206
|
-
Visible = props.visible,
|
|
117
|
+
Visible = props.visible or props.exiting,
|
|
207
118
|
ref = setContentRef,
|
|
208
119
|
}, props.children))
|
|
209
120
|
return React.createElement(DismissableLayer, {
|
|
210
121
|
enabled = props.enabled,
|
|
211
122
|
modal = false,
|
|
212
123
|
onDismiss = props.onDismiss,
|
|
213
|
-
onEscapeKeyDown = props.onEscapeKeyDown,
|
|
214
124
|
onInteractOutside = props.onInteractOutside,
|
|
215
125
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
216
|
-
}, React.createElement(
|
|
126
|
+
}, React.createElement(FocusScope, {
|
|
217
127
|
active = props.enabled,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
orientation = "vertical",
|
|
128
|
+
restoreFocus = true,
|
|
129
|
+
trapped = false,
|
|
221
130
|
}, contentNode))
|
|
222
131
|
end
|
|
223
132
|
local function SelectContent(props)
|
|
@@ -234,9 +143,9 @@ local function SelectContent(props)
|
|
|
234
143
|
return React.createElement(SelectContentImpl, {
|
|
235
144
|
asChild = props.asChild,
|
|
236
145
|
enabled = open,
|
|
146
|
+
exiting = false,
|
|
237
147
|
offset = props.offset,
|
|
238
148
|
onDismiss = handleDismiss,
|
|
239
|
-
onEscapeKeyDown = props.onEscapeKeyDown,
|
|
240
149
|
onInteractOutside = props.onInteractOutside,
|
|
241
150
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
242
151
|
padding = props.padding,
|
|
@@ -245,15 +154,16 @@ local function SelectContent(props)
|
|
|
245
154
|
}, props.children)
|
|
246
155
|
end
|
|
247
156
|
return React.createElement(Presence, {
|
|
248
|
-
exitFallbackMs =
|
|
157
|
+
exitFallbackMs = 180,
|
|
249
158
|
present = open,
|
|
250
159
|
render = function(state)
|
|
251
160
|
return React.createElement(SelectContentImpl, {
|
|
252
161
|
asChild = props.asChild,
|
|
253
162
|
enabled = state.isPresent,
|
|
163
|
+
exiting = not state.isPresent,
|
|
254
164
|
offset = props.offset,
|
|
255
165
|
onDismiss = handleDismiss,
|
|
256
|
-
|
|
166
|
+
onExitComplete = state.onExitComplete,
|
|
257
167
|
onInteractOutside = props.onInteractOutside,
|
|
258
168
|
onPointerDownOutside = props.onPointerDownOutside,
|
|
259
169
|
padding = props.padding,
|
|
@@ -3,19 +3,11 @@ 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 useSelectContext = TS.import(script, script.Parent, "context").useSelectContext
|
|
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 SelectItem(props)
|
|
17
10
|
local selectContext = useSelectContext()
|
|
18
|
-
local itemRef = React.useRef()
|
|
19
11
|
local disabled = selectContext.disabled or props.disabled == true
|
|
20
12
|
local _condition = props.textValue
|
|
21
13
|
if _condition == nil then
|
|
@@ -45,9 +37,6 @@ local function SelectItem(props)
|
|
|
45
37
|
id = itemIdRef.current,
|
|
46
38
|
value = props.value,
|
|
47
39
|
order = itemOrderRef.current,
|
|
48
|
-
getNode = function()
|
|
49
|
-
return itemRef.current
|
|
50
|
-
end,
|
|
51
40
|
getDisabled = function()
|
|
52
41
|
return disabledRef.current
|
|
53
42
|
end,
|
|
@@ -56,9 +45,6 @@ local function SelectItem(props)
|
|
|
56
45
|
end,
|
|
57
46
|
})
|
|
58
47
|
end, { props.value, selectContext })
|
|
59
|
-
local setItemRef = React.useCallback(function(instance)
|
|
60
|
-
itemRef.current = toGuiObject(instance)
|
|
61
|
-
end, {})
|
|
62
48
|
local handleSelect = React.useCallback(function()
|
|
63
49
|
if disabled then
|
|
64
50
|
return nil
|
|
@@ -88,36 +74,28 @@ local function SelectItem(props)
|
|
|
88
74
|
if not child then
|
|
89
75
|
error("[SelectItem] `asChild` requires a child element.")
|
|
90
76
|
end
|
|
91
|
-
return React.createElement(
|
|
92
|
-
asChild = true,
|
|
93
|
-
disabled = disabled,
|
|
94
|
-
}, React.createElement(Slot, {
|
|
77
|
+
return React.createElement(Slot, {
|
|
95
78
|
Active = not disabled,
|
|
96
79
|
Event = eventHandlers,
|
|
97
|
-
Selectable =
|
|
98
|
-
|
|
99
|
-
}, child))
|
|
80
|
+
Selectable = false,
|
|
81
|
+
}, child)
|
|
100
82
|
end
|
|
101
|
-
return React.createElement(
|
|
102
|
-
asChild = true,
|
|
103
|
-
disabled = disabled,
|
|
104
|
-
}, React.createElement("textbutton", {
|
|
83
|
+
return React.createElement("textbutton", {
|
|
105
84
|
Active = not disabled,
|
|
106
85
|
AutoButtonColor = false,
|
|
107
86
|
BackgroundColor3 = Color3.fromRGB(47, 53, 68),
|
|
108
87
|
BorderSizePixel = 0,
|
|
109
88
|
Event = eventHandlers,
|
|
110
|
-
Selectable =
|
|
89
|
+
Selectable = false,
|
|
111
90
|
Size = UDim2.fromOffset(220, 32),
|
|
112
91
|
Text = textValue,
|
|
113
92
|
TextColor3 = if disabled then Color3.fromRGB(134, 141, 156) else Color3.fromRGB(234, 239, 247),
|
|
114
93
|
TextSize = 15,
|
|
115
94
|
TextXAlignment = Enum.TextXAlignment.Left,
|
|
116
|
-
ref = setItemRef,
|
|
117
95
|
}, React.createElement("uipadding", {
|
|
118
96
|
PaddingLeft = UDim.new(0, 10),
|
|
119
97
|
PaddingRight = UDim.new(0, 10),
|
|
120
|
-
}), props.children)
|
|
98
|
+
}), props.children)
|
|
121
99
|
end
|
|
122
100
|
return {
|
|
123
101
|
SelectItem = SelectItem,
|
|
@@ -44,11 +44,6 @@ local function SelectRoot(props)
|
|
|
44
44
|
local setValueState = _binding_1[2]
|
|
45
45
|
local disabled = props.disabled == true
|
|
46
46
|
local required = props.required == true
|
|
47
|
-
local _condition_1 = props.loop
|
|
48
|
-
if _condition_1 == nil then
|
|
49
|
-
_condition_1 = true
|
|
50
|
-
end
|
|
51
|
-
local loop = _condition_1
|
|
52
47
|
local triggerRef = React.useRef()
|
|
53
48
|
local contentRef = React.useRef()
|
|
54
49
|
local itemEntriesRef = React.useRef({})
|
|
@@ -184,14 +179,12 @@ local function SelectRoot(props)
|
|
|
184
179
|
setValue = setValue,
|
|
185
180
|
disabled = disabled,
|
|
186
181
|
required = required,
|
|
187
|
-
loop = loop,
|
|
188
182
|
triggerRef = triggerRef,
|
|
189
183
|
contentRef = contentRef,
|
|
190
184
|
registerItem = registerItem,
|
|
191
|
-
getOrderedItems = resolveOrderedItems,
|
|
192
185
|
getItemText = getItemText,
|
|
193
186
|
}
|
|
194
|
-
end, { disabled, getItemText,
|
|
187
|
+
end, { disabled, getItemText, open, registerItem, required, setOpen, setValue, value })
|
|
195
188
|
return React.createElement(SelectContextProvider, {
|
|
196
189
|
value = contextValue,
|
|
197
190
|
}, props.children)
|
|
@@ -13,9 +13,10 @@ end
|
|
|
13
13
|
local function SelectTrigger(props)
|
|
14
14
|
local selectContext = useSelectContext()
|
|
15
15
|
local disabled = selectContext.disabled or props.disabled == true
|
|
16
|
+
local triggerRef = selectContext.triggerRef
|
|
16
17
|
local setTriggerRef = React.useCallback(function(instance)
|
|
17
|
-
|
|
18
|
-
end, {
|
|
18
|
+
triggerRef.current = toGuiObject(instance)
|
|
19
|
+
end, { triggerRef })
|
|
19
20
|
local handleActivated = React.useCallback(function()
|
|
20
21
|
if disabled then
|
|
21
22
|
return nil
|
|
@@ -29,10 +30,6 @@ local function SelectTrigger(props)
|
|
|
29
30
|
local keyCode = inputObject.KeyCode
|
|
30
31
|
if keyCode == Enum.KeyCode.Return or keyCode == Enum.KeyCode.Space then
|
|
31
32
|
selectContext.setOpen(not selectContext.open)
|
|
32
|
-
return nil
|
|
33
|
-
end
|
|
34
|
-
if keyCode == Enum.KeyCode.Down or keyCode == Enum.KeyCode.Up then
|
|
35
|
-
selectContext.setOpen(true)
|
|
36
33
|
end
|
|
37
34
|
end, { disabled, selectContext })
|
|
38
35
|
local eventHandlers = React.useMemo(function()
|
|
@@ -49,7 +46,7 @@ local function SelectTrigger(props)
|
|
|
49
46
|
return React.createElement(Slot, {
|
|
50
47
|
Active = not disabled,
|
|
51
48
|
Event = eventHandlers,
|
|
52
|
-
Selectable =
|
|
49
|
+
Selectable = false,
|
|
53
50
|
ref = setTriggerRef,
|
|
54
51
|
}, child)
|
|
55
52
|
end
|
|
@@ -59,7 +56,7 @@ local function SelectTrigger(props)
|
|
|
59
56
|
BackgroundColor3 = Color3.fromRGB(41, 48, 63),
|
|
60
57
|
BorderSizePixel = 0,
|
|
61
58
|
Event = eventHandlers,
|
|
62
|
-
Selectable =
|
|
59
|
+
Selectable = false,
|
|
63
60
|
Size = UDim2.fromOffset(220, 36),
|
|
64
61
|
Text = "Select",
|
|
65
62
|
TextColor3 = if disabled then Color3.fromRGB(140, 148, 164) else Color3.fromRGB(235, 241, 248),
|
package/out/Select/types.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export type SelectItemRegistration = {
|
|
|
7
7
|
id: number;
|
|
8
8
|
value: string;
|
|
9
9
|
order: number;
|
|
10
|
-
getNode: () => GuiObject | undefined;
|
|
11
10
|
getDisabled: () => boolean;
|
|
12
11
|
getTextValue: () => string;
|
|
13
12
|
};
|
|
@@ -18,11 +17,9 @@ export type SelectContextValue = {
|
|
|
18
17
|
setValue: SelectSetValue;
|
|
19
18
|
disabled: boolean;
|
|
20
19
|
required: boolean;
|
|
21
|
-
loop: boolean;
|
|
22
20
|
triggerRef: React.MutableRefObject<GuiObject | undefined>;
|
|
23
21
|
contentRef: React.MutableRefObject<GuiObject | undefined>;
|
|
24
22
|
registerItem: (item: SelectItemRegistration) => () => void;
|
|
25
|
-
getOrderedItems: () => Array<SelectItemRegistration>;
|
|
26
23
|
getItemText: (value: string) => string | undefined;
|
|
27
24
|
};
|
|
28
25
|
export type SelectProps = {
|
|
@@ -34,7 +31,6 @@ export type SelectProps = {
|
|
|
34
31
|
onOpenChange?: (open: boolean) => void;
|
|
35
32
|
disabled?: boolean;
|
|
36
33
|
required?: boolean;
|
|
37
|
-
loop?: boolean;
|
|
38
34
|
children?: React.ReactNode;
|
|
39
35
|
};
|
|
40
36
|
export type SelectTriggerProps = {
|
|
@@ -58,7 +54,6 @@ export type SelectContentProps = {
|
|
|
58
54
|
placement?: PopperPlacement;
|
|
59
55
|
offset?: Vector2;
|
|
60
56
|
padding?: number;
|
|
61
|
-
onEscapeKeyDown?: (event: LayerInteractEvent) => void;
|
|
62
57
|
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
63
58
|
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
64
59
|
children?: React.ReactNode;
|
package/out/index.d.ts
CHANGED
|
@@ -18,5 +18,5 @@ export declare const Select: {
|
|
|
18
18
|
readonly Label: typeof SelectLabel;
|
|
19
19
|
readonly Separator: typeof SelectSeparator;
|
|
20
20
|
};
|
|
21
|
-
export { SelectContent, SelectGroup, SelectItem, SelectLabel, SelectPortal, SelectRoot, SelectSeparator, SelectTrigger, SelectValue, };
|
|
22
21
|
export type { SelectContentProps, SelectContextValue, SelectGroupProps, SelectItemProps, SelectItemRegistration, SelectLabelProps, SelectPortalProps, SelectProps, SelectSeparatorProps, SelectSetOpen, SelectSetValue, SelectTriggerProps, SelectValueProps, } from "./Select/types";
|
|
22
|
+
export { SelectContent, SelectGroup, SelectItem, SelectLabel, SelectPortal, SelectRoot, SelectSeparator, SelectTrigger, SelectValue, };
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lattice-ui/select",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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/focus": "0.
|
|
10
|
-
"@lattice-ui/
|
|
11
|
-
"@lattice-ui/
|
|
8
|
+
"@lattice-ui/core": "0.4.0",
|
|
9
|
+
"@lattice-ui/focus": "0.4.0",
|
|
10
|
+
"@lattice-ui/popper": "0.4.0",
|
|
11
|
+
"@lattice-ui/layer": "0.4.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@rbxts/react": "17.3.7-ts.1",
|
|
@@ -1,35 +1,27 @@
|
|
|
1
1
|
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
-
import {
|
|
2
|
+
import { FocusScope } from "@lattice-ui/focus";
|
|
3
3
|
import { DismissableLayer, Presence } from "@lattice-ui/layer";
|
|
4
4
|
import { usePopper } from "@lattice-ui/popper";
|
|
5
5
|
import { useSelectContext } from "./context";
|
|
6
|
-
import type { SelectContentProps
|
|
6
|
+
import type { SelectContentProps } from "./types";
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const UserInputService = game.GetService("UserInputService");
|
|
8
|
+
const TweenService = game.GetService("TweenService");
|
|
10
9
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Two: "2",
|
|
15
|
-
Three: "3",
|
|
16
|
-
Four: "4",
|
|
17
|
-
Five: "5",
|
|
18
|
-
Six: "6",
|
|
19
|
-
Seven: "7",
|
|
20
|
-
Eight: "8",
|
|
21
|
-
Nine: "9",
|
|
22
|
-
};
|
|
10
|
+
const OPEN_TWEEN_INFO = new TweenInfo(0.12, Enum.EasingStyle.Quad, Enum.EasingDirection.Out);
|
|
11
|
+
const CLOSE_TWEEN_INFO = new TweenInfo(0.09, Enum.EasingStyle.Quad, Enum.EasingDirection.In);
|
|
12
|
+
const CONTENT_OPEN_Y_OFFSET = 6;
|
|
23
13
|
|
|
24
14
|
type SelectContentImplProps = {
|
|
25
15
|
enabled: boolean;
|
|
26
16
|
visible: boolean;
|
|
17
|
+
exiting: boolean;
|
|
27
18
|
onDismiss: () => void;
|
|
19
|
+
onExitComplete?: () => void;
|
|
28
20
|
asChild?: boolean;
|
|
29
21
|
placement?: SelectContentProps["placement"];
|
|
30
22
|
offset?: SelectContentProps["offset"];
|
|
31
23
|
padding?: SelectContentProps["padding"];
|
|
32
|
-
} & Pick<SelectContentProps, "children" | "
|
|
24
|
+
} & Pick<SelectContentProps, "children" | "onInteractOutside" | "onPointerDownOutside">;
|
|
33
25
|
|
|
34
26
|
function toGuiObject(instance: Instance | undefined) {
|
|
35
27
|
if (!instance || !instance.IsA("GuiObject")) {
|
|
@@ -39,79 +31,8 @@ function toGuiObject(instance: Instance | undefined) {
|
|
|
39
31
|
return instance;
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
return " ";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const digitCharacter = digitKeyMap[keyCode.Name];
|
|
48
|
-
if (digitCharacter !== undefined) {
|
|
49
|
-
return digitCharacter;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (keyCode.Name.size() === 1) {
|
|
53
|
-
return string.lower(keyCode.Name);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function startsWithIgnoreCase(value: string, query: string) {
|
|
60
|
-
if (query.size() === 0) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const normalizedValue = string.lower(value);
|
|
65
|
-
return string.sub(normalizedValue, 1, query.size()) === query;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function findCurrentIndex(items: Array<SelectItemRegistration>, selectedObject: GuiObject | undefined) {
|
|
69
|
-
if (!selectedObject) {
|
|
70
|
-
return -1;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return items.findIndex((item) => {
|
|
74
|
-
const node = item.getNode();
|
|
75
|
-
if (!node) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return selectedObject === node || selectedObject.IsDescendantOf(node);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function findTypeaheadMatch(items: Array<SelectItemRegistration>, query: string, startIndex: number) {
|
|
84
|
-
const itemCount = items.size();
|
|
85
|
-
if (itemCount === 0) {
|
|
86
|
-
return -1;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
for (let attempts = 0; attempts < itemCount; attempts++) {
|
|
90
|
-
const candidateIndex = (startIndex + attempts) % itemCount;
|
|
91
|
-
const candidate = items[candidateIndex];
|
|
92
|
-
if (!candidate || candidate.getDisabled()) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (startsWithIgnoreCase(candidate.getTextValue(), query)) {
|
|
97
|
-
return candidateIndex;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return -1;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function focusItem(item: SelectItemRegistration | undefined) {
|
|
105
|
-
if (!item) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const node = item.getNode();
|
|
110
|
-
if (!node || !node.Selectable) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
GuiService.SelectedObject = node;
|
|
34
|
+
function withVerticalOffset(position: UDim2, offset: number) {
|
|
35
|
+
return new UDim2(position.X.Scale, position.X.Offset, position.Y.Scale, position.Y.Offset + offset);
|
|
115
36
|
}
|
|
116
37
|
|
|
117
38
|
function SelectContentImpl(props: SelectContentImplProps) {
|
|
@@ -133,69 +54,78 @@ function SelectContentImpl(props: SelectContentImplProps) {
|
|
|
133
54
|
[selectContext.contentRef],
|
|
134
55
|
);
|
|
135
56
|
|
|
136
|
-
const
|
|
137
|
-
const
|
|
57
|
+
const positionTweenRef = React.useRef<Tween>();
|
|
58
|
+
const tweenCompletedConnectionRef = React.useRef<RBXScriptConnection>();
|
|
59
|
+
const previousVisibleRef = React.useRef(props.visible);
|
|
60
|
+
const previousExitingRef = React.useRef(props.exiting);
|
|
138
61
|
|
|
139
|
-
React.
|
|
140
|
-
|
|
141
|
-
|
|
62
|
+
const clearTween = React.useCallback(() => {
|
|
63
|
+
const tween = positionTweenRef.current;
|
|
64
|
+
if (tween) {
|
|
65
|
+
tween.Cancel();
|
|
66
|
+
positionTweenRef.current = undefined;
|
|
142
67
|
}
|
|
143
68
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
69
|
+
const completedConnection = tweenCompletedConnectionRef.current;
|
|
70
|
+
if (completedConnection) {
|
|
71
|
+
completedConnection.Disconnect();
|
|
72
|
+
tweenCompletedConnectionRef.current = undefined;
|
|
73
|
+
}
|
|
74
|
+
}, []);
|
|
148
75
|
|
|
149
76
|
React.useEffect(() => {
|
|
150
|
-
|
|
77
|
+
return () => {
|
|
78
|
+
clearTween();
|
|
79
|
+
};
|
|
80
|
+
}, [clearTween]);
|
|
81
|
+
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
const contentNode = selectContext.contentRef.current;
|
|
84
|
+
if (!contentNode) {
|
|
151
85
|
return;
|
|
152
86
|
}
|
|
153
87
|
|
|
154
|
-
|
|
155
|
-
|
|
88
|
+
const wasVisible = previousVisibleRef.current;
|
|
89
|
+
const wasExiting = previousExitingRef.current;
|
|
90
|
+
previousVisibleRef.current = props.visible;
|
|
91
|
+
previousExitingRef.current = props.exiting;
|
|
156
92
|
|
|
157
|
-
|
|
158
|
-
if (
|
|
93
|
+
if (props.exiting) {
|
|
94
|
+
if (wasExiting) {
|
|
159
95
|
return;
|
|
160
96
|
}
|
|
161
97
|
|
|
162
|
-
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
98
|
+
clearTween();
|
|
165
99
|
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
100
|
+
const tween = TweenService.Create(contentNode, CLOSE_TWEEN_INFO, {
|
|
101
|
+
Position: withVerticalOffset(popper.position, CONTENT_OPEN_Y_OFFSET),
|
|
102
|
+
});
|
|
171
103
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
104
|
+
positionTweenRef.current = tween;
|
|
105
|
+
tweenCompletedConnectionRef.current = tween.Completed.Connect((playbackState) => {
|
|
106
|
+
if (playbackState === Enum.PlaybackState.Completed) {
|
|
107
|
+
props.onExitComplete?.();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
176
110
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
searchRef.current = nextQuery;
|
|
181
|
-
searchTimestampRef.current = now;
|
|
111
|
+
tween.Play();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
182
114
|
|
|
183
|
-
|
|
184
|
-
const currentIndex = findCurrentIndex(orderedItems, selectedObject);
|
|
185
|
-
const startIndex = currentIndex >= 0 ? currentIndex + 1 : 0;
|
|
186
|
-
const matchIndex = findTypeaheadMatch(orderedItems, string.lower(nextQuery), startIndex);
|
|
187
|
-
if (matchIndex < 0) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
115
|
+
clearTween();
|
|
190
116
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
117
|
+
if (props.visible && !wasVisible) {
|
|
118
|
+
contentNode.Position = withVerticalOffset(popper.position, CONTENT_OPEN_Y_OFFSET);
|
|
119
|
+
const tween = TweenService.Create(contentNode, OPEN_TWEEN_INFO, {
|
|
120
|
+
Position: popper.position,
|
|
121
|
+
});
|
|
122
|
+
positionTweenRef.current = tween;
|
|
123
|
+
tween.Play();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
194
126
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
};
|
|
198
|
-
}, [props.enabled, selectContext]);
|
|
127
|
+
contentNode.Position = popper.position;
|
|
128
|
+
}, [clearTween, popper.position, props.exiting, props.onExitComplete, props.visible, selectContext.contentRef]);
|
|
199
129
|
|
|
200
130
|
const contentNode = props.asChild ? (
|
|
201
131
|
(() => {
|
|
@@ -205,7 +135,12 @@ function SelectContentImpl(props: SelectContentImplProps) {
|
|
|
205
135
|
}
|
|
206
136
|
|
|
207
137
|
return (
|
|
208
|
-
<Slot
|
|
138
|
+
<Slot
|
|
139
|
+
AnchorPoint={popper.anchorPoint}
|
|
140
|
+
Position={popper.position}
|
|
141
|
+
Visible={props.visible || props.exiting}
|
|
142
|
+
ref={setContentRef}
|
|
143
|
+
>
|
|
209
144
|
{child}
|
|
210
145
|
</Slot>
|
|
211
146
|
);
|
|
@@ -217,7 +152,7 @@ function SelectContentImpl(props: SelectContentImplProps) {
|
|
|
217
152
|
BorderSizePixel={0}
|
|
218
153
|
Position={popper.position}
|
|
219
154
|
Size={UDim2.fromOffset(0, 0)}
|
|
220
|
-
Visible={props.visible}
|
|
155
|
+
Visible={props.visible || props.exiting}
|
|
221
156
|
ref={setContentRef}
|
|
222
157
|
>
|
|
223
158
|
{props.children}
|
|
@@ -229,13 +164,12 @@ function SelectContentImpl(props: SelectContentImplProps) {
|
|
|
229
164
|
enabled={props.enabled}
|
|
230
165
|
modal={false}
|
|
231
166
|
onDismiss={props.onDismiss}
|
|
232
|
-
onEscapeKeyDown={props.onEscapeKeyDown}
|
|
233
167
|
onInteractOutside={props.onInteractOutside}
|
|
234
168
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
235
169
|
>
|
|
236
|
-
<
|
|
170
|
+
<FocusScope active={props.enabled} restoreFocus={true} trapped={false}>
|
|
237
171
|
{contentNode}
|
|
238
|
-
</
|
|
172
|
+
</FocusScope>
|
|
239
173
|
</DismissableLayer>
|
|
240
174
|
);
|
|
241
175
|
}
|
|
@@ -258,9 +192,9 @@ export function SelectContent(props: SelectContentProps) {
|
|
|
258
192
|
<SelectContentImpl
|
|
259
193
|
asChild={props.asChild}
|
|
260
194
|
enabled={open}
|
|
195
|
+
exiting={false}
|
|
261
196
|
offset={props.offset}
|
|
262
197
|
onDismiss={handleDismiss}
|
|
263
|
-
onEscapeKeyDown={props.onEscapeKeyDown}
|
|
264
198
|
onInteractOutside={props.onInteractOutside}
|
|
265
199
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
266
200
|
padding={props.padding}
|
|
@@ -274,15 +208,16 @@ export function SelectContent(props: SelectContentProps) {
|
|
|
274
208
|
|
|
275
209
|
return (
|
|
276
210
|
<Presence
|
|
277
|
-
exitFallbackMs={
|
|
211
|
+
exitFallbackMs={180}
|
|
278
212
|
present={open}
|
|
279
213
|
render={(state) => (
|
|
280
214
|
<SelectContentImpl
|
|
281
215
|
asChild={props.asChild}
|
|
282
216
|
enabled={state.isPresent}
|
|
217
|
+
exiting={!state.isPresent}
|
|
283
218
|
offset={props.offset}
|
|
284
219
|
onDismiss={handleDismiss}
|
|
285
|
-
|
|
220
|
+
onExitComplete={state.onExitComplete}
|
|
286
221
|
onInteractOutside={props.onInteractOutside}
|
|
287
222
|
onPointerDownOutside={props.onPointerDownOutside}
|
|
288
223
|
padding={props.padding}
|
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
-
import { RovingFocusItem } from "@lattice-ui/focus";
|
|
3
2
|
import { useSelectContext } from "./context";
|
|
4
3
|
import type { SelectItemProps } 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 SelectItem(props: SelectItemProps) {
|
|
18
9
|
const selectContext = useSelectContext();
|
|
19
|
-
const itemRef = React.useRef<GuiObject>();
|
|
20
|
-
|
|
21
10
|
const disabled = selectContext.disabled || props.disabled === true;
|
|
22
11
|
const textValue = props.textValue ?? props.value;
|
|
23
12
|
|
|
@@ -49,16 +38,11 @@ export function SelectItem(props: SelectItemProps) {
|
|
|
49
38
|
id: itemIdRef.current,
|
|
50
39
|
value: props.value,
|
|
51
40
|
order: itemOrderRef.current,
|
|
52
|
-
getNode: () => itemRef.current,
|
|
53
41
|
getDisabled: () => disabledRef.current,
|
|
54
42
|
getTextValue: () => textValueRef.current,
|
|
55
43
|
});
|
|
56
44
|
}, [props.value, selectContext]);
|
|
57
45
|
|
|
58
|
-
const setItemRef = React.useCallback((instance: Instance | undefined) => {
|
|
59
|
-
itemRef.current = toGuiObject(instance);
|
|
60
|
-
}, []);
|
|
61
|
-
|
|
62
46
|
const handleSelect = React.useCallback(() => {
|
|
63
47
|
if (disabled) {
|
|
64
48
|
return;
|
|
@@ -100,33 +84,28 @@ export function SelectItem(props: SelectItemProps) {
|
|
|
100
84
|
}
|
|
101
85
|
|
|
102
86
|
return (
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
</Slot>
|
|
107
|
-
</RovingFocusItem>
|
|
87
|
+
<Slot Active={!disabled} Event={eventHandlers} Selectable={false}>
|
|
88
|
+
{child}
|
|
89
|
+
</Slot>
|
|
108
90
|
);
|
|
109
91
|
}
|
|
110
92
|
|
|
111
93
|
return (
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
{props.children}
|
|
129
|
-
</textbutton>
|
|
130
|
-
</RovingFocusItem>
|
|
94
|
+
<textbutton
|
|
95
|
+
Active={!disabled}
|
|
96
|
+
AutoButtonColor={false}
|
|
97
|
+
BackgroundColor3={Color3.fromRGB(47, 53, 68)}
|
|
98
|
+
BorderSizePixel={0}
|
|
99
|
+
Event={eventHandlers}
|
|
100
|
+
Selectable={false}
|
|
101
|
+
Size={UDim2.fromOffset(220, 32)}
|
|
102
|
+
Text={textValue}
|
|
103
|
+
TextColor3={disabled ? Color3.fromRGB(134, 141, 156) : Color3.fromRGB(234, 239, 247)}
|
|
104
|
+
TextSize={15}
|
|
105
|
+
TextXAlignment={Enum.TextXAlignment.Left}
|
|
106
|
+
>
|
|
107
|
+
<uipadding PaddingLeft={new UDim(0, 10)} PaddingRight={new UDim(0, 10)} />
|
|
108
|
+
{props.children}
|
|
109
|
+
</textbutton>
|
|
131
110
|
);
|
|
132
111
|
}
|
|
@@ -27,7 +27,6 @@ export function SelectRoot(props: SelectProps) {
|
|
|
27
27
|
|
|
28
28
|
const disabled = props.disabled === true;
|
|
29
29
|
const required = props.required === true;
|
|
30
|
-
const loop = props.loop ?? true;
|
|
31
30
|
|
|
32
31
|
const triggerRef = React.useRef<GuiObject>();
|
|
33
32
|
const contentRef = React.useRef<GuiObject>();
|
|
@@ -110,14 +109,12 @@ export function SelectRoot(props: SelectProps) {
|
|
|
110
109
|
setValue,
|
|
111
110
|
disabled,
|
|
112
111
|
required,
|
|
113
|
-
loop,
|
|
114
112
|
triggerRef,
|
|
115
113
|
contentRef,
|
|
116
114
|
registerItem,
|
|
117
|
-
getOrderedItems: resolveOrderedItems,
|
|
118
115
|
getItemText,
|
|
119
116
|
}),
|
|
120
|
-
[disabled, getItemText,
|
|
117
|
+
[disabled, getItemText, open, registerItem, required, setOpen, setValue, value],
|
|
121
118
|
);
|
|
122
119
|
|
|
123
120
|
return <SelectContextProvider value={contextValue}>{props.children}</SelectContextProvider>;
|
|
@@ -13,12 +13,13 @@ function toGuiObject(instance: Instance | undefined) {
|
|
|
13
13
|
export function SelectTrigger(props: SelectTriggerProps) {
|
|
14
14
|
const selectContext = useSelectContext();
|
|
15
15
|
const disabled = selectContext.disabled || props.disabled === true;
|
|
16
|
+
const triggerRef = selectContext.triggerRef;
|
|
16
17
|
|
|
17
18
|
const setTriggerRef = React.useCallback(
|
|
18
19
|
(instance: Instance | undefined) => {
|
|
19
|
-
|
|
20
|
+
triggerRef.current = toGuiObject(instance);
|
|
20
21
|
},
|
|
21
|
-
[
|
|
22
|
+
[triggerRef],
|
|
22
23
|
);
|
|
23
24
|
|
|
24
25
|
const handleActivated = React.useCallback(() => {
|
|
@@ -38,11 +39,6 @@ export function SelectTrigger(props: SelectTriggerProps) {
|
|
|
38
39
|
const keyCode = inputObject.KeyCode;
|
|
39
40
|
if (keyCode === Enum.KeyCode.Return || keyCode === Enum.KeyCode.Space) {
|
|
40
41
|
selectContext.setOpen(!selectContext.open);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (keyCode === Enum.KeyCode.Down || keyCode === Enum.KeyCode.Up) {
|
|
45
|
-
selectContext.setOpen(true);
|
|
46
42
|
}
|
|
47
43
|
},
|
|
48
44
|
[disabled, selectContext],
|
|
@@ -63,7 +59,7 @@ export function SelectTrigger(props: SelectTriggerProps) {
|
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
return (
|
|
66
|
-
<Slot Active={!disabled} Event={eventHandlers} Selectable={
|
|
62
|
+
<Slot Active={!disabled} Event={eventHandlers} Selectable={false} ref={setTriggerRef}>
|
|
67
63
|
{child}
|
|
68
64
|
</Slot>
|
|
69
65
|
);
|
|
@@ -76,7 +72,7 @@ export function SelectTrigger(props: SelectTriggerProps) {
|
|
|
76
72
|
BackgroundColor3={Color3.fromRGB(41, 48, 63)}
|
|
77
73
|
BorderSizePixel={0}
|
|
78
74
|
Event={eventHandlers}
|
|
79
|
-
Selectable={
|
|
75
|
+
Selectable={false}
|
|
80
76
|
Size={UDim2.fromOffset(220, 36)}
|
|
81
77
|
Text="Select"
|
|
82
78
|
TextColor3={disabled ? Color3.fromRGB(140, 148, 164) : Color3.fromRGB(235, 241, 248)}
|
package/src/Select/types.ts
CHANGED
|
@@ -9,7 +9,6 @@ export type SelectItemRegistration = {
|
|
|
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
|
};
|
|
@@ -21,11 +20,9 @@ export type SelectContextValue = {
|
|
|
21
20
|
setValue: SelectSetValue;
|
|
22
21
|
disabled: boolean;
|
|
23
22
|
required: boolean;
|
|
24
|
-
loop: boolean;
|
|
25
23
|
triggerRef: React.MutableRefObject<GuiObject | undefined>;
|
|
26
24
|
contentRef: React.MutableRefObject<GuiObject | undefined>;
|
|
27
25
|
registerItem: (item: SelectItemRegistration) => () => void;
|
|
28
|
-
getOrderedItems: () => Array<SelectItemRegistration>;
|
|
29
26
|
getItemText: (value: string) => string | undefined;
|
|
30
27
|
};
|
|
31
28
|
|
|
@@ -38,7 +35,6 @@ export type SelectProps = {
|
|
|
38
35
|
onOpenChange?: (open: boolean) => void;
|
|
39
36
|
disabled?: boolean;
|
|
40
37
|
required?: boolean;
|
|
41
|
-
loop?: boolean;
|
|
42
38
|
children?: React.ReactNode;
|
|
43
39
|
};
|
|
44
40
|
|
|
@@ -66,7 +62,6 @@ export type SelectContentProps = {
|
|
|
66
62
|
placement?: PopperPlacement;
|
|
67
63
|
offset?: Vector2;
|
|
68
64
|
padding?: number;
|
|
69
|
-
onEscapeKeyDown?: (event: LayerInteractEvent) => void;
|
|
70
65
|
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
71
66
|
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
72
67
|
children?: React.ReactNode;
|
package/src/index.ts
CHANGED
|
@@ -30,18 +30,6 @@ export const Select = {
|
|
|
30
30
|
Separator: typeof SelectSeparator;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export {
|
|
34
|
-
SelectContent,
|
|
35
|
-
SelectGroup,
|
|
36
|
-
SelectItem,
|
|
37
|
-
SelectLabel,
|
|
38
|
-
SelectPortal,
|
|
39
|
-
SelectRoot,
|
|
40
|
-
SelectSeparator,
|
|
41
|
-
SelectTrigger,
|
|
42
|
-
SelectValue,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
33
|
export type {
|
|
46
34
|
SelectContentProps,
|
|
47
35
|
SelectContextValue,
|
|
@@ -57,3 +45,14 @@ export type {
|
|
|
57
45
|
SelectTriggerProps,
|
|
58
46
|
SelectValueProps,
|
|
59
47
|
} from "./Select/types";
|
|
48
|
+
export {
|
|
49
|
+
SelectContent,
|
|
50
|
+
SelectGroup,
|
|
51
|
+
SelectItem,
|
|
52
|
+
SelectLabel,
|
|
53
|
+
SelectPortal,
|
|
54
|
+
SelectRoot,
|
|
55
|
+
SelectSeparator,
|
|
56
|
+
SelectTrigger,
|
|
57
|
+
SelectValue,
|
|
58
|
+
};
|