@lattice-ui/combobox 0.5.0-next.1 → 0.5.0-next.2

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.
@@ -1,17 +1,23 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
3
  local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local composeRefs = _core.composeRefs
5
+ local getElementRef = _core.getElementRef
4
6
  local React = _core.React
5
- local Slot = _core.Slot
6
7
  local _layer = TS.import(script, TS.getModule(script, "@lattice-ui", "layer").out)
7
8
  local DismissableLayer = _layer.DismissableLayer
8
9
  local Presence = _layer.Presence
9
10
  local _motion = TS.import(script, TS.getModule(script, "@lattice-ui", "motion").out)
10
- local createPopperEntranceRecipe = _motion.createPopperEntranceRecipe
11
- local usePresenceMotion = _motion.usePresenceMotion
11
+ local createCanvasGroupPopperEntranceRecipe = _motion.createCanvasGroupPopperEntranceRecipe
12
+ local usePresenceMotionController = _motion.usePresenceMotionController
12
13
  local usePopper = TS.import(script, TS.getModule(script, "@lattice-ui", "popper").out).usePopper
13
14
  local useComboboxContext = TS.import(script, script.Parent, "context").useComboboxContext
14
15
  local CONTENT_OFFSET = 6
16
+ local HIDDEN_POSITION = UDim2.fromOffset(-9999, -9999)
17
+ local function toGuiPropBag(value)
18
+ local _value = value
19
+ return if type(_value) == "table" then value else {}
20
+ end
15
21
  local function toGuiObject(instance)
16
22
  if not instance or not instance:IsA("GuiObject") then
17
23
  return nil
@@ -21,43 +27,64 @@ end
21
27
  local function ComboboxContentImpl(props)
22
28
  local comboboxContext = useComboboxContext()
23
29
  local open = comboboxContext.open
30
+ local shouldMeasure = open or props.motionPresent or props.onExitComplete ~= nil
24
31
  local popper = usePopper({
25
32
  anchorRef = comboboxContext.anchorRef,
26
33
  contentRef = comboboxContext.contentRef,
27
34
  placement = props.placement,
28
35
  offset = props.offset,
29
36
  padding = props.padding,
30
- enabled = open,
37
+ enabled = shouldMeasure,
31
38
  })
32
39
  local defaultTransition = React.useMemo(function()
33
- return createPopperEntranceRecipe(popper.placement, CONTENT_OFFSET)
40
+ return createCanvasGroupPopperEntranceRecipe(popper.placement, CONTENT_OFFSET)
34
41
  end, { popper.placement })
35
- local motionRef = usePresenceMotion(props.motionPresent and popper.isPositioned, props.transition or defaultTransition, props.onExitComplete)
42
+ local recipe = props.transition or defaultTransition
43
+ local motion = usePresenceMotionController({
44
+ present = props.motionPresent,
45
+ ready = popper.isPositioned,
46
+ forceMount = props.forceMount,
47
+ config = recipe,
48
+ onExitComplete = props.onExitComplete,
49
+ })
36
50
  local setContentRef = React.useCallback(function(instance)
37
- comboboxContext.contentRef.current = toGuiObject(instance)
38
- motionRef.current = toGuiObject(instance)
39
- end, { comboboxContext.contentRef, motionRef })
51
+ local guiObject = toGuiObject(instance)
52
+ comboboxContext.contentRef.current = guiObject
53
+ motion.ref.current = guiObject
54
+ end, { comboboxContext.contentRef, motion.ref })
40
55
  local handleDismiss = React.useCallback(function()
41
56
  comboboxContext.setOpen(false)
42
57
  end, { comboboxContext })
