@c9up/aurora 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cn.js ADDED
@@ -0,0 +1,908 @@
1
+ /**
2
+ * `cn` — class-name composition with Tailwind conflict resolution, reimplemented
3
+ * from scratch with ZERO dependencies (no clsx, no tailwind-merge).
4
+ *
5
+ * cn('px-2 py-1', isActive && 'bg-indigo-600', props.class)
6
+ * cn('px-2', 'px-4') // → 'px-4' (later wins within a group)
7
+ * cn('mr-4', 'mr-8') // → 'mr-8' (dedup is the whole point)
8
+ * cn('text-red-500', 'text-sm') // → 'text-red-500 text-sm' (different groups)
9
+ * cn('hover:p-2', 'hover:p-4', 'p-1')// → 'p-1 hover:p-4' (variant-scoped)
10
+ *
11
+ * Two parts:
12
+ * 1. {@link clsx} — flatten strings / numbers / arrays / objects into a class
13
+ * string, dropping falsy values (full clsx semantics).
14
+ * 2. {@link twMerge} — within the SAME variant stack (`hover:`, `md:`, `!`, …),
15
+ * keep only the LAST class of each conflicting Tailwind group; unknown
16
+ * classes never conflict and are always kept, in source order.
17
+ *
18
+ * Targets the standard Tailwind v4 utility set. Node-free — part of aurora's
19
+ * client runtime.
20
+ */
21
+ function appendValue(mix) {
22
+ if (typeof mix === "string")
23
+ return mix;
24
+ if (typeof mix === "number" || typeof mix === "bigint") {
25
+ // clsx includes any truthy number (0 / 0n are falsy → dropped).
26
+ return mix ? String(mix) : "";
27
+ }
28
+ if (typeof mix !== "object" || mix === null)
29
+ return "";
30
+ if (Array.isArray(mix)) {
31
+ let out = "";
32
+ for (const item of mix) {
33
+ const piece = appendValue(item);
34
+ if (piece !== "")
35
+ out = out === "" ? piece : `${out} ${piece}`;
36
+ }
37
+ return out;
38
+ }
39
+ // Plain object: include each key whose value is truthy.
40
+ let out = "";
41
+ for (const key in mix) {
42
+ if (mix[key])
43
+ out = out === "" ? key : `${out} ${key}`;
44
+ }
45
+ return out;
46
+ }
47
+ /** clsx-equivalent: join truthy class values (strings, numbers, arrays, objects). */
48
+ export function clsx(...inputs) {
49
+ let out = "";
50
+ for (const input of inputs) {
51
+ const piece = appendValue(input);
52
+ if (piece !== "")
53
+ out = out === "" ? piece : `${out} ${piece}`;
54
+ }
55
+ return out;
56
+ }
57
+ // ─── validators ──────────────────────────────────────────────────────────────
58
+ const FRACTION = /^\d+\/\d+$/;
59
+ const NUMBER = /^\d+(\.\d+)?$/;
60
+ const LENGTH_UNIT = /^-?\d+(\.\d+)?(px|r?em|%|vh|vw|vmin|vmax|cm|mm|in|pt|pc|ex|ch|fr|deg|rad|grad|turn|s|ms|q)$/;
61
+ const TSHIRT = /^(\d+xs|xs|sm|md|lg|xl|\d+xl)$/;
62
+ const isArbitrary = (s) => /^\[.+\]$/.test(s);
63
+ /** Arbitrary value tagged as a length/size, e.g. `[12px]`, `[length:…]`, `[3.2rem]`. */
64
+ const isArbitraryLength = (s) => /^\[(length|size|percentage):/.test(s) ||
65
+ /^\[-?\d+(\.\d+)?(px|r?em|%|vh|vw|vmin|vmax|cm|mm|in|pt|pc|ex|ch|fr)\]$/.test(s);
66
+ const isNumber = (s) => NUMBER.test(s);
67
+ const isInteger = (s) => /^\d+$/.test(s);
68
+ const isLength = (s) => s === "px" ||
69
+ s === "full" ||
70
+ s === "auto" ||
71
+ NUMBER.test(s) ||
72
+ FRACTION.test(s) ||
73
+ LENGTH_UNIT.test(s) ||
74
+ isArbitrary(s);
75
+ const isTshirt = (s) => TSHIRT.test(s);
76
+ const any = () => true;
77
+ const WEIGHTS = [
78
+ "thin",
79
+ "extralight",
80
+ "light",
81
+ "normal",
82
+ "medium",
83
+ "semibold",
84
+ "bold",
85
+ "extrabold",
86
+ "black",
87
+ ];
88
+ /** The eight Tailwind spacing/side suffixes (mx, my, ms, me, mt, mr, mb, ml…). */
89
+ const SIDES = ["x", "y", "s", "e", "t", "r", "b", "l"];
90
+ /** A spacing-style family (p, m, scroll-m, scroll-p): a base group + 8 side groups. */
91
+ function spacing(prefix, validate) {
92
+ return [
93
+ { id: prefix, pre: [[prefix, validate]] },
94
+ ...SIDES.map((s) => ({
95
+ id: `${prefix}${s}`,
96
+ pre: [[`${prefix}${s}`, validate]],
97
+ })),
98
+ ];
99
+ }
100
+ /** Conflicts for a spacing family: base overrides all sides; x→l,r and y→t,b. */
101
+ function spacingConflicts(prefix) {
102
+ return {
103
+ [prefix]: SIDES.map((s) => `${prefix}${s}`),
104
+ [`${prefix}x`]: [`${prefix}l`, `${prefix}r`],
105
+ [`${prefix}y`]: [`${prefix}t`, `${prefix}b`],
106
+ };
107
+ }
108
+ /**
109
+ * ORDERED rules — first match wins, so the more specific keyword/size rules
110
+ * (text-shadow, border-collapse, font-stretch…) MUST precede the catch-all
111
+ * color rules (text-*, border-*, bg-*) that would otherwise swallow them.
112
+ */
113
+ const RULES = [
114
+ // ─ layout ─
115
+ { id: "aspect", pre: [["aspect", any]] },
116
+ { id: "container", eq: ["container"] },
117
+ { id: "columns", pre: [["columns", any]] },
118
+ { id: "break-after", pre: [["break-after", any]] },
119
+ { id: "break-before", pre: [["break-before", any]] },
120
+ { id: "break-inside", pre: [["break-inside", any]] },
121
+ {
122
+ id: "box-decoration",
123
+ eq: ["box-decoration-clone", "box-decoration-slice"],
124
+ },
125
+ { id: "box", eq: ["box-border", "box-content"] },
126
+ { id: "sr", eq: ["sr-only", "not-sr-only"] },
127
+ {
128
+ id: "display",
129
+ eq: [
130
+ "block",
131
+ "inline-block",
132
+ "inline",
133
+ "flex",
134
+ "inline-flex",
135
+ "table",
136
+ "inline-table",
137
+ "table-caption",
138
+ "table-cell",
139
+ "table-row",
140
+ "table-column",
141
+ "table-column-group",
142
+ "table-footer-group",
143
+ "table-header-group",
144
+ "table-row-group",
145
+ "flow-root",
146
+ "grid",
147
+ "inline-grid",
148
+ "contents",
149
+ "list-item",
150
+ "hidden",
151
+ ],
152
+ },
153
+ {
154
+ id: "float",
155
+ pre: [
156
+ ["float", (r) => ["right", "left", "none", "start", "end"].includes(r)],
157
+ ],
158
+ },
159
+ { id: "clear", pre: [["clear", any]] },
160
+ { id: "isolation", eq: ["isolate", "isolation-auto"] },
161
+ {
162
+ id: "object-fit",
163
+ pre: [
164
+ [
165
+ "object",
166
+ (r) => ["contain", "cover", "fill", "none", "scale-down"].includes(r),
167
+ ],
168
+ ],
169
+ },
170
+ { id: "object-position", pre: [["object", any]] },
171
+ { id: "overflow-x", pre: [["overflow-x", any]] },
172
+ { id: "overflow-y", pre: [["overflow-y", any]] },
173
+ {
174
+ id: "overflow",
175
+ pre: [["overflow", (r) => !r.startsWith("x-") && !r.startsWith("y-")]],
176
+ },
177
+ { id: "overscroll-x", pre: [["overscroll-x", any]] },
178
+ { id: "overscroll-y", pre: [["overscroll-y", any]] },
179
+ {
180
+ id: "overscroll",
181
+ pre: [["overscroll", (r) => !r.startsWith("x-") && !r.startsWith("y-")]],
182
+ },
183
+ { id: "position", eq: ["static", "fixed", "absolute", "relative", "sticky"] },
184
+ { id: "inset-x", pre: [["inset-x", any]] },
185
+ { id: "inset-y", pre: [["inset-y", any]] },
186
+ {
187
+ id: "inset",
188
+ pre: [
189
+ [
190
+ "inset",
191
+ (r) => r !== "ring" &&
192
+ r !== "shadow" &&
193
+ !["x-", "y-", "ring-", "shadow-"].some((p) => r.startsWith(p)),
194
+ ],
195
+ ],
196
+ },
197
+ { id: "top", pre: [["top", any]] },
198
+ { id: "right", pre: [["right", any]] },
199
+ { id: "bottom", pre: [["bottom", any]] },
200
+ { id: "left", pre: [["left", any]] },
201
+ { id: "start", pre: [["start", any]] },
202
+ { id: "end", pre: [["end", any]] },
203
+ { id: "visibility", eq: ["visible", "invisible", "collapse"] },
204
+ { id: "z", pre: [["z", any]] },
205
+ // ─ flexbox / grid ─
206
+ { id: "basis", pre: [["basis", any]] },
207
+ {
208
+ id: "flex-direction",
209
+ pre: [
210
+ ["flex", (r) => ["row", "row-reverse", "col", "col-reverse"].includes(r)],
211
+ ],
212
+ },
213
+ {
214
+ id: "flex-wrap",
215
+ pre: [["flex", (r) => ["wrap", "wrap-reverse", "nowrap"].includes(r)]],
216
+ },
217
+ {
218
+ id: "flex",
219
+ pre: [
220
+ [
221
+ "flex",
222
+ (r) => r === "1" ||
223
+ r === "auto" ||
224
+ r === "initial" ||
225
+ r === "none" ||
226
+ isNumber(r) ||
227
+ isFractionOrArbitrary(r),
228
+ ],
229
+ ],
230
+ },
231
+ { id: "grow", eq: ["grow"], pre: [["grow", isNumber]] },
232
+ { id: "shrink", eq: ["shrink"], pre: [["shrink", isNumber]] },
233
+ { id: "order", pre: [["order", any]] },
234
+ { id: "grid-cols", pre: [["grid-cols", any]] },
235
+ { id: "grid-rows", pre: [["grid-rows", any]] },
236
+ { id: "col-start-end", pre: [["col", any]] },
237
+ { id: "row-start-end", pre: [["row", any]] },
238
+ { id: "grid-flow", pre: [["grid-flow", any]] },
239
+ { id: "auto-cols", pre: [["auto-cols", any]] },
240
+ { id: "auto-rows", pre: [["auto-rows", any]] },
241
+ { id: "gap-x", pre: [["gap-x", isLength]] },
242
+ { id: "gap-y", pre: [["gap-y", isLength]] },
243
+ { id: "gap", pre: [["gap", isLength]] },
244
+ { id: "justify-items", pre: [["justify-items", any]] },
245
+ { id: "justify-self", pre: [["justify-self", any]] },
246
+ { id: "justify-content", pre: [["justify", any]] },
247
+ { id: "content", pre: [["content", (r) => r === "none" || isArbitrary(r)]] },
248
+ { id: "align-content", pre: [["content", any]] },
249
+ { id: "align-items", pre: [["items", any]] },
250
+ { id: "align-self", pre: [["self", any]] },
251
+ { id: "place-content", pre: [["place-content", any]] },
252
+ { id: "place-items", pre: [["place-items", any]] },
253
+ { id: "place-self", pre: [["place-self", any]] },
254
+ // ─ spacing ─
255
+ ...spacing("p", isLength),
256
+ ...spacing("m", isLength),
257
+ { id: "space-x-reverse", eq: ["space-x-reverse"] },
258
+ { id: "space-y-reverse", eq: ["space-y-reverse"] },
259
+ { id: "space-x", pre: [["space-x", any]] },
260
+ { id: "space-y", pre: [["space-y", any]] },
261
+ ...spacing("scroll-m", isLength),
262
+ ...spacing("scroll-p", isLength),
263
+ // ─ sizing ─
264
+ { id: "size", pre: [["size", isLength]] },
265
+ {
266
+ id: "w",
267
+ eq: ["w-screen", "w-min", "w-max", "w-fit"],
268
+ pre: [["w", isLength]],
269
+ },
270
+ { id: "min-w", pre: [["min-w", any]] },
271
+ { id: "max-w", pre: [["max-w", any]] },
272
+ {
273
+ id: "h",
274
+ eq: ["h-screen", "h-min", "h-max", "h-fit"],
275
+ pre: [["h", isLength]],
276
+ },
277
+ { id: "min-h", pre: [["min-h", any]] },
278
+ { id: "max-h", pre: [["max-h", any]] },
279
+ // ─ typography ─
280
+ { id: "font-stretch", pre: [["font-stretch", any]] },
281
+ {
282
+ id: "font-weight",
283
+ pre: [["font", (r) => WEIGHTS.includes(r) || isNumber(r)]],
284
+ },
285
+ { id: "font-family", pre: [["font", any]] },
286
+ {
287
+ id: "text-shadow",
288
+ eq: ["text-shadow"],
289
+ pre: [
290
+ ["text-shadow", (r) => isTshirt(r) || r === "none" || isArbitrary(r)],
291
+ ],
292
+ },
293
+ { id: "text-shadow-color", pre: [["text-shadow", any]] },
294
+ {
295
+ id: "text-align",
296
+ pre: [
297
+ [
298
+ "text",
299
+ (r) => ["left", "center", "right", "justify", "start", "end"].includes(r),
300
+ ],
301
+ ],
302
+ },
303
+ {
304
+ id: "text-overflow",
305
+ eq: ["truncate"],
306
+ pre: [["text", (r) => ["ellipsis", "clip"].includes(r)]],
307
+ },
308
+ {
309
+ id: "text-wrap",
310
+ pre: [["text", (r) => ["wrap", "nowrap", "balance", "pretty"].includes(r)]],
311
+ },
312
+ {
313
+ id: "font-size",
314
+ pre: [["text", (r) => isTshirt(r) || isLength(r) || isArbitraryLength(r)]],
315
+ },
316
+ { id: "text-color", pre: [["text", any]] },
317
+ { id: "leading", pre: [["leading", any]] },
318
+ { id: "tracking", pre: [["tracking", any]] },
319
+ { id: "line-clamp", pre: [["line-clamp", any]] },
320
+ {
321
+ id: "list-style-position",
322
+ pre: [["list", (r) => ["inside", "outside"].includes(r)]],
323
+ },
324
+ { id: "list-image", pre: [["list-image", any]] },
325
+ { id: "list-style-type", pre: [["list", any]] },
326
+ {
327
+ id: "text-decoration",
328
+ eq: ["underline", "overline", "line-through", "no-underline"],
329
+ },
330
+ {
331
+ id: "text-decoration-style",
332
+ pre: [
333
+ [
334
+ "decoration",
335
+ (r) => ["solid", "dashed", "dotted", "double", "wavy"].includes(r),
336
+ ],
337
+ ],
338
+ },
339
+ {
340
+ id: "text-decoration-thickness",
341
+ pre: [
342
+ ["decoration", (r) => r === "auto" || r === "from-font" || isLength(r)],
343
+ ],
344
+ },
345
+ { id: "decoration-color", pre: [["decoration", any]] },
346
+ { id: "underline-offset", pre: [["underline-offset", any]] },
347
+ {
348
+ id: "text-transform",
349
+ eq: ["uppercase", "lowercase", "capitalize", "normal-case"],
350
+ },
351
+ { id: "font-style", eq: ["italic", "not-italic"] },
352
+ { id: "font-smoothing", eq: ["antialiased", "subpixel-antialiased"] },
353
+ { id: "whitespace", pre: [["whitespace", any]] },
354
+ {
355
+ id: "word-break",
356
+ pre: [["break", (r) => ["normal", "words", "all", "keep"].includes(r)]],
357
+ },
358
+ { id: "hyphens", pre: [["hyphens", any]] },
359
+ { id: "indent", pre: [["indent", any]] },
360
+ { id: "align", pre: [["align", any]] },
361
+ // ─ backgrounds ─
362
+ {
363
+ id: "bg-attachment",
364
+ pre: [["bg", (r) => ["fixed", "local", "scroll"].includes(r)]],
365
+ },
366
+ { id: "bg-clip", pre: [["bg-clip", any]] },
367
+ { id: "bg-origin", pre: [["bg-origin", any]] },
368
+ {
369
+ id: "bg-position",
370
+ pre: [
371
+ [
372
+ "bg",
373
+ (r) => [
374
+ "bottom",
375
+ "center",
376
+ "left",
377
+ "left-bottom",
378
+ "left-top",
379
+ "right",
380
+ "right-bottom",
381
+ "right-top",
382
+ "top",
383
+ ].includes(r),
384
+ ],
385
+ ],
386
+ },
387
+ {
388
+ id: "bg-repeat",
389
+ pre: [
390
+ [
391
+ "bg",
392
+ (r) => r === "repeat" || r.startsWith("repeat-") || r === "no-repeat",
393
+ ],
394
+ ],
395
+ },
396
+ {
397
+ id: "bg-size",
398
+ pre: [["bg", (r) => ["auto", "cover", "contain"].includes(r)]],
399
+ },
400
+ {
401
+ id: "bg-image",
402
+ pre: [
403
+ [
404
+ "bg",
405
+ (r) => r === "none" ||
406
+ r.startsWith("gradient-") ||
407
+ r.startsWith("linear-") ||
408
+ r.startsWith("radial") ||
409
+ r.startsWith("conic"),
410
+ ],
411
+ ],
412
+ },
413
+ { id: "bg-blend", pre: [["bg-blend", any]] },
414
+ { id: "bg-color", pre: [["bg", any]] },
415
+ { id: "gradient-from", pre: [["from", any]] },
416
+ { id: "gradient-via", pre: [["via", any]] },
417
+ { id: "gradient-to", pre: [["to", any]] },
418
+ // ─ borders ─
419
+ { id: "rounded-ss", pre: [["rounded-ss", any]] },
420
+ { id: "rounded-se", pre: [["rounded-se", any]] },
421
+ { id: "rounded-ee", pre: [["rounded-ee", any]] },
422
+ { id: "rounded-es", pre: [["rounded-es", any]] },
423
+ { id: "rounded-s", pre: [["rounded-s", any]] },
424
+ { id: "rounded-e", pre: [["rounded-e", any]] },
425
+ { id: "rounded-t", pre: [["rounded-t", any]] },
426
+ { id: "rounded-r", pre: [["rounded-r", any]] },
427
+ { id: "rounded-b", pre: [["rounded-b", any]] },
428
+ { id: "rounded-l", pre: [["rounded-l", any]] },
429
+ { id: "rounded-tl", pre: [["rounded-tl", any]] },
430
+ { id: "rounded-tr", pre: [["rounded-tr", any]] },
431
+ { id: "rounded-br", pre: [["rounded-br", any]] },
432
+ { id: "rounded-bl", pre: [["rounded-bl", any]] },
433
+ {
434
+ id: "rounded",
435
+ eq: ["rounded"],
436
+ pre: [
437
+ [
438
+ "rounded",
439
+ (r) => isTshirt(r) || r === "none" || r === "full" || isArbitrary(r),
440
+ ],
441
+ ],
442
+ },
443
+ { id: "border-collapse", eq: ["border-collapse", "border-separate"] },
444
+ { id: "border-spacing-x", pre: [["border-spacing-x", any]] },
445
+ { id: "border-spacing-y", pre: [["border-spacing-y", any]] },
446
+ {
447
+ id: "border-spacing",
448
+ pre: [
449
+ ["border-spacing", (r) => !r.startsWith("x-") && !r.startsWith("y-")],
450
+ ],
451
+ },
452
+ {
453
+ id: "border-style",
454
+ pre: [
455
+ [
456
+ "border",
457
+ (r) => ["solid", "dashed", "dotted", "double", "hidden", "none"].includes(r),
458
+ ],
459
+ ],
460
+ },
461
+ {
462
+ id: "border-w-x",
463
+ pre: [
464
+ [
465
+ "border-x",
466
+ (r) => r === "" || isInteger(r) || r === "px" || isArbitrary(r),
467
+ ],
468
+ ],
469
+ },
470
+ {
471
+ id: "border-w-y",
472
+ pre: [
473
+ [
474
+ "border-y",
475
+ (r) => r === "" || isInteger(r) || r === "px" || isArbitrary(r),
476
+ ],
477
+ ],
478
+ },
479
+ {
480
+ id: "border-w-t",
481
+ pre: [
482
+ [
483
+ "border-t",
484
+ (r) => r === "" || isInteger(r) || r === "px" || isArbitrary(r),
485
+ ],
486
+ ],
487
+ },
488
+ {
489
+ id: "border-w-r",
490
+ pre: [
491
+ [
492
+ "border-r",
493
+ (r) => r === "" || isInteger(r) || r === "px" || isArbitrary(r),
494
+ ],
495
+ ],
496
+ },
497
+ {
498
+ id: "border-w-b",
499
+ pre: [
500
+ [
501
+ "border-b",
502
+ (r) => r === "" || isInteger(r) || r === "px" || isArbitrary(r),
503
+ ],
504
+ ],
505
+ },
506
+ {
507
+ id: "border-w-l",
508
+ pre: [
509
+ [
510
+ "border-l",
511
+ (r) => r === "" || isInteger(r) || r === "px" || isArbitrary(r),
512
+ ],
513
+ ],
514
+ },
515
+ {
516
+ id: "border-w",
517
+ eq: ["border"],
518
+ pre: [["border", (r) => isInteger(r) || r === "px" || isArbitrary(r)]],
519
+ },
520
+ { id: "border-color-x", pre: [["border-x", any]] },
521
+ { id: "border-color-y", pre: [["border-y", any]] },
522
+ { id: "border-color-t", pre: [["border-t", any]] },
523
+ { id: "border-color-r", pre: [["border-r", any]] },
524
+ { id: "border-color-b", pre: [["border-b", any]] },
525
+ { id: "border-color-l", pre: [["border-l", any]] },
526
+ { id: "border-color", pre: [["border", any]] },
527
+ { id: "divide-x-reverse", eq: ["divide-x-reverse"] },
528
+ { id: "divide-y-reverse", eq: ["divide-y-reverse"] },
529
+ { id: "divide-x", pre: [["divide-x", any]] },
530
+ { id: "divide-y", pre: [["divide-y", any]] },
531
+ {
532
+ id: "divide-style",
533
+ pre: [
534
+ [
535
+ "divide",
536
+ (r) => ["solid", "dashed", "dotted", "double", "none"].includes(r),
537
+ ],
538
+ ],
539
+ },
540
+ { id: "divide-color", pre: [["divide", any]] },
541
+ {
542
+ id: "outline-style",
543
+ eq: ["outline", "outline-none"],
544
+ pre: [["outline", (r) => ["dashed", "dotted", "double"].includes(r)]],
545
+ },
546
+ { id: "outline-offset", pre: [["outline-offset", any]] },
547
+ {
548
+ id: "outline-w",
549
+ pre: [["outline", (r) => isInteger(r) || isArbitrary(r)]],
550
+ },
551
+ { id: "outline-color", pre: [["outline", any]] },
552
+ {
553
+ id: "inset-ring-w",
554
+ eq: ["inset-ring"],
555
+ pre: [["inset-ring", (r) => isInteger(r) || isArbitrary(r)]],
556
+ },
557
+ { id: "inset-ring-color", pre: [["inset-ring", any]] },
558
+ {
559
+ id: "ring-w",
560
+ eq: ["ring"],
561
+ pre: [["ring", (r) => isInteger(r) || r === "inset" || isArbitrary(r)]],
562
+ },
563
+ {
564
+ id: "ring-offset-w",
565
+ pre: [["ring-offset", (r) => isInteger(r) || isArbitrary(r)]],
566
+ },
567
+ { id: "ring-offset-color", pre: [["ring-offset", any]] },
568
+ { id: "ring-color", pre: [["ring", any]] },
569
+ // ─ effects ─
570
+ {
571
+ id: "shadow",
572
+ eq: ["shadow"],
573
+ pre: [
574
+ [
575
+ "shadow",
576
+ (r) => isTshirt(r) || r === "none" || r === "inner" || isArbitrary(r),
577
+ ],
578
+ ],
579
+ },
580
+ { id: "shadow-color", pre: [["shadow", any]] },
581
+ {
582
+ id: "inset-shadow",
583
+ eq: ["inset-shadow"],
584
+ pre: [
585
+ ["inset-shadow", (r) => isTshirt(r) || r === "none" || isArbitrary(r)],
586
+ ],
587
+ },
588
+ { id: "inset-shadow-color", pre: [["inset-shadow", any]] },
589
+ { id: "opacity", pre: [["opacity", any]] },
590
+ { id: "mix-blend", pre: [["mix-blend", any]] },
591
+ // ─ filters ─
592
+ { id: "filter", eq: ["filter", "filter-none"] },
593
+ { id: "blur", eq: ["blur"], pre: [["blur", any]] },
594
+ { id: "brightness", pre: [["brightness", any]] },
595
+ { id: "contrast", pre: [["contrast", any]] },
596
+ {
597
+ id: "drop-shadow",
598
+ eq: ["drop-shadow"],
599
+ pre: [
600
+ ["drop-shadow", (r) => isTshirt(r) || r === "none" || isArbitrary(r)],
601
+ ],
602
+ },
603
+ { id: "drop-shadow-color", pre: [["drop-shadow", any]] },
604
+ { id: "grayscale", eq: ["grayscale"], pre: [["grayscale", any]] },
605
+ { id: "hue-rotate", pre: [["hue-rotate", any]] },
606
+ { id: "invert", eq: ["invert"], pre: [["invert", any]] },
607
+ { id: "saturate", pre: [["saturate", any]] },
608
+ { id: "sepia", eq: ["sepia"], pre: [["sepia", any]] },
609
+ { id: "backdrop-filter", eq: ["backdrop-filter", "backdrop-filter-none"] },
610
+ { id: "backdrop-blur", eq: ["backdrop-blur"], pre: [["backdrop-blur", any]] },
611
+ { id: "backdrop-brightness", pre: [["backdrop-brightness", any]] },
612
+ { id: "backdrop-contrast", pre: [["backdrop-contrast", any]] },
613
+ {
614
+ id: "backdrop-grayscale",
615
+ eq: ["backdrop-grayscale"],
616
+ pre: [["backdrop-grayscale", any]],
617
+ },
618
+ { id: "backdrop-hue-rotate", pre: [["backdrop-hue-rotate", any]] },
619
+ {
620
+ id: "backdrop-invert",
621
+ eq: ["backdrop-invert"],
622
+ pre: [["backdrop-invert", any]],
623
+ },
624
+ { id: "backdrop-opacity", pre: [["backdrop-opacity", any]] },
625
+ { id: "backdrop-saturate", pre: [["backdrop-saturate", any]] },
626
+ {
627
+ id: "backdrop-sepia",
628
+ eq: ["backdrop-sepia"],
629
+ pre: [["backdrop-sepia", any]],
630
+ },
631
+ // ─ tables ─
632
+ {
633
+ id: "table-layout",
634
+ pre: [["table", (r) => ["auto", "fixed"].includes(r)]],
635
+ },
636
+ { id: "caption", pre: [["caption", any]] },
637
+ // ─ transitions / animation ─
638
+ { id: "transition", eq: ["transition"], pre: [["transition", any]] },
639
+ { id: "duration", pre: [["duration", any]] },
640
+ { id: "ease", pre: [["ease", any]] },
641
+ { id: "delay", pre: [["delay", any]] },
642
+ { id: "animate", pre: [["animate", any]] },
643
+ // ─ transforms ─
644
+ {
645
+ id: "transform",
646
+ eq: ["transform", "transform-none", "transform-gpu", "transform-cpu"],
647
+ },
648
+ { id: "transform-origin", pre: [["origin", any]] },
649
+ { id: "perspective-origin", pre: [["perspective-origin", any]] },
650
+ { id: "perspective", pre: [["perspective", any]] },
651
+ { id: "backface", pre: [["backface", any]] },
652
+ { id: "scale-x", pre: [["scale-x", any]] },
653
+ { id: "scale-y", pre: [["scale-y", any]] },
654
+ { id: "scale-z", pre: [["scale-z", any]] },
655
+ { id: "scale", pre: [["scale", any]] },
656
+ { id: "rotate-x", pre: [["rotate-x", any]] },
657
+ { id: "rotate-y", pre: [["rotate-y", any]] },
658
+ { id: "rotate-z", pre: [["rotate-z", any]] },
659
+ { id: "rotate", pre: [["rotate", any]] },
660
+ { id: "translate-x", pre: [["translate-x", any]] },
661
+ { id: "translate-y", pre: [["translate-y", any]] },
662
+ { id: "translate-z", pre: [["translate-z", any]] },
663
+ { id: "translate", pre: [["translate", any]] },
664
+ { id: "skew-x", pre: [["skew-x", any]] },
665
+ { id: "skew-y", pre: [["skew-y", any]] },
666
+ { id: "skew", pre: [["skew", any]] },
667
+ // ─ interactivity ─
668
+ { id: "accent", pre: [["accent", any]] },
669
+ { id: "appearance", pre: [["appearance", any]] },
670
+ { id: "caret", pre: [["caret", any]] },
671
+ { id: "color-scheme", pre: [["scheme", any]] },
672
+ { id: "cursor", pre: [["cursor", any]] },
673
+ { id: "field-sizing", pre: [["field-sizing", any]] },
674
+ { id: "pointer-events", pre: [["pointer-events", any]] },
675
+ { id: "resize", eq: ["resize", "resize-none", "resize-x", "resize-y"] },
676
+ {
677
+ id: "scroll-behavior",
678
+ pre: [["scroll", (r) => ["auto", "smooth"].includes(r)]],
679
+ },
680
+ {
681
+ id: "snap-align",
682
+ pre: [
683
+ ["snap", (r) => ["start", "end", "center", "align-none"].includes(r)],
684
+ ],
685
+ },
686
+ { id: "snap-stop", pre: [["snap", (r) => ["normal", "always"].includes(r)]] },
687
+ {
688
+ id: "snap-strictness",
689
+ pre: [["snap", (r) => ["mandatory", "proximity"].includes(r)]],
690
+ },
691
+ {
692
+ id: "snap-type",
693
+ pre: [["snap", (r) => ["none", "x", "y", "both"].includes(r)]],
694
+ },
695
+ { id: "select", pre: [["select", any]] },
696
+ { id: "touch", pre: [["touch", any]] },
697
+ { id: "user-select", pre: [["user-select", any]] },
698
+ { id: "will-change", pre: [["will-change", any]] },
699
+ // ─ svg ─
700
+ { id: "fill", pre: [["fill", any]] },
701
+ {
702
+ id: "stroke-w",
703
+ pre: [["stroke", (r) => isInteger(r) || isArbitraryLength(r)]],
704
+ },
705
+ { id: "stroke", pre: [["stroke", any]] },
706
+ // ─ accessibility ─
707
+ { id: "forced-color-adjust", pre: [["forced-color-adjust", any]] },
708
+ // ─ masking (Tailwind v4) ─
709
+ { id: "mask-type", pre: [["mask-type", any]] },
710
+ { id: "mask-clip", pre: [["mask-clip", any]] },
711
+ { id: "mask-origin", pre: [["mask-origin", any]] },
712
+ {
713
+ id: "mask-mode",
714
+ pre: [["mask", (r) => ["alpha", "luminance", "match"].includes(r)]],
715
+ },
716
+ {
717
+ id: "mask-composite",
718
+ pre: [
719
+ ["mask", (r) => ["add", "subtract", "intersect", "exclude"].includes(r)],
720
+ ],
721
+ },
722
+ {
723
+ id: "mask-repeat",
724
+ pre: [
725
+ [
726
+ "mask",
727
+ (r) => r === "repeat" || r.startsWith("repeat-") || r === "no-repeat",
728
+ ],
729
+ ],
730
+ },
731
+ {
732
+ id: "mask-size",
733
+ pre: [["mask", (r) => ["auto", "cover", "contain"].includes(r)]],
734
+ },
735
+ { id: "mask-image", pre: [["mask", (r) => r === "none" || isArbitrary(r)]] },
736
+ ];
737
+ /** flex shorthand can take a fraction (`flex-1/2`) or an arbitrary value. */
738
+ function isFractionOrArbitrary(s) {
739
+ return FRACTION.test(s) || isArbitrary(s);
740
+ }
741
+ /**
742
+ * Groups that a kept class additionally overrides — e.g. `p-4` overrides every
743
+ * `px/py/pt/…`, and `inset-0` overrides `top/left/…`. Keyed group → groups it
744
+ * supersedes. Resolution marks these as claimed so an EARLIER such class drops.
745
+ */
746
+ const CONFLICTS = {
747
+ overflow: ["overflow-x", "overflow-y"],
748
+ overscroll: ["overscroll-x", "overscroll-y"],
749
+ inset: [
750
+ "inset-x",
751
+ "inset-y",
752
+ "top",
753
+ "right",
754
+ "bottom",
755
+ "left",
756
+ "start",
757
+ "end",
758
+ ],
759
+ "inset-x": ["right", "left"],
760
+ "inset-y": ["top", "bottom"],
761
+ ...spacingConflicts("p"),
762
+ ...spacingConflicts("m"),
763
+ ...spacingConflicts("scroll-m"),
764
+ ...spacingConflicts("scroll-p"),
765
+ gap: ["gap-x", "gap-y"],
766
+ size: ["w", "h"],
767
+ rounded: [
768
+ "rounded-ss",
769
+ "rounded-se",
770
+ "rounded-ee",
771
+ "rounded-es",
772
+ "rounded-s",
773
+ "rounded-e",
774
+ "rounded-t",
775
+ "rounded-r",
776
+ "rounded-b",
777
+ "rounded-l",
778
+ "rounded-tl",
779
+ "rounded-tr",
780
+ "rounded-br",
781
+ "rounded-bl",
782
+ ],
783
+ "rounded-s": ["rounded-ss", "rounded-es"],
784
+ "rounded-e": ["rounded-se", "rounded-ee"],
785
+ "rounded-t": ["rounded-tl", "rounded-tr"],
786
+ "rounded-r": ["rounded-tr", "rounded-br"],
787
+ "rounded-b": ["rounded-br", "rounded-bl"],
788
+ "rounded-l": ["rounded-tl", "rounded-bl"],
789
+ "border-w": [
790
+ "border-w-x",
791
+ "border-w-y",
792
+ "border-w-t",
793
+ "border-w-r",
794
+ "border-w-b",
795
+ "border-w-l",
796
+ ],
797
+ "border-w-x": ["border-w-l", "border-w-r"],
798
+ "border-w-y": ["border-w-t", "border-w-b"],
799
+ "border-color": [
800
+ "border-color-x",
801
+ "border-color-y",
802
+ "border-color-t",
803
+ "border-color-r",
804
+ "border-color-b",
805
+ "border-color-l",
806
+ ],
807
+ "border-color-x": ["border-color-l", "border-color-r"],
808
+ "border-color-y": ["border-color-t", "border-color-b"],
809
+ "border-spacing": ["border-spacing-x", "border-spacing-y"],
810
+ scale: ["scale-x", "scale-y", "scale-z"],
811
+ translate: ["translate-x", "translate-y", "translate-z"],
812
+ skew: ["skew-x", "skew-y"],
813
+ };
814
+ /** Split on a separator char at BRACKET DEPTH 0 (so `[&:hover]` / `[x:y]` stay intact). */
815
+ function splitTopLevel(s, sep) {
816
+ const parts = [];
817
+ let depth = 0;
818
+ let buf = "";
819
+ for (const ch of s) {
820
+ if (ch === "[")
821
+ depth++;
822
+ else if (ch === "]")
823
+ depth = depth > 0 ? depth - 1 : 0;
824
+ if (ch === sep && depth === 0) {
825
+ parts.push(buf);
826
+ buf = "";
827
+ }
828
+ else {
829
+ buf += ch;
830
+ }
831
+ }
832
+ parts.push(buf);
833
+ return parts;
834
+ }
835
+ function resolveGroup(base) {
836
+ // Arbitrary property `[mask-type:luminance]` → its own per-property group.
837
+ if (base.startsWith("[") && base.endsWith("]")) {
838
+ const colon = base.indexOf(":");
839
+ return colon > 0 ? `arbitrary:${base.slice(1, colon)}` : "arbitrary";
840
+ }
841
+ for (const rule of RULES) {
842
+ if (rule.eq?.includes(base))
843
+ return rule.id;
844
+ if (rule.pre) {
845
+ for (const [prefix, validate] of rule.pre) {
846
+ if (base === prefix && validate(""))
847
+ return rule.id;
848
+ if (base.startsWith(`${prefix}-`) &&
849
+ validate(base.slice(prefix.length + 1))) {
850
+ return rule.id;
851
+ }
852
+ }
853
+ }
854
+ }
855
+ return null;
856
+ }
857
+ function parse(className) {
858
+ const modifiers = splitTopLevel(className, ":");
859
+ const last = modifiers.pop() ?? "";
860
+ let base = last;
861
+ let important = false;
862
+ // Important modifier — Tailwind v4 trailing `!` (e.g. `bg-red-500!`).
863
+ if (base.endsWith("!")) {
864
+ important = true;
865
+ base = base.slice(0, -1);
866
+ }
867
+ // Drop the opacity/postfix modifier (`/50`) at top level — not part of the group.
868
+ base = splitTopLevel(base, "/")[0] ?? base;
869
+ // Negative utilities share their positive group (`-mt-2` ≡ `mt-2`).
870
+ const lookup = base.startsWith("-") ? base.slice(1) : base;
871
+ const scope = `${modifiers.slice().sort().join(":")}${important ? "!" : ""}`;
872
+ return { className, scope, groupId: resolveGroup(lookup) };
873
+ }
874
+ // ─── twMerge ─────────────────────────────────────────────────────────────────
875
+ /** Resolve Tailwind class conflicts: within a variant scope, the last class of each group wins. */
876
+ export function twMerge(classList) {
877
+ const classes = classList.split(/\s+/).filter(Boolean);
878
+ const claimed = new Set();
879
+ const kept = [];
880
+ // Walk right→left: the first time we see a (scope, group) it's the winner;
881
+ // any earlier class of that group (or a group it supersedes) is dropped.
882
+ for (let i = classes.length - 1; i >= 0; i--) {
883
+ const cls = classes[i];
884
+ if (cls === undefined)
885
+ continue;
886
+ const { scope, groupId } = parse(cls);
887
+ if (groupId === null) {
888
+ kept.push(cls); // unknown class — never conflicts
889
+ continue;
890
+ }
891
+ const key = `${scope}|${groupId}`;
892
+ if (claimed.has(key))
893
+ continue; // already overridden by a later class
894
+ kept.push(cls);
895
+ claimed.add(key);
896
+ const supersedes = CONFLICTS[groupId];
897
+ if (supersedes) {
898
+ for (const g of supersedes)
899
+ claimed.add(`${scope}|${g}`);
900
+ }
901
+ }
902
+ return kept.reverse().join(" ");
903
+ }
904
+ // ─── cn ──────────────────────────────────────────────────────────────────────
905
+ /** Compose class values (clsx) then resolve Tailwind conflicts (twMerge). */
906
+ export function cn(...inputs) {
907
+ return twMerge(clsx(...inputs));
908
+ }