Hoists selected top-level token groups under a shared parent, rewriting {ref}s
to match.
CSS property names come from a token's path (color.hue.300 -> --color-hue-300),
but Figma exports each collection at the root (hue, accent, ...). Nesting
them under a color parent yields --color-* names and rewrites every {ref}
to a moved key ({hue.300} -> {color.hue.300}) so they keep resolving.
Idempotent: a second run finds nothing at the root and the prefixed refs no
longer match the moved keys. unnest/3 is the exact inverse.
Jetons.CSS.from_resolver/3 calls nest/3 at build time (driven by a
dev.jetons.css prefix extension on a set/modifier), so the source token
files can stay in their flat Figma shape.
Usage
{decoded, _moved} =
"palette.tokens.json"
|> File.read!()
|> Jason.decode!()
|> Jetons.Namespace.nest("color", ["hue", "accent"])Example
Input — a flat Figma export (group: "color", keys: ["hue", "accent"]):
{
"hue": { "300": { "$value": "{blue.300}" } },
"accent": { "$value": "{hue.300}" }
}Output — hoisted under color, the {hue.300} ref rewritten ({blue.300}
left alone, since blue is not a moved key):
{
"color": {
"hue": { "300": { "$value": "{blue.300}" } },
"accent": { "$value": "{color.hue.300}" }
}
}
Summary
Functions
@spec nest(map(), String.t(), [String.t()]) :: {map(), non_neg_integer()}
Hoists keys found at the root of map under the group parent and rewrites
matching references throughout.
Returns {converted_map, moved_count} where moved_count is how many
top-level groups were relocated (0 when the file only needed ref rewriting).
Raises ArgumentError when group is itself one of the keys (would break
the idempotency invariant).
Examples
iex> Jetons.Namespace.nest(
...> %{"hue" => %{"$value" => "{accent}"}, "accent" => %{"$value" => "#c00"}},
...> "color",
...> ["hue", "accent"]
...> )
{%{"color" => %{"accent" => %{"$value" => "#c00"}, "hue" => %{"$value" => "{color.accent}"}}}, 2}
@spec unnest(map(), String.t(), [String.t()]) :: {map(), non_neg_integer()}
Inverse of nest/3: lifts keys out of the group parent back to the root
and strips the group. prefix from matching references.
Returns {converted_map, lifted_count}.
Examples
iex> Jetons.Namespace.unnest(
...> %{"color" => %{"accent" => %{"$value" => "{color.hue.300}"}, "background" => %{"$value" => "#fff"}}},
...> "color",
...> ["hue", "accent"]
...> )
{%{"accent" => %{"$value" => "{hue.300}"}, "color" => %{"background" => %{"$value" => "#fff"}}}, 1}