so it begins

This commit is contained in:
Seth Trowbridge 2025-10-07 17:00:34 -04:00
parent 784ef673c8
commit 2ac046a297
24 changed files with 583 additions and 811 deletions

View File

@ -1,11 +0,0 @@
Run in an empty directory to setup basic files:
```
deno run -Ar https://gitea.hptrow.me/SethTrowbridge/gale/raw/branch/master/scripts/scaffold.ts
```
- `deno task work` starts the dev server
- `deno task scan` scans the deno.json for type declaration files (be sure to then restart the Deno language server)
- `deno task html` creates an index.html for use when static file hosting (the dev serve will create its own index.html if none exists in the file system)
`entry` in the import map points to the starting file

45
app.js
View File

@ -1,45 +0,0 @@
const {DOM, Tag, Div} = Gale({
Button: {
padding: "20px",
background: "orange",
".Inner": {
fontSize: "10rem"
}
},
Outline: {
border: "2px solid orange"
},
Window:{
height: "100vh",
border: "2px solid black",
display: "flex",
flexDirection: "row",
alignItems: "end",
justifyContent: "center",
gap: "10px"
},
Ability:{
width: "50px",
height: "50px",
background: "red",
transition: "all 0.4s",
":hover":{
transform:"scale(1.1)"
}
},
Orange:{
background:"orange"
}
});
const UI =()=>
{
return Div.Window(
Div.Ability({class:Tag("Ability", "Orange")}),
Div.Ability(),
Div.Ability(),
Div.Ability.Orange(),
)
}
van.add(document.body, UI());

9
app.tsx Normal file
View File

@ -0,0 +1,9 @@
import * as React from ">/npm:react";
export function App(){
return <>
<div>lol hey!</div>
</>
}
export default "lol"

View File

@ -1,19 +1,13 @@
{ {
"compilerOptions": "tasks": {
{ "dev": "deno -A --unstable-bundle --watch server.ts"
"checkJs": true, },
"lib": ["deno.window", "DOM"], "compilerOptions": {
"types": ["GALE@HOST/types.d.ts"] "jsx": "react-jsx",
}, "jsxImportSource": "react"
"tasks": },
{ "imports": {
"work": "deno run -Ar GALE@HOST/scripts/dev_server.ts", "@std/assert": "jsr:@std/assert@1",
"scan": "deno run -Ar GALE@HOST/scripts/refresh_types.ts", "react":"npm:react"
"html": "deno run -Ar GALE@HOST/scripts/scaffold.ts --html" }
},
"imports":
{
"GALE@HOST/":"./",
"GALE@ENTRY":"./app.js"
}
} }

113
deno.lock
View File

@ -1,111 +1,26 @@
{ {
"version": "5", "version": "5",
"specifiers": { "specifiers": {
"jsr:@std/media-types@*": "1.1.0", "npm:react@*": "19.2.0"
"npm:vanjs-core@*": "1.5.5",
"npm:vanjs-core@^1.5.5": "1.5.5",
"npm:vanjs-ext@~0.6.3": "0.6.3"
},
"jsr": {
"@std/media-types@1.1.0": {
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
}
}, },
"npm": { "npm": {
"vanjs-core@1.5.5": { "react@19.2.0": {
"integrity": "sha512-BC9MjbXYIRqnwncXfacT6upJpVmIKyrV2MjZi8NuCK+yc9RP0hfdghTpmEuYswXOfkLarDPPcYK4X6q68T9e+g==" "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="
},
"vanjs-ext@0.6.3": {
"integrity": "sha512-Jmaeqx9nCjelwDVSQEaRtt7R4Y/Kj/zJBG3bZSiIPj8Wtr8nEFRsJX9K5qcGl1o3cGEEFBE9suyoSqs/6tiOBg==",
"dependencies": [
"vanjs-core"
]
} }
}, },
"redirects": { "redirects": {
"https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts" "https://esm.sh/type-detect@^4.0.0?target=denonext": "https://esm.sh/type-detect@4.1.0?target=denonext"
}, },
"remote": { "remote": {
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", "https://esm.sh/deep-eql@4.1.3": "324a95b802d9f87b5ed66afdf079a0c47cd1cac3db59e9face0969be8eb980f7",
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", "https://esm.sh/deep-eql@4.1.3/denonext/deep-eql.mjs": "53319cc47b4be171d3a1aeeef9f3160a818e08b35baf9018cd14093f79e2910c",
"https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", "https://esm.sh/type-detect@4.1.0/denonext/type-detect.mjs": "ea850c5962bd47b0157c7e4cf38376cb7fb9fb3ad2438be0a724dbbadda5b94e",
"https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", "https://esm.sh/type-detect@4.1.0?target=denonext": "7257f955377cabc9a54bfa18f3bd16e12e40a090f25bf238299325d562e92fca"
"https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", },
"https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", "workspace": {
"https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", "dependencies": [
"https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", "jsr:@std/assert@1",
"https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", "npm:react@*"
"https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", ]
"https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
"https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3",
"https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607",
"https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a",
"https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883",
"https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0",
"https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15",
"https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e",
"https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643",
"https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36",
"https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c",
"https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441",
"https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac",
"https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069",
"https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972",
"https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7",
"https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141",
"https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a",
"https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0",
"https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd",
"https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352",
"https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f",
"https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a",
"https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d",
"https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0",
"https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
"https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1",
"https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00",
"https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2",
"https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1",
"https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40",
"https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f",
"https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede",
"https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
"https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63",
"https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
"https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
"https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91",
"https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
"https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a",
"https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c",
"https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf",
"https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf",
"https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0",
"https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add",
"https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d",
"https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b",
"https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40",
"https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808",
"https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660",
"https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
"https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5",
"https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9",
"https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef",
"https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6",
"https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01",
"https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8",
"https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a",
"https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
"https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
"https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
"https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
"https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780",
"https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
"https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707",
"https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7",
"https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972",
"https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e",
"https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c",
"https://vanjs.org/code/van-1.5.5.nomodule.min.js": "32403d4dd6203a46513f000fd64b18e4eaef0823bf757ca0092e70130d059aa3",
"https://vanjs.org/code/van-x-0.6.3.nomodule.min.js": "e4b7de89bf2f84c22669ce7bcef592fb398d9dcf8b8a36f4562b8ac6354b1f2f"
} }
} }

