uraniyum 1.1.10 → 1.2.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.
package/src/styles.ts CHANGED
@@ -1,242 +1,159 @@
1
1
  export type CSSValue = string | number | (() => string | number);
2
2
 
3
- export interface CSSProperties {
4
- [key: string]: CSSValue | CSSProperties | undefined;
5
- }
6
-
7
3
  export interface StyleRule {
8
- [key: string]: CSSValue | CSSProperties | undefined;
4
+ [key: string]: CSSValue | StyleRule | undefined;
9
5
  }
10
6
 
11
7
  export type StylesMap = Record<string, StyleRule>;
12
8
  export type ClassNames<T extends StylesMap> = { [K in keyof T]: string };
13
9
 
14
- const styleCache = new Map<string, true>();
15
- const keyframeCache = new Map<string, string>();
16
10
  let styleElement: HTMLStyleElement | null = null;
17
11
  let styleSheet: CSSStyleSheet | null = null;
18
12
 
19
- const ALLOWED_PREFIXES = [
20
- "root",
21
- "button",
22
- "icon",
23
- "text",
24
- "container",
25
- "wrapper",
26
- "card",
27
- "header",
28
- "section",
29
- ];
30
-
31
- const KEYFRAMES_PREFIX = "@keyframes ";
32
-
33
13
  function ensureStyleSheet(): CSSStyleSheet {
34
14
  if (!styleSheet) {
35
- if (!styleElement) {
36
- styleElement = document.createElement("style");
37
- styleElement.id = "styles";
38
- document.head.appendChild(styleElement);
39
- }
15
+ styleElement = document.createElement("style");
16
+ styleElement.id = "styles";
17
+ document.head.appendChild(styleElement);
40
18
  styleSheet = styleElement.sheet as CSSStyleSheet;
41
19
  }
42
20
  return styleSheet;
43
21
  }
44
22
 
45
- function hashString(str: string): string {
46
- let hash = 5381;
47
- for (let i = 0; i < str.length; i++) {
48
- hash = (hash * 33) ^ str.charCodeAt(i);
49
- }
50
- return (hash >>> 0).toString(36);
51
- }
52
-
53
23
  const camelToKebabCache = new Map<string, string>();
54
24
 
55
25
  function camelToKebab(str: string): string {
56
26
  const cached = camelToKebabCache.get(str);
57
27
  if (cached) return cached;
58
-
59
- const result = str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
60
- camelToKebabCache.set(str, result);
61
- return result;
28
+ const res = str.replace(/[A-Z]/g, m => "-" + m.toLowerCase());
29
+ camelToKebabCache.set(str, res);
30
+ return res;
62
31
  }
