deep-proxy #1

Merged
SethTrowbridge merged 16 commits from deep-proxy into master 2025-02-22 17:28:43 -05:00
15 changed files with 158 additions and 313 deletions
Showing only changes of commit 6479974994 - Show all commits

12
.vscode/launch.json vendored
View File

@ -1,12 +0,0 @@
{
"configurations": [
{
"name": "deno launch",
"request": "launch",
"type": "node",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": ["styler.tsx"],
}
]
}

5
app.js Normal file
View File

@ -0,0 +1,5 @@
import Styles from "./styles.dev.js";
const sheet = Styles({
Button:{padding:"20px", background:"orange"},
Test:{fontSize:"3rem"}
});

View File

@ -1,13 +1,9 @@
{ {
"imports": { "compilerOptions": {
"react": "https://esm.sh/preact@10.18.1/compat", "checkJs": true,
"react/": "https://esm.sh/preact@10.18.1/compat/", "lib": [
"preact-render-to-string": "https://esm.sh/preact-render-to-string@6.5.7?deps=preact@10.18.1" "deno.window",
}, "DOM"
"tasks": {"go": "deno run -A styler.tsx"}, ]
"compilerOptions": { }
"jsx": "react-jsx",
"jsxImportSource": "react",
"lib": ["deno.window","dom","dom.asynciterable"]
}
} }

View File

@ -1,21 +0,0 @@
{
"version": "3",
"redirects": {
"https://esm.sh/v128/@types/react@~18.3/index.d.ts": "https://esm.sh/v128/@types/react@18.2.38/index.d.ts",
"https://esm.sh/v135/@types/react-dom@~18.3/server~.d.ts": "https://esm.sh/v135/@types/react-dom@18.3.0/server~.d.ts"
},
"remote": {
"https://esm.sh/preact-render-to-string@6.5.7?deps=preact@10.18.1": "1d9e844b000fd2bf592142f98e6a6c9c3c10953ac90bef90a67ded5e28667279",
"https://esm.sh/preact@10.18.1/compat/jsx-runtime": "d9addc942dc9b5eb507c622e7ad93d5594093f5d31e461d4125f7a1ab475f932",
"https://esm.sh/react-dom@18.3.1/server": "90e374c5323303d5315c780b19e5d6297ec5a569f9cee1df28ee1030dd23af9c",
"https://esm.sh/react@18.3.1": "52480d4c00855e4f76843caa4f2818eb001671523740cbf064cbd108721c29c8",
"https://esm.sh/stable/preact@10.18.1/denonext/compat.js": "cb1028add6b66ecea904a750ad9887f21c5a42c17303c4c317710866c290efa7",
"https://esm.sh/stable/preact@10.18.1/denonext/compat/jsx-runtime.js": "16e3c12a4942f4f3027f6f3c5a0686ef00a80d70a4fb08097b702f4ddd5d488f",
"https://esm.sh/stable/preact@10.18.1/denonext/hooks.js": "cb7e8c9973e6a224348eaa51fba21e13f239839e403f751b29894a258a6d16d0",
"https://esm.sh/stable/preact@10.18.1/denonext/jsx-runtime.js": "be3f1ff4c3c03b08ed19d69428e35bf3d90360a8e081a2e60075ddfd38fd86df",
"https://esm.sh/stable/preact@10.18.1/denonext/preact.mjs": "b2ad171554b90f2be0f30b1318f63d0df90420b2bdb727fddd97193daa177f84",
"https://esm.sh/stable/react@18.3.1/denonext/react.mjs": "fc048ffc55366baf7669519127d186761db72046e2bebf35fe8d0de3964defa3",
"https://esm.sh/v135/preact-render-to-string@6.5.7/X-ZC9wcmVhY3RAMTAuMTguMQ/denonext/preact-render-to-string.mjs": "80d81cb9eeee0b973a136113bd8ff22288b5d40d3dae15f91838dbc72356e095",
"https://esm.sh/v135/react-dom@18.3.1/denonext/server.js": "b4db56f16b45002b5e9b127a9938ab45b12f39825161dffe875920510c524878"
}
}

View File

@ -1,11 +0,0 @@
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width, initial-s" />
<style>.Face-sans_lg{ @media(min-width:1024px){ font-family:sans-serif; }}
.Pad-large_md{ @media(min-width:767px){ padding:3rem; }}
.Pad-small_lg{ @media(min-width:1024px){ padding:1rem; }}</style>
</head>
<body>
<div class="Face-sans_lg Pad-large_md Pad-small_lg">hello!</div>
</body>
</html>

