╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ script.js ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
122 await document.fonts.ready; 123 } 124 125 // the page is embedded as base64 so it can sit inside the host html 126 // without worrying about characters that would terminate the script tag. 127 // decode here and hand the raw json bytes to the wasm. 128 const jsonBytes = Uint8Array.from(atob(pageJsonBase64), (c) => c.charCodeAt(0)); 129 const ptr = wasmInstance.exports._alloc(jsonBytes.length); 130 new Uint8Array(wasmInstance.exports.memory.buffer, ptr, jsonBytes.length).set(jsonBytes); 131 wasmInstance.exports._start(ptr, jsonBytes.length, minRows(), maxCols()); 132 133 document.addEventListener("keydown", (event) => { 134 // when an overlay input owns focus, the browser handles typing & 135 // cursor movement natively; wasm gets the new value via input events 136 // instead of per-key dispatch. don't forward, and don't intercept 137 // arrow / home / end which the input needs for cursor movement. 138 // exception: Enter triggers form submission when one is on the page. 139 if (document.activeElement && document.activeElement.tagName === "INPUT") { 140 if (event.key === "Enter") { 141 event.preventDefault(); 142 const submitSpan = grid.querySelector('[data-action="submit"]'); 143 if (submitSpan) { 144 submitForm(submitSpan.dataset.url); 145 } 146 } 147 return; 148 } 149 // a focused submit-button span (Tab can land here now that it has a 150 // tabindex) should submit on Enter the same as a click does. 151 if (event.key === "Enter" && document.activeElement && document.activeElement.dataset && document.activeElement.dataset.action === "submit") { 152 event.preventDefault(); 153 submitForm(document.activeElement.dataset.url); 154 return; 155 } 156 if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "PageUp", "PageDown", "Home", "End"].includes(event.key)) { 157 event.preventDefault(); 158 } 159 wasmInstance.exports._onKeyDown(event.keyCode); 160 wasmInstance.exports._tick(minRows(), maxCols()); 161 }); 162 163 // event delegation: each render rebuilds the input elements (innerHTML 164 // wipe), so we can't attach listeners per-input. delegating on grid 165 // catches inputs created at any point. 166 grid.addEventListener("input", (event) => { 167 const t = event.target; 168 if (!t || t.tagName !== "INPUT" || !t.dataset.focusId) return; 169 sendTextInputValue(Number(t.dataset.focusId), t.value); 170 }); 171 172 grid.addEventListener("focusin", (event) => { 173 const t = event.target; 174 if (!t || !t.dataset || !t.dataset.focusId) return; 175 wasmInstance.exports._onMouseClick(Number(t.dataset.focusId)); 176 wasmInstance.exports._tick(minRows(), maxCols()); 177 }); 178 179 // submit handled on mousedown rather than click because submit-button 180 // spans are tabbable: a mouse click on one moves focus to it, which 181 // fires focusin, which re-renders via innerHTML, which detaches the 182 // very span the click event was about to target. mousedown fires 183 // before the focus change, so the target is guaranteed to be live. 184 grid.addEventListener("mousedown", (event) => { 185 const span = event.target.closest(".clickable"); 186 if (!span || span.dataset.action !== "submit") return; 187 event.preventDefault(); 188 submitForm(span.dataset.url); 189 }); 190 191 grid.addEventListener("click", (event) => { 192 const span = event.target.closest(".clickable"); 193 if (!span) return; 194 // submit is handled in mousedown above 195 if (span.dataset.action === "submit") { 196 event.preventDefault(); 197 return; 198 } 199 const focusId = Number(span.dataset.focusId); 200 wasmInstance.exports._onMouseClick(focusId); 201 // synthesize Enter so the focused widget's "activate" handler fires 202 // (e.g. a button click runs its submit handler). widgets that don't 203 // care about Enter just ignore it. 204 wasmInstance.exports._onKeyDown(13); 205 wasmInstance.exports._tick(minRows(), maxCols()); 206 }); 207 208 let resizeTimer = null; 209 window.addEventListener("resize", () => { 210 clearTimeout(resizeTimer); 211 resizeTimer = setTimeout(() => { 212 wasmInstance.exports._tick(minRows(), maxCols()); 213 }, 100); 214 }); 215 }); 216