-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)).