These ship in the hooks bundle and install themselves on import. No page-local
phx-hook and no extra Hooks entry. Markup is enough.
import LanternHooks from "../../deps/lantern_ui/priv/static/lantern_ui_hooks.js"Rebuild the bundle after editing assets/js/ with npm run build and commit
priv/static/lantern_ui_hooks.js.
List keyboard nav
data-lantern-list-nav on the list, data-lantern-list-item on each row.
While focus is inside the list: j / ArrowDown and k / ArrowUp move a
roving tabindex, Home / End jump, Enter activates the item's own link
(or clicks the item). The focused row gets a visible ring.
<ul data-lantern-list-nav>
<li :for={row <- @rows} data-lantern-list-item>
<.link navigate={~p"/tickets/#{row}"}>{row.title}</.link>
</li>
</ul>Do not bind j/k on a typing target (input, textarea, select,
contenteditable) — those keys stay characters there.
Persist open/collapsed
data-lantern-persist="<key>" stores open / closed in localStorage
(lantern:persist:<key>). Failures (quota, private mode) are ignored.
On a <details>:
<details data-lantern-persist="hub:filters">
<summary>Filters</summary>
...
</details>On any other element, add a toggle (and optional panel):
<div data-lantern-persist="hub:side" data-open="true">
<button type="button" data-lantern-persist-toggle aria-expanded="true">
Inspector
</button>
<div data-lantern-persist-panel>
...
</div>
</div>side_panel in a consuming app can keep a temporary page-local hook until it
switches to this attribute. Same key space; do not write both to the same key.
Overlay placement
Popover, dropdown, select, menu, date picker, and autocomplete panels are
positioned with @floating-ui/dom (flip + shift), bundled into the hooks
file. Consumers do not add that dependency themselves.