@lattice-ui/core 0.1.1 → 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.
- package/out/slot.luau +24 -0
- package/out/useControllableState.luau +15 -4
- package/package.json +8 -4
- package/scripts/ensure-hoisted-links.mjs +0 -35
- package/src/context.ts +0 -15
- package/src/index.ts +0 -6
- package/src/react.ts +0 -3
- package/src/reactRoblox.ts +0 -3
- package/src/refs.ts +0 -28
- package/src/slot.tsx +0 -116
- package/src/useControllableState.ts +0 -28
- package/tsconfig.json +0 -11
- package/tsconfig.typecheck.json +0 -25
package/out/slot.luau
CHANGED
|
@@ -77,6 +77,27 @@ local function mergeHandlerTable(a, b)
|
|
|
77
77
|
end
|
|
78
78
|
return out
|
|
79
79
|
end
|
|
80
|
+
local function moveHandlersToReactKeyedProps(props, key)
|
|
81
|
+
local handlers = toHandlerTable(props[key])
|
|
82
|
+
if not handlers then
|
|
83
|
+
props[key] = nil
|
|
84
|
+
return nil
|
|
85
|
+
end
|
|
86
|
+
local reactRuntime = React
|
|
87
|
+
local source = if key == "Event" then reactRuntime.Event else reactRuntime.Change
|
|
88
|
+
local dynamicProps = props
|
|
89
|
+
for rawKey, candidate in pairs(handlers) do
|
|
90
|
+
if not (type(rawKey) == "string") or not isFn(candidate) then
|
|
91
|
+
continue
|
|
92
|
+
end
|
|
93
|
+
local reactKey = source[rawKey]
|
|
94
|
+
if reactKey == nil then
|
|
95
|
+
continue
|
|
96
|
+
end
|
|
97
|
+
dynamicProps[reactKey] = candidate
|
|
98
|
+
end
|
|
99
|
+
props[key] = nil
|
|
100
|
+
end
|
|
80
101
|
local Slot = React.forwardRef(function(props, forwardedRef)
|
|
81
102
|
local child = props.children
|
|
82
103
|
local childProps = toSlotPropBag(child.props)
|
|
@@ -103,6 +124,9 @@ local Slot = React.forwardRef(function(props, forwardedRef)
|
|
|
103
124
|
local childRef = toForwardedRef(childProps.ref)
|
|
104
125
|
local mergedRef = composeRefs(childRef, forwardedRef, slotRef)
|
|
105
126
|
mergedProps.ref = mergedRef
|
|
127
|
+
-- cloneElement bypasses @rbxts/react createElement Event/Change normalization.
|
|
128
|
+
moveHandlersToReactKeyedProps(mergedProps, "Event")
|
|
129
|
+
moveHandlersToReactKeyedProps(mergedProps, "Change")
|
|
106
130
|
return React.cloneElement(child, mergedProps)
|
|
107
131
|
end)
|
|
108
132
|
Slot.displayName = "Slot"
|
|
@@ -12,16 +12,27 @@ local function useControllableState(_param)
|
|
|
12
12
|
local inner, setInner = React.useState(defaultValue)
|
|
13
13
|
local controlled = value ~= nil
|
|
14
14
|
local state = if value ~= nil then value else inner
|
|
15
|
+
local stateRef = React.useRef(state)
|
|
16
|
+
local controlledRef = React.useRef(controlled)
|
|
17
|
+
local onChangeRef = React.useRef(onChange)
|
|
18
|
+
stateRef.current = state
|
|
19
|
+
controlledRef.current = controlled
|
|
20
|
+
onChangeRef.current = onChange
|
|
15
21
|
local setState = React.useCallback(function(nextValue)
|
|
16
|
-
local
|
|
17
|
-
if
|
|
22
|
+
local current = stateRef.current
|
|
23
|
+
local computed = if isUpdater(nextValue) then nextValue(current) else nextValue
|
|
24
|
+
if computed == current then
|
|
25
|
+
return nil
|
|
26
|
+
end
|
|
27
|
+
stateRef.current = computed
|
|
28
|
+
if not controlledRef.current then
|
|
18
29
|
setInner(computed)
|
|
19
30
|
end
|
|
20
|
-
local _result =
|
|
31
|
+
local _result = onChangeRef.current
|
|
21
32
|
if _result ~= nil then
|
|
22
33
|
_result(computed)
|
|
23
34
|
end
|
|
24
|
-
end, {
|
|
35
|
+
end, {})
|
|
25
36
|
return { state, setState }
|
|
26
37
|
end
|
|
27
38
|
return {
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lattice-ui/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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
|
"devDependencies": {
|
|
8
12
|
"@rbxts/react": "17.3.7-ts.1",
|
|
9
13
|
"@rbxts/react-roblox": "17.3.7-ts.1"
|
|
@@ -13,10 +17,10 @@
|
|
|
13
17
|
"@rbxts/react-roblox": "^17"
|
|
14
18
|
},
|
|
15
19
|
"scripts": {
|
|
16
|
-
"prebuild": "node ./scripts/ensure-hoisted-links.mjs",
|
|
17
20
|
"build": "rbxtsc -p tsconfig.json",
|
|
21
|
+
"prebuild": "node ./scripts/ensure-hoisted-links.mjs",
|
|
18
22
|
"prewatch": "node ./scripts/ensure-hoisted-links.mjs",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
23
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
24
|
+
"watch": "rbxtsc -p tsconfig.json -w"
|
|
21
25
|
}
|
|
22
26
|
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const packageDir = path.resolve(scriptDir, "..");
|
|
7
|
-
const packageNodeModulesDir = path.join(packageDir, "node_modules");
|
|
8
|
-
const rootNodeModulesDir = path.resolve(packageDir, "../../node_modules");
|
|
9
|
-
const scopedDirs = ["@rbxts", "@rbxts-js"];
|
|
10
|
-
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
11
|
-
|
|
12
|
-
fs.mkdirSync(packageNodeModulesDir, { recursive: true });
|
|
13
|
-
|
|
14
|
-
for (const scopedDir of scopedDirs) {
|
|
15
|
-
const targetPath = path.join(rootNodeModulesDir, scopedDir);
|
|
16
|
-
const linkPath = path.join(packageNodeModulesDir, scopedDir);
|
|
17
|
-
|
|
18
|
-
if (!fs.existsSync(targetPath)) {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let shouldRelink = true;
|
|
23
|
-
if (fs.existsSync(linkPath)) {
|
|
24
|
-
const current = fs.realpathSync(linkPath);
|
|
25
|
-
const expected = fs.realpathSync(targetPath);
|
|
26
|
-
shouldRelink = current !== expected;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!shouldRelink) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
fs.rmSync(linkPath, { recursive: true, force: true });
|
|
34
|
-
fs.symlinkSync(targetPath, linkPath, symlinkType);
|
|
35
|
-
}
|
package/src/context.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from "@rbxts/react";
|
|
2
|
-
|
|
3
|
-
export function createStrictContext<T>(name: string) {
|
|
4
|
-
const Ctx = React.createContext<T | undefined>(undefined);
|
|
5
|
-
|
|
6
|
-
function useCtx(): T {
|
|
7
|
-
const v = React.useContext(Ctx);
|
|
8
|
-
if (v === undefined) {
|
|
9
|
-
error(`[${name}] context is undefined. Wrap components with <${name}.Provider>.`);
|
|
10
|
-
}
|
|
11
|
-
return v;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return [Ctx.Provider, useCtx] as const;
|
|
15
|
-
}
|
package/src/index.ts
DELETED
package/src/react.ts
DELETED
package/src/reactRoblox.ts
DELETED
package/src/refs.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type React from "@rbxts/react";
|
|
2
|
-
|
|
3
|
-
type AnyRef<T> = React.Ref<T> | React.ForwardedRef<T>;
|
|
4
|
-
type RefCallback<T> = (value: T | undefined) => void;
|
|
5
|
-
|
|
6
|
-
function isRefCallback<T>(ref: AnyRef<T> | undefined): ref is RefCallback<T> {
|
|
7
|
-
return typeIs(ref, "function");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function isMutableRefObject<T>(ref: AnyRef<T> | undefined): ref is React.MutableRefObject<T | undefined> {
|
|
11
|
-
return typeIs(ref, "table") && "current" in ref;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function setRef<T>(ref: AnyRef<T> | undefined, value: T | undefined) {
|
|
15
|
-
if (isRefCallback(ref)) {
|
|
16
|
-
ref(value);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (isMutableRefObject(ref)) {
|
|
20
|
-
ref.current = value;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function composeRefs<T>(...refs: Array<AnyRef<T> | undefined>) {
|
|
25
|
-
return (node: T | undefined) => {
|
|
26
|
-
for (const ref of refs) if (ref) setRef(ref, node);
|
|
27
|
-
};
|
|
28
|
-
}
|
package/src/slot.tsx
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import React from "@rbxts/react";
|
|
2
|
-
import { composeRefs } from "./refs";
|
|
3
|
-
|
|
4
|
-
type Fn = (...args: unknown[]) => void;
|
|
5
|
-
type HandlerTable = Partial<Record<string, Fn>>;
|
|
6
|
-
type SlotRef = React.ForwardedRef<Instance>;
|
|
7
|
-
type SlotPropBag = React.Attributes & Record<string, unknown>;
|
|
8
|
-
type InstanceRefCallback = (instance: Instance | undefined) => void;
|
|
9
|
-
|
|
10
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
11
|
-
return typeIs(value, "table");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function toSlotPropBag(value: unknown): SlotPropBag {
|
|
15
|
-
return isRecord(value) ? (value as SlotPropBag) : {};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function isFn(value: unknown): value is Fn {
|
|
19
|
-
return typeIs(value, "function");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function toHandlerTable(value: unknown): HandlerTable | undefined {
|
|
23
|
-
if (!isRecord(value)) {
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const out: HandlerTable = {};
|
|
28
|
-
for (const [rawKey, candidate] of pairs(value)) {
|
|
29
|
-
if (!typeIs(rawKey, "string")) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (isFn(candidate)) {
|
|
34
|
-
out[rawKey] = candidate;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return next(out)[0] !== undefined ? out : undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function toForwardedRef(value: unknown): SlotRef | undefined {
|
|
42
|
-
if (value === undefined) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (isInstanceRefCallback(value)) {
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (isInstanceMutableRefObject(value)) {
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isInstanceRefCallback(value: unknown): value is InstanceRefCallback {
|
|
58
|
-
return typeIs(value, "function");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function isInstanceMutableRefObject(value: unknown): value is React.MutableRefObject<Instance | undefined> {
|
|
62
|
-
return typeIs(value, "table") && "current" in value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function mergeHandlerTable(a?: HandlerTable, b?: HandlerTable) {
|
|
66
|
-
if (!a) return b;
|
|
67
|
-
if (!b) return a;
|
|
68
|
-
const out: HandlerTable = { ...a };
|
|
69
|
-
for (const [rawKey, candidate] of pairs(b)) {
|
|
70
|
-
if (!typeIs(rawKey, "string") || !isFn(candidate)) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const af = a[rawKey];
|
|
75
|
-
const bf = candidate;
|
|
76
|
-
out[rawKey] =
|
|
77
|
-
af && bf
|
|
78
|
-
? (...args) => {
|
|
79
|
-
bf(...args);
|
|
80
|
-
af(...args);
|
|
81
|
-
}
|
|
82
|
-
: (bf ?? af)!;
|
|
83
|
-
}
|
|
84
|
-
return out;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export type SlotProps = {
|
|
88
|
-
children: React.ReactElement<SlotPropBag>;
|
|
89
|
-
ref?: SlotRef;
|
|
90
|
-
} & SlotPropBag;
|
|
91
|
-
|
|
92
|
-
export const Slot = React.forwardRef<Instance, SlotProps>((props, forwardedRef) => {
|
|
93
|
-
const child = props.children;
|
|
94
|
-
const childProps = toSlotPropBag((child as { props?: unknown }).props);
|
|
95
|
-
|
|
96
|
-
const mergedProps: SlotPropBag = { ...props, ...childProps };
|
|
97
|
-
mergedProps.children = childProps.children;
|
|
98
|
-
|
|
99
|
-
const slotEvent = toHandlerTable(props.Event);
|
|
100
|
-
const childEvent = toHandlerTable(childProps.Event);
|
|
101
|
-
const slotChange = toHandlerTable(props.Change);
|
|
102
|
-
const childChange = toHandlerTable(childProps.Change);
|
|
103
|
-
|
|
104
|
-
const Event = mergeHandlerTable(slotEvent, childEvent);
|
|
105
|
-
const Change = mergeHandlerTable(slotChange, childChange);
|
|
106
|
-
if (Event) mergedProps.Event = Event;
|
|
107
|
-
if (Change) mergedProps.Change = Change;
|
|
108
|
-
|
|
109
|
-
const slotRef = toForwardedRef(props.ref);
|
|
110
|
-
const childRef = toForwardedRef(childProps.ref);
|
|
111
|
-
const mergedRef = composeRefs(childRef, forwardedRef, slotRef);
|
|
112
|
-
mergedProps.ref = mergedRef;
|
|
113
|
-
|
|
114
|
-
return React.cloneElement(child, mergedProps);
|
|
115
|
-
});
|
|
116
|
-
Slot.displayName = "Slot";
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import React from "@rbxts/react";
|
|
2
|
-
|
|
3
|
-
type Props<T> = {
|
|
4
|
-
value?: T;
|
|
5
|
-
defaultValue: T;
|
|
6
|
-
onChange?: (next: T) => void;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
function isUpdater<T>(value: T | ((prev: T) => T)): value is (prev: T) => T {
|
|
10
|
-
return typeIs(value, "function");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function useControllableState<T>({ value, defaultValue, onChange }: Props<T>) {
|
|
14
|
-
const [inner, setInner] = React.useState(defaultValue);
|
|
15
|
-
const controlled = value !== undefined;
|
|
16
|
-
const state = value !== undefined ? value : inner;
|
|
17
|
-
|
|
18
|
-
const setState = React.useCallback(
|
|
19
|
-
(nextValue: T | ((prev: T) => T)) => {
|
|
20
|
-
const computed = isUpdater(nextValue) ? nextValue(state) : nextValue;
|
|
21
|
-
if (!controlled) setInner(computed);
|
|
22
|
-
onChange?.(computed);
|
|
23
|
-
},
|
|
24
|
-
[controlled, onChange, state],
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
return [state, setState] as const;
|
|
28
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"outDir": "out",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"typeRoots": ["./node_modules/@rbxts", "../../node_modules/@rbxts"],
|
|
8
|
-
"types": ["types", "compiler-types"]
|
|
9
|
-
},
|
|
10
|
-
"include": ["src"]
|
|
11
|
-
}
|
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
|
-
}
|