File diff suppressed because one or more lines are too long

31
hmr/hmr-listen.tsx Normal file
View File

@ -0,0 +1,31 @@
import { HMR } from "./hmr-react.tsx";
import { GroupSignal, GroupSignalHook } from "./hmr-signal.tsx";
const FileListeners = new Map() as Map<string, Array<(module:unknown)=>void>>;
export const FileListen =(inPath:string, inHandler:()=>void)=>
{
const members = FileListeners.get(inPath)??[];
members.push(inHandler);
FileListeners.set(inPath, members);
};
const Socket:WebSocket = new WebSocket("ws://"+document.location.host);
Socket.addEventListener('message', async(event:{data:string})=>
{
// When a file changes, dynamically re-import it to get the updated members
// send the updated members to any listeners for that file
GroupSignal.reset();
const reImport = await import(document.location.origin+event.data+"?reload="+Math.random());
FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport));
GroupSignal.swap();
GroupSignalHook.reset();
HMR.update();
GroupSignalHook.reset();
});
Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")})
const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000);

176
hmr/hmr-react.tsx Normal file
View File

@ -0,0 +1,176 @@
import * as ReactParts from "react-original";
/*
Each custom component is secretly modified to have an extra state and id.
When there is an HMR update, this state is changed, forcing it to re-render.
Each *user-created* React.useState is secretly modified and accompanied by an ID.
Every time its state is set, the HMR.statesNew map for this ID is set to contain the new state and updater.
When a component is removed, any of it's states in HMR.statesNew are also removed.
(HMR.statesNew is the "running total" of all states currently at play).
---
When a state is interacted with:
- statesNew for this id is set
- the internal state is also set in the traditional way
When there is an HMR update:
- All custom components are re-rendered...
for each useState(value) call that then happens in the re-render:
- if there is a "statesOld" value for this state, use that and ignore the passed value, otherwise use the passed value
- if this state has not been interacted with since the last reload (statesNew is empty at this id), set statesNew<id> with whatever is in statesOld<id>
- statesNew is moved into *statesOld*
- statesNew is cleared.
*/
export const HMR =
{
reloads:1,
RegisteredComponents: new Map() as Map<string, ()=>void>,
statesNew: new Map() as Map<string, StateCapture>,
statesOld: new Map() as Map<string, StateCapture>,
wireframe: false,
RegisterComponent(reactID:string, value:()=>void):void
{
this.RegisteredComponents.set(reactID, value);
},
update()
{
this.reloads++;
this.RegisteredComponents.forEach(handler=>handler());
this.RegisteredComponents.clear();
this.statesOld = this.statesNew;
this.statesNew = new Map();
}
};
export type StateType = boolean|number|string|Record<string, string>
export type StateCapture = {state:StateType, set:ReactParts.StateUpdater<StateType>, reload:number};
type FuncArgs = [element:keyof ReactParts.JSX.IntrinsicElements, props:Record<string, string>, children:ReactParts.JSX.Element[]];
const H = ReactParts.createElement;
const MapIndex =(inMap:Map<string, StateCapture>, inIndex:number)=>
{
let index = 0;
for(const kvp of inMap)
{
if(index == inIndex)
{
return kvp;
}
index++;
}
return false;
};
const ProxyCreate =(...args:FuncArgs)=> (typeof args[0] == "string") ? H(...args) : H(ProxyElement, {__args:args, ...args[1]});
const ProxyElement = (props:{__args:FuncArgs})=>
{
const [stateGet, stateSet] = ReactParts.useState(0);
const id = ReactParts.useId();
HMR.RegisterComponent(id, ()=>stateSet(stateGet+1));
const child = H(...props.__args);
if(HMR.wireframe)
{
return H("div", {style:{padding:"10px", border:"2px solid red"}},
H("p", null, stateGet),
child
);
}
else
{
return child;
}
};
const ProxyState =(argNew:StateType)=>
{
// does statesOld have an entry for this state? use that instead of the passed arg
const check = MapIndex(HMR.statesOld, HMR.statesNew.size);
const argOld = check ? check[1].state : argNew;
const id = ReactParts.useId();
const [stateGet, stateSet] = ReactParts.useState(argOld);
// state updates due to clicks, interactivity, etc. since the last reload may already be in statesNew for this slot.
// DONT overwrite it.
if(!HMR.statesNew.get(id))
{
HMR.statesNew.set(id, {state:stateGet, set:stateSet, reload:HMR.reloads});
}
const lastKnowReloads = HMR.reloads;
ReactParts.useEffect(()=>{
return ()=>{
if(HMR.reloads == lastKnowReloads)/*i have no idea what this does. this may have to be re-introduced when routing is added*/
{
// this is a switch/ui change, not a HMR reload change
const oldState = MapIndex(HMR.statesOld, HMR.statesNew.size-1);
oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:argNew});
}
HMR.statesNew.delete(id);
}
}, []);
// do we need to account for the function set?
function proxySetter ( inArg:StateType|((old:StateType)=>StateType) )
{
const stateUser = {state:inArg as StateType, set:stateSet, reload:HMR.reloads};
if(typeof inArg == "function")
{
//const passedFunction = inArg;
stateSet((oldState:StateType)=>
{
const output = inArg(oldState);
stateUser.state = output;
HMR.statesNew.set(id, stateUser);
return output;
});
}
else
{
HMR.statesNew.set(id, stateUser);
stateSet(inArg);
}
}
return [stateGet, proxySetter];
};
type Storelike = Record<string, string>
const ProxyReducer =(inReducer:(inState:Storelike, inAction:string)=>Storelike, inState:Storelike, inInit?:(inState:Storelike)=>Storelike)=>
{
const check = MapIndex(HMR.statesOld, HMR.statesNew.size);
const argOld = check ? check[1].state : (inInit ? inInit(inState) : inState);
const intercept =(inInterceptState:Storelike, inInterceptAction:string)=>
{
const capture = inReducer(inInterceptState, inInterceptAction);
const stateUser = {state:capture, set:()=>{}, reload:HMR.reloads};
HMR.statesNew.set(id, stateUser);
return capture;
};
const id = ReactParts.useId();
const [state, dispatch] = ReactParts.useReducer(intercept, argOld as Storelike);
if(!HMR.statesNew.get(id))
{
HMR.statesNew.set(id, {state:state, set:()=>{}, reload:HMR.reloads});
}
return [state, dispatch];
};
export * from "react-original";
export {ProxyCreate as createElement, ProxyState as useState, ProxyReducer as useReducer };
export default {...ReactParts, createElement:ProxyCreate, useState:ProxyState, useReducer:ProxyReducer};

