= React.useRef(true);
+
+ type MenuClassStates = {Keep:string, Open:string, Shut:string, Move:string, Exit:string};
+ const base = `relative transition-all border(8 black) overflow-hidden`;
+ const Classes:MenuClassStates =
+ {
+ Shut: `${base} h-0 top-0 w-1/2 duration-300`,
+ Open: `${base} h-auto top-8 w-full duration-700`,
+ lol: `${base} h-auto top-36 bg-yellow-500 w-2/3 duration-700`,
+ };
+ const Window = window as {TwindInst?:(c:string)=>string};
+ if(Window.TwindInst)
+ {
+ for(let stateName in Classes)
+ {
+ Classes[stateName as keyof MenuClassStates] = Window.TwindInst(Classes[stateName as keyof MenuClassStates]);
+ }
+ }
+
+ React.useEffect(()=>
+ {
+ refControl.current = refElement.current && Collapser(refElement.current, stateGet.open ? "Open" : "Shut", Classes);
+ }
+ , []);
+ React.useEffect(()=>
+ {
+ (!refInitial.current && refControl.current) && refControl.current(stateGet.open ? "Open" : "Shut", ()=>stateSet({done:true}));
+ refInitial.current = false;
+ }
+ , [stateGet.open]);
+
+ useAway(refElement, (e)=>stateSet({open:false, done:false}) );
+
+ return } class={Classes.Shut}>
+ { (!stateGet.open && stateGet.done) ? null : props.children}
+
;
+};
+
+export const Button =()=>
+{
+ const [stateGet, stateSet] = React.useContext(CTX);
+ return <>
+ {JSON.stringify(stateGet)}
+
+
+ >;
+};
+
+type Handler = (e:MouseEvent)=>void
+const Refs:Map> = new Map();
+function isHighest(inElement:HTMLElement, inSelection:HTMLElement[])
+{
+ let currentNode = inElement;
+ while (currentNode != document.body)
+ {
+ currentNode = currentNode.parentNode as HTMLElement;
+ if(currentNode.hasAttribute("data-use-away") && inSelection.includes(currentNode))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+window.innerWidth && document.addEventListener("click", e=>
+{
+ const path = e.composedPath();
+ const away:HTMLElement[] = [];
+
+ Refs.forEach( (handlerRef, element)=>
+ {
+ if(!path.includes(element) && handlerRef.current)
+ {
+ away.push(element);
+ }
+ });
+
+ away.forEach((element)=>
+ {
+ if(isHighest(element, away))
+ {
+ const handler = Refs.get(element);
+ handler?.current && handler.current(e);
+ }
+ });
+
+}
+, true);
+const useAway =(inRef:React.Ref, handleAway:Handler)=>
+{
+ const refHandler:React.MutableRefObject = React.useRef(handleAway);
+ refHandler.current = handleAway;
+
+ React.useEffect(()=>
+ {
+ if(inRef.current)
+ {
+ inRef.current.setAttribute("data-use-away", "0");
+ Refs.set(inRef.current, refHandler);
+ }
+ return ()=> inRef.current && Refs.delete(inRef.current);
+ }
+ , []);
+};
+
+type StyleSize = [classes:string, width:number, height:number];
+type StylePack = Record;
+type StyleCalc = Record;
+const StyleCalc =(inElement:HTMLElement, inClasses:StylePack)=>
+{
+ const initialStyle = inElement.getAttribute("style")||"";
+ const initialClass = inElement.getAttribute("class")||"";
+ const output = {} as StyleCalc;
+
+ inElement.setAttribute("style", `transition: none;`);
+ Object.entries(inClasses).forEach(([key, value])=>
+ {
+ inElement.setAttribute("class", value);
+ output[key] = [value, inElement.offsetWidth, inElement.offsetHeight];
+ });
+ inElement.setAttribute("class", initialClass);
+ inElement.offsetHeight; // this has be be exactly here
+ inElement.setAttribute("style", initialStyle);
+
+ return output;
+};
+
+type DoneCallback =(inState:string)=>void;
+export type CollapseControls =(inOpen?:string, inDone?:DoneCallback)=>void;
+export function Collapser(inElement:HTMLElement, initialState:string, library:Record)
+{
+ let userDone:DoneCallback = (openState) => {};
+ let userMode = initialState;
+ let frameRequest = 0;
+ let inTransition = false;
+ let measurements:StyleCalc;
+ const transitions:Set = new Set();
+
+ const run = (inEvent:TransitionEvent)=> (inEvent.target == inElement) && transitions.add(inEvent.propertyName);
+ const end = (inEvent:TransitionEvent)=>
+ {
+ if (inEvent.target == inElement)
+ {
+ transitions.delete(inEvent.propertyName);
+ if(transitions.size === 0)
+ {
+ measurements = StyleCalc(inElement, library);
+ const [, w, h] = measurements[userMode];
+ if(inElement.offsetHeight != h || inElement.offsetWidth != w)
+ {
+ anim(userMode, userDone);
+ }
+ else
+ {
+ inElement.setAttribute("style", "");
+ inTransition = false;
+ userDone(userMode);
+ }
+ }
+ }
+ };
+ const anim = function(inState:string, inDone)
+ {
+ cancelAnimationFrame(frameRequest);
+
+ if(arguments.length)
+ {
+ if(!library[inState]){ return; }
+
+ userDone = inDone|| ((m)=>{}) as DoneCallback;
+ userMode = inState;
+
+ if(!inTransition)
+ {
+ measurements = StyleCalc(inElement, library);
+ }
+
+ if(measurements)
+ {
+ const [classes, width, height] = measurements[inState] as StyleSize;
+ const oldWidth = inElement.offsetWidth;
+ const oldHeight = inElement.offsetHeight;
+ inElement.style.width = oldWidth + "px";
+ inElement.style.height = oldHeight + "px";
+ inTransition = true;
+
+ frameRequest = requestAnimationFrame(()=>
+ {
+ inElement.style.height = height + "px";
+ inElement.style.width = width + "px";
+ inElement.className = classes;
+ });
+ }
+ }
+ else
+ {
+ inElement.removeEventListener("transitionrun", run);
+ inElement.removeEventListener("transitionend", end);
+ }
+ } as CollapseControls;
+
+ inElement.addEventListener("transitionend", end);
+ inElement.addEventListener("transitionrun", run);
+
+ return anim;
+}
diff --git a/run-serve.tsx b/run-serve.tsx
index f67320a..2ca9f30 100644
--- a/run-serve.tsx
+++ b/run-serve.tsx
@@ -55,7 +55,15 @@ let Configuration:Configuration =
Allow: "*",
Reset: "/clear-cache",
Spoof: "/@able",
- Serve(inReq, inURL, inExt, inMap, inConfig){},
+ async Serve(inReq, inURL, inExt, inMap, inConfig)
+ {
+ if( inReq.headers.get("user-agent")?.startsWith("Deno") || inURL.searchParams.get("deno") )
+ {
+ const file = await fetch(inConfig.Proxy + inURL.pathname);
+ const text = await file.text();
+ return new Response(Transpile.Patch(text, "deno-"+inURL.pathname, inMap), {headers:{"content-type":"application/javascript"}} );
+ }
+ },
Remap: (inImports, inConfig)=>
{
const reactURL = inImports["react"];
@@ -89,6 +97,9 @@ let Configuration:Configuration =
},
SWCOp:
{
+ env:{
+ dynamicImport:false
+ },
sourceMaps: false,
minify: true,
jsc:
@@ -127,6 +138,77 @@ export const Transpile =
ImportMapReload();
return size;
},
+ /**
+ * Converts dynamic module imports in to static, also can resolve paths with an import map
+ */
+ Patch(inText:string, inKey:string|false = false, inMap?:DenoConfig)
+ {
+
+ if(inKey)
+ {
+ const check = this.Cache.get(inKey);
+ if(check)
+ {
+ return check;
+ }
+ }
+
+ const remap = inMap ? (inPath:string)=>
+ {
+ const match = inMap.imports[inPath];
+ if(match)
+ {
+ return match;
+ }
+ else if(inPath.includes("/"))
+ {
+ let bestKey = "";
+ let bestLength = 0;
+ Object.keys(inMap.imports).forEach((key, i, arr)=>
+ {
+ if(key.endsWith("/") && inPath.startsWith(key) && key.length > bestLength)
+ {
+ bestKey = key;
+ bestLength = bestLength;
+ }
+ });
+ if(bestKey)
+ {
+ return inMap.imports[bestKey]+inPath.substring(bestKey.length);
+ }
+ }
+ return inPath;
+ }
+ : (inPath:string)=>inPath;
+ let match, regex;
+ let convertedBody = inText;
+
+ // remap static imports
+ regex = /from\s+(['"`])(.*?)\1/g;
+ while ((match = regex.exec(inText)))
+ {
+ const importStatement = match[0];
+ const importPath = match[2];
+ convertedBody = convertedBody.replace(importStatement, `from "${remap(importPath)}"`);
+ }
+
+ // convert dynamic imports into static (to work around deno deploy)
+ const staticImports = [];
+ regex = /(?