defmodule LiveViewContinuity.Popover do @moduledoc """ An unstyled native auto popover with patch-safe browser-owned open state. See `POPOVER.md` for the observable interaction contract. """ use Phoenix.Component alias Phoenix.LiveView.JS attr(:id, :string, required: true) attr(:class, :any, default: nil) attr(:trigger_class, :any, default: nil) attr(:popup_class, :any, default: nil) attr(:rest, :global) slot(:trigger, required: true) slot(:inner_block, required: true) def popover(assigns) do validate_id!(assigns.id) trigger = one!(assigns.trigger, "trigger") one!(assigns.inner_block, "body") assigns = assign(assigns, :trigger_entry, trigger) ~H"""
"-popup"} class={@popup_class} popover="auto" aria-labelledby={@id <> "-trigger"} data-lvc-popover-popup > {render_slot(@inner_block)}
""" end defp one!([entry], _name), do: entry defp one!(entries, name), do: raise(ArgumentError, "popover requires exactly one #{name} slot, got: #{length(entries)}") defp validate_id!(id) when is_binary(id) do if id == "" or String.contains?(id, <<0>>) or Regex.match?(~r/[\t\n\f\r ]/, id), do: raise( ArgumentError, "popover id must be a non-empty string without ASCII whitespace or NUL" ) end defp validate_id!(_), do: raise(ArgumentError, "popover id must be a string") end