/* 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 */ // Browser DOM bindings — absorbed from Crafter.CppDOM. Only meaningful // when the build defined CRAFTER_GRAPHICS_WINDOW_DOM (i.e. a wasm32-* // target paired with additional/dom-env.js); on native platforms the // partition still parses but every method links to nothing because the // build excludes the impl. project.cpp's per-target module set is what // makes the partition disappear from the native libs. // // Two-class hierarchy (simplified from CppDOM's three-class design): // HtmlElementPtr — non-owning element reference. Tracks every handler // it registered and clears them on destruction, so // the silent-leak bug from CppDOM's bare-pointer // flavour can't happen. Releases the JS handle on // destruction. // HtmlElement — `HtmlElementPtr` plus element ownership: removes // the element from the DOM on destruction. The new // `Create` factory builds a fresh element under a // parent and returns one of these. // // Move-only throughout. The JS handle (`ptr`) of a moved-from instance // is zeroed so the destructor of the moved-from carcass is a no-op. export module Crafter.Graphics:Dom; #ifdef CRAFTER_GRAPHICS_WINDOW_DOM import std; import :DomEvents; export namespace Crafter::Dom { class HtmlElementPtr { public: // Opaque JS-side element cookie. 0 = "no element"; the destructor // treats 0 as "moved-from, nothing to release". std::int32_t ptr; // Look up an existing element by its DOM id. HtmlElementPtr(const std::string_view id); // Look up + replace innerHTML in one step (matches CppDOM). HtmlElementPtr(const std::string_view id, const std::string_view html); protected: // Adopt a JS handle directly (used by HtmlElement::Create — the // JS side has already minted the handle, no second lookup needed). // Tagged-type ctor so the public id-based overloads stay // unambiguous from the call site. struct FromHandle { std::int32_t handle; }; explicit HtmlElementPtr(FromHandle h) noexcept : ptr(h.handle) {} public: // Move-only — copying would silently double-free the JS handle. HtmlElementPtr(HtmlElementPtr&&) noexcept; HtmlElementPtr& operator=(HtmlElementPtr&&) noexcept; HtmlElementPtr(const HtmlElementPtr&) = delete; HtmlElementPtr& operator=(const HtmlElementPtr&) = delete; ~HtmlElementPtr(); // DOM ops ───────────────────────────────────────────────────── void SetInnerHTML(const std::string_view html); void SetStyle(const std::string_view style); void SetProperty(const std::string_view property, const std::string_view value); void AddClass(const std::string_view className); void RemoveClass(const std::string_view className); void ToggleClass(const std::string_view className); bool HasClass(const std::string_view className); std::string GetValue(); void SetValue(const std::string_view value); // Listener API — each Add* returns an opaque id that can be // passed to the matching Remove*. The destructor automatically // removes every handler still registered, so manual removal is // optional. Returns 0 only if registration failed at the JS // boundary (the element was already collected). The 23 event // types are 1:1 with CppDOM's surface. std::int32_t AddClickListener(std::function callback); void RemoveClickListener(std::int32_t id); std::int32_t AddMouseOverListener(std::function callback); void RemoveMouseOverListener(std::int32_t id); std::int32_t AddMouseOutListener(std::function callback); void RemoveMouseOutListener(std::int32_t id); std::int32_t AddMouseMoveListener(std::function callback); void RemoveMouseMoveListener(std::int32_t id); std::int32_t AddMouseDownListener(std::function callback); void RemoveMouseDownListener(std::int32_t id); std::int32_t AddMouseUpListener(std::function callback); void RemoveMouseUpListener(std::int32_t id); std::int32_t AddFocusListener(std::function callback); void RemoveFocusListener(std::int32_t id); std::int32_t AddBlurListener(std::function callback); void RemoveBlurListener(std::int32_t id); std::int32_t AddKeyDownListener(std::function callback); void RemoveKeyDownListener(std::int32_t id); std::int32_t AddKeyUpListener(std::function callback); void RemoveKeyUpListener(std::int32_t id); std::int32_t AddKeyPressListener(std::function callback); void RemoveKeyPressListener(std::int32_t id); std::int32_t AddChangeListener(std::function callback); void RemoveChangeListener(std::int32_t id); std::int32_t AddSubmitListener(std::function callback); void RemoveSubmitListener(std::int32_t id); std::int32_t AddInputListener(std::function callback); void RemoveInputListener(std::int32_t id); std::int32_t AddResizeListener(std::function callback); void RemoveResizeListener(std::int32_t id); std::int32_t AddScrollListener(std::function callback); void RemoveScrollListener(std::int32_t id); std::int32_t AddContextMenuListener(std::function callback); void RemoveContextMenuListener(std::int32_t id); std::int32_t AddDragStartListener(std::function callback); void RemoveDragStartListener(std::int32_t id); std::int32_t AddDragEndListener(std::function callback); void RemoveDragEndListener(std::int32_t id); std::int32_t AddDropListener(std::function callback); void RemoveDropListener(std::int32_t id); std::int32_t AddDragOverListener(std::function callback); void RemoveDragOverListener(std::int32_t id); std::int32_t AddDragEnterListener(std::function callback); void RemoveDragEnterListener(std::int32_t id); std::int32_t AddDragLeaveListener(std::function callback); void RemoveDragLeaveListener(std::int32_t id); std::int32_t AddWheelListener(std::function callback); void RemoveWheelListener(std::int32_t id); protected: // Per-event-kind handler-id lists. Each `Add*Listener` push_backs // the id it returns; `RemoveAllHandlers` walks every list and // calls the matching JS Remove on each id. The lists are indexed // by event kind so a single handler-id collision across kinds // (allowed by the JS bridge — counters are per-event-kind) // doesn't cause a wrong-kind remove. std::vector handlerIds_[24]; // Shared cleanup used by the destructor AND by move-assignment. // Removes every registered handler from both the C++ map and // the JS side, then frees the JS handle. Leaves `ptr == 0` on // return so a second call is a no-op. void RemoveAllHandlersAndFree(); }; class HtmlElement : public HtmlElementPtr { public: // Adopt an existing element by id; the dtor will remove it from // the DOM. Useful when the caller created the element in markup // and wants C++ to own its lifetime from now on. HtmlElement(const std::string_view id); HtmlElement(const std::string_view id, const std::string_view html); HtmlElement(HtmlElement&&) noexcept; HtmlElement& operator=(HtmlElement&&) noexcept; ~HtmlElement(); // Create a new element and append it to `parent`. Optional `id` // sets the new element's DOM id (use it when you want to look the // element up from non-C++ code later); empty leaves the element // unnamed. Returns an owning handle — the element is removed from // the DOM when this `HtmlElement` is destroyed. static HtmlElement Create(const HtmlElementPtr& parent, std::string_view tagName, std::string_view id = {}); // Convenience: create a top-level element under document.body. static HtmlElement CreateInBody(std::string_view tagName, std::string_view id = {}); private: // Hidden ctor used by Create/CreateInBody — takes an already // allocated JS handle via the base class FromHandle tag. Kept // private so user code doesn't construct an HtmlElement from a // raw handle by accident. explicit HtmlElement(HtmlElementPtr::FromHandle h) noexcept : HtmlElementPtr(h) {} }; } #endif // CRAFTER_GRAPHICS_WINDOW_DOM