@nonsoanetoh/lil-gui-helper 0.1.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/dist/index.d.mts +131 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +299 -0
- package/dist/index.mjs +251 -0
- package/package.json +55 -0
- package/readme.md +156 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
type GuiRoute = "*" | `/${string}`;
|
|
4
|
+
type GuiItemBase = {
|
|
5
|
+
id?: string;
|
|
6
|
+
name: string;
|
|
7
|
+
routes?: GuiRoute[];
|
|
8
|
+
};
|
|
9
|
+
type GuiItemNumber = GuiItemBase & {
|
|
10
|
+
type: "number";
|
|
11
|
+
value: number;
|
|
12
|
+
min?: number;
|
|
13
|
+
max?: number;
|
|
14
|
+
step?: number;
|
|
15
|
+
onChange: (value: number) => void;
|
|
16
|
+
};
|
|
17
|
+
type GuiItemColor = GuiItemBase & {
|
|
18
|
+
type: "color";
|
|
19
|
+
value: string;
|
|
20
|
+
onChange: (value: string) => void;
|
|
21
|
+
};
|
|
22
|
+
type GuiItemBoolean = GuiItemBase & {
|
|
23
|
+
type: "boolean";
|
|
24
|
+
value: boolean;
|
|
25
|
+
onChange: (value: boolean) => void;
|
|
26
|
+
};
|
|
27
|
+
type GuiItemString = GuiItemBase & {
|
|
28
|
+
type: "string";
|
|
29
|
+
value: string;
|
|
30
|
+
onChange: (value: string) => void;
|
|
31
|
+
};
|
|
32
|
+
type GuiItemDropdown = GuiItemBase & {
|
|
33
|
+
type: "dropdown";
|
|
34
|
+
value: string;
|
|
35
|
+
options: string[] | Record<string, string>;
|
|
36
|
+
onChange: (value: string) => void;
|
|
37
|
+
};
|
|
38
|
+
type GuiItemFolder = GuiItemBase & {
|
|
39
|
+
type: "folder";
|
|
40
|
+
children: GuiItem[];
|
|
41
|
+
};
|
|
42
|
+
type GuiItem = GuiItemNumber | GuiItemColor | GuiItemBoolean | GuiItemString | GuiItemDropdown | GuiItemFolder;
|
|
43
|
+
type SettingDefinitionValue = string | number | boolean;
|
|
44
|
+
type SettingDefinitionBase = {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
default: SettingDefinitionValue;
|
|
48
|
+
routes?: GuiRoute[];
|
|
49
|
+
};
|
|
50
|
+
type SettingDefinition = (SettingDefinitionBase & {
|
|
51
|
+
type: "color";
|
|
52
|
+
}) | (SettingDefinitionBase & {
|
|
53
|
+
type: "number";
|
|
54
|
+
min?: number;
|
|
55
|
+
max?: number;
|
|
56
|
+
step?: number;
|
|
57
|
+
}) | (SettingDefinitionBase & {
|
|
58
|
+
type: "boolean";
|
|
59
|
+
}) | (SettingDefinitionBase & {
|
|
60
|
+
type: "string";
|
|
61
|
+
}) | (SettingDefinitionBase & {
|
|
62
|
+
type: "dropdown";
|
|
63
|
+
options: string[] | Record<string, string>;
|
|
64
|
+
});
|
|
65
|
+
type SettingDefinitionFolder = {
|
|
66
|
+
type: "folder";
|
|
67
|
+
name: string;
|
|
68
|
+
routes?: GuiRoute[];
|
|
69
|
+
children: SettingDefinition[];
|
|
70
|
+
};
|
|
71
|
+
type SettingDefinitionItem = SettingDefinition | SettingDefinitionFolder;
|
|
72
|
+
type SettingPrimitive = number | string | boolean;
|
|
73
|
+
type SettingValue = SettingPrimitive | SettingsGroup;
|
|
74
|
+
interface SettingsGroup {
|
|
75
|
+
[key: string]: SettingValue;
|
|
76
|
+
}
|
|
77
|
+
type Settings = Record<string, SettingsGroup>;
|
|
78
|
+
type SettingsContextValue = {
|
|
79
|
+
settings: Settings;
|
|
80
|
+
updateSetting: (path: string, value: SettingValue) => void;
|
|
81
|
+
getSetting: <T extends SettingValue = SettingValue>(path: string) => T | undefined;
|
|
82
|
+
getGroup: (name: string) => SettingsGroup;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type SettingsProviderProps = {
|
|
86
|
+
children: React.ReactNode;
|
|
87
|
+
initialSettings?: Partial<Settings>;
|
|
88
|
+
};
|
|
89
|
+
declare function SettingsProvider({ children, initialSettings }: SettingsProviderProps): React.FunctionComponentElement<React.ProviderProps<SettingsContextValue | null>>;
|
|
90
|
+
declare function useSettings(): SettingsContextValue;
|
|
91
|
+
|
|
92
|
+
declare function matchRoute(pathname: string, route: GuiRoute): boolean;
|
|
93
|
+
declare function getDefinitionsForRoute(definitions: SettingDefinitionItem[], pathname: string): SettingDefinitionItem[];
|
|
94
|
+
declare function getDefinition(definitions: SettingDefinitionItem[], id: string): SettingDefinition | undefined;
|
|
95
|
+
|
|
96
|
+
type GuiConfigProviderProps = {
|
|
97
|
+
definitions: SettingDefinitionItem[];
|
|
98
|
+
children: React.ReactNode;
|
|
99
|
+
};
|
|
100
|
+
declare function GuiConfigProvider({ definitions, children }: GuiConfigProviderProps): React.FunctionComponentElement<React.ProviderProps<SettingDefinitionItem[]>>;
|
|
101
|
+
declare function useGuiDefinitions(): SettingDefinitionItem[];
|
|
102
|
+
declare function useGetDefinition(): (id: string) => ReturnType<typeof getDefinition>;
|
|
103
|
+
|
|
104
|
+
type GuiProviderProps = {
|
|
105
|
+
children: React.ReactNode;
|
|
106
|
+
visible?: boolean;
|
|
107
|
+
};
|
|
108
|
+
declare function GuiProvider({ children, visible, }: GuiProviderProps): React.FunctionComponentElement<React.ProviderProps<boolean>>;
|
|
109
|
+
declare function useGuiVisible(): boolean;
|
|
110
|
+
|
|
111
|
+
declare function GuiManager(): React.DetailedReactHTMLElement<{
|
|
112
|
+
ref: React.RefObject<HTMLDivElement | null>;
|
|
113
|
+
style: {
|
|
114
|
+
position: "fixed";
|
|
115
|
+
top: number;
|
|
116
|
+
right: number;
|
|
117
|
+
zIndex: number;
|
|
118
|
+
};
|
|
119
|
+
}, HTMLDivElement> | null;
|
|
120
|
+
|
|
121
|
+
type UseSettingOptions<T extends SettingDefinitionValue = SettingDefinitionValue> = {
|
|
122
|
+
default?: T;
|
|
123
|
+
onChange?: (value: T) => void;
|
|
124
|
+
};
|
|
125
|
+
declare function useSetting<T extends SettingDefinitionValue = SettingDefinitionValue>(path: string, defaultOrOptions?: T | UseSettingOptions<T>, onChange?: (value: T) => void): T | undefined;
|
|
126
|
+
|
|
127
|
+
type GetSetting = (path: string) => unknown;
|
|
128
|
+
type UpdateSetting = (path: string, value: SettingDefinitionValue) => void;
|
|
129
|
+
declare function buildGuiItemsFromDefinitions(definitions: SettingDefinitionItem[], pathname: string, getSetting: GetSetting, updateSetting: UpdateSetting): GuiItem[];
|
|
130
|
+
|
|
131
|
+
export { GuiConfigProvider, type GuiConfigProviderProps, type GuiItem, GuiManager, GuiProvider, type GuiProviderProps, type GuiRoute, type SettingDefinition, type SettingDefinitionBase, type SettingDefinitionFolder, type SettingDefinitionItem, type SettingDefinitionValue, type SettingValue, type Settings, type SettingsContextValue, SettingsProvider, type SettingsProviderProps, type UseSettingOptions, buildGuiItemsFromDefinitions, getDefinition, getDefinitionsForRoute, matchRoute, useGetDefinition, useGuiDefinitions, useGuiVisible, useSetting, useSettings };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
type GuiRoute = "*" | `/${string}`;
|
|
4
|
+
type GuiItemBase = {
|
|
5
|
+
id?: string;
|
|
6
|
+
name: string;
|
|
7
|
+
routes?: GuiRoute[];
|
|
8
|
+
};
|
|
9
|
+
type GuiItemNumber = GuiItemBase & {
|
|
10
|
+
type: "number";
|
|
11
|
+
value: number;
|
|
12
|
+
min?: number;
|
|
13
|
+
max?: number;
|
|
14
|
+
step?: number;
|
|
15
|
+
onChange: (value: number) => void;
|
|
16
|
+
};
|
|
17
|
+
type GuiItemColor = GuiItemBase & {
|
|
18
|
+
type: "color";
|
|
19
|
+
value: string;
|
|
20
|
+
onChange: (value: string) => void;
|
|
21
|
+
};
|
|
22
|
+
type GuiItemBoolean = GuiItemBase & {
|
|
23
|
+
type: "boolean";
|
|
24
|
+
value: boolean;
|
|
25
|
+
onChange: (value: boolean) => void;
|
|
26
|
+
};
|
|
27
|
+
type GuiItemString = GuiItemBase & {
|
|
28
|
+
type: "string";
|
|
29
|
+
value: string;
|
|
30
|
+
onChange: (value: string) => void;
|
|
31
|
+
};
|
|
32
|
+
type GuiItemDropdown = GuiItemBase & {
|
|
33
|
+
type: "dropdown";
|
|
34
|
+
value: string;
|
|
35
|
+
options: string[] | Record<string, string>;
|
|
36
|
+
onChange: (value: string) => void;
|
|
37
|
+
};
|
|
38
|
+
type GuiItemFolder = GuiItemBase & {
|
|
39
|
+
type: "folder";
|
|
40
|
+
children: GuiItem[];
|
|
41
|
+
};
|
|
42
|
+
type GuiItem = GuiItemNumber | GuiItemColor | GuiItemBoolean | GuiItemString | GuiItemDropdown | GuiItemFolder;
|
|
43
|
+
type SettingDefinitionValue = string | number | boolean;
|
|
44
|
+
type SettingDefinitionBase = {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
default: SettingDefinitionValue;
|
|
48
|
+
routes?: GuiRoute[];
|
|
49
|
+
};
|
|
50
|
+
type SettingDefinition = (SettingDefinitionBase & {
|
|
51
|
+
type: "color";
|
|
52
|
+
}) | (SettingDefinitionBase & {
|
|
53
|
+
type: "number";
|
|
54
|
+
min?: number;
|
|
55
|
+
max?: number;
|
|
56
|
+
step?: number;
|
|
57
|
+
}) | (SettingDefinitionBase & {
|
|
58
|
+
type: "boolean";
|
|
59
|
+
}) | (SettingDefinitionBase & {
|
|
60
|
+
type: "string";
|
|
61
|
+
}) | (SettingDefinitionBase & {
|
|
62
|
+
type: "dropdown";
|
|
63
|
+
options: string[] | Record<string, string>;
|
|
64
|
+
});
|
|
65
|
+
type SettingDefinitionFolder = {
|
|
66
|
+
type: "folder";
|
|
67
|
+
name: string;
|
|
68
|
+
routes?: GuiRoute[];
|
|
69
|
+
children: SettingDefinition[];
|
|
70
|
+
};
|
|
71
|
+
type SettingDefinitionItem = SettingDefinition | SettingDefinitionFolder;
|
|
72
|
+
type SettingPrimitive = number | string | boolean;
|
|
73
|
+
type SettingValue = SettingPrimitive | SettingsGroup;
|
|
74
|
+
interface SettingsGroup {
|
|
75
|
+
[key: string]: SettingValue;
|
|
76
|
+
}
|
|
77
|
+
type Settings = Record<string, SettingsGroup>;
|
|
78
|
+
type SettingsContextValue = {
|
|
79
|
+
settings: Settings;
|
|
80
|
+
updateSetting: (path: string, value: SettingValue) => void;
|
|
81
|
+
getSetting: <T extends SettingValue = SettingValue>(path: string) => T | undefined;
|
|
82
|
+
getGroup: (name: string) => SettingsGroup;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type SettingsProviderProps = {
|
|
86
|
+
children: React.ReactNode;
|
|
87
|
+
initialSettings?: Partial<Settings>;
|
|
88
|
+
};
|
|
89
|
+
declare function SettingsProvider({ children, initialSettings }: SettingsProviderProps): React.FunctionComponentElement<React.ProviderProps<SettingsContextValue | null>>;
|
|
90
|
+
declare function useSettings(): SettingsContextValue;
|
|
91
|
+
|
|
92
|
+
declare function matchRoute(pathname: string, route: GuiRoute): boolean;
|
|
93
|
+
declare function getDefinitionsForRoute(definitions: SettingDefinitionItem[], pathname: string): SettingDefinitionItem[];
|
|
94
|
+
declare function getDefinition(definitions: SettingDefinitionItem[], id: string): SettingDefinition | undefined;
|
|
95
|
+
|
|
96
|
+
type GuiConfigProviderProps = {
|
|
97
|
+
definitions: SettingDefinitionItem[];
|
|
98
|
+
children: React.ReactNode;
|
|
99
|
+
};
|
|
100
|
+
declare function GuiConfigProvider({ definitions, children }: GuiConfigProviderProps): React.FunctionComponentElement<React.ProviderProps<SettingDefinitionItem[]>>;
|
|
101
|
+
declare function useGuiDefinitions(): SettingDefinitionItem[];
|
|
102
|
+
declare function useGetDefinition(): (id: string) => ReturnType<typeof getDefinition>;
|
|
103
|
+
|
|
104
|
+
type GuiProviderProps = {
|
|
105
|
+
children: React.ReactNode;
|
|
106
|
+
visible?: boolean;
|
|
107
|
+
};
|
|
108
|
+
declare function GuiProvider({ children, visible, }: GuiProviderProps): React.FunctionComponentElement<React.ProviderProps<boolean>>;
|
|
109
|
+
declare function useGuiVisible(): boolean;
|
|
110
|
+
|
|
111
|
+
declare function GuiManager(): React.DetailedReactHTMLElement<{
|
|
112
|
+
ref: React.RefObject<HTMLDivElement | null>;
|
|
113
|
+
style: {
|
|
114
|
+
position: "fixed";
|
|
115
|
+
top: number;
|
|
116
|
+
right: number;
|
|
117
|
+
zIndex: number;
|
|
118
|
+
};
|
|
119
|
+
}, HTMLDivElement> | null;
|
|
120
|
+
|
|
121
|
+
type UseSettingOptions<T extends SettingDefinitionValue = SettingDefinitionValue> = {
|
|
122
|
+
default?: T;
|
|
123
|
+
onChange?: (value: T) => void;
|
|
124
|
+
};
|
|
125
|
+
declare function useSetting<T extends SettingDefinitionValue = SettingDefinitionValue>(path: string, defaultOrOptions?: T | UseSettingOptions<T>, onChange?: (value: T) => void): T | undefined;
|
|
126
|
+
|
|
127
|
+
type GetSetting = (path: string) => unknown;
|
|
128
|
+
type UpdateSetting = (path: string, value: SettingDefinitionValue) => void;
|
|
129
|
+
declare function buildGuiItemsFromDefinitions(definitions: SettingDefinitionItem[], pathname: string, getSetting: GetSetting, updateSetting: UpdateSetting): GuiItem[];
|
|
130
|
+
|
|
131
|
+
export { GuiConfigProvider, type GuiConfigProviderProps, type GuiItem, GuiManager, GuiProvider, type GuiProviderProps, type GuiRoute, type SettingDefinition, type SettingDefinitionBase, type SettingDefinitionFolder, type SettingDefinitionItem, type SettingDefinitionValue, type SettingValue, type Settings, type SettingsContextValue, SettingsProvider, type SettingsProviderProps, type UseSettingOptions, buildGuiItemsFromDefinitions, getDefinition, getDefinitionsForRoute, matchRoute, useGetDefinition, useGuiDefinitions, useGuiVisible, useSetting, useSettings };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.tsx
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
GuiConfigProvider: () => GuiConfigProvider,
|
|
35
|
+
GuiManager: () => GuiManager,
|
|
36
|
+
GuiProvider: () => GuiProvider,
|
|
37
|
+
SettingsProvider: () => SettingsProvider,
|
|
38
|
+
buildGuiItemsFromDefinitions: () => buildGuiItemsFromDefinitions,
|
|
39
|
+
getDefinition: () => getDefinition,
|
|
40
|
+
getDefinitionsForRoute: () => getDefinitionsForRoute,
|
|
41
|
+
matchRoute: () => matchRoute,
|
|
42
|
+
useGetDefinition: () => useGetDefinition,
|
|
43
|
+
useGuiDefinitions: () => useGuiDefinitions,
|
|
44
|
+
useGuiVisible: () => useGuiVisible,
|
|
45
|
+
useSetting: () => useSetting,
|
|
46
|
+
useSettings: () => useSettings
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/settings-context.tsx
|
|
51
|
+
var React = __toESM(require("react"));
|
|
52
|
+
function getByPath(obj, path) {
|
|
53
|
+
const parts = path.split(".").filter(Boolean);
|
|
54
|
+
let current = obj;
|
|
55
|
+
for (const key of parts) {
|
|
56
|
+
if (current == null || typeof current !== "object" || Array.isArray(current)) return void 0;
|
|
57
|
+
current = current[key];
|
|
58
|
+
}
|
|
59
|
+
return current;
|
|
60
|
+
}
|
|
61
|
+
function setByPath(obj, path, value) {
|
|
62
|
+
const parts = path.split(".").filter(Boolean);
|
|
63
|
+
if (parts.length === 0) return obj;
|
|
64
|
+
const result = { ...obj };
|
|
65
|
+
let current = result;
|
|
66
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
67
|
+
const key = parts[i];
|
|
68
|
+
const next = current[key];
|
|
69
|
+
const nextObj = next != null && typeof next === "object" && !Array.isArray(next) ? { ...next } : {};
|
|
70
|
+
current[key] = nextObj;
|
|
71
|
+
current = nextObj;
|
|
72
|
+
}
|
|
73
|
+
current[parts[parts.length - 1]] = value;
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
var SettingsContext = React.createContext(null);
|
|
77
|
+
function SettingsProvider({ children, initialSettings = {} }) {
|
|
78
|
+
const [settings, setSettings] = React.useState(() => {
|
|
79
|
+
const result = {};
|
|
80
|
+
for (const key in initialSettings) {
|
|
81
|
+
const group = initialSettings[key];
|
|
82
|
+
if (group !== void 0) result[key] = group;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
});
|
|
86
|
+
const updateSetting = React.useCallback((path, value2) => {
|
|
87
|
+
setSettings((prev) => setByPath(prev, path, value2));
|
|
88
|
+
}, []);
|
|
89
|
+
const getSetting = React.useCallback(
|
|
90
|
+
(path) => getByPath(settings, path),
|
|
91
|
+
[settings]
|
|
92
|
+
);
|
|
93
|
+
const getGroup = React.useCallback((name) => settings[name] ?? {}, [settings]);
|
|
94
|
+
const value = React.useMemo(
|
|
95
|
+
() => ({ settings, updateSetting, getSetting, getGroup }),
|
|
96
|
+
[settings, updateSetting, getSetting, getGroup]
|
|
97
|
+
);
|
|
98
|
+
return React.createElement(SettingsContext.Provider, { value }, children);
|
|
99
|
+
}
|
|
100
|
+
function useSettings() {
|
|
101
|
+
const ctx = React.useContext(SettingsContext);
|
|
102
|
+
if (!ctx) throw new Error("useSettings must be used within SettingsProvider");
|
|
103
|
+
return ctx;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/config-context.tsx
|
|
107
|
+
var React2 = __toESM(require("react"));
|
|
108
|
+
|
|
109
|
+
// src/routes.ts
|
|
110
|
+
function matchRoute(pathname, route) {
|
|
111
|
+
if (route === "*") return true;
|
|
112
|
+
if (route.endsWith("/*")) {
|
|
113
|
+
const prefix = route.slice(0, -2);
|
|
114
|
+
return pathname.startsWith(prefix + "/");
|
|
115
|
+
}
|
|
116
|
+
return pathname === route;
|
|
117
|
+
}
|
|
118
|
+
function getDefinitionsForRoute(definitions, pathname) {
|
|
119
|
+
return definitions.filter((def) => {
|
|
120
|
+
const routes = def.routes;
|
|
121
|
+
if (!routes?.length) return true;
|
|
122
|
+
return routes.some((r) => matchRoute(pathname, r));
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function flatten(items, acc = /* @__PURE__ */ new Map()) {
|
|
126
|
+
for (const d of items) {
|
|
127
|
+
if (d.type === "folder") flatten(d.children, acc);
|
|
128
|
+
else if (d.id != null) acc.set(d.id, d);
|
|
129
|
+
}
|
|
130
|
+
return acc;
|
|
131
|
+
}
|
|
132
|
+
function getDefinition(definitions, id) {
|
|
133
|
+
return flatten(definitions).get(id);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/config-context.tsx
|
|
137
|
+
var DefinitionsContext = React2.createContext([]);
|
|
138
|
+
function GuiConfigProvider({ definitions, children }) {
|
|
139
|
+
const value = React2.useMemo(() => definitions, [definitions]);
|
|
140
|
+
return React2.createElement(DefinitionsContext.Provider, { value }, children);
|
|
141
|
+
}
|
|
142
|
+
function useGuiDefinitions() {
|
|
143
|
+
return React2.useContext(DefinitionsContext);
|
|
144
|
+
}
|
|
145
|
+
function useGetDefinition() {
|
|
146
|
+
const definitions = useGuiDefinitions();
|
|
147
|
+
return React2.useCallback((id) => getDefinition(definitions, id), [definitions]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/gui-provider.tsx
|
|
151
|
+
var React3 = __toESM(require("react"));
|
|
152
|
+
var GuiVisibilityContext = React3.createContext(false);
|
|
153
|
+
function GuiProvider({
|
|
154
|
+
children,
|
|
155
|
+
visible = true
|
|
156
|
+
}) {
|
|
157
|
+
const value = React3.useMemo(() => !!visible, [visible]);
|
|
158
|
+
return React3.createElement(GuiVisibilityContext.Provider, { value }, children);
|
|
159
|
+
}
|
|
160
|
+
function useGuiVisible() {
|
|
161
|
+
return React3.useContext(GuiVisibilityContext);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/gui-manager.tsx
|
|
165
|
+
var React4 = __toESM(require("react"));
|
|
166
|
+
var import_navigation = require("next/navigation");
|
|
167
|
+
var import_lil_gui = __toESM(require("lil-gui"));
|
|
168
|
+
|
|
169
|
+
// src/builder.ts
|
|
170
|
+
function buildGuiItemsFromDefinitions(definitions, pathname, getSetting, updateSetting) {
|
|
171
|
+
const forRoute = getDefinitionsForRoute(definitions, pathname);
|
|
172
|
+
return forRoute.map((def) => definitionToGuiItem(def, getSetting, updateSetting));
|
|
173
|
+
}
|
|
174
|
+
function definitionToGuiItem(def, getSetting, updateSetting) {
|
|
175
|
+
if (def.type === "folder") {
|
|
176
|
+
return {
|
|
177
|
+
type: "folder",
|
|
178
|
+
name: def.name,
|
|
179
|
+
children: def.children.map((c) => definitionToGuiItem(c, getSetting, updateSetting))
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const value = getSetting(def.id) ?? def.default;
|
|
183
|
+
switch (def.type) {
|
|
184
|
+
case "color":
|
|
185
|
+
return { type: "color", id: def.id, name: def.name, value, onChange: (v) => updateSetting(def.id, v) };
|
|
186
|
+
case "number":
|
|
187
|
+
return { type: "number", id: def.id, name: def.name, value, min: def.min, max: def.max, step: def.step, onChange: (v) => updateSetting(def.id, v) };
|
|
188
|
+
case "boolean":
|
|
189
|
+
return { type: "boolean", id: def.id, name: def.name, value, onChange: (v) => updateSetting(def.id, v) };
|
|
190
|
+
case "string":
|
|
191
|
+
return { type: "string", id: def.id, name: def.name, value, onChange: (v) => updateSetting(def.id, v) };
|
|
192
|
+
case "dropdown":
|
|
193
|
+
return { type: "dropdown", id: def.id, name: def.name, value, options: def.options, onChange: (v) => updateSetting(def.id, v) };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/gui-manager.tsx
|
|
198
|
+
function addItem(gui, item) {
|
|
199
|
+
if (item.type === "folder") {
|
|
200
|
+
const folder = gui.addFolder(item.name);
|
|
201
|
+
for (const child of item.children) addItem(folder, child);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const obj = { value: item.value };
|
|
205
|
+
let controller;
|
|
206
|
+
switch (item.type) {
|
|
207
|
+
case "number":
|
|
208
|
+
controller = gui.add(obj, "value", item.min ?? 0, item.max ?? 100, item.step ?? 1);
|
|
209
|
+
break;
|
|
210
|
+
case "color":
|
|
211
|
+
controller = gui.addColor(obj, "value");
|
|
212
|
+
break;
|
|
213
|
+
case "boolean":
|
|
214
|
+
controller = gui.add(obj, "value");
|
|
215
|
+
break;
|
|
216
|
+
case "string":
|
|
217
|
+
controller = gui.add(obj, "value");
|
|
218
|
+
break;
|
|
219
|
+
case "dropdown":
|
|
220
|
+
controller = gui.add(obj, "value", item.options);
|
|
221
|
+
break;
|
|
222
|
+
default:
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
controller.name(item.name);
|
|
226
|
+
controller.onChange((value) => {
|
|
227
|
+
item.onChange(value);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function GuiManager() {
|
|
231
|
+
const pathname = (0, import_navigation.usePathname)();
|
|
232
|
+
const visible = useGuiVisible();
|
|
233
|
+
const definitions = useGuiDefinitions();
|
|
234
|
+
const { getSetting, updateSetting } = useSettings();
|
|
235
|
+
const containerRef = React4.useRef(null);
|
|
236
|
+
const getSettingRef = React4.useRef(getSetting);
|
|
237
|
+
const updateSettingRef = React4.useRef(updateSetting);
|
|
238
|
+
React4.useEffect(() => {
|
|
239
|
+
getSettingRef.current = getSetting;
|
|
240
|
+
updateSettingRef.current = updateSetting;
|
|
241
|
+
}, [getSetting, updateSetting]);
|
|
242
|
+
React4.useEffect(() => {
|
|
243
|
+
if (!visible || !definitions.length) return;
|
|
244
|
+
const container = containerRef.current;
|
|
245
|
+
if (!container) return;
|
|
246
|
+
const get = (path) => getSettingRef.current(path);
|
|
247
|
+
const set = (path, value) => updateSettingRef.current(path, value);
|
|
248
|
+
const items = buildGuiItemsFromDefinitions(definitions, pathname, get, set);
|
|
249
|
+
if (items.length === 0) return;
|
|
250
|
+
const gui = new import_lil_gui.default({ container, title: "Settings", autoPlace: false });
|
|
251
|
+
for (const item of items) addItem(gui, item);
|
|
252
|
+
return () => gui.destroy();
|
|
253
|
+
}, [pathname, visible, definitions]);
|
|
254
|
+
if (!visible) return null;
|
|
255
|
+
return React4.createElement("div", {
|
|
256
|
+
ref: containerRef,
|
|
257
|
+
style: { position: "fixed", top: 16, right: 16, zIndex: 9999 }
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/use-setting.ts
|
|
262
|
+
var React5 = __toESM(require("react"));
|
|
263
|
+
function useSetting(path, defaultOrOptions, onChange) {
|
|
264
|
+
const { getSetting } = useSettings();
|
|
265
|
+
const visible = useGuiVisible();
|
|
266
|
+
const getDef = useGetDefinition();
|
|
267
|
+
const options = defaultOrOptions != null && typeof defaultOrOptions === "object" && !Array.isArray(defaultOrOptions) && "default" in defaultOrOptions ? defaultOrOptions : { default: defaultOrOptions, onChange };
|
|
268
|
+
const defaultVal = options.default ?? getDef(path)?.default;
|
|
269
|
+
const onChangeFn = options.onChange ?? onChange;
|
|
270
|
+
const raw = getSetting(path);
|
|
271
|
+
const value = visible ? raw ?? defaultVal : void 0;
|
|
272
|
+
const prevRef = React5.useRef(value);
|
|
273
|
+
React5.useEffect(() => {
|
|
274
|
+
if (value === void 0) return;
|
|
275
|
+
if (value !== prevRef.current) {
|
|
276
|
+
prevRef.current = value;
|
|
277
|
+
onChangeFn?.(value);
|
|
278
|
+
} else {
|
|
279
|
+
prevRef.current = value;
|
|
280
|
+
}
|
|
281
|
+
}, [value, onChangeFn]);
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
285
|
+
0 && (module.exports = {
|
|
286
|
+
GuiConfigProvider,
|
|
287
|
+
GuiManager,
|
|
288
|
+
GuiProvider,
|
|
289
|
+
SettingsProvider,
|
|
290
|
+
buildGuiItemsFromDefinitions,
|
|
291
|
+
getDefinition,
|
|
292
|
+
getDefinitionsForRoute,
|
|
293
|
+
matchRoute,
|
|
294
|
+
useGetDefinition,
|
|
295
|
+
useGuiDefinitions,
|
|
296
|
+
useGuiVisible,
|
|
297
|
+
useSetting,
|
|
298
|
+
useSettings
|
|
299
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/settings-context.tsx
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
function getByPath(obj, path) {
|
|
6
|
+
const parts = path.split(".").filter(Boolean);
|
|
7
|
+
let current = obj;
|
|
8
|
+
for (const key of parts) {
|
|
9
|
+
if (current == null || typeof current !== "object" || Array.isArray(current)) return void 0;
|
|
10
|
+
current = current[key];
|
|
11
|
+
}
|
|
12
|
+
return current;
|
|
13
|
+
}
|
|
14
|
+
function setByPath(obj, path, value) {
|
|
15
|
+
const parts = path.split(".").filter(Boolean);
|
|
16
|
+
if (parts.length === 0) return obj;
|
|
17
|
+
const result = { ...obj };
|
|
18
|
+
let current = result;
|
|
19
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
20
|
+
const key = parts[i];
|
|
21
|
+
const next = current[key];
|
|
22
|
+
const nextObj = next != null && typeof next === "object" && !Array.isArray(next) ? { ...next } : {};
|
|
23
|
+
current[key] = nextObj;
|
|
24
|
+
current = nextObj;
|
|
25
|
+
}
|
|
26
|
+
current[parts[parts.length - 1]] = value;
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
var SettingsContext = React.createContext(null);
|
|
30
|
+
function SettingsProvider({ children, initialSettings = {} }) {
|
|
31
|
+
const [settings, setSettings] = React.useState(() => {
|
|
32
|
+
const result = {};
|
|
33
|
+
for (const key in initialSettings) {
|
|
34
|
+
const group = initialSettings[key];
|
|
35
|
+
if (group !== void 0) result[key] = group;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
});
|
|
39
|
+
const updateSetting = React.useCallback((path, value2) => {
|
|
40
|
+
setSettings((prev) => setByPath(prev, path, value2));
|
|
41
|
+
}, []);
|
|
42
|
+
const getSetting = React.useCallback(
|
|
43
|
+
(path) => getByPath(settings, path),
|
|
44
|
+
[settings]
|
|
45
|
+
);
|
|
46
|
+
const getGroup = React.useCallback((name) => settings[name] ?? {}, [settings]);
|
|
47
|
+
const value = React.useMemo(
|
|
48
|
+
() => ({ settings, updateSetting, getSetting, getGroup }),
|
|
49
|
+
[settings, updateSetting, getSetting, getGroup]
|
|
50
|
+
);
|
|
51
|
+
return React.createElement(SettingsContext.Provider, { value }, children);
|
|
52
|
+
}
|
|
53
|
+
function useSettings() {
|
|
54
|
+
const ctx = React.useContext(SettingsContext);
|
|
55
|
+
if (!ctx) throw new Error("useSettings must be used within SettingsProvider");
|
|
56
|
+
return ctx;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/config-context.tsx
|
|
60
|
+
import * as React2 from "react";
|
|
61
|
+
|
|
62
|
+
// src/routes.ts
|
|
63
|
+
function matchRoute(pathname, route) {
|
|
64
|
+
if (route === "*") return true;
|
|
65
|
+
if (route.endsWith("/*")) {
|
|
66
|
+
const prefix = route.slice(0, -2);
|
|
67
|
+
return pathname.startsWith(prefix + "/");
|
|
68
|
+
}
|
|
69
|
+
return pathname === route;
|
|
70
|
+
}
|
|
71
|
+
function getDefinitionsForRoute(definitions, pathname) {
|
|
72
|
+
return definitions.filter((def) => {
|
|
73
|
+
const routes = def.routes;
|
|
74
|
+
if (!routes?.length) return true;
|
|
75
|
+
return routes.some((r) => matchRoute(pathname, r));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function flatten(items, acc = /* @__PURE__ */ new Map()) {
|
|
79
|
+
for (const d of items) {
|
|
80
|
+
if (d.type === "folder") flatten(d.children, acc);
|
|
81
|
+
else if (d.id != null) acc.set(d.id, d);
|
|
82
|
+
}
|
|
83
|
+
return acc;
|
|
84
|
+
}
|
|
85
|
+
function getDefinition(definitions, id) {
|
|
86
|
+
return flatten(definitions).get(id);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/config-context.tsx
|
|
90
|
+
var DefinitionsContext = React2.createContext([]);
|
|
91
|
+
function GuiConfigProvider({ definitions, children }) {
|
|
92
|
+
const value = React2.useMemo(() => definitions, [definitions]);
|
|
93
|
+
return React2.createElement(DefinitionsContext.Provider, { value }, children);
|
|
94
|
+
}
|
|
95
|
+
function useGuiDefinitions() {
|
|
96
|
+
return React2.useContext(DefinitionsContext);
|
|
97
|
+
}
|
|
98
|
+
function useGetDefinition() {
|
|
99
|
+
const definitions = useGuiDefinitions();
|
|
100
|
+
return React2.useCallback((id) => getDefinition(definitions, id), [definitions]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/gui-provider.tsx
|
|
104
|
+
import * as React3 from "react";
|
|
105
|
+
var GuiVisibilityContext = React3.createContext(false);
|
|
106
|
+
function GuiProvider({
|
|
107
|
+
children,
|
|
108
|
+
visible = true
|
|
109
|
+
}) {
|
|
110
|
+
const value = React3.useMemo(() => !!visible, [visible]);
|
|
111
|
+
return React3.createElement(GuiVisibilityContext.Provider, { value }, children);
|
|
112
|
+
}
|
|
113
|
+
function useGuiVisible() {
|
|
114
|
+
return React3.useContext(GuiVisibilityContext);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/gui-manager.tsx
|
|
118
|
+
import * as React4 from "react";
|
|
119
|
+
import { usePathname } from "next/navigation";
|
|
120
|
+
import GUI from "lil-gui";
|
|
121
|
+
|
|
122
|
+
// src/builder.ts
|
|
123
|
+
function buildGuiItemsFromDefinitions(definitions, pathname, getSetting, updateSetting) {
|
|
124
|
+
const forRoute = getDefinitionsForRoute(definitions, pathname);
|
|
125
|
+
return forRoute.map((def) => definitionToGuiItem(def, getSetting, updateSetting));
|
|
126
|
+
}
|
|
127
|
+
function definitionToGuiItem(def, getSetting, updateSetting) {
|
|
128
|
+
if (def.type === "folder") {
|
|
129
|
+
return {
|
|
130
|
+
type: "folder",
|
|
131
|
+
name: def.name,
|
|
132
|
+
children: def.children.map((c) => definitionToGuiItem(c, getSetting, updateSetting))
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const value = getSetting(def.id) ?? def.default;
|
|
136
|
+
switch (def.type) {
|
|
137
|
+
case "color":
|
|
138
|
+
return { type: "color", id: def.id, name: def.name, value, onChange: (v) => updateSetting(def.id, v) };
|
|
139
|
+
case "number":
|
|
140
|
+
return { type: "number", id: def.id, name: def.name, value, min: def.min, max: def.max, step: def.step, onChange: (v) => updateSetting(def.id, v) };
|
|
141
|
+
case "boolean":
|
|
142
|
+
return { type: "boolean", id: def.id, name: def.name, value, onChange: (v) => updateSetting(def.id, v) };
|
|
143
|
+
case "string":
|
|
144
|
+
return { type: "string", id: def.id, name: def.name, value, onChange: (v) => updateSetting(def.id, v) };
|
|
145
|
+
case "dropdown":
|
|
146
|
+
return { type: "dropdown", id: def.id, name: def.name, value, options: def.options, onChange: (v) => updateSetting(def.id, v) };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/gui-manager.tsx
|
|
151
|
+
function addItem(gui, item) {
|
|
152
|
+
if (item.type === "folder") {
|
|
153
|
+
const folder = gui.addFolder(item.name);
|
|
154
|
+
for (const child of item.children) addItem(folder, child);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const obj = { value: item.value };
|
|
158
|
+
let controller;
|
|
159
|
+
switch (item.type) {
|
|
160
|
+
case "number":
|
|
161
|
+
controller = gui.add(obj, "value", item.min ?? 0, item.max ?? 100, item.step ?? 1);
|
|
162
|
+
break;
|
|
163
|
+
case "color":
|
|
164
|
+
controller = gui.addColor(obj, "value");
|
|
165
|
+
break;
|
|
166
|
+
case "boolean":
|
|
167
|
+
controller = gui.add(obj, "value");
|
|
168
|
+
break;
|
|
169
|
+
case "string":
|
|
170
|
+
controller = gui.add(obj, "value");
|
|
171
|
+
break;
|
|
172
|
+
case "dropdown":
|
|
173
|
+
controller = gui.add(obj, "value", item.options);
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
controller.name(item.name);
|
|
179
|
+
controller.onChange((value) => {
|
|
180
|
+
item.onChange(value);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function GuiManager() {
|
|
184
|
+
const pathname = usePathname();
|
|
185
|
+
const visible = useGuiVisible();
|
|
186
|
+
const definitions = useGuiDefinitions();
|
|
187
|
+
const { getSetting, updateSetting } = useSettings();
|
|
188
|
+
const containerRef = React4.useRef(null);
|
|
189
|
+
const getSettingRef = React4.useRef(getSetting);
|
|
190
|
+
const updateSettingRef = React4.useRef(updateSetting);
|
|
191
|
+
React4.useEffect(() => {
|
|
192
|
+
getSettingRef.current = getSetting;
|
|
193
|
+
updateSettingRef.current = updateSetting;
|
|
194
|
+
}, [getSetting, updateSetting]);
|
|
195
|
+
React4.useEffect(() => {
|
|
196
|
+
if (!visible || !definitions.length) return;
|
|
197
|
+
const container = containerRef.current;
|
|
198
|
+
if (!container) return;
|
|
199
|
+
const get = (path) => getSettingRef.current(path);
|
|
200
|
+
const set = (path, value) => updateSettingRef.current(path, value);
|
|
201
|
+
const items = buildGuiItemsFromDefinitions(definitions, pathname, get, set);
|
|
202
|
+
if (items.length === 0) return;
|
|
203
|
+
const gui = new GUI({ container, title: "Settings", autoPlace: false });
|
|
204
|
+
for (const item of items) addItem(gui, item);
|
|
205
|
+
return () => gui.destroy();
|
|
206
|
+
}, [pathname, visible, definitions]);
|
|
207
|
+
if (!visible) return null;
|
|
208
|
+
return React4.createElement("div", {
|
|
209
|
+
ref: containerRef,
|
|
210
|
+
style: { position: "fixed", top: 16, right: 16, zIndex: 9999 }
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/use-setting.ts
|
|
215
|
+
import * as React5 from "react";
|
|
216
|
+
function useSetting(path, defaultOrOptions, onChange) {
|
|
217
|
+
const { getSetting } = useSettings();
|
|
218
|
+
const visible = useGuiVisible();
|
|
219
|
+
const getDef = useGetDefinition();
|
|
220
|
+
const options = defaultOrOptions != null && typeof defaultOrOptions === "object" && !Array.isArray(defaultOrOptions) && "default" in defaultOrOptions ? defaultOrOptions : { default: defaultOrOptions, onChange };
|
|
221
|
+
const defaultVal = options.default ?? getDef(path)?.default;
|
|
222
|
+
const onChangeFn = options.onChange ?? onChange;
|
|
223
|
+
const raw = getSetting(path);
|
|
224
|
+
const value = visible ? raw ?? defaultVal : void 0;
|
|
225
|
+
const prevRef = React5.useRef(value);
|
|
226
|
+
React5.useEffect(() => {
|
|
227
|
+
if (value === void 0) return;
|
|
228
|
+
if (value !== prevRef.current) {
|
|
229
|
+
prevRef.current = value;
|
|
230
|
+
onChangeFn?.(value);
|
|
231
|
+
} else {
|
|
232
|
+
prevRef.current = value;
|
|
233
|
+
}
|
|
234
|
+
}, [value, onChangeFn]);
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
export {
|
|
238
|
+
GuiConfigProvider,
|
|
239
|
+
GuiManager,
|
|
240
|
+
GuiProvider,
|
|
241
|
+
SettingsProvider,
|
|
242
|
+
buildGuiItemsFromDefinitions,
|
|
243
|
+
getDefinition,
|
|
244
|
+
getDefinitionsForRoute,
|
|
245
|
+
matchRoute,
|
|
246
|
+
useGetDefinition,
|
|
247
|
+
useGuiDefinitions,
|
|
248
|
+
useGuiVisible,
|
|
249
|
+
useSetting,
|
|
250
|
+
useSettings
|
|
251
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nonsoanetoh/lil-gui-helper",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Route-aware lil-gui manager for Next.js with a central config and useSetting hook",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"next": ">=14.0.0",
|
|
20
|
+
"react": ">=18.0.0",
|
|
21
|
+
"react-dom": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"lil-gui": "^0.21.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"@types/react": "^19.0.0",
|
|
29
|
+
"@types/react-dom": "^19.0.0",
|
|
30
|
+
"next": ">=14.0.0",
|
|
31
|
+
"react": "^19.0.0",
|
|
32
|
+
"react-dom": "^19.0.0",
|
|
33
|
+
"tsup": "^8.0.0",
|
|
34
|
+
"typescript": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"next",
|
|
38
|
+
"lil-gui",
|
|
39
|
+
"gui",
|
|
40
|
+
"settings",
|
|
41
|
+
"dev-tools"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/nonsoanetoh/lil-gui-helper.git"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup src/index.tsx --format cjs,esm --dts --clean",
|
|
53
|
+
"dev": "tsup src/index.tsx --format cjs,esm --dts --watch"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# lil-gui-helper
|
|
2
|
+
|
|
3
|
+
Route-aware [lil-gui](https://lil-gui.georgealways.com/) manager for Next.js App Router: central config, optional visibility flag, and `useSetting` hook.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
From npm (when published):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add lil-gui-helper
|
|
11
|
+
# or
|
|
12
|
+
npm i lil-gui-helper
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
From a **private GitHub repo** (see [Publishing to a private repo](#publishing-to-a-private-repo) below):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add git+https://github.com/YOUR_USERNAME/lil-gui-helper.git
|
|
19
|
+
# or with SSH
|
|
20
|
+
pnpm add git+ssh://git@github.com:YOUR_USERNAME/lil-gui-helper.git
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### 1. Define your config
|
|
26
|
+
|
|
27
|
+
Create a config array (e.g. `lib/gui-config.ts`):
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import type { SettingDefinitionItem } from "lil-gui-helper";
|
|
31
|
+
|
|
32
|
+
export const GUI_DEFINITIONS: SettingDefinitionItem[] = [
|
|
33
|
+
{
|
|
34
|
+
id: "home.backgroundColor",
|
|
35
|
+
type: "color",
|
|
36
|
+
name: "Background Color",
|
|
37
|
+
default: "#fefefe",
|
|
38
|
+
routes: ["/"],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: "folder",
|
|
42
|
+
name: "Position",
|
|
43
|
+
routes: ["/work/*"],
|
|
44
|
+
children: [
|
|
45
|
+
{ id: "position.x", type: "number", name: "X", default: 0 },
|
|
46
|
+
{ id: "position.y", type: "number", name: "Y", default: 0 },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Routes:** `"*"` = all; `"/work"` = exact; `"/work/*"` = subroutes only (not `/work`).
|
|
53
|
+
|
|
54
|
+
### 2. Wrap the app
|
|
55
|
+
|
|
56
|
+
In your root layout (inside `body`):
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import {
|
|
60
|
+
SettingsProvider,
|
|
61
|
+
GuiConfigProvider,
|
|
62
|
+
GuiProvider,
|
|
63
|
+
GuiManager,
|
|
64
|
+
} from "lil-gui-helper";
|
|
65
|
+
import { GUI_DEFINITIONS } from "@/lib/gui-config";
|
|
66
|
+
|
|
67
|
+
export default function RootLayout({ children }) {
|
|
68
|
+
return (
|
|
69
|
+
<html>
|
|
70
|
+
<body>
|
|
71
|
+
<SettingsProvider>
|
|
72
|
+
<GuiConfigProvider definitions={GUI_DEFINITIONS}>
|
|
73
|
+
<GuiProvider visible={process.env.NODE_ENV === "development"}>
|
|
74
|
+
{children}
|
|
75
|
+
<GuiManager />
|
|
76
|
+
</GuiProvider>
|
|
77
|
+
</GuiConfigProvider>
|
|
78
|
+
</SettingsProvider>
|
|
79
|
+
</body>
|
|
80
|
+
</html>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **SettingsProvider** – stores values updated by the GUI.
|
|
86
|
+
- **GuiConfigProvider** – provides your definitions (required for `GuiManager` and `useSetting` defaults).
|
|
87
|
+
- **GuiProvider** – `visible`: when `false`, the panel is hidden and `useSetting` returns `undefined` so consumers use their default. Pass e.g. `visible={process.env.NODE_ENV === "development"}` to show only in dev.
|
|
88
|
+
- **GuiManager** – builds and mounts the lil-gui panel from definitions + current pathname.
|
|
89
|
+
|
|
90
|
+
### 3. Read values in pages
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
"use client";
|
|
94
|
+
|
|
95
|
+
import { useSetting } from "lil-gui-helper";
|
|
96
|
+
|
|
97
|
+
export default function Home() {
|
|
98
|
+
const bg = useSetting("home.backgroundColor", "#fefefe");
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div style={{ minHeight: "100vh", backgroundColor: bg ?? "#fefefe" }}>
|
|
102
|
+
…
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `useSetting(path, default?)` – when GUI is visible returns the current value (or default); when hidden returns `undefined` so `value ?? default` uses your fallback.
|
|
109
|
+
- `useSetting(path, { default, onChange })` – optional `onChange` when the value changes.
|
|
110
|
+
|
|
111
|
+
## API
|
|
112
|
+
|
|
113
|
+
| Export | Description |
|
|
114
|
+
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| `SettingsProvider` | Holds settings state; required above GUI. |
|
|
116
|
+
| `GuiConfigProvider` | Injects your `definitions`; required for `GuiManager` and definition-based defaults in `useSetting`. |
|
|
117
|
+
| `GuiProvider` | `visible` flag; when false, panel is hidden and `useSetting` is undefined. |
|
|
118
|
+
| `GuiManager` | Renders the lil-gui panel from definitions + pathname. |
|
|
119
|
+
| `useSetting(path, default?, onChange?)` | Current value or undefined when GUI hidden. |
|
|
120
|
+
| `useSettings()` | Raw getSetting / updateSetting. |
|
|
121
|
+
| `matchRoute(pathname, route)` | `"*"` \| `"/path"` \| `"/path/*"`. |
|
|
122
|
+
| `getDefinitionsForRoute(definitions, pathname)` | Filter definitions by pathname. |
|
|
123
|
+
| `getDefinition(definitions, id)` | Get leaf definition by id. |
|
|
124
|
+
| `buildGuiItemsFromDefinitions(definitions, pathname, getSetting, updateSetting)` | Build lil-gui items (for custom UIs). |
|
|
125
|
+
|
|
126
|
+
## Publishing to a private repo
|
|
127
|
+
|
|
128
|
+
### Option A: Private GitHub repository
|
|
129
|
+
|
|
130
|
+
1. Create a **new private repo** on GitHub (e.g. `lil-gui-helper`).
|
|
131
|
+
2. Update `package.json` in this package: set `repository.url` to your repo URL, e.g. `"url": "https://github.com/YOUR_USERNAME/lil-gui-helper.git"`.
|
|
132
|
+
3. From this monorepo, copy only the package into the new repo (or push the whole `packages/lil-gui-helper` folder as the root of the new repo):
|
|
133
|
+
- Easiest: create the repo, then push the contents of `packages/lil-gui-helper` as the root (so the new repo has `package.json`, `src/`, `README.md`, etc. at top level).
|
|
134
|
+
4. In the new repo, run `pnpm install` and `pnpm build`.
|
|
135
|
+
5. In other projects, install from Git:
|
|
136
|
+
```bash
|
|
137
|
+
pnpm add git+https://github.com/YOUR_USERNAME/lil-gui-helper.git
|
|
138
|
+
```
|
|
139
|
+
For a private repo you’ll need auth (SSH key, or a personal access token in the URL, or `npm config set` for Git credentials).
|
|
140
|
+
|
|
141
|
+
### Option B: Private npm package (scoped)
|
|
142
|
+
|
|
143
|
+
1. Use a scoped name in `package.json`, e.g. `"name": "@your-npm-username/lil-gui-helper"`.
|
|
144
|
+
2. Build: `cd packages/lil-gui-helper && pnpm build`.
|
|
145
|
+
3. Publish as private (requires an npm paid plan for private packages):
|
|
146
|
+
```bash
|
|
147
|
+
npm login
|
|
148
|
+
npm publish --access restricted
|
|
149
|
+
```
|
|
150
|
+
Or with a scope that’s configured for private: `npm publish`.
|
|
151
|
+
|
|
152
|
+
4. Install in other projects: `pnpm add @your-npm-username/lil-gui-helper`.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
**Next step:** Open a new tab, create the private GitHub repo, then we can wire the `repository` URL and decide whether you’re using Option A (install from Git) or Option B (private npm).
|