63
32
 
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}`);
78
- }
79
-
80
- parts.sort();
81
- return hashString(parts.join("|"));
33
+ function resolveValue(v: CSSValue): string | number {
34
+ return typeof v === "function" ? v() : v;
82
35
  }
83
36
 
84
- function stringifyDecls(
85
- props: Record<string, any>,
86
- keyframes: Map<string, string>,
87
- ): string {
88
- const out: string[] = [];
37
+ const PRECEDENCE_LAYERS = 3;
38
+ let atomicCounter = 0;
89
39
 
90
- for (const key in props) {
91
- if (!Object.prototype.hasOwnProperty.call(props, key)) continue;
92
- const raw = props[key];
93
-
94
- if (raw == null || typeof raw === "object") continue;
95
-
96
- let val: string | number =
97
- typeof raw === "function" ? raw() : (raw as string | number);
40
+ function encode(n: number): string {
41
+ return n.toString(36);
42
+ }
98
43
 
99
- if (typeof val === "string" && keyframes.size > 0) {
100
- for (const [orig, uniq] of keyframes) {
101
- if (val.includes(`$${orig}`)) {
102
- val = val.replaceAll(`$${orig}`, uniq);
103
- }
104
- }
105
- }
44
+ function makeClassName(layer: number): string {
45
+ // looks like f1phragk
46
+ return `f${layer}${encode(atomicCounter++).padStart(5, "0")}`;
47
+ }
106
48
 
107
- out.push(`${camelToKebab(key)}:${val}`);
108
- }
49
+ type AtomicSignature = string;
109
50
 
110
- return out.join(";");
111
- }
51
+ const atomicCache = new Map<AtomicSignature, string[]>();
112
52
 
113
- function registerKeyframes(
114
- name: string,
115
- frames: Record<string, any>,
116
- ): string {
117
- const hash = hashString(name + JSON.stringify(frames));
118
- const unique = `${name}-${hash}`;
53
+ function getAtomicClasses(
54
+ selector: string,
55
+ decl: string,
56
+ ): string[] {
57
+ const key = `${selector}|${decl}`;
119
58
 
120
- const cached = keyframeCache.get(unique);
59
+ const cached = atomicCache.get(key);
121
60
  if (cached) return cached;
122
61
 
62
+ const classes: string[] = [];
123
63
  const sheet = ensureStyleSheet();
124
64
 
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
- }
65
+ for (let layer = 0; layer < PRECEDENCE_LAYERS; layer++) {
66
+ const className = makeClassName(layer);
67
+ classes.push(className);
68
+
69
+ const css =
70
+ selector === "&"
71
+ ? `.${className}{${decl}}`
72
+ : selector.replace(/&/g, `.${className}`) + `{${decl}}`;
131
73
 
132
- const ruleText = `@keyframes ${unique}{${steps.join("")}}`;
133
- sheet.insertRule(ruleText, sheet.cssRules.length);
134
- keyframeCache.set(unique, unique);
74
+ sheet.insertRule(css, sheet.cssRules.length);
75
+ }
135
76
 
136
- return unique;
77
+ atomicCache.set(key, classes);
78
+ return classes;
137
79
  }
138
80
 
139
- function buildRules(
140
- className: string,
141
- rule: Record<string, any>,
142
- keyframes: Map<string, string>,
143
- ): string[] {
144
- const rules: string[] = [];
145
- const baseDecls: string[] = [];
81
+ type AtomicEntry = {
82
+ classes: string[];
83
+ prop: string;
84
+ };
85
+
86
+ function buildAtomicEntries(rule: StyleRule): AtomicEntry[] {
87
+ const entries: AtomicEntry[] = [];
146
88
 
147
89
  for (const key in rule) {
148
- if (!Object.prototype.hasOwnProperty.call(rule, key)) continue;
149
90
  const val = rule[key];
91
+ if (val == null) continue;
150
92
 
151
- if (key.startsWith("@keyframes")) {
152
- continue;
153
- }
154
-
155
- if (!val) continue;
93
+ if (key.startsWith(":") && typeof val === "object") {
94
+ for (const prop in val) {
95
+ const v = val[prop];
96
+ if (v == null || typeof v === "object") continue;
156
97
 
157
- if (key[0] === ":" && typeof val === "object") {
158
- const decls = stringifyDecls(val ?? {}, keyframes);
159
- if (decls) {
160
- rules.push(`.${className}${key}{${decls}}`);
98
+ const decl = `${camelToKebab(prop)}:${resolveValue(v)}`;
99
+ const classes = getAtomicClasses(`&${key}`, decl);
100
+ entries.push({ classes, prop });
161
101
  }
162
102
  continue;
163
103
  }
164
104
 
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;
105
+ if (typeof val !== "object") {
106
+ const decl = `${camelToKebab(key)}:${resolveValue(val)}`;
107
+ const classes = getAtomicClasses("&", decl);
108
+ entries.push({ classes, prop: key });
171
109
  }
110
+ }
172
111
 
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
- }
112
+ return entries;
113
+ }
181
114
 
182
- if (!key.startsWith("@") && typeof val !== "object") {
183
- const raw = typeof val === "function" ? val() : val;
184
- baseDecls.push(`${camelToKebab(key)}:${raw}`);
185
- }
186
- }
115
+ export function mergeClasses(
116
+ ...inputs: (string | undefined | null | false)[]
117
+ ): string {
118
+ const perLayer: string[][] = [];
187
119
 
188
- if (baseDecls.length > 0) {
189
- rules.push(`.${className}{${baseDecls.join(";")}}`);
120
+ for (let i = 0; i < PRECEDENCE_LAYERS; i++) {
121
+ perLayer[i] = [];
190
122
  }
191
123
 
192
- return rules;
124
+ inputs.forEach((input, index) => {
125
+ if (typeof input !== "string") return;
126
+
127
+ const layer = Math.min(index, PRECEDENCE_LAYERS - 1);
128
+
129
+ const bucket = perLayer[layer];
130
+ if (!bucket) return;
131
+
132
+ const parts = input.trim().split(/\s+/);
133
+ for (const cls of parts) {
134
+ if (cls) bucket.push(cls);
135
+ }
136
+ });
137
+
138
+ return perLayer.flat().join(" ");
193
139
  }
194
140
 
195
141
  export function makeStyles<T extends StylesMap>(styles: T) {
196
142
  return (): ClassNames<T> => {
197
- const classNames = {} as ClassNames<T>;
198
- const keyframesMap = new Map<string, string>();
199
- const sheet = ensureStyleSheet();
200
-
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);
209
- }
143
+ const result = {} as ClassNames<T>;
210
144
 
211
145
  for (const slot in styles) {
212
- if (!Object.prototype.hasOwnProperty.call(styles, slot)) continue;
213
- if (slot.startsWith(KEYFRAMES_PREFIX)) continue;
214
-
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}`;
220
-
221
- if (!styleCache.has(className)) {
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);
227
- }
146
+ const rule = (styles[slot] ?? {}) as StyleRule;
147
+ const entries = buildAtomicEntries(rule);
228
148
 
229
- (classNames as any)[slot] = className;
149
+ const classes = entries.map(e => e.classes[0]).join(" ");
150
+ (result as any)[slot] = classes;
230
151
  }
231
152
 
232
- return classNames;
153
+ return result;
233
154
  };
234
155
  }
235
156
 
236
- export const mergeClasses = (
237
- ...classes: (string | undefined | null | false)[]
238
- ) => classes.filter(Boolean).join(" ");
239
-
240
157
  export function mergeStyleSets<T extends StylesMap>(
241
158
  ...sets: (Partial<T> | undefined)[]
242
159
  ): ClassNames<T> {
@@ -245,13 +162,12 @@ export function mergeStyleSets<T extends StylesMap>(
245
162
  for (const set of sets) {
246
163
  if (!set) continue;
247
164
  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;
165
+ merged[key] = {
166
+ ...(merged[key] as StyleRule),
167
+ ...(set[key] as StyleRule),
168
+ };
253
169
  }
254
170
  }
255
171
 
256
172
  return makeStyles(merged as T)();
257
- }
173
+ }