tss/main.ts
2025-10-26 22:43:42 -04:00

161 lines
5.2 KiB
TypeScript

// tss.ts
type PseudoKeys = ["hover"|"before"|"after"] | ["hover", "after"|"before"];
type ValueSignature = (...args:any[])=>string
type EnumDefinition<Signature> = [property:string, options:Record<string, string>, values?:Signature];
type EnumBlock<Signature> = Record<string, EnumDefinition<Signature>>;
type RecursiveObject<Sig extends ValueSignature, Obj extends EnumBlock<Sig>> =
{
[F in keyof Obj]: Obj[F][2] extends Sig ? ((...args:Parameters<Obj[F][2]>)=> RecursiveObject<Sig, Obj>&((...args:PseudoKeys)=>RecursiveObject<Sig, Obj>)&Array<RecursiveObject<Sig, Obj>>)&{
[E in keyof Obj[F][1]]:RecursiveObject<Sig, Obj>&((...args:PseudoKeys)=>RecursiveObject<Sig, Obj>)&Array<RecursiveObject<Sig, Obj>>
} :
{
[E in keyof Obj[F][1]]:RecursiveObject<Sig, Obj>&((...args:PseudoKeys)=>RecursiveObject<Sig, Obj>)&Array<RecursiveObject<Sig, Obj>>
}
}
function Block<Signature extends ValueSignature, Fields extends EnumBlock<Signature>>(options:Fields):()=>RecursiveObject<Signature, Fields>
{
return ()=>{
let pseudo = false;
let query = false;
const list = [];
let fieldLookup = {};
const proxyOuter = new Proxy(
function(...args)
{
// OUTER context called as function:
if(args.length) // pseudo elements are being declared, capture and return the OUTER context
{
list.push(`${(pseudo||query) ? "}" : ""} &:${args.join("::")}{`);
pseudo = true;
query = false;
return proxyOuter;
}
else // css dump requested:
{
pseudo && list.push("}");
query && list.push("}");
pseudo = false;
query = false;
return list;
}
},
{
get(_target, propName)
{
// OUTER context property access:
if(parseInt(propName)) // if a number was used as a property, treat that as a mobile-first media query in px units, and return the OUTER context
{
list.push(`${(query) ? "}" : ""} @media(min-width:${propName}px){`);
query = true;
return proxyOuter;
}
// a css property alias was accessed, look up the mapped css property name from the declaration array, push that, and return the OUTER context
fieldLookup = options[propName];
if(fieldLookup)
{
list.push(fieldLookup[0]);
}
return proxyInner;
}
}
);
const proxyInner = new Proxy(
function(...args)
{
// INNER context called as function
// capture the evalued dynamic values and return the OUTER context
list.push(`:${fieldLookup[2](...args)};`);
return proxyOuter;
},
{
get(_target, valName)
{
// INNER context property access
try // capture the mapped css value and return the OUTER context
{
list.push(`:${fieldLookup[1][valName]};`);
return proxyOuter;
}
catch(_e)
{
console.warn("someone is trying to stringify a style proxy");
return ()=>"[StyleProxy]";
}
}
}
);
return proxyOuter;
}
}
type Measure = "px" | "em" | "rem" | "vh" | "vw" | "%";
type UnitString = `${number}${Measure}`;
const funcUnitSingle = (amount:UnitString)=>amount
const funcUnitTRBL=<T = UnitString>(...args:[t:T, r?:T, b?:T, l?:T])=>args.join(" ")
const UnitAuto = {Auto:"auto"};
const styles = Block(
{
Pos:["position", { Abs:"absolute", Rel:"relative", Fix:"fixed", Pin:"sticky", No:"static" }],
Dis:["display", { Flex:"flex", Grid:"grid", None:"none", Block:"block", InlineBlock:"inline-block" }],
Box:["box-sizing", { Border:"border-box", Content:"content-box" }],
T:["top", UnitAuto, funcUnitSingle],
R:["right", UnitAuto, funcUnitSingle],
B:["bottom", UnitAuto, funcUnitSingle],
L:["left", UnitAuto, funcUnitSingle],
Pad: ["padding", {}, funcUnitTRBL ],
PadT:["padding-top", {}, funcUnitSingle],
PadR:["padding-right", {}, funcUnitSingle],
PadB:["padding-bottom", {}, funcUnitSingle],
PadL:["padding-left", {}, funcUnitSingle],
Mar: ["margin", UnitAuto, funcUnitTRBL<UnitString|"auto"> ],
MarT:["margin-top", UnitAuto, funcUnitSingle],
MarR:["margin-right", UnitAuto, funcUnitSingle],
MarB:["margin-bottom", UnitAuto, funcUnitSingle],
MarL:["margin-left", UnitAuto, funcUnitSingle]
}
);
let masterSheet = [];
export function Sheet<UserClasses extends Record<string, (this:ReturnType<typeof styles>, css:ReturnType<typeof styles>)=>void >>(userClasses:UserClasses):{[Class in keyof UserClasses]:string}
{
const hash = Math.floor(Math.random()*10000);
return new Proxy(function(){}, {get(_target, className){
const lookup = userClasses[className];
if(typeof lookup == "function")
{
const skewer = styles();
lookup.call(skewer, skewer);
const name = `.${className}_${hash}`;
masterSheet.push( `${name}{ ${skewer().join("")} }` );
userClasses[className] = name;
return name;
}
else
{
return lookup;
}
}});
}
export function Divulge()
{
return masterSheet.join("\n");
}