/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // JS bridge for the CRAFTER_GRAPHICS_WINDOW_DOM build of Crafter.Graphics. // Populates `window.crafter_webbuild_env` (same global as Crafter.CppDOM // used) with the env imports the .wasm declares. Crafter.Build's // runtime.js merges this object into the WebAssembly import object as // the `env` module before instantiation. // // Three groups of imports: // 1. DOM ops + 23 element-scoped listener kinds (port of CppDOM env.js, // plus createElement / getBody for the new element-creation API). // 2. Window-level event hookup — DOM events delivered to a global // Window object on the C++ side. Set up when the C++ Window ctor // calls domAttachWindow(). // 3. requestAnimationFrame driver — domStartFrameLoop / domStopFrameLoop. const __decoder = new TextDecoder(); const __encoder = new TextEncoder(); // jsmemory: integer cookie → live DOM element. Counter is monotone; // 32-bit wrap is unlikely over a single page's lifetime but if it // ever matters we'd switch to a free-list. let __cookieCounter = 0; const __jsmemory = new Map(); const __listenerHandlers = new Map(); // `${ptr}-${id}-${kind}` → JS handler // Window-level (document / window) listeners installed by domAttachWindow. // Indexed by event kind; each entry is the JS handler we'd remove if the // user ever destroyed the Window. V1 only ever has one Window so we don't // support tear-down; the listeners live for the page's lifetime. const __windowListeners = {}; let __windowAttachedHandle = 0; // cookie of the C++ Window for export dispatch let __frameLoopActive = false; function __wasm() { return window.crafter_webbuild_wasi.instance.exports; } function __memBuf() { return window.crafter_webbuild_wasi.instance.exports.memory.buffer; } function __readUtf8(ptr, len) { return __decoder.decode(new Uint8Array(__memBuf(), ptr, len)); } function __writeUtf8(str) { const encoded = __encoder.encode(str + '\0'); const ptr = __wasm().WasmAlloc(encoded.length); new Uint8Array(__memBuf(), ptr, encoded.length).set(encoded); return ptr; } function __storeElement(el) { __jsmemory.set(++__cookieCounter, el); return __cookieCounter; } // ─── DOM lookup / creation ──────────────────────────────────────────── function getElementById(idPtr, idLen) { try { const id = __readUtf8(idPtr, idLen); const el = document.getElementById(id); if (!el) { console.error(`Crafter.Dom: getElementById("${id}") → null`); return 0; } return __storeElement(el); } catch (err) { console.error(err); return 0; } } function createElement(parentCookie, tagPtr, tagLen, idPtr, idLen) { try { const tag = __readUtf8(tagPtr, tagLen); const el = document.createElement(tag); if (idLen > 0) { el.id = __readUtf8(idPtr, idLen); } const parent = __jsmemory.get(parentCookie); if (parent) { parent.appendChild(el); } else { // parent cookie 0 / not-found → append to body so the element // still ends up in the DOM (matches CreateInBody semantics // when the user passes a moved-from / default-constructed // HtmlElementPtr). document.body.appendChild(el); } return __storeElement(el); } catch (err) { console.error(err); return 0; } } function getBody() { return __storeElement(document.body); } function freeJs(cookie) { __jsmemory.delete(cookie); } // ─── DOM mutations ──────────────────────────────────────────────────── function setInnerHTML(cookie, htmlPtr, htmlLen) { const el = __jsmemory.get(cookie); if (el) el.innerHTML = __readUtf8(htmlPtr, htmlLen); } function setStyle(cookie, stylePtr, styleLen) { const el = __jsmemory.get(cookie); if (el) el.style.cssText = __readUtf8(stylePtr, styleLen); } function setProperty(cookie, propPtr, propLen, valPtr, valLen) { const el = __jsmemory.get(cookie); if (el) el.style.setProperty(__readUtf8(propPtr, propLen), __readUtf8(valPtr, valLen)); } function addClass(cookie, namePtr, nameLen) { const el = __jsmemory.get(cookie); if (el) el.classList.add(__readUtf8(namePtr, nameLen)); } function removeClass(cookie, namePtr, nameLen) { const el = __jsmemory.get(cookie); if (el) el.classList.remove(__readUtf8(namePtr, nameLen)); } function toggleClass(cookie, namePtr, nameLen) { const el = __jsmemory.get(cookie); if (el) el.classList.toggle(__readUtf8(namePtr, nameLen)); } function hasClass(cookie, namePtr, nameLen) { const el = __jsmemory.get(cookie); if (!el) return false; return el.classList.contains(__readUtf8(namePtr, nameLen)); } function deleteElement(cookie) { const el = __jsmemory.get(cookie); if (el && el.parentNode) el.parentNode.removeChild(el); } function getValue(cookie) { const el = __jsmemory.get(cookie); if (!el || el.value === undefined) return 0; return __writeUtf8(el.value || ""); } function setValue(cookie, valPtr, valLen) { const el = __jsmemory.get(cookie); if (el) el.value = __readUtf8(valPtr, valLen); } // ─── Element-scoped event listeners ─────────────────────────────────── // // Each Add*Listener factory generates an addEventListener call whose // handler marshals the event fields through to a specific C++ export. // We key the handler in __listenerHandlers by `${cookie}-${id}-${kind}` // so removeEventListener can re-find it. C++-side handler id counters // are per-kind, so a per-kind suffix is what makes the keys unique. // devicePixelRatio scaling factor. dom-webgpu.js sets window.crafter_dpr // during its canvas sync so this side and the GPU side agree on a single // physical-pixel coordinate space. Fallback to the live DPR if no GPU // bridge ran (pure-CppDOM apps); ultimately fallback to 1 so non-HiDPI // browsers behave as before. function __dpr() { return window.crafter_dpr || window.devicePixelRatio || 1; } function __makeMouseListenerPair(kind, eventName, exportName) { return { add(cookie, id) { const el = __jsmemory.get(cookie); if (!el) return; const handler = (event) => { const s = __dpr(); __wasm()[exportName](id, event.clientX * s, event.clientY * s, event.screenX * s, event.screenY * s, event.button, event.buttons, event.altKey, event.ctrlKey, event.shiftKey, event.metaKey); }; __listenerHandlers.set(`${cookie}-${id}-${kind}`, handler); el.addEventListener(eventName, handler); }, remove(cookie, id) { const el = __jsmemory.get(cookie); const key = `${cookie}-${id}-${kind}`; const handler = __listenerHandlers.get(key); if (el && handler) el.removeEventListener(eventName, handler); __listenerHandlers.delete(key); } }; } function __makeKeyListenerPair(kind, eventName, exportName) { return { add(cookie, id) { const el = __jsmemory.get(cookie); if (!el) return; const handler = (event) => { const keyPtr = __writeUtf8(event.key || ""); __wasm()[exportName](id, keyPtr, event.keyCode, event.altKey, event.ctrlKey, event.shiftKey, event.metaKey); __wasm().WasmFree(keyPtr); }; __listenerHandlers.set(`${cookie}-${id}-${kind}`, handler); el.addEventListener(eventName, handler); }, remove(cookie, id) { const el = __jsmemory.get(cookie); const key = `${cookie}-${id}-${kind}`; const handler = __listenerHandlers.get(key); if (el && handler) el.removeEventListener(eventName, handler); __listenerHandlers.delete(key); } }; } const __mouse_Click = __makeMouseListenerPair("click", "click", "ExecuteClickHandler"); const __mouse_MouseOver = __makeMouseListenerPair("mouseover", "mouseover", "ExecuteMouseOverHandler"); const __mouse_MouseOut = __makeMouseListenerPair("mouseout", "mouseout", "ExecuteMouseOutHandler"); const __mouse_MouseMove = __makeMouseListenerPair("mousemove", "mousemove", "ExecuteMouseMoveHandler"); const __mouse_MouseDown = __makeMouseListenerPair("mousedown", "mousedown", "ExecuteMouseDownHandler"); const __mouse_MouseUp = __makeMouseListenerPair("mouseup", "mouseup", "ExecuteMouseUpHandler"); const __mouse_ContextMenu = __makeMouseListenerPair("contextmenu", "contextmenu", "ExecuteContextMenuHandler"); const __mouse_DragStart = __makeMouseListenerPair("dragstart", "dragstart", "ExecuteDragStartHandler"); const __mouse_DragEnd = __makeMouseListenerPair("dragend", "dragend", "ExecuteDragEndHandler"); const __mouse_Drop = __makeMouseListenerPair("drop", "drop", "ExecuteDropHandler"); const __mouse_DragOver = __makeMouseListenerPair("dragover", "dragover", "ExecuteDragOverHandler"); const __mouse_DragEnter = __makeMouseListenerPair("dragenter", "dragenter", "ExecuteDragEnterHandler"); const __mouse_DragLeave = __makeMouseListenerPair("dragleave", "dragleave", "ExecuteDragLeaveHandler"); const __key_KeyDown = __makeKeyListenerPair("keydown", "keydown", "ExecuteKeyDownHandler"); const __key_KeyUp = __makeKeyListenerPair("keyup", "keyup", "ExecuteKeyUpHandler"); const __key_KeyPress = __makeKeyListenerPair("keypress", "keypress", "ExecuteKeyPressHandler"); function __makeFocusListenerPair(kind, eventName, exportName) { return { add(cookie, id) { const el = __jsmemory.get(cookie); if (!el) return; const handler = (event) => { const tCookie = event.target ? __storeElement(event.target) : 0; const rCookie = event.relatedTarget ? __storeElement(event.relatedTarget) : 0; __wasm()[exportName](id, tCookie, rCookie); }; __listenerHandlers.set(`${cookie}-${id}-${kind}`, handler); el.addEventListener(eventName, handler); }, remove(cookie, id) { const el = __jsmemory.get(cookie); const key = `${cookie}-${id}-${kind}`; const handler = __listenerHandlers.get(key); if (el && handler) el.removeEventListener(eventName, handler); __listenerHandlers.delete(key); } }; } const __focusPair = __makeFocusListenerPair("focus", "focus", "ExecuteFocusHandler"); const __blurPair = __makeFocusListenerPair("blur", "blur", "ExecuteBlurHandler"); // Change / Input / Submit / Resize / Scroll have bespoke marshaling. const __changePair = { add(cookie, id) { const el = __jsmemory.get(cookie); if (!el) return; const handler = (event) => { const p = __writeUtf8(event.target.value || ""); __wasm().ExecuteChangeHandler(id, p); __wasm().WasmFree(p); }; __listenerHandlers.set(`${cookie}-${id}-change`, handler); el.addEventListener("change", handler); }, remove(cookie, id) { const el = __jsmemory.get(cookie); const key = `${cookie}-${id}-change`; const h = __listenerHandlers.get(key); if (el && h) el.removeEventListener("change", h); __listenerHandlers.delete(key); } }; const __inputPair = { add(cookie, id) { const el = __jsmemory.get(cookie); if (!el) return; const handler = (event) => { const text = event.data || (event.target && event.target.value) || ""; const p = __writeUtf8(text); __wasm().ExecuteInputHandler(id, p, event.inputType === 'insertCompositionText'); __wasm().WasmFree(p); }; __listenerHandlers.set(`${cookie}-${id}-input`, handler); el.addEventListener("input", handler); }, remove(cookie, id) { const el = __jsmemory.get(cookie); const key = `${cookie}-${id}-input`; const h = __listenerHandlers.get(key); if (el && h) el.removeEventListener("input", h); __listenerHandlers.delete(key); } }; const __submitPair = { add(cookie, id) { const el = __jsmemory.get(cookie); if (!el) return; const handler = (event) => { event.preventDefault(); __wasm().ExecuteSubmitHandler(id); }; __listenerHandlers.set(`${cookie}-${id}-submit`, handler); el.addEventListener("submit", handler); }, remove(cookie, id) { const el = __jsmemory.get(cookie); const key = `${cookie}-${id}-submit`; const h = __listenerHandlers.get(key); if (el && h) el.removeEventListener("submit", h); __listenerHandlers.delete(key); } }; const __resizePair = { // Resize is window-global in CppDOM. Mirror that: attach to `window` // regardless of which element the C++ caller passed. add(cookie, id) { const handler = () => { const s = __dpr(); __wasm().ExecuteResizeHandler(id, window.innerWidth * s, window.innerHeight * s); }; __listenerHandlers.set(`${cookie}-${id}-resize`, handler); window.addEventListener("resize", handler); }, remove(cookie, id) { const key = `${cookie}-${id}-resize`; const h = __listenerHandlers.get(key); if (h) window.removeEventListener("resize", h); __listenerHandlers.delete(key); } }; const __scrollPair = { add(cookie, id) { const handler = () => __wasm().ExecuteScrollHandler(id, window.scrollX, window.scrollY); __listenerHandlers.set(`${cookie}-${id}-scroll`, handler); window.addEventListener("scroll", handler); }, remove(cookie, id) { const key = `${cookie}-${id}-scroll`; const h = __listenerHandlers.get(key); if (h) window.removeEventListener("scroll", h); __listenerHandlers.delete(key); } }; const __wheelPair = { add(cookie, id) { const el = __jsmemory.get(cookie); if (!el) return; const handler = (event) => { const s = __dpr(); __wasm().ExecuteWheelHandler(id, event.deltaX, event.deltaY, event.deltaZ, event.deltaMode, event.clientX * s, event.clientY * s, event.screenX * s, event.screenY * s, event.button, event.buttons, event.altKey, event.ctrlKey, event.shiftKey, event.metaKey); }; __listenerHandlers.set(`${cookie}-${id}-wheel`, handler); el.addEventListener("wheel", handler); }, remove(cookie, id) { const el = __jsmemory.get(cookie); const key = `${cookie}-${id}-wheel`; const h = __listenerHandlers.get(key); if (el && h) el.removeEventListener("wheel", h); __listenerHandlers.delete(key); } }; // ─── Window-level event hookup ──────────────────────────────────────── // // Called by the C++ Window ctor (`domAttachWindow`). Installs document / // window listeners that route into Window exports (`__crafterDom_*`). // Only one Window per page in V1 — multiple Attach calls overwrite the // previous attachment. function domAttachWindow(windowHandle) { __windowAttachedHandle = windowHandle; const fire = (name, args) => { const fn = __wasm()[name]; if (fn) fn(__windowAttachedHandle, ...args); }; // Synthetic absolute position for pointer-lock mode. While the // pointer is locked, browsers fire mousemove events with movementX/Y // deltas instead of meaningful clientX/Y, and the cursor is hidden + // captured by the canvas (no window-edge clamp). We accumulate the // deltas into a synthetic position and feed *that* to the C++ side, // so the existing `currentMousePos - lastMousePos` delta computation // keeps working unchanged. Initialised to the cursor position the // moment lock is acquired. let __ptrLockSyntheticX = 0; let __ptrLockSyntheticY = 0; const __isPointerLocked = () => document.pointerLockElement !== null && document.pointerLockElement !== undefined; // pointermove (not mousemove) so we can pull sub-frame events out of // `getCoalescedEvents()`. Browsers normally collapse multiple raw // mouse events between paint frames into a single event you'd see // via `mousemove`; PointerEvent.getCoalescedEvents() returns the raw // pre-coalesced list. Summing those gives a higher-resolution delta // per frame than the single coalesced movementX/Y. PointerEvent also // delivers fractional movementX from high-precision mice on Chromium. __windowListeners.mousemove = (e) => { const s = __dpr(); const locked = __isPointerLocked(); if (locked) { // Accumulate over every sub-frame event the browser had // queued up. `getCoalescedEvents` is the spec-correct way // to access raw input between rAF ticks. Some browsers // return an empty list — fall back to the top-level event. let dx = 0, dy = 0; const sub = (typeof e.getCoalescedEvents === "function") ? e.getCoalescedEvents() : null; if (sub && sub.length > 0) { for (let i = 0; i < sub.length; i++) { dx += sub[i].movementX; dy += sub[i].movementY; } } else { dx = e.movementX; dy = e.movementY; } // No DPR scaling in pointer-lock: position is synthetic and // there's no UI hit-test using it. DPR-scaling here only // rounds finer movements up to multiples of `dpr`, which is // pure quantization loss for aim controls. __ptrLockSyntheticX += dx; __ptrLockSyntheticY += dy; fire("__crafterDom_mouseMove", [__ptrLockSyntheticX, __ptrLockSyntheticY]); } else { fire("__crafterDom_mouseMove", [e.clientX * s, e.clientY * s]); } }; __windowListeners.mousedown = (e) => { // Right-click holds engage pointer lock — typical FPS-camera // convention. Acquiring on any click (the previous policy) made // menus annoying: clicking a button hid the cursor mid-flow. Now // the cursor stays free for clicks/menus until the user holds // RMB to actively look around. Browsers require lock requests // from user gestures, which mousedown satisfies. if (e.button === 2 && !__isPointerLocked()) { const target = document.body; if (target && target.requestPointerLock) { target.requestPointerLock(); // Seed the synthetic position from the click point so // there's no jump when the lock starts producing deltas. __ptrLockSyntheticX = e.clientX; __ptrLockSyntheticY = e.clientY; } } fire("__crafterDom_mouseDown", [e.button]); }; __windowListeners.mouseup = (e) => { // Release lock on RMB up — cursor reappears at the seed point // for clicks/menus until the next RMB hold. if (e.button === 2 && __isPointerLocked()) { document.exitPointerLock(); } fire("__crafterDom_mouseUp", [e.button]); }; __windowListeners.wheel = (e) => fire("__crafterDom_wheel", [e.deltaY]); __windowListeners.contextmenu = (e) => { e.preventDefault(); }; __windowListeners.pointerlockchange = () => { // Reset the synthetic accumulator when lock is released so the // next acquisition starts cleanly. The C++ side will see one // small jump back to the real cursor position on release. if (!__isPointerLocked()) { __ptrLockSyntheticX = 0; __ptrLockSyntheticY = 0; } }; // Keyboard events go through the document so they fire even when no // input element is focused. event.code is the layout-independent // physical key identifier — matches what the C++ Keys table hashes. __windowListeners.keydown = (e) => { const codePtr = __writeUtf8(e.code || ""); const keyPtr = __writeUtf8(e.key || ""); fire("__crafterDom_keyDown", [codePtr, e.code.length, keyPtr, e.key.length, e.repeat]); __wasm().WasmFree(codePtr); __wasm().WasmFree(keyPtr); }; __windowListeners.keyup = (e) => { const codePtr = __writeUtf8(e.code || ""); fire("__crafterDom_keyUp", [codePtr, e.code.length]); __wasm().WasmFree(codePtr); }; __windowListeners.resize = () => { const s = __dpr(); fire("__crafterDom_resize", [window.innerWidth * s, window.innerHeight * s]); }; __windowListeners.beforeunload = () => fire("__crafterDom_close", []); // pointermove (not mousemove) so the handler receives PointerEvents // and can use getCoalescedEvents() to recover sub-frame motion. The // handler's variable name stays "mousemove" — it's the same JS object, // just bound to a different event type. document.addEventListener("pointermove", __windowListeners.mousemove); document.addEventListener("mousedown", __windowListeners.mousedown); document.addEventListener("mouseup", __windowListeners.mouseup); document.addEventListener("wheel", __windowListeners.wheel); document.addEventListener("contextmenu", __windowListeners.contextmenu); document.addEventListener("keydown", __windowListeners.keydown); document.addEventListener("keyup", __windowListeners.keyup); document.addEventListener("pointerlockchange", __windowListeners.pointerlockchange); window .addEventListener("resize", __windowListeners.resize); window .addEventListener("beforeunload",__windowListeners.beforeunload); } function domSetTitle(titlePtr, titleLen) { document.title = __readUtf8(titlePtr, titleLen); } function domGetInnerWidth() { return Math.round(window.innerWidth * __dpr()); } function domGetInnerHeight() { return Math.round(window.innerHeight * __dpr()); } // ─── requestAnimationFrame loop ─────────────────────────────────────── function __frameTick() { if (!__frameLoopActive) return; const fn = __wasm().__crafterDom_frame; if (fn) fn(__windowAttachedHandle); window.requestAnimationFrame(__frameTick); } function domStartFrameLoop() { if (__frameLoopActive) return; __frameLoopActive = true; window.requestAnimationFrame(__frameTick); } function domStopFrameLoop() { __frameLoopActive = false; } // ─── Clipboard ──────────────────────────────────────────────────────── // // Read path is necessarily a latched buffer: navigator.clipboard.readText // is async (Promise) but the C++ Clipboard::GetText signature is // synchronous to match the Wayland / Win32 backends. Two feeders keep // the cache fresh: // // 1. A capture-phase `paste` listener on `window` reads // event.clipboardData.getData('text/plain') synchronously — this // covers the standard Ctrl+V flow, where the paste event fires // in the same task as the C++ key handler that will call GetText. // 2. Each GetText call also kicks off a navigator.clipboard.readText // in the background. The promise resolves in a later task; the // first call usually still returns the latched paste value (or // nullopt), subsequent calls see the readText result. let __clipboardCache = null; window.addEventListener("paste", (event) => { try { const t = event.clipboardData && event.clipboardData.getData("text/plain"); if (typeof t === "string") __clipboardCache = t; } catch (err) { // Some browsers reject clipboardData access inside non-input // targets; the async readText fallback still has a chance. } }, true); function clipboardSetText(strPtr, strLen) { try { navigator.clipboard.writeText(__readUtf8(strPtr, strLen)); return true; } catch (err) { console.error("Crafter.Clipboard.SetText failed:", err); return false; } } function clipboardGetText() { // Best-effort async refresh. Permission/focus rejections are // expected in non-activated contexts — swallow them so we don't // spam the console on every paste check. if (navigator.clipboard && navigator.clipboard.readText) { navigator.clipboard.readText() .then((t) => { if (typeof t === "string") __clipboardCache = t; }) .catch(() => {}); } if (__clipboardCache === null) return 0; return __writeUtf8(__clipboardCache); } // ─── History / routing ──────────────────────────────────────────────── function pushState(dataPtr, dataLen, titlePtr, titleLen, urlPtr, urlLen) { const dataStr = __readUtf8(dataPtr, dataLen); const titleStr = __readUtf8(titlePtr, titleLen); const urlStr = __readUtf8(urlPtr, urlLen); let parsed; try { parsed = JSON.parse(dataStr); } catch { parsed = null; } window.history.pushState(parsed, titleStr, urlStr); } function addPopStateListener(id) { const handler = () => __wasm().ExecutePopStateHandler(id); __listenerHandlers.set(`popstate-${id}`, handler); window.addEventListener("popstate", handler); } function removePopStateListener(id) { const h = __listenerHandlers.get(`popstate-${id}`); if (h) window.removeEventListener("popstate", h); __listenerHandlers.delete(`popstate-${id}`); } function getPathName() { return __writeUtf8(window.location.pathname); } // ─── Gamepad polling helper ─────────────────────────────────────────── // // V1: a minimal polling shim used by the C++ Gamepad backend. Returns // the navigator.getGamepads() result reshaped into a flat layout the // C++ side can read out via getter calls. Hot-plug events are exposed // as a flag the C++ side polls during Tick. let __gamepadConnectedFlag = false; let __gamepadDisconnectedFlag = false; window.addEventListener("gamepadconnected", () => { __gamepadConnectedFlag = true; }); window.addEventListener("gamepaddisconnected", () => { __gamepadDisconnectedFlag = true; }); function gamepadPollConnected() { const v = __gamepadConnectedFlag; __gamepadConnectedFlag = false; return v; } function gamepadPollDisconnected() { const v = __gamepadDisconnectedFlag; __gamepadDisconnectedFlag = false; return v; } function gamepadCount() { return (navigator.getGamepads ? navigator.getGamepads() : []).filter(g => g).length; } function gamepadGetButton(idx, buttonIdx) { const pads = navigator.getGamepads ? navigator.getGamepads() : []; const p = pads.filter(g => g)[idx]; return p && p.buttons[buttonIdx] ? (p.buttons[buttonIdx].pressed ? 1 : 0) : 0; } function gamepadGetAxis(idx, axisIdx) { const pads = navigator.getGamepads ? navigator.getGamepads() : []; const p = pads.filter(g => g)[idx]; return p && typeof p.axes[axisIdx] === "number" ? p.axes[axisIdx] : 0; } // ─── Export env object ──────────────────────────────────────────────── if (!window.crafter_webbuild_env) { window.crafter_webbuild_env = {}; } Object.assign(window.crafter_webbuild_env, { // DOM lookup / creation / mutation freeJs, getElementById, createElement, getBody, setInnerHTML, setStyle, setProperty, addClass, removeClass, toggleClass, hasClass, deleteElement, getValue, setValue, // Element listeners — pair add/remove addClickListener: __mouse_Click.add, removeClickListener: __mouse_Click.remove, addMouseOverListener: __mouse_MouseOver.add, removeMouseOverListener: __mouse_MouseOver.remove, addMouseOutListener: __mouse_MouseOut.add, removeMouseOutListener: __mouse_MouseOut.remove, addMouseMoveListener: __mouse_MouseMove.add, removeMouseMoveListener: __mouse_MouseMove.remove, addMouseDownListener: __mouse_MouseDown.add, removeMouseDownListener: __mouse_MouseDown.remove, addMouseUpListener: __mouse_MouseUp.add, removeMouseUpListener: __mouse_MouseUp.remove, addContextMenuListener: __mouse_ContextMenu.add, removeContextMenuListener: __mouse_ContextMenu.remove, addDragStartListener: __mouse_DragStart.add, removeDragStartListener: __mouse_DragStart.remove, addDragEndListener: __mouse_DragEnd.add, removeDragEndListener: __mouse_DragEnd.remove, addDropListener: __mouse_Drop.add, removeDropListener: __mouse_Drop.remove, addDragOverListener: __mouse_DragOver.add, removeDragOverListener: __mouse_DragOver.remove, addDragEnterListener: __mouse_DragEnter.add, removeDragEnterListener: __mouse_DragEnter.remove, addDragLeaveListener: __mouse_DragLeave.add, removeDragLeaveListener: __mouse_DragLeave.remove, addKeyDownListener: __key_KeyDown.add, removeKeyDownListener: __key_KeyDown.remove, addKeyUpListener: __key_KeyUp.add, removeKeyUpListener: __key_KeyUp.remove, addKeyPressListener: __key_KeyPress.add, removeKeyPressListener: __key_KeyPress.remove, addFocusListener: __focusPair.add, removeFocusListener: __focusPair.remove, addBlurListener: __blurPair.add, removeBlurListener: __blurPair.remove, addChangeListener: __changePair.add, removeChangeListener: __changePair.remove, addInputListener: __inputPair.add, removeInputListener: __inputPair.remove, addSubmitListener: __submitPair.add, removeSubmitListener: __submitPair.remove, addResizeListener: __resizePair.add, removeResizeListener: __resizePair.remove, addScrollListener: __scrollPair.add, removeScrollListener: __scrollPair.remove, addWheelListener: __wheelPair.add, removeWheelListener: __wheelPair.remove, // Window / lifecycle domAttachWindow, domSetTitle, domGetInnerWidth, domGetInnerHeight, domStartFrameLoop, domStopFrameLoop, // Clipboard clipboardSetText, clipboardGetText, // History pushState, addPopStateListener, removePopStateListener, getPathName, // Gamepad gamepadPollConnected, gamepadPollDisconnected, gamepadCount, gamepadGetButton, gamepadGetAxis, });