# JavaScript Hooks
Phoenix Duskmoon UI provides five LiveView hooks for client-side interactions
that cannot be handled server-side. Some components require specific hooks to
function correctly.
## Setup
Import all hooks and register them with your LiveSocket:
```javascript
import { LiveSocket } from "phoenix_live_view";
import * as DuskmoonHooks from "phoenix_duskmoon/hooks";
let liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
hooks: DuskmoonHooks,
});
```
Or import individual hooks:
```javascript
import {
WebComponentHook,
FormElementHook,
ThemeSwitcher,
Spotlight,
PageHeader,
} from "phoenix_duskmoon/hooks";
let hooks = { WebComponentHook, FormElementHook, ThemeSwitcher, Spotlight, PageHeader };
```
## Hook Reference
### WebComponentHook
**Purpose**: Universal bridge between LiveView and `el-dm-*` custom elements.
**Used by**: All components that render as custom elements.
**How it works**:
1. **Forwarding events to LiveView** — Maps custom element events to Phoenix events:
| Custom Element Event | Phoenix Event |
|---------------------|---------------|
| `dm-click` | `phx-click` |
| `dm-change` | `phx-change` |
| `dm-input` | `phx-input` |
| `dm-submit` | `phx-submit` |
| `dm-focus` | `phx-focus` |
| `dm-blur` | `phx-blur` |
| `dm-select` | `phx-select` |
| `dm-close` | `phx-close` |
| `dm-open` | `phx-open` |
| `dm-toggle` | `phx-toggle` |
2. **Custom event routing** — Use `duskmoon-send-{event}="{phxEvent}"` attributes
to forward arbitrary custom element events to specific LiveView event handlers:
```heex
<el-dm-slider duskmoon-send-change="slider_changed" />
```
3. **Receiving events from LiveView** — Use `duskmoon-receive-{event}="{handler}"`
to push LiveView events to the custom element.
### FormElementHook
**Purpose**: Extends `WebComponentHook` with form-specific behavior.
**Used by**: Form components (`dm_input`, `dm_select`, `dm_checkbox`, etc.) when
used within a `<.dm_form>`.
**How it works**: Watches the parent form for the `phx-no-feedback` class via
`MutationObserver`. When LiveView removes this class (after form submission or
validation), the hook triggers `phx-feedback-for` error display on the element.
This ensures validation errors only appear after user interaction, matching
Phoenix's standard form feedback timing.
### ThemeSwitcher
**Purpose**: Theme toggle with `localStorage` persistence.
**Used by**: `<.dm_theme_switcher />`
**How it works**:
- On mount, reads the saved theme from `localStorage` and syncs it with the
server-side `data-theme` attribute
- When the user toggles the theme, persists the choice to `localStorage` and
pushes a `"theme_changed"` event to the LiveView
- On update, syncs checkbox state with the `data-theme` attribute
### Spotlight
**Purpose**: Keyboard shortcut handler for spotlight/command palette search.
**Used by**: `<.dmf_spotlight />`
**How it works**:
- On mount, adds a `keydown` listener on `window` for `Cmd+K` (macOS) / `Ctrl+K`
(other platforms)
- When triggered, calls `this.el.showModal()` to open the spotlight dialog
- Handles `Escape` to close the dialog
- Cleans up listeners on destroy
### PageHeader
**Purpose**: Intersection observer for scroll-based effects.
**Used by**: `<.dm_page_header />`
**How it works**:
- On mount, creates an `IntersectionObserver` watching the page header element
- When the header scrolls out of view (`intersectionRatio <= 0.5`), shows a
secondary navigation element (identified by `data-nav-id`) with opacity
proportional to scroll position
- Cleans up the observer on destroy
## Components Requiring Hooks
| Component | Required Hook | What Breaks Without It |
|-----------|--------------|----------------------|
| `<.dm_theme_switcher />` | ThemeSwitcher | Theme choice not persisted across page loads |
| `<.dmf_spotlight />` | Spotlight | Keyboard shortcut (Cmd/Ctrl+K) doesn't work |
| `<.dm_page_header />` | PageHeader | Scroll-based nav visibility doesn't trigger |
| All `el-dm-*` elements | WebComponentHook | Custom element events not forwarded to LiveView |
| Form `el-dm-*` elements | FormElementHook | Validation errors don't respect feedback timing |