-module(gg_cn).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gg_cn.gleam").
-export([new/0, default/0, tw_join/1, tw_merge/2, cn/2]).
-export_type([merger/0, class_value/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" gg_cn — a pure-Gleam `tailwind-merge`.\n"
"\n"
" Ported from [cnfast](https://github.com/aidenybai/cnfast)'s logic engine\n"
" (itself a faithful reimplementation of `tailwind-merge` v4), this resolves\n"
" conflicting Tailwind utility classes by keeping the last one per conflict\n"
" group: `tw_merge(\"px-2 px-4\") == \"px-4\"`.\n"
"\n"
" This is the engine behind `gg_base_ui/helpers/cn` (which `gg_ui` and its\n"
" components use): a real `clsx + tailwind-merge`, for any markup that mixes\n"
" raw Tailwind utilities and needs conflict resolution. It is pure Gleam (no\n"
" FFI), so it compiles and behaves identically on JS and the BEAM — which is\n"
" why it can back gg_ui's `cn` without bringing Elixir `tails` into the build.\n"
"\n"
" ## Usage\n"
"\n"
" ```gleam\n"
" import gg_cn\n"
"\n"
" let merge = gg_cn.new()\n"
" merge |> gg_cn.tw_merge(\"px-2 py-1 px-4\") // \"py-1 px-4\"\n"
" ```\n"
"\n"
" `new()` builds the class trie once (it is moderately expensive); reuse the\n"
" returned `Merger` across calls rather than rebuilding it per merge. For\n"
" render-time callers, prefer [`default`](#default) — a process-global `Merger`\n"
" built once and reused (what `gg_base_ui/helpers/cn` uses).\n"
).
-opaque merger() :: {merger, gg_cn@internal@merge:engine()}.
-type class_value() :: {class, binary()} |
{'when', boolean(), binary()} |
{group, list(class_value())}.
-file("src/gg_cn.gleam", 44).
?DOC(" Build a `Merger` from the baked-in Tailwind v4 configuration.\n").
-spec new() -> merger().
new() ->
Regexes = gg_cn@internal@validators:compile(),
Cfg = gg_cn@internal@config:default_config(Regexes),
{merger, gg_cn@internal@merge:new(Cfg)}.
-file("src/gg_cn.gleam", 59).
?DOC(
" A process-global default `Merger`, built **once** on first use and reused\n"
" forever after. Backed by `global_value` (persistent_term on the BEAM, a\n"
" singleton object on JS), so the expensive trie + regex build happens a single\n"
" time per runtime — the right thing for render-time callers (e.g. gg_ui's\n"
" `cn`) that shouldn't thread a `Merger` around or rebuild it per call.\n"
"\n"
" This memoizes only the *engine* (the trie). It does not yet cache per-input\n"
" merge *results*; that LRU is a separate, optional layer (see the package\n"
" README / gg_ui follow-up).\n"
).
-spec default() -> merger().
default() ->
global_value:create_with_unique_name(
<<"gg_cn.default_merger"/utf8>>,
fun new/0
).
-file("src/gg_cn.gleam", 83).
-spec resolve_value(class_value()) -> binary().
resolve_value(Value) ->
case Value of
{class, Class} ->
Class;
{'when', true, Class@1} ->
Class@1;
{'when', false, _} ->
<<""/utf8>>;
{group, Values} ->
tw_join(Values)
end.
-file("src/gg_cn.gleam", 76).
?DOC(
" Join class values into one space-separated string, dropping falsy/empty\n"
" parts — the `twJoin`/`clsx` step, without conflict resolution.\n"
).
-spec tw_join(list(class_value())) -> binary().
tw_join(Values) ->
_pipe = Values,
_pipe@1 = gleam@list:map(_pipe, fun resolve_value/1),
_pipe@2 = gleam@list:filter(_pipe@1, fun(Part) -> Part /= <<""/utf8>> end),
gleam@string:join(_pipe@2, <<" "/utf8>>).
-file("src/gg_cn.gleam", 99).
?DOC(
" Merge an already-joined, space-separated class string, resolving conflicts.\n"
"\n"
" The result is memoized by input string (JS only; the BEAM recomputes — see\n"
" `internal/cache`). Output depends solely on the input (single baked config),\n"
" so the cache never changes results, only speed.\n"
).
-spec tw_merge(merger(), binary()) -> binary().
tw_merge(Merger, Class_list) ->
gg_cn@internal@cache:cached(
Class_list,
fun() ->
gg_cn@internal@merge:merge_class_list(
erlang:element(2, Merger),
Class_list
)
end
).
-file("src/gg_cn.gleam", 107).
?DOC(
" `clsx` + `twMerge` in one — the shadcn `cn` helper. Joins the class values,\n"
" then resolves conflicts.\n"
).
-spec cn(merger(), list(class_value())) -> binary().
cn(Merger, Values) ->
tw_merge(Merger, tw_join(Values)).