diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 130e1f5..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "configurations": [ - { - "name": "deno launch", - "request": "launch", - "type": "node", - "cwd": "${workspaceFolder}", - "runtimeExecutable": "deno", - "runtimeArgs": ["styler.tsx"], - } - ] -} \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..fa7542e --- /dev/null +++ b/app.js @@ -0,0 +1,5 @@ +import Styles from "./styles.dev.js"; +const sheet = Styles({ + Button:{padding:"20px", background:"orange"}, + Test:{fontSize:"3rem"} +}); \ No newline at end of file diff --git a/deno.json b/deno.json index 93ab167..b7a8787 100644 --- a/deno.json +++ b/deno.json @@ -1,13 +1,9 @@ { - "imports": { - "react": "https://esm.sh/preact@10.18.1/compat", - "react/": "https://esm.sh/preact@10.18.1/compat/", - "preact-render-to-string": "https://esm.sh/preact-render-to-string@6.5.7?deps=preact@10.18.1" - }, - "tasks": {"go": "deno run -A styler.tsx"}, - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "react", - "lib": ["deno.window","dom","dom.asynciterable"] - } + "compilerOptions": { + "checkJs": true, + "lib": [ + "deno.window", + "DOM" + ] + } } \ No newline at end of file diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 989d9df..0000000 --- a/deno.lock +++ /dev/null @@ -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" - } -} diff --git a/dump.html b/dump.html deleted file mode 100644 index 6b9c8fa..0000000 --- a/dump.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - -
hello!
- - \ No newline at end of file diff --git a/gale-custom.tsx b/gale-custom.tsx deleted file mode 100644 index 828f76e..0000000 --- a/gale-custom.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import {config} from "./gale.tsx"; - -export default config({other:"lol"}); \ No newline at end of file diff --git a/gale.js b/gale.js new file mode 100644 index 0000000..4646e3a --- /dev/null +++ b/gale.js @@ -0,0 +1,72 @@ +// @ts-check +const KeyQuery = "@"; +const KeyPseudo = ":"; +const KeyChild = "."; +const KeyGroup = "^"; + +/** @typedef { Partial & {[key: `${KeyQuery|KeyPseudo|KeyChild|KeyGroup}${string}`]: UserStyles } } UserStyles */ +/** @typedef {Record} UserSheet */ + + +/** + * @template Obj + * @typedef { { [Key in keyof Obj]: Obj[Key] extends object ? Key | CollectKeys : 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>> : 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 {(sheet:UserSheet&T)=> ((...args:CrossMultiplyRecord[])=>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", ``); + 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; +} \ No newline at end of file diff --git a/gale.tsx b/gale.tsx deleted file mode 100644 index 4995edf..0000000 --- a/gale.tsx +++ /dev/null @@ -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(); - - -function Mapper>(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>(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 & 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(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({}); \ No newline at end of file diff --git a/index.html b/index.html index 38e07e8..6c1c4d7 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,10 @@ - - - + + + + + Document + -
- + \ No newline at end of file diff --git a/styler.tsx b/styler.tsx deleted file mode 100644 index cdc3595..0000000 --- a/styler.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import Gale from "./gale-custom.tsx"; -import Render from "preact-render-to-string"; - -const jsx =
-hello! -
- -Deno.writeTextFile( -"dump.html", -` - - - - - - ${Render(jsx)} - -` -) - -// 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]) \ No newline at end of file diff --git a/styles.dev.js b/styles.dev.js new file mode 100644 index 0000000..e179198 --- /dev/null +++ b/styles.dev.js @@ -0,0 +1,67 @@ +// @ts-check +const KeyQuery = "@"; +const KeyPseudo = ":"; +const KeyChild = "."; +const KeyGroup = "^"; + +/** + * @typedef { Partial & { + * [key: `${KeyQuery|KeyPseudo|KeyChild|KeyGroup}${string}`]: UserStyles; + * } & { + * [key in keyof CSSStyleDeclaration]?: CSSStyleDeclaration[key] | CSSStyleDeclaration[key][] + * }} UserStyles + */ + +/** @typedef {Record & { global?: UserStyles }} UserSheet */ + +/** + * @template {UserSheet} T + * @typedef {keyof T | (string & {})} ValidSelectors + */ + +/** + * @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", ``); + + /** @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; diff --git a/type-experiments-01.ts b/type-experiments-01.ts deleted file mode 100644 index 27bdf82..0000000 --- a/type-experiments-01.ts +++ /dev/null @@ -1,19 +0,0 @@ -////////////////////// -type OriginalType = { - key1: { sub1: string; sub2: string }; - key2: { subA: string; subB: string }; - }; - - type ValidKeys = keyof OriginalType; - type ValidSubKeys = keyof OriginalType[K]; - - type EnforcedRecord = { - [K in ValidKeys]: ValidSubKeys[]; - }; - - // Example usage: - const example: EnforcedRecord = { - key1: ['sub1', 'sub2'], - key2: ['subA', 'subB', "sub1"], // last is invalid - }; - diff --git a/type-experiments-02.ts b/type-experiments-02.ts deleted file mode 100644 index 02b0b29..0000000 --- a/type-experiments-02.ts +++ /dev/null @@ -1,22 +0,0 @@ -type OriginalType = { - key1: { sub1: string; sub2: string }; - key2: { subA: string; subB: string }; -}; - -type ValidKeys = keyof OriginalType; -type ValidSubKeys = keyof OriginalType[K]; - -type FixedLengthArray = [T, ...T[]] & { length: L }; - -type EnforcedRecord = { - [K in ValidKeys]: FixedLengthArray, 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 -}; \ No newline at end of file diff --git a/type-experiments-03.ts b/type-experiments-03.ts deleted file mode 100644 index 4f2e037..0000000 --- a/type-experiments-03.ts +++ /dev/null @@ -1,27 +0,0 @@ -type OriginalType = { - key1: { sub1: string; sub2: string }; - key2: { subA: string; subB: string }; -}; - -type ValidKeys = keyof OriginalType; -type ValidSubKeys = keyof OriginalType[K]; - -type FixedLengthArray = [T, ...T[]] & { length: L }; - -type EnforcedRecord = { - [K in ValidKeys]: FixedLengthArray, L>; -}; - -const createTable = ( - cols: T, - rows: EnforcedRecord -) => { - 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 -}); diff --git a/type-experiments-04.ts b/type-experiments-04.ts deleted file mode 100644 index 3fb5f16..0000000 --- a/type-experiments-04.ts +++ /dev/null @@ -1,30 +0,0 @@ -type OriginalType = { - key1: { sub1: string; sub2: string }; - key2: { subA: string; subB: string }; -}; - -type ValidKeys = keyof OriginalType; -type ValidSubKeys = keyof OriginalType[K]; - -type FixedLengthArray = T[] & { length: L }; - -type EnforcedRecord = { - [K in ValidKeys]: FixedLengthArray, L>; -}; - -type FixedArr = Arr & { length: Length }; -function Func(arr1:FixedArr) -{ - 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 -}; \ No newline at end of file