@birdycom/omniacss-migrate 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +129 -129
- package/bin/omniacss-migrate.mjs +8 -8
- package/package.json +61 -61
- package/scripts/build-color-map.mjs +31 -31
- package/scripts/extract-palette.mjs +13 -13
- package/scripts/tailwind-palette.json +290 -290
- package/src/bootstrap/compat.mjs +72 -72
- package/src/bootstrap/convert.mjs +102 -102
- package/src/bootstrap/migrate.mjs +259 -259
- package/src/bootstrap/rules.mjs +212 -212
- package/src/cli.mjs +139 -139
- package/src/codemod.mjs +82 -82
- package/src/core.mjs +67 -67
- package/src/data/colors.json +290 -290
- package/src/extract.mjs +91 -91
- package/src/index.mjs +12 -12
- package/src/native.mjs +72 -72
- package/src/postcss.mjs +308 -308
- package/src/preflight.mjs +52 -52
- package/src/project.mjs +194 -194
- package/src/resolve.mjs +220 -220
- package/src/scan.mjs +106 -106
- package/src/setup.mjs +205 -205
- package/src/utilities.mjs +713 -713
- package/src/values.mjs +184 -184
- package/src/variants.mjs +189 -189
- package/src/vite.mjs +81 -81
package/src/variants.mjs
CHANGED
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
// File: packages/migrate/src/variants.mjs
|
|
2
|
-
// Variant handling: hover:, md:, dark:, group-hover:, data-[state=open]:, [&>svg]:, has-[...]: …
|
|
3
|
-
// A variant transforms a "state" ({ sels, media, prelude, rank }) around the utility's class selector.
|
|
4
|
-
// Author: Serge Cools | Framework: OmniaCSS
|
|
5
|
-
|
|
6
|
-
import { arb, normalizeArb } from "./values.mjs";
|
|
7
|
-
|
|
8
|
-
/** Escape a string for use in a CSS class selector. */
|
|
9
|
-
export function esc(s) {
|
|
10
|
-
return s.replace(/^\d/, (c) => `\\3${c} `).replace(/[^A-Za-z0-9_ --]/g, (c) => `\\${c}`).replace(/^-(\d)/, "-\\3$1 ");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Split on `sep` outside of [] and (). */
|
|
14
|
-
export function splitTop(str, sep) {
|
|
15
|
-
const out = [];
|
|
16
|
-
let depth = 0, cur = "";
|
|
17
|
-
for (let i = 0; i < str.length; i++) {
|
|
18
|
-
const ch = str[i];
|
|
19
|
-
if (ch === "\\") { cur += ch + (str[++i] ?? ""); continue; }
|
|
20
|
-
if (ch === "[" || ch === "(") depth++;
|
|
21
|
-
else if (ch === "]" || ch === ")") depth--;
|
|
22
|
-
if (ch === sep && depth === 0) { out.push(cur); cur = ""; } else cur += ch;
|
|
23
|
-
}
|
|
24
|
-
out.push(cur);
|
|
25
|
-
return out;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Rank: static pseudo-classes in cascade order (hover < focus < active < disabled …), then group/peer, dark, media, screens.
|
|
29
|
-
const PSEUDO_ORDER = ["first-letter", "first-line", "marker", "selection", "file", "placeholder", "backdrop", "before", "after", "first", "last", "only", "odd", "even", "first-of-type", "last-of-type", "only-of-type",
|
|
30
|
-
"visited", "target", "open", "default", "checked", "indeterminate", "placeholder-shown", "autofill", "optional", "required", "valid", "invalid", "user-valid", "user-invalid", "in-range", "out-of-range", "read-only", "empty",
|
|
31
|
-
"focus-within", "hover", "focus", "focus-visible", "active", "enabled", "disabled"];
|
|
32
|
-
|
|
33
|
-
const PSEUDO_CLASS = {
|
|
34
|
-
first: ":first-child", last: ":last-child", only: ":only-child", odd: ":nth-child(odd)", even: ":nth-child(even)", "first-of-type": ":first-of-type", "last-of-type": ":last-of-type", "only-of-type": ":only-of-type",
|
|
35
|
-
visited: ":visited", target: ":target", open: ":is([open], :popover-open, :open)", default: ":default", checked: ":checked", indeterminate: ":indeterminate", "placeholder-shown": ":placeholder-shown", autofill: ":autofill",
|
|
36
|
-
optional: ":optional", required: ":required", valid: ":valid", invalid: ":invalid", "user-valid": ":user-valid", "user-invalid": ":user-invalid", "in-range": ":in-range", "out-of-range": ":out-of-range", "read-only": ":read-only",
|
|
37
|
-
empty: ":empty", "focus-within": ":focus-within", hover: ":hover", focus: ":focus", "focus-visible": ":focus-visible", active: ":active", enabled: ":enabled", disabled: ":disabled", inert: ":is([inert], [inert] *)",
|
|
38
|
-
};
|
|
39
|
-
const PSEUDO_ELEMENT = {
|
|
40
|
-
before: "::before", after: "::after", "first-letter": "::first-letter", "first-line": "::first-line", marker: "::marker", placeholder: "::placeholder", backdrop: "::backdrop", file: "::file-selector-button",
|
|
41
|
-
};
|
|
42
|
-
const MEDIA = {
|
|
43
|
-
print: "print", "motion-safe": "(prefers-reduced-motion: no-preference)", "motion-reduce": "(prefers-reduced-motion: reduce)", portrait: "(orientation: portrait)", landscape: "(orientation: landscape)",
|
|
44
|
-
"contrast-more": "(prefers-contrast: more)", "contrast-less": "(prefers-contrast: less)", "forced-colors": "(forced-colors: active)", "pointer-fine": "(pointer: fine)", "pointer-coarse": "(pointer: coarse)",
|
|
45
|
-
"pointer-none": "(pointer: none)", "any-pointer-fine": "(any-pointer: fine)", "any-pointer-coarse": "(any-pointer: coarse)", "noscript": "(scripting: none)",
|
|
46
|
-
};
|
|
47
|
-
const ARIA = ["busy", "checked", "disabled", "expanded", "hidden", "pressed", "readonly", "required", "selected"];
|
|
48
|
-
|
|
49
|
-
const quoteAttr = (s) => {
|
|
50
|
-
const m = s.match(/^([^=~|^$*]+)([~|^$*]?=)(.*)$/);
|
|
51
|
-
if (!m) return s;
|
|
52
|
-
let v = m[3];
|
|
53
|
-
if (!/^["']/.test(v)) v = `"${v}"`;
|
|
54
|
-
return `${m[1]}${m[2]}${v}`;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/** Selector pattern ("&:hover", "&[data-x]") that a pseudo-ish variant name applies to the element, or null. */
|
|
58
|
-
function innerPattern(name, ctx) {
|
|
59
|
-
const st = { sels: ["&"], media: [], prelude: [], rank: 0 };
|
|
60
|
-
const ok = applyVariant(name, st, ctx, { inner: true });
|
|
61
|
-
return ok && !st.media.length ? st.sels[0] : null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Apply one variant to the state.
|
|
66
|
-
* @returns {boolean} false when the variant is unknown
|
|
67
|
-
*/
|
|
68
|
-
export function applyVariant(name, st, ctx, { inner = false } = {}) {
|
|
69
|
-
const mapSels = (fn) => { st.sels = st.sels.map(fn); };
|
|
70
|
-
const rankOf = (n) => { const i = PSEUDO_ORDER.indexOf(n); return (i < 0 ? 40 : i) + 1; };
|
|
71
|
-
|
|
72
|
-
if (PSEUDO_CLASS[name]) { mapSels((s) => addSimple(s, PSEUDO_CLASS[name])); st.rank += rankOf(name) * 10; return true; }
|
|
73
|
-
if (name === "selection") { st.sels = st.sels.flatMap((s) => [`${s} ::selection`, `${s}::selection`]); st.rank += 40; return true; }
|
|
74
|
-
if (PSEUDO_ELEMENT[name]) {
|
|
75
|
-
mapSels((s) => s + PSEUDO_ELEMENT[name]);
|
|
76
|
-
if (name === "before" || name === "after") st.prelude.push(["content", "var(--oc-content, \"\")"]);
|
|
77
|
-
st.rank += rankOf(name) * 10; return true;
|
|
78
|
-
}
|
|
79
|
-
if (name === "dark") {
|
|
80
|
-
if (ctx.darkMode === "media") st.media.unshift("@media (prefers-color-scheme: dark)");
|
|
81
|
-
else mapSels((s) => `${ctx.darkSelector} ${s}`);
|
|
82
|
-
st.rank += 5000; return true;
|
|
83
|
-
}
|
|
84
|
-
if (name === "ltr" || name === "rtl") { mapSels((s) => `[dir="${name}"] ${s}`); st.rank += 4000; return true; }
|
|
85
|
-
if (MEDIA[name]) { st.media.unshift(`@media ${MEDIA[name]}`); st.rank += 6000; return true; }
|
|
86
|
-
if (name === "starting") { st.media.unshift("@starting-style"); st.rank += 6500; return true; }
|
|
87
|
-
if (name === "*") { mapSels((s) => `${s} > *`); st.rank += 3000; return true; }
|
|
88
|
-
if (name === "**") { mapSels((s) => `${s} *`); st.rank += 3000; return true; }
|
|
89
|
-
|
|
90
|
-
// screens
|
|
91
|
-
if (ctx.screens[name]) { st.media.unshift(`@media (min-width: ${ctx.screens[name]})`); st.rank += 10000 + ctx.screenRank(name); return true; }
|
|
92
|
-
let m = name.match(/^max-(.+)$/);
|
|
93
|
-
if (m && ctx.screens[m[1]]) { st.media.unshift(`@media (width < ${ctx.screens[m[1]]})`); st.rank += 9000 - ctx.screenRank(m[1]); return true; }
|
|
94
|
-
m = name.match(/^(min|max)-(\[.+\])$/);
|
|
95
|
-
if (m) { const a = arb(m[2]); st.media.unshift(`@media (${m[1]}-width: ${a.value})`); st.rank += m[1] === "min" ? 10050 : 8999; return true; }
|
|
96
|
-
// container queries
|
|
97
|
-
m = name.match(/^@(?:(min|max)-)?(.+?)(?:\/([\w-]+))?$/);
|
|
98
|
-
if (m) {
|
|
99
|
-
const [, kind, size, cname] = m;
|
|
100
|
-
const sizes = { "3xs": 16, "2xs": 18, xs: 20, sm: 24, md: 28, lg: 32, xl: 36, "2xl": 42, "3xl": 48, "4xl": 56, "5xl": 64, "6xl": 72, "7xl": 80 };
|
|
101
|
-
const a = arb(size);
|
|
102
|
-
const width = a ? a.value : sizes[size] ? `${sizes[size]}rem` : null;
|
|
103
|
-
if (width) {
|
|
104
|
-
const cond = kind === "max" ? `(width < ${width})` : `(min-width: ${width})`;
|
|
105
|
-
st.media.unshift(`@container ${cname ? cname + " " : ""}${cond}`);
|
|
106
|
-
st.rank += 20000 + (sizes[size] || 0); return true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// supports
|
|
110
|
-
m = name.match(/^(not-)?supports-(.+)$/);
|
|
111
|
-
if (m) {
|
|
112
|
-
const a = arb(m[2]);
|
|
113
|
-
const cond = a ? (/^[\w-]+:/.test(a.value) ? `(${a.value})` : a.value) : `(${m[2]}: var(--oc-x))`;
|
|
114
|
-
st.media.unshift(`@supports ${m[1] ? "not " : ""}${cond}`); st.rank += 6200; return true;
|
|
115
|
-
}
|
|
116
|
-
// data- / aria-
|
|
117
|
-
m = name.match(/^data-(.+)$/);
|
|
118
|
-
if (m) {
|
|
119
|
-
const a = arb(m[1]);
|
|
120
|
-
const attr = a ? `[data-${quoteAttr(a.value)}]` : `[data-${m[1]}]`;
|
|
121
|
-
mapSels((s) => addSimple(s, attr)); st.rank += 3500; return true;
|
|
122
|
-
}
|
|
123
|
-
m = name.match(/^aria-(.+)$/);
|
|
124
|
-
if (m) {
|
|
125
|
-
const a = arb(m[1]);
|
|
126
|
-
const attr = a ? `[aria-${quoteAttr(a.value)}]` : `[aria-${m[1]}="true"]`;
|
|
127
|
-
mapSels((s) => addSimple(s, attr)); st.rank += 3500; return true;
|
|
128
|
-
}
|
|
129
|
-
// group / peer
|
|
130
|
-
m = name.match(/^(group|peer)(?:-(.+?))?(?:\/([\w-]+))?$/);
|
|
131
|
-
if (m && m[2] !== undefined) {
|
|
132
|
-
const [, kind, innerName, gname] = m;
|
|
133
|
-
const base = `.${kind}${gname ? "\\/" + esc(gname) : ""}`;
|
|
134
|
-
let pattern;
|
|
135
|
-
const a = arb(innerName);
|
|
136
|
-
if (a) pattern = a.value.includes("&") ? a.value : `&${a.value}`;
|
|
137
|
-
else pattern = innerPattern(innerName, ctx);
|
|
138
|
-
if (!pattern) return false;
|
|
139
|
-
const rel = pattern.replace(/&/g, base);
|
|
140
|
-
mapSels((s) => (kind === "group" ? `${rel} ${s}` : `${rel} ~ ${s}`));
|
|
141
|
-
st.rank += 2000; return true;
|
|
142
|
-
}
|
|
143
|
-
// has / not / in / nth
|
|
144
|
-
m = name.match(/^(has|not|in)-(.+)$/);
|
|
145
|
-
if (m) {
|
|
146
|
-
const [, kind, innerName] = m;
|
|
147
|
-
const a = arb(innerName);
|
|
148
|
-
let pattern;
|
|
149
|
-
if (a) pattern = a.value.includes("&") ? a.value : `&${a.value}`;
|
|
150
|
-
else pattern = innerPattern(innerName, ctx);
|
|
151
|
-
if (kind === "not" && !pattern) {
|
|
152
|
-
const med = MEDIA[innerName];
|
|
153
|
-
if (med) { st.media.unshift(`@media not ${med}`); st.rank += 6000; return true; }
|
|
154
|
-
if (ctx.screens[innerName]) { st.media.unshift(`@media (width < ${ctx.screens[innerName]})`); st.rank += 9000; return true; }
|
|
155
|
-
}
|
|
156
|
-
if (!pattern) return false;
|
|
157
|
-
const rest = pattern.startsWith("&") ? pattern.slice(1) : pattern;
|
|
158
|
-
if (kind === "has") mapSels((s) => addSimple(s, `:has(${rest})`));
|
|
159
|
-
else if (kind === "not") mapSels((s) => addSimple(s, `:not(${rest})`));
|
|
160
|
-
else mapSels((s) => `:where(${rest || "*"}) ${s}`);
|
|
161
|
-
st.rank += 3200; return true;
|
|
162
|
-
}
|
|
163
|
-
m = name.match(/^nth-(last-)?(of-type-)?(.+)$/);
|
|
164
|
-
if (m) {
|
|
165
|
-
const a = arb(m[3]);
|
|
166
|
-
const arg = a ? a.value : m[3];
|
|
167
|
-
mapSels((s) => `${s}:nth-${m[1] ? "last-" : ""}${m[2] ? "of-type" : "child"}(${arg})`); st.rank += 450; return true;
|
|
168
|
-
}
|
|
169
|
-
if (ctx.customVariants && Object.hasOwn(ctx.customVariants, name)) {
|
|
170
|
-
const p = ctx.customVariants[name].trim();
|
|
171
|
-
if (p.startsWith("@")) st.media.unshift(p); else mapSels((s) => p.replace(/&/g, s));
|
|
172
|
-
st.rank += 3000; return true;
|
|
173
|
-
}
|
|
174
|
-
// arbitrary variant: [&>*]:, [&_svg]:, [@media(min-width:900px)]:
|
|
175
|
-
m = name.match(/^\[([\s\S]+)\]$/);
|
|
176
|
-
if (m) {
|
|
177
|
-
const body = normalizeArb(m[1]);
|
|
178
|
-
if (body.startsWith("@")) { st.media.unshift(body.replace(/^@(media|supports|container)\s*/, "@$1 ")); st.rank += 6100; return true; }
|
|
179
|
-
if (!body.includes("&")) return false;
|
|
180
|
-
mapSels((s) => body.replace(/&/g, s)); st.rank += 3000; return true;
|
|
181
|
-
}
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** Append a simple selector (pseudo-class, attribute …) in front of a trailing pseudo-element: `.a::after` + `:hover` -> `.a:hover::after`. */
|
|
186
|
-
function addSimple(sel, suffix) {
|
|
187
|
-
const m = sel.match(/^(.*?)(::[a-z-]+)$/s);
|
|
188
|
-
return m ? m[1] + suffix + m[2] : sel + suffix;
|
|
189
|
-
}
|
|
1
|
+
// File: packages/migrate/src/variants.mjs
|
|
2
|
+
// Variant handling: hover:, md:, dark:, group-hover:, data-[state=open]:, [&>svg]:, has-[...]: …
|
|
3
|
+
// A variant transforms a "state" ({ sels, media, prelude, rank }) around the utility's class selector.
|
|
4
|
+
// Author: Serge Cools | Framework: OmniaCSS
|
|
5
|
+
|
|
6
|
+
import { arb, normalizeArb } from "./values.mjs";
|
|
7
|
+
|
|
8
|
+
/** Escape a string for use in a CSS class selector. */
|
|
9
|
+
export function esc(s) {
|
|
10
|
+
return s.replace(/^\d/, (c) => `\\3${c} `).replace(/[^A-Za-z0-9_ --]/g, (c) => `\\${c}`).replace(/^-(\d)/, "-\\3$1 ");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Split on `sep` outside of [] and (). */
|
|
14
|
+
export function splitTop(str, sep) {
|
|
15
|
+
const out = [];
|
|
16
|
+
let depth = 0, cur = "";
|
|
17
|
+
for (let i = 0; i < str.length; i++) {
|
|
18
|
+
const ch = str[i];
|
|
19
|
+
if (ch === "\\") { cur += ch + (str[++i] ?? ""); continue; }
|
|
20
|
+
if (ch === "[" || ch === "(") depth++;
|
|
21
|
+
else if (ch === "]" || ch === ")") depth--;
|
|
22
|
+
if (ch === sep && depth === 0) { out.push(cur); cur = ""; } else cur += ch;
|
|
23
|
+
}
|
|
24
|
+
out.push(cur);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Rank: static pseudo-classes in cascade order (hover < focus < active < disabled …), then group/peer, dark, media, screens.
|
|
29
|
+
const PSEUDO_ORDER = ["first-letter", "first-line", "marker", "selection", "file", "placeholder", "backdrop", "before", "after", "first", "last", "only", "odd", "even", "first-of-type", "last-of-type", "only-of-type",
|
|
30
|
+
"visited", "target", "open", "default", "checked", "indeterminate", "placeholder-shown", "autofill", "optional", "required", "valid", "invalid", "user-valid", "user-invalid", "in-range", "out-of-range", "read-only", "empty",
|
|
31
|
+
"focus-within", "hover", "focus", "focus-visible", "active", "enabled", "disabled"];
|
|
32
|
+
|
|
33
|
+
const PSEUDO_CLASS = {
|
|
34
|
+
first: ":first-child", last: ":last-child", only: ":only-child", odd: ":nth-child(odd)", even: ":nth-child(even)", "first-of-type": ":first-of-type", "last-of-type": ":last-of-type", "only-of-type": ":only-of-type",
|
|
35
|
+
visited: ":visited", target: ":target", open: ":is([open], :popover-open, :open)", default: ":default", checked: ":checked", indeterminate: ":indeterminate", "placeholder-shown": ":placeholder-shown", autofill: ":autofill",
|
|
36
|
+
optional: ":optional", required: ":required", valid: ":valid", invalid: ":invalid", "user-valid": ":user-valid", "user-invalid": ":user-invalid", "in-range": ":in-range", "out-of-range": ":out-of-range", "read-only": ":read-only",
|
|
37
|
+
empty: ":empty", "focus-within": ":focus-within", hover: ":hover", focus: ":focus", "focus-visible": ":focus-visible", active: ":active", enabled: ":enabled", disabled: ":disabled", inert: ":is([inert], [inert] *)",
|
|
38
|
+
};
|
|
39
|
+
const PSEUDO_ELEMENT = {
|
|
40
|
+
before: "::before", after: "::after", "first-letter": "::first-letter", "first-line": "::first-line", marker: "::marker", placeholder: "::placeholder", backdrop: "::backdrop", file: "::file-selector-button",
|
|
41
|
+
};
|
|
42
|
+
const MEDIA = {
|
|
43
|
+
print: "print", "motion-safe": "(prefers-reduced-motion: no-preference)", "motion-reduce": "(prefers-reduced-motion: reduce)", portrait: "(orientation: portrait)", landscape: "(orientation: landscape)",
|
|
44
|
+
"contrast-more": "(prefers-contrast: more)", "contrast-less": "(prefers-contrast: less)", "forced-colors": "(forced-colors: active)", "pointer-fine": "(pointer: fine)", "pointer-coarse": "(pointer: coarse)",
|
|
45
|
+
"pointer-none": "(pointer: none)", "any-pointer-fine": "(any-pointer: fine)", "any-pointer-coarse": "(any-pointer: coarse)", "noscript": "(scripting: none)",
|
|
46
|
+
};
|
|
47
|
+
const ARIA = ["busy", "checked", "disabled", "expanded", "hidden", "pressed", "readonly", "required", "selected"];
|
|
48
|
+
|
|
49
|
+
const quoteAttr = (s) => {
|
|
50
|
+
const m = s.match(/^([^=~|^$*]+)([~|^$*]?=)(.*)$/);
|
|
51
|
+
if (!m) return s;
|
|
52
|
+
let v = m[3];
|
|
53
|
+
if (!/^["']/.test(v)) v = `"${v}"`;
|
|
54
|
+
return `${m[1]}${m[2]}${v}`;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Selector pattern ("&:hover", "&[data-x]") that a pseudo-ish variant name applies to the element, or null. */
|
|
58
|
+
function innerPattern(name, ctx) {
|
|
59
|
+
const st = { sels: ["&"], media: [], prelude: [], rank: 0 };
|
|
60
|
+
const ok = applyVariant(name, st, ctx, { inner: true });
|
|
61
|
+
return ok && !st.media.length ? st.sels[0] : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Apply one variant to the state.
|
|
66
|
+
* @returns {boolean} false when the variant is unknown
|
|
67
|
+
*/
|
|
68
|
+
export function applyVariant(name, st, ctx, { inner = false } = {}) {
|
|
69
|
+
const mapSels = (fn) => { st.sels = st.sels.map(fn); };
|
|
70
|
+
const rankOf = (n) => { const i = PSEUDO_ORDER.indexOf(n); return (i < 0 ? 40 : i) + 1; };
|
|
71
|
+
|
|
72
|
+
if (PSEUDO_CLASS[name]) { mapSels((s) => addSimple(s, PSEUDO_CLASS[name])); st.rank += rankOf(name) * 10; return true; }
|
|
73
|
+
if (name === "selection") { st.sels = st.sels.flatMap((s) => [`${s} ::selection`, `${s}::selection`]); st.rank += 40; return true; }
|
|
74
|
+
if (PSEUDO_ELEMENT[name]) {
|
|
75
|
+
mapSels((s) => s + PSEUDO_ELEMENT[name]);
|
|
76
|
+
if (name === "before" || name === "after") st.prelude.push(["content", "var(--oc-content, \"\")"]);
|
|
77
|
+
st.rank += rankOf(name) * 10; return true;
|
|
78
|
+
}
|
|
79
|
+
if (name === "dark") {
|
|
80
|
+
if (ctx.darkMode === "media") st.media.unshift("@media (prefers-color-scheme: dark)");
|
|
81
|
+
else mapSels((s) => `${ctx.darkSelector} ${s}`);
|
|
82
|
+
st.rank += 5000; return true;
|
|
83
|
+
}
|
|
84
|
+
if (name === "ltr" || name === "rtl") { mapSels((s) => `[dir="${name}"] ${s}`); st.rank += 4000; return true; }
|
|
85
|
+
if (MEDIA[name]) { st.media.unshift(`@media ${MEDIA[name]}`); st.rank += 6000; return true; }
|
|
86
|
+
if (name === "starting") { st.media.unshift("@starting-style"); st.rank += 6500; return true; }
|
|
87
|
+
if (name === "*") { mapSels((s) => `${s} > *`); st.rank += 3000; return true; }
|
|
88
|
+
if (name === "**") { mapSels((s) => `${s} *`); st.rank += 3000; return true; }
|
|
89
|
+
|
|
90
|
+
// screens
|
|
91
|
+
if (ctx.screens[name]) { st.media.unshift(`@media (min-width: ${ctx.screens[name]})`); st.rank += 10000 + ctx.screenRank(name); return true; }
|
|
92
|
+
let m = name.match(/^max-(.+)$/);
|
|
93
|
+
if (m && ctx.screens[m[1]]) { st.media.unshift(`@media (width < ${ctx.screens[m[1]]})`); st.rank += 9000 - ctx.screenRank(m[1]); return true; }
|
|
94
|
+
m = name.match(/^(min|max)-(\[.+\])$/);
|
|
95
|
+
if (m) { const a = arb(m[2]); st.media.unshift(`@media (${m[1]}-width: ${a.value})`); st.rank += m[1] === "min" ? 10050 : 8999; return true; }
|
|
96
|
+
// container queries
|
|
97
|
+
m = name.match(/^@(?:(min|max)-)?(.+?)(?:\/([\w-]+))?$/);
|
|
98
|
+
if (m) {
|
|
99
|
+
const [, kind, size, cname] = m;
|
|
100
|
+
const sizes = { "3xs": 16, "2xs": 18, xs: 20, sm: 24, md: 28, lg: 32, xl: 36, "2xl": 42, "3xl": 48, "4xl": 56, "5xl": 64, "6xl": 72, "7xl": 80 };
|
|
101
|
+
const a = arb(size);
|
|
102
|
+
const width = a ? a.value : sizes[size] ? `${sizes[size]}rem` : null;
|
|
103
|
+
if (width) {
|
|
104
|
+
const cond = kind === "max" ? `(width < ${width})` : `(min-width: ${width})`;
|
|
105
|
+
st.media.unshift(`@container ${cname ? cname + " " : ""}${cond}`);
|
|
106
|
+
st.rank += 20000 + (sizes[size] || 0); return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// supports
|
|
110
|
+
m = name.match(/^(not-)?supports-(.+)$/);
|
|
111
|
+
if (m) {
|
|
112
|
+
const a = arb(m[2]);
|
|
113
|
+
const cond = a ? (/^[\w-]+:/.test(a.value) ? `(${a.value})` : a.value) : `(${m[2]}: var(--oc-x))`;
|
|
114
|
+
st.media.unshift(`@supports ${m[1] ? "not " : ""}${cond}`); st.rank += 6200; return true;
|
|
115
|
+
}
|
|
116
|
+
// data- / aria-
|
|
117
|
+
m = name.match(/^data-(.+)$/);
|
|
118
|
+
if (m) {
|
|
119
|
+
const a = arb(m[1]);
|
|
120
|
+
const attr = a ? `[data-${quoteAttr(a.value)}]` : `[data-${m[1]}]`;
|
|
121
|
+
mapSels((s) => addSimple(s, attr)); st.rank += 3500; return true;
|
|
122
|
+
}
|
|
123
|
+
m = name.match(/^aria-(.+)$/);
|
|
124
|
+
if (m) {
|
|
125
|
+
const a = arb(m[1]);
|
|
126
|
+
const attr = a ? `[aria-${quoteAttr(a.value)}]` : `[aria-${m[1]}="true"]`;
|
|
127
|
+
mapSels((s) => addSimple(s, attr)); st.rank += 3500; return true;
|
|
128
|
+
}
|
|
129
|
+
// group / peer
|
|
130
|
+
m = name.match(/^(group|peer)(?:-(.+?))?(?:\/([\w-]+))?$/);
|
|
131
|
+
if (m && m[2] !== undefined) {
|
|
132
|
+
const [, kind, innerName, gname] = m;
|
|
133
|
+
const base = `.${kind}${gname ? "\\/" + esc(gname) : ""}`;
|
|
134
|
+
let pattern;
|
|
135
|
+
const a = arb(innerName);
|
|
136
|
+
if (a) pattern = a.value.includes("&") ? a.value : `&${a.value}`;
|
|
137
|
+
else pattern = innerPattern(innerName, ctx);
|
|
138
|
+
if (!pattern) return false;
|
|
139
|
+
const rel = pattern.replace(/&/g, base);
|
|
140
|
+
mapSels((s) => (kind === "group" ? `${rel} ${s}` : `${rel} ~ ${s}`));
|
|
141
|
+
st.rank += 2000; return true;
|
|
142
|
+
}
|
|
143
|
+
// has / not / in / nth
|
|
144
|
+
m = name.match(/^(has|not|in)-(.+)$/);
|
|
145
|
+
if (m) {
|
|
146
|
+
const [, kind, innerName] = m;
|
|
147
|
+
const a = arb(innerName);
|
|
148
|
+
let pattern;
|
|
149
|
+
if (a) pattern = a.value.includes("&") ? a.value : `&${a.value}`;
|
|
150
|
+
else pattern = innerPattern(innerName, ctx);
|
|
151
|
+
if (kind === "not" && !pattern) {
|
|
152
|
+
const med = MEDIA[innerName];
|
|
153
|
+
if (med) { st.media.unshift(`@media not ${med}`); st.rank += 6000; return true; }
|
|
154
|
+
if (ctx.screens[innerName]) { st.media.unshift(`@media (width < ${ctx.screens[innerName]})`); st.rank += 9000; return true; }
|
|
155
|
+
}
|
|
156
|
+
if (!pattern) return false;
|
|
157
|
+
const rest = pattern.startsWith("&") ? pattern.slice(1) : pattern;
|
|
158
|
+
if (kind === "has") mapSels((s) => addSimple(s, `:has(${rest})`));
|
|
159
|
+
else if (kind === "not") mapSels((s) => addSimple(s, `:not(${rest})`));
|
|
160
|
+
else mapSels((s) => `:where(${rest || "*"}) ${s}`);
|
|
161
|
+
st.rank += 3200; return true;
|
|
162
|
+
}
|
|
163
|
+
m = name.match(/^nth-(last-)?(of-type-)?(.+)$/);
|
|
164
|
+
if (m) {
|
|
165
|
+
const a = arb(m[3]);
|
|
166
|
+
const arg = a ? a.value : m[3];
|
|
167
|
+
mapSels((s) => `${s}:nth-${m[1] ? "last-" : ""}${m[2] ? "of-type" : "child"}(${arg})`); st.rank += 450; return true;
|
|
168
|
+
}
|
|
169
|
+
if (ctx.customVariants && Object.hasOwn(ctx.customVariants, name)) {
|
|
170
|
+
const p = ctx.customVariants[name].trim();
|
|
171
|
+
if (p.startsWith("@")) st.media.unshift(p); else mapSels((s) => p.replace(/&/g, s));
|
|
172
|
+
st.rank += 3000; return true;
|
|
173
|
+
}
|
|
174
|
+
// arbitrary variant: [&>*]:, [&_svg]:, [@media(min-width:900px)]:
|
|
175
|
+
m = name.match(/^\[([\s\S]+)\]$/);
|
|
176
|
+
if (m) {
|
|
177
|
+
const body = normalizeArb(m[1]);
|
|
178
|
+
if (body.startsWith("@")) { st.media.unshift(body.replace(/^@(media|supports|container)\s*/, "@$1 ")); st.rank += 6100; return true; }
|
|
179
|
+
if (!body.includes("&")) return false;
|
|
180
|
+
mapSels((s) => body.replace(/&/g, s)); st.rank += 3000; return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Append a simple selector (pseudo-class, attribute …) in front of a trailing pseudo-element: `.a::after` + `:hover` -> `.a:hover::after`. */
|
|
186
|
+
function addSimple(sel, suffix) {
|
|
187
|
+
const m = sel.match(/^(.*?)(::[a-z-]+)$/s);
|
|
188
|
+
return m ? m[1] + suffix + m[2] : sel + suffix;
|
|
189
|
+
}
|
package/src/vite.mjs
CHANGED
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
// File: packages/migrate/src/vite.mjs
|
|
2
|
-
// Vite plugin: drop-in replacement for the Tailwind Vite plugin. It puts the "keep your classes" PostCSS plugin
|
|
3
|
-
// in front of your existing PostCSS plugins (autoprefixer …) and removes any Tailwind PostCSS plugin.
|
|
4
|
-
//
|
|
5
|
-
// // vite.config.js
|
|
6
|
-
// import omniaMigrate from "@birdycom/omniacss-migrate/vite";
|
|
7
|
-
// export default defineConfig({ plugins: [laravel({ ... }), omniaMigrate()] });
|
|
8
|
-
//
|
|
9
|
-
// Author: Serge Cools | Framework: OmniaCSS
|
|
10
|
-
|
|
11
|
-
import fs from "node:fs";
|
|
12
|
-
import path from "node:path";
|
|
13
|
-
import { createRequire } from "node:module";
|
|
14
|
-
import { pathToFileURL } from "node:url";
|
|
15
|
-
import postcss from "postcss";
|
|
16
|
-
import omniaMigratePostcss from "./postcss.mjs";
|
|
17
|
-
|
|
18
|
-
// every PostCSS entry that belongs to the Tailwind ecosystem is dropped (@tailwindcss/postcss, tailwindcss, @iconify/tailwind4 …)
|
|
19
|
-
const isTailwind = (name) => /tailwind/i.test(name);
|
|
20
|
-
|
|
21
|
-
/** Load the project's postcss.config.* into a plugin array (Tailwind removed). */
|
|
22
|
-
async function loadExistingPlugins(root) {
|
|
23
|
-
const file = ["postcss.config.js", "postcss.config.mjs", "postcss.config.cjs"].map((f) => path.join(root, f)).find((f) => fs.existsSync(f));
|
|
24
|
-
if (!file) return [];
|
|
25
|
-
let cfg;
|
|
26
|
-
try { const mod = await import(pathToFileURL(file).href); cfg = mod.default ?? mod; } catch { return []; }
|
|
27
|
-
if (typeof cfg === "function") cfg = cfg({ env: process.env.NODE_ENV || "development" });
|
|
28
|
-
const plugins = cfg?.plugins;
|
|
29
|
-
if (!plugins) return [];
|
|
30
|
-
const req = createRequire(path.join(root, "package.json"));
|
|
31
|
-
const out = [];
|
|
32
|
-
if (Array.isArray(plugins)) {
|
|
33
|
-
for (const p of plugins) {
|
|
34
|
-
if (typeof p === "string") { if (isTailwind(p)) continue; try { const m = req(p); out.push((m.default ?? m)()); } catch { /* skip */ } }
|
|
35
|
-
else if (p && (p.postcssPlugin === "tailwindcss" || p.postcssPlugin === "@tailwindcss/postcss" || p.postcssPlugin === "internal:tailwind")) continue;
|
|
36
|
-
else if (typeof p === "function" && (p.name === "tailwindcss" || p.postcssPlugin === "tailwindcss")) continue;
|
|
37
|
-
else out.push(p);
|
|
38
|
-
}
|
|
39
|
-
} else {
|
|
40
|
-
for (const [name, opts] of Object.entries(plugins)) {
|
|
41
|
-
if (isTailwind(name) || opts === false) continue;
|
|
42
|
-
try {
|
|
43
|
-
const m = await import(pathToFileURL(req.resolve(name)).href).catch(() => ({ default: req(name) }));
|
|
44
|
-
const factory = m.default ?? m;
|
|
45
|
-
out.push(typeof factory === "function" ? factory(opts) : factory);
|
|
46
|
-
} catch { /* plugin not installed: skip */ }
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return out;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** @param {Parameters<typeof omniaMigratePostcss>[0]} [options] */
|
|
53
|
-
export default function omniaMigrate(options = {}) {
|
|
54
|
-
let root = process.cwd();
|
|
55
|
-
return {
|
|
56
|
-
name: "omniacss-migrate",
|
|
57
|
-
enforce: "pre",
|
|
58
|
-
// Vite inlines `@import` before PostCSS plugins run, and `@import "tailwindcss"` no longer resolves once the package is gone:
|
|
59
|
-
// so the directive entry files are compiled here, before Vite looks at them. @apply in imported files is handled by the PostCSS plugin below.
|
|
60
|
-
async transform(code, id) {
|
|
61
|
-
const file = id.split("?")[0];
|
|
62
|
-
if (!/\.(css|pcss|postcss)$/.test(file)) return null;
|
|
63
|
-
if (!/@import\s+["']tailwindcss|@tailwind\s+\w+|@apply\s|@theme\b/.test(code)) return null;
|
|
64
|
-
const result = await postcss([omniaMigratePostcss({ cwd: root, ...options })]).process(code, { from: file, map: false });
|
|
65
|
-
for (const m of result.messages) if (m.type === "dependency" && m.file) this.addWatchFile(m.file);
|
|
66
|
-
return { code: result.css, map: null };
|
|
67
|
-
},
|
|
68
|
-
configResolved(config) {
|
|
69
|
-
root = config.root;
|
|
70
|
-
},
|
|
71
|
-
async config(config) {
|
|
72
|
-
const root = path.resolve(config.root || process.cwd());
|
|
73
|
-
config.css ||= {};
|
|
74
|
-
const existing = config.css.postcss;
|
|
75
|
-
if (typeof existing === "string") return; // custom config dir: use the PostCSS plugin there instead
|
|
76
|
-
const others = existing && typeof existing === "object" && existing.plugins ? existing.plugins : await loadExistingPlugins(root);
|
|
77
|
-
const keep = others.filter((p) => !(p && (p.postcssPlugin === "tailwindcss" || p.postcssPlugin === "@tailwindcss/postcss" || p.name === "tailwindcss")) && p !== omniaMigratePostcss);
|
|
78
|
-
config.css.postcss = { ...(existing && typeof existing === "object" ? existing : {}), plugins: [omniaMigratePostcss({ cwd: root, ...options }), ...keep] };
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
1
|
+
// File: packages/migrate/src/vite.mjs
|
|
2
|
+
// Vite plugin: drop-in replacement for the Tailwind Vite plugin. It puts the "keep your classes" PostCSS plugin
|
|
3
|
+
// in front of your existing PostCSS plugins (autoprefixer …) and removes any Tailwind PostCSS plugin.
|
|
4
|
+
//
|
|
5
|
+
// // vite.config.js
|
|
6
|
+
// import omniaMigrate from "@birdycom/omniacss-migrate/vite";
|
|
7
|
+
// export default defineConfig({ plugins: [laravel({ ... }), omniaMigrate()] });
|
|
8
|
+
//
|
|
9
|
+
// Author: Serge Cools | Framework: OmniaCSS
|
|
10
|
+
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { pathToFileURL } from "node:url";
|
|
15
|
+
import postcss from "postcss";
|
|
16
|
+
import omniaMigratePostcss from "./postcss.mjs";
|
|
17
|
+
|
|
18
|
+
// every PostCSS entry that belongs to the Tailwind ecosystem is dropped (@tailwindcss/postcss, tailwindcss, @iconify/tailwind4 …)
|
|
19
|
+
const isTailwind = (name) => /tailwind/i.test(name);
|
|
20
|
+
|
|
21
|
+
/** Load the project's postcss.config.* into a plugin array (Tailwind removed). */
|
|
22
|
+
async function loadExistingPlugins(root) {
|
|
23
|
+
const file = ["postcss.config.js", "postcss.config.mjs", "postcss.config.cjs"].map((f) => path.join(root, f)).find((f) => fs.existsSync(f));
|
|
24
|
+
if (!file) return [];
|
|
25
|
+
let cfg;
|
|
26
|
+
try { const mod = await import(pathToFileURL(file).href); cfg = mod.default ?? mod; } catch { return []; }
|
|
27
|
+
if (typeof cfg === "function") cfg = cfg({ env: process.env.NODE_ENV || "development" });
|
|
28
|
+
const plugins = cfg?.plugins;
|
|
29
|
+
if (!plugins) return [];
|
|
30
|
+
const req = createRequire(path.join(root, "package.json"));
|
|
31
|
+
const out = [];
|
|
32
|
+
if (Array.isArray(plugins)) {
|
|
33
|
+
for (const p of plugins) {
|
|
34
|
+
if (typeof p === "string") { if (isTailwind(p)) continue; try { const m = req(p); out.push((m.default ?? m)()); } catch { /* skip */ } }
|
|
35
|
+
else if (p && (p.postcssPlugin === "tailwindcss" || p.postcssPlugin === "@tailwindcss/postcss" || p.postcssPlugin === "internal:tailwind")) continue;
|
|
36
|
+
else if (typeof p === "function" && (p.name === "tailwindcss" || p.postcssPlugin === "tailwindcss")) continue;
|
|
37
|
+
else out.push(p);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
for (const [name, opts] of Object.entries(plugins)) {
|
|
41
|
+
if (isTailwind(name) || opts === false) continue;
|
|
42
|
+
try {
|
|
43
|
+
const m = await import(pathToFileURL(req.resolve(name)).href).catch(() => ({ default: req(name) }));
|
|
44
|
+
const factory = m.default ?? m;
|
|
45
|
+
out.push(typeof factory === "function" ? factory(opts) : factory);
|
|
46
|
+
} catch { /* plugin not installed: skip */ }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @param {Parameters<typeof omniaMigratePostcss>[0]} [options] */
|
|
53
|
+
export default function omniaMigrate(options = {}) {
|
|
54
|
+
let root = process.cwd();
|
|
55
|
+
return {
|
|
56
|
+
name: "omniacss-migrate",
|
|
57
|
+
enforce: "pre",
|
|
58
|
+
// Vite inlines `@import` before PostCSS plugins run, and `@import "tailwindcss"` no longer resolves once the package is gone:
|
|
59
|
+
// so the directive entry files are compiled here, before Vite looks at them. @apply in imported files is handled by the PostCSS plugin below.
|
|
60
|
+
async transform(code, id) {
|
|
61
|
+
const file = id.split("?")[0];
|
|
62
|
+
if (!/\.(css|pcss|postcss)$/.test(file)) return null;
|
|
63
|
+
if (!/@import\s+["']tailwindcss|@tailwind\s+\w+|@apply\s|@theme\b/.test(code)) return null;
|
|
64
|
+
const result = await postcss([omniaMigratePostcss({ cwd: root, ...options })]).process(code, { from: file, map: false });
|
|
65
|
+
for (const m of result.messages) if (m.type === "dependency" && m.file) this.addWatchFile(m.file);
|
|
66
|
+
return { code: result.css, map: null };
|
|
67
|
+
},
|
|
68
|
+
configResolved(config) {
|
|
69
|
+
root = config.root;
|
|
70
|
+
},
|
|
71
|
+
async config(config) {
|
|
72
|
+
const root = path.resolve(config.root || process.cwd());
|
|
73
|
+
config.css ||= {};
|
|
74
|
+
const existing = config.css.postcss;
|
|
75
|
+
if (typeof existing === "string") return; // custom config dir: use the PostCSS plugin there instead
|
|
76
|
+
const others = existing && typeof existing === "object" && existing.plugins ? existing.plugins : await loadExistingPlugins(root);
|
|
77
|
+
const keep = others.filter((p) => !(p && (p.postcssPlugin === "tailwindcss" || p.postcssPlugin === "@tailwindcss/postcss" || p.name === "tailwindcss")) && p !== omniaMigratePostcss);
|
|
78
|
+
config.css.postcss = { ...(existing && typeof existing === "object" ? existing : {}), plugins: [omniaMigratePostcss({ cwd: root, ...options }), ...keep] };
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|