Skip to content

feat(input): text entry primitive + text input field #586

Description

@stormmuller

Summary

Forge has no way to accept typed text. A chat box, a name-entry prompt, a debug console, a seed field, or a search box are all currently impossible.

Raised by @stormmuller while reviewing #580, following the observation that a "text input" is really three concerns and only one of them is UI-shaped. Split out of the UI design for the same reason as text rendering (#584) and clipping (#583).

The three concerns

Concern Home Notes
Text entry — keystrokes, caret, selection, IME, clipboard, soft keyboard /src/input Where all the genuinely hard browser complexity lives. Reusable by anything needing typed input, not specific to one widget.
Text display — drawing the string, caret, and selection highlight #584 Identical to drawing any other text.
The focusable rect — hit testing, focus, styling /src/ui (#580) An interactive rectangle; nothing special.

This issue covers the first, and the thin UI component that consumes it.

Why a hidden DOM <input> rather than hand-rolled key handling

Hand-rolling from KeyboardInputSource means reimplementing:

  • IME composition — Japanese, Chinese, Korean. Candidate windows, composition state, commit events. This alone is a multi-month problem.
  • Mobile soft keyboards — they only appear in response to a real focus event on a real focusable element. There is no way to summon one from a canvas.
  • Clipboard (paste, cut), autocorrect, autocapitalize, spellcheck
  • Text selection by drag and by keyboard, word-jump modifiers, platform-specific caret conventions
  • Screen-reader accessibility

Browsers already solve every one of these correctly. This is the one place where DOM interop is the right engineering call rather than a shortcut, and it's the standard approach in browser game engines (PixiJS, Phaser).

Suggested approach

A small primitive in /src/input — roughly "give me a hidden DOM input synced to this screen rect, and tell me its value, caret, and selection":

  • Creates an off-screen or transparent <input> / <textarea>, positioned to follow a screen rect so mobile keyboards and IME candidate windows appear in the right place. Positioning needs worldToScreenSpace, already present in src/rendering/transforms/.
  • Exposes value, caret index, selection range, and composition state as plain data for a system to read.
  • Handles focus/blur, and cleans up the DOM node on teardown.
  • Emits change/submit/cancel as InputActions where it makes sense, consistent with docs(ui): add design document for the Forge UI system #580's DL-14 — so text entry can also be driven programmatically for tests and scripted tutorials.

Then a thin TextInputEcsComponent in /src/ui binds that primitive to a focusable rect, and renders value + caret + selection via #584.

Design questions worth settling early

Dependencies

Relationship to #580

design/ui-system.md DL-10 holds the decision record that led here and now defers to this issue.

One consequence worth noting: moving the DOM bridge out means the UI module's "no DOM-backed widgets" non-goal becomes a real invariant rather than one with an exception carved out of it. The UI module stays DOM-free, and the unavoidable browser interop sits in /src/input alongside the other input-source complexity.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions