@lattice-ui/layer 0.1.1 → 0.3.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/LICENSE +7 -0
- package/package.json +8 -4
- package/src/dismissable/DismissableLayer.tsx +0 -118
- package/src/dismissable/events.ts +0 -78
- package/src/dismissable/layerStack.ts +0 -147
- package/src/dismissable/types.ts +0 -18
- package/src/index.ts +0 -5
- package/src/internals/constants.ts +0 -3
- package/src/internals/env.ts +0 -7
- package/src/portal/Portal.tsx +0 -9
- package/src/portal/PortalProvider.tsx +0 -20
- package/src/portal/types.ts +0 -17
- package/src/presence/Presence.tsx +0 -93
- package/src/presence/types.ts +0 -16
- package/tsconfig.json +0 -16
- package/tsconfig.typecheck.json +0 -25
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 astra-void
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lattice-ui/layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "out/init.luau",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"out",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
7
11
|
"dependencies": {
|
|
8
|
-
"@lattice-ui/core": "0.
|
|
12
|
+
"@lattice-ui/core": "0.3.1"
|
|
9
13
|
},
|
|
10
14
|
"devDependencies": {
|
|
11
15
|
"@rbxts/react": "17.3.7-ts.1",
|
|
@@ -17,7 +21,7 @@
|
|
|
17
21
|
},
|
|
18
22
|
"scripts": {
|
|
19
23
|
"build": "rbxtsc -p tsconfig.json",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
24
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
25
|
+
"watch": "rbxtsc -p tsconfig.json -w"
|
|
22
26
|
}
|
|
23
27
|
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { React } from "@lattice-ui/core";
|
|
2
|
-
import { DEFAULT_LAYER_IGNORE_GUI_INSET } from "../internals/constants";
|
|
3
|
-
import { Portal } from "../portal/Portal";
|
|
4
|
-
import { usePortalContext } from "../portal/PortalProvider";
|
|
5
|
-
import { isOutsidePointerEvent } from "./events";
|
|
6
|
-
import { registerLayer, unregisterLayer } from "./layerStack";
|
|
7
|
-
import type { DismissableLayerProps, LayerInteractEvent } from "./types";
|
|
8
|
-
|
|
9
|
-
function useLatest<T>(value: T) {
|
|
10
|
-
const ref = React.useRef(value);
|
|
11
|
-
React.useEffect(() => {
|
|
12
|
-
ref.current = value;
|
|
13
|
-
}, [value]);
|
|
14
|
-
return ref;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function DismissableLayer(props: DismissableLayerProps) {
|
|
18
|
-
const enabled = props.enabled ?? true;
|
|
19
|
-
const shouldBlockOutsidePointer = props.modal === true || props.disableOutsidePointerEvents === true;
|
|
20
|
-
const layerIgnoresGuiInset = DEFAULT_LAYER_IGNORE_GUI_INSET;
|
|
21
|
-
|
|
22
|
-
const portalContext = usePortalContext();
|
|
23
|
-
const contentRootRef = React.useRef<Frame>();
|
|
24
|
-
const [stackOrder, setStackOrder] = React.useState(0);
|
|
25
|
-
|
|
26
|
-
const enabledRef = useLatest(enabled);
|
|
27
|
-
const onDismissRef = useLatest(props.onDismiss);
|
|
28
|
-
const onPointerDownOutsideRef = useLatest(props.onPointerDownOutside);
|
|
29
|
-
const onInteractOutsideRef = useLatest(props.onInteractOutside);
|
|
30
|
-
const onEscapeKeyDownRef = useLatest(props.onEscapeKeyDown);
|
|
31
|
-
|
|
32
|
-
const callPointerDownOutside = React.useCallback((event: LayerInteractEvent) => {
|
|
33
|
-
onPointerDownOutsideRef.current?.(event);
|
|
34
|
-
}, []);
|
|
35
|
-
|
|
36
|
-
const callInteractOutside = React.useCallback((event: LayerInteractEvent) => {
|
|
37
|
-
onInteractOutsideRef.current?.(event);
|
|
38
|
-
}, []);
|
|
39
|
-
|
|
40
|
-
const callEscape = React.useCallback((event: LayerInteractEvent) => {
|
|
41
|
-
onEscapeKeyDownRef.current?.(event);
|
|
42
|
-
}, []);
|
|
43
|
-
|
|
44
|
-
const callDismiss = React.useCallback(() => {
|
|
45
|
-
onDismissRef.current?.();
|
|
46
|
-
}, []);
|
|
47
|
-
|
|
48
|
-
React.useEffect(() => {
|
|
49
|
-
const registration = registerLayer({
|
|
50
|
-
getEnabled: () => enabledRef.current,
|
|
51
|
-
isPointerOutside: (inputObject) => {
|
|
52
|
-
const contentRoot = contentRootRef.current;
|
|
53
|
-
if (!contentRoot) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
return isOutsidePointerEvent(inputObject, portalContext.container, contentRoot, {
|
|
57
|
-
layerIgnoresGuiInset,
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
onPointerDownOutside: callPointerDownOutside,
|
|
61
|
-
onInteractOutside: callInteractOutside,
|
|
62
|
-
onEscapeKeyDown: callEscape,
|
|
63
|
-
onDismiss: callDismiss,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
setStackOrder(registration.mountOrder);
|
|
67
|
-
|
|
68
|
-
return () => {
|
|
69
|
-
unregisterLayer(registration.id);
|
|
70
|
-
};
|
|
71
|
-
}, [
|
|
72
|
-
callDismiss,
|
|
73
|
-
callEscape,
|
|
74
|
-
callInteractOutside,
|
|
75
|
-
callPointerDownOutside,
|
|
76
|
-
enabledRef,
|
|
77
|
-
layerIgnoresGuiInset,
|
|
78
|
-
portalContext.container,
|
|
79
|
-
]);
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<Portal>
|
|
83
|
-
<screengui
|
|
84
|
-
key={`Layer_${stackOrder}`}
|
|
85
|
-
DisplayOrder={portalContext.displayOrderBase + stackOrder}
|
|
86
|
-
IgnoreGuiInset={layerIgnoresGuiInset}
|
|
87
|
-
ResetOnSpawn={false}
|
|
88
|
-
ZIndexBehavior={Enum.ZIndexBehavior.Sibling}
|
|
89
|
-
>
|
|
90
|
-
{shouldBlockOutsidePointer ? (
|
|
91
|
-
<textbutton
|
|
92
|
-
Active={true}
|
|
93
|
-
AutoButtonColor={false}
|
|
94
|
-
BackgroundTransparency={1}
|
|
95
|
-
BorderSizePixel={0}
|
|
96
|
-
Modal={true}
|
|
97
|
-
Position={UDim2.fromScale(0, 0)}
|
|
98
|
-
Selectable={false}
|
|
99
|
-
Size={UDim2.fromScale(1, 1)}
|
|
100
|
-
Text=""
|
|
101
|
-
TextTransparency={1}
|
|
102
|
-
ZIndex={0}
|
|
103
|
-
/>
|
|
104
|
-
) : undefined}
|
|
105
|
-
<frame
|
|
106
|
-
BackgroundTransparency={1}
|
|
107
|
-
BorderSizePixel={0}
|
|
108
|
-
Position={UDim2.fromScale(0, 0)}
|
|
109
|
-
Size={UDim2.fromScale(1, 1)}
|
|
110
|
-
ref={contentRootRef}
|
|
111
|
-
ZIndex={1}
|
|
112
|
-
>
|
|
113
|
-
{props.children}
|
|
114
|
-
</frame>
|
|
115
|
-
</screengui>
|
|
116
|
-
</Portal>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { getGuiInsetTopLeft } from "../internals/env";
|
|
2
|
-
import type { LayerInteractEvent } from "./types";
|
|
3
|
-
|
|
4
|
-
type OutsidePointerOptions = {
|
|
5
|
-
layerIgnoresGuiInset: boolean;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export function isPointerInput(inputObject: InputObject) {
|
|
9
|
-
return (
|
|
10
|
-
inputObject.UserInputType === Enum.UserInputType.MouseButton1 ||
|
|
11
|
-
inputObject.UserInputType === Enum.UserInputType.Touch
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function toLayerInteractEvent(originalEvent: InputObject): LayerInteractEvent {
|
|
16
|
-
const event: LayerInteractEvent = {
|
|
17
|
-
originalEvent,
|
|
18
|
-
defaultPrevented: false,
|
|
19
|
-
preventDefault: () => {
|
|
20
|
-
event.defaultPrevented = true;
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
return event;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function addUniqueSample(samples: Array<Vector2>, sampleKeys: Record<string, true>, x: number, y: number) {
|
|
27
|
-
const roundedX = math.round(x);
|
|
28
|
-
const roundedY = math.round(y);
|
|
29
|
-
const key = `${roundedX}:${roundedY}`;
|
|
30
|
-
if (sampleKeys[key]) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
sampleKeys[key] = true;
|
|
35
|
-
samples.push(new Vector2(roundedX, roundedY));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getPointerSamples(pointerPosition: Vector2, options: OutsidePointerOptions) {
|
|
39
|
-
const insetTopLeft = getGuiInsetTopLeft();
|
|
40
|
-
|
|
41
|
-
const samples = new Array<Vector2>();
|
|
42
|
-
const sampleKeys: Record<string, true> = {};
|
|
43
|
-
|
|
44
|
-
addUniqueSample(samples, sampleKeys, pointerPosition.X, pointerPosition.Y);
|
|
45
|
-
addUniqueSample(samples, sampleKeys, pointerPosition.X + insetTopLeft.X, pointerPosition.Y + insetTopLeft.Y);
|
|
46
|
-
addUniqueSample(samples, sampleKeys, pointerPosition.X - insetTopLeft.X, pointerPosition.Y - insetTopLeft.Y);
|
|
47
|
-
|
|
48
|
-
if (options.layerIgnoresGuiInset) {
|
|
49
|
-
addUniqueSample(samples, sampleKeys, pointerPosition.X, pointerPosition.Y + insetTopLeft.Y);
|
|
50
|
-
addUniqueSample(samples, sampleKeys, pointerPosition.X, pointerPosition.Y - insetTopLeft.Y);
|
|
51
|
-
addUniqueSample(samples, sampleKeys, pointerPosition.X + insetTopLeft.X, pointerPosition.Y);
|
|
52
|
-
addUniqueSample(samples, sampleKeys, pointerPosition.X - insetTopLeft.X, pointerPosition.Y);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return samples;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function isOutsidePointerEvent(
|
|
59
|
-
inputObject: InputObject,
|
|
60
|
-
container: BasePlayerGui,
|
|
61
|
-
contentRoot: GuiObject,
|
|
62
|
-
options: OutsidePointerOptions,
|
|
63
|
-
) {
|
|
64
|
-
const rawPointerPosition = inputObject.Position;
|
|
65
|
-
const pointerPosition = new Vector2(rawPointerPosition.X, rawPointerPosition.Y);
|
|
66
|
-
const pointerSamples = getPointerSamples(pointerPosition, options);
|
|
67
|
-
|
|
68
|
-
for (const sample of pointerSamples) {
|
|
69
|
-
const hitGuiObjects = container.GetGuiObjectsAtPosition(sample.X, sample.Y);
|
|
70
|
-
for (const hitObject of hitGuiObjects) {
|
|
71
|
-
if (hitObject.IsDescendantOf(contentRoot)) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { GuiService, UserInputService } from "../internals/env";
|
|
2
|
-
import { isPointerInput, toLayerInteractEvent } from "./events";
|
|
3
|
-
import type { LayerInteractEvent } from "./types";
|
|
4
|
-
|
|
5
|
-
type LayerEntry = {
|
|
6
|
-
id: number;
|
|
7
|
-
mountOrder: number;
|
|
8
|
-
getEnabled: () => boolean;
|
|
9
|
-
isPointerOutside: (inputObject: InputObject) => boolean;
|
|
10
|
-
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
11
|
-
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
12
|
-
onEscapeKeyDown?: (event: LayerInteractEvent) => void;
|
|
13
|
-
onDismiss?: () => void;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type RegisterLayerParams = Omit<LayerEntry, "id" | "mountOrder">;
|
|
17
|
-
|
|
18
|
-
export type LayerRegistration = {
|
|
19
|
-
id: number;
|
|
20
|
-
mountOrder: number;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const layerEntries = new Array<LayerEntry>();
|
|
24
|
-
let nextLayerId = 0;
|
|
25
|
-
let nextMountOrder = 0;
|
|
26
|
-
let inputConnection: RBXScriptConnection | undefined;
|
|
27
|
-
|
|
28
|
-
function getTopMostEnabledLayer() {
|
|
29
|
-
for (let index = layerEntries.size() - 1; index >= 0; index--) {
|
|
30
|
-
const entry = layerEntries[index];
|
|
31
|
-
if (entry.getEnabled()) {
|
|
32
|
-
return entry;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function handleDismissEvent(entry: LayerEntry, event: LayerInteractEvent) {
|
|
39
|
-
if (!event.defaultPrevented) {
|
|
40
|
-
entry.onDismiss?.();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function shouldIgnoreEscapeDismiss() {
|
|
45
|
-
const focusedTextBox = UserInputService.GetFocusedTextBox();
|
|
46
|
-
if (focusedTextBox) {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const selectedObject = GuiService.SelectedObject;
|
|
51
|
-
if (selectedObject && selectedObject.IsA("TextBox")) {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function handleInputBegan(inputObject: InputObject, gameProcessedEvent: boolean) {
|
|
59
|
-
if (gameProcessedEvent) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const topLayer = getTopMostEnabledLayer();
|
|
64
|
-
if (!topLayer) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (inputObject.KeyCode === Enum.KeyCode.Escape) {
|
|
69
|
-
if (shouldIgnoreEscapeDismiss()) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const escapeEvent = toLayerInteractEvent(inputObject);
|
|
74
|
-
topLayer.onEscapeKeyDown?.(escapeEvent);
|
|
75
|
-
handleDismissEvent(topLayer, escapeEvent);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!isPointerInput(inputObject)) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!topLayer.isPointerOutside(inputObject)) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const outsideEvent = toLayerInteractEvent(inputObject);
|
|
88
|
-
topLayer.onPointerDownOutside?.(outsideEvent);
|
|
89
|
-
topLayer.onInteractOutside?.(outsideEvent);
|
|
90
|
-
handleDismissEvent(topLayer, outsideEvent);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function startInputListener() {
|
|
94
|
-
if (inputConnection) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
inputConnection = UserInputService.InputBegan.Connect((inputObject, gameProcessedEvent) => {
|
|
99
|
-
handleInputBegan(inputObject, gameProcessedEvent);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function stopInputListener() {
|
|
104
|
-
if (!inputConnection) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
inputConnection.Disconnect();
|
|
109
|
-
inputConnection = undefined;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function syncInputListener() {
|
|
113
|
-
if (layerEntries.size() > 0) {
|
|
114
|
-
startInputListener();
|
|
115
|
-
} else {
|
|
116
|
-
stopInputListener();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function registerLayer(params: RegisterLayerParams): LayerRegistration {
|
|
121
|
-
nextLayerId += 1;
|
|
122
|
-
nextMountOrder += 1;
|
|
123
|
-
|
|
124
|
-
const entry: LayerEntry = {
|
|
125
|
-
id: nextLayerId,
|
|
126
|
-
mountOrder: nextMountOrder,
|
|
127
|
-
...params,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
layerEntries.push(entry);
|
|
131
|
-
syncInputListener();
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
id: entry.id,
|
|
135
|
-
mountOrder: entry.mountOrder,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function unregisterLayer(layerId: number) {
|
|
140
|
-
const layerIndex = layerEntries.findIndex((entry) => entry.id === layerId);
|
|
141
|
-
if (layerIndex === -1) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
layerEntries.remove(layerIndex);
|
|
146
|
-
syncInputListener();
|
|
147
|
-
}
|
package/src/dismissable/types.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type React from "@rbxts/react";
|
|
2
|
-
|
|
3
|
-
export type LayerInteractEvent = {
|
|
4
|
-
originalEvent: InputObject;
|
|
5
|
-
defaultPrevented: boolean;
|
|
6
|
-
preventDefault: () => void;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export type DismissableLayerProps = {
|
|
10
|
-
children?: React.ReactNode;
|
|
11
|
-
enabled?: boolean;
|
|
12
|
-
modal?: boolean;
|
|
13
|
-
disableOutsidePointerEvents?: boolean;
|
|
14
|
-
onPointerDownOutside?: (event: LayerInteractEvent) => void;
|
|
15
|
-
onInteractOutside?: (event: LayerInteractEvent) => void;
|
|
16
|
-
onEscapeKeyDown?: (event: LayerInteractEvent) => void;
|
|
17
|
-
onDismiss?: () => void;
|
|
18
|
-
};
|
package/src/index.ts
DELETED
package/src/internals/env.ts
DELETED
package/src/portal/Portal.tsx
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { ReactRoblox } from "@lattice-ui/core";
|
|
2
|
-
import { usePortalContext } from "./PortalProvider";
|
|
3
|
-
import type { PortalProps } from "./types";
|
|
4
|
-
|
|
5
|
-
export function Portal(props: PortalProps) {
|
|
6
|
-
const contextValue = usePortalContext();
|
|
7
|
-
const target = props.container ?? contextValue.container;
|
|
8
|
-
return ReactRoblox.createPortal(props.children, target);
|
|
9
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { createStrictContext, React } from "@lattice-ui/core";
|
|
2
|
-
import { DEFAULT_DISPLAY_ORDER_BASE } from "../internals/constants";
|
|
3
|
-
import type { PortalContextValue, PortalProviderProps } from "./types";
|
|
4
|
-
|
|
5
|
-
const [PortalContextProvider, usePortalContext] = createStrictContext<PortalContextValue>("PortalProvider");
|
|
6
|
-
|
|
7
|
-
export function PortalProvider(props: PortalProviderProps) {
|
|
8
|
-
const displayOrderBase = props.displayOrderBase ?? DEFAULT_DISPLAY_ORDER_BASE;
|
|
9
|
-
const contextValue = React.useMemo(
|
|
10
|
-
() => ({
|
|
11
|
-
container: props.container,
|
|
12
|
-
displayOrderBase,
|
|
13
|
-
}),
|
|
14
|
-
[displayOrderBase, props.container],
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
return <PortalContextProvider value={contextValue}>{props.children}</PortalContextProvider>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export { usePortalContext };
|
package/src/portal/types.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type React from "@rbxts/react";
|
|
2
|
-
|
|
3
|
-
export type PortalContextValue = {
|
|
4
|
-
container: BasePlayerGui;
|
|
5
|
-
displayOrderBase: number;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type PortalProviderProps = {
|
|
9
|
-
container: BasePlayerGui;
|
|
10
|
-
displayOrderBase?: number;
|
|
11
|
-
children?: React.ReactNode;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type PortalProps = {
|
|
15
|
-
children?: React.ReactNode;
|
|
16
|
-
container?: Instance;
|
|
17
|
-
};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { React } from "@lattice-ui/core";
|
|
2
|
-
import { DEFAULT_PRESENCE_EXIT_FALLBACK_MS } from "../internals/constants";
|
|
3
|
-
import type { PresenceProps } from "./types";
|
|
4
|
-
|
|
5
|
-
export function Presence(props: PresenceProps) {
|
|
6
|
-
const [mounted, setMounted] = React.useState(props.present);
|
|
7
|
-
const [isPresent, setIsPresent] = React.useState(props.present);
|
|
8
|
-
const mountedRef = React.useRef(mounted);
|
|
9
|
-
const fallbackTaskRef = React.useRef<thread>();
|
|
10
|
-
const onExitCompleteRef = React.useRef(props.onExitComplete);
|
|
11
|
-
|
|
12
|
-
React.useEffect(() => {
|
|
13
|
-
onExitCompleteRef.current = props.onExitComplete;
|
|
14
|
-
}, [props.onExitComplete]);
|
|
15
|
-
|
|
16
|
-
React.useEffect(() => {
|
|
17
|
-
mountedRef.current = mounted;
|
|
18
|
-
}, [mounted]);
|
|
19
|
-
|
|
20
|
-
const completeExit = React.useCallback(() => {
|
|
21
|
-
if (!mountedRef.current) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const fallbackTask = fallbackTaskRef.current;
|
|
26
|
-
if (fallbackTask) {
|
|
27
|
-
task.cancel(fallbackTask);
|
|
28
|
-
fallbackTaskRef.current = undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
mountedRef.current = false;
|
|
32
|
-
setMounted(false);
|
|
33
|
-
onExitCompleteRef.current?.();
|
|
34
|
-
}, []);
|
|
35
|
-
|
|
36
|
-
React.useEffect(() => {
|
|
37
|
-
if (props.present) {
|
|
38
|
-
const fallbackTask = fallbackTaskRef.current;
|
|
39
|
-
if (fallbackTask) {
|
|
40
|
-
task.cancel(fallbackTask);
|
|
41
|
-
fallbackTaskRef.current = undefined;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!mountedRef.current) {
|
|
45
|
-
mountedRef.current = true;
|
|
46
|
-
setMounted(true);
|
|
47
|
-
}
|
|
48
|
-
setIsPresent(true);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!mountedRef.current) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
setIsPresent(false);
|
|
57
|
-
|
|
58
|
-
const fallbackTask = fallbackTaskRef.current;
|
|
59
|
-
if (fallbackTask) {
|
|
60
|
-
task.cancel(fallbackTask);
|
|
61
|
-
fallbackTaskRef.current = undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const timeoutMs = props.exitFallbackMs ?? DEFAULT_PRESENCE_EXIT_FALLBACK_MS;
|
|
65
|
-
fallbackTaskRef.current = task.delay(timeoutMs / 1000, () => {
|
|
66
|
-
completeExit();
|
|
67
|
-
});
|
|
68
|
-
}, [completeExit, props.exitFallbackMs, props.present]);
|
|
69
|
-
|
|
70
|
-
React.useEffect(() => {
|
|
71
|
-
return () => {
|
|
72
|
-
const fallbackTask = fallbackTaskRef.current;
|
|
73
|
-
if (fallbackTask) {
|
|
74
|
-
task.cancel(fallbackTask);
|
|
75
|
-
fallbackTaskRef.current = undefined;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}, []);
|
|
79
|
-
|
|
80
|
-
if (!mounted) {
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const render = props.render ?? props.children;
|
|
85
|
-
if (!render) {
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return render({
|
|
90
|
-
isPresent,
|
|
91
|
-
onExitComplete: completeExit,
|
|
92
|
-
});
|
|
93
|
-
}
|
package/src/presence/types.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type React from "@rbxts/react";
|
|
2
|
-
|
|
3
|
-
export type PresenceRenderState = {
|
|
4
|
-
isPresent: boolean;
|
|
5
|
-
onExitComplete: () => void;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type PresenceRender = (state: PresenceRenderState) => React.ReactElement | undefined;
|
|
9
|
-
|
|
10
|
-
export type PresenceProps = {
|
|
11
|
-
present: boolean;
|
|
12
|
-
exitFallbackMs?: number;
|
|
13
|
-
onExitComplete?: () => void;
|
|
14
|
-
children?: PresenceRender;
|
|
15
|
-
render?: PresenceRender;
|
|
16
|
-
};
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"outDir": "out",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"typeRoots": [
|
|
8
|
-
"./node_modules/@rbxts",
|
|
9
|
-
"../../node_modules/@rbxts",
|
|
10
|
-
"./node_modules/@lattice-ui",
|
|
11
|
-
"../../node_modules/@lattice-ui"
|
|
12
|
-
],
|
|
13
|
-
"types": ["types", "compiler-types"]
|
|
14
|
-
},
|
|
15
|
-
"include": ["src"]
|
|
16
|
-
}
|
package/tsconfig.typecheck.json
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"noEmit": true,
|
|
5
|
-
"baseUrl": "..",
|
|
6
|
-
"rootDir": "..",
|
|
7
|
-
"paths": {
|
|
8
|
-
"@lattice-ui/checkbox": ["checkbox/src/index.ts"],
|
|
9
|
-
"@lattice-ui/core": ["core/src/index.ts"],
|
|
10
|
-
"@lattice-ui/dialog": ["dialog/src/index.ts"],
|
|
11
|
-
"@lattice-ui/focus": ["focus/src/index.ts"],
|
|
12
|
-
"@lattice-ui/layer": ["layer/src/index.ts"],
|
|
13
|
-
"@lattice-ui/menu": ["menu/src/index.ts"],
|
|
14
|
-
"@lattice-ui/popover": ["popover/src/index.ts"],
|
|
15
|
-
"@lattice-ui/popper": ["popper/src/index.ts"],
|
|
16
|
-
"@lattice-ui/radio-group": ["radio-group/src/index.ts"],
|
|
17
|
-
"@lattice-ui/style": ["style/src/index.ts"],
|
|
18
|
-
"@lattice-ui/switch": ["switch/src/index.ts"],
|
|
19
|
-
"@lattice-ui/system": ["system/src/index.ts"],
|
|
20
|
-
"@lattice-ui/tabs": ["tabs/src/index.ts"],
|
|
21
|
-
"@lattice-ui/toggle-group": ["toggle-group/src/index.ts"],
|
|
22
|
-
"@lattice-ui/tooltip": ["tooltip/src/index.ts"]
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|