47
hmr/hmr-signal.tsx Normal file
View File

@ -0,0 +1,47 @@
import * as SignalsParts from "signals-original";
import DeepEqual from "https://esm.sh/deep-eql@4.1.3";
type Entry<T> = [signal:SignalsParts.Signal<T>, initArg:T];
function ProxyGroup<T>(inFunc:(initArg:T)=>SignalsParts.Signal<T>)
{
let recordEntry:Entry<T>[] = [];
let recordEntryNew:Entry<T>[] = [];
let recordIndex = 0;
const reset =()=> recordIndex = 0;
const swap =()=>
{
recordEntry = recordEntryNew;
recordEntryNew = [] as Entry<T>[];
};
const proxy =(arg:T)=>
{
const lookupOld = recordEntry[recordIndex];
if(lookupOld && DeepEqual(lookupOld[1], arg))
{
recordEntryNew[recordIndex] = lookupOld;
recordIndex++;
return lookupOld[0];
}
else
{
const sig = inFunc(arg);
recordEntryNew[recordIndex] = [sig, arg];
recordEntry[recordIndex] = [sig, arg];
recordIndex++;
return sig;
}
};
return {reset, swap, proxy};
}
export const GroupSignal = ProxyGroup(SignalsParts.signal);
export const GroupSignalHook = ProxyGroup(SignalsParts.useSignal);
const proxySignal = GroupSignal.proxy;
const proxySignalHook = GroupSignalHook.proxy;
export * from "signals-original";
export { proxySignal as signal, proxySignalHook as useSignal };
export default {...SignalsParts, signal:proxySignal, useSignal:proxySignalHook};

144
hmr/hmr-static.tsx Normal file
View File

