Documentation
¶
Overview ¶
Package interactive provides declarative interactivity primitives for GoFastr components. It wraps arbitrary render.HTML with data-cui-* attributes the runtime understands, RPC calls, signal bindings, widget chaining, without writing any JavaScript.
Usage:
interactive.OnClick(btn,
interactive.Post("/api/like").OnSuccess(interactive.SetSignal("count")),
)
The package only emits attributes the runtime already handles (data-cui-rpc, data-cui-signal, data-cui-open, etc.) plus new ones added for chaining (data-cui-rpc-open, data-cui-rpc-signal).
Index ¶
- Constants
- func AnimateOnSignal(html render.HTML, signalName, cssClass string) render.HTML
- func BindAttr(html render.HTML, signal, attr string) render.HTML
- func BindHTML(html render.HTML, signal string) render.HTML
- func BindText(html render.HTML, signal string) render.HTML
- func CancelEdit(html render.HTML, signalName string) render.HTML
- func ClosePaneOnClick(html render.HTML, pane string) render.HTML
- func Dropdown(trigger, panel render.HTML) render.HTML
- func EditToggle(html render.HTML, signalName string) render.HTML
- func IncLocal(html render.HTML, signalName string, delta int) render.HTML
- func LiveSearch(form render.HTML, action Action, debounceMs int) render.HTML
- func OnClick(html render.HTML, action Action) render.HTML
- func OnSubmit(html render.HTML, action Action) render.HTML
- func OpenOnClick(html render.HTML, widget string) render.HTML
- func OpenPaneOnClick(html render.HTML, pane string) render.HTML
- func OptimisticUpdate(action Action, idle, success render.HTML) render.HTML
- func PaneKey(html render.HTML, key string) render.HTML
- func PhoneSheet(cfg PhoneSheetConfig, body render.HTML) render.HTML
- func Reveal(html render.HTML, animationType string) render.HTML
- func SectionMenu(cfg SectionMenuConfig) render.HTML
- func SectionMenuDrawer(cfg SectionMenuConfig) *widget.Builder
- func SectionMenuDrawerFunc(build func(ctx context.Context) SectionMenuConfig) *widget.Builder
- func SetLocal(html render.HTML, signalName, value string) render.HTML
- func SheetPill(cfg SheetPillConfig) render.HTML
- func SignalAttrAllowed(htmlAttr string) bool
- func SwapPaneOnClick(html render.HTML, pane string) render.HTML
- func ToastOnClick(html render.HTML, t Toast) render.HTML
- func ToggleLocal(html render.HTML, signalName string) render.HTML
- type Action
- func (a Action) Attrs() map[string]string
- func (a Action) OnErrorToast(title string) Action
- func (a Action) OnSuccess(effects ...Effect) Action
- func (a Action) OnSuccessToast(title string) Action
- func (a Action) OnSuccessToastAction(label string, next Action) Action
- func (a Action) WithBody(body string) Action
- func (a Action) WithConfirm(message string) Action
- func (a Action) WithConfirmDialog(c Confirm) Action
- type Confirm
- type Effect
- type PhoneSheetConfig
- type SectionGroup
- type SectionItem
- type SectionMenuConfig
- type SheetPillConfig
- type SwapWidth
- type Toast
- type ToastAction
Constants ¶
const SheetChipClass = "cui-sheet-chip"
SheetChipClass marks a part of a sheet entry's link that is not its name, such as a rail link's number or count: the pill that names the current entry reads the link's text without it.
Variables ¶
This section is empty.
Functions ¶
func AnimateOnSignal ¶
AnimateOnSignal wraps an element so it gets a CSS class when a signal is truthy and loses it when falsy. Used for CSS transition-driven animations like slide-down, fade, etc.
Panics if signalName or cssClass is empty, or signalName is reserved.
func BindAttr ¶ added in v0.42.0
BindAttr wraps an HTML element whose attribute tracks a signal. Injects data-cui-signal, data-cui-signal-mode="attr", and data-cui-signal-attr="<attr>". Use when a signal should drive a single attribute (e.g. aria-expanded, data-active) rather than text content. The boolean attributes disabled, hidden, checked, open and selected are removed for a false value (false, null, "", 0, "false") rather than written as "false", and the runtime writes the live checked, selected and value properties, so a field the reader already edited still follows. Panics if an allowed attribute binds a reserved signal name.
func BindHTML ¶ added in v0.42.0
BindHTML wraps an HTML region whose innerHTML is replaced with the signal value (the trusted-HTML path). Injects data-cui-signal and data-cui-signal-mode="html". Use for an island slot an RPC re-renders server-side and returns as a fragment. Panics if signal is a reserved signal name.
func BindText ¶ added in v0.42.0
BindText wraps an HTML element whose textContent tracks a signal (HTML-escaped). Injects data-cui-signal and data-cui-signal-mode="text". This is the default mode, a bare data-cui-signal with no mode behaves identically, but emitting the mode explicitly documents intent. Panics if signal is a reserved signal name.
func CancelEdit ¶
CancelEdit wraps an element so clicking it sets a signal to false, closing the inline-edit mode. Typically used on a cancel button inside the edit form.
func ClosePaneOnClick ¶ added in v0.42.0
ClosePaneOnClick wraps an HTML element so clicking it closes a side pane. Maps to data-hui-pane-close="<pane>". A non-empty pane ("secondary" or "tertiary") closes that specific pane; an empty pane emits data-hui-pane-close="" and closes the topmost open pane. Any other value panics.
func EditToggle ¶
EditToggle wraps an element so clicking it toggles a boolean signal. Semantic alias for ToggleLocal used in inline-edit patterns: clicking the text span enters edit mode.
func IncLocal ¶
IncLocal wraps an HTML element so clicking it increments a numeric signal by delta (default 1). No RPC is fired. Panics if signalName is a reserved signal name.
func LiveSearch ¶
LiveSearch wraps a form element so input changes fire debounced RPCs. The search input should be the first <input> inside the form. Results are written into the signal named by the action's OnSuccess effect (typically via SetSignal).
debounceMs controls the delay between keystrokes and the RPC call. If debounceMs is 0, a default of 300ms is used.
func OnClick ¶
OnClick wraps an HTML element so that clicking it fires the action. The element must be a clickable element (button, a, etc.).
func OpenOnClick ¶ added in v0.42.0
OpenOnClick wraps an HTML element so clicking it opens a registered widget surface. Maps to data-cui-open="<widget>". This is the click-to-open trigger, distinct from OpenWidget, which opens a widget only after a successful RPC (data-cui-rpc-open).
func OpenPaneOnClick ¶ added in v0.42.0
OpenPaneOnClick wraps an HTML element so clicking it opens the named side pane. Maps to data-hui-pane-open-control="<pane>". Panics unless pane is "secondary" or "tertiary".
func PaneKey ¶ added in v0.44.0
PaneKey labels a pane trigger with the identity of what it opens, the ticket id, the record slug, whatever the pane will show. Maps to data-hui-pane-key="<key>".
It is only read on hosts that opted into URL round-tripping with ui.PaneHostConfig.DeepLinkParam: opening through a keyed trigger writes `?<param>=<pane>:<key>`, so refreshing or sharing that URL reproduces the open pane, and Back closes it. Compose it with OpenPaneOnClick (or a manual data-hui-pane-open-control) on the same element:
interactive.PaneKey(
interactive.OpenPaneOnClick(row, "secondary"), ticket.ID)
An unkeyed trigger still opens its pane; it just leaves the URL unchanged, which is the right behavior for a pane whose contents have no addressable identity. On a host without DeepLinkParam the attribute is inert.
The key lands in the URL verbatim, so keep it to record identifiers. The server must look it up rather than trusting it. See ui.PaneDeepLink.
func PhoneSheet ¶ added in v0.88.0
func PhoneSheet(cfg PhoneSheetConfig, body render.HTML) render.HTML
PhoneSheet renders body in normal flow at and above the swap width, and below it as a bottom sheet a floating SheetPill opens. The sheet is a native popover: it opens with no script, closes on Escape and on a tap outside it, and holds the page still while it is open. The pill names the entry the body marks aria-current, so a list that marks where the reader is (a table of contents, a menu) shows it on the pill. Following a link in the sheet closes it.
The pill floats at the bottom of the screen, so a page carries one floating pill: beside a SectionMenu's, set Top and the pill becomes a bar under the sticky header with the sheet hanging from it.
func Reveal ¶
Reveal wraps an element so it animates in when it enters the viewport. The animationType determines the direction ("fade-up", "fade-in", "slide-left", "slide-right"). Empty → "fade-in". The element renders visible without JS (progressive enhancement); reveal.js adds the hidden state on boot and removes it on intersection.
func SectionMenu ¶
func SectionMenu(cfg SectionMenuConfig) render.HTML
SectionMenu renders the desktop rail plus the mobile trigger pill. Mount the matching sheet once with SectionMenuDrawer.
func SectionMenuDrawer ¶
func SectionMenuDrawer(cfg SectionMenuConfig) *widget.Builder
SectionMenuDrawer returns the mobile sheet widget for a SectionMenu, a preset.BottomSheet (backdrop + click-outside / Escape / drag-down close + scroll lock + focus trap) carrying the same menu body. Mount it once at startup:
widget.MountBuilder(router, interactive.SectionMenuDrawer(cfg))
func SectionMenuDrawerFunc ¶ added in v0.96.0
func SectionMenuDrawerFunc(build func(ctx context.Context) SectionMenuConfig) *widget.Builder
SectionMenuDrawerFunc is SectionMenuDrawer for a menu whose items change after startup: build runs on every render of the sheet, with the request's context, so the sheet lists what the rail beside it lists on the same request. build is also called once here, with context.Background(), for the sheet's name; the DrawerName it returns then is kept for every later render.
widget.MountBuilder(router, interactive.SectionMenuDrawerFunc(menuFor))
func SetLocal ¶
SetLocal wraps an HTML element so clicking it sets a signal to a fixed value. No RPC is fired, the update is instant. Panics if signalName is a reserved signal name.
func SheetPill ¶ added in v0.88.0
func SheetPill(cfg SheetPillConfig) render.HTML
SheetPill renders the phone pill: an icon, the current entry and its detail, and a chevron. A behaviour that tracks the current entry rewrites the label and detail spans in place.
func SignalAttrAllowed ¶ added in v0.45.0
SignalAttrAllowed reports whether a signal binding may write the named HTML attribute. Matching is case-insensitive: HTML attribute names are case-insensitive to the parser, so `OnClick` is an event handler no matter how it was cased.
Exported so both emitters of data-cui-signal-attr, BindAttr here and core-ui/store's Slice.BindAttr, enforce the same list instead of growing a second copy.
func SwapPaneOnClick ¶ added in v0.86.0
SwapPaneOnClick wraps an HTML element so clicking it opens the named side pane and closes its sibling (one open pane at a time). Maps to data-hui-pane-swap="<pane>". Panics unless pane is "secondary" or "tertiary".
func ToastOnClick ¶ added in v0.42.0
ToastOnClick wraps an HTML element so clicking it fires a toast with the given config. Maps to data-cui-toast="<json>". The JSON is compact (no whitespace) with HTML escaping disabled, matching what call sites hand-write today; render.Attr then escapes the value for the attribute context.
Types ¶
type Action ¶
type Action struct {
// contains filtered or unexported fields
}
Action describes an RPC call triggered by click or submit.
func (Action) Attrs ¶ added in v0.42.0
Attrs returns the data-cui-* attributes this Action would inject, as a plain map[string]string. It is the same map OnClick/OnSubmit splice into the opening tag, exported so a call site can merge it into an existing attribute map (an render.Tag attrs map or a ui.*Config ExtraAttrs) and let render.Tag's sorted writer place every attribute in the same order a hand-written map would. Prefer this over the wrapper functions whenever byte-identical attribute ordering matters (the wrappers append at the tag's first '>', which can reorder attributes relative to a sorted map render).
The returned map is a fresh copy; mutating it does not affect the Action. It is assignable directly to html.Attrs (a map[string]string alias) and to render.Tag's attrs argument.
func (Action) OnErrorToast ¶ added in v0.87.0
OnErrorToast makes a refused or failed RPC visible: on a non-2xx answer (or a network error) the runtime shows an error toast titled title, with the server's JSON "error" message as its body. Without it a non-form RPC that fails ends in silence (a form already reports its refusals beside the fields). An empty title falls back to "Request failed".
interactive.Delete("/api/orders/42").
OnSuccess(interactive.Navigate("/orders")).
OnErrorToast("Could not delete this order.")
Maps to data-cui-rpc-error-toast="title".
func (Action) OnSuccessToast ¶ added in v0.87.0
OnSuccessToast shows a success toast titled title on a 2xx answer, before any OnSuccess(Navigate) runs. The toast rides the stack outside the swapped region, so it survives the navigation that follows it — the save that lands the reader back on the record page still says "Saved".
interactive.Post("/api/orders").
OnSuccessToast("Order created.").
OnSuccess(interactive.Navigate("/orders"))
Maps to data-cui-rpc-success-toast="title".
func (Action) OnSuccessToastAction ¶ added in v0.87.0
OnSuccessToastAction puts one action on the OnSuccessToast toast: a button labelled label that runs next when pressed, with next's own toasts and navigation. The toast stays up for ten seconds, paused while hovered or focused, so there is time to press it. The button carries next's RPC wiring and nothing else, so next may not carry a confirm: it panics on one, and on an empty label. The action rides the success toast, so an Action that renders without OnSuccessToast panics too.
interactive.Delete("/api/invoices/42").
OnSuccessToast("Invoice deleted").
OnSuccessToastAction("Undo", interactive.Post("/api/invoices/42/_restore").
WithBody(`{}`).OnSuccessToast("Invoice restored"))
Maps to data-cui-rpc-success-action='{"label":…,"attrs":{…}}'.
func (Action) WithBody ¶ added in v0.42.0
WithBody attaches a static JSON body to the action, the payload sent for a non-form RPC (a button click that isn't inside a <form>). Maps to data-cui-rpc-body="<json>". The runtime sends it verbatim as the request body with Content-Type: application/json.
Panics if json is not valid JSON (json.Valid), so a malformed body fails loudly at build/render time rather than producing a runtime request the server rejects. For form-backed RPCs the runtime serializes the form fields itself, do not call WithBody there.
func (Action) WithConfirm ¶ added in v0.12.0
WithConfirm gates the action behind a PRE-FLIGHT confirmation. Before the RPC is dispatched, the runtime opens the kit's confirm dialog with message; cancelling aborts the request entirely, so the RPC never fires. Because the gate runs *before* the request, not after it succeeds, it is a property of the Action itself, not an OnSuccess effect.
interactive.OnClick(saveBtn,
interactive.Post("/api/items/42/publish").
WithConfirm("Publish this item to every customer?"),
)
It is WithConfirmDialog(Confirm{Message: message}): reach for that to title the dialog, name the accept button or mark the action destructive. A page with no confirm dialog registered (no kit imported) falls back to window.confirm, so the gate never opens.
Maps to data-cui-confirm="message".
func (Action) WithConfirmDialog ¶ added in v0.87.0
WithConfirmDialog gates the action behind the kit's confirm dialog, spelled in full. Use it for destructive actions (delete, revoke, drop), where the dialog names what goes and the accept button says so in the danger variant:
interactive.Delete("/api/items/42").
WithConfirmDialog(interactive.Confirm{
Title: "Delete item?",
Message: "This cannot be undone.",
Accept: "Delete",
Danger: true,
})
Panics on an empty Message: a confirmation that says nothing asks nothing. Maps to data-cui-confirm, data-cui-confirm-title, data-cui-confirm-accept and data-cui-confirm-tone="danger".
type Confirm ¶ added in v0.3.2
Confirm is what a pre-flight confirmation says: the dialog's title, its message, the accept button's label, and whether accepting destroys something (Danger draws the accept button in the danger variant). Only Message is required; an empty Title or Accept keeps the kit dialog's own ("Are you sure?", "Confirm").
type Effect ¶
type Effect interface {
// contains filtered or unexported methods
}
Effect is something that happens after an RPC response.
func AfterDisable ¶ added in v0.3.2
func AfterDisable() Effect
AfterDisable permanently disables the trigger element on 2xx RPC success (sets aria-disabled="true" and, for buttons/inputs, disabled=true). Use with AfterText for "Saved ✓" / "Revealed ✓" feedback. Maps to the boolean attribute data-cui-rpc-after-disable.
func AfterText ¶ added in v0.3.2
AfterText replaces the trigger element's text content with text on 2xx RPC success. One-shot, subsequent re-clicks are idempotent via data-cui-rpc-after-done. Pair with AfterDisable for "Saved ✓" feedback. Maps to data-cui-rpc-after-text="text".
func CloseWidget ¶
func CloseWidget() Effect
CloseWidget closes the enclosing widget on RPC success. Maps to data-cui-rpc-close="true".
func Navigate ¶
Navigate does an SPA navigation on RPC success. Maps to data-cui-rpc-navigate="path".
func OpenWidget ¶
OpenWidget opens a named widget when the RPC succeeds. Maps to data-cui-rpc-open="name".
func PushState ¶ added in v0.3.2
PushState applies a URL update via history.pushState after 2xx RPC success without triggering a fetch. The server-supplied X-Gofastr-Push-State header takes precedence over this attribute when both are present. Maps to data-cui-push-state="path".
func ResetForm ¶
func ResetForm() Effect
ResetForm resets the enclosing form on RPC success. Maps to data-cui-rpc-reset="true".
type PhoneSheetConfig ¶ added in v0.88.0
type PhoneSheetConfig struct {
// CloseLabel names the dismiss control. Empty is "Close" in the
// language of Ctx.
CloseLabel string
// Ctx carries the per-request context the default close label is
// written in. When nil, English applies.
Ctx context.Context
// ID is the sheet's element id, which the pill names to open it.
// Unique on the page. Required.
ID string
// Title names the sheet: its heading, its dialog's name and the
// pill's Name. Required.
Title string
// SwapAt is the width below which the body folds into the sheet.
// In a ui.ContentRow column, match the row's Breakpoint.
SwapAt SwapWidth
// Top pins the pill as a bar under the page's sticky header
// (SheetPillConfig.Bar) and hangs the sheet from it, for a page
// whose bottom edge already holds a SectionMenu's pill.
Top bool
}
PhoneSheetConfig configures a PhoneSheet.
type SectionGroup ¶
type SectionGroup struct {
Label string
Eyebrow string // optional ordinal/kicker shown before the label (e.g. "01")
Items []SectionItem
// Collapsed starts the group closed in the mobile sheet. A group
// holding the active item is forced open regardless. (The desktop
// rail always shows every group expanded.)
Collapsed bool
}
SectionGroup is a labelled, collapsible cluster of items.
type SectionItem ¶
type SectionItem struct {
Label string
Href string
Active bool // marks the current page (aria-current="page" + .is-active)
}
SectionItem is one navigable link in a SectionMenu group.
type SectionMenuConfig ¶
type SectionMenuConfig struct {
// Lead is an optional ungrouped item rendered above the groups
// (e.g. an "Overview" / index link).
Lead *SectionItem
Groups []SectionGroup
// AriaLabel names the nav landmark (e.g. "Documentation sections").
AriaLabel string
// TriggerLabel names the mobile pill and titles the sheet. The pill
// shows the current page and its group instead when there is one,
// and keeps this as the start of its accessible name. Default: the
// interactive.menu phrase ("Menu") in the locale Ctx carries.
TriggerLabel string
// DrawerName is the widget name shared by the trigger pill
// (data-cui-open) and SectionMenuDrawer. Required for the mobile sheet;
// must be unique per distinct menu on a site.
DrawerName string
// Filter adds a filter box above the groups, on the rail and in the
// sheet: typing hides every link whose label does not contain the
// text, opens every group holding a match and marks the match;
// Escape clears it. It filters the menu's own links in the browser:
// there is no request and no data behind it.
Filter bool
// FilterPlaceholder is the filter box's placeholder and accessible
// name. Default "Filter", in the reader's language.
FilterPlaceholder string
// FilterEmpty is the line shown when nothing matches. Default
// "No matches", in the reader's language.
FilterEmpty string
// InlineTrigger keeps the mobile trigger in flow where the menu sits.
// By default the pill floats at the bottom of the viewport, which is
// right for the page's own section nav (in a ui.ContentRow, set
// PhoneNavFlush so the stacked nav column draws no empty band). A
// menu that is not the page's nav (a demo, a menu inside a card)
// sets this so its pill does not float over the page.
InlineTrigger bool
// FillColumn is for a menu placed in a column its host already pins
// at a fixed height (a ui.ContentRow{Sticky: true} nav column): on
// desktop the rail drops its own sticky offset and viewport cap,
// fills the column top to bottom and scrolls its overflow there. In
// normal flow leave it off and the rail sticks on its own.
FillColumn bool
// SwapAt picks the width below which the rail gives way to the
// trigger and sheet: 900px by default. In a ui.ContentRow nav
// column, match the row's Breakpoint (SwapMD for the
// default, SwapLG for ui.StackBelowLG), so the column
// never holds the rail beside content too narrow for it. An unknown
// value panics.
SwapAt SwapWidth
Class string
ID string
// Ctx carries the per-request context the menu's default words and
// each group's link count (i18n.Number) are written in. When nil,
// English applies. A sheet mounted once with SectionMenuDrawer
// speaks the locale of each request that renders it.
Ctx context.Context
}
SectionMenuConfig configures a SectionMenu and its sheet.
type SheetPillConfig ¶ added in v0.88.0
type SheetPillConfig struct {
// Name is what the pill opens ("Components", "On this page"). It
// starts the pill's accessible name, so a screen reader hears what
// the control opens before where the reader is, and it is the
// visible label while nothing is current. Required.
Name string
// Label names the current entry. Empty shows Name.
Label string
// Detail is a muted second part after the label, such as the
// current entry's group. Optional.
Detail string
// Opens names the mounted widget the pill opens (data-cui-open).
Opens string
// Popover is the id of the inline popover the pill opens
// (popovertarget). Set exactly one of Opens and Popover.
Popover string
// Inline keeps the pill in flow where it sits. By default it floats
// at the bottom of the viewport, centred, and the page ends one
// pill's height lower so the pill never covers its last line.
Inline bool
// Bar pins the pill as a full-width bar under the page's sticky
// header (--ui-sticky-top) instead of floating it, the name shown
// before the current entry ("On this page: Overview"). A page whose
// bottom edge already holds a floating pill (a SectionMenu's) shows
// its second navigation this way. Bar and Inline are exclusive.
Bar bool
// SwapAt is the width at and above which the pill hides: the
// desktop surface it stands in for takes over there.
SwapAt SwapWidth
}
SheetPillConfig configures a SheetPill.
type SwapWidth ¶ added in v0.88.0
type SwapWidth string
SwapWidth picks the viewport width where a desktop surface (a SectionMenu's rail, a PhoneSheet's inline body) gives way to the phone pill and its sheet.
const ( // SwapDefault swaps below 900px: a surface in normal flow, beside a // column the page lays out itself. SwapDefault SwapWidth = "" // SwapMD swaps below 48rem, where a ui.ContentRow with the default // Breakpoint stacks its columns. SwapMD SwapWidth = "md" // SwapLG swaps below 64rem, where a ui.ContentRow with // Breakpoint: ui.StackBelowLG stacks. SwapLG SwapWidth = "lg" )
type Toast ¶ added in v0.42.0
type Toast struct {
Variant string `json:"variant,omitempty"` // "success" | "warning" | "danger" | "info" | "neutral"; defaults to "info"
Title string `json:"title,omitempty"` // required by the runtime, a toast with no title is dropped
Body string `json:"body,omitempty"` // optional supporting copy
Stack string `json:"stack,omitempty"` // named [data-cui-toast-stack] container; empty → the auto stack
TTLMs int `json:"ttl,omitempty"` // auto-dismiss delay in ms; 0 → persistent (manual dismiss only)
}
Toast is the config for a click-fired toast notification. Zero fields are omitted from the emitted JSON, so a Toast{Variant, Title, Body, TTLMs} marshals to exactly {"variant":…,"title":…,"body":…,"ttl":…}, the shape call sites hand-write. The feedback module's toast runtime (`headless-feedback` `__gofastr.toast`) reads these keys: variant, title, body, ttl, stack.
type ToastAction ¶ added in v0.87.0
ToastAction is a toast's one button as the runtime reads it: its label and the RPC attributes it carries. Build it with NewToastAction; a server toast carries it as ui.ToastTrigger.Action.
func NewToastAction ¶ added in v0.87.0
func NewToastAction(label string, next Action) *ToastAction
NewToastAction is a toast button labelled label that runs next. The button carries next's RPC wiring and nothing else, so it panics on an empty label, on a next that is no RPC, and on a next carrying any other attribute (a confirm, a signal).