Documentation
¶
Overview ¶
Package widget provides the framework's overlay-UI primitive.
A widget is a self-mounting UI surface that runs on top of any page — regardless of whether that page is built with core-ui components or is a plain HTML document. Widgets are distinct from components:
- Components are part of a server-rendered page tree.
- Widgets are siblings that float above the page (fixed corners, modal overlays, toast stacks). They mount themselves via a bootstrap script tag the framework generates.
Architectural goals:
Hosts (kiln, future feature packages) describe widgets declaratively via WidgetDef. They never write DOM/CSS by hand.
The runtime (core-ui/runtime + core-ui/html/Overlay) owns mounting, stacking, focus management, SSE wiring, and RPC dispatch. Hosts only fill slots and declare server-side signals + RPC handlers.
Theming flows through core-ui/style. A widget never embeds hex colors or magic spacing; it references theme tokens ({colors.primary}, {spacing.lg}). Override the theme to reskin every widget at once.
Anatomy of a widget — code skeleton:
def := widget.New("kiln-panel").
Mount(widget.BottomRight).
Slot("header", headerComponent).
Slot("body", bodyComponent).
Signal("page", pageSignalSrc).
SSE("/.kiln/events", "world_edit", "page").
RPC("POST", "/kiln/tool/reset_session", resetHandler)
widget.Mount(app, def)
The package is intentionally small in interface. Built-in surfaces (FloatingPanel, Modal, Toast, Drawer, Popover) live in core-ui/widget/preset and are constructed with the same WidgetDef builder — no new primitive per surface.
Index ¶
- func Mount(r *router.Router, def *Definition)
- func MountBuilder(r *router.Router, b *Builder)
- func MountRuntime(r *router.Router)
- func RenderChrome(d *Definition) string
- func RuntimeModuleHash(name string) string
- func RuntimeModuleManifestScript() string
- func RuntimeTag() string
- func ServeRuntimeModule(w http.ResponseWriter, r *http.Request)
- func ServeWidgetList(w http.ResponseWriter, r *http.Request)
- type BootstrapMode
- type Builder
- func (b *Builder) Backdrop() *Builder
- func (b *Builder) Bootstrap(m BootstrapMode) *Builder
- func (b *Builder) Build() Definition
- func (b *Builder) DeepLink(key, value string) *Builder
- func (b *Builder) DeepLinkParam(key string) *Builder
- func (b *Builder) Definition() *Definition
- func (b *Builder) DescribedBy(id string) *Builder
- func (b *Builder) DragDismiss() *Builder
- func (b *Builder) Hidden() *Builder
- func (b *Builder) LabelledBy(id string) *Builder
- func (b *Builder) Mount(p Position) *Builder
- func (b *Builder) Pages(paths ...string) *Builder
- func (b *Builder) PagesMatch(fn func(path string) bool) *Builder
- func (b *Builder) PagesPrefix(prefixes ...string) *Builder
- func (b *Builder) RPC(method, path string, h http.Handler) *Builder
- func (b *Builder) RPCWithSignal(method, path string, h http.Handler, signal string) *Builder
- func (b *Builder) Role(r string) *Builder
- func (b *Builder) SSE(path, event, signal string) *Builder
- func (b *Builder) SSERefetch(path, event, signal string) *Builder
- func (b *Builder) SSEReload(path, event string, matchPairs ...string) *Builder
- func (b *Builder) Signal(name string, src SignalSource) *Builder
- func (b *Builder) Skeleton(fn func(slots map[string]render.HTML) render.HTML) *Builder
- func (b *Builder) Slot(name string, c component.Component) *Builder
- type Definition
- type Position
- type RPCEndpoint
- type RouteMatcher
- type SSEBinding
- type SignalFunc
- type SignalSource
- type Slot
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Mount ¶
func Mount(r *router.Router, def *Definition)
Mount registers the widget's HTTP routes on r and adds it to the process-global registry the framework runtime auto-discovers. Hosts don't embed a per-widget script tag — they embed ONE shared runtime tag (see RuntimeTag) and the runtime mounts every registered widget.
Routes mounted on r:
GET <StylePath> widget styles (theme-resolved CSS) GET <StatePath> JSON snapshot of all signals (initial render) GET /core-ui/widget/<name>/chrome rendered chrome HTML (lazy) * <RPC.Path> for each RPC, the host's handler
Default paths are filled in on def if unset so the caller can read them after Mount returns. Mount is idempotent on def.Name.
func MountBuilder ¶
MountBuilder builds the widget from b and mounts it on r — sugar over the two-step
def := b.Build() widget.Mount(r, &def)
which every caller otherwise repeats. Use Builder.Hidden() in the chain for widgets (modals, drawers) that start closed.
func MountRuntime ¶
MountRuntime registers the framework runtime endpoints on r:
GET /__gofastr/runtime.js the default bundled runtime
(single-payload IIFE every
page ships)
GET /__gofastr/runtime/<name>.js one split runtime module
(loaded on demand via the
optional module-loader path)
GET /__gofastr/widgets JSON list of registered widgets
Call this once per host (kiln serve, examples/site, etc.). The runtime IIFE is idempotent, so re-mounting on rebuilds is safe.
func RenderChrome ¶
func RenderChrome(d *Definition) string
RenderChrome returns the rendered chrome HTML for a single widget, using its registered Skeleton or the framework's defaultSkeleton. Exported so SSR hosts can inline chrome without instantiating `server` themselves.
func RuntimeModuleHash ¶
RuntimeModuleHash returns the content-addressed hash for a split runtime module. Used by client-side preload tags + by the loader to construct `?v=<hash>` URLs. Empty string if the module isn't embedded.
func RuntimeModuleManifestScript ¶
func RuntimeModuleManifestScript() string
RuntimeModuleManifestScript emits an inert JSON manifest mapping every split runtime module to its content-addressed hash. Returns "" when no modules are embedded.
Both RuntimeTag (kiln + manual hosts) and framework/uihost embed this script. Pages without the manifest fall through to un-versioned module URLs and then collide with the immutable cache headers — see TestRuntimeTagEmbedsModuleManifest for the regression that motivated this.
func RuntimeTag ¶
func RuntimeTag() string
RuntimeTag returns the markup a host page embeds to load the framework runtime + auto-mount every registered widget. The URL includes a content-hash query param so a new server build invalidates any cached runtime in the browser without manual hard-reload.
Also emits an inert <script type="application/json" id="gofastr-runtime-modules"> manifest mapping each split runtime module (popover, toasts, widgets, …) to its content-addressed hash. The client-side module loader reads this manifest to build cache-busted `?v=<hash>` URLs. Without it, kiln-style hosts that don't go through framework/uihost would fall through to un-versioned URLs and end up poisoning the browser cache (server returns `Cache-Control: ...immutable` unconditionally).
Implemented as a func, not a const, because the hashes are computed lazily from the embedded JS bytes.
func ServeRuntimeModule ¶
func ServeRuntimeModule(w http.ResponseWriter, r *http.Request)
ServeRuntimeModule is the exported handler for /__gofastr/runtime/<name>.js. Hosts that mount routes via uihost get it through framework/uihost; kiln and standalone hosts can wire it themselves alongside MountRuntime.
func ServeWidgetList ¶
func ServeWidgetList(w http.ResponseWriter, r *http.Request)
ServeWidgetList returns the JSON list of registered widgets — the payload the framework runtime fetches at /__gofastr/widgets to discover and mount what's been registered with widget.Mount. Exported so hosts that already own the /__gofastr/widgets route (e.g. framework/uihost, which serves an empty stub for widget-free apps) can delegate to the registry without double-registering the HTTP route.
Types ¶
type BootstrapMode ¶
type BootstrapMode string
BootstrapMode selects how the widget injects itself onto a page.
- AutoScript: framework emits a <script> tag and the runtime mounts it on every page that includes the tag. Used by kiln's panel.
- Embedded: the widget is rendered as part of an existing page tree (no script tag). Useful when the page already runs core-ui.
const ( AutoScript BootstrapMode = "auto-script" Embedded BootstrapMode = "embedded" )
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder fluently composes a Definition. Use widget.New(name) to start.
func (*Builder) Bootstrap ¶
func (b *Builder) Bootstrap(m BootstrapMode) *Builder
func (*Builder) Build ¶
func (b *Builder) Build() Definition
Build returns the assembled Definition.
func (*Builder) DeepLink ¶
DeepLink wires this widget to a query-string pair. When the URL includes `?key=value`, the SSR layer opens the widget at first paint and the runtime mirrors open/close as pushState updates — so refresh, share, and the browser back button all work.
Pair with DeepLinkParam to pass extra data into the widget's signals.
Intended for modal/drawer presets; not for toasts or dropdowns.
func (*Builder) DeepLinkParam ¶
DeepLinkParam registers a query-string key whose value is mirrored into a same-named signal whenever this widget opens via deep link. Call once per param. Example:
preset.Modal("user-edit").
Hidden().
DeepLink("modal", "user-edit").
DeepLinkParam("user_id").
Signal("user_id", widget.SignalFunc(func() (any, error) { ... })).
Visiting `/users?modal=user-edit&user_id=42` opens the modal with signal "user_id" pre-seeded to "42".
func (*Builder) Definition ¶
func (b *Builder) Definition() *Definition
Definition returns a pointer to the in-progress Definition so preset builders (drawer / modal / toast) can tweak fields the fluent API doesn't expose setters for. Callers building widgets by hand should still finish with .Build().
func (*Builder) DescribedBy ¶
DescribedBy sets aria-describedby on the widget root. The id MUST match an element in the rendered slot HTML.
func (*Builder) DragDismiss ¶
DragDismiss enables drag-to-dismiss for bottom-edge widgets. See Definition.DragDismiss for the full contract.
func (*Builder) Hidden ¶
Hidden marks the widget as registered-but-not-auto-mounted. Open it from a button with data-fui-open="<name>".
func (*Builder) LabelledBy ¶
LabelledBy sets aria-labelledby on the widget root. The id MUST match an element in the rendered slot HTML.
func (*Builder) Pages ¶
Pages scopes the widget to exact path matches. The widget is hidden from the catalog + SSR-inlining on every page whose path isn't in the list. Stack with PagesPrefix / PagesMatch to combine rules.
func (*Builder) PagesMatch ¶
PagesMatch scopes the widget to paths accepted by the supplied matcher. Use for non-trivial rules (e.g. regex, glob, denylist).
func (*Builder) PagesPrefix ¶
PagesPrefix scopes the widget to paths that start with any of the given prefixes. Useful for section-wide modals (e.g. every /customers/* page).
func (*Builder) RPC ¶
RPC registers a server-side handler the widget can invoke from the client. method=="" defaults to "POST".
func (*Builder) RPCWithSignal ¶
RPCWithSignal is RPC + ResponseSignal: on success the handler's JSON response body is pushed into the named signal. Useful for "fetch and render" flows where a button click updates a region.
func (*Builder) Role ¶
Role sets the ARIA role on the widget root (e.g. "dialog", "alertdialog", "menu"). Pair with LabelledBy / DescribedBy for a complete a11y label.
func (*Builder) SSE ¶
SSE binds an SSE event to a signal. When the event fires on the stream at path, the event's payload becomes the named signal's new value.
func (*Builder) SSERefetch ¶
SSERefetch binds an SSE event to a signal whose value is rendered server-side. The runtime re-fetches /state on each event and applies the named signal's fresh value, rather than using the event's payload. Use for HTML/derived signals where the SSE event is just a trigger.
func (*Builder) SSEReload ¶
SSEReload triggers a full page reload on the SSE event. Use for events that change what's rendered at the current URL (e.g. kiln's add_page / delete_page / add_route — the page itself is now different). Pass matchPairs as alternating key/value strings to filter on payload fields (e.g. SSEReload(path, "world_edit", "op", "add_page")).
func (*Builder) Signal ¶
func (b *Builder) Signal(name string, src SignalSource) *Builder
Signal registers a named server-side signal source. The runtime pushes new values to client DOM nodes bound to it.
type Definition ¶
type Definition struct {
Name string
Position Position
Bootstrap BootstrapMode
Slots []Slot
Signals map[string]SignalSource
SSE []SSEBinding
RPCs []RPCEndpoint
// Skeleton is the host's chrome wrapper. If nil, the framework
// uses a sensible default for the chosen Position (FloatingPanel
// for corners, Modal for Center, etc.). Most hosts leave this
// nil and use a preset.
Skeleton func(slots map[string]render.HTML) render.HTML
// ExtraCSS is host-supplied CSS appended after the framework's
// chrome rules in the per-widget stylesheet (/<StylePath>). Use
// for content styling (slot innards, host-specific class names)
// that doesn't fit in the page theme. Generate it through
// core-ui/style.NewStyleSheet for token consistency, or pass an
// already-resolved CSS string.
ExtraCSS func() string
// Modal flags
Backdrop bool // dim the page behind the widget
CloseOnEscape bool // ESC closes the widget
CloseOnClickOutside bool
// Role is the ARIA role applied to the widget root element.
// Defaults to "dialog" for backdrop'd widgets and is left empty
// for plain panels / floating surfaces. Use "alertdialog" for
// widgets that demand the user's immediate attention.
Role string
// LabelledBy is the id of an element (typically a heading) inside
// the slot HTML that names this widget for screen readers. Becomes
// aria-labelledby on the widget root. The host is responsible for
// putting a matching id on the element.
LabelledBy string
// DescribedBy is the id of an element inside the slot HTML that
// provides supplementary description for the widget. Becomes
// aria-describedby on the widget root.
DescribedBy string
// Hidden=true means the widget is registered but NOT auto-mounted
// on page load. A button with data-fui-open="<name>" calls
// __gofastr.openWidget(name) to mount it on demand. Use for
// modals + drawers that should appear in response to user action.
Hidden bool
// DeepLinkKey is the URL query parameter that controls open state
// for this widget — e.g. "modal". When the request URL contains
// `?<DeepLinkKey>=<DeepLinkValue>`, the SSR layer renders the
// widget open at first paint AND the runtime mirrors open/close to
// pushState so refresh/share/back-button all stay consistent.
//
// Empty (the default) disables deep-linking — the widget remains
// purely click-driven via data-fui-open.
//
// Only meaningful for Hidden widgets (modal / drawer). Toasts and
// dropdowns intentionally do NOT support deep links.
DeepLinkKey string
// DeepLinkValue is the literal value of DeepLinkKey that opens
// THIS widget. Multiple widgets can share the same DeepLinkKey
// ("modal") as long as their DeepLinkValue is distinct
// ("user-edit", "confirm-delete").
DeepLinkValue string
// DeepLinkParams lists additional query parameters whose values
// should be mirrored into named signals when the widget opens via
// deep link. e.g. ["user_id"] with URL `?modal=user-edit&user_id=42`
// seeds signal "user_id"="42" before the slot renders.
DeepLinkParams []string
// Routes scopes the widget to specific request paths. When non-
// empty, the SSR layer and the runtime catalog only expose this
// widget on pages whose path is accepted by at least one matcher.
// Empty (the default) means "available on every page" — the
// behaviour before per-page scoping shipped.
//
// Constructed via the Builder methods .Pages, .PagesPrefix,
// .PagesMatch (or manually for advanced cases).
Routes []RouteMatcher
// DragDismiss enables pointer-driven drag-to-dismiss for bottom-edge
// widgets. The chrome renders a visible drag-handle bar at the top
// of the panel; the runtime listens for pointerdown/move/up on the
// handle (and on the panel itself) and closes the widget when the
// user drags past a distance + velocity threshold. Snaps back to
// the resting position when released earlier.
//
// Only meaningful for Bottom (and bottom-edge) positions today;
// silently no-op elsewhere.
DragDismiss bool
// Asset path overrides. Default routes are derived from Name.
BootstrapPath string // default: /core-ui/widget/<name>/bootstrap.js
StylePath string // default: /core-ui/widget/<name>/style.css
StatePath string // default: /core-ui/widget/<name>/state
}
Definition is the full description of a widget. It is built via the New(name) builder and consumed by Mount(app, def).
func AllForSSR ¶
func AllForSSR() []*Definition
AllForSSR returns a snapshot of every registered widget. Exported so SSR hosts (framework/uihost) can walk the registry and inline chrome HTML on the page response. The returned slice is a copy of the live registry; callers may iterate freely.
func AvailableOn ¶
func AvailableOn(path string) []*Definition
AvailableOn returns the subset of registered widgets visible on the given request path. The SSR host + per-request catalog handler use this to keep page-scoped widgets out of unrelated pages.
func (*Definition) IsAvailableOn ¶
func (d *Definition) IsAvailableOn(path string) bool
IsAvailableOn returns true when the widget is registered for the given request path. Widgets with no Routes (the default) are available everywhere; widgets with one or more matchers are available on paths accepted by at least one matcher.
type Position ¶
type Position string
Position is where the widget's root anchors itself when mounted. Modal/Center implies a backdrop and focus trap; Corner positions float without a backdrop.
const ( BottomRight Position = "bottom-right" BottomCenter Position = "bottom-center" // toast / banner stack mid-edge BottomLeft Position = "bottom-left" TopRight Position = "top-right" TopCenter Position = "top-center" // toast / banner stack mid-edge TopLeft Position = "top-left" Center Position = "center" // modal — backdrop + focus trap Top Position = "top" // banner across the top Bottom Position = "bottom" // banner across the bottom Edge Position = "edge-left" // drawer-style edge mount EdgeRight Position = "edge-right" )
type RPCEndpoint ¶
type RPCEndpoint struct {
Method string // "POST" by default
Path string
Handler http.Handler
ResponseSignal string
}
RPCEndpoint is a server-side HTTP handler the widget can invoke from the client (typically via a button click or form submit). The runtime POSTs to Path; on success it can push the response body into a signal named ResponseSignal (optional).
type RouteMatcher ¶
RouteMatcher decides whether a widget is available on a given request path. A Definition with one or more matchers is filtered out of catalogs + SSR-inlining for paths that no matcher accepts. A Definition with NO matchers is available on every path (the historical default).
type SSEBinding ¶
type SSEBinding struct {
Path string `json:"path"` // e.g. "/.kiln/events"
Event string `json:"event"` // e.g. "world_edit"
Signal string `json:"signal"` // e.g. "page"
// Refetch=true tells the runtime to re-fetch /state and apply the
// signal's fresh value instead of using the SSE event's payload.
// Use when the signal source is server-rendered (HTML, derived
// state) and the SSE event is just a "something changed" trigger.
Refetch bool `json:"refetch,omitempty"`
// Reload=true tells the runtime to do a full page reload on this
// event. Useful for events that change which page is rendered at
// the current URL (kiln add_page / delete_page / add_route /
// delete_route). Signal is ignored when Reload is set.
Reload bool `json:"reload,omitempty"`
// Match optionally filters the binding to events whose JSON
// payload contains all the listed key=value pairs. Useful when
// the SSE channel multiplexes by event type (e.g. "world_edit")
// and the host wants to react only to specific ops.
Match map[string]string `json:"match,omitempty"`
}
SSEBinding maps a server-sent event kind to a signal name. When the named SSE event arrives on the bus mounted at Path, the runtime pushes the event payload into the named signal — every DOM node bound to that signal updates.
type SignalFunc ¶
SignalFunc is a func adapter for SignalSource.
func (SignalFunc) Read ¶
func (f SignalFunc) Read() (any, error)
type SignalSource ¶
SignalSource produces JSON-serializable values that flow to the browser as named signals. The runtime polls (or receives via SSE) and pushes new values into [data-fui-signal="<name>"] html.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package preset bundles the most common widget surfaces as opinionated builders on top of widget.Definition.
|
Package preset bundles the most common widget surfaces as opinionated builders on top of widget.Definition. |
|
Package theme provides the framework's default page theme — the visual identity for any app built via core-ui (or its consumers like kiln).
|
Package theme provides the framework's default page theme — the visual identity for any app built via core-ui (or its consumers like kiln). |