@ -0,0 +1,144 @@
type GlyphCheck = (inGlyph:string)=>boolean
const isAlphaLike:GlyphCheck =(inGlyph:string)=>
{
const inCode = inGlyph.charCodeAt(0);
if(inCode >= 97 && inCode <= 122)
{
return true;
}
if(inCode >= 65 && inCode <= 90)
{
return true;
}
return `$_.`.includes(inGlyph);
}
const isWhiteSpace:GlyphCheck =(inGlyph:string)=> `\n\r\t `.includes(inGlyph);
const isQuote:GlyphCheck =(inGlyph:string)=>`"'\``.includes(inGlyph)
const isNot =(inCheck:GlyphCheck)=> (inGlyph:string)=>!inCheck(inGlyph);
const contiguous =(inText:string, inStart:number, inTest:GlyphCheck):number=>
{
let ok = true;
let index = inStart;
let count = 0;
while(ok && count < inText.length)
{
count++;
ok = inTest(inText.charAt(index++));
}
return index-1;
}
const findNextExport =(inFile:string, inIndex=0, inLocal:Array<string>, inForeign:Array<string>)=>
{
const pos = inFile.indexOf("export", inIndex);
if(pos !== -1)
{
if(!isAlphaLike(inFile.charAt(pos-1)) || !isAlphaLike(inFile.charAt(pos+6)))
{
const nextCharInd = contiguous(inFile, pos+6, isWhiteSpace);
const nextChar = inFile[nextCharInd];
//console.log(inFile.substring(pos, nextCharInd+1), `>>${nextChar}<<`)
if(nextChar === "*")
{
const firstQuoteInd = contiguous(inFile, nextCharInd+1, isNot(isQuote) );
const secondQuoteInd = contiguous(inFile, firstQuoteInd+1, isNot(isQuote) );
//console.log("ASTERISK:", inFile.substring(pos, secondQuoteInd+1));
inForeign.push(inFile.substring(nextCharInd, secondQuoteInd+1));
}
else if(nextChar == "{")
{
const endBracketInd = contiguous(inFile, nextCharInd, (inGlyph:string)=>inGlyph!=="}");
const nextLetterInd = contiguous(inFile, endBracketInd+1, isWhiteSpace);
if(inFile.substring(nextLetterInd, nextLetterInd+4) == "from")
{
const firstQuoteInd = contiguous(inFile, nextLetterInd+4, isNot(isQuote) );
const secondQuoteInd = contiguous(inFile, firstQuoteInd+1, isNot(isQuote) );
//console.log(`BRACKET foreign: >>${inFile.substring(nextCharInd, secondQuoteInd+1)}<<`);
inForeign.push(inFile.substring(nextCharInd, secondQuoteInd+1));
}
else
{
const members = inFile.substring(nextCharInd+1, endBracketInd).replace(/\s/g, '');
members.split(",").forEach(part=>
{
const renamed = part.split(" as ");
inLocal.push(renamed[1] || renamed[0]);
});
}
}
else if(isAlphaLike(nextChar))
{
const keywordEndInd = contiguous(inFile, nextCharInd, isAlphaLike);
const keyword = inFile.substring(nextCharInd, keywordEndInd);
if(keyword === "default")
{
inLocal.push(keyword);
//console.log(`MEMBER: >>${keyword})}<<`);
}
else if(["const", "let", "var", "function", "class"].includes(keyword))
{
const varStartInd = contiguous(inFile, keywordEndInd+1, isWhiteSpace);
const varEndInd = contiguous(inFile, varStartInd+1, isAlphaLike);
//console.log(`MEMBER: >>${inFile.substring(varStartInd, varEndInd)}<<`);
inLocal.push(inFile.substring(varStartInd, varEndInd))
}
}
}
return pos + 7;
}
else
{
return false;
}
};
export const ModuleShape =(inText:string)=>
{
let match = 0 as number|false;
let count = 0;
const local = [] as string[];
const foreign = [] as string[];
while(match !== false && count <200)
{
count++;
match = findNextExport(inText, match, local, foreign);
}
return[local, foreign] as [local:string[], foreign:string[]];
};
export const ModuleProxy =(inText:string, inPath:string)=>
{
const [local, foreign] = ModuleShape(inText);
console.log("shape local", local);
return `
import {FileListen} from ">/hmr/hmr-listen.tsx";
import * as Import from "${inPath}?reload=${new Date().getTime()}";
${ local.map(m=>`let proxy_${m} = Import.${m}; export { proxy_${m} as ${m} };`).join("\n") }
FileListen("${inPath}", (updatedModule)=>
{
${ local.map(m=>`proxy_${m} = updatedModule.${m};`).join("\n") }
});
${ foreign.join(";\n") }`;
};
// ///////////////////////////////////////////////
// const [local, global] = Exports(`
// // export in comment
// export * from "react";
// const fakeexport =()=>{};
// export{ thing1 as remapped, thing2}
// export { thing1 as remapped, thing2} from 'React';
// export
// export const func=()=>{};
// `);
//
// console.log(local, global);

View File

@ -1,8 +1,12 @@
<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head> <title>Document</title>
<body></body> </head>
<body>
<script type="module">
import App from ">/app.tsx";
</script>
</body>
</html> </html>

View File

@ -1,13 +0,0 @@
export const Root = import.meta.resolve("../");
export const Load =async(file:string)=> await fetch(Root + file).then(resp=>resp.text());
export const Save =async(text:string, name:string)=> await Deno.writeTextFile(name, text);
const index = await Load("index.html");
export const HTML = index.replace(
`</head>`,
`
<style>*{margin:0;padding:0;box-sizing:border-box;}html, body{height:100%;width:100%;font-family:Arial, sans-serif;line-height:1.6;}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}img, video{max-width:100%;height:auto;}a{text-decoration:none;color:inherit;}ul, ol{list-style:none;}button, input, textarea{font-family:inherit;font-size:inherit;line-height:inherit;border:none;background:none;padding:0;margin:0;outline:none;}table{border-collapse:collapse;width:100%;}</style>
<script type="module" src="/proxy/dist/bundle_entry.js?hmr&map"}"></script>
</head>`);

View File

@ -1,61 +0,0 @@
export interface DenoBundleOptions {
entry: string;
output?: string;
outdir?: string;
check?: boolean | "all";
noCheck?: boolean | "remote";
frozen?: boolean;
importMap?: string;
lock?: string;
noLock?: boolean;
noNpm?: boolean;
noRemote?: boolean;
nodeModulesDir?: boolean;
reload?: boolean | string[];
vendor?: boolean;
allowImport?: string[];
allowScripts?: string[];
cert?: string;
codeSplitting?: boolean;
conditions?: string[];
config?: string;
denyImport?: string[];
inlineImports?: boolean;
minify?: boolean;
noConfig?: boolean;
packages?: "bundle" | "external";
platform?: "browser" | "deno";
preload?: string[];
sourcemap?: "linked" | "inline" | "external";
watch?: boolean;
}
export default (options: DenoBundleOptions):{ out: string; err: string; }=>
{
const argify =(str: string)=> "--"+str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
const args: string[] = ["bundle"];
const { entry, ...rest } = options;
for (const [key, value] of Object.entries(rest))
{
let flag = argify(key);
if (value !== true)
{
if(!value)
{
continue;
}
flag = flag + "=" + (Array.isArray(value) ? value.join(",") : value);
}
args.push(flag);
}
args.push(entry);
console.log(args);
const command = new Deno.Command("deno", {args});
const result = command.outputSync();
const decode = new TextDecoder("utf-8");
const output = (str:Uint8Array<ArrayBuffer>)=> decode.decode(str).replace(/\x1b\[[0-9;]*m/g,"").replace(/\n+$/, "");
return { out: output(result.stdout), err: output(result.stderr) }
}

View File

@ -1,34 +0,0 @@
import Van from "npm:vanjs-core@^1.5.5";
globalThis.van = Van;
import * as VanX from "npm:vanjs-ext@^0.6.3";
globalThis.vanX = VanX;
import Gale from "../src/gale.js";
globalThis.Gale = Gale;
const args = new URL(import.meta.url).searchParams;
//Store
vanX.Store=(e,t)=>{const a=localStorage.getItem(t),r=vanX.reactive(a?JSON.parse(a):e);return van.derive((()=>localStorage.setItem(t,JSON.stringify(vanX.compact(r))))),r};
if(args.has("hmr"))
{
const path = "/proxy/"+"src/hmr.js";
await import(path);
}
const root = args.get("root")||"/";
fetch(root+"deno.json")
.then(text=>text.json())
.then(json=>{
const Script=(t,e)=>{let n=document.createElement("script"); n.type=t; n.textContent=e; document.head.appendChild(n); return n;};
const imports = json.imports;
for(let n in imports)
{
const path=imports[n];
path.startsWith("./")&&(imports[n]=root+path.substring(2))
}
args.has("map") && Script("importmap",JSON.stringify({imports}));
Script("module", "").src=imports["GALE@ENTRY"];
});

View File

@ -1,13 +0,0 @@
import Bundler from "./bundle.ts";
const result = Bundler({
entry: "scripts/bundle_entry.ts",
outdir: "dist",
inlineImports: true,
minify: true,
codeSplitting: true,
platform: "browser",
noLock: true,
});
console.log(result);

View File

@ -1,80 +0,0 @@
import { contentType } from "jsr:@std/media-types";
import {HTML, Root} from "./assemble_files.ts";
// Parse the port from the command-line arguments, defaulting to 8000
const port = parseInt(Deno.args[0] || "8000", 10);
const sockets: WebSocket[] = [];
function extension(path: string): string {
// Remove trailing slash if it exists
const normalizedPath = path.endsWith("/") ? path.slice(0, -1) : path;
// Get the last part of the path
const lastPart = normalizedPath.split("/").pop() || "";
// Check if the last part contains a "."
return lastPart.split(".")[1] || "";
}
// Start the HTTP server using Deno.serve
Deno.serve({ port }, async (req: Request) => {
const path = new URL(req.url).pathname;
// Handle WebSocket connections
if (path === "/ws") {
const { socket, response } = Deno.upgradeWebSocket(req);
sockets.push(socket);
return response;
}
// Serve static files or the predefined HTML for non-file routes
const ext = extension(path);
// Serve the predefined HTML for non-file routes
if (!ext) {
return new Response(HTML, {
headers: { "Content-Type": "text/html" },
});
}
try
{
const proxyPrefix = "/proxy/";
let streamable;
if(path.startsWith(proxyPrefix))
{
const file = await fetch(Root + path.slice(proxyPrefix.length));
streamable = file.body;
}
else
{
const file = await Deno.open("." + path, { read: true });
streamable = file.readable;
}
return new Response(streamable, {
headers: { "Content-Type": contentType(ext) || "application/javascript" },
});
}
catch (err)
{
if (err instanceof Deno.errors.NotFound) {
return new Response("File not found", { status: 404 });
} else {
return new Response("Internal server error", { status: 500 });
}
}
});
// Start watching for file changes
const watcher = Deno.watchFs(".");
for await (const event of watcher) {
if (event.kind === "modify") {
for (const ws of sockets) {
if (ws.readyState === WebSocket.OPEN) {
ws.send("reload");
continue;
}
}
}
}

View File

@ -1,37 +0,0 @@
import { resolve, toFileUrl } from "https://deno.land/std/path/mod.ts";
async function main()
{
// Read and parse the deno.json file
const denoJson = JSON.parse(await Deno.readTextFile("./deno.json"));
// Check if compilerOptions and types are defined
if (denoJson.compilerOptions?.types)
{
const types:string[] = denoJson.compilerOptions.types;
// Iterate over each type file and cache it
for (const typeFile of types)
{
let lookup:string = typeFile;
console.log(`found ambient type file`, lookup);
if(typeFile.startsWith("."))
{
lookup = toFileUrl(resolve(Deno.cwd(), typeFile)).href;
}
console.log(`Scan found types: ${lookup}`);
await import(lookup); // This will cache the file
}
console.log("Scan complete; be sure to restart the Deno Language Server!!");
} else {
console.log("No types found in compilerOptions.");
}
}
main().catch((error) => {
console.error("Error:", error);
Deno.exit(1);
});

View File

@ -1,16 +0,0 @@
import { parseArgs } from "jsr:@std/cli/parse-args";
import {HTML, Save, Load, Root} from "./assemble_files.ts";
const args = parseArgs(Deno.args);
if(args.html)
{
Save(HTML, "index.html");
}
else
{
const config = await Load("deno.json");
const json = JSON.parse(config);
json.imports["GALE@HOST/"] = Root;
Save(JSON.stringify(json, null, "\t"), "deno.json");
Save(await Load("app.js"), "app.js");
}

140
server.ts Normal file
View File

@ -0,0 +1,140 @@
import { ModuleProxy } from "./hmr/hmr-static.tsx";
const keyProxy = encodeURI(">");
const keyAdjacent = "^";
const keyReload = "reload";
const keysRemote = ["npm:", "jsr:", "http"];
const keysExtension = ["ts", "tsx"];
const extractExtension =(path)=>
{
return path.substring(path.lastIndexOf(".")+1);
}
const RootRunning = new URL(`file://${Deno.cwd()}`).toString();
const RootSiblings = import.meta.resolve("./");
console.log(RootRunning);
console.log(RootSiblings);
const bakeConfigPackage:Deno.bundle.Options =
{
entrypoints:[""],
platform: "browser",
write: false,
minify: true,
}
const bakeConfigLocal:Deno.bundle.Options = {...bakeConfigPackage, minify:false, sourcemap:"inline", inlineImports:false };
async function BakeForce(path:string, type?:"package")
{
console.log("baking", path);
const config = type ? bakeConfigPackage : bakeConfigLocal;
config.entrypoints[0] = type ? path : RootRunning+path;
const result = await Deno.bundle(config);
if(result.outputFiles)
{
const body = result.outputFiles.map(file=>file.text()).join("\n");
const save:CachedTranspile = [body, type ? "" : ModuleProxy(body, path)];
BakeCache[path] = save;
return save;
}
return undefined;
};
async function BakeCheck(path:string, type?:"package")
{
const lookup:CachedTranspile = await BakeCache[path];
if(!lookup)
{
return BakeForce(path, type);
}
return lookup;
}
type CachedTranspile = [file:string, profile:string]
const BakeCache:Record<string, CachedTranspile> = {}
const JSResponse =(body:string)=>new Response(body, {headers:{"content-type":"application/javascript"}});
Deno.serve(async(req:Request)=>
{
const url = new URL(req.url);
const parts = url.pathname.split("/").filter(part=>part);
const lastPart = parts.at(-1);
const extension = extractExtension(lastPart);
console.log("REQUEST:", parts, extension);
if(parts[0] == keyProxy)
{
const proxiedPath = parts.slice(1).join("/");
console.log("PROXIED:", proxiedPath);
const transpiled = await BakeCheck(proxiedPath, "package");
return JSResponse(transpiled[0]);
}
if(keysExtension.includes(extension))
{
const transpiled = await BakeCheck(url.pathname);
return JSResponse(transpiled[url.searchParams.has(keyReload) ? 0 : 1]);
}
if(req.headers.get("upgrade") == "websocket")
{
try
{
const { response, socket } = Deno.upgradeWebSocket(req);
socket.onopen = () => SocketsLive.add(socket);
socket.onclose = () => SocketsLive.delete(socket);
socket.onmessage = () => {};
socket.onerror = (e) => console.log("Socket errored:", e);
return response;
}
catch(e){ console.log("Socket errored:", e); }
}
return new Response();
});
const SocketsLive:Set<WebSocket> = new Set();
const SocketsSend =(inData:string)=>{ for (const socket of SocketsLive){ socket.send(inData); } }
const Watcher =async()=>
{
let blocking = false;
const filesChanged:Map<string, string> = new Map();
for await (const event of Deno.watchFs(Deno.cwd()))
{
event.paths.forEach( path => filesChanged.set(path, event.kind) );
if(!blocking)
{
blocking = true;
setTimeout(async()=>
{
for await (const [path, action] of filesChanged)
{
const extension = extractExtension(path);
if(keysExtension.includes(extension))
{
console.log("File change", path);
const key = path.substring(Deno.cwd().length).replaceAll("\\", "/");
if(action != "remove")
{
BakeForce(path);
SocketsSend(key);
}
else
{
delete BakeCache[key];
}
}
}
filesChanged.clear();
blocking = false;
}
, 1000);
}
}
}
Watcher();

View File

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow DOM Example</title>
</head>
<body>
<script>
// Define the custom web component
class APP extends HTMLElement {
constructor() {
super();
// Attach a shadow root to the custom element
const shadowRoot = this.attachShadow({ mode: 'open' });
// Add custom markup and styles to the shadow root
shadowRoot.innerHTML = `
<style>
.shadow-content {
color: white;
background-color: black;
padding: 10px;
border-radius: 5px;
}
</style>
<div class="shadow-content">
This is content inside the shadow root of a web component.
</div>
`;
}
}
// Register the custom element
customElements.define('gale-app', APP);
</script>
<gale-app></gale-app>
</body>
</html>

View File

@ -1,54 +0,0 @@
Sheet({
fontSize:`12px`
},{
1024:
{
fontSize:`14px`
}
})
function Prox()
{
const obj = new Proxy({}, {
get(target, propName, receiver)
{
console.log("get:", propName)
return obj;
},
set(target, propName, value, receiver)
{
console.log("set:", propName)
return true;
}
})
return obj;
}
//
// const sheet = {};
//
// sheet
// .Font.size`8px`.color`#aabbcc`
// [512]
// .Font.size`10px`
// [1024]
// .Font.size`12px`
//
//
// sheet(1024,
// Font.size`12px`.color`#aabbcc`
// )(512,
// Font.size`10px`);
//
const p1 = Prox();
p1.read
p1.write = 123;

View File

@ -1,89 +0,0 @@
/** @type {Gale.CreateSheet} */
export default (sheet, hash="")=>
{
const KeyQuery = "@";
const KeyState = ":";
const KeyChild = ".";
const KeyGroup = "^";
/** @type {Gale.Tier} */
const Tier=(selector, obj, suffix)=>
{
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, suffix);
case KeyState :
return Tier(`&${key}`, value, suffix);
case KeyChild :
return Tier(`${key}${suffix}`, value, suffix);
case KeyGroup :
return Tier(`&:hover .${key.substring(KeyGroup.length)}${suffix}`, value, suffix);
}
return `${ key.replace(/([a-z])([A-Z])/g, '$1-$2') }: ${value};`
});
return `${selector}{${styles.join("\n")}}`;
}
/** @type {(needle:string, str:string)=>string} */
const extractLast =(needle, str)=>{
const ind = str.lastIndexOf(needle)+needle.length;
return ind ? str.substring(ind) : str;
}
const collect =(tagName)=>
{
const pending = van.tags[tagName];
let mentioned = [];
const collector = new Proxy(
(...args)=>
{
const element = pending(...args);
element.className = mentioned.join(id+" ")+id + " " + element.className;
mentioned = [];
return element;
},
{
get(_, prop)
{
mentioned.push(prop.substring(prop.lastIndexOf(".")+1));
return collector;
}
}
);
return collector;
}
const id = hash ? "_"+hash : "";
const css = Object.keys(sheet).map(key=>Tier("."+key+id, sheet[key], id)).join(`\n`);
globalThis.document?.head.insertAdjacentHTML("beforeend", `<style data-sheet="${id}">${css}</style>`);
return {
Tag(...args){
return args.map((arg)=>extractLast(KeyGroup, extractLast(KeyChild, arg))).join(id+" ")+id;
},
CSS: css,
DOM: new Proxy(
{},
{
get(_, prop)
{
return collect(prop)
}
}
),
Div: new Proxy(
{},
{
get(_, prop)
{
return collect("div")[prop]
}
}
)
}
}

