StatifierUI.Live.ExpressionInput (StatifierUI v0.3.0)

Copy Markdown View Source

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:

AttributeOnMeaning
data-completionsthe inputthe full completion list as JSON, the hook's whole input
data-completion-countthe inputhow many were offered
data-vocabularythe inputwhether the grammar half resolved
data-candidatesthe inputhow 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

expression_input(assigns)

@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-hook name; nil renders the datalist-only field. Defaults to "StatifierUIExpressionInput".
  • placeholder (:string) - Defaults to "an expression".
  • class (:string) - Defaults to nil.
  • vocabulary_opts (:list) - passed to Predicator.Vocabulary.functions/1 - a host's own providers. Defaults to [].
  • field (:any) - accepted from the sb seam and never read. Defaults to nil.

hook_name()

@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"