mnfst 0.5.14

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.
Files changed (47) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +58 -0
  3. package/dist/manifest.accordion.css +81 -0
  4. package/dist/manifest.appwrite.auth.js +6247 -0
  5. package/dist/manifest.appwrite.data.js +1586 -0
  6. package/dist/manifest.appwrite.presence.js +1845 -0
  7. package/dist/manifest.avatar.css +113 -0
  8. package/dist/manifest.button.css +79 -0
  9. package/dist/manifest.checkbox.css +58 -0
  10. package/dist/manifest.code.css +453 -0
  11. package/dist/manifest.code.js +958 -0
  12. package/dist/manifest.code.min.css +1 -0
  13. package/dist/manifest.components.js +737 -0
  14. package/dist/manifest.css +3124 -0
  15. package/dist/manifest.data.js +11413 -0
  16. package/dist/manifest.dialog.css +130 -0
  17. package/dist/manifest.divider.css +77 -0
  18. package/dist/manifest.dropdown.css +278 -0
  19. package/dist/manifest.dropdowns.js +378 -0
  20. package/dist/manifest.form.css +169 -0
  21. package/dist/manifest.icons.js +161 -0
  22. package/dist/manifest.input.css +129 -0
  23. package/dist/manifest.js +302 -0
  24. package/dist/manifest.localization.js +571 -0
  25. package/dist/manifest.markdown.js +738 -0
  26. package/dist/manifest.min.css +1 -0
  27. package/dist/manifest.radio.css +38 -0
  28. package/dist/manifest.resize.css +233 -0
  29. package/dist/manifest.resize.js +442 -0
  30. package/dist/manifest.router.js +1207 -0
  31. package/dist/manifest.sidebar.css +102 -0
  32. package/dist/manifest.slides.css +80 -0
  33. package/dist/manifest.slides.js +173 -0
  34. package/dist/manifest.switch.css +44 -0
  35. package/dist/manifest.table.css +74 -0
  36. package/dist/manifest.tabs.js +273 -0
  37. package/dist/manifest.tailwind.js +578 -0
  38. package/dist/manifest.theme.css +119 -0
  39. package/dist/manifest.themes.js +109 -0
  40. package/dist/manifest.toast.css +92 -0
  41. package/dist/manifest.toasts.js +285 -0
  42. package/dist/manifest.tooltip.css +156 -0
  43. package/dist/manifest.tooltips.js +331 -0
  44. package/dist/manifest.typography.css +341 -0
  45. package/dist/manifest.utilities.css +399 -0
  46. package/dist/manifest.utilities.js +3197 -0
  47. package/package.json +63 -0
