release: WRNexusJS 0.4.0

This commit is contained in:
2026-07-27 12:42:18 +05:30
parent 8b728a3e5d
commit 30e5721e84
250 changed files with 10065 additions and 3923 deletions
+80
View File
@@ -0,0 +1,80 @@
export interface CssTokenAudit {
declared: string[];
used: string[];
missing: string[];
unused: string[];
}
/** Audit framework design-token declarations and var() references. */
export function auditWireTokens(css: string): CssTokenAudit {
const declared = new Set<string>();
const used = new Set<string>();
for (const match of css.matchAll(/(--wire-[A-Za-z0-9_-]+)\s*:/g)) declared.add(match[1]!);
for (const match of css.matchAll(/var\(\s*(--wire-[A-Za-z0-9_-]+)/g)) used.add(match[1]!);
return {
declared: [...declared].sort(),
used: [...used].sort(),
missing: [...used].filter((token) => !declared.has(token)).sort(),
unused: [...declared].filter((token) => !used.has(token)).sort(),
};
}
export interface StyleSource {
path: string;
reason?: string;
}
/** Normalize/dedupe Tailwind scan sources without allowing line injection. */
export function normalizeStyleSources(values: readonly (string | StyleSource)[]): StyleSource[] {
const result = new Map<string, StyleSource>();
for (const value of values) {
const source = typeof value === "string" ? { path: value } : value;
const path = source.path.trim();
if (!path || /[\r\n]/.test(path)) continue;
result.set(path, { path, reason: source.reason });
}
return [...result.values()];
}
export function tailwindSourceDirectives(values: readonly (string | StyleSource)[]): string {
return normalizeStyleSources(values)
.map(({ path }) => `@source ${JSON.stringify(path)};`)
.join("\n");
}
export interface ContrastResult {
ratio: number;
level: "fail" | "aa-large" | "aa" | "aaa";
}
function hexRgb(value: string): [number, number, number] | null {
const hex = value.trim().replace(/^#/, "");
const normalized = hex.length === 3 ? [...hex].map((part) => part + part).join("") : hex;
if (!/^[0-9a-f]{6}$/i.test(normalized)) return null;
return [0, 2, 4].map((offset) => Number.parseInt(normalized.slice(offset, offset + 2), 16)) as [
number,
number,
number,
];
}
function luminance(value: string): number | null {
const rgb = hexRgb(value);
if (!rgb) return null;
const channels = rgb.map((channel) => {
const scaled = channel / 255;
return scaled <= 0.03928 ? scaled / 12.92 : ((scaled + 0.055) / 1.055) ** 2.4;
});
return 0.2126 * channels[0]! + 0.7152 * channels[1]! + 0.0722 * channels[2]!;
}
export function contrast(foreground: string, background: string): ContrastResult | null {
const left = luminance(foreground);
const right = luminance(background);
if (left === null || right === null) return null;
const ratio = (Math.max(left, right) + 0.05) / (Math.min(left, right) + 0.05);
return {
ratio: Math.round(ratio * 100) / 100,
level: ratio >= 7 ? "aaa" : ratio >= 4.5 ? "aa" : ratio >= 3 ? "aa-large" : "fail",
};
}