43
- local isActuallyVisible = open or (props.motionPresent and popper.isPositioned)
58
+ local shouldRender = motion.mounted
59
+ local contentVisible = shouldRender and (motion.present or motion.phase ~= "exited")
60
+ local popperPosition = if popper.isPositioned then popper.position else HIDDEN_POSITION
44
61
  local contentNode = if props.asChild then ((function()
45
62
  local child = props.children
46
63
  if not React.isValidElement(child) then
47
64
  error("[ComboboxContent] `asChild` requires a child element.")
48
65
  end
49
- return React.createElement(Slot, {
50
- AnchorPoint = popper.anchorPoint,
51
- Visible = isActuallyVisible,
66
+ local childProps = toGuiPropBag(child.props)
67
+ local childRef = getElementRef(child)
68
+ local _exp = child
69
+ local _object = table.clone(childProps)
70
+ setmetatable(_object, nil)
71
+ _object.Position = UDim2.fromOffset(0, 0)
72
+ _object.Visible = contentVisible
73
+ _object.ref = composeRefs(childRef)
74
+ return React.createElement("canvasgroup", {
75
+ AutomaticSize = Enum.AutomaticSize.XY,
76
+ BackgroundTransparency = 1,
77
+ BorderSizePixel = 0,
78
+ Size = UDim2.fromOffset(0, 0),
79
+ Visible = contentVisible,
52
80
  ref = setContentRef,
53
- }, child)
54
- end)()) else (React.createElement("frame", {
55
- AnchorPoint = popper.anchorPoint,
81
+ }, React.cloneElement(_exp, _object))
82
+ end)()) else (React.createElement("canvasgroup", {
56
83
  AutomaticSize = Enum.AutomaticSize.XY,
57
84
  BackgroundTransparency = 1,
58
85
  BorderSizePixel = 0,
59
86
  Size = UDim2.fromOffset(0, 0),
60
- Visible = isActuallyVisible,
87
+ Visible = contentVisible,
61
88
  ref = setContentRef,
62
89
  }, props.children))
63
90
  return React.createElement(DismissableLayer, {
@@ -68,10 +95,12 @@ local function ComboboxContentImpl(props)
68
95
  onInteractOutside = props.onInteractOutside,
69
96
  onPointerDownOutside = props.onPointerDownOutside,
70
97
  }, React.createElement("frame", {
98
+ AnchorPoint = popper.anchorPoint,
71
99
  BackgroundTransparency = 1,
72
100
  BorderSizePixel = 0,
73
- Position = if popper.isPositioned then popper.position else UDim2.fromOffset(-9999, -9999),
101
+ Position = popperPosition,
74
102
  Size = UDim2.fromOffset(0, 0),
103
+ Visible = shouldRender,
75
104
  }, contentNode))
76
105
  end
77
106
  local function ComboboxContent(props)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lattice-ui/combobox",
3
- "version": "0.5.0-next.1",
3
+ "version": "0.5.0-next.2",
4
4
  "private": false,
5
5
  "main": "out/init.luau",
6
6
  "types": "out/index.d.ts",
@@ -16,10 +16,10 @@
16
16
  "url": "https://github.com/astra-void/lattice-ui.git"
17
17
  },
