@lattice-ui/combobox 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +23 -0
  2. package/out/Combobox/ComboboxContent.d.ts +3 -0
  3. package/out/Combobox/ComboboxContent.luau +110 -0
  4. package/out/Combobox/ComboboxGroup.d.ts +3 -0
  5. package/out/Combobox/ComboboxGroup.luau +22 -0
  6. package/out/Combobox/ComboboxInput.d.ts +3 -0
  7. package/out/Combobox/ComboboxInput.luau +88 -0
  8. package/out/Combobox/ComboboxItem.d.ts +3 -0
  9. package/out/Combobox/ComboboxItem.luau +131 -0
  10. package/out/Combobox/ComboboxLabel.d.ts +3 -0
  11. package/out/Combobox/ComboboxLabel.luau +26 -0
  12. package/out/Combobox/ComboboxPortal.d.ts +3 -0
  13. package/out/Combobox/ComboboxPortal.luau +33 -0
  14. package/out/Combobox/ComboboxRoot.d.ts +4 -0
  15. package/out/Combobox/ComboboxRoot.luau +256 -0
  16. package/out/Combobox/ComboboxSeparator.d.ts +3 -0
  17. package/out/Combobox/ComboboxSeparator.luau +22 -0
  18. package/out/Combobox/ComboboxTrigger.d.ts +3 -0
  19. package/out/Combobox/ComboboxTrigger.luau +72 -0
  20. package/out/Combobox/ComboboxValue.d.ts +3 -0
  21. package/out/Combobox/ComboboxValue.luau +47 -0
  22. package/out/Combobox/context.d.ts +3 -0
  23. package/out/Combobox/context.luau +10 -0
  24. package/out/Combobox/logic.d.ts +10 -0
  25. package/out/Combobox/logic.luau +103 -0
  26. package/out/Combobox/types.d.ts +105 -0
  27. package/out/Combobox/types.luau +2 -0
  28. package/out/index.d.ts +24 -0
  29. package/out/init.luau +32 -0
  30. package/package.json +26 -0
  31. package/src/Combobox/ComboboxContent.tsx +142 -0
  32. package/src/Combobox/ComboboxGroup.tsx +19 -0
  33. package/src/Combobox/ComboboxInput.tsx +98 -0
  34. package/src/Combobox/ComboboxItem.tsx +134 -0
  35. package/src/Combobox/ComboboxLabel.tsx +27 -0
  36. package/src/Combobox/ComboboxPortal.tsx +28 -0
  37. package/src/Combobox/ComboboxRoot.tsx +202 -0
  38. package/src/Combobox/ComboboxSeparator.tsx +19 -0
  39. package/src/Combobox/ComboboxTrigger.tsx +89 -0
  40. package/src/Combobox/ComboboxValue.tsx +42 -0
  41. package/src/Combobox/context.ts +6 -0
  42. package/src/Combobox/logic.ts +56 -0
  43. package/src/Combobox/types.ts +119 -0
  44. package/src/index.ts +48 -0
  45. package/tsconfig.json +16 -0
  46. package/tsconfig.typecheck.json +35 -0
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @lattice-ui/combobox
2
+
3
+ Headless combobox primitives for Roblox UI with typed filtering and enforced option selection.
4
+
5
+ ## Exports
6
+
7
+ - `Combobox`
8
+ - `Combobox.Root`
9
+ - `Combobox.Trigger`
10
+ - `Combobox.Input`
11
+ - `Combobox.Value`
12
+ - `Combobox.Portal`
13
+ - `Combobox.Content`
14
+ - `Combobox.Item`
15
+ - `Combobox.Group`
16
+ - `Combobox.Label`
17
+ - `Combobox.Separator`
18
+
19
+ ## Notes
20
+
21
+ - Supports controlled/uncontrolled `value`, `inputValue`, and `open`.
22
+ - Input text is synchronized back to selected option text when the list closes.
23
+ - Filtering defaults to case-insensitive substring matching and can be customized with `filterFn`.
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ComboboxContentProps } from "./types";
3
+ export declare function ComboboxContent(props: ComboboxContentProps): React.JSX.Element | undefined;
@@ -0,0 +1,110 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local RovingFocusGroup = TS.import(script, TS.getModule(script, "@lattice-ui", "focus").out).RovingFocusGroup
7
+ local _layer = TS.import(script, TS.getModule(script, "@lattice-ui", "layer").out)
8
+ local DismissableLayer = _layer.DismissableLayer
9
+ local Presence = _layer.Presence
10
+ local usePopper = TS.import(script, TS.getModule(script, "@lattice-ui", "popper").out).usePopper
11
+ local useComboboxContext = TS.import(script, script.Parent, "context").useComboboxContext
12
+ local function toGuiObject(instance)
13
+ if not instance or not instance:IsA("GuiObject") then
14
+ return nil
15
+ end
16
+ return instance
17
+ end
18
+ local function ComboboxContentImpl(props)
19
+ local comboboxContext = useComboboxContext()
20
+ local popper = usePopper({
21
+ anchorRef = comboboxContext.triggerRef,
22
+ contentRef = comboboxContext.contentRef,
23
+ placement = props.placement,
24
+ offset = props.offset,
25
+ padding = props.padding,
26
+ enabled = props.enabled,
27
+ })
28
+ local setContentRef = React.useCallback(function(instance)
29
+ comboboxContext.contentRef.current = toGuiObject(instance)
30
+ end, { comboboxContext.contentRef })
31
+ local contentNode = if props.asChild then ((function()
32
+ local child = props.children
33
+ if not React.isValidElement(child) then
34
+ error("[ComboboxContent] `asChild` requires a child element.")
35
+ end
36
+ return React.createElement(Slot, {
37
+ AnchorPoint = popper.anchorPoint,
38
+ Position = popper.position,
39
+ Visible = props.visible,
40
+ ref = setContentRef,
41
+ }, child)
42
+ end)()) else (React.createElement("frame", {
43
+ AnchorPoint = popper.anchorPoint,
44
+ BackgroundTransparency = 1,
45
+ BorderSizePixel = 0,
46
+ Position = popper.position,
47
+ Size = UDim2.fromOffset(0, 0),
48
+ Visible = props.visible,
49
+ ref = setContentRef,
50
+ }, props.children))
51
+ return React.createElement(DismissableLayer, {
52
+ enabled = props.enabled,
53
+ modal = false,
54
+ onDismiss = props.onDismiss,
55
+ onEscapeKeyDown = props.onEscapeKeyDown,
56
+ onInteractOutside = props.onInteractOutside,
57
+ onPointerDownOutside = props.onPointerDownOutside,
58
+ }, React.createElement(RovingFocusGroup, {
59
+ active = props.enabled,
60
+ autoFocus = "first",
61
+ loop = comboboxContext.loop,
62
+ orientation = "vertical",
63
+ }, contentNode))
64
+ end
65
+ local function ComboboxContent(props)
66
+ local comboboxContext = useComboboxContext()
67
+ local open = comboboxContext.open
68
+ local forceMount = props.forceMount == true
69
+ local handleDismiss = React.useCallback(function()
70
+ comboboxContext.setOpen(false)
71
+ end, { comboboxContext })
72
+ if not open and not forceMount then
73
+ return nil
74
+ end
75
+ if forceMount then
76
+ return React.createElement(ComboboxContentImpl, {
77
+ asChild = props.asChild,
78
+ enabled = open,
79
+ offset = props.offset,
80
+ onDismiss = handleDismiss,
81
+ onEscapeKeyDown = props.onEscapeKeyDown,
82
+ onInteractOutside = props.onInteractOutside,
83
+ onPointerDownOutside = props.onPointerDownOutside,
84
+ padding = props.padding,
85
+ placement = props.placement,
86
+ visible = open,
87
+ }, props.children)
88
+ end
89
+ return React.createElement(Presence, {
90
+ exitFallbackMs = 0,
91
+ present = open,
92
+ render = function(state)
93
+ return React.createElement(ComboboxContentImpl, {
94
+ asChild = props.asChild,
95
+ enabled = state.isPresent,
96
+ offset = props.offset,
97
+ onDismiss = handleDismiss,
98
+ onEscapeKeyDown = props.onEscapeKeyDown,
99
+ onInteractOutside = props.onInteractOutside,
100
+ onPointerDownOutside = props.onPointerDownOutside,
101
+ padding = props.padding,
102
+ placement = props.placement,
103
+ visible = state.isPresent,
104
+ }, props.children)
105
+ end,
106
+ })
107
+ end
108
+ return {
109
+ ComboboxContent = ComboboxContent,
110
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ComboboxGroupProps } from "./types";
3
+ export declare function ComboboxGroup(props: ComboboxGroupProps): React.JSX.Element;
@@ -0,0 +1,22 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local function ComboboxGroup(props)
7
+ if props.asChild then
8
+ local child = props.children
9
+ if not child then
10
+ error("[ComboboxGroup] `asChild` requires a child element.")
11
+ end
12
+ return React.createElement(Slot, nil, child)
13
+ end
14
+ return React.createElement("frame", {
15
+ BackgroundTransparency = 1,
16
+ BorderSizePixel = 0,
17
+ Size = UDim2.fromOffset(220, 108),
18
+ }, props.children)
19
+ end
20
+ return {
21
+ ComboboxGroup = ComboboxGroup,
22
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ComboboxInputProps } from "./types";
3
+ export declare function ComboboxInput(props: ComboboxInputProps): React.JSX.Element;
@@ -0,0 +1,88 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local useComboboxContext = TS.import(script, script.Parent, "context").useComboboxContext
7
+ local function toTextBox(instance)
8
+ if not instance or not instance:IsA("TextBox") then
9
+ return nil
10
+ end
11
+ return instance
12
+ end
13
+ local function ComboboxInput(props)
14
+ local comboboxContext = useComboboxContext()
15
+ local disabled = comboboxContext.disabled or props.disabled == true
16
+ local readOnly = comboboxContext.readOnly or props.readOnly == true
17
+ local setInputRef = React.useCallback(function(instance)
18
+ comboboxContext.inputRef.current = toTextBox(instance)
19
+ end, { comboboxContext.inputRef })
20
+ local handleTextChanged = React.useCallback(function(textBox)
21
+ if disabled or readOnly then
22
+ if textBox.Text ~= comboboxContext.inputValue then
23
+ textBox.Text = comboboxContext.inputValue
24
+ end
25
+ return nil
26
+ end
27
+ comboboxContext.setInputValue(textBox.Text)
28
+ 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
+ local _object = {
43
+ Active = not disabled,
44
+ ClearTextOnFocus = false,
45
+ }
46
+ local _left = "PlaceholderText"
47
+ local _condition = props.placeholder
48
+ if _condition == nil then
49
+ _condition = "Type to filter"
50
+ end
51
+ _object[_left] = _condition
52
+ _object.Selectable = not disabled
53
+ _object.Text = comboboxContext.inputValue
54
+ _object.TextEditable = not disabled and not readOnly
55
+ _object.Change = {
56
+ Text = handleTextChanged,
57
+ }
58
+ _object.Event = {
59
+ FocusLost = handleFocusLost,
60
+ InputBegan = handleInputBegan,
61
+ }
62
+ _object.ref = setInputRef
63
+ local sharedProps = _object
64
+ if props.asChild then
65
+ local child = props.children
66
+ if not child then
67
+ error("[ComboboxInput] `asChild` requires a child element.")
68
+ end
69
+ local _attributes = table.clone(sharedProps)
70
+ setmetatable(_attributes, nil)
71
+ return React.createElement(Slot, _attributes, child)
72
+ end
73
+ local _attributes = table.clone(sharedProps)
74
+ setmetatable(_attributes, nil)
75
+ _attributes.BackgroundColor3 = Color3.fromRGB(39, 46, 61)
76
+ _attributes.BorderSizePixel = 0
77
+ _attributes.Size = UDim2.fromOffset(240, 36)
78
+ _attributes.TextColor3 = if disabled then Color3.fromRGB(137, 145, 162) else Color3.fromRGB(235, 240, 248)
79
+ _attributes.TextSize = 15
80
+ _attributes.TextXAlignment = Enum.TextXAlignment.Left
81
+ return React.createElement("textbox", _attributes, React.createElement("uipadding", {
82
+ PaddingLeft = UDim.new(0, 10),
83
+ PaddingRight = UDim.new(0, 10),
84
+ }))
85
+ end
86
+ return {
87
+ ComboboxInput = ComboboxInput,
88
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ComboboxItemProps } from "./types";
3
+ export declare function ComboboxItem(props: ComboboxItemProps): React.JSX.Element;
@@ -0,0 +1,131 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local RovingFocusItem = TS.import(script, TS.getModule(script, "@lattice-ui", "focus").out).RovingFocusItem
7
+ local useComboboxContext = TS.import(script, script.Parent, "context").useComboboxContext
8
+ local nextItemId = 0
9
+ 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
+ local function ComboboxItem(props)
17
+ local comboboxContext = useComboboxContext()
18
+ local itemRef = React.useRef()
19
+ local _condition = props.textValue
20
+ if _condition == nil then
21
+ _condition = props.value
22
+ end
23
+ local itemQueryMatch = comboboxContext.filterFn(_condition, comboboxContext.inputValue)
24
+ local disabled = comboboxContext.disabled or props.disabled == true or not itemQueryMatch
25
+ local _condition_1 = props.textValue
26
+ if _condition_1 == nil then
27
+ _condition_1 = props.value
28
+ end
29
+ local textValue = _condition_1
30
+ local disabledRef = React.useRef(disabled)
31
+ local textValueRef = React.useRef(textValue)
32
+ React.useEffect(function()
33
+ disabledRef.current = disabled
34
+ end, { disabled })
35
+ React.useEffect(function()
36
+ textValueRef.current = textValue
37
+ end, { textValue })
38
+ local itemIdRef = React.useRef(0)
39
+ if itemIdRef.current == 0 then
40
+ nextItemId += 1
41
+ itemIdRef.current = nextItemId
42
+ end
43
+ local itemOrderRef = React.useRef(0)
44
+ if itemOrderRef.current == 0 then
45
+ nextItemOrder += 1
46
+ itemOrderRef.current = nextItemOrder
47
+ end
48
+ React.useEffect(function()
49
+ return comboboxContext.registerItem({
50
+ id = itemIdRef.current,
51
+ value = props.value,
52
+ order = itemOrderRef.current,
53
+ getNode = function()
54
+ return itemRef.current
55
+ end,
56
+ getDisabled = function()
57
+ return disabledRef.current
58
+ end,
59
+ getTextValue = function()
60
+ return textValueRef.current
61
+ end,
62
+ })
63
+ end, { comboboxContext, props.value })
64
+ local setItemRef = React.useCallback(function(instance)
65
+ itemRef.current = toGuiObject(instance)
66
+ end, {})
67
+ local handleSelect = React.useCallback(function()
68
+ if disabled then
69
+ return nil
70
+ end
71
+ comboboxContext.setValue(props.value)
72
+ comboboxContext.setOpen(false)
73
+ end, { comboboxContext, disabled, props.value })
74
+ local handleInputBegan = React.useCallback(function(_rbx, inputObject)
75
+ if disabled then
76
+ return nil
77
+ end
78
+ local keyCode = inputObject.KeyCode
79
+ if keyCode ~= Enum.KeyCode.Return and keyCode ~= Enum.KeyCode.Space then
80
+ return nil
81
+ end
82
+ comboboxContext.setValue(props.value)
83
+ comboboxContext.setOpen(false)
84
+ end, { comboboxContext, disabled, props.value })
85
+ local eventHandlers = React.useMemo(function()
86
+ return {
87
+ Activated = handleSelect,
88
+ InputBegan = handleInputBegan,
89
+ }
90
+ end, { handleInputBegan, handleSelect })
91
+ if props.asChild then
92
+ local child = props.children
93
+ if not child then
94
+ error("[ComboboxItem] `asChild` requires a child element.")
95
+ end
96
+ return React.createElement(RovingFocusItem, {
97
+ asChild = true,
98
+ disabled = disabled,
99
+ }, React.createElement(Slot, {
100
+ Active = not disabled,
101
+ Event = eventHandlers,
102
+ Selectable = not disabled,
103
+ Visible = itemQueryMatch,
104
+ ref = setItemRef,
105
+ }, child))
106
+ end
107
+ return React.createElement(RovingFocusItem, {
108
+ asChild = true,
109
+ disabled = disabled,
110
+ }, React.createElement("textbutton", {
111
+ Active = not disabled,
112
+ AutoButtonColor = false,
113
+ BackgroundColor3 = Color3.fromRGB(47, 53, 68),
114
+ BorderSizePixel = 0,
115
+ Event = eventHandlers,
116
+ Selectable = not disabled,
117
+ Size = UDim2.fromOffset(220, 32),
118
+ Text = textValue,
119
+ TextColor3 = if disabled then Color3.fromRGB(134, 141, 156) else Color3.fromRGB(234, 239, 247),
120
+ TextSize = 15,
121
+ TextXAlignment = Enum.TextXAlignment.Left,
122
+ Visible = itemQueryMatch,
123
+ ref = setItemRef,
124
+ }, React.createElement("uipadding", {
125
+ PaddingLeft = UDim.new(0, 10),
126
+ PaddingRight = UDim.new(0, 10),
127
+ }), props.children))
128
+ end
129
+ return {
130
+ ComboboxItem = ComboboxItem,
131
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ComboboxLabelProps } from "./types";
3
+ export declare function ComboboxLabel(props: ComboboxLabelProps): React.JSX.Element;
@@ -0,0 +1,26 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local function ComboboxLabel(props)
7
+ if props.asChild then
8
+ local child = props.children
9
+ if not child then
10
+ error("[ComboboxLabel] `asChild` requires a child element.")
11
+ end
12
+ return React.createElement(Slot, nil, child)
13
+ end
14
+ return React.createElement("textlabel", {
15
+ BackgroundTransparency = 1,
16
+ BorderSizePixel = 0,
17
+ Size = UDim2.fromOffset(220, 20),
18
+ Text = "Label",
19
+ TextColor3 = Color3.fromRGB(168, 176, 191),
20
+ TextSize = 13,
21
+ TextXAlignment = Enum.TextXAlignment.Left,
22
+ }, props.children)
23
+ end
24
+ return {
25
+ ComboboxLabel = ComboboxLabel,
26
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ComboboxPortalProps } from "./types";
3
+ export declare function ComboboxPortal(props: ComboboxPortalProps): React.JSX.Element;
@@ -0,0 +1,33 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local React = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out).React
4
+ local _layer = TS.import(script, TS.getModule(script, "@lattice-ui", "layer").out)
5
+ local Portal = _layer.Portal
6
+ local PortalProvider = _layer.PortalProvider
7
+ local usePortalContext = _layer.usePortalContext
8
+ local function ComboboxPortalWithOverrides(props)
9
+ local portalContext = usePortalContext()
10
+ local container = props.container or portalContext.container
11
+ local _condition = props.displayOrderBase
12
+ if _condition == nil then
13
+ _condition = portalContext.displayOrderBase
14
+ end
15
+ local displayOrderBase = _condition
16
+ return React.createElement(PortalProvider, {
17
+ container = container,
18
+ displayOrderBase = displayOrderBase,
19
+ }, React.createElement(Portal, nil, props.children))
20
+ end
21
+ local function ComboboxPortal(props)
22
+ local hasOverrides = props.container ~= nil or props.displayOrderBase ~= nil
23
+ if hasOverrides then
24
+ return React.createElement(ComboboxPortalWithOverrides, {
25
+ container = props.container,
26
+ displayOrderBase = props.displayOrderBase,
27
+ }, props.children)
28
+ end
29
+ return React.createElement(Portal, nil, props.children)
30
+ end
31
+ return {
32
+ ComboboxPortal = ComboboxPortal,
33
+ }
@@ -0,0 +1,4 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ComboboxProps } from "./types";
3
+ export declare function ComboboxRoot(props: ComboboxProps): React.JSX.Element;
4
+ export { ComboboxRoot as Combobox };