@tailwind-styled/compiler 2.0.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/package.json +37 -0
- package/src/astParser.ts +397 -0
- package/src/astTransform.ts +359 -0
- package/src/atomicCss.ts +275 -0
- package/src/classExtractor.ts +69 -0
- package/src/classMerger.ts +45 -0
- package/src/componentGenerator.ts +91 -0
- package/src/componentHoister.ts +183 -0
- package/src/deadStyleEliminator.ts +398 -0
- package/src/incrementalEngine.ts +786 -0
- package/src/index.ts +100 -0
- package/src/loadTailwindConfig.ts +144 -0
- package/src/routeCssCollector.ts +188 -0
- package/src/rscAnalyzer.ts +240 -0
- package/src/safelistGenerator.ts +116 -0
- package/src/staticVariantCompiler.ts +240 -0
- package/src/styleBucketSystem.ts +498 -0
- package/src/styleRegistry.ts +462 -0
- package/src/tailwindEngine.ts +285 -0
- package/src/twDetector.ts +54 -0
- package/src/variantCompiler.ts +87 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tailwind-styled-v4 — Style Registry
|
|
3
|
+
*
|
|
4
|
+
* Global singleton yang menjadi pusat semua style yang di-generate compiler.
|
|
5
|
+
* Ini adalah jantung atomic CSS system.
|
|
6
|
+
*
|
|
7
|
+
* Fitur:
|
|
8
|
+
* - Deterministic class generation: hash("padding:16px") → "tw-p16" (selalu sama)
|
|
9
|
+
* - Cross-file deduplication: 2 file pakai p-4 → 1 class saja di CSS output
|
|
10
|
+
* - CSS layering engine: base → components → variants → utilities (seperti Tailwind)
|
|
11
|
+
* - Build cache: registry persist antar incremental build
|
|
12
|
+
*
|
|
13
|
+
* Pipeline:
|
|
14
|
+
* Compiler encounters tw.div`p-4 bg-blue-500`
|
|
15
|
+
* ↓ parse "p-4" → CSS property "padding: 1rem"
|
|
16
|
+
* ↓ registry.register("padding: 1rem") → "tw-a1" (atau existing if already seen)
|
|
17
|
+
* ↓ output: <div class="tw-a1 tw-b3" />
|
|
18
|
+
* ↓ at build end: extract all → styles.css
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// Types
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export type CssLayer = "tokens" | "base" | "components" | "variants" | "utilities"
|
|
26
|
+
|
|
27
|
+
export interface StyleEntry {
|
|
28
|
+
/** Original Tailwind class */
|
|
29
|
+
twClass: string
|
|
30
|
+
/** Generated atomic class name */
|
|
31
|
+
atomicClass: string
|
|
32
|
+
/** CSS declaration (e.g. "padding: 1rem") */
|
|
33
|
+
declaration: string
|
|
34
|
+
/** CSS selector modifier (e.g. ":hover", "@media (min-width: 640px)") */
|
|
35
|
+
modifier?: string
|
|
36
|
+
/** Layer for ordering */
|
|
37
|
+
layer: CssLayer
|
|
38
|
+
/** How many times this class was referenced (for dead code analysis) */
|
|
39
|
+
refCount: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RegistryStats {
|
|
43
|
+
totalEntries: number
|
|
44
|
+
totalRefCount: number
|
|
45
|
+
layerCounts: Record<CssLayer, number>
|
|
46
|
+
estimatedCssKb: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
|
+
// Deterministic Hash — based on declaration + modifier
|
|
51
|
+
//
|
|
52
|
+
// Requirements:
|
|
53
|
+
// - Same input → always same output (deterministic)
|
|
54
|
+
// - Stable across builds (not random)
|
|
55
|
+
// - Short output (minimize CSS selector length)
|
|
56
|
+
// - Collision safe for practical CSS property range
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
const BASE36_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* FNV-1a hash — fast, deterministic, good distribution.
|
|
63
|
+
* Produces same result for same input string across all runs.
|
|
64
|
+
*/
|
|
65
|
+
function fnv1a(str: string): number {
|
|
66
|
+
let hash = 2166136261 // FNV offset basis
|
|
67
|
+
for (let i = 0; i < str.length; i++) {
|
|
68
|
+
hash ^= str.charCodeAt(i)
|
|
69
|
+
// FNV prime multiply (32-bit)
|
|
70
|
+
hash = (hash * 16777619) >>> 0
|
|
71
|
+
}
|
|
72
|
+
return hash
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convert number to base36 string (compact class names).
|
|
77
|
+
* 6 digits base36 = 2.18 billion unique classes (more than enough)
|
|
78
|
+
*/
|
|
79
|
+
function toBase36(n: number, length = 4): string {
|
|
80
|
+
let result = ""
|
|
81
|
+
let num = n
|
|
82
|
+
for (let i = 0; i < length; i++) {
|
|
83
|
+
result = BASE36_CHARS[num % 36] + result
|
|
84
|
+
num = Math.floor(num / 36)
|
|
85
|
+
}
|
|
86
|
+
return result
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generate a deterministic, stable atomic class name.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* generateAtomicClass("padding: 1rem") → "tw-p16k"
|
|
94
|
+
* generateAtomicClass("padding: 1rem", ":hover") → "tw-h2p8"
|
|
95
|
+
*/
|
|
96
|
+
export function generateAtomicClass(declaration: string, modifier?: string): string {
|
|
97
|
+
const key = modifier ? `${declaration}::${modifier}` : declaration
|
|
98
|
+
return `tw-${toBase36(fnv1a(key))}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
102
|
+
// Style Registry — singleton per build
|
|
103
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
export class StyleRegistry {
|
|
106
|
+
private entries = new Map<string, StyleEntry>() // key: declaration::modifier
|
|
107
|
+
private twClassMap = new Map<string, string>() // twClass → atomicClass
|
|
108
|
+
private atomicToEntry = new Map<string, StyleEntry>() // atomicClass → entry
|
|
109
|
+
|
|
110
|
+
// ── Registration ────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Register a CSS declaration and get its atomic class.
|
|
114
|
+
* If already registered, increments refCount and returns existing class.
|
|
115
|
+
*
|
|
116
|
+
* @param twClass - Original Tailwind class (for debug/devtools)
|
|
117
|
+
* @param declaration - CSS declaration ("padding: 1rem")
|
|
118
|
+
* @param modifier - Optional modifier (":hover", "@media ...")
|
|
119
|
+
* @param layer - CSS layer for ordering
|
|
120
|
+
*/
|
|
121
|
+
register(
|
|
122
|
+
twClass: string,
|
|
123
|
+
declaration: string,
|
|
124
|
+
modifier?: string,
|
|
125
|
+
layer: CssLayer = "utilities"
|
|
126
|
+
): string {
|
|
127
|
+
const key = modifier ? `${declaration}::${modifier}` : declaration
|
|
128
|
+
|
|
129
|
+
// Return existing
|
|
130
|
+
if (this.entries.has(key)) {
|
|
131
|
+
const entry = this.entries.get(key)!
|
|
132
|
+
entry.refCount++
|
|
133
|
+
return entry.atomicClass
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Generate new atomic class
|
|
137
|
+
const atomicClass = generateAtomicClass(declaration, modifier)
|
|
138
|
+
const entry: StyleEntry = {
|
|
139
|
+
twClass,
|
|
140
|
+
atomicClass,
|
|
141
|
+
declaration,
|
|
142
|
+
modifier,
|
|
143
|
+
layer,
|
|
144
|
+
refCount: 1,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.entries.set(key, entry)
|
|
148
|
+
this.twClassMap.set(twClass, atomicClass)
|
|
149
|
+
this.atomicToEntry.set(atomicClass, entry)
|
|
150
|
+
|
|
151
|
+
return atomicClass
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Register multiple Tailwind classes at once.
|
|
156
|
+
* Returns space-separated atomic class string.
|
|
157
|
+
*/
|
|
158
|
+
registerClasses(twClasses: string, layer: CssLayer = "utilities"): string {
|
|
159
|
+
const parts = twClasses.split(/\s+/).filter(Boolean)
|
|
160
|
+
const atomicClasses: string[] = []
|
|
161
|
+
|
|
162
|
+
for (const cls of parts) {
|
|
163
|
+
// Parse modifier (hover:, md:, focus:, etc.)
|
|
164
|
+
const colonIdx = cls.lastIndexOf(":")
|
|
165
|
+
if (colonIdx > 0) {
|
|
166
|
+
const mod = cls.slice(0, colonIdx)
|
|
167
|
+
const base = cls.slice(colonIdx + 1)
|
|
168
|
+
const decl = this.twToDeclaration(base)
|
|
169
|
+
if (decl) {
|
|
170
|
+
atomicClasses.push(this.register(cls, decl, this.modifierToSelector(mod), layer))
|
|
171
|
+
} else {
|
|
172
|
+
atomicClasses.push(cls) // unknown — pass through
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
const decl = this.twToDeclaration(cls)
|
|
176
|
+
if (decl) {
|
|
177
|
+
atomicClasses.push(this.register(cls, decl, undefined, layer))
|
|
178
|
+
} else {
|
|
179
|
+
atomicClasses.push(cls) // unknown — pass through
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return atomicClasses.join(" ")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── CSS Generation ───────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Generate full CSS output, ordered by layer.
|
|
191
|
+
* Layer order: tokens → base → components → variants → utilities
|
|
192
|
+
*
|
|
193
|
+
* This ensures predictable specificity — same as Tailwind's approach.
|
|
194
|
+
*/
|
|
195
|
+
generateCss(opts: { minify?: boolean; includeComments?: boolean } = {}): string {
|
|
196
|
+
const { minify = false, includeComments = !minify } = opts
|
|
197
|
+
|
|
198
|
+
const layerOrder: CssLayer[] = ["tokens", "base", "components", "variants", "utilities"]
|
|
199
|
+
const sections: string[] = []
|
|
200
|
+
|
|
201
|
+
for (const layer of layerOrder) {
|
|
202
|
+
const layerEntries = Array.from(this.entries.values()).filter((e) => e.layer === layer)
|
|
203
|
+
|
|
204
|
+
if (layerEntries.length === 0) continue
|
|
205
|
+
|
|
206
|
+
if (includeComments) {
|
|
207
|
+
sections.push(`/* ── ${layer} ── */`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Separate regular rules from modifier rules
|
|
211
|
+
const regular = layerEntries.filter((e) => !e.modifier || !e.modifier.startsWith("@"))
|
|
212
|
+
const atRules = layerEntries.filter((e) => e.modifier?.startsWith("@"))
|
|
213
|
+
const pseudo = layerEntries.filter((e) => e.modifier && !e.modifier.startsWith("@"))
|
|
214
|
+
|
|
215
|
+
for (const entry of regular) {
|
|
216
|
+
sections.push(this.entryToCss(entry, minify))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const entry of pseudo) {
|
|
220
|
+
sections.push(this.entryToCss(entry, minify))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Group @media rules by query
|
|
224
|
+
const mediaGroups = new Map<string, StyleEntry[]>()
|
|
225
|
+
for (const entry of atRules) {
|
|
226
|
+
const key = entry.modifier!
|
|
227
|
+
if (!mediaGroups.has(key)) mediaGroups.set(key, [])
|
|
228
|
+
mediaGroups.get(key)!.push(entry)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const [query, entries] of mediaGroups) {
|
|
232
|
+
const inner = entries.map((e) => this.entryToCss(e, minify, " ")).join(minify ? "" : "\n")
|
|
233
|
+
sections.push(`${query} {\n${inner}\n}`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return sections.join(minify ? "" : "\n\n")
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private entryToCss(entry: StyleEntry, minify: boolean, indent = ""): string {
|
|
241
|
+
const selector =
|
|
242
|
+
entry.modifier && !entry.modifier.startsWith("@")
|
|
243
|
+
? `.${entry.atomicClass}${entry.modifier}`
|
|
244
|
+
: `.${entry.atomicClass}`
|
|
245
|
+
|
|
246
|
+
if (minify) {
|
|
247
|
+
return `${selector}{${entry.declaration}}`
|
|
248
|
+
}
|
|
249
|
+
return `${indent}${selector} { ${entry.declaration} }`
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Lookup ───────────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
getAtomicClass(twClass: string): string | undefined {
|
|
255
|
+
return this.twClassMap.get(twClass)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
getEntry(atomicClass: string): StyleEntry | undefined {
|
|
259
|
+
return this.atomicToEntry.get(atomicClass)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
getAllEntries(): StyleEntry[] {
|
|
263
|
+
return Array.from(this.entries.values())
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
stats(): RegistryStats {
|
|
267
|
+
const counts: Record<CssLayer, number> = {
|
|
268
|
+
tokens: 0,
|
|
269
|
+
base: 0,
|
|
270
|
+
components: 0,
|
|
271
|
+
variants: 0,
|
|
272
|
+
utilities: 0,
|
|
273
|
+
}
|
|
274
|
+
let totalRef = 0
|
|
275
|
+
for (const e of this.entries.values()) {
|
|
276
|
+
counts[e.layer]++
|
|
277
|
+
totalRef += e.refCount
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
totalEntries: this.entries.size,
|
|
281
|
+
totalRefCount: totalRef,
|
|
282
|
+
layerCounts: counts,
|
|
283
|
+
estimatedCssKb: this.entries.size * 0.04, // ~40 bytes per rule avg
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
clear(): void {
|
|
288
|
+
this.entries.clear()
|
|
289
|
+
this.twClassMap.clear()
|
|
290
|
+
this.atomicToEntry.clear()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Tailwind class → CSS declaration mapping ─────────────────────────────
|
|
294
|
+
|
|
295
|
+
private modifierToSelector(mod: string): string {
|
|
296
|
+
const pseudo: Record<string, string> = {
|
|
297
|
+
hover: ":hover",
|
|
298
|
+
focus: ":focus",
|
|
299
|
+
"focus-visible": ":focus-visible",
|
|
300
|
+
active: ":active",
|
|
301
|
+
disabled: ":disabled",
|
|
302
|
+
visited: ":visited",
|
|
303
|
+
checked: ":checked",
|
|
304
|
+
placeholder: "::placeholder",
|
|
305
|
+
before: "::before",
|
|
306
|
+
after: "::after",
|
|
307
|
+
first: ":first-child",
|
|
308
|
+
last: ":last-child",
|
|
309
|
+
odd: ":nth-child(odd)",
|
|
310
|
+
even: ":nth-child(even)",
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const responsive: Record<string, string> = {
|
|
314
|
+
sm: "@media (min-width: 640px)",
|
|
315
|
+
md: "@media (min-width: 768px)",
|
|
316
|
+
lg: "@media (min-width: 1024px)",
|
|
317
|
+
xl: "@media (min-width: 1280px)",
|
|
318
|
+
"2xl": "@media (min-width: 1536px)",
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return pseudo[mod] ?? responsive[mod] ?? `:${mod}`
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private twToDeclaration(cls: string): string | null {
|
|
325
|
+
// Spacing
|
|
326
|
+
const spacingRe = /^(p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap)-(\d+(?:\.\d+)?)$/
|
|
327
|
+
const sMatch = cls.match(spacingRe)
|
|
328
|
+
if (sMatch) {
|
|
329
|
+
const [, prefix, val] = sMatch
|
|
330
|
+
const props: Record<string, string> = {
|
|
331
|
+
p: "padding",
|
|
332
|
+
px: "padding-inline",
|
|
333
|
+
py: "padding-block",
|
|
334
|
+
pt: "padding-top",
|
|
335
|
+
pb: "padding-bottom",
|
|
336
|
+
pl: "padding-left",
|
|
337
|
+
pr: "padding-right",
|
|
338
|
+
m: "margin",
|
|
339
|
+
mx: "margin-inline",
|
|
340
|
+
my: "margin-block",
|
|
341
|
+
mt: "margin-top",
|
|
342
|
+
mb: "margin-bottom",
|
|
343
|
+
ml: "margin-left",
|
|
344
|
+
mr: "margin-right",
|
|
345
|
+
gap: "gap",
|
|
346
|
+
}
|
|
347
|
+
return `${props[prefix]}: ${parseFloat(val) * 0.25}rem`
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Opacity
|
|
351
|
+
const opacityMatch = cls.match(/^opacity-(\d+)$/)
|
|
352
|
+
if (opacityMatch) return `opacity: ${parseInt(opacityMatch[1], 10) / 100}`
|
|
353
|
+
|
|
354
|
+
// Z-index
|
|
355
|
+
const zMatch = cls.match(/^z-(\d+)$/)
|
|
356
|
+
if (zMatch) return `z-index: ${zMatch[1]}`
|
|
357
|
+
|
|
358
|
+
// Display
|
|
359
|
+
const display: Record<string, string> = {
|
|
360
|
+
block: "display: block",
|
|
361
|
+
"inline-block": "display: inline-block",
|
|
362
|
+
flex: "display: flex",
|
|
363
|
+
"inline-flex": "display: inline-flex",
|
|
364
|
+
grid: "display: grid",
|
|
365
|
+
hidden: "display: none",
|
|
366
|
+
table: "display: table",
|
|
367
|
+
}
|
|
368
|
+
if (display[cls]) return display[cls]
|
|
369
|
+
|
|
370
|
+
// Flex
|
|
371
|
+
const flex: Record<string, string> = {
|
|
372
|
+
"flex-row": "flex-direction: row",
|
|
373
|
+
"flex-col": "flex-direction: column",
|
|
374
|
+
"flex-wrap": "flex-wrap: wrap",
|
|
375
|
+
"flex-nowrap": "flex-wrap: nowrap",
|
|
376
|
+
"flex-1": "flex: 1 1 0%",
|
|
377
|
+
"flex-auto": "flex: 1 1 auto",
|
|
378
|
+
"flex-none": "flex: none",
|
|
379
|
+
"items-center": "align-items: center",
|
|
380
|
+
"items-start": "align-items: flex-start",
|
|
381
|
+
"items-end": "align-items: flex-end",
|
|
382
|
+
"items-stretch": "align-items: stretch",
|
|
383
|
+
"justify-center": "justify-content: center",
|
|
384
|
+
"justify-start": "justify-content: flex-start",
|
|
385
|
+
"justify-end": "justify-content: flex-end",
|
|
386
|
+
"justify-between": "justify-content: space-between",
|
|
387
|
+
"justify-around": "justify-content: space-around",
|
|
388
|
+
"justify-evenly": "justify-content: space-evenly",
|
|
389
|
+
}
|
|
390
|
+
if (flex[cls]) return flex[cls]
|
|
391
|
+
|
|
392
|
+
// Position
|
|
393
|
+
const pos: Record<string, string> = {
|
|
394
|
+
relative: "position: relative",
|
|
395
|
+
absolute: "position: absolute",
|
|
396
|
+
fixed: "position: fixed",
|
|
397
|
+
sticky: "position: sticky",
|
|
398
|
+
static: "position: static",
|
|
399
|
+
"inset-0": "inset: 0",
|
|
400
|
+
"inset-x-0": "inset-inline: 0",
|
|
401
|
+
"inset-y-0": "inset-block: 0",
|
|
402
|
+
}
|
|
403
|
+
if (pos[cls]) return pos[cls]
|
|
404
|
+
|
|
405
|
+
// Width/Height
|
|
406
|
+
const wMatch = cls.match(/^w-(.+)$/)
|
|
407
|
+
if (wMatch) return `width: ${sizeVal(wMatch[1])}`
|
|
408
|
+
const hMatch = cls.match(/^h-(.+)$/)
|
|
409
|
+
if (hMatch) return `height: ${sizeVal(hMatch[1])}`
|
|
410
|
+
|
|
411
|
+
// Border radius
|
|
412
|
+
const rrMap: Record<string, string> = {
|
|
413
|
+
"rounded-none": "border-radius: 0",
|
|
414
|
+
"rounded-sm": "border-radius: 0.125rem",
|
|
415
|
+
rounded: "border-radius: 0.25rem",
|
|
416
|
+
"rounded-md": "border-radius: 0.375rem",
|
|
417
|
+
"rounded-lg": "border-radius: 0.5rem",
|
|
418
|
+
"rounded-xl": "border-radius: 0.75rem",
|
|
419
|
+
"rounded-2xl": "border-radius: 1rem",
|
|
420
|
+
"rounded-3xl": "border-radius: 1.5rem",
|
|
421
|
+
"rounded-full": "border-radius: 9999px",
|
|
422
|
+
}
|
|
423
|
+
if (rrMap[cls]) return rrMap[cls]
|
|
424
|
+
|
|
425
|
+
return null
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
430
|
+
// Singleton
|
|
431
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
let _globalRegistry: StyleRegistry | null = null
|
|
434
|
+
|
|
435
|
+
export function getStyleRegistry(): StyleRegistry {
|
|
436
|
+
if (!_globalRegistry) _globalRegistry = new StyleRegistry()
|
|
437
|
+
return _globalRegistry
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function resetStyleRegistry(): void {
|
|
441
|
+
_globalRegistry = new StyleRegistry()
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
445
|
+
// Helper
|
|
446
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
function sizeVal(v: string): string {
|
|
449
|
+
const num = parseFloat(v)
|
|
450
|
+
if (!Number.isNaN(num)) return `${num * 0.25}rem`
|
|
451
|
+
const special: Record<string, string> = {
|
|
452
|
+
full: "100%",
|
|
453
|
+
screen: "100vw",
|
|
454
|
+
svh: "100svh",
|
|
455
|
+
svw: "100svw",
|
|
456
|
+
auto: "auto",
|
|
457
|
+
min: "min-content",
|
|
458
|
+
max: "max-content",
|
|
459
|
+
fit: "fit-content",
|
|
460
|
+
}
|
|
461
|
+
return special[v] ?? v
|
|
462
|
+
}
|