@lattice-ui/core 0.1.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.
@@ -0,0 +1,2 @@
1
+ import React from "@rbxts/react";
2
+ export declare function createStrictContext<T>(name: string): readonly [React.Provider<T | undefined>, () => T];
@@ -0,0 +1,17 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local React = TS.import(script, TS.getModule(script, "@rbxts", "react"))
4
+ local function createStrictContext(name)
5
+ local Ctx = React.createContext(nil)
6
+ local function useCtx()
7
+ local v = React.useContext(Ctx)
8
+ if v == nil then
9
+ error(`[{name}] context is undefined. Wrap components with <{name}.Provider>.`)
10
+ end
11
+ return v
12
+ end
13
+ return { Ctx.Provider, useCtx }
14
+ end
15
+ return {
16
+ createStrictContext = createStrictContext,
17
+ }
package/out/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./context";
2
+ export { default as React } from "./react";
3
+ export { default as ReactRoblox } from "./reactRoblox";
4
+ export * from "./refs";
5
+ export * from "./slot";
6
+ export * from "./useControllableState";
package/out/init.luau ADDED
@@ -0,0 +1,18 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local exports = {}
4
+ for _k, _v in TS.import(script, script, "context") or {} do
5
+ exports[_k] = _v
6
+ end
7
+ exports.React = TS.import(script, script, "react").default
8
+ exports.ReactRoblox = TS.import(script, script, "reactRoblox").default
9
+ for _k, _v in TS.import(script, script, "refs") or {} do
10
+ exports[_k] = _v
11
+ end
12
+ for _k, _v in TS.import(script, script, "slot") or {} do
13
+ exports[_k] = _v
14
+ end
15
+ for _k, _v in TS.import(script, script, "useControllableState") or {} do
16
+ exports[_k] = _v
17
+ end
18
+ return exports
package/out/react.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import React = require("@rbxts/react");
2
+ export default React;
package/out/react.luau ADDED
@@ -0,0 +1,7 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local React = TS.import(script, TS.getModule(script, "@rbxts", "react"))
4
+ local default = React
5
+ return {
6
+ default = default,
7
+ }
@@ -0,0 +1,2 @@
1
+ import ReactRoblox = require("@rbxts/react-roblox");
2
+ export default ReactRoblox;
@@ -0,0 +1,7 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local ReactRoblox = TS.import(script, TS.getModule(script, "@rbxts", "react-roblox"))
4
+ local default = ReactRoblox
5
+ return {
6
+ default = default,
7
+ }
package/out/refs.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type React from "@rbxts/react";
2
+ type AnyRef<T> = React.Ref<T> | React.ForwardedRef<T>;
3
+ export declare function setRef<T>(ref: AnyRef<T> | undefined, value: T | undefined): void;
4
+ export declare function composeRefs<T>(...refs: Array<AnyRef<T> | undefined>): (node: T | undefined) => void;
5
+ export {};
package/out/refs.luau ADDED
@@ -0,0 +1,36 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local function isRefCallback(ref)
3
+ local _ref = ref
4
+ return type(_ref) == "function"
5
+ end
6
+ local function isMutableRefObject(ref)
7
+ local _ref = ref
8
+ local _condition = type(_ref) == "table"
9
+ if _condition then
10
+ _condition = ref.current ~= nil
11
+ end
12
+ return _condition
13
+ end
14
+ local function setRef(ref, value)
15
+ if isRefCallback(ref) then
16
+ ref(value)
17
+ return nil
18
+ end
19
+ if isMutableRefObject(ref) then
20
+ ref.current = value
21
+ end
22
+ end
23
+ local function composeRefs(...)
24
+ local refs = { ... }
25
+ return function(node)
26
+ for _, ref in refs do
27
+ if ref then
28
+ setRef(ref, node)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ return {
34
+ setRef = setRef,
35
+ composeRefs = composeRefs,
36
+ }
package/out/slot.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import React from "@rbxts/react";
2
+ type SlotRef = React.ForwardedRef<Instance>;
3
+ type SlotPropBag = React.Attributes & Record<string, unknown>;
4
+ export type SlotProps = {
5
+ children: React.ReactElement<SlotPropBag>;
6
+ ref?: SlotRef;
7
+ } & SlotPropBag;
8
+ export declare const Slot: React.ForwardRefExoticComponent<Omit<SlotProps, "ref"> & React.RefAttributes<Instance>>;
9
+ export {};
package/out/slot.luau ADDED
@@ -0,0 +1,111 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local React = TS.import(script, TS.getModule(script, "@rbxts", "react"))
4
+ local composeRefs = TS.import(script, script.Parent, "refs").composeRefs
5
+ local function isRecord(value)
6
+ local _value = value
7
+ return type(_value) == "table"
8
+ end
9
+ local function toSlotPropBag(value)
10
+ return if isRecord(value) then value else {}
11
+ end
12
+ local function isFn(value)
13
+ local _value = value
14
+ return type(_value) == "function"
15
+ end
16
+ local function toHandlerTable(value)
17
+ if not isRecord(value) then
18
+ return nil
19
+ end
20
+ local out = {}
21
+ for rawKey, candidate in pairs(value) do
22
+ if not (type(rawKey) == "string") then
23
+ continue
24
+ end
25
+ if isFn(candidate) then
26
+ out[rawKey] = candidate
27
+ end
28
+ end
29
+ return if (next(out)) ~= nil then out else nil
30
+ end
31
+ local isInstanceRefCallback, isInstanceMutableRefObject
32
+ local function toForwardedRef(value)
33
+ if value == nil then
34
+ return nil
35
+ end
36
+ if isInstanceRefCallback(value) then
37
+ return value
38
+ end
39
+ if isInstanceMutableRefObject(value) then
40
+ return value
41
+ end
42
+ return nil
43
+ end
44
+ function isInstanceRefCallback(value)
45
+ local _value = value
46
+ return type(_value) == "function"
47
+ end
48
+ function isInstanceMutableRefObject(value)
49
+ local _value = value
50
+ local _condition = type(_value) == "table"
51
+ if _condition then
52
+ _condition = value.current ~= nil
53
+ end
54
+ return _condition
55
+ end
56
+ local function mergeHandlerTable(a, b)
57
+ if not a then
58
+ return b
59
+ end
60
+ if not b then
61
+ return a
62
+ end
63
+ local _object = table.clone(a)
64
+ setmetatable(_object, nil)
65
+ local out = _object
66
+ for rawKey, candidate in pairs(b) do
67
+ if not (type(rawKey) == "string") or not isFn(candidate) then
68
+ continue
69
+ end
70
+ local af = a[rawKey]
71
+ local bf = candidate
72
+ out[rawKey] = if af and bf then function(...)
73
+ local args = { ... }
74
+ bf(unpack(args))
75
+ af(unpack(args))
76
+ end else (bf or af)
77
+ end
78
+ return out
79
+ end
80
+ local Slot = React.forwardRef(function(props, forwardedRef)
81
+ local child = props.children
82
+ local childProps = toSlotPropBag(child.props)
83
+ local _object = table.clone(props)
84
+ setmetatable(_object, nil)
85
+ for _k, _v in childProps do
86
+ _object[_k] = _v
87
+ end
88
+ local mergedProps = _object
89
+ mergedProps.children = childProps.children
90
+ local slotEvent = toHandlerTable(props.Event)
91
+ local childEvent = toHandlerTable(childProps.Event)
92
+ local slotChange = toHandlerTable(props.Change)
93
+ local childChange = toHandlerTable(childProps.Change)
94
+ local Event = mergeHandlerTable(slotEvent, childEvent)
95
+ local Change = mergeHandlerTable(slotChange, childChange)
96
+ if Event then
97
+ mergedProps.Event = Event
98
+ end
99
+ if Change then
100
+ mergedProps.Change = Change
101
+ end
102
+ local slotRef = toForwardedRef(props.ref)
103
+ local childRef = toForwardedRef(childProps.ref)
104
+ local mergedRef = composeRefs(childRef, forwardedRef, slotRef)
105
+ mergedProps.ref = mergedRef
106
+ return React.cloneElement(child, mergedProps)
107
+ end)
108
+ Slot.displayName = "Slot"
109
+ return {
110
+ Slot = Slot,
111
+ }
@@ -0,0 +1,7 @@
1
+ type Props<T> = {
2
+ value?: T;
3
+ defaultValue: T;
4
+ onChange?: (next: T) => void;
5
+ };
6
+ export declare function useControllableState<T>({ value, defaultValue, onChange }: Props<T>): readonly [T, (nextValue: T | ((prev: T) => T)) => void];
7
+ export {};
@@ -0,0 +1,29 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local React = TS.import(script, TS.getModule(script, "@rbxts", "react"))
4
+ local function isUpdater(value)
5
+ local _value = value
6
+ return type(_value) == "function"
7
+ end
8
+ local function useControllableState(_param)
9
+ local value = _param.value
10
+ local defaultValue = _param.defaultValue
11
+ local onChange = _param.onChange
12
+ local inner, setInner = React.useState(defaultValue)
13
+ local controlled = value ~= nil
14
+ local state = if value ~= nil then value else inner
15
+ local setState = React.useCallback(function(nextValue)
16
+ local computed = if isUpdater(nextValue) then nextValue(state) else nextValue
17
+ if not controlled then
18
+ setInner(computed)
19
+ end
20
+ local _result = onChange
21
+ if _result ~= nil then
22
+ _result(computed)
23
+ end
24
+ end, { controlled, onChange, state })
25
+ return { state, setState }
26
+ end
27
+ return {
28
+ useControllableState = useControllableState,
29
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@lattice-ui/core",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "main": "out/init.luau",
6
+ "types": "out/index.d.ts",
7
+ "devDependencies": {
8
+ "@rbxts/react": "17.3.7-ts.1",
9
+ "@rbxts/react-roblox": "17.3.7-ts.1"
10
+ },
11
+ "peerDependencies": {
12
+ "@rbxts/react": "^17",
13
+ "@rbxts/react-roblox": "^17"
14
+ },
15
+ "scripts": {
16
+ "prebuild": "node ./scripts/ensure-hoisted-links.mjs",
17
+ "build": "rbxtsc -p tsconfig.json",
18
+ "prewatch": "node ./scripts/ensure-hoisted-links.mjs",
19
+ "watch": "rbxtsc -p tsconfig.json -w",
20
+ "typecheck": "tsc -p tsconfig.typecheck.json"
21
+ }
22
+ }
@@ -0,0 +1,35 @@
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 ADDED
@@ -0,0 +1,15 @@
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 ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./context";
2
+ export { default as React } from "./react";
3
+ export { default as ReactRoblox } from "./reactRoblox";
4
+ export * from "./refs";
5
+ export * from "./slot";
6
+ export * from "./useControllableState";
package/src/react.ts ADDED
@@ -0,0 +1,3 @@
1
+ import React = require("@rbxts/react");
2
+
3
+ export default React;
@@ -0,0 +1,3 @@
1
+ import ReactRoblox = require("@rbxts/react-roblox");
2
+
3
+ export default ReactRoblox;
package/src/refs.ts ADDED
@@ -0,0 +1,28 @@
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 ADDED
@@ -0,0 +1,116 @@
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";
@@ -0,0 +1,28 @@
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 ADDED
@@ -0,0 +1,11 @@
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
+ }
@@ -0,0 +1,25 @@
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
+ }