An expression field with completion: predicator's grammar and the host's declared datamodel paths, offered at the caret.
This is the affordance statifier_blocks ADR-0005 decision 15 defers here -
"Rich expression editing is statifier-ui's" - and it is written to drop
straight into that package's expression_component seam:
<StatifierBlocks.Editor.editor
...
expression_component={&StatifierUI.Live.ExpressionInput.expression_input/1}
path_candidates={StatifierBlocks.Datamodel.candidates(document, datamodel)}
/>Nothing here depends on statifier_blocks, and nothing here knows what a
block is. The seam hands this component a five-key map - :field, :id,
:name, :value, :candidates - and :field is the one key it
deliberately ignores: reading it would mean knowing that package's view
model, and ADR-0004's dependency arrow points the other way. Any host with
an expression to edit can call it the same way.
The assigns arrive as a bare map
The seam calls the override as a plain one-argument function rather than
through a HEEx component tag, so attr defaults never run. Every assign
outside those five keys is defaulted here, in normalize/1, which is what
makes the same function usable both ways - <.expression_input .../> in a
template and component.(%{...}) from the seam.
Edits round-trip through the host's own form
The rendered <input> carries the name it was handed and nothing else:
no phx-change, no phx-target, no event of its own. In the sb editor
that input sits inside <form phx-change="config-change">, so an edit is
already the host's event, arriving with every other field. The hook keeps
that true - after it writes a completion in, it dispatches a bubbling
input event, so a keyboard-driven completion and a typed character are
the same event to the host.
Two affordances, and the second one is optional
Without JavaScript the field is a text input bound to a <datalist> of the
word-shaped completions - the same affordance sb's plain control gives for
paths, widened to the grammar. With assets/js/expression_input.js
registered as the StatifierUIExpressionInput hook (ADR-0009: the
JavaScript ships as source and the host's bundler compiles it), the hook
drops the datalist and offers a caret-aware list instead: the token under
the cursor is the prefix, arrow keys move, Enter or Tab inserts.
A host that registers no hook loses the popup and keeps the field. A
predicator without Predicator.Vocabulary loses the grammar entries and
keeps the declared paths. Both states are stamped on the element rather
than inferred - data-hook and data-vocabulary - so a page that offers
nothing says which of the two reasons it is.
What it stamps
Per ADR-0007's data-attribute contract, the rendered structure is the testable surface:
| Attribute | On | Meaning |
|---|---|---|
data-completions | the input | the full completion list as JSON, the hook's whole input |
data-completion-count | the input | how many were offered |
data-vocabulary | the input | whether the grammar half resolved |
data-candidates | the input | how many declared paths were supplied |
Summary
Functions
The field: an input, its completion data, and the no-JavaScript datalist.
The hook name a host registers for the completion popup:
"StatifierUIExpressionInput".
Functions
@spec expression_input(map()) :: Phoenix.LiveView.Rendered.t()
The field: an input, its completion data, and the no-JavaScript datalist.
See the module doc for the seam this is written against and for what the rendered element stamps.
Attributes
id(:string) (required) - DOM id of the input itself.name(:string) (required) - form field name; the host's form owns the event.value(:string) - the expression source. Defaults to"".candidates(:list) - declared datamodel paths, from the host. Offered ahead of the grammar. Defaults to[].hook(:string) -phx-hookname;nilrenders the datalist-only field. Defaults to"StatifierUIExpressionInput".placeholder(:string) - Defaults to"an expression".class(:string) - Defaults tonil.vocabulary_opts(:list) - passed toPredicator.Vocabulary.functions/1- a host's own providers. Defaults to[].field(:any) - accepted from the sb seam and never read. Defaults tonil.
@spec hook_name() :: String.t()
The hook name a host registers for the completion popup:
"StatifierUIExpressionInput".
It is the name assets/js/index.js exports and the name this component
renders as phx-hook, returned as a function so a host's app.js and a
test can name the same string without copying it.
Examples
iex> StatifierUI.Live.ExpressionInput.hook_name()
"StatifierUIExpressionInput"