View File

@ -1,118 +0,0 @@
let Time = 0;
/** @type {Record<string, string>} */
let Temp = {};
function Tick()
{
for(const k in Temp)
{
sessionStorage.setItem(k, Temp[k]);
}
Temp = {};
Time = 0;
}
/** @type {(key:string, value:string)=>void} */
function Save(key, value)
{
Temp[key] = value;
if(!Time)
{
Time = setTimeout(Tick, 500);
}
};
/** @type {(key:string)=>string|null} */
function Load(key)
{
const value = sessionStorage.getItem(key);
return value;
}
/** @type {string|undefined} */
let _ID = undefined;
let _index = 0;
/** @type {(id:string|undefined = undefined)=>void} */
function StartID(id)
{
_index = 0;
_ID = id;
}
function NextID()
{
return _ID ? _ID + "_" + (_index++) + "_" : "";
}
//bind Van
const origninalState = globalThis.van.state;
globalThis.van.state =(value, key="")=>
{
const type = typeof value;
let reader =d=>d;
let writer =d=>d?.toString() || null;
switch(type)
{
case "boolean" :
reader =(data)=> data === "true"; break;
case "number" :
reader = parseFloat; break;
case "object" :
reader = JSON.parse;
writer = JSON.stringify;
break;
}
const fullKey = "HMR_" + NextID() + key;
const stringValue = Load(fullKey);
const signal = origninalState((stringValue ? reader(stringValue) : value));
van.derive(()=>Save(fullKey, writer(signal.val)));
return signal;
};
//bind VanX
const originalReactive = globalThis.vanX.reactive;
globalThis.vanX = {...globalThis.VanX, reactive:(obj, id)=>
{
StartID(id);
const state = originalReactive(obj);
StartID();
return state;
}};
// added in devmode to index.html
new WebSocket('ws://'+window.location.host+'/ws').addEventListener('message',e=>e.data==='reload'&&window.location.reload());
vanX.Store =(obj, key)=>
{
let checkInit = JSON.stringify(obj);
let checkStore = localStorage.getItem(key+"check");
localStorage.setItem(key+"check", checkInit);
let recallJSON;
if(checkInit == checkStore)
{
let recallText = localStorage.getItem(key);
try
{
recallJSON = JSON.parse(recallText) || obj;
}
catch(e)
{
recallJSON = obj;
}
}
else
{
recallJSON = obj;
}
const store = vanX.reactive( recallJSON );
van.derive(() => localStorage.setItem(key, JSON.stringify(vanX.compact(store))));
return store;
}

