defmodule Corex do @moduledoc false @components %{ accordion: {Corex.Accordion, [ accordion: 1, accordion_root: 1, accordion_item: 1, accordion_trigger: 1, accordion_indicator: 1, accordion_content: 1, accordion_skeleton: 1 ]}, action: {Corex.Action, [action: 1]}, angle_slider: {Corex.AngleSlider, [angle_slider: 1, angle_slider_skeleton: 1]}, avatar: {Corex.Avatar, [avatar: 1]}, carousel: {Corex.Carousel, [ carousel: 1, carousel_root: 1, carousel_item_group: 1, carousel_item: 1, carousel_control: 1, carousel_prev_trigger: 1, carousel_next_trigger: 1, carousel_indicator_group: 1, carousel_indicator: 1 ]}, checkbox: {Corex.Checkbox, [checkbox: 1, checkbox_skeleton: 1]}, clipboard: {Corex.Clipboard, [clipboard: 1]}, code: {Corex.Code, [code: 1]}, collapsible: {Corex.Collapsible, [collapsible: 1, collapsible_skeleton: 1]}, combobox: {Corex.Combobox, [combobox: 1]}, color_picker: {Corex.ColorPicker, [color_picker: 1]}, data_table: {Corex.DataTable, [data_table: 1]}, data_list: {Corex.DataList, [data_list: 1]}, date_picker: {Corex.DatePicker, [date_picker: 1]}, dialog: {Corex.Dialog, [dialog: 1, dialog_title: 1, dialog_description: 1, dialog_close_trigger: 1]}, editable: {Corex.Editable, [editable: 1]}, file_upload: {Corex.FileUpload, [file_upload: 1]}, file_upload_live: {Corex.FileUploadLive, [file_upload_live: 1]}, floating_panel: {Corex.FloatingPanel, [floating_panel: 1]}, heroicon: {Corex.Heroicon, [heroicon: 1]}, native_input: {Corex.NativeInput, [native_input: 1]}, hidden_input: {Corex.HiddenInput, [hidden_input: 1]}, listbox: {Corex.Listbox, [listbox: 1]}, layout_heading: {Corex.Layout.Heading, [layout_heading: 1]}, marquee: {Corex.Marquee, [marquee: 1]}, menu: {Corex.Menu, [menu: 1]}, navigate: {Corex.Navigate, [navigate: 1]}, number_input: {Corex.NumberInput, [number_input: 1]}, password_input: {Corex.PasswordInput, [password_input: 1]}, pin_input: {Corex.PinInput, [pin_input: 1]}, radio_group: {Corex.RadioGroup, [radio_group: 1]}, select: {Corex.Select, [select: 1]}, signature_pad: {Corex.SignaturePad, [signature_pad: 1]}, switch: {Corex.Switch, [switch: 1]}, tabs: {Corex.Tabs, [ tabs: 1, tabs_root: 1, tabs_list: 1, tabs_trigger: 1, tabs_indicator: 1, tabs_content: 1, tabs_skeleton: 1 ]}, timer: {Corex.Timer, [timer: 1]}, tooltip: {Corex.Tooltip, [tooltip: 1]}, toast: {Corex.Toast, [ toast_group: 1, toast_client_error: 1, toast_server_error: 1, toast_connected: 1, toast_disconnected: 1 ]}, toggle_group: {Corex.ToggleGroup, [toggle_group: 1]}, tree_view: {Corex.TreeView, [ tree_view: 1, tree_view_root: 1, tree_view_branch: 1, tree_view_branch_trigger: 1, tree_view_branch_indicator: 1, tree_view_branch_content: 1, tree_view_item: 1, tree_view_item_indicator: 1, tree_item: 1, tree_branch: 1, tree_view_skeleton: 1 ]} } defmacro __using__(opts \\ []) do only = Keyword.get(opts, :only, :all) except = Keyword.get(opts, :except, []) prefix = Keyword.get(opts, :prefix) # Filter components based on only/except selected_components = @components |> Enum.filter(fn {component_name, _} -> include?(component_name, only, except) end) if prefix do # Generate wrapper functions with prefix wrappers = for {_component_name, {mod, functions}} <- selected_components, {func_name, arity} <- functions do prefixed_name = :"#{prefix}_#{func_name}" # Generate the appropriate number of arguments args = Macro.generate_arguments(arity, __MODULE__) quote do defdelegate unquote(prefixed_name)(unquote_splicing(args)), to: unquote(mod), as: unquote(func_name) end end # Generate aliases aliases = for {_component_name, {mod, _functions}} <- selected_components do quote do alias unquote(mod) end end quote do unquote_splicing(wrappers) unquote_splicing(aliases) end else # Normal import without prefix imports = for {_component_name, {mod, functions}} <- selected_components do quote do import unquote(mod), only: unquote(functions) end end aliases = for {_component_name, {mod, _functions}} <- selected_components do quote do alias unquote(mod) end end quote do unquote_splicing(imports) unquote_splicing(aliases) end end end defp include?(_name, :all, []), do: true defp include?(name, :all, except), do: name not in except defp include?(name, only, _except) when is_list(only), do: name in only @doc "Returns sorted ids from the component registry for MCP and tooling." def component_ids do @components |> Map.keys() |> Enum.sort() end @doc "Resolves a registered component id to module and function-component metadata." def component_spec(id) when is_atom(id) do case Map.fetch(@components, id) do {:ok, {mod, functions}} -> fns = Enum.map(functions, fn {name, arity} -> %{name: name, arity: arity} end) {:ok, %{ id: id, module: inspect(mod), function_components: fns }} :error -> :error end end @doc "Maps a string MCP component id to its implementing module when registered." def component_module_for_mcp_id(id) when is_binary(id) do allowed = MapSet.new(for a <- component_ids(), do: to_string(a)) if MapSet.member?(allowed, id) do atom_id = String.to_existing_atom(id) case Map.fetch(@components, atom_id) do {:ok, {mod, _}} -> {:ok, mod} :error -> :error end else :error end end end