Register a new keyword (e.g. pct, color, date) that ggpaintr will
recognise as a substitutable token in a formula. The keyword's widget
is built by build_ui; the widget's value is turned back into the R
code spliced into the rendered call by resolve_expr. See
vignette("ggpaintr-tutorial") § "Defining your own placeholders" for
the lifecycle walk-through, signatures table, and runnable
ptr_app() examples — this help page is reference.
Arguments
- keyword
Single non-empty string. Must be a syntactically valid R name (passes
make.names()) and not an R reserved word. This is the token users type in the formula, e.g.geom_point(alpha = pct).- build_ui
function(node, label = NULL, selected = NULL, named_args = list(), ...)returning a Shiny tag. Passnode$idas the underlying widget'sinputId. Readnode$keywordandnode$paramif you need them. The framework also passes anyui_text_defaultsfield you declare by name (help,placeholder,empty_text) — or accept acopy = NULLlist and read them off it. If you declare anamed_argsformal (or accept...), the framework injects the validatedparse_named_argsvalues as a named list — soparse_named_args = list(step = ptr_arg_numeric())makesppPct(step = 5)arrive asnamed_args$step(the canonical post-validator value, not the raw call). Every injected argument is opt-in: the injector checks your formals, so name the ones you use and keep...to swallow the rest. Always end the signature with....Widget-seeding contract —
selected(authoritative; the fourinvoke_build_uicall sites inR/paintr-server.Rimplement what is described here, with short pointers back to this docblock).Declare
selected = NULLas an explicit formal, not a no-defaultselected(or accept...). The framework callsbuild_uion every renderUI fire and deliversselectedper this precedence:First render,
spec=seed present → seed value.First render, no spec, positional default present (e.g.
ppPct(50)) →node$default.First render, no spec, no positional default → framework omits
selected; yourselected = NULLformal default applies.Subsequent renders (Update Plot click, upstream change, layer toggle, …) →
current-inputverbatim. Thespec=seed is boot-only: it wins on the first render only and never participates again, so an upstream-triggered re-render can never snap the widget back to the seed and away from the user's live pick. If the user emptied the widget,current-inputis whatever the widget emits on clear — one ofNULL,character(0),"",NA_real_,NA_character_, depending on the widget — and the framework coercesNULLtocharacter(0)so you always receive a value on subsequent renders.
Because the framework omits the argument on the first-render-no- default path, a hook signature without a formal default for
selectedaborts there withargument "selected" is missing.Render empty when
selectedis empty. Treat any ofNULL,character(0),"",NA_real_,NA_character_as "the user wants this widget empty" and render the widget empty. Do not substitute a hardcoded fallback insidebuild_uifor the empty case — that re-introduces the deselect-snaps-back-to-default defect the framework's seeding layer is designed to prevent (closure-flag inptr_setup_value_uis()/ptr_setup_consumer_uis()/ptr_bind_shared_consumer_uis()). Boot-time defaults belong in the formula, e.g.ppPct(50), and reach you throughselected, not through a hardcoded constant in the hook body.Never read
node$defaultdirectly. It is exposed only so the framework can route it intoselectedon the boot fire. Reading it inside the hook bypasses the precedence chain and the closure- flag persistence guards, and will re-clobber a user-cleared widget on every Update Plot click.- resolve_expr
function(value, node, ...)returning the R expression spliced into the rendered call.valueisinput[[node$id]]— whatever Shiny stores for that widget. Allowed return types: scalar atomic (numeric / character / logical / integer),name/symbol(build withrlang::sym()),call/language(build withrlang::ppExpr()), orNULLto prune the argument from the rendered call. UseNULLfor empty / not-yet input; throw withrlang::abort()for malformed input.- validate_session_input
Optional
function(value, ctx)called beforeresolve_expr. ReturnTRUE/NULLto accept; return a single character string to reject (surfaced inline as the error message, layer pruned).ctxis a plain list with named fields:node(the placeholder AST node, carries$id,$keyword,$args),keyword(convenience alias fornode$keyword),upstream_cols, anddata. For a value placeholder,ctx$upstream_colsandctx$dataare alwaysNULL— value placeholders have no upstream column scope by definition. They are present in the signature so the same validator shape works across all roles; seeptr_define_placeholder_consumer()for the data-aware role where those fields are populated. ggpaintr invokes this function asvalidate_input(value, ctx)— no other positional or named arguments are passed, andctxcarries exactly the four fields above. The signature does not require....- parse_positional_arg
Optional validator closure for the (single) positional argument the keyword accepts inside a formula.
NULL(default) means positional arguments are rejected at translate time. A function receives the unevaluated AST and must return a canonical value orrlang::abort(). Validators are expected to operate on the AST only and not calleval(); ggpaintr trusts the author. Authors who eval in a validator are opting into the risk of running user code at translate time.- parse_named_args
Named list of validator closures for additional named arguments beyond the reserved
shared = .... Each entry's closure receives the unevaluated AST and returns a canonical value orrlang::abort(). The canonical values are delivered tobuild_uias itsnamed_argslist (and are also carried onnode$named_args, soresolve_expr/validate_session_inputcan read them offnode). Default islist()(no named args). The name"shared"is reserved and may not appear here.- embellish_eval
Optional
function(x, ...)body used when the placeholder keyword is also called as a plain-R function (outside a formula context) — i.e. how a placeholder-embellished ggplot expression evaluates with no app. WhenNULL(default), the identity fromembellish_identity()(function(x, ...) x) is supplied — callingpct(0.5)returns0.5unchanged. Override to give the keyword a non-identity plain-R meaning (e.g.embellish_symbol_to_string()). The same callable is returned to the caller of this helper so authors can bind it under the same name as the keyword:ppPct <- ptr_define_placeholder_value(keyword = "ppPct", ...).- ui_text_defaults
Named list of single non-NA character defaults feeding the
ui_texttree. Allowed names:label,help,placeholder,empty_text. Strings may contain{param}, which is interpolated to the surrounding formal-argument name at render time.
Value
The runtime callable. Default for a value placeholder is the
identity function(x, ...) x; override with embellish_eval = .... The
helper is also called for its registration side effect — use
ptr_clear_placeholder() to remove the entry.
Details
Three roles. Pick this constructor for a value placeholder (a
self-contained widget like a slider, color picker, or numeric input).
Use ptr_define_placeholder_consumer() when the widget needs the
upstream column names (column pickers). Use
ptr_define_placeholder_source() when the widget produces the data
the rest of the formula reads from (file upload, dataset chooser).
See also
vignette("ggpaintr-tutorial") § "Defining your own placeholders";
ptr_define_placeholder_consumer(), ptr_define_placeholder_source(),
ptr_clear_placeholder().
Examples
# A percentage placeholder: user types a number 0-100; we splice
# the fraction 0-1 into the rendered call. The hook reads `selected`
# to honor a formula default like `pct(75)` at boot and to keep a
# user-cleared widget empty across Update Plot fires (do NOT
# substitute a hardcoded fallback when selected is empty).
ptr_define_placeholder_value(
keyword = "pct",
build_ui = function(node, label = NULL, selected = NULL, ...) {
n <- suppressWarnings(as.numeric(selected))
initial <- if (length(n) == 1L && is.finite(n)) n else NA_real_
shiny::numericInput(node$id, label = label %||% "Percent",
value = initial, min = 0, max = 100, step = 1)
},
resolve_expr = function(value, node, ...) {
if (length(value) != 1L || !is.finite(value)) return(NULL)
value / 100
},
parse_positional_arg = ptr_arg_numeric(), # accept ppPct(50)-style defaults
ui_text_defaults = list(label = "Percent for {param}")
)
#> function (x, ...)
#> x
#> <bytecode: 0x5558f1b3ec88>
#> <environment: 0x5558f470e5e0>
ptr_clear_placeholder("pct")
#> ✔ Cleared placeholder: "pct".
