/* HelloDom — exercises every public surface of the DOM partition that absorbed Crafter.CppDOM: * `Window` as a page-level event hub (no rendering — that's V2 / WebGPU). * `HtmlElement::CreateInBody` for element creation (new in this lib; CppDOM was query-only). * `HtmlElementPtr::Add*Listener` with auto-cleanup on destruction. * `Router::PushState` / `AddPopStateListener` / `GetPath` driving a real SPA — each route swaps the main content area without a server round-trip. Serve the bin dir over HTTP, open index.html, navigate between Home / About via the in-page links, hit back/forward, watch mouse/keyboard events log into the box at the bottom. */ import Crafter.Graphics; import Crafter.Event; import std; using namespace Crafter; // ─── SPA scaffolding ────────────────────────────────────────────────── // The full app shape: // // [ nav: Home | About ] // ──────────────────── // [ main — replaced per route ] // ──────────────────── // [ log — append-only event trace ] // // `static` storage on the C++ side keeps the HtmlElements alive for the // page's whole lifetime (their destructors would otherwise yank the // nodes out of the DOM and unregister listeners when main() returns). // Window itself is also `static` because the JS bridge stashes a raw // pointer to it on construction; stack-local would leave that pointer // dangling as soon as main returned. static std::string log; static void Append(std::string_view line) { log += std::string(line) + "\n"; Dom::HtmlElementPtr pre("hello-log"); pre.SetInnerHTML(log); } // Elements scoped to the currently-rendered route. `HtmlElementPtr` // auto-removes its registered listeners when destroyed (the V1 fix // for CppDOM's silent-leak class of bug), so any element we want a // click handler on must outlive `Render()` — otherwise the destructor // kicks in immediately and the listener dies before the user can click. // Clearing this list at the top of every Render() detaches the previous // route's bindings; pushing into it within a branch keeps the new // route's bindings alive until the next navigation. static std::vector routeBindings; static void Render(std::string_view path) { routeBindings.clear(); Dom::HtmlElementPtr main("hello-main"); if (path == "/" || path.empty()) { main.SetInnerHTML(R"(

Home

This is the home route. Click About above to navigate without reloading.

)"); Dom::HtmlElementPtr& btn = routeBindings.emplace_back("hello-btn"); btn.AddClickListener([](Dom::MouseEvent e) { Append(std::format("button click @ ({:.0f},{:.0f})", e.clientX, e.clientY)); }); } else if (path == "/about") { main.SetInnerHTML(R"(

About

HelloDom is the demo for Crafter.Graphics's CRAFTER_GRAPHICS_WINDOW_DOM mode. The whole page — content, routing, event handling — is compiled C++ running in WebAssembly.

)"); } else { main.SetInnerHTML(std::format(R"(

Not found

No route registered for {}.

)", path)); } Append(std::format("rendered route: {}", path)); } // Used by both the nav click handlers and (indirectly) the popstate // listener. Mutates browser history, then re-renders. static void Navigate(std::string_view url) { Router::PushState("{}", "", url); Render(url); } int main() { Device::Initialize(); static Window window(0, 0, "HelloDom"); // Build the persistent page chrome. The route links use plain // (no ) so the browser does NOT // navigate when they're clicked — only our handler runs. This is // the canonical SPA pattern; if you want right-click-open-in-new-tab // you'd switch to and call preventDefault in a custom JS // intercept (not exposed in V1). static Dom::HtmlElement root = Dom::HtmlElement::CreateInBody("div", "hello-root"); root.SetStyle("font-family:system-ui,sans-serif;padding:24px;max-width:640px;"); root.SetInnerHTML(R"(

Event log


    )");

    // Nav link click handlers. `static` so they outlive main(); the
    // s themselves are persistent (we never SetInnerHTML on
    //