Password Input · API
LiveView push, client DOM events, programmatic visibility, and a native radio form.
LiveView binding
<div class="flex flex-wrap gap-2 mb-4">
<.action phx-click={Corex.PasswordInput.set_visible("password-input-api-binding", true)} class="button button--sm">
Show
</.action>
<.action phx-click={Corex.PasswordInput.set_visible("password-input-api-binding", false)} class="button button--sm">
Hide
</.action>
<.action phx-click={Corex.PasswordInput.toggle_visible("password-input-api-binding")} class="button button--sm">
Toggle
</.action>
<.action phx-click={Corex.PasswordInput.focus("password-input-api-binding")} class="button button--sm">
Focus
</.action>
</div>
<.password_input
class="password-input"
name="user[password]"
on_visibility_change="password_input_api_visibility"
>
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" class="icon" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" class="icon" /></:hidden_indicator>
</.password_input>
def handle_event("password_input_api_visibility", %{"id" => id, "visible" => visible}, socket) do
{:noreply, socket}
end
<.password_input
class="password-input"
name="user[password]"
visible
>
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" class="icon" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" class="icon" /></:hidden_indicator>
</.password_input>
# Initial visibility: pass visible={true} on the component.
# The hook reads data-default-visible on mount; LiveView does not control visibility after that.
JS العميل
<div class="flex flex-wrap gap-2 mb-4">
<.action
phx-click={
JS.dispatch("corex:password-input:set-visible",
to: "#password-input-api-client",
detail: %{visible: true},
bubbles: false
)
}
class="button button--sm"
>
Show
</.action>
<.action
phx-click={
JS.dispatch("corex:password-input:set-visible",
to: "#password-input-api-client",
detail: %{visible: false},
bubbles: false
)
}
class="button button--sm"
>
Hide
</.action>
<.action
phx-click={JS.dispatch("corex:password-input:toggle-visible", to: "#password-input-api-client", bubbles: false)}
class="button button--sm"
>
Toggle
</.action>
<.action
phx-click={JS.dispatch("corex:password-input:focus", to: "#password-input-api-client", bubbles: false)}
class="button button--sm"
>
Focus
</.action>
</div>
<.password_input
class="password-input"
name="user[password]"
on_visibility_change_client="password-input-api-visibility-changed"
>
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" class="icon" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" class="icon" /></:hidden_indicator>
</.password_input>
const el = document.getElementById("password-input-api-client");
el?.addEventListener("password-input-api-visibility-changed", (event) => {
console.log(event.detail);
});
el?.dispatchEvent(
new CustomEvent("corex:password-input:set-visible", { bubbles: false, detail: { visible: true } })
);
const el = document.getElementById("password-input-api-client");
el?.addEventListener("password-input-api-visibility-changed", (event: Event) => {
console.log((event as CustomEvent<{ id?: string; visible?: boolean }>).detail);
});
el?.dispatchEvent(
new CustomEvent("corex:password-input:set-visible", { bubbles: false, detail: { visible: true } })
);
تعيين الظهور (الخادم)
<div class="flex flex-wrap gap-2 mb-4">
<.action phx-click="api_password_show" phx-value-id="password-input-api-server" class="button button--sm">
Show
</.action>
<.action phx-click="api_password_hide" phx-value-id="password-input-api-server" class="button button--sm">
Hide
</.action>
<.action phx-click="api_password_toggle_visible" phx-value-id="password-input-api-server" class="button button--sm">
Toggle
</.action>
<.action phx-click="api_password_focus" phx-value-id="password-input-api-server" class="button button--sm">
Focus
</.action>
</div>
<.password_input
class="password-input"
name="user[password]"
>
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" class="icon" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" class="icon" /></:hidden_indicator>
</.password_input>
def handle_event("api_password_show", %{"id" => id}, socket) do
{:noreply, Corex.PasswordInput.set_visible(socket, id, true)}
end
def handle_event("api_password_hide", %{"id" => id}, socket) do
{:noreply, Corex.PasswordInput.set_visible(socket, id, false)}
end
def handle_event("api_password_toggle_visible", %{"id" => id}, socket) do
{:noreply, Corex.PasswordInput.toggle_visible(socket, id)}
end
def handle_event("api_password_focus", %{"id" => id}, socket) do
{:noreply, Corex.PasswordInput.focus(socket, id)}
end
نموذج أصلي (HTML عادي)
<form action={~p"/radio-group/form"} method="post">
<input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
<.radio_group
name="user[choice]"
class="radio-group"
items={[
%{value: "lorem", label: "Lorem ipsum dolor sit amet"},
%{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
%{value: "donec", label: "Donec condimentum ex mi"}
]
}
>
<:label>Choose one</:label>
<:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
</.radio_group>
<.action type="submit" class="button button--accent">Submit</.action>
</form>
defmodule MyApp.Forms.RadioChoiceForm do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :choice, :string
end
def changeset(form, attrs \\ %{}) do
form
|> cast(attrs, [:choice])
|> validate_required(:choice)
end
def changeset_validate(form, attrs \\ %{}) do
form
|> cast(attrs, [:choice])
|> validate_required([:choice])
end
end