18
18
  "dependencies": {
19
- "@lattice-ui/core": "0.5.0-next.1",
20
- "@lattice-ui/layer": "0.5.0-next.1",
21
- "@lattice-ui/motion": "0.5.0-next.1",
22
- "@lattice-ui/popper": "0.5.0-next.1"
19
+ "@lattice-ui/core": "0.5.0-next.2",
20
+ "@lattice-ui/motion": "0.5.0-next.2",
21
+ "@lattice-ui/popper": "0.5.0-next.2",
22
+ "@lattice-ui/layer": "0.5.0-next.2"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@rbxts/react": "17.3.7-ts.1",
@@ -1,13 +1,20 @@
1
- import { React, Slot } from "@lattice-ui/core";
1
+ import { composeRefs, getElementRef, React } from "@lattice-ui/core";
2
2
  import type { LayerInteractEvent } from "@lattice-ui/layer";
3
3
  import { DismissableLayer, Presence } from "@lattice-ui/layer";
4
- import { createPopperEntranceRecipe, usePresenceMotion } from "@lattice-ui/motion";
4
+ import { createCanvasGroupPopperEntranceRecipe, usePresenceMotionController } from "@lattice-ui/motion";
5
5
  import type { PopperPlacement } from "@lattice-ui/popper";
6
6
  import { usePopper } from "@lattice-ui/popper";
7
7
  import { useComboboxContext } from "./context";
8
8
  import type { ComboboxContentProps } from "./types";
9
9
 
10
10
  const CONTENT_OFFSET = 6;
11
+ const HIDDEN_POSITION = UDim2.fromOffset(-9999, -9999);
12
+
13
+ type GuiPropBag = React.Attributes & Record<string, unknown>;
14
+
15
+ function toGuiPropBag(value: unknown): GuiPropBag {
16
+ return typeIs(value, "table") ? (value as GuiPropBag) : {};
17
+ }
11
18
 
12
19
  function toGuiObject(instance: Instance | undefined) {
13
20
  if (!instance || !instance.IsA("GuiObject")) {
@@ -31,6 +38,7 @@ function ComboboxContentImpl(props: {
31
38
  }) {
32
39
  const comboboxContext = useComboboxContext();
33
40
  const open = comboboxContext.open;
41
+ const shouldMeasure = open || props.motionPresent || props.onExitComplete !== undefined;
34
42
 
35
43
  const popper = usePopper({
36
44
  anchorRef: comboboxContext.anchorRef,
@@ -38,31 +46,39 @@ function ComboboxContentImpl(props: {
38
46
  placement: props.placement,
39
47
  offset: props.offset,
40
48
  padding: props.padding,
41
- enabled: open,
49
+ enabled: shouldMeasure,
42
50
  });
43
51
 
44
52
  const defaultTransition = React.useMemo(
45
- () => createPopperEntranceRecipe(popper.placement, CONTENT_OFFSET),
53
+ () => createCanvasGroupPopperEntranceRecipe(popper.placement, CONTENT_OFFSET),
46
54
  [popper.placement],
47
55
  );
48
- const motionRef = usePresenceMotion<GuiObject>(
49
- props.motionPresent && popper.isPositioned,
50
- props.transition ?? defaultTransition,
51
- props.onExitComplete,
52
- );
56
+ const recipe = props.transition ?? defaultTransition;
57
+
58
+ const motion = usePresenceMotionController<GuiObject>({
59
+ present: props.motionPresent,
60
+ ready: popper.isPositioned,
61
+ forceMount: props.forceMount,
62
+ config: recipe,
63
+ onExitComplete: props.onExitComplete,
64
+ });
53
65
 
54
66
  const setContentRef = React.useCallback(
55
67
  (instance: Instance | undefined) => {
56
- comboboxContext.contentRef.current = toGuiObject(instance);
57
- motionRef.current = toGuiObject(instance);
68
+ const guiObject = toGuiObject(instance);
69
+ comboboxContext.contentRef.current = guiObject;
70
+ motion.ref.current = guiObject;
58
71
  },
59
- [comboboxContext.contentRef, motionRef],
72
+ [comboboxContext.contentRef, motion.ref],
60
73
  );
61
74
 
62
75
  const handleDismiss = React.useCallback(() => {
63
76
  comboboxContext.setOpen(false);
64
77
  }, [comboboxContext]);
65
- const isActuallyVisible = open || (props.motionPresent && popper.isPositioned);
78
+
79
+ const shouldRender = motion.mounted;
80
+ const contentVisible = shouldRender && (motion.present || motion.phase !== "exited");
81
+ const popperPosition = popper.isPositioned ? popper.position : HIDDEN_POSITION;
66
82
 
67
83
  const contentNode = props.asChild ? (
68
84
  (() => {
@@ -71,24 +87,38 @@ function ComboboxContentImpl(props: {
71
87
  error("[ComboboxContent] `asChild` requires a child element.");
72
88
  }
73
89
 
90
+ const childProps = toGuiPropBag((child as { props?: unknown }).props);
91
+ const childRef = getElementRef<Instance>(child);
92
+
74
93
  return (
75
- <Slot AnchorPoint={popper.anchorPoint} Visible={isActuallyVisible} ref={setContentRef}>
76
- {child}
77
- </Slot>
94
+ <canvasgroup
95
+ AutomaticSize={Enum.AutomaticSize.XY}
96
+ BackgroundTransparency={1}
97
+ BorderSizePixel={0}
98
+ Size={UDim2.fromOffset(0, 0)}
99
+ Visible={contentVisible}
100
+ ref={setContentRef as React.Ref<CanvasGroup>}
101
+ >
102
+ {React.cloneElement(child as React.ReactElement<GuiPropBag>, {
103
+ ...childProps,
104
+ Position: UDim2.fromOffset(0, 0),
105
+ Visible: contentVisible,
106
+ ref: composeRefs(childRef),
107
+ })}
108
+ </canvasgroup>
78
109
  );
79
110
  })()
80
111
  ) : (
81
- <frame
82
- AnchorPoint={popper.anchorPoint}
112
+ <canvasgroup
83
113
  AutomaticSize={Enum.AutomaticSize.XY}
84
114
  BackgroundTransparency={1}
85
115
  BorderSizePixel={0}
86
116
  Size={UDim2.fromOffset(0, 0)}
87
- Visible={isActuallyVisible}
117
+ Visible={contentVisible}
88
118
  ref={setContentRef}
89
119
  >
90
120
  {props.children}
91
- </frame>
121
+ </canvasgroup>
92
122
  );
93
123
 
94
124
  return (
@@ -101,10 +131,12 @@ function ComboboxContentImpl(props: {
101
131
  onPointerDownOutside={props.onPointerDownOutside}
102
132
  >
103
133
  <frame
134
+ AnchorPoint={popper.anchorPoint}
104
135
  BackgroundTransparency={1}
105
136
  BorderSizePixel={0}
106
- Position={popper.isPositioned ? popper.position : UDim2.fromOffset(-9999, -9999)}
137
+ Position={popperPosition}
107
138
  Size={UDim2.fromOffset(0, 0)}
139
+ Visible={shouldRender}
108
140
  >
109
141
  {contentNode}
110
142
  </frame>