JavaScript Hooks

View Source

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:

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:

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 EventPhoenix Event
    dm-clickphx-click
    dm-changephx-change
    dm-inputphx-input
    dm-submitphx-submit
    dm-focusphx-focus
    dm-blurphx-blur
    dm-selectphx-select
    dm-closephx-close
    dm-openphx-open
    dm-togglephx-toggle
  2. Custom event routing — Use duskmoon-send-{event}="{phxEvent}" attributes to forward arbitrary custom element events to specific LiveView event handlers:

    <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

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

ComponentRequired HookWhat Breaks Without It
<.dm_theme_switcher />ThemeSwitcherTheme choice not persisted across page loads
<.dmf_spotlight />SpotlightKeyboard shortcut (Cmd/Ctrl+K) doesn't work
<.dm_page_header />PageHeaderScroll-based nav visibility doesn't trigger
All el-dm-* elementsWebComponentHookCustom element events not forwarded to LiveView
Form el-dm-* elementsFormElementHookValidation errors don't respect feedback timing