@@ -0,0 +1,3197 @@
1
+ // Utility generators
2
+ // Functions that generate CSS utilities from CSS variable suffixes
3
+
4
+ function createUtilityGenerators() {
5
+ return {
6
+ 'color-': (suffix, value) => {
7
+ const utilities = [];
8
+ const addUtility = (prefix, property, baseValue) => {
9
+ utilities.push([`${prefix}-${suffix}`, `${property}: ${baseValue}`]);
10
+ };
11
+ addUtility('text', 'color', value);
12
+ addUtility('bg', 'background-color', value);
13
+ addUtility('border', 'border-color', value);
14
+ addUtility('outline', 'outline-color', value);
15
+ addUtility('ring', 'box-shadow', `0 0 0 1px ${value}`);
16
+ addUtility('fill', 'fill', value);
17
+ addUtility('stroke', 'stroke', value);
18
+ addUtility('decoration', 'text-decoration-color', value);
19
+ addUtility('accent', 'accent-color', value);
20
+ addUtility('caret', 'caret-color', value);
21
+ return utilities;
22
+ },
23
+ 'font-': (suffix, value) => [
24
+ [`font-${suffix}`, `font-family: ${value}`]
25
+ ],
26
+ 'text-': (suffix, value) => [
27
+ [`text-${suffix}`, `font-size: ${value}`]
28
+ ],
29
+ 'font-weight-': (suffix, value) => [
30
+ [`font-${suffix}`, `font-weight: ${value}`]
31
+ ],
32
+ 'tracking-': (suffix, value) => [
33
+ [`tracking-${suffix}`, `letter-spacing: ${value}`]
34
+ ],
35
+ 'leading-': (suffix, value) => [
36
+ [`leading-${suffix}`, `line-height: ${value}`]
37
+ ],
38
+ 'breakpoint-': (suffix, value) => [
39
+ [`@${suffix}`, `@media (min-width: ${value})`]
40
+ ],
41
+ 'container-': (suffix, value) => [
42
+ [`container-${suffix}`, `max-width: ${value}`],
43
+ [`@container-${suffix}`, `@container (min-width: ${value})`]
44
+ ],
45
+ 'spacing-': (suffix, value) => [
46
+ [`gap-${suffix}`, `gap: ${value}`],
47
+ [`p-${suffix}`, `padding: ${value}`],
48
+ [`px-${suffix}`, `padding-left: ${value}; padding-right: ${value}`],
49
+ [`py-${suffix}`, `padding-top: ${value}; padding-bottom: ${value}`],
50
+ [`m-${suffix}`, `margin: ${value}`],
51
+ [`mx-${suffix}`, `margin-left: ${value}; margin-right: ${value}`],
52
+ [`my-${suffix}`, `margin-top: ${value}; margin-bottom: ${value}`],
53
+ [`space-x-${suffix}`, `> * + * { margin-left: ${value}; }`],
54
+ [`space-y-${suffix}`, `> * + * { margin-top: ${value}; }`],
55
+ [`max-w-${suffix}`, `max-width: ${value}`],
56
+ [`max-h-${suffix}`, `max-height: ${value}`],
57
+ [`min-w-${suffix}`, `min-width: ${value}`],
58
+ [`min-h-${suffix}`, `min-height: ${value}`],
59
+ [`w-${suffix}`, `width: ${value}`],
60
+ [`h-${suffix}`, `height: ${value}`]
61
+ ],
62
+ 'radius-': (suffix, value) => [
63
+ [`rounded-${suffix}`, `border-radius: ${value}`]
64
+ ],
65
+ 'shadow-': (suffix, value) => [
66
+ [`shadow-${suffix}`, `box-shadow: ${value}`]
67
+ ],
68
+ 'inset-shadow-': (suffix, value) => [
69
+ [`inset-shadow-${suffix}`, `box-shadow: inset ${value}`]
70
+ ],
71
+ 'drop-shadow-': (suffix, value) => [
72
+ [`drop-shadow-${suffix}`, `filter: drop-shadow(${value})`]
73
+ ],
74
+ 'blur-': (suffix, value) => [
75
+ [`blur-${suffix}`, `filter: blur(${value})`]
76
+ ],
77
+ 'perspective-': (suffix, value) => [
78
+ [`perspective-${suffix}`, `perspective: ${value}`]
79
+ ],
80
+ 'aspect-': (suffix, value) => [
81
+ [`aspect-${suffix}`, `aspect-ratio: ${value}`]
82
+ ],
83
+ 'ease-': (suffix, value) => [
84
+ [`ease-${suffix}`, `transition-timing-function: ${value}`]
85
+ ],
86
+ 'animate-': (suffix, value) => [
87
+ [`animate-${suffix}`, `animation: ${value}`]
88
+ ],
89
+ 'border-width-': (suffix, value) => [
90
+ [`border-${suffix}`, `border-width: ${value}`]
91
+ ],
92
+ 'border-style-': (suffix, value) => [
93
+ [`border-${suffix}`, `border-style: ${value}`]
94
+ ],
95
+ 'outline-': (suffix, value) => [
96
+ [`outline-${suffix}`, `outline-color: ${value}`]
97
+ ],
98
+ 'outline-width-': (suffix, value) => [
99
+ [`outline-${suffix}`, `outline-width: ${value}`]
100
+ ],
101
+ 'outline-style-': (suffix, value) => [
102
+ [`outline-${suffix}`, `outline-style: ${value}`]
103
+ ],
104
+ 'ring-': (suffix, value) => [
105
+ [`ring-${suffix}`, `box-shadow: 0 0 0 ${value} var(--color-ring)`]
106
+ ],
107
+ 'ring-offset-': (suffix, value) => [
108
+ [`ring-offset-${suffix}`, `--tw-ring-offset-width: ${value}`]
109
+ ],
110
+ 'divide-': (suffix, value) => [
111
+ [`divide-${suffix}`, `border-color: ${value}`]
112
+ ],
113
+ 'accent-': (suffix, value) => [
114
+ [`accent-${suffix}`, `accent-color: ${value}`]
115
+ ],
116
+ 'caret-': (suffix, value) => [
117
+ [`caret-${suffix}`, `caret-color: ${value}`]
118
+ ],
119
+ 'decoration-': (suffix, value) => [
120
+ [`decoration-${suffix}`, `text-decoration-color: ${value}`]
121
+ ],
122
+ 'placeholder-': (suffix, value) => [
123
+ [`placeholder-${suffix}`, `&::placeholder { color: ${value} }`]
124
+ ],
125
+ 'selection-': (suffix, value) => [
126
+ [`selection-${suffix}`, `&::selection { background-color: ${value} }`]
127
+ ],
128
+ 'scrollbar-': (suffix, value) => [
129
+ [`scrollbar-${suffix}`, `scrollbar-color: ${value}`]
130
+ ]
131
+ };
132
+ }
133
+
134
+
135
+
136
+ // Variants and variant groups
137
+ // CSS selector mappings for Tailwind variants
138
+
139
+ function createVariants() {
140
+ return {
141
+ // State variants
142
+ 'hover': ':hover',
143
+ 'focus': ':focus',
144
+ 'focus-visible': ':focus-visible',
145
+ 'focus-within': ':focus-within',
146
+ 'active': ':active',
147
+ 'visited': ':visited',
148
+ 'target': ':target',
149
+ 'first': ':first-child',
150
+ 'last': ':last-child',
151
+ 'only': ':only-child',
152
+ 'odd': ':nth-child(odd)',
153
+ 'even': ':nth-child(even)',
154
+ 'first-of-type': ':first-of-type',
155
+ 'last-of-type': ':last-of-type',
156
+ 'only-of-type': ':only-of-type',
157
+ 'empty': ':empty',
158
+ 'disabled': ':disabled',
159
+ 'enabled': ':enabled',
160
+ 'checked': ':checked',
161
+ 'indeterminate': ':indeterminate',
162
+ 'default': ':default',
163
+ 'required': ':required',
164
+ 'valid': ':valid',
165
+ 'invalid': ':invalid',
166
+ 'in-range': ':in-range',
167
+ 'out-of-range': ':out-of-range',
168
+ 'placeholder-shown': ':placeholder-shown',
169
+ 'autofill': ':autofill',
170
+ 'read-only': ':read-only',
171
+ 'read-write': ':read-write',
172
+ 'optional': ':optional',
173
+ 'user-valid': ':user-valid',
174
+ 'user-invalid': ':user-invalid',
175
+ 'inert': ':inert',
176
+ 'open': ':is([open], :popover-open, :open) &',
177
+ 'closed': ':not(:is([open], :popover-open, :open)) &',
178
+ 'paused': '[data-state="paused"] &',
179
+ 'playing': '[data-state="playing"] &',
180
+ 'muted': '[data-state="muted"] &',
181
+ 'unmuted': '[data-state="unmuted"] &',
182
+ 'collapsed': '[data-state="collapsed"] &',
183
+ 'expanded': '[data-state="expanded"] &',
184
+ 'unchecked': ':not(:checked)',
185
+ 'selected': '[data-state="selected"] &',
186
+ 'unselected': '[data-state="unselected"] &',
187
+ 'details-content': '::details-content',
188
+ 'nth': ':nth-child',
189
+ 'nth-last': ':nth-last-child',
190
+ 'nth-of-type': ':nth-of-type',
191
+ 'nth-last-of-type': ':nth-last-of-type',
192
+ 'has': ':has',
193
+ 'not': ':not',
194
+
195
+ // Pseudo-elements
196
+ 'before': '::before',
197
+ 'after': '::after',
198
+ 'first-letter': '::first-letter',
199
+ 'first-line': '::first-line',
200
+ 'marker': '::marker',
201
+ 'selection': '::selection',
202
+ 'file': '::file-selector-button',
203
+ 'backdrop': '::backdrop',
204
+ 'placeholder': '::placeholder',
205
+ 'target-text': '::target-text',
206
+ 'spelling-error': '::spelling-error',
207
+ 'grammar-error': '::grammar-error',
208
+
209
+ // Media queries
210
+ 'dark': '.dark &',
211
+ 'light': '.light &',
212
+
213
+ // Group variants
214
+ 'group': '.group &',
215
+ 'group-hover': '.group:hover &',
216
+ 'group-focus': '.group:focus &',
217
+ 'group-focus-within': '.group:focus-within &',
218
+ 'group-active': '.group:active &',
219
+ 'group-disabled': '.group:disabled &',
220
+ 'group-visited': '.group:visited &',
221
+ 'group-checked': '.group:checked &',
222
+ 'group-required': '.group:required &',
223
+ 'group-valid': '.group:valid &',
224
+ 'group-invalid': '.group:invalid &',
225
+ 'group-in-range': '.group:in-range &',
226
+ 'group-out-of-range': '.group:out-of-range &',
227
+ 'group-placeholder-shown': '.group:placeholder-shown &',
228
+ 'group-autofill': '.group:autofill &',
229
+ 'group-read-only': '.group:read-only &',
230
+ 'group-read-write': '.group:read-write &',
231
+ 'group-optional': '.group:optional &',
232
+ 'group-user-valid': '.group:user-valid &',
233
+ 'group-user-invalid': '.group:user-invalid &',
234
+
235
+ // Peer variants
236
+ 'peer': '.peer ~ &',
237
+ 'peer-hover': '.peer:hover ~ &',
238
+ 'peer-focus': '.peer:focus ~ &',
239
+ 'peer-focus-within': '.peer:focus-within ~ &',
240
+ 'peer-active': '.peer:active ~ &',
241
+ 'peer-disabled': '.peer:disabled ~ &',
242
+ 'peer-visited': '.peer:visited ~ &',
243
+ 'peer-checked': '.peer:checked ~ &',
244
+ 'peer-required': '.peer:required ~ &',
245
+ 'peer-valid': '.peer:valid ~ &',
246
+ 'peer-invalid': '.peer:invalid ~ &',
247
+ 'peer-in-range': '.peer:in-range ~ &',
248
+ 'peer-out-of-range': '.peer:out-of-range ~ &',
249
+ 'peer-placeholder-shown': '.peer:placeholder-shown ~ &',
250
+ 'peer-autofill': '.peer:autofill ~ &',
251
+ 'peer-read-only': '.peer:read-only ~ &',
252
+ 'peer-read-write': '.peer:read-write ~ &',
253
+ 'peer-optional': '.peer:optional ~ &',
254
+ 'peer-user-valid': '.peer:user-valid ~ &',
255
+ 'peer-user-invalid': '.peer:user-invalid &',
256
+
257
+ 'motion-safe': '@media (prefers-reduced-motion: no-preference)',
258
+ 'motion-reduce': '@media (prefers-reduced-motion: reduce)',
259
+ 'print': '@media print',
260
+ 'portrait': '@media (orientation: portrait)',
261
+ 'landscape': '@media (orientation: landscape)',
262
+ 'contrast-more': '@media (prefers-contrast: more)',
263
+ 'contrast-less': '@media (prefers-contrast: less)',
264
+ 'forced-colors': '@media (forced-colors: active)',
265
+ 'rtl': '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)',
266
+ 'ltr': '&:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *)',
267
+ '[dir=rtl]': '[dir="rtl"] &',
268
+ '[dir=ltr]': '[dir="ltr"] &',
269
+ 'pointer-fine': '@media (pointer: fine)',
270
+ 'pointer-coarse': '@media (pointer: coarse)',
271
+ 'pointer-none': '@media (pointer: none)',
272
+ 'any-pointer-fine': '@media (any-pointer: fine)',
273
+ 'any-pointer-coarse': '@media (any-pointer: coarse)',
274
+ 'any-pointer-none': '@media (any-pointer: none)',
275
+ 'scripting-enabled': '@media (scripting: enabled)',
276
+ 'can-hover': '@media (hover: hover)',
277
+ 'can-not-hover': '@media (hover: none)',
278
+ 'any-hover': '@media (any-hover: hover)',
279
+ 'any-hover-none': '@media (any-hover: none)',
280
+ 'any-pointer': '@media (any-pointer: fine)',
281
+ 'any-pointer-coarse': '@media (any-pointer: coarse)',
282
+ 'any-pointer-none': '@media (any-pointer: none)',
283
+ 'color': '@media (color)',
284
+ 'color-gamut': '@media (color-gamut: srgb)',
285
+ 'color-gamut-p3': '@media (color-gamut: p3)',
286
+ 'color-gamut-rec2020': '@media (color-gamut: rec2020)',
287
+ 'monochrome': '@media (monochrome)',
288
+ 'monochrome-color': '@media (monochrome: 0)',
289
+ 'monochrome-grayscale': '@media (monochrome: 1)',
290
+ 'inverted-colors': '@media (inverted-colors: inverted)',
291
+ 'inverted-colors-none': '@media (inverted-colors: none)',
292
+ 'update': '@media (update: fast)',
293
+ 'update-slow': '@media (update: slow)',
294
+ 'update-none': '@media (update: none)',
295
+ 'overflow-block': '@media (overflow-block: scroll)',
296
+ 'overflow-block-paged': '@media (overflow-block: paged)',
297
+ 'overflow-inline': '@media (overflow-inline: scroll)',
298
+ 'overflow-inline-auto': '@media (overflow-inline: auto)',
299
+ 'prefers-color-scheme': '@media (prefers-color-scheme: dark)',
300
+ 'prefers-color-scheme-light': '@media (prefers-color-scheme: light)',
301
+ 'prefers-contrast': '@media (prefers-contrast: more)',
302
+ 'prefers-contrast-less': '@media (prefers-contrast: less)',
303
+ 'prefers-contrast-no-preference': '@media (prefers-contrast: no-preference)',
304
+ 'prefers-reduced-motion': '@media (prefers-reduced-motion: reduce)',
305
+ 'prefers-reduced-motion-no-preference': '@media (prefers-reduced-motion: no-preference)',
306
+ 'prefers-reduced-transparency': '@media (prefers-reduced-transparency: reduce)',
307
+ 'prefers-reduced-transparency-no-preference': '@media (prefers-reduced-transparency: no-preference)',
308
+ 'resolution': '@media (resolution: 1dppx)',
309
+ 'resolution-low': '@media (resolution: 1dppx)',
310
+ 'resolution-high': '@media (resolution: 2dppx)',
311
+ 'scan': '@media (scan: progressive)',
312
+ 'scan-interlace': '@media (scan: interlace)',
313
+ 'scripting': '@media (scripting: enabled)',
314
+ 'scripting-none': '@media (scripting: none)',
315
+ 'scripting-initial-only': '@media (scripting: initial-only)',
316
+
317
+ // Container queries
318
+ 'container': '@container',
319
+ 'container-name': '@container',
320
+
321
+ // Important modifier
322
+ '!': '!important',
323
+
324
+ // Responsive breakpoints
325
+ 'sm': '@media (min-width: 640px)',
326
+ 'md': '@media (min-width: 768px)',
327
+ 'lg': '@media (min-width: 1024px)',
328
+ 'xl': '@media (min-width: 1280px)',
329
+ '2xl': '@media (min-width: 1536px)',
330
+
331
+ // Supports queries
332
+ 'supports': '@supports',
333
+
334
+ // Starting style
335
+ 'starting': '@starting-style',
336
+
337
+ // Data attribute variants (common patterns)
338
+ 'data-open': '[data-state="open"] &',
339
+ 'data-closed': '[data-state="closed"] &',
340
+ 'data-checked': '[data-state="checked"] &',
341
+ 'data-unchecked': '[data-state="unchecked"] &',
342
+ 'data-on': '[data-state="on"] &',
343
+ 'data-off': '[data-state="off"] &',
344
+ 'data-visible': '[data-state="visible"] &',
345
+ 'data-hidden': '[data-state="hidden"] &',
346
+ 'data-disabled': '[data-disabled] &',
347
+ 'data-loading': '[data-loading] &',
348
+ 'data-error': '[data-error] &',
349
+ 'data-success': '[data-success] &',
350
+ 'data-warning': '[data-warning] &',
351
+ 'data-selected': '[data-selected] &',
352
+ 'data-highlighted': '[data-highlighted] &',
353
+ 'data-pressed': '[data-pressed] &',
354
+ 'data-expanded': '[data-expanded] &',
355
+ 'data-collapsed': '[data-collapsed] &',
356
+ 'data-active': '[data-active] &',
357
+ 'data-inactive': '[data-inactive] &',
358
+ 'data-valid': '[data-valid] &',
359
+ 'data-invalid': '[data-invalid] &',
360
+ 'data-required': '[data-required] &',
361
+ 'data-optional': '[data-optional] &',
362
+ 'data-readonly': '[data-readonly] &',
363
+ 'data-write': '[data-write] &',
364
+
365
+ // Aria attribute variants (common patterns)
366
+ 'aria-expanded': '[aria-expanded="true"] &',
367
+ 'aria-collapsed': '[aria-expanded="false"] &',
368
+ 'aria-pressed': '[aria-pressed="true"] &',
369
+ 'aria-unpressed': '[aria-pressed="false"] &',
370
+ 'aria-checked': '[aria-checked="true"] &',
371
+ 'aria-unchecked': '[aria-checked="false"] &',
372
+ 'aria-selected': '[aria-selected="true"] &',
373
+ 'aria-unselected': '[aria-selected="false"] &',
374
+ 'aria-invalid': '[aria-invalid="true"] &',
375
+ 'aria-valid': '[aria-invalid="false"] &',
376
+ 'aria-required': '[aria-required="true"] &',
377
+ 'aria-optional': '[aria-required="false"] &',
378
+ 'aria-disabled': '[aria-disabled="true"] &',
379
+ 'aria-enabled': '[aria-disabled="false"] &',
380
+ 'aria-hidden': '[aria-hidden="true"] &',
381
+ 'aria-visible': '[aria-hidden="false"] &',
382
+ 'aria-busy': '[aria-busy="true"] &',
383
+ 'aria-available': '[aria-busy="false"] &',
384
+ 'aria-current': '[aria-current="true"] &',
385
+ 'aria-not-current': '[aria-current="false"] &',
386
+ 'aria-live': '[aria-live="polite"] &, [aria-live="assertive"] &',
387
+ 'aria-atomic': '[aria-atomic="true"] &',
388
+ 'aria-relevant': '[aria-relevant="additions"] &, [aria-relevant="removals"] &, [aria-relevant="text"] &, [aria-relevant="all"] &'
389
+ };
390
+ }
391
+
392
+ function createVariantGroups() {
393
+ return {
394
+ 'state': ['hover', 'focus', 'active', 'visited', 'target', 'open', 'closed', 'paused', 'playing', 'muted', 'unmuted', 'collapsed', 'expanded', 'unchecked', 'selected', 'unselected'],
395
+ 'child': ['first', 'last', 'only', 'odd', 'even'],
396
+ 'form': ['disabled', 'enabled', 'checked', 'indeterminate', 'required', 'valid', 'invalid'],
397
+ 'pseudo': ['before', 'after', 'first-letter', 'first-line', 'marker', 'selection', 'file', 'backdrop'],
398
+ 'media': ['dark', 'light', 'motion-safe', 'motion-reduce', 'print', 'portrait', 'landscape', 'rtl', 'ltr', 'can-hover', 'can-not-hover', 'any-hover', 'any-hover-none', 'color', 'monochrome', 'inverted-colors', 'inverted-colors-none', 'update', 'update-slow', 'update-none', 'overflow-block', 'overflow-block-paged', 'overflow-inline', 'overflow-inline-auto', 'prefers-color-scheme', 'prefers-color-scheme-light', 'prefers-contrast', 'prefers-contrast-less', 'prefers-contrast-no-preference', 'prefers-reduced-motion', 'prefers-reduced-motion-no-preference', 'prefers-reduced-transparency', 'prefers-reduced-transparency-no-preference', 'resolution', 'resolution-low', 'resolution-high', 'scan', 'scan-interlace', 'scripting', 'scripting-none', 'scripting-initial-only', 'forced-colors', 'contrast-more', 'contrast-less', 'pointer-fine', 'pointer-coarse', 'pointer-none', 'any-pointer-fine', 'any-pointer-coarse', 'any-pointer-none', 'scripting-enabled'],
399
+ 'responsive': ['sm', 'md', 'lg', 'xl', '2xl'],
400
+ 'group': ['group', 'group-hover', 'group-focus', 'group-active', 'group-disabled', 'group-checked', 'group-required', 'group-valid', 'group-invalid'],
401
+ 'peer': ['peer', 'peer-hover', 'peer-focus', 'peer-active', 'peer-disabled', 'peer-checked', 'peer-required', 'peer-valid', 'peer-invalid'],
402
+ 'data': ['data-open', 'data-closed', 'data-checked', 'data-unchecked', 'data-visible', 'data-hidden', 'data-disabled', 'data-loading', 'data-error', 'data-success', 'data-warning', 'data-selected', 'data-highlighted', 'data-pressed', 'data-expanded', 'data-collapsed', 'data-active', 'data-inactive', 'data-valid', 'data-invalid', 'data-required', 'data-optional', 'data-readonly', 'data-write'],
403
+ 'aria': ['aria-expanded', 'aria-collapsed', 'aria-pressed', 'aria-unpressed', 'aria-checked', 'aria-unchecked', 'aria-selected', 'aria-unselected', 'aria-invalid', 'aria-valid', 'aria-required', 'aria-optional', 'aria-disabled', 'aria-enabled', 'aria-hidden', 'aria-visible', 'aria-busy', 'aria-available', 'aria-current', 'aria-not-current', 'aria-live', 'aria-atomic', 'aria-relevant']
404
+ };
405
+ }
406
+
407
+
408
+
409
+ /* Manifest Utilities */
410
+
411
+ // Browser runtime compiler
412
+ class TailwindCompiler {
413
+ constructor(options = {}) {
414
+ this.debug = options.debug === true;
415
+ this.startTime = performance.now();
416
+
417
+ // Create critical style element FIRST - must be before any rendering
418
+ const criticalStyleStart = performance.now();
419
+ this.criticalStyleElement = document.createElement('style');
420
+ this.criticalStyleElement.id = 'utility-styles-critical';
421
+ // Insert at the very beginning of head
422
+ if (document.head) {
423
+ if (document.head.firstChild) {
424
+ document.head.insertBefore(this.criticalStyleElement, document.head.firstChild);
425
+ } else {
426
+ document.head.appendChild(this.criticalStyleElement);
427
+ }
428
+ } else {
429
+ // If head doesn't exist yet, wait for it (shouldn't happen, but safety check)
430
+ const checkHead = setInterval(() => {
431
+ if (document.head) {
432
+ clearInterval(checkHead);
433
+ if (document.head.firstChild) {
434
+ document.head.insertBefore(this.criticalStyleElement, document.head.firstChild);
435
+ } else {
436
+ document.head.appendChild(this.criticalStyleElement);
437
+ }
438
+ }
439
+ }, 1);
440
+ }
441
+
442
+ // Initialize options first (needed for regex patterns)
443
+ this.options = {
444
+ rootSelector: options.rootSelector || ':root',
445
+ themeSelector: options.themeSelector || '@theme',
446
+ debounceTime: options.debounceTime || 50,
447
+ maxCacheAge: options.maxCacheAge || 24 * 60 * 60 * 1000,
448
+ debug: options.debug !== false,
449
+ ...options
450
+ };
451
+
452
+ // Initialize regex patterns (needed for utility generation)
453
+ this.regexPatterns = {
454
+ root: new RegExp(`${this.options.rootSelector}\\s*{([^}]*)}`, 'g'),
455
+ theme: new RegExp(`${this.options.themeSelector}\\s*{([^}]*)}`, 'g'),
456
+ variable: /--([\w-]+):\s*([^;]+);/g,
457
+ tailwindPrefix: /^(color|font|text|font-weight|tracking|leading|breakpoint|container|spacing|radius|shadow|inset-shadow|drop-shadow|blur|perspective|aspect|ease|animate|border-width|border-style|outline|outline-width|outline-style|ring|ring-offset|divide|accent|caret|decoration|placeholder|selection|scrollbar)-/
458
+ };
459
+
460
+ // Initialize utility generators from component
461
+ const generators = createUtilityGenerators();
462
+ // Start with minimal generators needed for synchronous generation
463
+ this.utilityGenerators = {
464
+ 'color-': generators['color-'],
465
+ 'font-': generators['font-'],
466
+ 'text-': generators['text-'],
467
+ 'font-weight-': generators['font-weight-'],
468
+ 'tracking-': generators['tracking-'],
469
+ 'leading-': generators['leading-'],
470
+ 'spacing-': generators['spacing-'],
471
+ 'radius-': generators['radius-'],
472
+ 'shadow-': generators['shadow-'],
473
+ 'blur-': generators['blur-']
474
+ };
475
+ // Add remaining generators
476
+ Object.assign(this.utilityGenerators, generators);
477
+
478
+ // Initialize variants from component (needed for parseClassName during sync generation)
479
+ this.variants = createVariants();
480
+ this.variantGroups = createVariantGroups();
481
+
482
+ // Cache for parsed class names (must be before addCriticalBlockingStylesSync)
483
+ this.classCache = new Map();
484
+
485
+ // Add critical styles IMMEDIATELY - don't wait for anything
486
+ this.addCriticalBlockingStylesSync();
487
+
488
+ // Create main style element for generated utilities
489
+ this.styleElement = document.createElement('style');
490
+ this.styleElement.id = 'utility-styles';
491
+ document.head.appendChild(this.styleElement);
492
+
493
+ // Initialize properties
494
+ this.tailwindLink = null;
495
+ this.observer = null;
496
+ this.isCompiling = false;
497
+ this.compileTimeout = null;
498
+ this.cache = new Map();
499
+ this.lastThemeHash = null;
500
+ this.processedElements = new WeakSet();
501
+ this.activeBreakpoints = new Set();
502
+ this.activeModifiers = new Set();
503
+ this.cssFiles = new Set();
504
+ this.pendingStyles = new Map();
505
+ this.currentThemeVars = new Map();
506
+ this.hasInitialized = false;
507
+ this.lastCompileTime = 0;
508
+ this.minCompileInterval = 100; // Minimum time between compilations in ms
509
+ this.cssContentCache = new Map(); // Cache CSS file contents with timestamps
510
+ this.lastClassesHash = ''; // Track changes in used classes
511
+ this.staticClassCache = new Set(); // Cache classes found in static HTML/components
512
+ this.dynamicClassCache = new Set(); // Cache classes that appear dynamically
513
+ this.hasScannedStatic = false; // Track if we've done initial static scan
514
+ this.staticScanPromise = null; // Promise for initial static scan
515
+ this.ignoredClassPatterns = [ // Patterns for classes to ignore
516
+ /^hljs/, /^language-/, /^copy$/, /^copied$/, /^lines$/, /^selected$/
517
+ ];
518
+ this.ignoredElementSelectors = [ // Elements to ignore for DOM mutations
519
+ 'pre', 'code', 'x-code', 'x-code-group'
520
+ ];
521
+ this.ignoredAttributes = [ // Attribute changes to ignore (non-visual/utility changes)
522
+ 'id', 'data-order', 'data-component-id', 'data-highlighted', 'data-processed',
523
+ 'x-intersect', 'x-intersect:leave', 'x-show', 'x-hide', 'x-transition',
524
+ 'aria-expanded', 'aria-selected', 'aria-current', 'aria-hidden', 'aria-label',
525
+ 'tabindex', 'role', 'title', 'alt', 'data-state', 'data-value'
526
+ ];
527
+ this.significantChangeSelectors = [ // Only these DOM additions trigger recompilation
528
+ '[data-component]', '[x-data]' // Components and Alpine elements
529
+ ];
530
+
531
+ // Pre-define pseudo classes
532
+ this.pseudoClasses = ['hover', 'focus', 'active', 'disabled', 'dark'];
533
+
534
+ // Cache for discovered custom utility classes
535
+ this.customUtilities = new Map();
536
+
537
+ // Variants and classCache already initialized above (before addCriticalBlockingStylesSync)
538
+
539
+ // Load cache and start processing
540
+ this.loadAndApplyCache();
541
+
542
+ // If cache loaded utilities, they'll be in the main style element
543
+ // The critical style element will be cleared when full utilities are ready
544
+
545
+ // Try to generate minimal utilities synchronously from inline styles
546
+ this.generateSynchronousUtilities();
547
+
548
+ // Listen for component loads
549
+ this.setupComponentLoadListener();
550
+
551
+ this.waitForTailwind().then(() => {
552
+ this.startProcessing();
553
+ });
554
+ }
555
+
556
+ // Public API for other plugins to configure behavior
557
+ addIgnoredClassPattern(pattern) {
558
+ if (pattern instanceof RegExp) {
559
+ this.ignoredClassPatterns.push(pattern);
560
+ } else if (typeof pattern === 'string') {
561
+ this.ignoredClassPatterns.push(new RegExp(pattern));
562
+ }
563
+ }
564
+
565
+ addIgnoredElementSelector(selector) {
566
+ if (typeof selector === 'string') {
567
+ this.ignoredElementSelectors.push(selector);
568
+ }
569
+ }
570
+
571
+ addSignificantChangeSelector(selector) {
572
+ if (typeof selector === 'string') {
573
+ this.significantChangeSelectors.push(selector);
574
+ }
575
+ }
576
+
577
+ // Allow plugins to trigger recompilation when needed
578
+ triggerRecompilation(reason = 'manual') {
579
+ this.compile();
580
+ }
581
+
582
+ // Debounce utility
583
+ debounce(func, wait) {
584
+ let timeout;
585
+ return function executedFunction(...args) {
586
+ const later = () => {
587
+ clearTimeout(timeout);
588
+ func(...args);
589
+ };
590
+ clearTimeout(timeout);
591
+ timeout = setTimeout(later, wait);
592
+ };
593
+ }
594
+
595
+ // Wait for Tailwind to be available
596
+ async waitForTailwind() {
597
+ return new Promise((resolve) => {
598
+ if (this.isTailwindAvailable()) {
599
+ resolve();
600
+ return;
601
+ }
602
+
603
+ const checkInterval = setInterval(() => {
604
+ if (this.isTailwindAvailable()) {
605
+ clearInterval(checkInterval);
606
+ resolve();
607
+ }
608
+ }, 100);
609
+
610
+ // Also check on DOMContentLoaded
611
+ document.addEventListener('DOMContentLoaded', () => {
612
+ if (this.isTailwindAvailable()) {
613
+ clearInterval(checkInterval);
614
+ resolve();
615
+ }
616
+ });
617
+
618
+ // Set a timeout to prevent infinite waiting
619
+ setTimeout(() => {
620
+ clearInterval(checkInterval);
621
+ resolve();
622
+ }, 5000);
623
+ });
624
+ }
625
+
626
+ // Check if Tailwind is available
627
+ isTailwindAvailable() {
628
+ // Check for Tailwind in various ways
629
+ return (
630
+ // Check for Tailwind CSS file
631
+ Array.from(document.styleSheets).some(sheet =>
632
+ sheet.href && (
633
+ sheet.href.includes('tailwind') ||
634
+ sheet.href.includes('tailwindcss') ||
635
+ sheet.href.includes('indux')
636
+ )
637
+ ) ||
638
+ // Check for Tailwind classes in document
639
+ document.querySelector('[class*="tailwind"]') ||
640
+ // Check for Tailwind in window object
641
+ window.tailwind ||
642
+ // Check for Tailwind in document head
643
+ document.head.innerHTML.includes('tailwind') ||
644
+ // Check for Manifest CSS files
645
+ document.head.innerHTML.includes('indux')
646
+ );
647
+ }
648
+ }
649
+
650
+
651
+
652
+ // Synchronous utility generation
653
+ // Methods for generating utilities synchronously before first paint
654
+
655
+ TailwindCompiler.prototype.addCriticalBlockingStylesSync = function () {
656
+ if (!this.criticalStyleElement) return;
657
+
658
+ const syncStart = performance.now();
659
+
660
+ try {
661
+ // Extract CSS variables synchronously from already-loaded sources
662
+ const cssVariables = new Map();
663
+
664
+ // 1. From inline style elements (already in DOM)
665
+ const inlineStyles = document.querySelectorAll('style:not(#utility-styles):not(#utility-styles-critical)');
666
+ for (const styleEl of inlineStyles) {
667
+ if (styleEl.textContent) {
668
+ const variables = this.extractThemeVariables(styleEl.textContent);
669
+ for (const [name, value] of variables.entries()) {
670
+ cssVariables.set(name, value);
671
+ }
672
+ }
673
+ }
674
+
675
+ // 2. From HTML source (parse style tags in HTML)
676
+ try {
677
+ if (document.documentElement) {
678
+ const htmlSource = document.documentElement.outerHTML;
679
+ const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
680
+ let styleMatch;
681
+ while ((styleMatch = styleRegex.exec(htmlSource)) !== null) {
682
+ const cssContent = styleMatch[1];
683
+ const variables = this.extractThemeVariables(cssContent);
684
+ for (const [name, value] of variables.entries()) {
685
+ cssVariables.set(name, value);
686
+ }
687
+ }
688
+ }
689
+ } catch (e) {
690
+ // Ignore parsing errors
691
+ }
692
+
693
+ // 3. From loaded stylesheets (synchronously read CSS rules)
694
+ try {
695
+ const stylesheets = Array.from(document.styleSheets);
696
+ for (const sheet of stylesheets) {
697
+ try {
698
+ // Try to access CSS rules (may fail due to CORS)
699
+ const rules = Array.from(sheet.cssRules || []);
700
+ for (const rule of rules) {
701
+ if (rule.type === CSSRule.STYLE_RULE && rule.styleSheet) {
702
+ // Handle @import rules that have nested stylesheets
703
+ try {
704
+ const nestedRules = Array.from(rule.styleSheet.cssRules || []);
705
+ for (const nestedRule of nestedRules) {
706
+ if (nestedRule.type === CSSRule.STYLE_RULE) {
707
+ const cssText = nestedRule.cssText;
708
+ const variables = this.extractThemeVariables(cssText);
709
+ for (const [name, value] of variables.entries()) {
710
+ cssVariables.set(name, value);
711
+ }
712
+ }
713
+ }
714
+ } catch (e) {
715
+ // Ignore nested rule errors
716
+ }
717
+ }
718
+ if (rule.type === CSSRule.STYLE_RULE) {
719
+ const cssText = rule.cssText;
720
+ const variables = this.extractThemeVariables(cssText);
721
+ for (const [name, value] of variables.entries()) {
722
+ cssVariables.set(name, value);
723
+ }
724
+ }
725
+ }
726
+ } catch (e) {
727
+ // CORS or other access errors - expected for some stylesheets
728
+ }
729
+ }
730
+ } catch (e) {
731
+ }
732
+
733
+ // 4. From computed styles (if :root is available)
734
+ try {
735
+ if (document.documentElement && document.readyState !== 'loading') {
736
+ const rootStyles = getComputedStyle(document.documentElement);
737
+ let computedVars = 0;
738
+ for (let i = 0; i < rootStyles.length; i++) {
739
+ const prop = rootStyles[i];
740
+ if (prop.startsWith('--')) {
741
+ const value = rootStyles.getPropertyValue(prop);
742
+ if (value && value.trim()) {
743
+ cssVariables.set(prop.substring(2), value.trim());
744
+ computedVars++;
745
+ }
746
+ }
747
+ }
748
+ }
749
+ } catch (e) {
750
+ // Ignore errors
751
+ }
752
+
753
+ // 5. Scan for classes that need utilities
754
+ const classesToGenerate = new Set();
755
+ try {
756
+ // Method A: Scan HTML source
757
+ if (document.documentElement) {
758
+ const htmlSource = document.documentElement.outerHTML;
759
+ const classRegex = /class=["']([^"']+)["']/gi;
760
+ let classMatch;
761
+ while ((classMatch = classRegex.exec(htmlSource)) !== null) {
762
+ const classes = classMatch[1].split(/\s+/).filter(Boolean);
763
+ for (const cls of classes) {
764
+ // Match utility patterns that might use CSS variables
765
+ if (/^(border|bg|text|ring|outline|decoration|caret|accent|fill|stroke)-[a-z0-9-]+(\/[0-9]+)?$/.test(cls)) {
766
+ classesToGenerate.add(cls);
767
+ }
768
+ }
769
+ }
770
+ }
771
+
772
+ // Method B: Scan DOM directly (if body exists)
773
+ if (document.body) {
774
+ const elements = document.body.querySelectorAll('*');
775
+ for (const el of elements) {
776
+ if (el.className && typeof el.className === 'string') {
777
+ const classes = el.className.split(/\s+/).filter(Boolean);
778
+ for (const cls of classes) {
779
+ if (/^(border|bg|text|ring|outline|decoration|caret|accent|fill|stroke)-[a-z0-9-]+(\/[0-9]+)?$/.test(cls)) {
780
+ classesToGenerate.add(cls);
781
+ }
782
+ }
783
+ }
784
+ }
785
+ }
786
+
787
+ } catch (e) {
788
+ }
789
+
790
+ // Generate utilities synchronously if we have CSS variables
791
+
792
+ if (cssVariables.size > 0) {
793
+ const generateStart = performance.now();
794
+ const cssText = Array.from(cssVariables.entries())
795
+ .map(([name, value]) => `--${name}: ${value};`)
796
+ .join('\n');
797
+
798
+ const tempCss = `:root { ${cssText} }`;
799
+
800
+ // If we have classes, use them. Otherwise, try to get classes from cache
801
+ let usedData = null; // Initialize to null so we can detect when it's not set
802
+ if (classesToGenerate.size > 0) {
803
+ usedData = {
804
+ classes: Array.from(classesToGenerate),
805
+ variableSuffixes: []
806
+ };
807
+ } else {
808
+ // Try to get classes from cache (most efficient - only generate what was used before)
809
+ const cached = localStorage.getItem('tailwind-cache');
810
+ let cachedClasses = new Set();
811
+
812
+ if (cached) {
813
+ try {
814
+ const parsed = JSON.parse(cached);
815
+ const cacheEntries = Object.values(parsed);
816
+
817
+ // Extract classes from cache keys (format: "class1,class2-themeHash")
818
+ for (const entry of cacheEntries) {
819
+ // Find the cache entry with the most recent timestamp
820
+ const mostRecent = cacheEntries.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0];
821
+ if (mostRecent && mostRecent.css) {
822
+ // Extract class names from generated CSS
823
+ const classMatches = mostRecent.css.match(/\.([a-zA-Z0-9_-]+(?::[a-zA-Z0-9_-]+)*)\s*{/g);
824
+ if (classMatches) {
825
+ for (const match of classMatches) {
826
+ const className = match.replace(/^\./, '').replace(/\s*{.*$/, '');
827
+ // Only include utility classes (not Tailwind native like red-500)
828
+ if (/^(border|bg|text|ring|outline|decoration|caret|accent|fill|stroke)-[a-z0-9-]+(\/[0-9]+)?$/.test(className.split(':').pop())) {
829
+ cachedClasses.add(className);
830
+ }
831
+ }
832
+ }
833
+ }
834
+ }
835
+ } catch (e) {
836
+ // Ignore cache parsing errors
837
+ }
838
+ }
839
+
840
+ if (cachedClasses.size > 0) {
841
+ usedData = {
842
+ classes: Array.from(cachedClasses),
843
+ variableSuffixes: []
844
+ };
845
+ } else {
846
+ // Last resort: scan HTML source text directly
847
+ try {
848
+ const htmlText = document.documentElement.innerHTML || '';
849
+ const classMatches = htmlText.match(/class=["']([^"']+)["']/g);
850
+ if (classMatches) {
851
+ for (const match of classMatches) {
852
+ const classString = match.replace(/class=["']/, '').replace(/["']$/, '');
853
+ const classes = classString.split(/\s+/).filter(Boolean);
854
+ for (const cls of classes) {
855
+ if (/^(border|bg|text|ring|outline|decoration|caret|accent|fill|stroke)-[a-z0-9-]+(\/[0-9]+)?$/.test(cls)) {
856
+ cachedClasses.add(cls);
857
+ }
858
+ }
859
+ }
860
+ }
861
+ } catch (e) {
862
+ // Ignore errors
863
+ }
864
+
865
+ if (cachedClasses.size > 0) {
866
+ usedData = {
867
+ classes: Array.from(cachedClasses),
868
+ variableSuffixes: []
869
+ };
870
+ }
871
+ // If still no classes, continue to generate utilities for all color variables
872
+ }
873
+ }
874
+
875
+ // If no classes found, generate utilities for all color variables
876
+ if (!usedData || !usedData.classes || usedData.classes.length === 0) {
877
+ // Generate utilities for all color-* variables to prevent flash
878
+ const colorVars = Array.from(cssVariables.entries())
879
+ .filter(([name]) => name.startsWith('color-'));
880
+
881
+ if (colorVars.length > 0) {
882
+ // Create synthetic classes for all color utilities (text, bg, border)
883
+ const syntheticClasses = [];
884
+ for (const [varName] of colorVars) {
885
+ const suffix = varName.replace('color-', '');
886
+ syntheticClasses.push(`text-${suffix}`);
887
+ syntheticClasses.push(`bg-${suffix}`);
888
+ syntheticClasses.push(`border-${suffix}`);
889
+ }
890
+ usedData = {
891
+ classes: syntheticClasses,
892
+ variableSuffixes: []
893
+ };
894
+ }
895
+ }
896
+
897
+ if (usedData && usedData.classes && usedData.classes.length > 0) {
898
+ const generated = this.generateUtilitiesFromVars(tempCss, usedData);
899
+ if (generated) {
900
+ const applyStart = performance.now();
901
+ this.criticalStyleElement.textContent = generated;
902
+
903
+ // Force a synchronous style recalculation
904
+ if (document.body) {
905
+ // Trigger a reflow to force style application
906
+ void document.body.offsetHeight;
907
+ } else {
908
+ // If body doesn't exist yet, force reflow on documentElement
909
+ void document.documentElement.offsetHeight;
910
+ }
911
+
912
+ const applyEnd = performance.now();
913
+ }
914
+ }
915
+ }
916
+ } catch (error) {
917
+ // Silently fail - async compilation will handle it
918
+ }
919
+ };
920
+
921
+ // Generate synchronous utilities (fallback method)
922
+ TailwindCompiler.prototype.generateSynchronousUtilities = function () {
923
+ try {
924
+ // Always try to generate, even if cache exists, to catch any new classes
925
+ const hasExistingStyles = this.styleElement.textContent && this.styleElement.textContent.trim();
926
+
927
+ let cssVariables = new Map();
928
+ const commonColorClasses = new Set();
929
+
930
+ // Method 1: Extract from inline style elements
931
+ const inlineStyles = document.querySelectorAll('style:not(#utility-styles)');
932
+ for (const styleEl of inlineStyles) {
933
+ if (styleEl.textContent) {
934
+ const variables = this.extractThemeVariables(styleEl.textContent);
935
+ for (const [name, value] of variables.entries()) {
936
+ cssVariables.set(name, value);
937
+ }
938
+ }
939
+ }
940
+
941
+ // Method 2: Parse HTML source directly for CSS variables in <style> tags
942
+ try {
943
+ const htmlSource = document.documentElement.outerHTML;
944
+ // Extract CSS from <style> tags in HTML source
945
+ const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
946
+ let styleMatch;
947
+ while ((styleMatch = styleRegex.exec(htmlSource)) !== null) {
948
+ const cssContent = styleMatch[1];
949
+ const variables = this.extractThemeVariables(cssContent);
950
+ for (const [name, value] of variables.entries()) {
951
+ cssVariables.set(name, value);
952
+ }
953
+ }
954
+ } catch (e) {
955
+ // Ignore parsing errors
956
+ }
957
+
958
+ // Method 3: Check computed styles from :root (if available)
959
+ try {
960
+ if (document.readyState !== 'loading') {
961
+ const rootStyles = getComputedStyle(document.documentElement);
962
+ // Extract all CSS variables, not just color ones
963
+ const allProps = rootStyles.length;
964
+ for (let i = 0; i < allProps; i++) {
965
+ const prop = rootStyles[i];
966
+ if (prop.startsWith('--')) {
967
+ const value = rootStyles.getPropertyValue(prop);
968
+ if (value && value.trim()) {
969
+ cssVariables.set(prop.substring(2), value.trim());
970
+ }
971
+ }
972
+ }
973
+ }
974
+ } catch (e) {
975
+ // Ignore errors accessing computed styles
976
+ }
977
+
978
+ // Method 4: Scan HTML source directly for class attributes
979
+ try {
980
+ const htmlSource = document.documentElement.outerHTML;
981
+ // Extract all class attributes from HTML source
982
+ const classRegex = /class=["']([^"']+)["']/gi;
983
+ let classMatch;
984
+ while ((classMatch = classRegex.exec(htmlSource)) !== null) {
985
+ const classString = classMatch[1];
986
+ const classes = classString.split(/\s+/).filter(Boolean);
987
+ for (const cls of classes) {
988
+ // Match common color utility patterns (more comprehensive)
989
+ if (/^(border|bg|text|ring|outline|decoration|caret|accent|fill|stroke)-[a-z0-9-]+(\/[0-9]+)?$/.test(cls)) {
990
+ commonColorClasses.add(cls);
991
+ }
992
+ }
993
+ }
994
+ } catch (e) {
995
+ // Fallback: scan DOM if HTML parsing fails
996
+ const elements = document.querySelectorAll('*');
997
+ for (const el of elements) {
998
+ if (el.className && typeof el.className === 'string') {
999
+ const classes = el.className.split(/\s+/);
1000
+ for (const cls of classes) {
1001
+ if (/^(border|bg|text|ring|outline|decoration|caret|accent|fill|stroke)-[a-z0-9-]+(\/[0-9]+)?$/.test(cls)) {
1002
+ commonColorClasses.add(cls);
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+ }
1008
+
1009
+ // If we have variables and classes, generate utilities
1010
+ if (cssVariables.size > 0 && commonColorClasses.size > 0) {
1011
+ const cssText = Array.from(cssVariables.entries())
1012
+ .map(([name, value]) => `--${name}: ${value};`)
1013
+ .join('\n');
1014
+
1015
+ const tempCss = `:root { ${cssText} }`;
1016
+ const usedData = {
1017
+ classes: Array.from(commonColorClasses),
1018
+ variableSuffixes: []
1019
+ };
1020
+
1021
+ const generated = this.generateUtilitiesFromVars(tempCss, usedData);
1022
+ if (generated) {
1023
+ const finalCss = `@layer utilities {\n${generated}\n}`;
1024
+ // Apply styles - append to existing if cache exists, replace if not
1025
+ if (hasExistingStyles) {
1026
+ // Append new utilities to existing cache (they'll be deduplicated by CSS)
1027
+ this.styleElement.textContent += '\n\n' + finalCss;
1028
+ } else {
1029
+ this.styleElement.textContent = finalCss;
1030
+ }
1031
+
1032
+ // Clear critical styles once we have generated utilities
1033
+ if (this.criticalStyleElement && generated.trim()) {
1034
+ this.criticalStyleElement.textContent = '';
1035
+ }
1036
+ }
1037
+ }
1038
+ } catch (error) {
1039
+ // Silently fail - async compilation will handle it
1040
+ }
1041
+ };
1042
+
1043
+
1044
+
1045
+ // Cache management
1046
+ // Methods for loading, saving, and managing cached utilities
1047
+
1048
+ // Load and apply cached utilities
1049
+ TailwindCompiler.prototype.loadAndApplyCache = function () {
1050
+ const cacheStart = performance.now();
1051
+ try {
1052
+ const cached = localStorage.getItem('tailwind-cache');
1053
+ if (cached) {
1054
+ const parsed = JSON.parse(cached);
1055
+ this.cache = new Map(Object.entries(parsed));
1056
+
1057
+ // Try to find the best matching cache entry
1058
+ // First, try to get a quick scan of current classes
1059
+ let currentClasses = new Set();
1060
+ try {
1061
+ // Quick scan of HTML source for classes
1062
+ if (document.documentElement) {
1063
+ const htmlSource = document.documentElement.outerHTML;
1064
+ const classRegex = /class=["']([^"']+)["']/gi;
1065
+ let classMatch;
1066
+ while ((classMatch = classRegex.exec(htmlSource)) !== null) {
1067
+ const classes = classMatch[1].split(/\s+/).filter(Boolean);
1068
+ classes.forEach(cls => {
1069
+ if (!cls.startsWith('x-') && !cls.startsWith('$')) {
1070
+ currentClasses.add(cls);
1071
+ }
1072
+ });
1073
+ }
1074
+ }
1075
+ } catch (e) {
1076
+ // If HTML parsing fails, just use most recent
1077
+ }
1078
+
1079
+ let bestMatch = null;
1080
+ let bestScore = 0;
1081
+
1082
+ // Score cache entries by how many classes they match
1083
+ if (currentClasses.size > 0) {
1084
+ for (const [key, value] of this.cache.entries()) {
1085
+ // Extract classes from cache key (format: "class1,class2-themeHash")
1086
+ // Find the last occurrence of '-' followed by 8 chars (theme hash length)
1087
+ const lastDashIndex = key.lastIndexOf('-');
1088
+ const classesPart = lastDashIndex > 0 ? key.substring(0, lastDashIndex) : key;
1089
+ const cachedClasses = classesPart ? classesPart.split(',') : [];
1090
+ const cachedSet = new Set(cachedClasses);
1091
+
1092
+ // Count how many current classes are in cache
1093
+ let matches = 0;
1094
+ for (const cls of currentClasses) {
1095
+ if (cachedSet.has(cls)) {
1096
+ matches++;
1097
+ }
1098
+ }
1099
+
1100
+ // Score based on match ratio and recency
1101
+ const matchRatio = matches / currentClasses.size;
1102
+ const recencyScore = (Date.now() - value.timestamp) / (24 * 60 * 60 * 1000); // Days since cache
1103
+ const score = matchRatio * 0.7 + (1 - Math.min(recencyScore, 1)) * 0.3; // 70% match, 30% recency
1104
+
1105
+ if (score > bestScore) {
1106
+ bestScore = score;
1107
+ bestMatch = value;
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ // Use best match, or fall back to most recent
1113
+ const cacheToUse = bestMatch || Array.from(this.cache.entries())
1114
+ .sort((a, b) => b[1].timestamp - a[1].timestamp)[0]?.[1];
1115
+
1116
+ if (cacheToUse && cacheToUse.css) {
1117
+ const applyCacheStart = performance.now();
1118
+ this.styleElement.textContent = cacheToUse.css;
1119
+ this.lastThemeHash = cacheToUse.themeHash;
1120
+
1121
+ // Also apply cache to critical style element
1122
+ // Extract utilities from @layer utilities block and apply directly (no @layer)
1123
+ if (this.criticalStyleElement && !this.criticalStyleElement.textContent) {
1124
+ let criticalCss = cacheToUse.css;
1125
+ // Remove @layer utilities wrapper if present
1126
+ criticalCss = criticalCss.replace(/@layer\s+utilities\s*\{/g, '').replace(/\}\s*$/, '').trim();
1127
+ if (criticalCss) {
1128
+ this.criticalStyleElement.textContent = criticalCss;
1129
+ }
1130
+ }
1131
+
1132
+ // Don't clear critical styles yet - keep them until full compilation completes
1133
+ }
1134
+ }
1135
+ } catch (error) {
1136
+ // Silently fail - cache is optional
1137
+ }
1138
+ };
1139
+
1140
+ // Save cache to localStorage
1141
+ TailwindCompiler.prototype.savePersistentCache = function () {
1142
+ try {
1143
+ const serialized = JSON.stringify(Object.fromEntries(this.cache));
1144
+ localStorage.setItem('tailwind-cache', serialized);
1145
+ } catch (error) {
1146
+ console.warn('Failed to save cached styles:', error);
1147
+ }
1148
+ };
1149
+
1150
+ // Load cache from localStorage
1151
+ TailwindCompiler.prototype.loadPersistentCache = function () {
1152
+ try {
1153
+ const cached = localStorage.getItem('tailwind-cache');
1154
+ if (cached) {
1155
+ const parsed = JSON.parse(cached);
1156
+ this.cache = new Map(Object.entries(parsed));
1157
+ }
1158
+ } catch (error) {
1159
+ console.warn('Failed to load cached styles:', error);
1160
+ }
1161
+ };
1162
+
1163
+ // Generate a hash of the theme variables to detect changes
1164
+ TailwindCompiler.prototype.generateThemeHash = function (themeCss) {
1165
+ // Use encodeURIComponent to handle non-Latin1 characters safely
1166
+ return encodeURIComponent(themeCss).slice(0, 8); // Simple hash of theme content
1167
+ };
1168
+
1169
+ // Clean up old cache entries
1170
+ TailwindCompiler.prototype.cleanupCache = function () {
1171
+ const now = Date.now();
1172
+ const maxAge = this.options.maxCacheAge;
1173
+ const entriesToDelete = [];
1174
+
1175
+ for (const [key, value] of this.cache.entries()) {
1176
+ if (value.timestamp && (now - value.timestamp > maxAge)) {
1177
+ entriesToDelete.push(key);
1178
+ }
1179
+ }
1180
+
1181
+ for (const key of entriesToDelete) {
1182
+ this.cache.delete(key);
1183
+ }
1184
+
1185
+ if (entriesToDelete.length > 0) {
1186
+ this.savePersistentCache();
1187
+ }
1188
+ };
1189
+
1190
+
1191
+
1192
+ // Helper methods
1193
+ // Utility functions for extracting, parsing, and processing CSS and classes
1194
+
1195
+ // Discover CSS files from stylesheets and imports
1196
+ TailwindCompiler.prototype.discoverCssFiles = function () {
1197
+ try {
1198
+ // Get all stylesheets from the document
1199
+ const stylesheets = Array.from(document.styleSheets);
1200
+
1201
+ // Process each stylesheet
1202
+ for (const sheet of stylesheets) {
1203
+ try {
1204
+ // Process local files and manifestjs CDN files
1205
+ if (sheet.href && (
1206
+ sheet.href.startsWith(window.location.origin) ||
1207
+ sheet.href.includes('manifestjs') ||
1208
+ (sheet.href.includes('jsdelivr') && sheet.href.includes('manifestjs')) ||
1209
+ (sheet.href.includes('unpkg') && sheet.href.includes('manifestjs'))
1210
+ )) {
1211
+ this.cssFiles.add(sheet.href);
1212
+ }
1213
+
1214
+ // Get all @import rules (local and manifestjs CDN)
1215
+ const rules = Array.from(sheet.cssRules || []);
1216
+ for (const rule of rules) {
1217
+ if (rule.type === CSSRule.IMPORT_RULE && rule.href && (
1218
+ rule.href.startsWith(window.location.origin) ||
1219
+ rule.href.includes('manifestjs') ||
1220
+ (rule.href.includes('jsdelivr') && rule.href.includes('manifestjs')) ||
1221
+ (rule.href.includes('unpkg') && rule.href.includes('manifestjs'))
1222
+ )) {
1223
+ this.cssFiles.add(rule.href);
1224
+ }
1225
+ }
1226
+ } catch (e) {
1227
+ // Skip stylesheets that can't be accessed (external CDN files, CORS, etc.)
1228
+ }
1229
+ }
1230
+
1231
+ // Add any inline styles (exclude generated styles)
1232
+ const styleElements = document.querySelectorAll('style:not(#utility-styles)');
1233
+ for (const style of styleElements) {
1234
+ if (style.textContent && style.textContent.trim()) {
1235
+ const id = style.id || `inline-style-${Array.from(styleElements).indexOf(style)}`;
1236
+ this.cssFiles.add('inline:' + id);
1237
+ }
1238
+ }
1239
+ } catch (error) {
1240
+ console.warn('Error discovering CSS files:', error);
1241
+ }
1242
+ };
1243
+
1244
+ // Scan static HTML files and components for classes
1245
+ TailwindCompiler.prototype.scanStaticClasses = async function () {
1246
+ if (this.staticScanPromise) {
1247
+ return this.staticScanPromise;
1248
+ }
1249
+
1250
+ this.staticScanPromise = (async () => {
1251
+ try {
1252
+ const staticClasses = new Set();
1253
+
1254
+ // 1. Scan index.html content
1255
+ const htmlContent = document.documentElement.outerHTML;
1256
+ this.extractClassesFromHTML(htmlContent, staticClasses);
1257
+
1258
+ // 2. Scan component files from manifest
1259
+ const registry = window.ManifestComponentsRegistry;
1260
+ const componentUrls = [];
1261
+
1262
+ if (registry && registry.manifest) {
1263
+ // Get all component paths from manifest
1264
+ const allComponents = [
1265
+ ...(registry.manifest.preloadedComponents || []),
1266
+ ...(registry.manifest.components || [])
1267
+ ];
1268
+ componentUrls.push(...allComponents);
1269
+ }
1270
+
1271
+ const componentPromises = componentUrls.map(async (url) => {
1272
+ try {
1273
+ const response = await fetch('/' + url);
1274
+ if (response.ok) {
1275
+ const html = await response.text();
1276
+ this.extractClassesFromHTML(html, staticClasses);
1277
+ }
1278
+ } catch (error) {
1279
+ // Silently ignore missing components
1280
+ }
1281
+ });
1282
+
1283
+ await Promise.all(componentPromises);
1284
+
1285
+ // Cache static classes
1286
+ for (const cls of staticClasses) {
1287
+ this.staticClassCache.add(cls);
1288
+ }
1289
+
1290
+ this.hasScannedStatic = true;
1291
+
1292
+ return staticClasses;
1293
+ } catch (error) {
1294
+ console.warn('[TailwindCompiler] Error scanning static classes:', error);
1295
+ this.hasScannedStatic = true;
1296
+ return new Set();
1297
+ }
1298
+ })();
1299
+
1300
+ return this.staticScanPromise;
1301
+ };
1302
+
1303
+ // Extract classes from HTML content
1304
+ TailwindCompiler.prototype.extractClassesFromHTML = function (html, classSet) {
1305
+ // Match class attributes: class="..." or class='...'
1306
+ const classRegex = /class=["']([^"']+)["']/g;
1307
+ let match;
1308
+
1309
+ while ((match = classRegex.exec(html)) !== null) {
1310
+ const classString = match[1];
1311
+ const classes = classString.split(/\s+/).filter(Boolean);
1312
+ for (const cls of classes) {
1313
+ if (cls && !cls.startsWith('x-') && !cls.startsWith('$')) {
1314
+ classSet.add(cls);
1315
+ }
1316
+ }
1317
+ }
1318
+
1319
+ // Also check for x-data and other Alpine directives that might contain classes
1320
+ const alpineRegex = /x-(?:data|bind:class|class)=["']([^"']+)["']/g;
1321
+ while ((match = alpineRegex.exec(html)) !== null) {
1322
+ // Simple extraction - could be enhanced for complex Alpine expressions
1323
+ const content = match[1];
1324
+ const classMatches = content.match(/['"`]([^'"`\s]+)['"`]/g);
1325
+ if (classMatches) {
1326
+ for (const classMatch of classMatches) {
1327
+ const cls = classMatch.replace(/['"`]/g, '');
1328
+ if (cls && !cls.startsWith('$') && !cls.includes('(')) {
1329
+ classSet.add(cls);
1330
+ }
1331
+ }
1332
+ }
1333
+ }
1334
+ };
1335
+
1336
+ // Get all used classes from static and dynamic sources
1337
+ TailwindCompiler.prototype.getUsedClasses = function () {
1338
+ try {
1339
+ const allClasses = new Set();
1340
+ const usedVariableSuffixes = new Set();
1341
+
1342
+ // Add static classes (pre-scanned)
1343
+ for (const cls of this.staticClassCache) {
1344
+ allClasses.add(cls);
1345
+ }
1346
+
1347
+ // Scan current DOM for dynamic classes only
1348
+ const elements = document.getElementsByTagName('*');
1349
+ for (const element of elements) {
1350
+ let classes = [];
1351
+ if (typeof element.className === 'string') {
1352
+ classes = element.className.split(/\s+/).filter(Boolean);
1353
+ } else if (element.classList) {
1354
+ classes = Array.from(element.classList);
1355
+ }
1356
+
1357
+ for (const cls of classes) {
1358
+ if (!cls) continue;
1359
+
1360
+ // Skip classes using configurable patterns
1361
+ const isIgnoredClass = this.ignoredClassPatterns.some(pattern =>
1362
+ pattern.test(cls)
1363
+ );
1364
+
1365
+ if (isIgnoredClass) {
1366
+ continue;
1367
+ }
1368
+
1369
+ // Add all classes (static + dynamic)
1370
+ allClasses.add(cls);
1371
+
1372
+ // Track dynamic classes separately
1373
+ if (!this.staticClassCache.has(cls)) {
1374
+ this.dynamicClassCache.add(cls);
1375
+ }
1376
+ }
1377
+ }
1378
+
1379
+ // Process all classes for variable suffixes
1380
+ for (const cls of allClasses) {
1381
+ // Extract base class and variants
1382
+ const parts = cls.split(':');
1383
+ const baseClass = parts[parts.length - 1];
1384
+
1385
+ // Extract suffix for variable matching
1386
+ const classParts = baseClass.split('-');
1387
+ if (classParts.length > 1) {
1388
+ let suffix = classParts.slice(1).join('-');
1389
+
1390
+ // Handle opacity modifiers (like /90, /50)
1391
+ let baseSuffix = suffix;
1392
+ if (suffix.includes('/')) {
1393
+ const parts = suffix.split('/');
1394
+ baseSuffix = parts[0];
1395
+ const opacity = parts[1];
1396
+
1397
+ // Add both the base suffix and the full suffix with opacity
1398
+ usedVariableSuffixes.add(baseSuffix);
1399
+ usedVariableSuffixes.add(suffix); // Keep the full suffix with opacity
1400
+ } else {
1401
+ usedVariableSuffixes.add(suffix);
1402
+ }
1403
+
1404
+ // For compound classes like text-content-subtle, also add the full suffix
1405
+ if (classParts.length > 2) {
1406
+ const fullSuffix = classParts.slice(1).join('-');
1407
+ if (fullSuffix.includes('/')) {
1408
+ usedVariableSuffixes.add(fullSuffix.split('/')[0]);
1409
+ } else {
1410
+ usedVariableSuffixes.add(fullSuffix);
1411
+ }
1412
+ }
1413
+ }
1414
+ }
1415
+
1416
+ const result = {
1417
+ classes: Array.from(allClasses),
1418
+ variableSuffixes: Array.from(usedVariableSuffixes)
1419
+ };
1420
+
1421
+ return result;
1422
+ } catch (error) {
1423
+ console.error('Error getting used classes:', error);
1424
+ return { classes: [], variableSuffixes: [] };
1425
+ }
1426
+ };
1427
+
1428
+ // Fetch theme content from CSS files
1429
+ TailwindCompiler.prototype.fetchThemeContent = async function () {
1430
+ const themeContents = new Set();
1431
+ const fetchPromises = [];
1432
+
1433
+ // If we haven't discovered CSS files yet, do it now
1434
+ if (this.cssFiles.size === 0) {
1435
+ this.discoverCssFiles();
1436
+ }
1437
+
1438
+ // Process all files concurrently
1439
+ for (const source of this.cssFiles) {
1440
+ const fetchPromise = (async () => {
1441
+ try {
1442
+ let content = '';
1443
+ let needsFetch = true;
1444
+
1445
+ if (source.startsWith('inline:')) {
1446
+ const styleId = source.replace('inline:', '');
1447
+ const styleElement = styleId ?
1448
+ document.getElementById(styleId) :
1449
+ document.querySelector('style');
1450
+ if (styleElement) {
1451
+ content = styleElement.textContent;
1452
+ }
1453
+ needsFetch = false;
1454
+ } else {
1455
+ // Smart caching: use session storage + timestamp approach
1456
+ const cacheKey = source;
1457
+ const cached = this.cssContentCache.get(cacheKey);
1458
+ const now = Date.now();
1459
+
1460
+ // Different cache times based on file source
1461
+ let cacheTime;
1462
+ if (source.includes('manifestjs') || source.includes('jsdelivr') || source.includes('unpkg')) {
1463
+ // CDN files: cache longer (5 minutes for static, 1 minute for dynamic)
1464
+ cacheTime = this.hasScannedStatic ? 60000 : 300000;
1465
+ } else {
1466
+ // Local files: shorter cache (5 seconds for dynamic, 30 seconds for static)
1467
+ cacheTime = this.hasScannedStatic ? 5000 : 30000;
1468
+ }
1469
+
1470
+ if (cached && (now - cached.timestamp) < cacheTime) {
1471
+ content = cached.content;
1472
+ needsFetch = false;
1473
+ }
1474
+
1475
+ if (needsFetch) {
1476
+ // Add timestamp for development cache busting, but keep it minimal
1477
+ const timestamp = Math.floor(now / 1000); // Only changes every second
1478
+ const url = `${source}?t=${timestamp}`;
1479
+
1480
+ const response = await fetch(url);
1481
+
1482
+ if (!response.ok) {
1483
+ console.warn('Failed to fetch stylesheet:', url);
1484
+ return;
1485
+ }
1486
+
1487
+ content = await response.text();
1488
+
1489
+ // Cache the content with timestamp
1490
+ this.cssContentCache.set(cacheKey, {
1491
+ content: content,
1492
+ timestamp: now
1493
+ });
1494
+ }
1495
+ }
1496
+
1497
+ if (content) {
1498
+ themeContents.add(content);
1499
+ }
1500
+ } catch (error) {
1501
+ console.warn(`Error fetching CSS from ${source}:`, error);
1502
+ }
1503
+ })();
1504
+ fetchPromises.push(fetchPromise);
1505
+ }
1506
+
1507
+ // Wait for all fetches to complete
1508
+ await Promise.all(fetchPromises);
1509
+
1510
+ return Array.from(themeContents).join('\n');
1511
+ };
1512
+
1513
+ // Extract CSS variables from CSS text
1514
+ TailwindCompiler.prototype.extractThemeVariables = function (cssText) {
1515
+ const variables = new Map();
1516
+
1517
+ // Extract ALL CSS custom properties from ANY declaration block
1518
+ const varRegex = /--([\w-]+):\s*([^;]+);/g;
1519
+
1520
+ let varMatch;
1521
+ while ((varMatch = varRegex.exec(cssText)) !== null) {
1522
+ const name = varMatch[1];
1523
+ const value = varMatch[2].trim();
1524
+ variables.set(name, value);
1525
+ }
1526
+
1527
+ return variables;
1528
+ };
1529
+
1530
+ // Extract custom utilities from CSS text
1531
+ TailwindCompiler.prototype.extractCustomUtilities = function (cssText) {
1532
+ const utilities = new Map();
1533
+
1534
+ // Helper to ensure CSS is always a string
1535
+ const ensureCssString = (css) => {
1536
+ if (typeof css === 'string') {
1537
+ // Clean up any [object Object] that might have snuck in
1538
+ return css.replace(/\[object Object\](;?\s*)/g, '').trim();
1539
+ }
1540
+ if (css && typeof css === 'object' && css.css) {
1541
+ return ensureCssString(css.css);
1542
+ }
1543
+ const stringified = String(css);
1544
+ // Return empty string instead of [object Object]
1545
+ if (stringified === '[object Object]') {
1546
+ return '';
1547
+ }
1548
+ return stringified;
1549
+ };
1550
+
1551
+ // Extract custom utility classes from CSS
1552
+ // Match: .classname or .!classname (where classname can contain word chars and hyphens)
1553
+ const utilityRegex = /(?:@layer\s+utilities\s*{[^}]*}|^)(?:[^{}]*?)(?:^|\s)(\.!?[\w-]+)\s*{([^}]+)}/gm;
1554
+
1555
+ let match;
1556
+ while ((match = utilityRegex.exec(cssText)) !== null) {
1557
+ const className = match[1].substring(1); // Remove the leading dot
1558
+ const cssRules = match[2].trim();
1559
+
1560
+ // Skip if it's a Tailwind-generated class (check base name without !)
1561
+ const baseClassName = className.startsWith('!') ? className.slice(1) : className;
1562
+ if (this.isTailwindGeneratedClass(baseClassName)) {
1563
+ continue;
1564
+ }
1565
+
1566
+ // Check if CSS rules contain !important
1567
+ const hasImportant = /\s!important/.test(cssRules);
1568
+ const classHasImportantPrefix = className.startsWith('!');
1569
+
1570
+ // Determine final CSS rules
1571
+ let finalCssRules = cssRules;
1572
+ if (classHasImportantPrefix) {
1573
+ // Class has ! prefix, ensure CSS has !important
1574
+ if (!hasImportant) {
1575
+ finalCssRules = cssRules.includes(';') ?
1576
+ cssRules.replace(/;/g, ' !important;') :
1577
+ cssRules + ' !important';
1578
+ }
1579
+ } else {
1580
+ // Class doesn't have ! prefix, remove !important if present
1581
+ if (hasImportant) {
1582
+ finalCssRules = cssRules.replace(/\s!important/g, '');
1583
+ }
1584
+ }
1585
+
1586
+ // Store the utility class with its full name (including ! prefix) as the key
1587
+ // This ensures !col is stored separately from col
1588
+ // Ensure CSS is always a string
1589
+ const cssString = ensureCssString(finalCssRules);
1590
+ if (utilities.has(className)) {
1591
+ const existingRules = utilities.get(className);
1592
+ const existingCssString = ensureCssString(existingRules);
1593
+ utilities.set(className, `${existingCssString}; ${cssString}`);
1594
+ } else {
1595
+ utilities.set(className, cssString);
1596
+ }
1597
+ }
1598
+
1599
+ // Also look for :where() selectors which are common in Manifest utilities
1600
+ // Handle both single class and multiple class selectors
1601
+ // Use a function to properly match braces for nested rules
1602
+ const extractWhereSelectors = (text) => {
1603
+ const matches = [];
1604
+ let i = 0;
1605
+ while (i < text.length) {
1606
+ // Find :where(
1607
+ const whereStart = text.indexOf(':where(', i);
1608
+ if (whereStart === -1) break;
1609
+
1610
+ // Find matching closing paren for :where(
1611
+ let parenDepth = 1;
1612
+ let j = whereStart + 7; // Skip ':where('
1613
+ while (j < text.length && parenDepth > 0) {
1614
+ if (text[j] === '(') parenDepth++;
1615
+ else if (text[j] === ')') parenDepth--;
1616
+ j++;
1617
+ }
1618
+ if (parenDepth > 0) {
1619
+ i = whereStart + 1;
1620
+ continue; // Malformed, skip
1621
+ }
1622
+
1623
+ const selectorContent = text.slice(whereStart + 7, j - 1);
1624
+
1625
+ // Skip whitespace and find opening brace
1626
+ while (j < text.length && /\s/.test(text[j])) j++;
1627
+ if (j >= text.length || text[j] !== '{') {
1628
+ i = whereStart + 1;
1629
+ continue;
1630
+ }
1631
+
1632
+ // Find matching closing brace (handle nested braces)
1633
+ let braceDepth = 1;
1634
+ let blockStart = j + 1;
1635
+ j++;
1636
+ while (j < text.length && braceDepth > 0) {
1637
+ if (text[j] === '{') braceDepth++;
1638
+ else if (text[j] === '}') braceDepth--;
1639
+ j++;
1640
+ }
1641
+ if (braceDepth > 0) {
1642
+ i = whereStart + 1;
1643
+ continue; // Malformed, skip
1644
+ }
1645
+
1646
+ const cssRules = text.slice(blockStart, j - 1).trim();
1647
+ matches.push({ selectorContent, cssRules, fullMatch: text.slice(whereStart, j) });
1648
+ i = j;
1649
+ }
1650
+ return matches;
1651
+ };
1652
+
1653
+ const whereMatches = extractWhereSelectors(cssText);
1654
+ for (const match of whereMatches) {
1655
+ const selectorContent = match.selectorContent;
1656
+ const cssRules = match.cssRules;
1657
+
1658
+ // Check if CSS rules contain !important
1659
+ const hasImportant = /\s!important/.test(cssRules);
1660
+
1661
+ // Extract individual class names from the selector, including those with ! prefix
1662
+ // Match: .classname or .!classname (where classname can contain word chars and hyphens)
1663
+ const classMatches = selectorContent.match(/\.(!?[\w-]+)/g);
1664
+ if (classMatches) {
1665
+ for (const classMatch of classMatches) {
1666
+ const className = classMatch.substring(1); // Remove the leading dot
1667
+
1668
+ // Skip if it's a Tailwind-generated class (but check base name without !)
1669
+ const baseClassName = className.startsWith('!') ? className.slice(1) : className;
1670
+ if (this.isTailwindGeneratedClass(baseClassName)) {
1671
+ continue;
1672
+ }
1673
+
1674
+ // Determine if this class should have !important
1675
+ // Only apply !important if the class name itself starts with ! (e.g., !col)
1676
+ const classHasImportantPrefix = className.startsWith('!');
1677
+ let finalCssRules = cssRules;
1678
+
1679
+ if (classHasImportantPrefix) {
1680
+ // Class has ! prefix, ensure CSS has !important
1681
+ if (!hasImportant) {
1682
+ finalCssRules = cssRules.includes(';') ?
1683
+ cssRules.replace(/;/g, ' !important;') :
1684
+ cssRules + ' !important';
1685
+ }
1686
+ } else {
1687
+ // Class doesn't have ! prefix, remove !important if present
1688
+ if (hasImportant) {
1689
+ finalCssRules = cssRules.replace(/\s!important/g, '');
1690
+ }
1691
+ }
1692
+
1693
+ // Store the class with its full name (including ! prefix) as the key
1694
+ // This ensures !col is stored separately from col
1695
+ // Store as selector-aware object to preserve :where() context for variants
1696
+ // Mark as fullBlock since it contains nested rules
1697
+ const storageKey = className;
1698
+ const originalSelector = `:where(${selectorContent})`;
1699
+ // Ensure CSS is always a string
1700
+ const cssString = ensureCssString(finalCssRules);
1701
+ const value = {
1702
+ selector: originalSelector,
1703
+ css: cssString,
1704
+ fullBlock: true // :where() blocks often contain nested rules
1705
+ };
1706
+
1707
+ // Combine CSS rules if the class already exists
1708
+ if (utilities.has(storageKey)) {
1709
+ const existing = utilities.get(storageKey);
1710
+ // If existing is a string, convert to array format
1711
+ if (typeof existing === 'string') {
1712
+ const existingCssString = ensureCssString(existing);
1713
+ utilities.set(storageKey, [
1714
+ { selector: `.${className}`, css: existingCssString },
1715
+ value
1716
+ ]);
1717
+ } else if (Array.isArray(existing)) {
1718
+ // Check if this selector already exists in the array
1719
+ const found = existing.find(e => e.selector === value.selector);
1720
+ if (found) {
1721
+ // Ensure both are strings before concatenating
1722
+ const foundCss = ensureCssString(found.css);
1723
+ const valueCss = ensureCssString(value.css);
1724
+ found.css = `${foundCss}; ${valueCss}`;
1725
+ } else {
1726
+ existing.push(value);
1727
+ }
1728
+ } else if (existing && existing.selector) {
1729
+ // Convert single object to array
1730
+ if (existing.selector === value.selector) {
1731
+ // Ensure both are strings before concatenating
1732
+ const existingCss = ensureCssString(existing.css);
1733
+ const valueCss = ensureCssString(value.css);
1734
+ existing.css = `${existingCss}; ${valueCss}`;
1735
+ utilities.set(storageKey, [existing]);
1736
+ } else {
1737
+ utilities.set(storageKey, [existing, value]);
1738
+ }
1739
+ }
1740
+ } else {
1741
+ utilities.set(storageKey, [value]);
1742
+ }
1743
+ }
1744
+ }
1745
+ }
1746
+
1747
+ // Fallback: detect classes inside compound selectors (e.g., aside[popover].appear-start { ... })
1748
+ // Use brace matching to handle nested rules properly
1749
+ try {
1750
+ let i = 0;
1751
+ while (i < cssText.length) {
1752
+ // Skip whitespace
1753
+ while (i < cssText.length && /\s/.test(cssText[i])) i++;
1754
+ if (i >= cssText.length) break;
1755
+
1756
+ // Skip at-rules
1757
+ if (cssText[i] === '@') {
1758
+ // Skip to next line or closing brace
1759
+ while (i < cssText.length && cssText[i] !== '\n' && cssText[i] !== '}') i++;
1760
+ continue;
1761
+ }
1762
+
1763
+ // Capture selector up to '{'
1764
+ let selStart = i;
1765
+ while (i < cssText.length && cssText[i] !== '{') i++;
1766
+ if (i >= cssText.length) break;
1767
+ const selector = cssText.slice(selStart, i).trim();
1768
+
1769
+ // Find matching '}' with brace depth (handles nested braces)
1770
+ i++; // skip '{'
1771
+ let depth = 1;
1772
+ let blockStart = i;
1773
+ while (i < cssText.length && depth > 0) {
1774
+ if (cssText[i] === '{') depth++;
1775
+ else if (cssText[i] === '}') depth--;
1776
+ i++;
1777
+ }
1778
+ if (depth > 0) {
1779
+ // Malformed, skip
1780
+ continue;
1781
+ }
1782
+ const cssRules = cssText.slice(blockStart, i - 1).trim();
1783
+
1784
+ // Skip at-rules and keyframes/selectors without classes
1785
+ if (selector.startsWith('@')) continue;
1786
+
1787
+ // Match: .classname or .!classname
1788
+ const classMatches = selector.match(/\.!?[A-Za-z0-9_-]+/g);
1789
+ if (!classMatches) continue;
1790
+
1791
+ // Check if CSS rules contain !important
1792
+ const hasImportant = /\s!important/.test(cssRules);
1793
+
1794
+ for (const classToken of classMatches) {
1795
+ const className = classToken.substring(1); // Remove the leading dot
1796
+
1797
+ // Skip Tailwind-generated or already captured classes (check base name without !)
1798
+ const baseClassName = className.startsWith('!') ? className.slice(1) : className;
1799
+ if (this.isTailwindGeneratedClass(baseClassName)) continue;
1800
+
1801
+ // Determine if this class should have !important
1802
+ const classHasImportantPrefix = className.startsWith('!');
1803
+ let finalCssRules = cssRules;
1804
+
1805
+ if (classHasImportantPrefix) {
1806
+ // Class has ! prefix, ensure CSS has !important
1807
+ if (!hasImportant) {
1808
+ finalCssRules = cssRules.includes(';') ?
1809
+ cssRules.replace(/;/g, ' !important;') :
1810
+ cssRules + ' !important';
1811
+ }
1812
+ } else {
1813
+ // Class doesn't have ! prefix, remove !important if present
1814
+ if (hasImportant) {
1815
+ finalCssRules = cssRules.replace(/\s!important/g, '');
1816
+ }
1817
+ }
1818
+
1819
+ // Store the class with its full name (including ! prefix) as the key
1820
+ // Ensure CSS is always a string
1821
+ const cssString = ensureCssString(finalCssRules);
1822
+ if (utilities.has(className)) {
1823
+ const existingRules = utilities.get(className);
1824
+
1825
+ // Handle arrays properly - don't stringify them
1826
+ if (Array.isArray(existingRules)) {
1827
+ // If existing is an array, add this as a new entry
1828
+ // Create a simple selector entry for the compound selector
1829
+ utilities.set(className, [...existingRules, {
1830
+ selector: `.${className}`,
1831
+ css: cssString,
1832
+ fullBlock: true // Compound selectors often have nested rules
1833
+ }]);
1834
+ } else {
1835
+ // Existing is a string or object - convert to array format
1836
+ const existingCssString = ensureCssString(existingRules);
1837
+ utilities.set(className, [
1838
+ { selector: `.${className}`, css: existingCssString },
1839
+ { selector: `.${className}`, css: cssString, fullBlock: true }
1840
+ ]);
1841
+ }
1842
+ } else {
1843
+ utilities.set(className, cssString);
1844
+ }
1845
+ }
1846
+ }
1847
+ } catch (e) {
1848
+ // Be tolerant: this is a best-effort extractor
1849
+ }
1850
+
1851
+ // Universal fallback with basic nesting resolution for selectors using '&'
1852
+ // Captures context like :where(aside[popover]) &.appear-start &:not(:popover-open)
1853
+ try {
1854
+ const rules = [];
1855
+
1856
+ // Minimal nested CSS resolver: scans and builds combined selectors
1857
+ // Only stores top-level rules with their full block content (including nested blocks)
1858
+ const resolveNested = (text, parentSelector = '', isTopLevel = true) => {
1859
+ let i = 0;
1860
+ while (i < text.length) {
1861
+ // Skip whitespace
1862
+ while (i < text.length && /\s/.test(text[i])) i++;
1863
+ if (i >= text.length) break;
1864
+
1865
+ // Capture selector up to '{'
1866
+ let selStart = i;
1867
+ while (i < text.length && text[i] !== '{') i++;
1868
+ if (i >= text.length) break;
1869
+ const rawSelector = text.slice(selStart, i).trim();
1870
+
1871
+ // Find matching '}' with brace depth
1872
+ i++; // skip '{'
1873
+ let depth = 1;
1874
+ let blockStart = i;
1875
+ while (i < text.length && depth > 0) {
1876
+ if (text[i] === '{') depth++;
1877
+ else if (text[i] === '}') depth--;
1878
+ i++;
1879
+ }
1880
+ const block = text.slice(blockStart, i - 1);
1881
+
1882
+ // Build combined selector by replacing '&' with parentSelector
1883
+ const combinedSelector = parentSelector
1884
+ ? rawSelector.replace(/&/g, parentSelector).trim()
1885
+ : rawSelector.trim();
1886
+
1887
+ // Only store top-level rules (not nested blocks) with their full content
1888
+ // This preserves @starting-style and other nested at-rules in the CSS
1889
+ if (isTopLevel) {
1890
+ const fullBlock = block.trim();
1891
+ if (fullBlock) {
1892
+ rules.push({ selector: combinedSelector, css: fullBlock, fullBlock: true });
1893
+ }
1894
+ }
1895
+
1896
+ // Recurse into nested blocks but don't store them separately
1897
+ // They're already included in the parent block's CSS
1898
+ resolveNested(block, combinedSelector, false);
1899
+ }
1900
+ };
1901
+
1902
+ resolveNested(cssText, '');
1903
+
1904
+ // Map resolved rules to utilities by class token presence
1905
+ for (const rule of rules) {
1906
+ // Clean selector: strip comments and normalize whitespace
1907
+ let cleanedSelector = rule.selector.replace(/\/\*[^]*?\*\//g, '').replace(/\s+/g, ' ').trim();
1908
+ // Match: .classname or .!classname
1909
+ const classTokens = cleanedSelector.match(/\.!?[A-Za-z0-9_-]+/g);
1910
+ if (!classTokens) continue;
1911
+
1912
+ // Check if CSS rules contain !important
1913
+ const hasImportant = /\s!important/.test(rule.css);
1914
+
1915
+ for (const token of classTokens) {
1916
+ const className = token.slice(1); // Remove the leading dot
1917
+
1918
+ // Skip Tailwind-generated classes (check base name without !)
1919
+ const baseClassName = className.startsWith('!') ? className.slice(1) : className;
1920
+ if (this.isTailwindGeneratedClass(baseClassName)) continue;
1921
+
1922
+ // Determine if this class should have !important
1923
+ const classHasImportantPrefix = className.startsWith('!');
1924
+ let finalCss = rule.css;
1925
+
1926
+ if (classHasImportantPrefix) {
1927
+ // Class has ! prefix, ensure CSS has !important
1928
+ if (!hasImportant) {
1929
+ finalCss = rule.css.includes(';') ?
1930
+ rule.css.replace(/;/g, ' !important;') :
1931
+ rule.css + ' !important';
1932
+ }
1933
+ } else {
1934
+ // Class doesn't have ! prefix, remove !important if present
1935
+ if (hasImportant) {
1936
+ finalCss = rule.css.replace(/\s!important/g, '');
1937
+ }
1938
+ }
1939
+
1940
+ // Store selector-aware utility so variants preserve context and pseudos
1941
+ // Use full className (including !) as the key
1942
+ // Preserve fullBlock flag if present
1943
+ // Ensure CSS is always a string
1944
+ const cssString = ensureCssString(finalCss);
1945
+ const value = {
1946
+ selector: cleanedSelector,
1947
+ css: cssString,
1948
+ fullBlock: rule.fullBlock || false
1949
+ };
1950
+ if (utilities.has(className)) {
1951
+ const existing = utilities.get(className);
1952
+ if (typeof existing === 'string') {
1953
+ const existingCssString = ensureCssString(existing);
1954
+ utilities.set(className, [{ selector: `.${className}`, css: existingCssString }, value]);
1955
+ } else if (Array.isArray(existing)) {
1956
+ const found = existing.find(e => e.selector === value.selector);
1957
+ if (found) {
1958
+ // Ensure both are strings before concatenating
1959
+ const foundCss = ensureCssString(found.css);
1960
+ const valueCss = ensureCssString(value.css);
1961
+ found.css = `${foundCss}; ${valueCss}`;
1962
+ } else {
1963
+ existing.push(value);
1964
+ }
1965
+ } else if (existing && existing.selector) {
1966
+ if (existing.selector === value.selector) {
1967
+ // Ensure both are strings before concatenating
1968
+ const existingCss = ensureCssString(existing.css);
1969
+ const valueCss = ensureCssString(value.css);
1970
+ existing.css = `${existingCss}; ${valueCss}`;
1971
+ utilities.set(className, [existing]);
1972
+ } else {
1973
+ utilities.set(className, [existing, value]);
1974
+ }
1975
+ }
1976
+ } else {
1977
+ utilities.set(className, [value]);
1978
+ }
1979
+ }
1980
+ }
1981
+ } catch (e) {
1982
+ // Tolerate parsing errors; this is best-effort
1983
+ }
1984
+
1985
+ return utilities;
1986
+ };
1987
+
1988
+ // Check if a class name looks like a Tailwind-generated class
1989
+ TailwindCompiler.prototype.isTailwindGeneratedClass = function (className) {
1990
+ // Check if this looks like a Tailwind-generated class
1991
+ const tailwindPatterns = [
1992
+ /^[a-z]+-\d+$/, // spacing, sizing classes like p-4, w-10
1993
+ /^[a-z]+-\[/, // arbitrary values like w-[100px]
1994
+ /^(text|bg|border|ring|shadow|opacity|scale|rotate|translate|skew|origin|transform|transition|duration|delay|ease|animate|backdrop|blur|brightness|contrast|drop-shadow|grayscale|hue-rotate|invert|saturate|sepia|filter|backdrop-)/, // common Tailwind prefixes
1995
+ /^(sm|md|lg|xl|2xl):/, // responsive prefixes
1996
+ /^(hover|focus|active|disabled|group-hover|group-focus|peer-hover|peer-focus):/, // state prefixes
1997
+ /^(dark|light):/, // theme prefixes
1998
+ /^!/, // important modifier
1999
+ /^\[/, // arbitrary selectors
2000
+ ];
2001
+
2002
+ return tailwindPatterns.some(pattern => pattern.test(className));
2003
+ };
2004
+
2005
+ // Parse a class name into its components (variants, base class, important)
2006
+ TailwindCompiler.prototype.parseClassName = function (className) {
2007
+ // Check cache first
2008
+ if (this.classCache.has(className)) {
2009
+ return this.classCache.get(className);
2010
+ }
2011
+
2012
+ const result = {
2013
+ important: className.startsWith('!'),
2014
+ variants: [],
2015
+ baseClass: className
2016
+ };
2017
+
2018
+ // Remove important modifier if present
2019
+ if (result.important) {
2020
+ className = className.slice(1);
2021
+ }
2022
+
2023
+ // Split by variant separator, but preserve content within brackets
2024
+ const parts = [];
2025
+ let current = '';
2026
+ let bracketDepth = 0;
2027
+
2028
+ for (let i = 0; i < className.length; i++) {
2029
+ const char = className[i];
2030
+
2031
+ if (char === '[') {
2032
+ bracketDepth++;
2033
+ } else if (char === ']') {
2034
+ bracketDepth--;
2035
+ }
2036
+
2037
+ if (char === ':' && bracketDepth === 0) {
2038
+ parts.push(current);
2039
+ current = '';
2040
+ } else {
2041
+ current += char;
2042
+ }
2043
+ }
2044
+ parts.push(current); // Add the last part
2045
+
2046
+ result.baseClass = parts.pop(); // Last part is always the base class
2047
+
2048
+ // Process variants in order (left to right)
2049
+ result.variants = parts.map(variant => {
2050
+ // FIRST: Check for exact variant matches (most common case)
2051
+ const exactSelector = this.variants[variant];
2052
+ if (exactSelector) {
2053
+ return {
2054
+ name: variant,
2055
+ selector: exactSelector,
2056
+ isArbitrary: false
2057
+ };
2058
+ }
2059
+
2060
+ // SECOND: Check for arbitrary selector variants [&_selector]
2061
+ if (variant.startsWith('[') && variant.endsWith(']')) {
2062
+ const arbitrarySelector = variant.slice(1, -1); // Remove brackets
2063
+ if (arbitrarySelector.startsWith('&')) {
2064
+ return {
2065
+ name: variant,
2066
+ selector: arbitrarySelector,
2067
+ isArbitrary: true
2068
+ };
2069
+ }
2070
+ // Handle arbitrary data variants like [data-state=active]
2071
+ if (arbitrarySelector.startsWith('data-')) {
2072
+ return {
2073
+ name: variant,
2074
+ selector: `[${arbitrarySelector}] &`,
2075
+ isArbitrary: true
2076
+ };
2077
+ }
2078
+ }
2079
+
2080
+ // THIRD: Handle parameterized variants (only if exact match not found)
2081
+
2082
+ // Handle data-[...] variants like data-[state=active] (not starting with bracket)
2083
+ // These should be regular attribute selectors, not arbitrary
2084
+ const dataMatch = variant.match(/^data-\[(.+)\]$/);
2085
+ if (dataMatch) {
2086
+ const attrValue = dataMatch[1];
2087
+ // Add quotes around the value if it contains = (e.g., state=active -> state="active")
2088
+ const quotedValue = attrValue.includes('=')
2089
+ ? attrValue.replace(/^([^=]+)=(.+)$/, '$1="$2"')
2090
+ : attrValue;
2091
+ return {
2092
+ name: variant,
2093
+ selector: `[data-${quotedValue}] &`,
2094
+ isArbitrary: false
2095
+ };
2096
+ }
2097
+
2098
+ // Handle parameterized nth variants: nth-3, nth-last-2, nth-of-type-2, nth-last-of-type-2
2099
+ const nthMatch = variant.match(/^(nth|nth-last|nth-of-type|nth-last-of-type)-(\d+)$/);
2100
+ if (nthMatch) {
2101
+ const baseVariant = nthMatch[1];
2102
+ const param = nthMatch[2];
2103
+ const baseSelector = this.variants[baseVariant];
2104
+ if (baseSelector) {
2105
+ return {
2106
+ name: variant,
2107
+ selector: `${baseSelector}(${param})`,
2108
+ isArbitrary: false
2109
+ };
2110
+ }
2111
+ }
2112
+
2113
+ // Handle parameterized has/not variants: has-[>p], not-\[hidden\]
2114
+ const hasMatch = variant.match(/^has-\[(.+)\]$/);
2115
+ if (hasMatch) {
2116
+ const param = hasMatch[1];
2117
+ const baseSelector = this.variants['has'];
2118
+ if (baseSelector) {
2119
+ return {
2120
+ name: variant,
2121
+ selector: `${baseSelector}(${param})`,
2122
+ isArbitrary: false
2123
+ };
2124
+ }
2125
+ }
2126
+
2127
+ // Handle not-\[...\] variants like not-\[hidden\]
2128
+ // The backslashes escape the brackets in the class name
2129
+ // Match: not-\[content\] where content may have escaped brackets
2130
+ const notMatch = variant.match(/^not-\\\[(.+?)\\\]$/);
2131
+ if (notMatch) {
2132
+ const param = notMatch[1];
2133
+ // The parameter is already clean (backslashes were just for escaping brackets in class name)
2134
+ const baseSelector = this.variants['not'];
2135
+ if (baseSelector) {
2136
+ return {
2137
+ name: variant,
2138
+ selector: `${baseSelector}([${param}])`,
2139
+ isArbitrary: false
2140
+ };
2141
+ }
2142
+ }
2143
+
2144
+ // If no match found, warn and return null
2145
+ console.warn(`Unknown variant: ${variant}`);
2146
+ return null;
2147
+ }).filter(Boolean);
2148
+
2149
+ // Cache the result
2150
+ this.classCache.set(className, result);
2151
+ return result;
2152
+ };
2153
+
2154
+
2155
+
2156
+ // Compilation methods
2157
+ // Main compilation logic and utility generation
2158
+
2159
+ // Generate utilities from CSS variables
2160
+ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedData) {
2161
+ try {
2162
+ const utilities = [];
2163
+ const generatedRules = new Set(); // Track generated rules to prevent duplicates
2164
+ const variables = this.extractThemeVariables(cssText);
2165
+ const { classes: usedClasses, variableSuffixes } = usedData;
2166
+
2167
+ if (variables.size === 0) {
2168
+ return '';
2169
+ }
2170
+
2171
+ // Helper to escape special characters in class names
2172
+ const escapeClassName = (className) => {
2173
+ return className.replace(/[^a-zA-Z0-9-]/g, '\\$&');
2174
+ };
2175
+
2176
+ // Helper to generate a single utility with its variants
2177
+ const generateUtility = (baseClass, css) => {
2178
+ // Find all variants of this base class that are actually used
2179
+ const usedVariants = usedClasses
2180
+ .filter(cls => {
2181
+ const parts = cls.split(':');
2182
+ const basePart = parts[parts.length - 1];
2183
+ return basePart === baseClass || (basePart.startsWith('!') && basePart.slice(1) === baseClass);
2184
+ });
2185
+
2186
+ // Generate base utility if it's used directly
2187
+ if (usedClasses.includes(baseClass)) {
2188
+ const rule = `.${escapeClassName(baseClass)} { ${css} }`;
2189
+ if (!generatedRules.has(rule)) {
2190
+ utilities.push(rule);
2191
+ generatedRules.add(rule);
2192
+ }
2193
+ }
2194
+ // Generate important version if used
2195
+ if (usedClasses.includes('!' + baseClass)) {
2196
+ const importantCss = css.includes(';') ?
2197
+ css.replace(/;/g, ' !important;') :
2198
+ css + ' !important';
2199
+ const rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
2200
+ if (!generatedRules.has(rule)) {
2201
+ utilities.push(rule);
2202
+ generatedRules.add(rule);
2203
+ }
2204
+ }
2205
+
2206
+ // Generate each variant as a separate class
2207
+ for (const variantClass of usedVariants) {
2208
+ if (variantClass === baseClass) continue;
2209
+
2210
+ const parsed = this.parseClassName(variantClass);
2211
+
2212
+ // Check if this is an important variant
2213
+ const isImportant = parsed.important;
2214
+ // Ensure css is a string (handle cases where it might be an object)
2215
+ const cssString = typeof css === 'string' ? css : (css && typeof css === 'object' && css.css ? css.css : String(css));
2216
+ const cssContent = isImportant ?
2217
+ (cssString.includes(';') ? cssString.replace(/;/g, ' !important;') : cssString + ' !important') :
2218
+ cssString;
2219
+
2220
+ // Build selector by applying variants
2221
+ let selector = `.${escapeClassName(variantClass)}`;
2222
+ let hasMediaQuery = false;
2223
+ let mediaQueryRule = '';
2224
+ let nestedSelector = null; // For variants that end with & (CSS nesting)
2225
+
2226
+ for (const variant of parsed.variants) {
2227
+ if (variant.isArbitrary) {
2228
+ // Handle arbitrary selectors like [&_figure] or [&_fieldset:has(legend):not(.whatever)]
2229
+ // For selectors starting with &, replace & with the base class and use as regular selector
2230
+ let arbitrarySelector = variant.selector;
2231
+
2232
+ if (arbitrarySelector.startsWith('&')) {
2233
+ // Replace & with the base class selector and convert _ to spaces
2234
+ arbitrarySelector = arbitrarySelector.replace(/_/g, ' ').replace(/&/g, selector);
2235
+ selector = arbitrarySelector;
2236
+ } else {
2237
+ // For other arbitrary selectors (like data attributes), use nested CSS
2238
+ arbitrarySelector = arbitrarySelector.replace(/_/g, ' ');
2239
+ selector = { baseClass: selector, arbitrarySelector };
2240
+ }
2241
+ } else if (variant.selector.includes('&')) {
2242
+ // Check if selector ends with & (indicates CSS nesting)
2243
+ if (variant.selector.trim().endsWith('&')) {
2244
+ // This is a nested selector - move & to the beginning for CSS nesting
2245
+ const selectorWithoutAmpersand = variant.selector.trim().slice(0, -1).trim();
2246
+ const nestedSelectorText = `&${selectorWithoutAmpersand}`;
2247
+ nestedSelector = nestedSelector ? `${nestedSelector} ${nestedSelectorText}` : nestedSelectorText;
2248
+ } else {
2249
+ // Handle variants like .dark &, .light &, .group &, etc.
2250
+ // Replace & with the actual selector
2251
+ const replacedSelector = variant.selector.replace(/&/g, selector);
2252
+ selector = replacedSelector;
2253
+ }
2254
+ } else if (variant.selector.startsWith(':')) {
2255
+ // For pseudo-classes, append to selector
2256
+ selector = `${selector}${variant.selector}`;
2257
+ } else if (variant.selector.startsWith('@')) {
2258
+ // For media queries, wrap the whole rule
2259
+ hasMediaQuery = true;
2260
+ mediaQueryRule = variant.selector;
2261
+ }
2262
+ }
2263
+
2264
+ // Generate the final rule
2265
+ let rule;
2266
+ if (typeof selector === 'object' && selector.arbitrarySelector) {
2267
+ // Handle arbitrary selectors with nested CSS (for non-& selectors)
2268
+ rule = `${selector.baseClass} {\n ${selector.arbitrarySelector} {\n ${cssContent}\n }\n}`;
2269
+ } else if (nestedSelector) {
2270
+ // Handle nested selectors (variants ending with &)
2271
+ rule = `${selector} {\n ${nestedSelector} {\n ${cssContent}\n }\n}`;
2272
+ } else {
2273
+ // Regular selector
2274
+ rule = `${selector} { ${cssContent} }`;
2275
+ }
2276
+
2277
+ const finalRule = hasMediaQuery ?
2278
+ `${mediaQueryRule} { ${rule} }` :
2279
+ rule;
2280
+
2281
+ if (!generatedRules.has(finalRule)) {
2282
+ utilities.push(finalRule);
2283
+ generatedRules.add(finalRule);
2284
+ }
2285
+ }
2286
+ };
2287
+
2288
+ // Generate utilities based on variable prefix
2289
+ for (const [varName, varValue] of variables.entries()) {
2290
+ if (!varName.match(this.regexPatterns.tailwindPrefix)) {
2291
+ continue;
2292
+ }
2293
+
2294
+ const suffix = varName.split('-').slice(1).join('-');
2295
+ const value = `var(--${varName})`;
2296
+ const prefix = varName.split('-')[0] + '-';
2297
+ const generator = this.utilityGenerators[prefix];
2298
+
2299
+ if (generator) {
2300
+ const utilityPairs = generator(suffix, value);
2301
+ for (const [className, css] of utilityPairs) {
2302
+ // Check if this specific utility class is actually used (including variants and important)
2303
+ const isUsed = usedClasses.some(cls => {
2304
+ // Parse the class to extract the base utility name
2305
+ const parsed = this.parseClassName(cls);
2306
+ const baseClass = parsed.baseClass;
2307
+
2308
+ // Check both normal and important versions
2309
+ return baseClass === className ||
2310
+ baseClass === '!' + className ||
2311
+ (baseClass.startsWith('!') && baseClass.slice(1) === className);
2312
+ });
2313
+ if (isUsed) {
2314
+ generateUtility(className, css);
2315
+ }
2316
+
2317
+ // Check for opacity variants of this utility
2318
+ const opacityVariants = usedClasses.filter(cls => {
2319
+ // Parse the class to extract the base utility name
2320
+ const parsed = this.parseClassName(cls);
2321
+ const baseClass = parsed.baseClass;
2322
+
2323
+ // Check if this class has an opacity modifier and matches our base class
2324
+ if (baseClass.includes('/')) {
2325
+ const baseWithoutOpacity = baseClass.split('/')[0];
2326
+ if (baseWithoutOpacity === className) {
2327
+ const opacity = baseClass.split('/')[1];
2328
+ // Validate that the opacity is a number between 0-100
2329
+ return !isNaN(opacity) && opacity >= 0 && opacity <= 100;
2330
+ }
2331
+ }
2332
+ return false;
2333
+ });
2334
+
2335
+ // Generate opacity utilities for each variant found
2336
+ for (const variant of opacityVariants) {
2337
+ const opacity = variant.split('/')[1];
2338
+ const opacityValue = `color-mix(in oklch, ${value} ${opacity}%, transparent)`;
2339
+ const opacityCss = css.replace(value, opacityValue);
2340
+ generateUtility(variant, opacityCss);
2341
+ }
2342
+ }
2343
+ }
2344
+ }
2345
+
2346
+ return utilities.join('\n');
2347
+ } catch (error) {
2348
+ console.error('Error generating utilities:', error);
2349
+ return '';
2350
+ }
2351
+ };
2352
+
2353
+ // Generate custom utilities from discovered custom utility classes
2354
+ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2355
+ try {
2356
+ const utilities = [];
2357
+ const generatedRules = new Set();
2358
+ const { classes: usedClasses } = usedData;
2359
+
2360
+ // Helper to clean up [object Object] from CSS strings
2361
+ const cleanCssString = (css) => {
2362
+ if (typeof css !== 'string') return css;
2363
+ return css.replace(/\[object Object\](;?\s*)/g, '').trim();
2364
+ };
2365
+
2366
+ if (this.customUtilities.size === 0) {
2367
+ return '';
2368
+ }
2369
+
2370
+ // Helper to escape special characters in class names
2371
+ const escapeClassName = (className) => {
2372
+ return className.replace(/[^a-zA-Z0-9-]/g, '\\$&');
2373
+ };
2374
+
2375
+ // Helper to replace & in CSS selectors (not in property values or comments)
2376
+ // IMPORTANT: For CSS nesting, we should NOT replace & in nested selectors
2377
+ // The & should remain as-is so CSS nesting works correctly
2378
+ // This function should only be used for legacy/flattened CSS, not nested CSS
2379
+ const replaceAmpersandInSelectors = (cssText, replacement) => {
2380
+ // For full blocks with nested rules, don't replace & at all - preserve CSS nesting
2381
+ // Check if this looks like nested CSS:
2382
+ // - Has & followed by :, ., [, or whitespace then { (nested selector)
2383
+ // - Has ] & (attribute selector followed by &, like [dir=rtl] &)
2384
+ // - Has & on its own line followed by :, ., or [ (common nested pattern)
2385
+ const hasNestedSelectors =
2386
+ /&\s*[:\.\[{]/.test(cssText) || // &:not(), &::before, &[attr], & {
2387
+ /&\s*\n\s*[:\.\[{]/.test(cssText) || // & on new line followed by selector
2388
+ /\]\s*&/.test(cssText) || // [dir=rtl] & pattern
2389
+ /&\s*$/.test(cssText.split('\n').find(line => line.trim().startsWith('&')) || ''); // & on its own line
2390
+
2391
+ if (hasNestedSelectors) {
2392
+ // This is nested CSS - don't replace &, preserve it as-is
2393
+ return cssText;
2394
+ }
2395
+
2396
+ // Legacy behavior: replace & for flattened CSS (shouldn't be needed for nested CSS)
2397
+ let result = '';
2398
+ let i = 0;
2399
+ let inString = false;
2400
+ let stringChar = '';
2401
+ let inComment = false;
2402
+
2403
+ while (i < cssText.length) {
2404
+ const char = cssText[i];
2405
+ const nextChar = i + 1 < cssText.length ? cssText[i + 1] : '';
2406
+ const prevChar = i > 0 ? cssText[i - 1] : '';
2407
+
2408
+ // Handle strings
2409
+ if ((char === '"' || char === "'") && !inComment) {
2410
+ if (!inString) {
2411
+ inString = true;
2412
+ stringChar = char;
2413
+ } else if (char === stringChar && prevChar !== '\\') {
2414
+ inString = false;
2415
+ stringChar = '';
2416
+ }
2417
+ result += char;
2418
+ i++;
2419
+ continue;
2420
+ }
2421
+
2422
+ if (inString) {
2423
+ result += char;
2424
+ i++;
2425
+ continue;
2426
+ }
2427
+
2428
+ // Handle comments
2429
+ if (char === '/' && nextChar === '*') {
2430
+ inComment = true;
2431
+ result += char;
2432
+ i++;
2433
+ continue;
2434
+ }
2435
+
2436
+ if (inComment) {
2437
+ if (char === '*' && nextChar === '/') {
2438
+ inComment = false;
2439
+ result += char;
2440
+ i++;
2441
+ continue;
2442
+ }
2443
+ result += char;
2444
+ i++;
2445
+ continue;
2446
+ }
2447
+
2448
+ // Replace & when it's in a selector context (only for legacy/flattened CSS)
2449
+ if (char === '&') {
2450
+ const lookAhead = cssText.slice(i + 1, Math.min(i + 10, cssText.length));
2451
+ const isSelector =
2452
+ lookAhead.match(/^[:\.\[\+\>~,)]/) ||
2453
+ lookAhead.match(/^\s+[:\.\[\+\>~,{]/) ||
2454
+ lookAhead === '' ||
2455
+ lookAhead[0] === '\n' ||
2456
+ (prevChar === '\n' && (lookAhead[0] === ':' || lookAhead[0] === '.' || lookAhead[0] === '[' || lookAhead[0] === ' '));
2457
+
2458
+ if (isSelector) {
2459
+ result += replacement;
2460
+ i++;
2461
+ continue;
2462
+ }
2463
+ }
2464
+
2465
+ result += char;
2466
+ i++;
2467
+ }
2468
+
2469
+ return result;
2470
+ };
2471
+
2472
+ // Helper to generate a single utility with its variants
2473
+ const generateUtility = (baseClass, css, selectorInfo) => {
2474
+ // Ensure css is a string at the start
2475
+ if (typeof css !== 'string') {
2476
+ css = typeof css === 'object' && css && css.css ? css.css : String(css);
2477
+ }
2478
+
2479
+ // Find all variants of this base class that are actually used
2480
+ const usedVariants = usedClasses
2481
+ .filter(cls => {
2482
+ const parts = cls.split(':');
2483
+ const basePart = parts[parts.length - 1];
2484
+ const isMatch = basePart === baseClass || (basePart.startsWith('!') && basePart.slice(1) === baseClass);
2485
+ return isMatch;
2486
+ });
2487
+
2488
+ // Skip generating base utility - it already exists in the CSS
2489
+ // Only generate variants and important versions
2490
+
2491
+ // Generate important version if used
2492
+ if (usedClasses.includes('!' + baseClass)) {
2493
+ // Check if CSS already has !important to avoid double !important
2494
+ const alreadyHasImportant = /\s!important/.test(css);
2495
+ const importantCss = alreadyHasImportant ? css :
2496
+ (css.includes(';') ?
2497
+ css.replace(/;/g, ' !important;') :
2498
+ css + ' !important');
2499
+ let rule;
2500
+ if (selectorInfo && selectorInfo.selector) {
2501
+ // If the original selector contains :where(), don't try to modify it
2502
+ // Generate a separate rule for the important version
2503
+ // This prevents :where(.row, .col) from becoming :where(.row, .!col) with !important on all
2504
+ if (selectorInfo.selector.includes(':where(')) {
2505
+ // For :where() selectors, generate individual class selector for important version
2506
+ rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
2507
+ } else {
2508
+ // For other contextual selectors, do the replacement
2509
+ const variantSel = `.${escapeClassName('!' + baseClass)}`;
2510
+ let contextual = selectorInfo.selector.replace(new RegExp(`\\.${baseClass}(?=[^a-zA-Z0-9_-]|$)`), variantSel);
2511
+ if (contextual === selectorInfo.selector) {
2512
+ // Fallback: append class to the end if base token not found
2513
+ contextual = `${selectorInfo.selector}${variantSel}`;
2514
+ }
2515
+ rule = `${contextual} { ${importantCss} }`;
2516
+ }
2517
+ } else {
2518
+ rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
2519
+ }
2520
+ if (!generatedRules.has(rule)) {
2521
+ utilities.push(rule);
2522
+ generatedRules.add(rule);
2523
+ }
2524
+ }
2525
+
2526
+ // Generate each variant as a separate class
2527
+ for (const variantClass of usedVariants) {
2528
+ if (variantClass === baseClass) continue;
2529
+
2530
+ const parsed = this.parseClassName(variantClass);
2531
+
2532
+ // Check if this is an important variant
2533
+ const isImportant = parsed.important;
2534
+ // Ensure css is a string (handle cases where it might be an object)
2535
+ const cssString = typeof css === 'string' ? css : (css && typeof css === 'object' && css.css ? css.css : String(css));
2536
+ const cssContent = isImportant ?
2537
+ (cssString.includes(';') ? cssString.replace(/;/g, ' !important;') : cssString + ' !important') :
2538
+ cssString;
2539
+
2540
+ // Build selector by applying variants
2541
+ let selector = `.${escapeClassName(variantClass)}`;
2542
+ let hasMediaQuery = false;
2543
+ let mediaQueryRule = '';
2544
+ let nestedSelector = null; // For variants that end with & (CSS nesting)
2545
+
2546
+ for (const variant of parsed.variants) {
2547
+ if (variant.isArbitrary) {
2548
+ // Handle arbitrary selectors like [&_figure] or [&_fieldset:has(legend):not(.whatever)]
2549
+ // For selectors starting with &, replace & with the base class and use as regular selector
2550
+ let arbitrarySelector = variant.selector;
2551
+
2552
+ if (arbitrarySelector.startsWith('&')) {
2553
+ // Replace & with the base class selector and convert _ to spaces
2554
+ arbitrarySelector = arbitrarySelector.replace(/_/g, ' ').replace(/&/g, selector);
2555
+ selector = arbitrarySelector;
2556
+ } else {
2557
+ // For other arbitrary selectors (like data attributes), use nested CSS
2558
+ arbitrarySelector = arbitrarySelector.replace(/_/g, ' ');
2559
+ selector = { baseClass: selector, arbitrarySelector };
2560
+ }
2561
+ } else if (variant.selector.includes('&')) {
2562
+ // Check if selector ends with & (indicates CSS nesting)
2563
+ if (variant.selector.trim().endsWith('&')) {
2564
+ // This is a nested selector - move & to the beginning for CSS nesting
2565
+ const selectorWithoutAmpersand = variant.selector.trim().slice(0, -1).trim();
2566
+ const nestedSelectorText = `&${selectorWithoutAmpersand}`;
2567
+ nestedSelector = nestedSelector ? `${nestedSelector} ${nestedSelectorText}` : nestedSelectorText;
2568
+ } else {
2569
+ // Handle variants like .dark &, .light &, .group &, etc.
2570
+ // Replace & with the actual selector
2571
+ const replacedSelector = variant.selector.replace(/&/g, selector);
2572
+ selector = replacedSelector;
2573
+ }
2574
+ } else if (variant.selector.startsWith(':')) {
2575
+ // For pseudo-classes, append to selector
2576
+ selector = `${selector}${variant.selector}`;
2577
+ } else if (variant.selector.startsWith('@')) {
2578
+ // For media queries, wrap the whole rule
2579
+ hasMediaQuery = true;
2580
+ mediaQueryRule = variant.selector;
2581
+ }
2582
+ }
2583
+
2584
+ // Generate the final rule
2585
+ let rule;
2586
+ // Ensure cssContent is a string before using it anywhere
2587
+ let cssContentStr = typeof cssContent === 'string' ? cssContent : String(cssContent);
2588
+ // Clean up any [object Object] that might have snuck in
2589
+ cssContentStr = cssContentStr.replace(/\[object Object\](;?\s*)/g, '').trim();
2590
+
2591
+ if (typeof selector === 'object' && selector.arbitrarySelector) {
2592
+ // Handle arbitrary selectors with nested CSS (for non-& selectors)
2593
+ rule = `${selector.baseClass} {\n ${selector.arbitrarySelector} {\n ${cssContentStr}\n }\n}`;
2594
+ } else if (nestedSelector) {
2595
+ // Handle nested selectors (variants ending with &)
2596
+ // Check if CSS is a full block (contains nested blocks like @starting-style)
2597
+ const isFullBlock = selectorInfo && selectorInfo.fullBlock !== undefined ? selectorInfo.fullBlock :
2598
+ (cssContentStr.includes('@starting-style') ||
2599
+ cssContentStr.includes('@media') ||
2600
+ cssContentStr.includes('@supports') ||
2601
+ (cssContentStr.includes('{') && cssContentStr.includes('}')));
2602
+
2603
+ // Regular selector or contextual replacement using original selector info
2604
+ if (selectorInfo && selectorInfo.selector) {
2605
+ // If the original selector contains :where(), don't try to modify it
2606
+ if (selectorInfo.selector.includes(':where(')) {
2607
+ // For :where() selectors, generate individual class selector
2608
+ if (isFullBlock) {
2609
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, selector);
2610
+ rule = `${selector} {\n ${nestedSelector} {\n${resolvedCss}\n }\n}`;
2611
+ } else {
2612
+ rule = `${selector} {\n ${nestedSelector} {\n ${cssContentStr}\n }\n}`;
2613
+ }
2614
+ } else {
2615
+ // For other contextual selectors, do the replacement
2616
+ const contextualRe = new RegExp(`\\.${baseClass}(?=[^a-zA-Z0-9_-]|$)`);
2617
+ let contextual = selectorInfo.selector.replace(contextualRe, selector);
2618
+ if (contextual === selectorInfo.selector) {
2619
+ // Fallback when base token not directly present
2620
+ contextual = `${selectorInfo.selector}${selector}`;
2621
+ }
2622
+ if (isFullBlock) {
2623
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, contextual);
2624
+ rule = `${contextual} {\n ${nestedSelector} {\n${resolvedCss}\n }\n}`;
2625
+ } else {
2626
+ rule = `${contextual} {\n ${nestedSelector} {\n ${cssContentStr}\n }\n}`;
2627
+ }
2628
+ }
2629
+ } else {
2630
+ if (isFullBlock) {
2631
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, selector);
2632
+ rule = `${selector} {\n ${nestedSelector} {\n${resolvedCss}\n }\n}`;
2633
+ } else {
2634
+ rule = `${selector} {\n ${nestedSelector} {\n ${cssContentStr}\n }\n}`;
2635
+ }
2636
+ }
2637
+ } else {
2638
+ // Check if CSS is a full block (contains nested blocks like @starting-style)
2639
+ // Use selectorInfo.fullBlock if available, otherwise check CSS content
2640
+ const isFullBlock = selectorInfo && selectorInfo.fullBlock !== undefined ? selectorInfo.fullBlock :
2641
+ (cssContentStr.includes('@starting-style') ||
2642
+ cssContentStr.includes('@media') ||
2643
+ cssContentStr.includes('@supports') ||
2644
+ (cssContentStr.includes('{') && cssContentStr.includes('}')));
2645
+
2646
+ // Regular selector or contextual replacement using original selector info
2647
+ if (selectorInfo && selectorInfo.selector) {
2648
+ // If the original selector contains :where(), don't try to modify it
2649
+ // Instead, generate a separate rule for this specific class
2650
+ // This prevents issues where :where(.row, .col) would become :where(.row, .!col)
2651
+ // with !important applied to all classes
2652
+ if (selectorInfo.selector.includes(':where(')) {
2653
+ // For :where() selectors, generate individual class selector
2654
+ // This ensures !important is only applied to the specific class
2655
+ if (isFullBlock) {
2656
+ // Full block CSS includes nested content with & references
2657
+ // Replace & in selectors with the actual selector (handles all selector contexts)
2658
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, selector);
2659
+ rule = `${selector} {\n${resolvedCss}\n}`;
2660
+ } else {
2661
+ rule = `${selector} { ${cssContentStr} }`;
2662
+ }
2663
+ } else {
2664
+ // For other contextual selectors, do the replacement
2665
+ const contextualRe = new RegExp(`\\.${baseClass}(?=[^a-zA-Z0-9_-]|$)`);
2666
+ let contextual = selectorInfo.selector.replace(contextualRe, selector);
2667
+ if (contextual === selectorInfo.selector) {
2668
+ // Fallback when base token not directly present
2669
+ contextual = `${selectorInfo.selector}${selector}`;
2670
+ }
2671
+ if (isFullBlock) {
2672
+ // Replace & in selectors with the contextual selector (handles all selector contexts)
2673
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, contextual);
2674
+ rule = `${contextual} {\n${resolvedCss}\n}`;
2675
+ } else {
2676
+ rule = `${contextual} { ${cssContentStr} }`;
2677
+ }
2678
+ }
2679
+ } else {
2680
+ if (isFullBlock) {
2681
+ // Replace & in selectors with the actual selector (handles all selector contexts)
2682
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, selector);
2683
+ rule = `${selector} {\n${resolvedCss}\n}`;
2684
+ } else {
2685
+ rule = `${selector} { ${cssContentStr} }`;
2686
+ }
2687
+ }
2688
+ }
2689
+
2690
+ let finalRule;
2691
+ if (hasMediaQuery) {
2692
+ // Wrap once for responsive variants unless the rule already contains @media
2693
+ if (typeof rule === 'string' && rule.trim().startsWith('@media')) {
2694
+ finalRule = rule;
2695
+ } else {
2696
+ finalRule = `${mediaQueryRule} { ${rule} }`;
2697
+ }
2698
+ } else {
2699
+ finalRule = rule;
2700
+ }
2701
+
2702
+ if (!generatedRules.has(finalRule)) {
2703
+ utilities.push(finalRule);
2704
+ generatedRules.add(finalRule);
2705
+ }
2706
+ }
2707
+ };
2708
+
2709
+ // Generate utilities for each custom class that's actually used
2710
+ for (const [className, cssOrSelector] of this.customUtilities.entries()) {
2711
+ // Normalize class name: if it starts with !, extract the base name
2712
+ const hasImportantPrefix = className.startsWith('!');
2713
+ const baseClassName = hasImportantPrefix ? className.slice(1) : className;
2714
+
2715
+ // Check if this specific utility class is actually used (including variants and important)
2716
+ const isUsed = usedClasses.some(cls => {
2717
+ // Parse the class to extract the base utility name
2718
+ const parsed = this.parseClassName(cls);
2719
+ const baseClass = parsed.baseClass;
2720
+
2721
+ // Check both normal and important versions
2722
+ return baseClass === className ||
2723
+ baseClass === baseClassName ||
2724
+ baseClass === '!' + baseClassName ||
2725
+ (baseClass.startsWith('!') && baseClass.slice(1) === baseClassName);
2726
+ });
2727
+
2728
+ if (isUsed) {
2729
+ // Normalize CSS: if className has ! prefix, the CSS should already have !important
2730
+ // But we need to pass the base class name to generateUtility
2731
+ let normalizedCss = cssOrSelector;
2732
+ if (typeof cssOrSelector === 'string') {
2733
+ normalizedCss = cleanCssString(cssOrSelector);
2734
+ } else if (Array.isArray(cssOrSelector)) {
2735
+ // For arrays, we'll handle each entry separately below
2736
+ } else if (cssOrSelector && cssOrSelector.css) {
2737
+ // Ensure we extract the CSS string, not an object
2738
+ const extracted = typeof cssOrSelector.css === 'string' ? cssOrSelector.css :
2739
+ (cssOrSelector.css && typeof cssOrSelector.css === 'object' && cssOrSelector.css.css ? cssOrSelector.css.css :
2740
+ String(cssOrSelector.css));
2741
+ normalizedCss = cleanCssString(extracted);
2742
+ } else {
2743
+ // Fallback: convert to string
2744
+ normalizedCss = cleanCssString(String(cssOrSelector));
2745
+ }
2746
+
2747
+ // Generate utility with base class name (without !)
2748
+ // The CSS already has !important if className started with !
2749
+ if (typeof cssOrSelector === 'string') {
2750
+ generateUtility(baseClassName, normalizedCss, null);
2751
+ } else if (Array.isArray(cssOrSelector)) {
2752
+ for (const entry of cssOrSelector) {
2753
+ if (entry && entry.css && entry.selector) {
2754
+ // Ensure entry.css is a string (not an object)
2755
+ const extracted = typeof entry.css === 'string' ? entry.css :
2756
+ (entry.css && typeof entry.css === 'object' && entry.css.css ? entry.css.css :
2757
+ String(entry.css));
2758
+ const entryCss = cleanCssString(extracted);
2759
+
2760
+ generateUtility(baseClassName, entryCss, {
2761
+ selector: entry.selector,
2762
+ fullBlock: entry.fullBlock || false
2763
+ });
2764
+ }
2765
+ }
2766
+ } else if (cssOrSelector && cssOrSelector.css && cssOrSelector.selector) {
2767
+ // Ensure cssOrSelector.css is a string (not an object)
2768
+ const extracted = typeof cssOrSelector.css === 'string' ? cssOrSelector.css :
2769
+ (cssOrSelector.css && typeof cssOrSelector.css === 'object' && cssOrSelector.css.css ? cssOrSelector.css.css :
2770
+ String(cssOrSelector.css));
2771
+ const selectorCss = cleanCssString(extracted);
2772
+
2773
+ generateUtility(baseClassName, selectorCss, {
2774
+ selector: cssOrSelector.selector,
2775
+ fullBlock: cssOrSelector.fullBlock || false
2776
+ });
2777
+ }
2778
+ }
2779
+ }
2780
+
2781
+ return utilities.join('\n');
2782
+ } catch (error) {
2783
+ console.error('Error generating custom utilities:', error);
2784
+ return '';
2785
+ }
2786
+ };
2787
+
2788
+ // Main compilation method
2789
+ TailwindCompiler.prototype.compile = async function () {
2790
+ const compileStart = performance.now();
2791
+
2792
+ try {
2793
+ // Prevent too frequent compilations
2794
+ const now = Date.now();
2795
+ if (now - this.lastCompileTime < this.minCompileInterval) {
2796
+ return;
2797
+ }
2798
+ this.lastCompileTime = now;
2799
+
2800
+ if (this.isCompiling) {
2801
+ return;
2802
+ }
2803
+ this.isCompiling = true;
2804
+
2805
+ // On first run, scan static classes and CSS variables
2806
+ if (!this.hasScannedStatic) {
2807
+ await this.scanStaticClasses();
2808
+
2809
+ // Fetch CSS content once for initial compilation
2810
+ const themeCss = await this.fetchThemeContent();
2811
+ if (themeCss) {
2812
+ // Extract and cache custom utilities
2813
+ const discoveredCustomUtilities = this.extractCustomUtilities(themeCss);
2814
+ for (const [name, value] of discoveredCustomUtilities.entries()) {
2815
+ this.customUtilities.set(name, value);
2816
+ }
2817
+
2818
+ const variables = this.extractThemeVariables(themeCss);
2819
+ for (const [name, value] of variables.entries()) {
2820
+ this.currentThemeVars.set(name, value);
2821
+ }
2822
+
2823
+ // Generate utilities for all static classes
2824
+ const staticUsedData = {
2825
+ classes: Array.from(this.staticClassCache),
2826
+ variableSuffixes: []
2827
+ };
2828
+ // Process static classes for variable suffixes
2829
+ for (const cls of this.staticClassCache) {
2830
+ const parts = cls.split(':');
2831
+ const baseClass = parts[parts.length - 1];
2832
+ const classParts = baseClass.split('-');
2833
+ if (classParts.length > 1) {
2834
+ staticUsedData.variableSuffixes.push(classParts.slice(1).join('-'));
2835
+ }
2836
+ }
2837
+
2838
+ // Generate both variable-based and custom utilities
2839
+ const varUtilities = this.generateUtilitiesFromVars(themeCss, staticUsedData);
2840
+ const customUtilitiesGenerated = this.generateCustomUtilities(staticUsedData);
2841
+
2842
+ const allUtilities = [varUtilities, customUtilitiesGenerated].filter(Boolean).join('\n\n');
2843
+ if (allUtilities) {
2844
+ const finalCss = `@layer utilities {\n${allUtilities}\n}`;
2845
+
2846
+ // Read critical styles BEFORE clearing (they'll be merged into final CSS)
2847
+ const criticalCss = this.criticalStyleElement && this.criticalStyleElement.textContent ?
2848
+ `\n\n/* Critical utilities (non-layer, will be overridden by layer utilities) */\n${this.criticalStyleElement.textContent}` :
2849
+ '';
2850
+
2851
+ this.styleElement.textContent = finalCss + criticalCss;
2852
+
2853
+ // Clear critical styles AFTER merging, but wait for paint to ensure no flash
2854
+ // Use requestAnimationFrame to ensure styles are painted before clearing
2855
+ requestAnimationFrame(() => {
2856
+ requestAnimationFrame(() => {
2857
+ // Double RAF ensures paint has occurred
2858
+ if (this.criticalStyleElement && this.criticalStyleElement.textContent) {
2859
+ this.criticalStyleElement.textContent = '';
2860
+ }
2861
+ });
2862
+ });
2863
+ this.lastClassesHash = staticUsedData.classes.sort().join(',');
2864
+
2865
+ // Save to cache for next page load
2866
+ const themeHash = this.generateThemeHash(themeCss);
2867
+ const cacheKey = `${this.lastClassesHash}-${themeHash}`;
2868
+ this.cache.set(cacheKey, {
2869
+ css: finalCss,
2870
+ timestamp: Date.now(),
2871
+ themeHash: themeHash
2872
+ });
2873
+ this.savePersistentCache();
2874
+ }
2875
+ }
2876
+
2877
+ this.hasInitialized = true;
2878
+ this.isCompiling = false;
2879
+ return;
2880
+ }
2881
+
2882
+ // For subsequent compilations, check for new dynamic classes
2883
+ const usedData = this.getUsedClasses();
2884
+ const dynamicClasses = Array.from(this.dynamicClassCache);
2885
+
2886
+ // Create a hash of current dynamic classes to detect changes
2887
+ const dynamicClassesHash = dynamicClasses.sort().join(',');
2888
+
2889
+ // Check if dynamic classes have actually changed
2890
+ if (dynamicClassesHash !== this.lastClassesHash || !this.hasInitialized) {
2891
+ // Fetch CSS content for dynamic compilation
2892
+ const themeCss = await this.fetchThemeContent();
2893
+ if (!themeCss) {
2894
+ this.isCompiling = false;
2895
+ return;
2896
+ }
2897
+
2898
+ // Update custom utilities cache if needed
2899
+ const discoveredCustomUtilities = this.extractCustomUtilities(themeCss);
2900
+ for (const [name, value] of discoveredCustomUtilities.entries()) {
2901
+ this.customUtilities.set(name, value);
2902
+ }
2903
+
2904
+ // Check for variable changes
2905
+ const variables = this.extractThemeVariables(themeCss);
2906
+ let hasVariableChanges = false;
2907
+ for (const [name, value] of variables.entries()) {
2908
+ const currentValue = this.currentThemeVars.get(name);
2909
+ if (currentValue !== value) {
2910
+ hasVariableChanges = true;
2911
+ this.currentThemeVars.set(name, value);
2912
+ }
2913
+ }
2914
+
2915
+ // Generate utilities for all classes (static + dynamic) if needed
2916
+ if (hasVariableChanges || dynamicClassesHash !== this.lastClassesHash) {
2917
+
2918
+ // Generate both variable-based and custom utilities
2919
+ const varUtilities = this.generateUtilitiesFromVars(themeCss, usedData);
2920
+ const customUtilitiesGenerated = this.generateCustomUtilities(usedData);
2921
+
2922
+ const allUtilities = [varUtilities, customUtilitiesGenerated].filter(Boolean).join('\n\n');
2923
+ if (allUtilities) {
2924
+ const finalCss = `@layer utilities {\n${allUtilities}\n}`;
2925
+
2926
+ // Read critical styles BEFORE clearing (they'll be merged into final CSS)
2927
+ const criticalCss = this.criticalStyleElement && this.criticalStyleElement.textContent ?
2928
+ `\n\n/* Critical utilities (non-layer, will be overridden by layer utilities) */\n${this.criticalStyleElement.textContent}` :
2929
+ '';
2930
+
2931
+ this.styleElement.textContent = finalCss + criticalCss;
2932
+
2933
+ // Clear critical styles AFTER merging, but wait for paint to ensure no flash
2934
+ // Use requestAnimationFrame to ensure styles are painted before clearing
2935
+ requestAnimationFrame(() => {
2936
+ requestAnimationFrame(() => {
2937
+ // Double RAF ensures paint has occurred
2938
+ if (this.criticalStyleElement && this.criticalStyleElement.textContent) {
2939
+ this.criticalStyleElement.textContent = '';
2940
+ }
2941
+ });
2942
+ });
2943
+ this.lastClassesHash = dynamicClassesHash;
2944
+
2945
+ // Save to cache for next page load
2946
+ const themeHash = this.generateThemeHash(themeCss);
2947
+ const cacheKey = `${this.lastClassesHash}-${themeHash}`;
2948
+ this.cache.set(cacheKey, {
2949
+ css: finalCss,
2950
+ timestamp: Date.now(),
2951
+ themeHash: themeHash
2952
+ });
2953
+ this.savePersistentCache();
2954
+ }
2955
+ }
2956
+ }
2957
+
2958
+ } catch (error) {
2959
+ console.error('[Manifest Utilities] Error compiling Tailwind CSS:', error);
2960
+ } finally {
2961
+ this.isCompiling = false;
2962
+ }
2963
+ };
2964
+
2965
+
2966
+
2967
+ // DOM observation and event handling
2968
+ // Methods for watching DOM changes and triggering recompilation
2969
+
2970
+ // Setup component load listener and MutationObserver
2971
+ TailwindCompiler.prototype.setupComponentLoadListener = function() {
2972
+ // Use a single debounced handler for all component-related events
2973
+ const debouncedCompile = this.debounce(() => {
2974
+ if (!this.isCompiling) {
2975
+ this.compile();
2976
+ }
2977
+ }, this.options.debounceTime);
2978
+
2979
+ // Listen for custom event when components are loaded
2980
+ document.addEventListener('indux:component-loaded', (event) => {
2981
+ debouncedCompile();
2982
+ });
2983
+
2984
+ // Listen for route changes but don't recompile unnecessarily
2985
+ document.addEventListener('indux:route-change', (event) => {
2986
+ // Only trigger compilation if we detect new dynamic classes
2987
+ // The existing MutationObserver will handle actual DOM changes
2988
+ if (this.hasScannedStatic) {
2989
+ // Wait longer for route content to fully load before checking
2990
+ setTimeout(() => {
2991
+ const currentDynamicCount = this.dynamicClassCache.size;
2992
+ const currentClassesHash = this.lastClassesHash;
2993
+
2994
+ // Scan for new classes
2995
+ const usedData = this.getUsedClasses();
2996
+ const newDynamicCount = this.dynamicClassCache.size;
2997
+ const dynamicClasses = Array.from(this.dynamicClassCache);
2998
+ const newClassesHash = dynamicClasses.sort().join(',');
2999
+
3000
+ // Only compile if we found genuinely new classes, not just code processing artifacts
3001
+ if (newDynamicCount > currentDynamicCount && newClassesHash !== currentClassesHash) {
3002
+ const newClasses = dynamicClasses.filter(cls =>
3003
+ // Filter out classes that are likely from code processing
3004
+ !cls.includes('hljs') &&
3005
+ !cls.startsWith('language-') &&
3006
+ !cls.includes('copy') &&
3007
+ !cls.includes('lines')
3008
+ );
3009
+
3010
+ if (newClasses.length > 0) {
3011
+ debouncedCompile();
3012
+ }
3013
+ }
3014
+ }, 300); // Longer delay to let code processing finish
3015
+ }
3016
+ });
3017
+
3018
+ // Use a single MutationObserver for all DOM changes
3019
+ const observer = new MutationObserver((mutations) => {
3020
+ let shouldRecompile = false;
3021
+
3022
+ for (const mutation of mutations) {
3023
+ // Skip attribute changes that don't affect utilities
3024
+ if (mutation.type === 'attributes') {
3025
+ const attributeName = mutation.attributeName;
3026
+
3027
+ // Skip ignored attributes (like id changes from router)
3028
+ if (this.ignoredAttributes.includes(attributeName)) {
3029
+ continue;
3030
+ }
3031
+
3032
+ // Only care about class attribute changes
3033
+ if (attributeName !== 'class') {
3034
+ continue;
3035
+ }
3036
+
3037
+ // If it's a class change, check if we have new classes that need utilities
3038
+ const element = mutation.target;
3039
+ if (element.nodeType === Node.ELEMENT_NODE) {
3040
+ const currentClasses = Array.from(element.classList || []);
3041
+ const newClasses = currentClasses.filter(cls => {
3042
+ // Skip ignored patterns
3043
+ if (this.ignoredClassPatterns.some(pattern => pattern.test(cls))) {
3044
+ return false;
3045
+ }
3046
+
3047
+ // Check if this class is new (not in our cache)
3048
+ return !this.staticClassCache.has(cls) && !this.dynamicClassCache.has(cls);
3049
+ });
3050
+
3051
+ if (newClasses.length > 0) {
3052
+ // Add new classes to dynamic cache
3053
+ newClasses.forEach(cls => this.dynamicClassCache.add(cls));
3054
+ shouldRecompile = true;
3055
+ break;
3056
+ }
3057
+ }
3058
+ }
3059
+ else if (mutation.type === 'childList') {
3060
+ for (const node of mutation.addedNodes) {
3061
+ if (node.nodeType === Node.ELEMENT_NODE) {
3062
+ // Skip ignored elements using configurable selectors
3063
+ const isIgnoredElement = this.ignoredElementSelectors.some(selector =>
3064
+ node.tagName?.toLowerCase() === selector.toLowerCase() ||
3065
+ node.closest(selector)
3066
+ );
3067
+
3068
+ if (isIgnoredElement) {
3069
+ continue;
3070
+ }
3071
+
3072
+ // Only recompile for significant changes using configurable selectors
3073
+ const hasSignificantChange = this.significantChangeSelectors.some(selector => {
3074
+ try {
3075
+ return node.matches?.(selector) || node.querySelector?.(selector);
3076
+ } catch (e) {
3077
+ return false; // Invalid selector
3078
+ }
3079
+ });
3080
+
3081
+ if (hasSignificantChange) {
3082
+ shouldRecompile = true;
3083
+ break;
3084
+ }
3085
+ }
3086
+ }
3087
+ }
3088
+ if (shouldRecompile) break;
3089
+ }
3090
+
3091
+ if (shouldRecompile) {
3092
+ debouncedCompile();
3093
+ }
3094
+ });
3095
+
3096
+ // Start observing the document with the configured parameters
3097
+ observer.observe(document.documentElement, {
3098
+ childList: true,
3099
+ subtree: true,
3100
+ attributes: true,
3101
+ attributeFilter: ['class'] // Only observe class changes
3102
+ });
3103
+ };
3104
+
3105
+ // Start processing with initial compilation and observer setup
3106
+ TailwindCompiler.prototype.startProcessing = async function() {
3107
+ try {
3108
+ // Start initial compilation immediately
3109
+ const initialCompilation = this.compile();
3110
+
3111
+ // Set up observer while compilation is running
3112
+ this.observer = new MutationObserver((mutations) => {
3113
+ const relevantMutations = mutations.filter(mutation => {
3114
+ if (mutation.type === 'attributes' &&
3115
+ mutation.attributeName === 'class') {
3116
+ return true;
3117
+ }
3118
+ if (mutation.type === 'childList') {
3119
+ return Array.from(mutation.addedNodes).some(node =>
3120
+ node.nodeType === Node.ELEMENT_NODE);
3121
+ }
3122
+ return false;
3123
+ });
3124
+
3125
+ if (relevantMutations.length === 0) return;
3126
+
3127
+ // Check if there are any new classes that need processing
3128
+ const newClasses = this.getUsedClasses();
3129
+ if (newClasses.classes.length === 0) return;
3130
+
3131
+ if (this.compileTimeout) {
3132
+ clearTimeout(this.compileTimeout);
3133
+ }
3134
+ this.compileTimeout = setTimeout(() => {
3135
+ if (!this.isCompiling) {
3136
+ this.compile();
3137
+ }
3138
+ }, this.options.debounceTime);
3139
+ });
3140
+
3141
+ // Start observing immediately
3142
+ this.observer.observe(document.documentElement, {
3143
+ childList: true,
3144
+ subtree: true,
3145
+ attributes: true,
3146
+ attributeFilter: ['class']
3147
+ });
3148
+
3149
+ // Wait for initial compilation
3150
+ await initialCompilation;
3151
+
3152
+ this.hasInitialized = true;
3153
+ } catch (error) {
3154
+ console.error('Error starting Tailwind compiler:', error);
3155
+ }
3156
+ };
3157
+
3158
+
3159
+
3160
+ // Utilities initialization
3161
+ // Initialize compiler and set up event listeners
3162
+
3163
+ // Initialize immediately without waiting for DOMContentLoaded
3164
+ const compiler = new TailwindCompiler();
3165
+
3166
+ // Expose utilities compiler for optional integration
3167
+ window.ManifestUtilities = compiler;
3168
+
3169
+ // Log when DOM is ready
3170
+ if (document.readyState === 'loading') {
3171
+ document.addEventListener('DOMContentLoaded', () => {
3172
+ // DOM ready
3173
+ });
3174
+ } else {
3175
+ // DOM already ready
3176
+ }
3177
+
3178
+ // Log first paint if available
3179
+ if ('PerformanceObserver' in window) {
3180
+ try {
3181
+ const paintObserver = new PerformanceObserver((list) => {
3182
+ for (const entry of list.getEntries()) {
3183
+ }
3184
+ });
3185
+ paintObserver.observe({ entryTypes: ['paint'] });
3186
+ } catch (e) {
3187
+ // PerformanceObserver might not be available
3188
+ }
3189
+ }
3190
+
3191
+ // Also handle DOMContentLoaded for any elements that might be added later
3192
+ document.addEventListener('DOMContentLoaded', () => {
3193
+ if (!compiler.isCompiling) {
3194
+ compiler.compile();
3195
+ }
3196
+ });
3197
+