View File

@ -1,3 +0,0 @@
import {config} from "./gale.tsx";
export default config({other:"lol"});

72
gale.js Normal file
View File

@ -0,0 +1,72 @@
// @ts-check
const KeyQuery = "@";
const KeyPseudo = ":";
const KeyChild = ".";
const KeyGroup = "^";
/** @typedef { Partial<CSSStyleDeclaration> & {[key: `${KeyQuery|KeyPseudo|KeyChild|KeyGroup}${string}`]: UserStyles } } UserStyles */
/** @typedef {Record<string, UserStyles>} UserSheet */
/**
* @template Obj
* @typedef { { [Key in keyof Obj]: Obj[Key] extends object ? Key | CollectKeys<Obj[Key]> : Key }[keyof Obj] } CollectKeys
*/
/**
* @template Keys
* @typedef { Keys extends `${KeyChild|KeyGroup}${infer Rest}` ? Keys : never } FilterKeys
*/
/**
* @template A
* @template B
* @typedef {A extends string ? B extends string ? `${A}${B}` : never : never } CrossMultiply
*/
/**
* @template Rec
* @typedef { keyof Rec | { [K in keyof Rec]: K extends string ? CrossMultiply<K, FilterKeys<CollectKeys<Rec[K]>>> : never }[keyof Rec] } CrossMultiplyRecord
*/
/** @type {(selector:string, obj:UserStyles)=>string} */
const Tier=(selector, obj)=>
{
const styles = Object.keys(obj).map((key)=>
{
const value = obj[key];
switch(key[0])
{
case KeyQuery :
return Tier(`@media(max-width:${key.substring(KeyQuery.length)})`, value);
case KeyPseudo :
return Tier(`&${key}`, value);
case KeyChild :
return Tier(`${key}`, value);
case KeyGroup :
return Tier(`&:hover .${key.substring(KeyGroup.length)}`, value);
}
return `${ key.replace(/([a-z])([A-Z])/g, '$1-$2') }: ${value};`
});
return `${selector}{${styles.join("\n")}}`;
}
let i = 0;
/** @type {<T extends UserSheet>(sheet:UserSheet&T)=> ((...args:CrossMultiplyRecord<T>[])=>string)&{css:string}} */
export default (sheet)=>
{
const id = i ? "_"+i : "";
i++;
const css = Object.keys(sheet).map(key=>Tier("."+key, sheet[key])).join(`\n`);
globalThis.document?.head.insertAdjacentHTML("beforeend", `<style data-sheet="${i}">${css}</style>`);
const classes =(...args)=>{
/** @type {(needle:string, str:string)=>string} */
const extractLast =(needle, str)=>{
const ind = str.lastIndexOf(needle)+needle.length;
return ind ? str.substring(ind) : str;
}
return args.map((arg)=>extractLast(KeyGroup, extractLast(KeyChild, arg))).join(id+" ")+id;
}
classes.css = css;
return classes;
}

104
gale.tsx
View File