71
types.d.ts vendored
View File

@ -1,71 +0,0 @@
import type * as VAN from "https://vanjs.org/code/van-1.5.5.d.ts";
import type * as VANX from "https://vanjs.org/code/van-x-0.6.3.d.ts";
type Replace<T extends object, K extends keyof T, R> = Omit<T, K> & { readonly [P in K]: R };
declare module "vanjs-core" { export type State<T> = VAN.State<T> }
declare global
{
namespace Van { export type * from "https://vanjs.org/code/van-1.5.5.d.ts"; }
namespace VanX { export type * from "https://vanjs.org/code/van-x-0.6.3.d.ts"; }
const van: Replace<VAN.Van, "state", <T>(arg:T, HMRKey?:string)=>VAN.State<T>>
const vanX: Replace<typeof VANX, "reactive", <T extends object>(obj: T, HMRKey?:string) => T> & {Store:<T>(obj:T, key:string)=>T}
}
declare global {
namespace Gale {
type KeyQuery = "@";
type KeyState = ":";
type KeyChild = ".";
type KeyGroup = "^";
type UserStyles = Partial<CSSStyleDeclaration> & {[key: `${KeyQuery|KeyState|KeyChild|KeyGroup}${string}`]: UserStyles }
type UserSheet = Record<string, UserStyles>
type CollectKeys<Obj> = {[Key in keyof Obj]: Obj[Key] extends object ? Key | CollectKeys<Obj[Key]> : Key }[keyof Obj]
type FilterKeys<Keys> = Keys extends `${KeyChild|KeyGroup}${infer Rest}` ? Keys : never
type CrossMultiply<A, B> = A extends string ? B extends string ? `${A}${B}` : never : never
type CrossMultiplyRecord<Rec> = keyof Rec | { [K in keyof Rec]: K extends string ? CrossMultiply<K, FilterKeys<CollectKeys<Rec[K]>>> : never }[keyof Rec]
type Tier = (selector:string, obj:UserStyles, suffix:string)=>string;
type CreateSheet = <T extends UserSheet>(sheet:UserSheet&T, hash?:string)=>{
Tag:(...args:CrossMultiplyRecord<T>[])=>string,
CSS:string,
DOM:Elemental<CrossMultiplyRecord<T>>,
Div:Circular<CrossMultiplyRecord<T>, Van.TagFunc<HTMLDivElement>>
}
type Elemental<T extends string> = {[K in keyof HTMLElementTagNameMap]: Van.TagFunc<HTMLElementTagNameMap[K]>&Circular<T, Van.TagFunc<HTMLElementTagNameMap[K]>>}
type Circular<Keys extends string, Func> = {
[K in Keys]: Circular<Keys, Func>&Func
};
}
const Gale:Gale.CreateSheet
}
declare global
{
namespace JSS
{
type Block = Partial<Record<keyof CSSStyleDeclaration, UnitValue>>
type Responsive = Record<number, Block>
type Unit = "px" | "em" | "rem" | "%" | "vh" | "vw" | "vmin" | "vmax" | "cm" | "mm" | "in" | "pt" | "pc" | "ch" | "ex"
type UnitValue = `${number}${Unit}` | [amount:number, unit:Unit]
type HexNumber = "1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"0"|"a"|"b"|"c"|"d"|"e"|"f"
type HexTriplet = `#${HexNumber}${HexNumber}${HexNumber}`
type HexColor = `#${HexNumber}${HexNumber}${HexNumber}${HexNumber}${HexNumber}${HexNumber}`
type Rules = {
fontSize: UnitValue,
letterSpacing: UnitValue
}
type SheetGen =(mobile:Block, conditions?:Responsive)=> void
}
const Sheet:JSS.SheetGen
}