| Title: | Component-Based 'Shiny' UI with Fine-Grained Reactivity |
| Version: | 0.3.0 |
| Description: | A component-based reactive UI framework for 'Shiny' that binds reactive values directly to DOM attributes, enabling fine-grained updates without re-rendering or 'update*Input' callbacks. |
| License: | MIT + file LICENSE |
| URL: | https://irid.kylehusmann.com, https://github.com/khusmann/irid |
| BugReports: | https://github.com/khusmann/irid/issues |
| Depends: | R (≥ 4.1.0) |
| Imports: | cli, htmltools, rlang (≥ 1.2.0), shiny |
| Suggests: | bslib, callr, chromote, DT, ggplot2, httpuv, jsonlite, knitr, pkgload, plotly, purrr, rmarkdown, testthat (≥ 3.0.0), tibble, vctrs, withr |
| VignetteBuilder: | knitr |
| Encoding: | UTF-8 |
| RoxygenNote: | 7.3.3 |
| Config/testthat/edition: | 3 |
| NeedsCompilation: | no |
| Packaged: | 2026-07-23 01:54:48 UTC; khusmann |
| Author: | Kyle Husmann [aut, cre] |
| Maintainer: | Kyle Husmann <irid@kylehusmann.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-08-02 16:10:02 UTC |
irid: Component-Based 'Shiny' UI with Fine-Grained Reactivity
Description
A component-based reactive UI framework for 'Shiny' that binds reactive values directly to DOM attributes, enabling fine-grained updates without re-rendering or 'update*Input' callbacks.
Author(s)
Maintainer: Kyle Husmann irid@kylehusmann.com
See Also
Useful links:
Report bugs at https://github.com/khusmann/irid/issues
Define a case for Match()
Description
Define a case for Match()
Usage
Case(predicate, body)
Arguments
predicate |
One of: a function |
body |
A function |
Value
A case definition (a list).
Embed a DT DataTable output in a irid tag tree
Description
Shorthand for Output(DT::renderDT, DT::DTOutput, ...). Requires the
DT package.
Usage
DTOutput(fn, ...)
Arguments
fn |
A function that produces a DataTable. |
... |
Additional arguments passed to |
Value
A irid output node.
Define a default (fallback) case for Match()
Description
Sugar for Case(\() TRUE, body) — matches when no earlier Case does.
Place this as the last argument to Match.
Usage
Default(body)
Arguments
body |
A function returning the tag tree to render when no other
case matches. Same arity rules as |
Value
A case definition (a list).
Render a reactive list
Description
Iterates over a reactive list and calls fn for each item. The
callback receives a per-item callable (a mini-store for record items,
a scalar accessor for atomic items) and an optional position
accessor. The reconciliation strategy is selected by by:
Usage
Each(items, fn, by = NULL)
Arguments
items |
A reactive expression that returns a list. |
fn |
A function of |
by |
|
Details
-
Positional (
by = NULL, the default) — slot i is slot i. The list can grow or shrink at the end; in-place value changes update each slot's accessor without DOM recreation. -
Keyed (
by = \(x) x$id) — items are tracked across reorders, adds, and removes by their key. Kept items propagate new values through their mini-store (only changed leaves fire); reordered items have their DOM nodes moved (no recreation).
Records are projected as a per-item mini-store: item() reads the
whole record, item(record) writes it back, item$field() reads a
leaf, and item$field(v) is a synthetic setter that writes through
the parent. Scalars are passed as a per-item callable: item() reads,
item(value) writes back to the parent's slot.
Records may be heterogeneous in shape — different leaf trees per
entry. Each slot's mini-store is sized to its own item, and a
Match() inside the body dispatches on the discriminator:
Each(state$blocks, by = \(b) b$id, \(block) {
Match(block,
Case(\(b) b$type == "heading", \(b) Heading(b)),
Case(\(b) b$type == "paragraph", \(b) Paragraph(b)),
Case(\(b) b$type == "todo", \(b) Todo(b))
)
})
When a record's shape changes (different key set, or a sub-record's shape shifts), that one entry is torn down and rebuilt with the new mini-store. Shape-stable updates use the fine-grained in-place path.
Mixing records and scalars in the same list is rejected at flush
time as a likely data-modeling slip — wrap scalars in
list(value = ...) to mix them.
Value
A irid control-flow node.
Construct a widget — a wrapper for an arbitrary JavaScript library
Description
IridWidget() is the third irid process-tags citizen (alongside
control-flow nodes and Output). It emits a container element plus an
init record that mount turns into a widget-init op.
The client's irid.defineWidget("<name>", factory) registration is
looked up by name and called once per mount. The factory may return its
{update, destroy} handle directly or a Promise of it — make it async
and await whatever its construction needs first (a script-tag library
global, an ESM import, a WASM init). irid buffers updates during the
wait and disposes cleanly on a teardown mid-construction. See the JS-side
API in ARCHITECTURE.md.
Usage
IridWidget(
name,
props = list(),
events = list(),
deps = NULL,
container = NULL
)
Arguments
name |
Widget registry name, matching a JS-side
|
props |
Named list of props. Callable values are two-way-capable
bindings; non-callable values are init-only constants. |
events |
Named list of notifications (client → server), keyed by wire
event name. Each value is a handler, a |
deps |
Optional |
container |
Optional |
Details
Props are two-way-capable by default, exactly like DOM value /
checked. A callable prop (reactiveVal, store leaf, reactiveProxy,
...) reads inbound to the widget (server → client, routed to the
factory's update(key, value) hook) and accepts write-back: when the
widget JS calls setProp(key, value), irid writes the value through the
bound reactive (gated by writability — a read-only reactive's write is
dropped and the canonical value is snapped back). Whether a prop is
actually two-way depends on whether the widget JS pushes through
setProp; the snap-back machinery is latent until it does. A non-callable
prop rides in the init message as a constant and is never re-sent.
Wrap a prop in wire() only to tune its round-trip timing
(content = wire(content, wire_debounce(200))) — never to enable or
disable two-way. To react to a prop's change, observe the bound reactive
or pass a reactiveProxy; a bound prop is not also handled.
events carries genuine notifications the widget emits that correspond to
no prop (e.g. cursor-changed). Keys are the wire event names the widget
JS passes to sendEvent() (lowercase kebab-case by web CustomEvent
convention). Each value is a handler or a wire() (to tune timing);
NULL entries are dropped so optional handlers forward declaratively.
dom_opts is illegal on a widget event.
Value
A irid widget construct with class irid_widget.
Render the first matching case
Description
Evaluates cases in order against a bound value and renders the body of
the first case whose predicate is TRUE. Records are projected as a
mini-store for the active case's body; scalars are passed as the bare
callable. On active-case change, the previous case is torn down (its
reactives, DOM, and mini-store) and the new case is mounted fresh.
Usage
Match(callable, ...)
Arguments
callable |
The bound value — any 0-arg callable. Records (named lists) are projected as a mini-store; scalars are passed as the bare callable. |
... |
Details
Use a choice function as the leading callable to fold unrelated reactive state into a tagged variant on the fly:
Match(\() if (loading()) list(tag = "loading") else list(tag = "data", x = data()), Case(\(r) r$tag == "loading", \() Spinner()), Case(\(r) r$tag == "data", \(r) Items(r$x)) )
Conceptually, When() is a fixed-shape binary specialization of Match.
Value
A irid control-flow node.
Embed a Shiny render/output pair in a irid tag tree
Description
A generic wrapper that pairs a Shiny render function with its
corresponding output function. For common cases, use the convenience
wrappers PlotOutput(), TableOutput(), or DTOutput().
Usage
Output(render_fn, output_fn, fn, ...)
Arguments
render_fn |
A Shiny render function (e.g. |
output_fn |
A Shiny output function (e.g. |
fn |
A function passed to |
... |
Additional arguments passed to |
Value
A irid output node.
Embed a plot output in a irid tag tree
Description
Shorthand for Output(renderPlot, plotOutput, ...).
Usage
PlotOutput(fn, ...)
Arguments
fn |
A function that produces a plot. |
... |
Additional arguments passed to |
Value
A irid output node.
Reactive plotly output
Description
A first-class output primitive for interactive plotly charts, on par with
PlotOutput and TableOutput. Unlike the {plotly} htmlwidget — which
destroys and recreates the chart on every reactive update — PlotlyOutput
uses Plotly.react() for incremental updates, so a data change preserves
the user's zoom, pan, and selection (via plotly's uirevision).
Usage
PlotlyOutput(
spec,
...,
onClick = NULL,
onHover = NULL,
onUnhover = NULL,
onDoubleclick = NULL,
onDeselect = NULL,
onSelecting = NULL,
onBrushing = NULL,
onLegendClick = NULL,
onLegendDoubleclick = NULL,
onClickAnnotation = NULL,
onSunburstClick = NULL,
onRelayout = NULL,
container = NULL
)
Arguments
spec |
A zero-argument function returning a plotly object (from
|
... |
Named state arguments, each a callable ( A
|
onClick, onHover, onUnhover, onDoubleclick |
Discrete pointer callbacks. Each receives the (slimmed) plotly event payload. |
onDeselect, onSelecting, onBrushing |
Selection-lifecycle notifications.
|
onLegendClick, onLegendDoubleclick, onClickAnnotation, onSunburstClick |
Discrete interaction callbacks. |
onRelayout |
Escape hatch — receives the raw |
container |
Optional |
Details
PlotlyOutput is a thin wrapper over IridWidget. User-controllable UI
state (axis ranges, drag mode, selection, trace visibility) is exposed as
named reactive arguments, each a two-way prop: bind a reactiveVal,
store leaf, or reactiveProxy and the user's interaction writes back to it.
A reactiveProxy that rejects a write snaps the plot back. Discrete events
(clicks, hovers, legend interactions) are plain on* callbacks.
Value
An irid_widget construct.
Embed a table output in a irid tag tree
Description
Shorthand for Output(renderTable, tableOutput, ...).
Usage
TableOutput(fn, ...)
Arguments
fn |
A function that produces a table. |
... |
Additional arguments passed to |
Value
A irid output node.
Conditionally render content
Description
Renders the yes branch when condition is TRUE, and otherwise
(if provided) when it is FALSE. The active branch is fully mounted
and the inactive branch is destroyed.
Usage
When(condition, yes, otherwise = NULL)
Arguments
condition |
A reactive expression that returns a logical value. |
yes |
A 0-arg function returning the tag tree to render when the
condition is |
otherwise |
An optional 0-arg function returning the tag tree to
render when the condition is |
Details
Conceptually a fixed-shape binary specialization of Match():
When(\() cond, \() yes, \() no) is equivalent to
Match(\() cond, Case(TRUE, \() yes), Case(FALSE, \() no)).
Bodies are 0-arg functions that return tag trees — not tag trees
directly. When mounts and unmounts the active branch on transition,
so each activation must construct a fresh tag tree (the previous
branch's closures were torn down with its reactives). Reach for
Match() when the branch needs to consume the dispatching value.
Value
A irid control-flow node.
Create a pair of HTML comment anchors bracketing a control-flow range
Description
Comment nodes are legal children of any element (including <select>,
<table>, <tbody>, etc.) so they serve as invisible range markers
that the client can use to locate and mutate content without needing a
wrapper element.
Usage
anchor_pair(id)
Arguments
id |
The control-flow node ID. |
Value
An htmltools::HTML() fragment containing the start/end markers.
Test whether a callable can accept a write value
Description
Returns TRUE for any callable that can accept a positional argument
(reactiveVal, store leaf, reactiveProxy() with a setter, a primitive,
or a closure with at least one formal). Returns FALSE for read-only
callables (reactive(...), a \() expr closure, a reactiveProxy
built with no set), and for non-callables.
Usage
can_accept_write(fn)
Arguments
fn |
A value to test. |
Details
Gate a write through this when you don't control whether the callable the caller handed you is writable — used by the DOM autobind path and the synthesized widget two-way-prop write-back to silently skip writes to a read-only reactive rather than error.
Value
A length-1 logical.
Create a irid application
Description
Builds a Shiny app from a function that returns a irid tag tree. The function is called once per session so that each client gets its own reactive state.
Usage
iridApp(fn, ...)
Arguments
fn |
A zero-argument function that returns a irid tag tree (e.g. a
|
... |
Additional arguments passed to |
Value
A Shiny app object.
Run a irid example application
Description
Launches one of the example apps shipped with the package, mirroring
shiny::runExample(). The examples are the same apps published as editable
editors on the package website.
Usage
iridExample(
example = NA,
port = getOption("shiny.port"),
launch.browser = getOption("shiny.launch.browser", interactive()),
host = getOption("shiny.host", "127.0.0.1"),
display.mode = c("showcase", "normal", "auto")
)
Arguments
example |
Name of the example to run (e.g. |
port |
The TCP port the application should listen on. Defaults to
|
launch.browser |
If |
host |
The IPv4 address the application should listen on. Defaults to
|
display.mode |
The display mode. Defaults to |
Details
Called with no argument (or an unrecognised name), it lists the available examples instead of launching one.
Some examples depend on packages beyond irid's hard dependencies (e.g.
bslib, plotly). The runner checks for these up front and, if any are
missing, stops with a message naming what to install. A couple of examples
(codemirror, plotly) also load JavaScript from a CDN at runtime, so they
need an internet connection even when run locally.
By default the app runs in Shiny's showcase display mode, so the annotated
source appears beside the running app (as with shiny::runExample()). Pass
display.mode = "normal" to run the app on its own.
The remaining arguments mirror shiny::runExample().
Value
If example is supplied, launches the app and blocks until it is
stopped. Otherwise returns the available example names invisibly as a
character vector (after listing them).
Examples
# List the available examples
iridExample()
if (interactive()) {
iridExample("todo")
}
Create a irid UI output placeholder
Description
Creates a shiny::uiOutput() with the irid JavaScript dependency
attached. Use this in a standard Shiny UI to mark where renderIrid()
should inject its content.
Usage
iridOutput(id)
Arguments
id |
The output ID, matching the corresponding |
Value
An HTML tag with the irid dependency.
irid JavaScript dependency
Description
Returns an htmltools::htmlDependency() for the client-side irid
runtime (irid.js).
Usage
irid_dependency()
Value
An html_dependency object.
Create a local ID counter for use within a single process_tags call
Description
Create a local ID counter for use within a single process_tags call
Usage
irid_id_counter(prefix = "irid")
Value
A function that returns the next ID each time it is called.
Mount a pre-processed irid tag tree
Description
Takes the output of process_tags() and wires up Shiny observers for
reactive attribute bindings, event listeners, Shiny outputs, and
control-flow nodes (When, Each, Match).
Usage
irid_mount_processed(result, session, depth = 0L)
Arguments
result |
A list returned by |
session |
A Shiny session object. |
depth |
Nesting depth used to compute binding priority. Top-level
mounts ( |
Details
Binding observers run at priority = -100 + depth, so deeper-nested
bindings fire before shallower ones in the same flush. Control flow
observers stay at the default priority 0 and always fire first. This
guarantees that on initial mount (and on any cascading re-render),
content inserted by a control flow is fully populated by its inner
bindings before any parent attribute binding observes it. The motivating
case is <select value=rv> whose options come from Each — the parent's
value binding must fire after the options exist and have their
value attributes set, otherwise the browser silently sets
selectedIndex = -1 and the select renders blank.
Value
A mount handle with $tag (the processed HTML) and $destroy()
(a function that tears down all observers).
Test whether a value is a irid-reactive function
Description
Returns TRUE for any callable irid treats as reactive — plain
functions, Shiny reactives, reactiveStore nodes, and reactiveProxy
wrappers. Used by process_tags() to decide which attributes participate
in auto-bind / event extraction.
Usage
is_irid_reactive(x)
Arguments
x |
An object to test. |
Value
Logical.
Mini-store projection over a record
Description
Builds a reactiveStore-shaped callable tree that projects a single
record out of a parent collection. Reads route through get_record();
writes (whole-record or per-field synthetic setters) route through
set_record(). The mini-store never owns the record's state – the
parent collection is the single source of truth.
Usage
make_mini_store(get_record, set_record, scope)
Arguments
get_record |
A 0-arg reactive callable that returns the current record (a fully named list). |
set_record |
A 1-arg function called with the new record on whole-record write or after a per-field synthetic setter has built the complete record. |
scope |
A scope from |
Details
Used by Each (record items) and Match (record bound value) to
project fine-grained leaf reactivity out of a coarse-grained parent.
Recursive – nested named lists in the initial record become sub-mini-stores
(so mini$user$name(v) writes through the same chain as
mini$user(<full-user>) would).
Each level threads a sub-get/set pair down the tree; writes at any
depth fan out through the chain of synthetic setters until they reach
the user-supplied set_record. Shape uses the same shared
branch-vs-leaf rules as reactiveStore() (is_branch, is_bare_list,
strip_asis are reused), and the same recursive validate_write()
enforces "no unknown keys, no missing keys" at every level on
whole-record writes.
Shape is strict. Writes are validated against the keys captured
at construction – same contract as a reactiveStore() branch.
Callers that need to change a slot's shape reshape the parent
collection directly (e.g. for Each(items, ...), write the new
items() with the slot already replaced); the outer reconciler
observes the parent change and rebuilds this mini-store with a
fresh leaf tree of the new shape on the same flush.
Writes replace, like reactiveStore(). A branch write must
include every locked key; missing keys are an error. The complete
record is what reaches set_record. Per-field writes
(mini$field(v)) remain the dedicated single-slot path – use them
when you want to update one field without naming the rest. Dropping
a field is a parent-level operation: write the reshaped collection
through the source callable (items(...)) and the outer reconciler
rebuilds.
Internally, every leaf holds a reactiveVal kept in sync with the
parent by a single root-level propagating observer. The observer
walks the tree top-down via each branch's internal set_internal,
which recurses to children's set_internal; only at leaves does an
actual reactiveVal write occur, gated by identical(old, new).
This is what delivers the "only changed fields fire" promise – the
diff happens inside the projection rather than at the call site, so
callers can't get it wrong.
Value
A callable with class c("reactiveStore", "reactive", "function"),
shaped like a reactiveStore branch – mini() reads the record,
mini(record) writes it, mini$field() reads a leaf or sub-branch,
and mini$field(v) writes through the parent.
Per-item / per-case reactive scope
Description
Wraps a Shiny session into a small "scope" object whose responsibility is
to bound the lifetime of reactives and observers created inside per-item
(Each) or per-case (Match) mounts. Every per-item / per-case mount
creates one scope at construction; on unmount, scope$destroy() tears
down everything created against it.
Usage
make_scope(session, id)
Arguments
session |
A Shiny session, a child scope proxy, or |
id |
Scope identifier for the shiny#4372 child scope (a non-empty string; the caller's element/wrapper counter token). Required — every call site already has a unique counter token. Unused on the fallback path, but always supplied so the scope's identity is explicit. |
Details
Two implementations, chosen by runtime feature detection.
-
shiny#4372 path (when
session$onDestroy/session$destroyexist):make_scopeallocates a child scope viasession$makeScope(id). Reactive primitives constructed while that child is the default reactive domain auto-register a weak destroy handle against it, sochild$destroy()reclaims observers andreactiveVals in one call.with_scope(expr)runsexprunder the child domain (shiny::withReactiveDomain);register_observeris a no-op (auto-tracked). -
Fallback path (any shiny without #4372): a thin manual tracker. Observers register via
register_observerand are torn down indestroy.with_scopeis identity —reactiveVals created inside still leak until session end (no public API to destroy areactiveValpre-#4372). This is today's behavior.
The seam is forward-compatible: every per-item / per-case reactive is
constructed through with_scope, so the same call sites reclaim correctly
once a user upgrades to a shiny carrying #4372 — no irid change required.
Teardown ordering. On unmount, callers MUST destroy the per-item
/ per-case mount handle before the scope. The mount's observers
(auto-bind bindings, event handlers) read mini-store / slot-accessor
leaves that live in the scope; under #4372 a destroyed leaf throws on
access (it is actively destroyed, not lazily GC'd), so tearing the scope
down first would make those observers error on their next read. The order
is mount → scope at every site that owns both.
shiny#4372: https://github.com/rstudio/shiny/pull/4372 merged 2026-05-29,
not yet on CRAN as of this writing. Verified against shiny 1.13.0.9000
(HEAD 44fd783); re-confirm method names / weak-ref semantics on bump. Every
site that depends on this seam is tagged # shiny#4372: for grep-ability.
Value
A list with session, register_observer(obs),
with_scope(expr), and destroy().
Scalar slot accessor for Each (positional and keyed)
Description
Builds the scalar-item analogue of make_mini_store() – a callable
(reactiveProxy) over a single value held in the parent collection.
Reads route through an internal reactiveVal that is kept in sync with
get_value() by a propagating observer; writes route through
set_value() to the parent. The internal reactiveVal is what gives
fine-grained reactivity: when the parent list is patched somewhere
else, an unchanged slot's observers don't fire.
Usage
make_slot_accessor(get_value, set_value, scope)
Arguments
get_value |
0-arg callable returning the slot's current value. |
set_value |
1-arg function called with the new value on write. |
scope |
Scope from |
Value
A reactiveProxy that reads from the internal leaf and writes
through set_value.
Overlay one wire over another
Description
Override-wins overlay used where a widget wrapper layers a caller's input
over its own default config. Each field of y (subject, timing,
coalesce, dom_opts) wins when non-NULL; otherwise x's field
carries through. y may be NULL (identity) or a bare callable (fills in
only the subject), both normalized first.
Usage
## S3 method for class 'irid_wire'
merge(x, y, ...)
Arguments
x |
The default |
y |
The override: a |
... |
Unused. |
Details
Extends the base merge() generic rather than introducing a new one.
Value
A wire.
Plotly html dependencies for PlotlyOutput
Description
The plotly.js bundle (sourced from the suggested {plotly} package) plus the
irid-plotly factory script, carried by PlotlyOutput() as its widget deps
and delivered at mount time via insertUI (see deliver_widget_deps in
mount.R).
Usage
plotly_dependency()
Value
A list of html_dependency objects.
Walk a tag tree and extract reactive bindings
Description
Recursively walks an HTML tag tree, replacing reactive attributes and
event handlers with plain IDs. Returns the cleaned tag along with lists
of bindings, events, control-flow nodes, and Shiny outputs to be mounted
by irid_mount_processed().
Usage
process_tags(tag, counter = irid_id_counter())
Arguments
tag |
A Shiny tag, tag list, or irid control-flow node. |
Value
A list with elements $tag, $bindings, $events,
$control_flows, and $shiny_outputs.
Wrap a reader and optional writer as a callable
Description
Builds a callable proxy from a 0-arg get reader and an optional
1-arg set writer. The proxy is itself a callable: proxy() invokes
get(), proxy(value) invokes set(value). Auto-bind treats the
proxy like any other callable, so it composes with value and
checked props without any special handling.
Usage
reactiveProxy(get, set = NULL)
Arguments
get |
A 0-arg callable returning the read value. Typically a
|
set |
A 1-arg function called with the incoming value on write,
or |
Details
set is a side-effectful handler, not a pure transform. It receives
the incoming value and decides what to do — write to a target, write
a transformed value, set an error flag, trigger a side effect, or
drop the write entirely. Because set is a closure, it can read
sibling state for cross-field validation.
Pass set = NULL (or omit it) to make the proxy read-only — writes
are silently dropped. With auto-bind, this lets the input snap back
to the current value via the optimistic-update protocol.
Proxies compose: a proxy is itself a callable (0-arg → get, 1-arg →
set), so another reactiveProxy can use it as either its get or
its set.
Value
A callable with class c("reactiveProxy", "reactive", "function").
Hierarchical reactive state container
Description
Builds a callable hierarchical state tree from a nested list. The shape
rule is simple: a bare named list (length > 0) becomes a navigable
reactiveStore node (every sub-tree is itself a reactiveStore);
everything else becomes a plain shiny::reactiveVal() leaf that accepts
any value on write. To force a bare named list to be treated as a leaf
instead of a store, wrap it in base::I().
Usage
reactiveStore(initial)
Arguments
initial |
A bare named list describing the initial shape.
Sub-positions classify as follows: bare named lists (length > 0)
become store sub-nodes; anything else (scalars, vectors, |
Details
Every node is callable: node() reads, node(value) writes. Branch
writes replace: the input must list every locked key for that branch.
Both unknown keys and missing keys are an error. Types are not enforced.
Per-field writes (node$key(value)) remain the dedicated single-slot
path — use them when you want to update one field without naming the
rest.
length() on a leaf returns 1 (the underlying reactiveVal is a
callable, and neither R's closure default nor shiny override
length). htmltools' dropNullsOrEmpty calls length() on every
attribute value, so this is what lets value = state$leaf survive
tag construction. Use length(leaf()) for the underlying length.
Value
A callable reactiveStore node with class
c("reactiveStore", "reactive", "function"). Sub-trees share the
same class. Leaf positions are plain shiny::reactiveVal()s with
class c("reactiveVal", "reactive", "function") — the shared
"reactive" class is what auto-bind dispatches on.
Shiny reactive primitives
Description
These functions are re-exported from shiny for convenience, so you can use them in irid apps without loading shiny explicitly.
Details
-
reactiveVal(): Create a reactive value -
reactive(): Create a reactive expression -
observe(): Create an observer -
observeEvent(): Create an event-driven observer -
isolate(): Run an expression without reactive dependencies -
tags: HTML tag builder
-
tagList(): Combine tags into a list
See the shiny documentation for full details.
Value
The return values are those of the underlying shiny functions:
-
reactiveVal()returns a function that reads the current value when called with no argument and sets it when called with one value. -
reactive()returns a reactive expression (a function of classreactiveExpr/reactive) that yields the cached expression value when called. -
observe()andobserveEvent()return an observer reference object (classObserver) invisibly; they are called for their side effects. -
isolate()returns the value ofexpr, evaluated without taking a reactive dependency. -
tagsis a named list of functions that construct HTML tag objects, andtagList()returns a list of tags (classshiny.tag.list).
Render irid content inside a Shiny app
Description
A render function for use with iridOutput(). Evaluates expr to
produce a irid tag tree, processes it, and mounts reactive bindings and
event handlers after the UI is flushed.
Usage
renderIrid(expr, env = parent.frame(), quoted = FALSE)
Arguments
expr |
An expression that returns a irid tag tree. |
env |
The environment in which to evaluate |
quoted |
If |
Value
A shiny::renderUI() result.
Materialize a node's own htmltools render hooks / tag function
Description
bslib (and htmltools generally) defer structure to render time: a
shiny.tag can carry .renderHooks that build its final DOM
(layout_sidebar()'s grid wrapper, card()'s fill plumbing, ...), and
a shiny.tag.function produces its tags only when called. process_tags()
rebuilds each tag from name/attribs/children, so without running
these first the deferred wrapper is silently dropped (see GH #27).
Usage
resolve_render_hooks(node)
Arguments
node |
A node from the tag tree. |
Details
This runs only the node's own hooks (one level), looping until the node
is a resolved tag / non-tag. Unlike htmltools::as.tags() it does not
recurse into children — process_tags' walker descends into them itself
and resolves each child's hooks as it arrives. That separation is what
lets irid's own children (reactive functions, irid_output,
irid_widget, control-flow nodes) survive: they never carry render hooks,
so they pass straight through, while a child hook that moved them into a
new wrapper still gets walked.
Value
The node with its top-level render hooks / tag function resolved.
Serialize a plotly object to the JSON string PlotlyOutput ships
Description
Pre-encodes with plotly's own to_JSON (digits/encoding identical to what
the {plotly} htmlwidget itself ships) and returns a plain JSON string.
The substrate's encoder then ships the string verbatim; the JS side
JSON.parses it. Works for both plot_ly() and ggplotly() — both build
to the same structure.
Usage
to_plotly_spec(p, require_ids = FALSE, require_names = FALSE)
Arguments
p |
A plotly or ggplotly object. |
require_ids |
When |
require_names |
When |
Value
A length-1 character vector of JSON.
Event & binding dispatch config
Description
Configure how an event handler or value binding is dispatched between the
browser and the server. A single per-slot carrier, wire(), rides
the slot it configures — an on* handler slot or a value/checked
binding slot — so an event's timing, backpressure, and DOM-listener
options live next to the handler/reactive they govern rather than in
separate element-level lists.
Usage
wire_immediate()
wire_throttle(ms, leading = TRUE)
wire_debounce(ms)
wire_dom_opts(
prevent_default = FALSE,
stop_propagation = FALSE,
capture = FALSE,
passive = FALSE,
filter = NULL
)
wire(subject = NULL, timing = NULL, coalesce = NULL, dom_opts = NULL)
Arguments
ms |
Minimum interval (throttle) or quiet period (debounce) in milliseconds. |
leading |
If |
prevent_default |
Call |
stop_propagation |
Call |
capture |
Register the listener in the capture phase. |
passive |
Register the listener as passive. |
filter |
A JavaScript expression string, evaluated client-side with
the event object bound to |
subject |
The handler or reactive the wire configures. The slot
decides which: a bare callable means "bind" in |
timing |
An |
coalesce |
Logical scalar, or |
dom_opts |
A |
Value
wire() returns an irid_wire; the timing constructors
return an irid_wire_timing; wire_dom_opts() returns an irid_dom_opts.
Timing shapes
The timing constructors are pure shapes — they describe when an event fires and carry no other config:
-
wire_immediate(): Fires on every event with no rate limiting. -
wire_throttle(ms, leading): Fires at most everymsmilliseconds while the event is active. -
wire_debounce(ms): Waits until the user pauses formsmilliseconds before firing.
When a wire carries no timing, irid applies a per-event default keyed on
the DOM event name: input → wire_debounce(200) (typing produces a
flood of intermediate values); the high-frequency continuous streams
(mousemove, pointermove, touchmove, drag, dragover, scroll,
wheel, resize) → wire_throttle(100) (paced "latest position"
stream, with the derived coalesce = TRUE keeping it from outrunning the
server); every other event → wire_immediate(). Explicit timing always
wins over the default.
Backpressure (coalesce)
coalesce is universal across timing modes, so it lives on the carrier,
not in the shapes. When TRUE, dispatch gates on server-idle state so
events never queue faster than the server can process them. When NULL
(the default), it derives from the timing mode: FALSE for
wire_immediate(), TRUE for wire_throttle() / wire_debounce().
DOM listener options
wire_dom_opts() bundles the DOM-only listener flags. It is legal only
where the event is backed by a real DOM listener (a plain tag, a custom
element emitting cancelable events); placing it on a widget-emitted event
errors. Whether prevent_default has any effect further depends on the
event being cancelable — a runtime fact.
filter is a JavaScript expression string evaluated client-side with the
DOM event object bound to e. When it evaluates falsy the event is
dropped entirely — no prevent_default/stop_propagation, no server
round-trip — so a handler that only cares about some events never floods
the server with the rest. For example, an onKeyDown that acts only on
Enter takes filter = "e.key === 'Enter'".
The event object
The event argument passed to handlers is a list containing all
primitive-valued properties (string, numeric, logical) from the browser
event object, plus these element properties:
valueThe element's current value (character).
valueAsNumberNumeric value of the element, or
NAif the input is empty or non-numeric (e.g. a blank text box). Useful for range and number inputs.checkedLogical, for checkbox and radio inputs.
Keyboard events additionally include key, code, ctrlKey,
shiftKey, altKey, and metaKey.