@ -1,104 +0,0 @@
const typeface = {
sans: "sans-serif",
serif: "Times New Roman"
};
const sizes = {
small: "1rem",
large: "3rem"
};
const colors = {
red: "#ff2200",
blue: "#0022ff"
};
const responsive = {
md: "min-width:767px",
lg: "min-width:1024px",
};
let SheetDOM = {insertRule(_:string){}} as CSSStyleSheet;
if(globalThis?.document)
{
const sheetElement = document.createElement("style");
document.head.setAttribute("data-gale", "true");
document.head.appendChild(sheetElement);
SheetDOM = sheetElement.sheet as CSSStyleSheet;
}
const SheetMap = new Map<string, string>();
function Mapper<T extends Record<string, string>>(className:string, propertyName:string, lut:T)
{
const build =(propertyValue:string, customPropertyName?:string):string=>
{
const selector = `${className}-${(customPropertyName||propertyValue as string).replace(/[^a-zA-Z0-9]/g, "")}${activeQuery.val?"_"+activeQuery.key:""}`;
const check = SheetMap.get(selector);
if(!check)
{
const body = `{ ${propertyName}:${propertyValue}; }`;
const rules = activeQuery.val ? `{ @media(${activeQuery.val})${body}}` : body
SheetMap.set(selector, rules);
SheetDOM.insertRule(selector + " " + rules);
}
return selector;
}
type UseCustom = (custom:string)=>string;
return new Proxy(((override:string)=>build(override)) as UseCustom,
{
get(_, prop){
return build(lut[prop as string], prop as string);
}
}
) as T & UseCustom;
}
function MapperQuery<T extends Record<string, string>>(lut:T)
{
type UseCustom = (custom:string)=>QueryInvoker;
return new Proxy(((override:string)=>Query(override, override)) as UseCustom,
{
get(_, prop:string){
return Query(lut[prop], prop)
}
}
) as Record<keyof T, QueryInvoker> & UseCustom;
}
type ActiveQuery = {key:string|false, val:string|false};
type QueryInvoker = (...args:string[])=>string
let activeQuery:ActiveQuery;
const activeQueryStack:ActiveQuery[] = [];
function Query(amount:string, key?:string):QueryInvoker
{
activeQuery = {key:key||amount, val:amount};
activeQueryStack.push(activeQuery);
return (...args)=> {
activeQueryStack.pop()
activeQuery = activeQueryStack.at(-1) || {key:false, val:false};
return args.join(" ");
}
}
export function config<T>(obj:T)
{
const CSS = {
Sheet()
{
const rules = [];
for(const [key, value] of SheetMap.entries())
{
rules.push("."+key+value);
}
return rules.join("\n");
},
Face: Mapper("Face", "font-family", {...typeface, ...obj}),
Pad: Mapper("Pad", "padding", sizes),
Q: MapperQuery(responsive)
};
return CSS;
}
export default config({});

View File

@ -1,16 +1,10 @@
<!DOCTYPE html> <html lang="en">
<html> <head>
<head></head> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body> <body>
<div id="app"> </div> <script type="module" src="app.js"></script>
<script type="module">
import Gale from "./gale-custom.tsx";
document.querySelector("#app").innerHTML = `
<div class="${Gale.Q.lg( Gale.Face.sans, Gale.Q.md(Gale.Pad.large), Gale.Pad.small )}">
hey
</div>
`
</script>
</body> </body>
</html> </html>

View File

@ -1,40 +0,0 @@
import Gale from "./gale-custom.tsx";
import Render from "preact-render-to-string";
const jsx = <div className={Gale.Q.lg( Gale.Face.sans, Gale.Q.md(Gale.Pad.large), Gale.Pad.small)}>
hello!
</div>
Deno.writeTextFile(
"dump.html",
`<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width, initial-size=1" />
<style>${Gale.Sheet()}</style>
</head>
<body>
${Render(jsx)}
</body>
</html>`
)
// idea for table:
// Gale.Table(
// [, "md" ], {
// Face:["serif", "sans" ],
// Pad:["small", "large"]
// })
// idea for object
//Gale({
// lg:{
// Face:"sans",
// Pad:"small"
// },
// md:{
// Pad:"large"
// }
//})
// idea for function
//Gale.Gen(g=>[g.md(), g.Face.Sans])

67
styles.dev.js Normal file
View File

@ -0,0 +1,67 @@
// @ts-check
const KeyQuery = "@";
const KeyPseudo = ":";
const KeyChild = ".";
const KeyGroup = "^";
/**
* @typedef { Partial<CSSStyleDeclaration> & {
* [key: `${KeyQuery|KeyPseudo|KeyChild|KeyGroup}${string}`]: UserStyles;
* } & {
* [key in keyof CSSStyleDeclaration]?: CSSStyleDeclaration[key] | CSSStyleDeclaration[key][]
* }} UserStyles
*/
/** @typedef {Record<string, UserStyles> & { global?: UserStyles }} UserSheet */
/**
* @template {UserSheet} T
* @typedef {keyof T | (string & {})} ValidSelectors<T>
*/
/**
* @template {UserSheet} T
* @param {T} sheet
* @returns {((...selectors: (keyof T)[]) => string) & { css: string }}
*/
const createCss = (sheet) => {
let i = 0;
const id = i ? "_" + i : "";
i++;
const Tier = (selector, obj) => {
const styles = Object.keys(obj).map((key) => {
const value = obj[key];
switch (key[0]) {
case KeyQuery:
return Tier(`@media(max-width:${key.substring(KeyQuery.length)})`, value);
case KeyPseudo:
return Tier(`&${key}`, value);
case KeyChild:
return Tier(`${key}`, value);
case KeyGroup:
return Tier(`&:hover .${key.substring(KeyGroup.length)}`, value);
}
return `${key.replace(/([a-z])([A-Z])/g, '$1-$2')}: ${value};`;
});
return `${selector}{${styles.join("\n")}}`;
};
const css = Object.keys(sheet).map(key => Tier("." + key, sheet[key])).join("\n");
globalThis.document?.head.insertAdjacentHTML("beforeend", `<style data-sheet="${i}">${css}</style>`);
/** @type {(...args: (keyof T)[]) => string} */
const classes = (...args) => {
/** @type {(needle: string, str: string) => string} */
const extractLast = (needle, str) => {
const ind = str.lastIndexOf(needle) + needle.length;
return ind ? str.substring(ind) : str;
};
return args.map(arg => extractLast(KeyGroup, extractLast(KeyChild, arg))).join(id + " ") + id;
};
classes.css = css;
return classes;
};
export default createCss;

