@neuralumina/lumina-ui 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,121 @@
1
+ import { isVNode, luminaTheme } from "./utils.js";
2
+
3
+ export function Text(content = "", props = {}) {
4
+ const {
5
+ size,
6
+ weight,
7
+ align,
8
+ color,
9
+ lineHeight,
10
+ maxLines,
11
+ as,
12
+ style: userStyle,
13
+ ...rest
14
+ } = props;
15
+
16
+ const fontSize = typeof size === "number" ? `${size}px` : size || "14px";
17
+
18
+ const style = {
19
+ fontSize,
20
+ fontWeight: weight || "normal",
21
+ color: color || "inherit",
22
+ textAlign: align || "left",
23
+ margin: 0,
24
+ lineHeight: lineHeight || "1.5",
25
+ letterSpacing: "0",
26
+ WebkitFontSmoothing: "antialiased",
27
+ ...(userStyle || {}),
28
+ };
29
+
30
+ if (maxLines) {
31
+ style.display = "-webkit-box";
32
+ style.WebkitLineClamp = maxLines;
33
+ style.WebkitBoxOrient = "vertical";
34
+ style.overflow = "hidden";
35
+ }
36
+
37
+ const tag = as || "span";
38
+ const safeProps = { style, ...rest };
39
+
40
+ // Normalize content: preserve nodes if passed
41
+ const children = [];
42
+ const isNode = (value) => typeof Node !== "undefined" && value instanceof Node;
43
+
44
+ if (isNode(content) || isVNode(content)) children.push(content);
45
+ else if (Array.isArray(content))
46
+ content.forEach((c) =>
47
+ children.push(isNode(c) || isVNode(c) ? c : String(c ?? "")),
48
+ );
49
+ else children.push(String(content ?? ""));
50
+
51
+ return { tag, props: safeProps, children };
52
+ }
53
+
54
+ export function Heading(props = {}, children = []) {
55
+ const level = props.level ?? 1;
56
+ const sizes = { 1: 32, 2: 28, 3: 24, 4: 20, 5: 18, 6: 16 };
57
+ const content = Array.isArray(children) ? children : [children];
58
+ return Text(content, {
59
+ size: sizes[level] ?? 16,
60
+ weight: "bold",
61
+ as: `h${level}`,
62
+ ...props,
63
+ });
64
+ }
65
+
66
+ export function Caption(props = {}, children = []) {
67
+ const content = Array.isArray(children) ? children : [children];
68
+ return Text(content, { size: 12, color: luminaTheme.colors.muted, ...props });
69
+ }
70
+
71
+ export function DefaultTextStyle(props = {}, children = []) {
72
+ const content = Array.isArray(children) ? children : [children];
73
+ const {
74
+ size,
75
+ weight,
76
+ color,
77
+ align,
78
+ lineHeight,
79
+ style = {},
80
+ ...rest
81
+ } = props;
82
+
83
+ return {
84
+ tag: "div",
85
+ props: {
86
+ ...rest,
87
+ style: {
88
+ fontSize: typeof size === "number" ? `${size}px` : size,
89
+ fontWeight: weight,
90
+ color,
91
+ textAlign: align,
92
+ lineHeight,
93
+ ...style,
94
+ },
95
+ },
96
+ children: content,
97
+ key: props.key,
98
+ };
99
+ }
100
+
101
+ export function RichText({ spans = [], as = "span", style = {}, ...props } = {}) {
102
+ return {
103
+ tag: as,
104
+ props: {
105
+ ...props,
106
+ style,
107
+ },
108
+ children: spans.map((span, index) => ({
109
+ tag: span.as || "span",
110
+ props: {
111
+ key: span.key ?? index,
112
+ style: span.style || {},
113
+ },
114
+ children: Array.isArray(span.children)
115
+ ? span.children
116
+ : [span.text ?? ""],
117
+ key: span.key ?? index,
118
+ })),
119
+ key: props.key,
120
+ };
121
+ }
@@ -0,0 +1,221 @@
1
+ export function isDomNode(value) {
2
+ return typeof Node !== "undefined" && value instanceof Node;
3
+ }
4
+
5
+ export const luminaTheme = {
6
+ colors: {
7
+ primary: "#2563eb",
8
+ primaryDark: "#1d4ed8",
9
+ primarySoft: "rgba(37, 99, 235, 0.12)",
10
+ danger: "#dc2626",
11
+ dangerDark: "#b91c1c",
12
+ dangerSoft: "rgba(220, 38, 38, 0.12)",
13
+ surface: "#ffffff",
14
+ surfaceMuted: "#f8fafc",
15
+ surfaceRaised: "#ffffff",
16
+ text: "#111827",
17
+ muted: "#64748b",
18
+ border: "#e5e7eb",
19
+ borderStrong: "#cbd5e1",
20
+ track: "#e2e8f0",
21
+ shadow: "rgba(15, 23, 42, 0.12)",
22
+ overlay: "rgba(15, 23, 42, 0.58)",
23
+ focus: "rgba(37, 99, 235, 0.18)",
24
+ },
25
+ radius: {
26
+ sm: "6px",
27
+ md: "8px",
28
+ lg: "12px",
29
+ xl: "16px",
30
+ pill: "999px",
31
+ },
32
+ shadow: {
33
+ xs: "0 1px 2px rgba(15, 23, 42, 0.06)",
34
+ sm: "0 8px 22px rgba(15, 23, 42, 0.08)",
35
+ md: "0 18px 44px rgba(15, 23, 42, 0.14)",
36
+ lg: "0 24px 70px rgba(15, 23, 42, 0.24)",
37
+ },
38
+ transition: "160ms ease",
39
+ };
40
+
41
+ export function isVNode(value) {
42
+ return value && typeof value === "object" && typeof value.tag === "string";
43
+ }
44
+
45
+ export function isRenderable(value) {
46
+ return (
47
+ value === null ||
48
+ value === undefined ||
49
+ Array.isArray(value) ||
50
+ isDomNode(value) ||
51
+ isVNode(value) ||
52
+ typeof value === "string" ||
53
+ typeof value === "number" ||
54
+ typeof value === "boolean" ||
55
+ typeof value === "function"
56
+ );
57
+ }
58
+
59
+ export function childrenOf(children) {
60
+ if (children === null || children === undefined) return [];
61
+ return Array.isArray(children) ? children : [children];
62
+ }
63
+
64
+ export function normalizeWidgetArgs(
65
+ propsOrChildren = {},
66
+ maybeChildren = undefined,
67
+ ) {
68
+ if (isRenderable(propsOrChildren)) {
69
+ return [{}, childrenOf(propsOrChildren)];
70
+ }
71
+
72
+ const props = propsOrChildren || {};
73
+ const propChildren = props.child !== undefined ? props.child : props.children;
74
+ const children =
75
+ propChildren !== undefined && maybeChildren === undefined
76
+ ? propChildren
77
+ : maybeChildren;
78
+
79
+ return [props, childrenOf(children)];
80
+ }
81
+
82
+ export function px(value, fallback = undefined) {
83
+ if (value === undefined || value === null) return fallback;
84
+ return typeof value === "number" ? `${value}px` : value;
85
+ }
86
+
87
+ export function edgeInsets(value, fallback = undefined) {
88
+ if (value === undefined || value === null) return fallback;
89
+ if (typeof value === "number" || typeof value === "string") return px(value);
90
+
91
+ const all = value.all;
92
+ const vertical = value.vertical ?? all ?? 0;
93
+ const horizontal = value.horizontal ?? all ?? 0;
94
+
95
+ return [
96
+ px(value.top ?? vertical),
97
+ px(value.right ?? horizontal),
98
+ px(value.bottom ?? vertical),
99
+ px(value.left ?? horizontal),
100
+ ].join(" ");
101
+ }
102
+
103
+ export function flexMainAlignment(value, fallback = "flex-start") {
104
+ const map = {
105
+ start: "flex-start",
106
+ end: "flex-end",
107
+ center: "center",
108
+ spaceBetween: "space-between",
109
+ "space-between": "space-between",
110
+ spaceAround: "space-around",
111
+ "space-around": "space-around",
112
+ spaceEvenly: "space-evenly",
113
+ "space-evenly": "space-evenly",
114
+ };
115
+
116
+ return map[value] || value || fallback;
117
+ }
118
+
119
+ export function flexCrossAlignment(value, fallback = "stretch") {
120
+ const map = {
121
+ start: "flex-start",
122
+ end: "flex-end",
123
+ center: "center",
124
+ stretch: "stretch",
125
+ baseline: "baseline",
126
+ };
127
+
128
+ return map[value] || value || fallback;
129
+ }
130
+
131
+ export function alignmentStyle(alignment = "center") {
132
+ const map = {
133
+ center: ["center", "center"],
134
+ topCenter: ["center", "flex-start"],
135
+ bottomCenter: ["center", "flex-end"],
136
+ centerLeft: ["flex-start", "center"],
137
+ centerRight: ["flex-end", "center"],
138
+ topLeft: ["flex-start", "flex-start"],
139
+ topRight: ["flex-end", "flex-start"],
140
+ bottomLeft: ["flex-start", "flex-end"],
141
+ bottomRight: ["flex-end", "flex-end"],
142
+ start: ["flex-start", "center"],
143
+ end: ["flex-end", "center"],
144
+ };
145
+
146
+ const [justifyContent, alignItems] = map[alignment] || map.center;
147
+ return { justifyContent, alignItems };
148
+ }
149
+
150
+ export function decorationStyle(decoration = {}) {
151
+ if (!decoration || typeof decoration !== "object") return {};
152
+
153
+ return {
154
+ backgroundColor: decoration.color,
155
+ border: decoration.border,
156
+ borderRadius: px(decoration.borderRadius),
157
+ boxShadow: decoration.boxShadow,
158
+ backgroundImage: decoration.gradient,
159
+ };
160
+ }
161
+
162
+ export function cleanStyle(style) {
163
+ return Object.fromEntries(
164
+ Object.entries(style || {}).filter(([, value]) => value !== undefined),
165
+ );
166
+ }
167
+
168
+ export function fieldStyle(style = {}) {
169
+ return cleanStyle({
170
+ width: "100%",
171
+ minHeight: "38px",
172
+ padding: "9px 12px",
173
+ borderRadius: luminaTheme.radius.md,
174
+ border: `1px solid ${luminaTheme.colors.borderStrong}`,
175
+ backgroundColor: luminaTheme.colors.surface,
176
+ color: luminaTheme.colors.text,
177
+ font: "inherit",
178
+ fontSize: "14px",
179
+ outline: "none",
180
+ transition: `border-color ${luminaTheme.transition}, box-shadow ${luminaTheme.transition}, background-color ${luminaTheme.transition}`,
181
+ boxShadow: "0 1px 1px rgba(15, 23, 42, 0.03)",
182
+ ...style,
183
+ });
184
+ }
185
+
186
+ export function applyFieldFocus(event, style = {}) {
187
+ event.target.style.borderColor =
188
+ style.borderColor || luminaTheme.colors.primary;
189
+ event.target.style.boxShadow =
190
+ style.boxShadow || `0 0 0 3px ${luminaTheme.colors.focus}`;
191
+ }
192
+
193
+ export function clearFieldFocus(event, style = {}) {
194
+ event.target.style.borderColor =
195
+ style.borderColor || luminaTheme.colors.borderStrong;
196
+ event.target.style.boxShadow =
197
+ style.boxShadow || "0 1px 1px rgba(15, 23, 42, 0.03)";
198
+ }
199
+
200
+ export function omitProps(props = {}, omitted = []) {
201
+ const omittedSet = new Set(["child", "children", "style", "key", ...omitted]);
202
+ return Object.fromEntries(
203
+ Object.entries(props).filter(([key]) => !omittedSet.has(key)),
204
+ );
205
+ }
206
+
207
+ export function ensureGlobalStyle(id, css) {
208
+ if (
209
+ typeof document === "undefined" ||
210
+ !document.head ||
211
+ typeof document.getElementById !== "function" ||
212
+ document.getElementById(id)
213
+ ) {
214
+ return;
215
+ }
216
+
217
+ const style = document.createElement("style");
218
+ style.id = id;
219
+ style.textContent = css;
220
+ document.head.appendChild(style);
221
+ }
package/lumina-ui.js ADDED
@@ -0,0 +1,154 @@
1
+ export { mount } from "./lumina-ui/core/renderer.js";
2
+ export {
3
+ createState as useState,
4
+ useEffect,
5
+ createStore,
6
+ } from "./lumina-ui/core/state.js";
7
+ export {
8
+ createElement,
9
+ Fragment,
10
+ applyStyles,
11
+ addClasses,
12
+ } from "./lumina-ui/core/element.js";
13
+ export { luminaTheme } from "./lumina-ui/widgets/utils.js";
14
+
15
+ export {
16
+ Column,
17
+ Row,
18
+ Container,
19
+ Center,
20
+ Align,
21
+ Expanded,
22
+ Flexible,
23
+ Padding,
24
+ SizedBox,
25
+ Spacer,
26
+ Wrap,
27
+ Stack,
28
+ Positioned,
29
+ Divider,
30
+ Card,
31
+ AspectRatio,
32
+ Baseline,
33
+ ConstrainedBox,
34
+ DecoratedBox,
35
+ FractionallySizedBox,
36
+ LayoutBuilder,
37
+ LimitedBox,
38
+ Offstage,
39
+ OverflowBox,
40
+ RotatedBox,
41
+ SizedOverflowBox,
42
+ Transform,
43
+ } from "./lumina-ui/widgets/layout.js";
44
+ export {
45
+ Button,
46
+ Input,
47
+ TextField,
48
+ Checkbox,
49
+ Switch,
50
+ } from "./lumina-ui/widgets/controls.js";
51
+ export {
52
+ Badge,
53
+ CircleAvatar,
54
+ ClipRRect,
55
+ Icon,
56
+ Image,
57
+ Placeholder,
58
+ ClipOval,
59
+ ClipPath,
60
+ ClipRect,
61
+ FittedBox,
62
+ Opacity,
63
+ PhysicalModel,
64
+ ShaderMask,
65
+ } from "./lumina-ui/widgets/display.js";
66
+ export {
67
+ CustomScrollView,
68
+ GridView,
69
+ ListView,
70
+ NestedScrollView,
71
+ PageView,
72
+ SingleChildScrollView,
73
+ SliverAppBar,
74
+ SliverGrid,
75
+ SliverList,
76
+ SliverPadding,
77
+ SliverToBoxAdapter,
78
+ } from "./lumina-ui/widgets/scrolling.js";
79
+ export {
80
+ AlertDialog,
81
+ CircularProgressIndicator,
82
+ Dialog,
83
+ LinearProgressIndicator,
84
+ ModalBarrier,
85
+ SnackBar,
86
+ Tooltip,
87
+ } from "./lumina-ui/widgets/feedback.js";
88
+ export {
89
+ Dropdown,
90
+ Form,
91
+ FormField,
92
+ Radio,
93
+ RadioGroup,
94
+ Slider,
95
+ TextArea,
96
+ } from "./lumina-ui/widgets/forms.js";
97
+ export {
98
+ AppBar,
99
+ BottomNavigationBar,
100
+ Drawer,
101
+ NavigationRail,
102
+ Scaffold,
103
+ TabBar,
104
+ TabBarView,
105
+ } from "./lumina-ui/widgets/navigation.js";
106
+ export {
107
+ AnimatedContainer,
108
+ AnimatedOpacity,
109
+ AnimatedScale,
110
+ AnimatedSlide,
111
+ AnimatedSwitcher,
112
+ } from "./lumina-ui/widgets/animation.js";
113
+ export {
114
+ Text,
115
+ Heading,
116
+ Caption,
117
+ DefaultTextStyle,
118
+ RichText,
119
+ } from "./lumina-ui/widgets/text.js";
120
+ export {
121
+ Semantics,
122
+ ExcludeSemantics,
123
+ } from "./lumina-ui/widgets/accessibility.js";
124
+ export {
125
+ AbsorbPointer,
126
+ Dismissible,
127
+ Draggable,
128
+ DragTarget,
129
+ GestureDetector,
130
+ IgnorePointer,
131
+ } from "./lumina-ui/widgets/interaction.js";
132
+
133
+ /*
134
+ export default {
135
+ mount,
136
+ useState: createState,
137
+ useEffect,
138
+ createStore,
139
+ Column,
140
+ Row,
141
+ Container,
142
+ Center,
143
+ Expanded,
144
+ Padding,
145
+ Button,
146
+ Input,
147
+ Checkbox,
148
+ Switch,
149
+ Text,
150
+ Heading,
151
+ Caption,
152
+ App,
153
+ };
154
+ */
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@neuralumina/lumina-ui",
3
+ "version": "0.1.1",
4
+ "description": "A lightweight Flutter-inspired UI library for building web interfaces with plain JavaScript and the DOM.",
5
+ "type": "module",
6
+ "main": "./lumina-ui.js",
7
+ "module": "./lumina-ui.js",
8
+ "browser": "./lumina-ui.js",
9
+ "exports": {
10
+ ".": "./lumina-ui.js",
11
+ "./core/*": "./lumina-ui/core/*.js",
12
+ "./widgets/*": "./lumina-ui/widgets/*.js",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "lumina-ui.js",
17
+ "lumina-ui/core",
18
+ "lumina-ui/widgets",
19
+ "scripts",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "check": "find . -name '*.js' -not -path '*/.git/*' -not -path '*/node_modules/*' -print -exec node --check {} \\;",
26
+ "test": "npm run check && node scripts/smoke-test.mjs",
27
+ "pack:dry": "npm pack --dry-run"
28
+ },
29
+ "keywords": [
30
+ "ui",
31
+ "widgets",
32
+ "dom",
33
+ "vanilla-js",
34
+ "flutter-inspired"
35
+ ],
36
+ "author": "Neura Lumina",
37
+ "license": "MIT"
38
+ }