uraniyum 1.0.9 → 1.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.esm.js +266 -138
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +267 -137
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/router.d.ts +4 -1
- package/dist/state.d.ts +3 -0
- package/dist/styles.d.ts +7 -13
- package/dist/tags.d.ts +1 -1
- package/package.json +1 -1
- package/src/router.ts +80 -39
- package/src/state.ts +10 -5
- package/src/styles.ts +180 -87
- package/src/tags.ts +71 -64
package/src/styles.ts
CHANGED
|
@@ -1,164 +1,257 @@
|
|
|
1
1
|
export type CSSValue = string | number | (() => string | number);
|
|
2
|
+
|
|
2
3
|
export interface CSSProperties {
|
|
3
|
-
[key: string]: CSSValue | CSSProperties;
|
|
4
|
+
[key: string]: CSSValue | CSSProperties | undefined;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface StyleRule {
|
|
8
|
+
[key: string]: CSSValue | CSSProperties | undefined;
|
|
4
9
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[key: string]: any;
|
|
11
|
-
};
|
|
12
|
-
export type StylesMap<T extends Record<string, any> = {}> = {
|
|
13
|
-
[K in keyof T]: StyleRule;
|
|
14
|
-
};
|
|
15
|
-
export type ClassNames<T extends Record<string, any>> = { [K in keyof T]: string };
|
|
16
|
-
|
|
17
|
-
const styleCache = new Map<string, string>();
|
|
10
|
+
|
|
11
|
+
export type StylesMap = Record<string, StyleRule>;
|
|
12
|
+
export type ClassNames<T extends StylesMap> = { [K in keyof T]: string };
|
|
13
|
+
|
|
14
|
+
const styleCache = new Map<string, true>();
|
|
18
15
|
const keyframeCache = new Map<string, string>();
|
|
19
16
|
let styleElement: HTMLStyleElement | null = null;
|
|
17
|
+
let styleSheet: CSSStyleSheet | null = null;
|
|
20
18
|
|
|
21
19
|
const ALLOWED_PREFIXES = [
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
"root",
|
|
21
|
+
"button",
|
|
22
|
+
"icon",
|
|
23
|
+
"text",
|
|
24
|
+
"container",
|
|
25
|
+
"wrapper",
|
|
26
|
+
"card",
|
|
27
|
+
"header",
|
|
28
|
+
"section",
|
|
24
29
|
];
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
const KEYFRAMES_PREFIX = "@keyframes ";
|
|
32
|
+
|
|
33
|
+
function ensureStyleSheet(): CSSStyleSheet {
|
|
34
|
+
if (!styleSheet) {
|
|
35
|
+
if (!styleElement) {
|
|
36
|
+
styleElement = document.createElement("style");
|
|
37
|
+
styleElement.id = "styles";
|
|
38
|
+
document.head.appendChild(styleElement);
|
|
39
|
+
}
|
|
40
|
+
styleSheet = styleElement.sheet as CSSStyleSheet;
|
|
31
41
|
}
|
|
32
|
-
return
|
|
42
|
+
return styleSheet;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
function hashString(str: string): string {
|
|
36
46
|
let hash = 5381;
|
|
37
|
-
for (let i = 0; i < str.length; i++)
|
|
47
|
+
for (let i = 0; i < str.length; i++) {
|
|
48
|
+
hash = (hash * 33) ^ str.charCodeAt(i);
|
|
49
|
+
}
|
|
38
50
|
return (hash >>> 0).toString(36);
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
const
|
|
53
|
+
const camelToKebabCache = new Map<string, string>();
|
|
54
|
+
|
|
42
55
|
function camelToKebab(str: string): string {
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
const cached = camelToKebabCache.get(str);
|
|
57
|
+
if (cached) return cached;
|
|
58
|
+
|
|
59
|
+
const result = str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
|
60
|
+
camelToKebabCache.set(str, result);
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function stableHashRule(rule: Record<string, any>): string {
|
|
65
|
+
const parts: string[] = [];
|
|
66
|
+
|
|
67
|
+
for (const key in rule) {
|
|
68
|
+
if (!Object.prototype.hasOwnProperty.call(rule, key)) continue;
|
|
69
|
+
const val = rule[key];
|
|
70
|
+
|
|
71
|
+
if (key.startsWith("@") || typeof val === "object" || val == null) continue;
|
|
72
|
+
|
|
73
|
+
let v: string;
|
|
74
|
+
if (typeof val === "function") v = val.toString();
|
|
75
|
+
else v = String(val);
|
|
76
|
+
|
|
77
|
+
parts.push(`${key}:${v}`);
|
|
45
78
|
}
|
|
46
|
-
|
|
79
|
+
|
|
80
|
+
parts.sort();
|
|
81
|
+
return hashString(parts.join("|"));
|
|
47
82
|
}
|
|
48
83
|
|
|
49
|
-
function stringifyDecls(
|
|
84
|
+
function stringifyDecls(
|
|
85
|
+
props: Record<string, any>,
|
|
86
|
+
keyframes: Map<string, string>,
|
|
87
|
+
): string {
|
|
50
88
|
const out: string[] = [];
|
|
51
|
-
|
|
89
|
+
|
|
90
|
+
for (const key in props) {
|
|
91
|
+
if (!Object.prototype.hasOwnProperty.call(props, key)) continue;
|
|
92
|
+
const raw = props[key];
|
|
93
|
+
|
|
52
94
|
if (raw == null || typeof raw === "object") continue;
|
|
53
|
-
let val = typeof raw === "function" ? raw() : raw;
|
|
54
95
|
|
|
55
|
-
|
|
96
|
+
let val: string | number =
|
|
97
|
+
typeof raw === "function" ? raw() : (raw as string | number);
|
|
98
|
+
|
|
99
|
+
if (typeof val === "string" && keyframes.size > 0) {
|
|
56
100
|
for (const [orig, uniq] of keyframes) {
|
|
57
|
-
|
|
101
|
+
if (val.includes(`$${orig}`)) {
|
|
102
|
+
val = val.replaceAll(`$${orig}`, uniq);
|
|
103
|
+
}
|
|
58
104
|
}
|
|
59
105
|
}
|
|
106
|
+
|
|
60
107
|
out.push(`${camelToKebab(key)}:${val}`);
|
|
61
108
|
}
|
|
109
|
+
|
|
62
110
|
return out.join(";");
|
|
63
111
|
}
|
|
64
112
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
113
|
+
function registerKeyframes(
|
|
114
|
+
name: string,
|
|
115
|
+
frames: Record<string, any>,
|
|
116
|
+
): string {
|
|
68
117
|
const hash = hashString(name + JSON.stringify(frames));
|
|
69
118
|
const unique = `${name}-${hash}`;
|
|
70
119
|
|
|
71
|
-
|
|
120
|
+
const cached = keyframeCache.get(unique);
|
|
121
|
+
if (cached) return cached;
|
|
122
|
+
|
|
123
|
+
const sheet = ensureStyleSheet();
|
|
72
124
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
.
|
|
125
|
+
const steps: string[] = [];
|
|
126
|
+
for (const step in frames) {
|
|
127
|
+
if (!Object.prototype.hasOwnProperty.call(frames, step)) continue;
|
|
128
|
+
const decls = stringifyDecls(frames[step] ?? {}, new Map());
|
|
129
|
+
steps.push(`${step}{${decls}}`);
|
|
130
|
+
}
|
|
76
131
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
132
|
+
const ruleText = `@keyframes ${unique}{${steps.join("")}}`;
|
|
133
|
+
sheet.insertRule(ruleText, sheet.cssRules.length);
|
|
80
134
|
keyframeCache.set(unique, unique);
|
|
135
|
+
|
|
81
136
|
return unique;
|
|
82
137
|
}
|
|
83
138
|
|
|
84
|
-
function buildRules(
|
|
139
|
+
function buildRules(
|
|
140
|
+
className: string,
|
|
141
|
+
rule: Record<string, any>,
|
|
142
|
+
keyframes: Map<string, string>,
|
|
143
|
+
): string[] {
|
|
85
144
|
const rules: string[] = [];
|
|
86
|
-
const
|
|
145
|
+
const baseDecls: string[] = [];
|
|
87
146
|
|
|
88
|
-
for (const
|
|
89
|
-
if (
|
|
147
|
+
for (const key in rule) {
|
|
148
|
+
if (!Object.prototype.hasOwnProperty.call(rule, key)) continue;
|
|
149
|
+
const val = rule[key];
|
|
90
150
|
|
|
91
|
-
if (key.startsWith(
|
|
92
|
-
|
|
151
|
+
if (key.startsWith("@keyframes")) {
|
|
152
|
+
continue;
|
|
93
153
|
}
|
|
94
|
-
|
|
95
|
-
|
|
154
|
+
|
|
155
|
+
if (!val) continue;
|
|
156
|
+
|
|
157
|
+
if (key[0] === ":" && typeof val === "object") {
|
|
158
|
+
const decls = stringifyDecls(val ?? {}, keyframes);
|
|
159
|
+
if (decls) {
|
|
160
|
+
rules.push(`.${className}${key}{${decls}}`);
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
96
163
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
164
|
+
|
|
165
|
+
if (key.startsWith("@media") && typeof val === "object") {
|
|
166
|
+
const decls = stringifyDecls(val ?? {}, keyframes);
|
|
167
|
+
if (decls) {
|
|
168
|
+
rules.push(`${key}{.${className}{${decls}}}`);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
100
171
|
}
|
|
101
|
-
|
|
102
|
-
|
|
172
|
+
|
|
173
|
+
if (key.includes("&") && typeof val === "object") {
|
|
174
|
+
const selector = key.replace(/&/g, `.${className}`);
|
|
175
|
+
const decls = stringifyDecls(val ?? {}, keyframes);
|
|
176
|
+
if (decls) {
|
|
177
|
+
rules.push(`${selector}{${decls}}`);
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!key.startsWith("@") && typeof val !== "object") {
|
|
183
|
+
const raw = typeof val === "function" ? val() : val;
|
|
184
|
+
baseDecls.push(`${camelToKebab(key)}:${raw}`);
|
|
103
185
|
}
|
|
104
186
|
}
|
|
105
187
|
|
|
106
|
-
if (
|
|
107
|
-
rules.push(`.${className}{${
|
|
188
|
+
if (baseDecls.length > 0) {
|
|
189
|
+
rules.push(`.${className}{${baseDecls.join(";")}}`);
|
|
108
190
|
}
|
|
109
191
|
|
|
110
192
|
return rules;
|
|
111
193
|
}
|
|
112
194
|
|
|
113
195
|
export function makeStyles<T extends StylesMap>(styles: T) {
|
|
114
|
-
return () => {
|
|
196
|
+
return (): ClassNames<T> => {
|
|
115
197
|
const classNames = {} as ClassNames<T>;
|
|
116
|
-
const collectedRules: string[] = [];
|
|
117
198
|
const keyframesMap = new Map<string, string>();
|
|
199
|
+
const sheet = ensureStyleSheet();
|
|
118
200
|
|
|
119
|
-
for (const
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
201
|
+
for (const key in styles) {
|
|
202
|
+
if (!Object.prototype.hasOwnProperty.call(styles, key)) continue;
|
|
203
|
+
if (!key.startsWith(KEYFRAMES_PREFIX)) continue;
|
|
204
|
+
|
|
205
|
+
const name = key.slice(KEYFRAMES_PREFIX.length);
|
|
206
|
+
const frames = styles[key] as unknown as Record<string, any>;
|
|
207
|
+
const unique = registerKeyframes(name, frames);
|
|
208
|
+
keyframesMap.set(name, unique);
|
|
125
209
|
}
|
|
126
210
|
|
|
127
|
-
for (const
|
|
128
|
-
if (
|
|
211
|
+
for (const slot in styles) {
|
|
212
|
+
if (!Object.prototype.hasOwnProperty.call(styles, slot)) continue;
|
|
213
|
+
if (slot.startsWith(KEYFRAMES_PREFIX)) continue;
|
|
129
214
|
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
215
|
+
const rule = styles[slot] as StyleRule;
|
|
216
|
+
const hash = stableHashRule(rule);
|
|
217
|
+
const className = ALLOWED_PREFIXES.includes(slot)
|
|
218
|
+
? `${slot}-${hash}`
|
|
219
|
+
: `css-${hash}`;
|
|
133
220
|
|
|
134
221
|
if (!styleCache.has(className)) {
|
|
135
|
-
const rules = buildRules(className, rule
|
|
136
|
-
|
|
137
|
-
|
|
222
|
+
const rules = buildRules(className, rule, keyframesMap);
|
|
223
|
+
for (const r of rules) {
|
|
224
|
+
sheet.insertRule(r, sheet.cssRules.length);
|
|
225
|
+
}
|
|
226
|
+
styleCache.set(className, true);
|
|
138
227
|
}
|
|
139
228
|
|
|
140
|
-
classNames
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (collectedRules.length) {
|
|
144
|
-
const el = ensureStyleElement();
|
|
145
|
-
for (const r of collectedRules) el.appendChild(document.createTextNode(r + "\n"));
|
|
229
|
+
(classNames as any)[slot] = className;
|
|
146
230
|
}
|
|
147
231
|
|
|
148
232
|
return classNames;
|
|
149
233
|
};
|
|
150
234
|
}
|
|
151
235
|
|
|
152
|
-
export const mergeClasses = (
|
|
153
|
-
classes
|
|
236
|
+
export const mergeClasses = (
|
|
237
|
+
...classes: (string | undefined | null | false)[]
|
|
238
|
+
) => classes.filter(Boolean).join(" ");
|
|
239
|
+
|
|
240
|
+
export function mergeStyleSets<T extends StylesMap>(
|
|
241
|
+
...sets: (Partial<T> | undefined)[]
|
|
242
|
+
): ClassNames<T> {
|
|
243
|
+
const merged: StylesMap = {};
|
|
154
244
|
|
|
155
|
-
export function mergeStyleSets<T extends StylesMap>(...sets: (T | undefined)[]) {
|
|
156
|
-
const merged: any = {};
|
|
157
245
|
for (const set of sets) {
|
|
158
246
|
if (!set) continue;
|
|
159
|
-
for (const
|
|
160
|
-
|
|
247
|
+
for (const key in set) {
|
|
248
|
+
if (!Object.prototype.hasOwnProperty.call(set, key)) continue;
|
|
249
|
+
|
|
250
|
+
const prev = (merged[key] as StyleRule) ?? {};
|
|
251
|
+
const cur = (set[key] as StyleRule) ?? {};
|
|
252
|
+
merged[key] = { ...prev, ...cur } as StyleRule;
|
|
161
253
|
}
|
|
162
254
|
}
|
|
163
|
-
|
|
164
|
-
|
|
255
|
+
|
|
256
|
+
return makeStyles(merged as T)();
|
|
257
|
+
}
|
package/src/tags.ts
CHANGED
|
@@ -1,77 +1,84 @@
|
|
|
1
|
-
import { derive, state
|
|
1
|
+
import { derive, state } from "./state";
|
|
2
|
+
import type { State } from "./state";
|
|
2
3
|
|
|
3
4
|
function isState(x: any): x is State<any> {
|
|
4
|
-
return
|
|
5
|
-
x &&
|
|
6
|
-
typeof x === "object" &&
|
|
7
|
-
"subs" in x &&
|
|
8
|
-
Object.prototype.hasOwnProperty.call(x, "val")
|
|
9
|
-
);
|
|
5
|
+
return x && typeof x === "object" && "subs" in x && "val" in x;
|
|
10
6
|
}
|
|
11
7
|
|
|
12
|
-
|
|
8
|
+
type Child = Node | string | number | null | undefined | State<any> | (() => any) | Child[];
|
|
9
|
+
|
|
10
|
+
export const tags: Record<string, (...children: any[]) => HTMLElement> =
|
|
13
11
|
new Proxy({}, {
|
|
14
12
|
get(_, tag: string) {
|
|
15
|
-
return (
|
|
13
|
+
return (...children: any[]): HTMLElement => {
|
|
16
14
|
const el = document.createElement(tag);
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
attrsOrChild &&
|
|
20
|
-
typeof attrsOrChild === "object" &&
|
|
21
|
-
!(attrsOrChild instanceof Node) &&
|
|
22
|
-
!isState(attrsOrChild) &&
|
|
23
|
-
typeof attrsOrChild !== "function"
|
|
24
|
-
) {
|
|
25
|
-
for (const [k, v] of Object.entries(attrsOrChild)) {
|
|
26
|
-
el.setAttribute(k, String(v));
|
|
27
|
-
}
|
|
28
|
-
} else if (attrsOrChild != null) {
|
|
29
|
-
children.unshift(attrsOrChild);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
for (let child of children.flat()) {
|
|
33
|
-
|
|
34
|
-
if (typeof child === "function") {
|
|
35
|
-
let node: Node = document.createTextNode("");
|
|
36
|
-
el.appendChild(node);
|
|
37
|
-
|
|
38
|
-
derive(() => {
|
|
39
|
-
const value = child();
|
|
40
|
-
|
|
41
|
-
const newNode: Node =
|
|
42
|
-
value instanceof Node
|
|
43
|
-
? value
|
|
44
|
-
: document.createTextNode(String(value ?? ""));
|
|
45
|
-
|
|
46
|
-
node.parentNode?.replaceChild(newNode, node);
|
|
47
|
-
node = newNode;
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (isState(child)) {
|
|
54
|
-
const node = document.createTextNode(String(child.val));
|
|
55
|
-
el.appendChild(node);
|
|
56
|
-
|
|
57
|
-
child.subs.add(() => {
|
|
58
|
-
node.textContent = String(child.val);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (child instanceof Node) {
|
|
65
|
-
el.appendChild(child);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
el.appendChild(
|
|
70
|
-
document.createTextNode(String(child))
|
|
71
|
-
);
|
|
72
|
-
}
|
|
16
|
+
processChildren(el, children);
|
|
73
17
|
|
|
74
18
|
return el;
|
|
75
19
|
};
|
|
76
20
|
}
|
|
77
21
|
});
|
|
22
|
+
|
|
23
|
+
function processChildren(el: HTMLElement, children: any[]) {
|
|
24
|
+
for (const child of children.flat()) {
|
|
25
|
+
if (child == null || child === false) continue;
|
|
26
|
+
|
|
27
|
+
if (typeof child === "function") {
|
|
28
|
+
mountReactiveFunction(el, child);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isState(child)) {
|
|
33
|
+
mountReactiveState(el, child);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (child instanceof Node) {
|
|
38
|
+
el.appendChild(child);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
el.appendChild(document.createTextNode(String(child)));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function mountReactiveFunction(el: HTMLElement, fn: () => any) {
|
|
47
|
+
let node: Node = document.createTextNode("");
|
|
48
|
+
el.appendChild(node);
|
|
49
|
+
|
|
50
|
+
derive(() => {
|
|
51
|
+
const value = fn();
|
|
52
|
+
|
|
53
|
+
if (value instanceof Node) {
|
|
54
|
+
if (node !== value) {
|
|
55
|
+
node.parentNode?.replaceChild(value, node);
|
|
56
|
+
node = value;
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const text = String(value ?? "");
|
|
62
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
63
|
+
if ((node as Text).data !== text) {
|
|
64
|
+
(node as Text).data = text;
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
const newNode = document.createTextNode(text);
|
|
68
|
+
node.parentNode?.replaceChild(newNode, node);
|
|
69
|
+
node = newNode;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function mountReactiveState(el: HTMLElement, st: State<any>) {
|
|
75
|
+
const node = document.createTextNode(String(st.val));
|
|
76
|
+
el.appendChild(node);
|
|
77
|
+
|
|
78
|
+
st.subs.add(() => {
|
|
79
|
+
const newVal = String(st.val);
|
|
80
|
+
if (node.data !== newVal) {
|
|
81
|
+
node.data = newVal;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|