Skip to content

feat(interactions): interaction_spec for chart interactions - #124

Merged
Chenglong Wang (Chenglong-MS) merged 9 commits into
devfrom
feat/interactive-api
Sep 20, 2026
Merged

Chenglong Wang (Chenglong-MS) merged 9 commits into
devfrom
feat/interactive-api

Conversation

@xavier-shaw

@xavier-shaw xavier (xavier-shaw) commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

A chart spec can now request interactions. The new top-level field interaction_spec lists presets by name with their options. Each name maps to the factory that code already calls, so a spec entry and a factory call produce the same interaction. When a chart cannot support an entry, the mount drops it with a warning and the chart still renders.

"interaction_spec": {
  "interactions": [
    { "type": "legend-toggle" },
    { "type": "click-highlight", "options": { "dimOpacity": 0.2 } },
    { "type": "navigate", "options": { "axes": "x", "pan": false, "reset": ["double-click", "escape"] } }
  ]
}

Design notes: docs/design-interaction-spec.md.

What changed

  • core/interaction-spec.ts: the InteractionSpec type and interaction_spec on ChartAssemblyInput.
  • A preset registry and resolveInteractionSpec(). The resolver rejects unknown types, options outside options, missing required options, and duplicate ids.
  • admitInteractions(), extracted from addVegaLiteInteractions(). Code definitions throw as before. Spec entries drop with a ChartWarning.
  • composeInteractiveOptions() merges spec and code entries. Code wins on shared keys. A shared id is an error.
  • reset: every stateful preset accepts a list of click-none, double-click, escape. One dispatcher in the Vega runtime replaces the global dismiss policy.
  • Site: a Spec test cases tab that mounts every Test cases card from interaction_spec.

Verification

  • 1603 tests pass, 42 of them new.
  • Headless browser: the Test cases tab and the Spec test cases tab report the same status on all 837 cards.
  • Manual check of each reset gesture on the Spec test cases tab.

Per-chart-type admission, validateChart(), and the MCP schema follow in #127.

🤖 Generated with Claude Code

Adds the JSON shape for declarative interactions beside chart_spec and
theme_spec: a list of preset entries { type, id?, options }, retained
updates applied at mount, and the surface policies. The four policy
interfaces move to core so the contract needs no runtime import;
flint-chart/interactive re-exports them under their old names. Includes
the design document that records the decisions.
INTERACTION_PRESETS maps each preset name to the factory code calls
today, with the capability it needs and the gesture it uses.
resolveInteractionSpec turns interaction_spec entries into the same
CanvasInteractionDef values, tagged origin 'spec', with the type name as
the default id. It rejects an unknown type, a flat option outside
"options", a missing required option, and a duplicate id, and names the
entry in every message.
The checks addVegaLiteInteractions made inline move to
admitInteractions. A code definition still throws with the same
message. A spec entry the chart cannot honour is dropped and reported as
a ChartWarning; in a pan-versus-drag conflict the later entry yields.
The plan carries the admitted list and the warnings, the Vega mount runs
the admitted list, and the renderer exposes the warnings.
composeInteractiveOptions merges the spec with the code options: the
spec comes first, an id shared by both sources is an error, the code
wins on the surface policies, and a backend that runs no interactions
ignores the spec with one info warning. The surface exposes
surface.warnings, resolved after ready, and logs the list once.
…nteraction_spec

The Test cases lab gains a `source` and a second route, spec-test-cases,
that mounts every case from `input.interaction_spec` instead of factory
calls. `modeSpec()` mirrors `modeInteractions()` entry for entry, with the
surface policies in the spec. Each card shows the JSON it mounted from in
a foldable, token-coloured panel with a copy button, reports dropped
entries in a callout, and the page tallies ready and dropped cards. A
headless comparison of both tabs found no status difference across 23
modes and 837 cards.
@Chenglong-MS

Copy link
Copy Markdown
Contributor

Let's move update / dismiss out of the interaction API?

Retained state leaves the spec. An update says what the chart shows now
and arrives from outside: a host seeds a view, a story scrolls to a
sentence, an agent answers a question. The code already treats it that
way through options.updates, applyUpdate, setUpdates, and dispatch, so
the spec keeps interactions and the surface policies and nothing else.
The design document records the decision and notes the next step: a
per-interaction reset list that replaces the global dismiss policy.
… state

Step 1 of replacing the global dismiss policy with a per-interaction
reset. Three gestures, defined by what the reader does and nothing else:
click-none (a click whose hit resolves to no element), double-click
(anywhere), and escape. Every preset that retains state accepts `reset`
and normalises it onto its definition; presets that retain nothing have
no such option. The registry records each preset's supported and default
lists, the resolver rejects an unknown or unsupported gesture by name,
and navigate renames click-background to click-none (a margin click now
resets too). The runtime still honours only navigate's list; the
dispatcher that reads the others is step 2.
…r interaction

Step 2 of replacing the global dismiss policy. A click that hits nothing,
a double-click, or Escape now resets only the interactions whose reset
list holds that gesture, each by its own id. Host updates and the other
interactions keep their state; legend-toggle drops its closure through
onReset(). navigate flies home through its navigation path and the
stateful brush clears through the gesture's own reset(), so the special
double-click and Escape paths in the two gesture modules go away. A chart
with an escape reset becomes focusable and takes focus on a pointer
press, so Escape reaches the chart the reader touched last and no other.
double-activate next to a double-click reset is an admission conflict.
options.dismiss survives as a deprecated code option that maps onto every
interaction that resets by default.
…t lists own it

The global dismiss policy is gone from interaction_spec, from
buildInteractiveChart, the surface, and the Vega renderer options, and
the InteractionDismissPolicy type with it. Each interaction's reset list
already decides what a click on nothing, a double-click, or Escape
clears. The resolver rejects a spec that still carries dismiss or
updates and points at the replacement. The two callers in the repo
needed nothing in exchange: the Test cases lab passed the values the
presets default to, and the you-draw-it demo mounts a hand-built
definition with no reset list plus host updates, which no gesture
resets.
@xavier-shaw

Copy link
Copy Markdown
Collaborator Author

The update and dismiss are out of the API spec now.

updates left the spec in a8747bc. State arrives from the host through applyUpdate, setUpdates, dispatch, or options.updates. The spec describes behavior only.

dismiss left the spec and the code options in 1d5927c. Each interaction now owns a reset list (click-none, double-click, escape), so a gesture clears only the interactions that list it. The resolver rejects a spec that still carries either field.

@xavier-shaw xavier (xavier-shaw) changed the title feat(interactions): declarative interaction_spec with a preset registry and admission at mount feat(interactions): create declarative interaction_spec for specifying chart interactions with presets Sep 16, 2026
@xavier-shaw xavier (xavier-shaw) changed the title feat(interactions): create declarative interaction_spec for specifying chart interactions with presets feat(interactions): declarative interaction_spec for specifying chart interactions with presets Sep 16, 2026
@xavier-shaw
xavier (xavier-shaw) added this pull request to stack #128 September 16, 2026 00:56
@xavier-shaw xavier (xavier-shaw) changed the title feat(interactions): declarative interaction_spec for specifying chart interactions with presets feat(interactions): interaction_spec for chart interactions Sep 18, 2026
@Chenglong-MS
Chenglong Wang (Chenglong-MS) merged commit 705ee1c into main Sep 20, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants