//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // History / SPA routing. Thin C++ wrapper over the browser's // `history.pushState` / `popstate` and `window.location.pathname` — // exists so a single-page app written against this library can manage // its own URL without going through the DOM partition. Re-homed under // the `Crafter::Router` namespace (cleaner than CppDOM's bare free // functions; symmetric with `Crafter::Gamepad`). export module Crafter.Graphics:Router; #ifdef CRAFTER_GRAPHICS_WINDOW_DOM import std; export namespace Crafter::Router { // Push a new history entry. `data` is a JSON string serialized by // the caller — the browser stores it on the entry but the popstate // listener in V1 receives no payload (matches CppDOM's surface). // `url` is browser-relative, e.g. "/blog/post-1". Cannot leave the // origin; use Navigate for that. void PushState(std::string_view data, std::string_view title, std::string_view url); // As PushState, but rewrites the current entry instead of adding one. // For URL changes that shouldn't become their own Back destination: // filter/sort state, canonicalising a sloppy incoming URL. void ReplaceState(std::string_view data, std::string_view title, std::string_view url); // Subscribe to the browser's `popstate` event (back/forward button, // programmatic history.go). Returns an opaque id usable with // `RemovePopStateListener`. Multiple subscribers OK. // // The callback still receives no payload — re-read the location with // the accessors below rather than relying on the pushState `data`. std::int32_t AddPopStateListener(std::function callback); void RemovePopStateListener(std::int32_t id); // Location accessors. Each allocates a fresh string per call — cache // the result if used in a hot path. // // GetSearch / GetHash include their leading '?' / '#', so an absent // query and a bare "?" stay distinguishable and the result can be // concatenated straight back onto a path. std::string GetPath(); // window.location.pathname std::string GetSearch(); // window.location.search, e.g. "?page=2&sort=new" std::string GetHash(); // window.location.hash, e.g. "#reviews" std::string GetHref(); // window.location.href, absolute // Full-page navigation, cross-origin allowed. PushState deliberately // cannot leave the origin, so this is the only way to hand the user off // to an external URL (a hosted payment page, an OAuth consent screen). // `replace == true` drops the current page from history, so Back won't // return to a stale page. void Navigate(std::string_view url, bool replace = false); // ─── Query-string helpers ───────────────────────────────────────── // // Pure string functions — no JS round-trip, safe to call on any // string, not just the live location. Provided here so callers don't // each hand-roll percent decoding; `+` vs `%20` is the usual bug. // Split a query string into decoded key/value pairs, in source order. // Accepts an optional leading '?'. Duplicate keys are preserved as // separate entries (?tag=a&tag=b yields two). A valueless key // ("?debug") yields an empty value. Empty segments are skipped, so // "a=1&&b=2" and a trailing '&' are tolerated. std::vector> ParseQuery(std::string_view query); // First value for `key`, or nullopt if absent. Distinguishes "missing" // from "present but empty" — which ?debug and ?q= need. std::optional QueryGet(std::string_view query, std::string_view key); // Percent-decode a single component, treating '+' as a space per // application/x-www-form-urlencoded. A malformed escape is passed // through literally rather than dropped. std::string PercentDecode(std::string_view in); } #endif // CRAFTER_GRAPHICS_WINDOW_DOM