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).
Poll(15 * time.Second).
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 ComputeManifestScript() string
- func Mount(r *router.Router, def *Definition)
- func MountBuilder(r *router.Router, b *Builder)
- func MountRuntime(r *router.Router)
- func RenderCSS(d *Definition) string
- func RenderChrome(d *Definition) string
- func RuntimeModuleHash(name string) string
- func RuntimeModuleManifestScript() string
- func RuntimeTag() string
- func ServeComputeAsset(w http.ResponseWriter, r *http.Request)
- 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) Poll(interval time.Duration) *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) 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 SignalFunc
- type SignalSource
- type Slot
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ComputeManifestScript ¶ added in v0.30.0
func ComputeManifestScript() string
ComputeManifestScript emits an inert JSON manifest mapping registered compute asset names to their worker and WebAssembly content hashes.
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 RenderCSS ¶ added in v0.8.0
func RenderCSS(d *Definition) string
RenderCSS returns the widget's stylesheet: the framework positioning + chrome rules plus the widget's optional ExtraCSS. Exported so the static exporter can dump each widget's CSS as a query-free file alongside its chrome HTML. Mirrors what serveStyle writes to the live style endpoint.
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 ServeComputeAsset ¶ added in v0.30.0
func ServeComputeAsset(w http.ResponseWriter, r *http.Request)
ServeComputeAsset serves registered workers and WebAssembly modules from /__gofastr/compute/<name>.js|wasm under immutable cache headers.
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) Poll ¶ added in v0.38.0
Poll sets a re-fetch interval for the widget's signal state. The runtime GETs StatePath on this cadence and overwrites each declared signal with the fresh value (skipping the DOM write when the value is unchanged). ±10% jitter, pause-while-hidden, and exponential back-off on fetch failure are applied automatically by the runtime.
No-op in catalog emission when the widget declares no Signals (statePath is omitted, so there is nothing to poll). interval is recorded verbatim as milliseconds; the browser runtime enforces a 100ms floor (Go callers are trusted config — the page-attribute path clamps at 5s instead, because markup is cheap to typo).
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) 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
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
// PollMS is the freshness interval (milliseconds) the runtime
// should re-fetch StatePath on. Zero (the default) disables
// polling. Emitted as "pollMs" in the catalog ONLY when both
// PollMS > 0 AND the widget declares at least one Signal — a
// widget without signals has no StatePath to poll. The runtime
// applies ±10% jitter, pauses while document.hidden, fetches
// immediately on visibility regain, and backs off (doubling,
// capped at 5×) on fetch failure until the next success.
// Set via Builder.Poll.
PollMS int
}
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 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). |