View File

@ -1,19 +0,0 @@
//////////////////////
type OriginalType = {
key1: { sub1: string; sub2: string };
key2: { subA: string; subB: string };
};
type ValidKeys = keyof OriginalType;
type ValidSubKeys<K extends ValidKeys> = keyof OriginalType[K];
type EnforcedRecord = {
[K in ValidKeys]: ValidSubKeys<K>[];
};
// Example usage:
const example: EnforcedRecord = {
key1: ['sub1', 'sub2'],
key2: ['subA', 'subB', "sub1"], // last is invalid
};

View File

@ -1,22 +0,0 @@
type OriginalType = {
key1: { sub1: string; sub2: string };
key2: { subA: string; subB: string };
};
type ValidKeys = keyof OriginalType;
type ValidSubKeys<K extends ValidKeys> = keyof OriginalType[K];
type FixedLengthArray<T, L extends number> = [T, ...T[]] & { length: L };
type EnforcedRecord<L extends number> = {
[K in ValidKeys]: FixedLengthArray<ValidSubKeys<K>, L>;
};
// Example usage:
const example: EnforcedRecord<2> = {
key1: ['sub1', 'sub2'], // Valid
key2: ['subA', 'subB'], // Valid
// key1: ['sub1'], // Invalid, TypeScript will give an error
// key2: ['subA', 'subB', 'subC'], // Invalid, TypeScript will give an error
};

View File

@ -1,27 +0,0 @@
type OriginalType = {
key1: { sub1: string; sub2: string };
key2: { subA: string; subB: string };
};
type ValidKeys = keyof OriginalType;
type ValidSubKeys<K extends ValidKeys> = keyof OriginalType[K];
type FixedLengthArray<T, L extends number> = [T, ...T[]] & { length: L };
type EnforcedRecord<L extends number> = {
[K in ValidKeys]: FixedLengthArray<ValidSubKeys<K>, L>;
};
const createTable = <T extends { length: number }>(
cols: T,
rows: EnforcedRecord<T['length']>
) => {
return { cols, rows };
};
// Example usage:
const cols = ["a", "b", "c"] as const;
const table = createTable(cols, {
key1: ['sub1', 'sub2', 'sub1', 'sub1'], // invalid because too long
key2: ['subA', 'subB', 'sub1'], // invalid because bad key
});

View File

@ -1,30 +0,0 @@
type OriginalType = {
key1: { sub1: string; sub2: string };
key2: { subA: string; subB: string };
};
type ValidKeys = keyof OriginalType;
type ValidSubKeys<K extends ValidKeys> = keyof OriginalType[K];
type FixedLengthArray<T, L extends number> = T[] & { length: L };
type EnforcedRecord<L extends number> = {
[K in ValidKeys]: FixedLengthArray<ValidSubKeys<K>, L>;
};
type FixedArr<Arr, Length extends number> = Arr & { length: Length };
function Func<L extends number>(arr1:FixedArr<string[], L>)
{
return arr1.length;
}
const test = Func<3>(["a", "b", "c"])
// Example usage:
const example: EnforcedRecord<2> = {
key1: ['sub1', 'sub2', 'sub1'], // Valid
key2: ['subA', 'subB'], // Valid
// key1: ['sub1'], // Invalid, TypeScript will give an error
// key2: ['subA', 'subB', 'subC'], // Invalid, TypeScript will give an error
};