@birdycom/omniacss-migrate 0.11.0 → 0.11.2
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/bootstrap/compat.mjs
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
// File: packages/migrate/src/bootstrap/compat.mjs
|
|
2
|
-
// The small stylesheet that keeps the Bootstrap grid (container, row, col-*, offset-*, g-*, row-cols-*) and the few
|
|
3
|
-
// utilities without an OmniaCSS twin working, for exactly the classes a project uses. Colors are Omnia tokens.
|
|
4
|
-
// Map: media query per breakpoint -> grid rule per class -> utility rule per class -> sorted CSS
|
|
5
|
-
// Author: Serge Cools | Framework: OmniaCSS
|
|
6
|
-
|
|
7
|
-
import { BREAKPOINTS, CONTAINERS, OMNIA_BREAKPOINTS, utilityDecls } from "./rules.mjs";
|
|
8
|
-
import { bpVariant } from "./convert.mjs";
|
|
9
|
-
|
|
10
|
-
const SPACE = { 0: "0", 1: "0.25rem", 2: "0.5rem", 3: "1rem", 4: "1.5rem", 5: "3rem" };
|
|
11
|
-
const pct = (n, of) => `${+((n / of) * 100).toFixed(6)}%`;
|
|
12
|
-
|
|
13
|
-
/** min-width in rem for a Bootstrap breakpoint, following the chosen breakpoint mode. */
|
|
14
|
-
function minRem(bp, version, mode) {
|
|
15
|
-
const px = BREAKPOINTS[version][bp];
|
|
16
|
-
if (!px) return 0;
|
|
17
|
-
if (mode === "exact") return px / 16;
|
|
18
|
-
return OMNIA_BREAKPOINTS[bpVariant(bp, version, mode).slice(0, -1)] / 16;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {Iterable<string>} gridClasses grid classes found in the project
|
|
23
|
-
* @param {Iterable<string>} utilityClasses utilities without an OmniaCSS twin
|
|
24
|
-
* @param {{ version: 3|4|5, breakpoints?: "omnia"|"exact" }} o
|
|
25
|
-
*/
|
|
26
|
-
export function compatCss(gridClasses, utilityClasses, { version: v, breakpoints: mode = "omnia" }) {
|
|
27
|
-
const rules = []; // { at: rem, css }
|
|
28
|
-
const add = (at, css) => rules.push({ at, css });
|
|
29
|
-
const gutter = v === 3 ? "30px" : "1.5rem";
|
|
30
|
-
const grid = new Set(gridClasses);
|
|
31
|
-
let m;
|
|
32
|
-
|
|
33
|
-
if ([...grid].some((c) => c.startsWith("container"))) {
|
|
34
|
-
const list = [...grid].filter((c) => c.startsWith("container"));
|
|
35
|
-
add(0, `${list.map((c) => "." + c).join(", ")} {\n width: 100%;\n margin-inline: auto;\n padding-inline: calc(${gutter} * 0.5);\n max-width: none;\n}`);
|
|
36
|
-
for (const [bp, max] of Object.entries(CONTAINERS[v])) {
|
|
37
|
-
const sel = list.filter((c) => c === "container" || (/^container-(sm|md|lg|xl|xxl)$/.test(c) && BREAKPOINTS[v][c.slice(10)] <= BREAKPOINTS[v][bp]));
|
|
38
|
-
if (sel.length) add(minRem(bp, v, mode), `${sel.map((c) => "." + c).join(", ")} { max-width: ${max}px; }`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (grid.has("row") || [...grid].some((c) => /^(col|offset|g|row-cols)/.test(c))) {
|
|
42
|
-
add(0, `.row {\n --oc-gutter-x: ${gutter};\n --oc-gutter-y: 0;\n display: flex;\n flex-wrap: wrap;\n margin-top: calc(-1 * var(--oc-gutter-y));\n margin-inline: calc(-0.5 * var(--oc-gutter-x));\n}\n.row > * {\n flex-shrink: 0;\n width: 100%;\n max-width: 100%;\n padding-inline: calc(var(--oc-gutter-x) * 0.5);\n margin-top: var(--oc-gutter-y);\n}`);
|
|
43
|
-
}
|
|
44
|
-
for (const c of grid) {
|
|
45
|
-
// col, col-md, col-auto, col-md-auto
|
|
46
|
-
if ((m = c.match(/^col(?:-(sm|md|lg|xl|xxl))?$/))) add(minRem(m[1], v, mode), `.${c} { flex: 1 0 0%; }`);
|
|
47
|
-
else if ((m = c.match(/^col(?:-(sm|md|lg|xl|xxl))?-auto$/))) add(minRem(m[1], v, mode), `.${c} { flex: 0 0 auto; width: auto; }`);
|
|
48
|
-
else if ((m = c.match(/^col(?:-(xs|sm|md|lg|xl|xxl))?-(\d{1,2})$/))) add(m[1] === "xs" ? 0 : minRem(m[1], v, mode), `.${c} { flex: 0 0 auto; width: ${pct(+m[2], 12)}; }`);
|
|
49
|
-
else if ((m = c.match(/^offset(?:-(sm|md|lg|xl|xxl))?-(\d{1,2})$/)) || (m = c.match(/^col-(xs|sm|md|lg)-offset-(\d{1,2})$/))) add(m[1] === "xs" ? 0 : minRem(m[1], v, mode), `.${c} { margin-left: ${+m[2] ? pct(+m[2], 12) : "0"}; }`);
|
|
50
|
-
else if ((m = c.match(/^col-(xs|sm|md|lg)-(push|pull)-(\d{1,2})$/))) add(m[1] === "xs" ? 0 : minRem(m[1], v, mode), `.${c} { position: relative; ${m[2] === "push" ? "left" : "right"}: ${pct(+m[3], 12)}; }`);
|
|
51
|
-
else if ((m = c.match(/^row-cols(?:-(sm|md|lg|xl|xxl))?-(\d|auto)$/))) add(minRem(m[1], v, mode), `.${c} > * { flex: 0 0 auto; width: ${m[2] === "auto" ? "auto" : pct(1, +m[2])}; }`);
|
|
52
|
-
else if ((m = c.match(/^g([xy]?)(?:-(sm|md|lg|xl|xxl))?-([0-5])$/))) {
|
|
53
|
-
const vars = m[1] === "x" ? ["x"] : m[1] === "y" ? ["y"] : ["x", "y"];
|
|
54
|
-
add(minRem(m[2], v, mode), `.${c} { ${vars.map((a) => `--oc-gutter-${a}: ${SPACE[m[3]]};`).join(" ")} }`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
for (const c of utilityClasses) {
|
|
58
|
-
let base = c, at = 0;
|
|
59
|
-
if ((m = c.match(/^([a-z]+(?:-[a-z]+)*?)-(sm|md|lg|xl|xxl)-(.+)$/)) && BREAKPOINTS[v][m[2]]) { base = `${m[1]}-${m[3]}`; at = minRem(m[2], v, mode); }
|
|
60
|
-
const groups = utilityDecls(base, v);
|
|
61
|
-
if (groups) add(at, `.${c} { ${groups.flat().map((x) => x.replace(":", ": ") + ";").join(" ")} }`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const byAt = new Map();
|
|
65
|
-
for (const r of rules) byAt.set(r.at, [...(byAt.get(r.at) || []), r.css]);
|
|
66
|
-
const out = [`/* Bootstrap ${v} compat for OmniaCSS — generated by omniacss-migrate (breakpoints: ${mode}).\n Only the grid and the utilities without an OmniaCSS equivalent that this project uses. */`];
|
|
67
|
-
for (const at of [...byAt.keys()].sort((a, b) => a - b)) {
|
|
68
|
-
const body = byAt.get(at).join("\n");
|
|
69
|
-
out.push(at ? `@media (min-width: ${at}rem) {\n${body.replace(/^/gm, " ")}\n}` : body);
|
|
70
|
-
}
|
|
71
|
-
return out.join("\n\n") + "\n";
|
|
72
|
-
}
|
|
1
|
+
// File: packages/migrate/src/bootstrap/compat.mjs
|
|
2
|
+
// The small stylesheet that keeps the Bootstrap grid (container, row, col-*, offset-*, g-*, row-cols-*) and the few
|
|
3
|
+
// utilities without an OmniaCSS twin working, for exactly the classes a project uses. Colors are Omnia tokens.
|
|
4
|
+
// Map: media query per breakpoint -> grid rule per class -> utility rule per class -> sorted CSS
|
|
5
|
+
// Author: Serge Cools | Framework: OmniaCSS
|
|
6
|
+
|
|
7
|
+
import { BREAKPOINTS, CONTAINERS, OMNIA_BREAKPOINTS, utilityDecls } from "./rules.mjs";
|
|
8
|
+
import { bpVariant } from "./convert.mjs";
|
|
9
|
+
|
|
10
|
+
const SPACE = { 0: "0", 1: "0.25rem", 2: "0.5rem", 3: "1rem", 4: "1.5rem", 5: "3rem" };
|
|
11
|
+
const pct = (n, of) => `${+((n / of) * 100).toFixed(6)}%`;
|
|
12
|
+
|
|
13
|
+
/** min-width in rem for a Bootstrap breakpoint, following the chosen breakpoint mode. */
|
|
14
|
+
function minRem(bp, version, mode) {
|
|
15
|
+
const px = BREAKPOINTS[version][bp];
|
|
16
|
+
if (!px) return 0;
|
|
17
|
+
if (mode === "exact") return px / 16;
|
|
18
|
+
return OMNIA_BREAKPOINTS[bpVariant(bp, version, mode).slice(0, -1)] / 16;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Iterable<string>} gridClasses grid classes found in the project
|
|
23
|
+
* @param {Iterable<string>} utilityClasses utilities without an OmniaCSS twin
|
|
24
|
+
* @param {{ version: 3|4|5, breakpoints?: "omnia"|"exact" }} o
|
|
25
|
+
*/
|
|
26
|
+
export function compatCss(gridClasses, utilityClasses, { version: v, breakpoints: mode = "omnia" }) {
|
|
27
|
+
const rules = []; // { at: rem, css }
|
|
28
|
+
const add = (at, css) => rules.push({ at, css });
|
|
29
|
+
const gutter = v === 3 ? "30px" : "1.5rem";
|
|
30
|
+
const grid = new Set(gridClasses);
|
|
31
|
+
let m;
|
|
32
|
+
|
|
33
|
+
if ([...grid].some((c) => c.startsWith("container"))) {
|
|
34
|
+
const list = [...grid].filter((c) => c.startsWith("container"));
|
|
35
|
+
add(0, `${list.map((c) => "." + c).join(", ")} {\n width: 100%;\n margin-inline: auto;\n padding-inline: calc(${gutter} * 0.5);\n max-width: none;\n}`);
|
|
36
|
+
for (const [bp, max] of Object.entries(CONTAINERS[v])) {
|
|
37
|
+
const sel = list.filter((c) => c === "container" || (/^container-(sm|md|lg|xl|xxl)$/.test(c) && BREAKPOINTS[v][c.slice(10)] <= BREAKPOINTS[v][bp]));
|
|
38
|
+
if (sel.length) add(minRem(bp, v, mode), `${sel.map((c) => "." + c).join(", ")} { max-width: ${max}px; }`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (grid.has("row") || [...grid].some((c) => /^(col|offset|g|row-cols)/.test(c))) {
|
|
42
|
+
add(0, `.row {\n --oc-gutter-x: ${gutter};\n --oc-gutter-y: 0;\n display: flex;\n flex-wrap: wrap;\n margin-top: calc(-1 * var(--oc-gutter-y));\n margin-inline: calc(-0.5 * var(--oc-gutter-x));\n}\n.row > * {\n flex-shrink: 0;\n width: 100%;\n max-width: 100%;\n padding-inline: calc(var(--oc-gutter-x) * 0.5);\n margin-top: var(--oc-gutter-y);\n}`);
|
|
43
|
+
}
|
|
44
|
+
for (const c of grid) {
|
|
45
|
+
// col, col-md, col-auto, col-md-auto
|
|
46
|
+
if ((m = c.match(/^col(?:-(sm|md|lg|xl|xxl))?$/))) add(minRem(m[1], v, mode), `.${c} { flex: 1 0 0%; }`);
|
|
47
|
+
else if ((m = c.match(/^col(?:-(sm|md|lg|xl|xxl))?-auto$/))) add(minRem(m[1], v, mode), `.${c} { flex: 0 0 auto; width: auto; }`);
|
|
48
|
+
else if ((m = c.match(/^col(?:-(xs|sm|md|lg|xl|xxl))?-(\d{1,2})$/))) add(m[1] === "xs" ? 0 : minRem(m[1], v, mode), `.${c} { flex: 0 0 auto; width: ${pct(+m[2], 12)}; }`);
|
|
49
|
+
else if ((m = c.match(/^offset(?:-(sm|md|lg|xl|xxl))?-(\d{1,2})$/)) || (m = c.match(/^col-(xs|sm|md|lg)-offset-(\d{1,2})$/))) add(m[1] === "xs" ? 0 : minRem(m[1], v, mode), `.${c} { margin-left: ${+m[2] ? pct(+m[2], 12) : "0"}; }`);
|
|
50
|
+
else if ((m = c.match(/^col-(xs|sm|md|lg)-(push|pull)-(\d{1,2})$/))) add(m[1] === "xs" ? 0 : minRem(m[1], v, mode), `.${c} { position: relative; ${m[2] === "push" ? "left" : "right"}: ${pct(+m[3], 12)}; }`);
|
|
51
|
+
else if ((m = c.match(/^row-cols(?:-(sm|md|lg|xl|xxl))?-(\d|auto)$/))) add(minRem(m[1], v, mode), `.${c} > * { flex: 0 0 auto; width: ${m[2] === "auto" ? "auto" : pct(1, +m[2])}; }`);
|
|
52
|
+
else if ((m = c.match(/^g([xy]?)(?:-(sm|md|lg|xl|xxl))?-([0-5])$/))) {
|
|
53
|
+
const vars = m[1] === "x" ? ["x"] : m[1] === "y" ? ["y"] : ["x", "y"];
|
|
54
|
+
add(minRem(m[2], v, mode), `.${c} { ${vars.map((a) => `--oc-gutter-${a}: ${SPACE[m[3]]};`).join(" ")} }`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const c of utilityClasses) {
|
|
58
|
+
let base = c, at = 0;
|
|
59
|
+
if ((m = c.match(/^([a-z]+(?:-[a-z]+)*?)-(sm|md|lg|xl|xxl)-(.+)$/)) && BREAKPOINTS[v][m[2]]) { base = `${m[1]}-${m[3]}`; at = minRem(m[2], v, mode); }
|
|
60
|
+
const groups = utilityDecls(base, v);
|
|
61
|
+
if (groups) add(at, `.${c} { ${groups.flat().map((x) => x.replace(":", ": ") + ";").join(" ")} }`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const byAt = new Map();
|
|
65
|
+
for (const r of rules) byAt.set(r.at, [...(byAt.get(r.at) || []), r.css]);
|
|
66
|
+
const out = [`/* Bootstrap ${v} compat for OmniaCSS — generated by omniacss-migrate (breakpoints: ${mode}).\n Only the grid and the utilities without an OmniaCSS equivalent that this project uses. */`];
|
|
67
|
+
for (const at of [...byAt.keys()].sort((a, b) => a - b)) {
|
|
68
|
+
const body = byAt.get(at).join("\n");
|
|
69
|
+
out.push(at ? `@media (min-width: ${at}rem) {\n${body.replace(/^/gm, " ")}\n}` : body);
|
|
70
|
+
}
|
|
71
|
+
return out.join("\n\n") + "\n";
|
|
72
|
+
}
|
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
// File: packages/migrate/src/bootstrap/convert.mjs
|
|
2
|
-
// Convert one Bootstrap class to OmniaCSS / OmniaUI. Utilities are matched on their CSS declarations against the
|
|
3
|
-
// shipped OmniaCSS build (so `mt-3` = 1rem becomes `mt-1`, whatever the names), responsive infixes become variants.
|
|
4
|
-
// Map: declaration index -> breakpoint -> legacy check -> convertClass
|
|
5
|
-
// Author: Serge Cools | Framework: OmniaCSS
|
|
6
|
-
|
|
7
|
-
import { loadOmniaIndex } from "../native.mjs";
|
|
8
|
-
import { BREAKPOINTS, OMNIA_BREAKPOINTS, utilityDecls, RENAMES, COMPONENTS, MANUAL, JS_STATE, isGridClass } from "./rules.mjs";
|
|
9
|
-
|
|
10
|
-
/** Canonical declaration: no spaces around ":" and "/", zero lengths written as 0. */
|
|
11
|
-
const canon = (decl) => decl.trim().replace(/\s*:\s*/, ":").replace(/\s*\/\s*/g, "/").replace(/\s+/g, " ").replace(/(^|[\s:(,-])0(?:rem|px|em|%)(?=$|[\s),;])/g, "$10");
|
|
12
|
-
const sortKey = (decls) => decls.map(canon).sort().join(";");
|
|
13
|
-
const PREFIXED = /^-(webkit|moz|ms|o)-|:-(webkit|moz|ms|o)-/;
|
|
14
|
-
|
|
15
|
-
/** OmniaCSS index keyed on sorted declarations (order inside a rule does not matter). */
|
|
16
|
-
export function loadDeclIndex(cwd) {
|
|
17
|
-
const index = loadOmniaIndex(cwd);
|
|
18
|
-
if (index.sorted) return index;
|
|
19
|
-
index.sorted = new Map();
|
|
20
|
-
for (const [key, names] of index.byDecls) {
|
|
21
|
-
// Also index without vendor-prefixed fallbacks (position: -webkit-sticky; position: sticky).
|
|
22
|
-
const all = key.split(";");
|
|
23
|
-
for (const k of new Set([sortKey(all), sortKey(all.filter((x) => !PREFIXED.test(x.trim())))])) {
|
|
24
|
-
if (k) index.sorted.set(k, [...(index.sorted.get(k) || []), ...names]);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return index;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Pick the OmniaCSS class for one declaration group: the Bootstrap name itself when it means the same, else the shortest. */
|
|
31
|
-
function pick(names, preferred) {
|
|
32
|
-
if (names.includes(preferred)) return preferred;
|
|
33
|
-
return [...names].sort((a, b) => a.length - b.length || a.localeCompare(b))[0];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Bootstrap breakpoint -> OmniaCSS variant prefix ("md:" or "min-[48rem]:"). */
|
|
37
|
-
export function bpVariant(bp, version, mode = "omnia") {
|
|
38
|
-
const px = BREAKPOINTS[version][bp];
|
|
39
|
-
if (!px) return "";
|
|
40
|
-
if (mode === "exact") return `min-[${px / 16}rem]:`;
|
|
41
|
-
const [name] = Object.entries(OMNIA_BREAKPOINTS).sort((a, b) => Math.abs(a[1] - px) - Math.abs(b[1] - px))[0];
|
|
42
|
-
return `${name}:`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Bootstrap 3/4 names that do nothing in a newer major version (the project may style them itself). */
|
|
46
|
-
const LEGACY = [
|
|
47
|
-
[/^col-xs-/, 3], [/^col-(sm|md|lg)-(offset|push|pull)-/, 3], [/^panel/, 3], [/^well$/, 3], [/^label-/, 3], [/^help-block$/, 3],
|
|
48
|
-
[/^img-(responsive|rounded|circle)$/, 3], [/^pull-(left|right)$/, 3], [/^center-block$/, 3], [/^(hidden|visible)-/, 3], [/^glyphicon/, 3],
|
|
49
|
-
[/^btn-(default|xs)$/, 3], [/^input-group-addon$/, 3], [/^control-label$/, 3], [/^table-condensed$/, 3], [/^hidden$/, 3],
|
|
50
|
-
[/^[mp][lr]-/, 4], [/^font-(weight|italic)/, 4], [/^(float|text)-(left|right)$/, 4], [/^badge-/, 4], [/^sr-only/, 4], [/^custom-select$/, 4],
|
|
51
|
-
[/^embed-responsive/, 4], [/^btn-block$/, 4], [/^jumbotron$/, 4], [/^form-group$/, 4],
|
|
52
|
-
];
|
|
53
|
-
const legacyMax = (name) => LEGACY.find(([re]) => re.test(name))?.[1];
|
|
54
|
-
|
|
55
|
-
const HIDDEN_ORDER = { 3: ["xs", "sm", "md", "lg"] };
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @param {string} name a single Bootstrap class
|
|
59
|
-
* @param {{ version: 3|4|5, breakpoints?: "omnia"|"exact", index: object }} ctx
|
|
60
|
-
* @returns {{ kind: "utility"|"component"|"grid"|"manual"|"state"|"inactive"|"unmapped"|"custom", to?: string, hint?: string }}
|
|
61
|
-
*/
|
|
62
|
-
export function convertClass(name, ctx) {
|
|
63
|
-
const { version: v, breakpoints = "omnia", index } = ctx;
|
|
64
|
-
if (JS_STATE.has(name)) return { kind: "state" };
|
|
65
|
-
const max = legacyMax(name);
|
|
66
|
-
if (max && v > max) return { kind: "inactive", hint: `Bootstrap ${max} class: does nothing in Bootstrap ${v}` };
|
|
67
|
-
if (isGridClass(name)) return { kind: "grid" };
|
|
68
|
-
if (Object.hasOwn(COMPONENTS, name)) return { kind: "component", to: COMPONENTS[name] };
|
|
69
|
-
if (/^btn-outline-/.test(name)) return { kind: "component", to: "oc-btn--outline" };
|
|
70
|
-
if (Object.hasOwn(RENAMES, name)) return { kind: "utility", to: RENAMES[name] };
|
|
71
|
-
for (const [re, hint] of MANUAL) if (re.test(name)) return { kind: "manual", hint };
|
|
72
|
-
|
|
73
|
-
// Bootstrap 3 hidden-{bp}: hide inside that breakpoint range only.
|
|
74
|
-
let m;
|
|
75
|
-
if (v === 3 && (m = name.match(/^hidden-(xs|sm|md|lg)$/))) {
|
|
76
|
-
const order = HIDDEN_ORDER[3];
|
|
77
|
-
const i = order.indexOf(m[1]);
|
|
78
|
-
const from = i > 0 ? bpVariant(order[i], 3, breakpoints) : "";
|
|
79
|
-
const next = order[i + 1] ? bpVariant(order[i + 1], 3, breakpoints) : "";
|
|
80
|
-
const to = next.startsWith("min-") ? "max-" + next.slice(4) : next ? "max-" + next : "";
|
|
81
|
-
return { kind: "utility", to: `${from}${to}d-none` };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Responsive infix: d-md-none, mt-lg-3, text-md-center, justify-content-md-between
|
|
85
|
-
let base = name, prefix = "";
|
|
86
|
-
if ((m = name.match(/^([a-z]+(?:-[a-z]+)*?)-(sm|md|lg|xl|xxl)-(.+)$/)) && BREAKPOINTS[v][m[2]]) {
|
|
87
|
-
base = `${m[1]}-${m[3]}`;
|
|
88
|
-
prefix = bpVariant(m[2], v, breakpoints);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const groups = utilityDecls(base, v);
|
|
92
|
-
if (!groups) return { kind: "custom" };
|
|
93
|
-
const out = [];
|
|
94
|
-
for (const g of groups) {
|
|
95
|
-
const names = index.sorted.get(sortKey(g));
|
|
96
|
-
if (!names) return { kind: "unmapped", hint: `no OmniaCSS class with ${g.join("; ")}` };
|
|
97
|
-
out.push(prefix + pick(names, base));
|
|
98
|
-
}
|
|
99
|
-
return { kind: "utility", to: out.join(" ") };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export { sortKey };
|
|
1
|
+
// File: packages/migrate/src/bootstrap/convert.mjs
|
|
2
|
+
// Convert one Bootstrap class to OmniaCSS / OmniaUI. Utilities are matched on their CSS declarations against the
|
|
3
|
+
// shipped OmniaCSS build (so `mt-3` = 1rem becomes `mt-1`, whatever the names), responsive infixes become variants.
|
|
4
|
+
// Map: declaration index -> breakpoint -> legacy check -> convertClass
|
|
5
|
+
// Author: Serge Cools | Framework: OmniaCSS
|
|
6
|
+
|
|
7
|
+
import { loadOmniaIndex } from "../native.mjs";
|
|
8
|
+
import { BREAKPOINTS, OMNIA_BREAKPOINTS, utilityDecls, RENAMES, COMPONENTS, MANUAL, JS_STATE, isGridClass } from "./rules.mjs";
|
|
9
|
+
|
|
10
|
+
/** Canonical declaration: no spaces around ":" and "/", zero lengths written as 0. */
|
|
11
|
+
const canon = (decl) => decl.trim().replace(/\s*:\s*/, ":").replace(/\s*\/\s*/g, "/").replace(/\s+/g, " ").replace(/(^|[\s:(,-])0(?:rem|px|em|%)(?=$|[\s),;])/g, "$10");
|
|
12
|
+
const sortKey = (decls) => decls.map(canon).sort().join(";");
|
|
13
|
+
const PREFIXED = /^-(webkit|moz|ms|o)-|:-(webkit|moz|ms|o)-/;
|
|
14
|
+
|
|
15
|
+
/** OmniaCSS index keyed on sorted declarations (order inside a rule does not matter). */
|
|
16
|
+
export function loadDeclIndex(cwd) {
|
|
17
|
+
const index = loadOmniaIndex(cwd);
|
|
18
|
+
if (index.sorted) return index;
|
|
19
|
+
index.sorted = new Map();
|
|
20
|
+
for (const [key, names] of index.byDecls) {
|
|
21
|
+
// Also index without vendor-prefixed fallbacks (position: -webkit-sticky; position: sticky).
|
|
22
|
+
const all = key.split(";");
|
|
23
|
+
for (const k of new Set([sortKey(all), sortKey(all.filter((x) => !PREFIXED.test(x.trim())))])) {
|
|
24
|
+
if (k) index.sorted.set(k, [...(index.sorted.get(k) || []), ...names]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return index;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Pick the OmniaCSS class for one declaration group: the Bootstrap name itself when it means the same, else the shortest. */
|
|
31
|
+
function pick(names, preferred) {
|
|
32
|
+
if (names.includes(preferred)) return preferred;
|
|
33
|
+
return [...names].sort((a, b) => a.length - b.length || a.localeCompare(b))[0];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Bootstrap breakpoint -> OmniaCSS variant prefix ("md:" or "min-[48rem]:"). */
|
|
37
|
+
export function bpVariant(bp, version, mode = "omnia") {
|
|
38
|
+
const px = BREAKPOINTS[version][bp];
|
|
39
|
+
if (!px) return "";
|
|
40
|
+
if (mode === "exact") return `min-[${px / 16}rem]:`;
|
|
41
|
+
const [name] = Object.entries(OMNIA_BREAKPOINTS).sort((a, b) => Math.abs(a[1] - px) - Math.abs(b[1] - px))[0];
|
|
42
|
+
return `${name}:`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Bootstrap 3/4 names that do nothing in a newer major version (the project may style them itself). */
|
|
46
|
+
const LEGACY = [
|
|
47
|
+
[/^col-xs-/, 3], [/^col-(sm|md|lg)-(offset|push|pull)-/, 3], [/^panel/, 3], [/^well$/, 3], [/^label-/, 3], [/^help-block$/, 3],
|
|
48
|
+
[/^img-(responsive|rounded|circle)$/, 3], [/^pull-(left|right)$/, 3], [/^center-block$/, 3], [/^(hidden|visible)-/, 3], [/^glyphicon/, 3],
|
|
49
|
+
[/^btn-(default|xs)$/, 3], [/^input-group-addon$/, 3], [/^control-label$/, 3], [/^table-condensed$/, 3], [/^hidden$/, 3],
|
|
50
|
+
[/^[mp][lr]-/, 4], [/^font-(weight|italic)/, 4], [/^(float|text)-(left|right)$/, 4], [/^badge-/, 4], [/^sr-only/, 4], [/^custom-select$/, 4],
|
|
51
|
+
[/^embed-responsive/, 4], [/^btn-block$/, 4], [/^jumbotron$/, 4], [/^form-group$/, 4],
|
|
52
|
+
];
|
|
53
|
+
const legacyMax = (name) => LEGACY.find(([re]) => re.test(name))?.[1];
|
|
54
|
+
|
|
55
|
+
const HIDDEN_ORDER = { 3: ["xs", "sm", "md", "lg"] };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} name a single Bootstrap class
|
|
59
|
+
* @param {{ version: 3|4|5, breakpoints?: "omnia"|"exact", index: object }} ctx
|
|
60
|
+
* @returns {{ kind: "utility"|"component"|"grid"|"manual"|"state"|"inactive"|"unmapped"|"custom", to?: string, hint?: string }}
|
|
61
|
+
*/
|
|
62
|
+
export function convertClass(name, ctx) {
|
|
63
|
+
const { version: v, breakpoints = "omnia", index } = ctx;
|
|
64
|
+
if (JS_STATE.has(name)) return { kind: "state" };
|
|
65
|
+
const max = legacyMax(name);
|
|
66
|
+
if (max && v > max) return { kind: "inactive", hint: `Bootstrap ${max} class: does nothing in Bootstrap ${v}` };
|
|
67
|
+
if (isGridClass(name)) return { kind: "grid" };
|
|
68
|
+
if (Object.hasOwn(COMPONENTS, name)) return { kind: "component", to: COMPONENTS[name] };
|
|
69
|
+
if (/^btn-outline-/.test(name)) return { kind: "component", to: "oc-btn--outline" };
|
|
70
|
+
if (Object.hasOwn(RENAMES, name)) return { kind: "utility", to: RENAMES[name] };
|
|
71
|
+
for (const [re, hint] of MANUAL) if (re.test(name)) return { kind: "manual", hint };
|
|
72
|
+
|
|
73
|
+
// Bootstrap 3 hidden-{bp}: hide inside that breakpoint range only.
|
|
74
|
+
let m;
|
|
75
|
+
if (v === 3 && (m = name.match(/^hidden-(xs|sm|md|lg)$/))) {
|
|
76
|
+
const order = HIDDEN_ORDER[3];
|
|
77
|
+
const i = order.indexOf(m[1]);
|
|
78
|
+
const from = i > 0 ? bpVariant(order[i], 3, breakpoints) : "";
|
|
79
|
+
const next = order[i + 1] ? bpVariant(order[i + 1], 3, breakpoints) : "";
|
|
80
|
+
const to = next.startsWith("min-") ? "max-" + next.slice(4) : next ? "max-" + next : "";
|
|
81
|
+
return { kind: "utility", to: `${from}${to}d-none` };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Responsive infix: d-md-none, mt-lg-3, text-md-center, justify-content-md-between
|
|
85
|
+
let base = name, prefix = "";
|
|
86
|
+
if ((m = name.match(/^([a-z]+(?:-[a-z]+)*?)-(sm|md|lg|xl|xxl)-(.+)$/)) && BREAKPOINTS[v][m[2]]) {
|
|
87
|
+
base = `${m[1]}-${m[3]}`;
|
|
88
|
+
prefix = bpVariant(m[2], v, breakpoints);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const groups = utilityDecls(base, v);
|
|
92
|
+
if (!groups) return { kind: "custom" };
|
|
93
|
+
const out = [];
|
|
94
|
+
for (const g of groups) {
|
|
95
|
+
const names = index.sorted.get(sortKey(g));
|
|
96
|
+
if (!names) return { kind: "unmapped", hint: `no OmniaCSS class with ${g.join("; ")}` };
|
|
97
|
+
out.push(prefix + pick(names, base));
|
|
98
|
+
}
|
|
99
|
+
return { kind: "utility", to: out.join(" ") };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { sortKey };
|