@nxds/core 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1889,6 +1889,321 @@ function apcaLc(foreground, background) {
1889
1889
  );
1890
1890
  }
1891
1891
 
1892
+ // src/lib/contrast.ts
1893
+ import { APCAcontrast as APCAcontrast3, sRGBtoY as sRGBtoY3 } from "apca-w3";
1894
+ import { clampChroma as clampChroma5 } from "culori";
1895
+
1896
+ // src/lib/semantic-palette-references.ts
1897
+ var STATUS_PALETTE_FAMILIES = {
1898
+ success: "green",
1899
+ warning: "orange",
1900
+ error: "red",
1901
+ information: "blue"
1902
+ };
1903
+ var CHART_PALETTE_REFERENCES = [
1904
+ { palette: "teal", light: "700", dark: "200" },
1905
+ { palette: "lime", light: "700", dark: "200" },
1906
+ { palette: "orange", light: "600", dark: "200" },
1907
+ { palette: "rose", light: "600", dark: "200" },
1908
+ { palette: "indigo", light: "600", dark: "200" }
1909
+ ];
1910
+ var SYNTAX_PALETTE_REFERENCES = {
1911
+ keyword: CHART_PALETTE_REFERENCES[3],
1912
+ string: CHART_PALETTE_REFERENCES[1],
1913
+ function: CHART_PALETTE_REFERENCES[4],
1914
+ type: CHART_PALETTE_REFERENCES[0],
1915
+ constant: CHART_PALETTE_REFERENCES[2]
1916
+ };
1917
+
1918
+ // src/lib/apca-pairs.ts
1919
+ var COLOR_FAMILIES = [
1920
+ "primary",
1921
+ "secondary",
1922
+ "success",
1923
+ "warning",
1924
+ "error",
1925
+ "information"
1926
+ ];
1927
+ var CATEGORICAL_INDICES = CHART_PALETTE_REFERENCES.map(
1928
+ (_, index) => index + 1
1929
+ );
1930
+ var CHART_STAGGER_LC = 4.5;
1931
+ var DIFF_SURFACES = ["success-subtle", "error-subtle"];
1932
+ var CODE_SURFACES = ["container", "container-hover", ...DIFF_SURFACES];
1933
+ var FOCUS_SURFACES = [
1934
+ "background",
1935
+ "container",
1936
+ "popover",
1937
+ "nav-background",
1938
+ "nav-item-hover",
1939
+ "nav-item-active",
1940
+ "nav-border"
1941
+ ];
1942
+ var text = (fg, bg, tier) => ({
1943
+ fg,
1944
+ bg,
1945
+ tier,
1946
+ kind: "text"
1947
+ });
1948
+ var label = (fg, bg, tier) => ({
1949
+ fg,
1950
+ bg,
1951
+ tier,
1952
+ kind: "label"
1953
+ });
1954
+ var ink = (fg, bg, tier) => ({
1955
+ fg,
1956
+ bg,
1957
+ tier,
1958
+ kind: "ink"
1959
+ });
1960
+ var APCA_PAIRS = [
1961
+ text("foreground", "background", "body"),
1962
+ text("foreground", "background-hover", "ui"),
1963
+ text("foreground", "muted", "ui"),
1964
+ text("muted-foreground", "muted", "ui"),
1965
+ text("muted-foreground-subtle", "muted", "incidental"),
1966
+ text("muted-foreground", "container", "ui"),
1967
+ text("muted-foreground-subtle", "container", "incidental"),
1968
+ text("foreground", "muted-extralight", "ui"),
1969
+ text("muted-foreground", "muted-extralight", "ui"),
1970
+ text("disabled-foreground", "disabled", "incidental"),
1971
+ text("container-foreground", "container", "body"),
1972
+ text("foreground", "container", "body"),
1973
+ text("popover-foreground", "popover", "body"),
1974
+ text("popover-foreground", "popover-hover", "ui"),
1975
+ text("foreground", "control-background", "ui"),
1976
+ text("foreground", "control-background-hover", "ui"),
1977
+ text("nav-foreground", "nav-background", "ui"),
1978
+ text("nav-muted-foreground", "nav-background", "incidental"),
1979
+ text("nav-foreground", "nav-item-hover", "ui"),
1980
+ text("nav-foreground", "nav-item-active", "ui"),
1981
+ ink("error-subtle-foreground", "background", "ui"),
1982
+ ink("error-subtle-foreground", "container", "ui"),
1983
+ ...COLOR_FAMILIES.flatMap((family) => [
1984
+ label(`${family}-foreground`, `${family}-background`, "ui"),
1985
+ label(`${family}-foreground`, `${family}-background-hover`, "ui"),
1986
+ label(`${family}-foreground`, `${family}-background-active`, "ui"),
1987
+ ink(`${family}-subtle-foreground`, `${family}-subtle`, "ui"),
1988
+ ink(`${family}-subtle-foreground`, `${family}-subtle-hover`, "ui"),
1989
+ ink(`${family}-subtle-foreground`, `${family}-subtle-active`, "ui")
1990
+ ]),
1991
+ ...CATEGORICAL_INDICES.flatMap(
1992
+ (index) => ["container", "container-hover", "background"].map((bg) => ({
1993
+ ...ink(`chart-categorical-${index}`, bg, "ui"),
1994
+ offset: (index - 1) * CHART_STAGGER_LC
1995
+ }))
1996
+ ),
1997
+ ...CODE_SURFACES.flatMap(
1998
+ (bg) => Object.keys(SYNTAX_PALETTE_REFERENCES).map(
1999
+ (role) => ink(`syntax-${role}`, bg, "ui")
2000
+ )
2001
+ ),
2002
+ // codeBlockTheme also colours code with the text tokens, which already read
2003
+ // on container and container-hover.
2004
+ ...DIFF_SURFACES.flatMap((bg) => [
2005
+ text("foreground", bg, "ui"),
2006
+ text("muted-foreground", bg, "ui"),
2007
+ text("muted-foreground-subtle", bg, "incidental")
2008
+ ]),
2009
+ // Focus is the primary accent (components.md § Focus States), so it shares
2010
+ // one solved color with primary-subtle-foreground.
2011
+ ...FOCUS_SURFACES.flatMap((surface) => [
2012
+ {
2013
+ ...ink("focus-default", surface, "incidental"),
2014
+ solveAs: "primary-subtle-foreground"
2015
+ },
2016
+ ink("focus-error", surface, "incidental")
2017
+ ]),
2018
+ ...[
2019
+ "background-active",
2020
+ "container-hover",
2021
+ "container-active",
2022
+ "popover",
2023
+ "popover-hover",
2024
+ "popover-active",
2025
+ "nav-background"
2026
+ ].flatMap((surface) => [
2027
+ text("foreground", surface, "ui"),
2028
+ text("muted-foreground", surface, "ui"),
2029
+ text("muted-foreground-subtle", surface, "incidental")
2030
+ ]),
2031
+ text("muted-foreground", "background", "ui"),
2032
+ text("muted-foreground-subtle", "background", "incidental"),
2033
+ text("container-foreground", "container-hover", "ui"),
2034
+ text("container-foreground", "container-active", "ui"),
2035
+ text("popover-foreground", "popover-active", "ui"),
2036
+ ...["background", "container", "popover"].flatMap((backdrop) => [
2037
+ { ...text("popover-foreground", "popover-alpha", "body"), backdrop },
2038
+ { ...text("foreground", "background-hover-alpha", "ui"), backdrop }
2039
+ ]),
2040
+ ...["#000000", "#ffffff"].map((backdrop) => ({
2041
+ ...text("popover-foreground", "popover-alpha", "body"),
2042
+ backdrop
2043
+ }))
2044
+ ];
2045
+
2046
+ // src/lib/perceptual-ramp.ts
2047
+ import { clampChroma as clampChroma4, oklch as oklch5, parse as parse5 } from "culori";
2048
+ var EMIT_GAMUT2 = "p3";
2049
+ var CUSP_FRACTION2 = 0.95;
2050
+ function isColor(value) {
2051
+ return parse5(value) !== void 0;
2052
+ }
2053
+ function seedOklch(input) {
2054
+ const parsed = parse5(input);
2055
+ if (!parsed)
2056
+ throw new Error(`perceptual-ramp: cannot parse color '${input}'`);
2057
+ const converted = oklch5(parsed);
2058
+ if (!converted)
2059
+ throw new Error(`perceptual-ramp: cannot convert '${input}' to OKLCH`);
2060
+ return converted;
2061
+ }
2062
+ function pinnedOklch(seedHex, shade, options = {}) {
2063
+ const { capAtSeedChroma = true } = options;
2064
+ const seed = seedOklch(seedHex);
2065
+ const l = PERCEPTUAL_L_GRID[shade];
2066
+ const hue = seed.h ?? 0;
2067
+ const seedC = seed.c ?? 0;
2068
+ const cuspC = clampChroma4({ mode: "oklch", l, c: 0.5, h: hue }, "oklch", EMIT_GAMUT2).c ?? 0;
2069
+ let chroma = cuspC * CUSP_FRACTION2;
2070
+ if (capAtSeedChroma) chroma = Math.min(chroma, seedC);
2071
+ const target = { mode: "oklch", l, c: chroma, h: hue };
2072
+ return formatOklch(clampChroma4(target, "oklch", EMIT_GAMUT2));
2073
+ }
2074
+ function rampFromSeed(seedHex, options = {}) {
2075
+ const ramp = {};
2076
+ for (const shade of SHADES)
2077
+ ramp[shade] = pinnedOklch(seedHex, shade, options);
2078
+ return ramp;
2079
+ }
2080
+
2081
+ // src/lib/contrast.ts
2082
+ var CONTRAST_MIN = 0;
2083
+ var CONTRAST_MAX = 100;
2084
+ function normalizeContrast(value, fallback = 50) {
2085
+ return typeof value === "number" && Number.isFinite(value) ? Math.round(Math.max(CONTRAST_MIN, Math.min(CONTRAST_MAX, value))) : fallback;
2086
+ }
2087
+ function contrastTarget(tier, contrast) {
2088
+ return TIER_THRESHOLDS[tier] + 15 * contrast / 100;
2089
+ }
2090
+ var BISECT_STEPS = 12;
2091
+ function bisect(fail, pass, passes) {
2092
+ for (let i = 0; i < BISECT_STEPS; i++) {
2093
+ const middle = (fail + pass) / 2;
2094
+ if (passes(middle)) pass = middle;
2095
+ else fail = middle;
2096
+ }
2097
+ return pass;
2098
+ }
2099
+ var token = (map, name) => {
2100
+ const value = map[`--nx-color-${name}`];
2101
+ if (value === void 0) throw new Error(`contrast: missing ${name}`);
2102
+ return value;
2103
+ };
2104
+ function pairBackground(map, pair) {
2105
+ const backdrop = pair.backdrop ? resolveToSrgbInts(
2106
+ pair.backdrop.startsWith("#") ? pair.backdrop : token(map, pair.backdrop)
2107
+ ) : void 0;
2108
+ return resolveToSrgbInts(token(map, pair.bg), backdrop);
2109
+ }
2110
+ function contrastForPair(map, pair) {
2111
+ const background = pairBackground(map, pair);
2112
+ return Math.abs(
2113
+ APCAcontrast3(
2114
+ sRGBtoY3(resolveToSrgbInts(token(map, pair.fg), background)),
2115
+ sRGBtoY3(background)
2116
+ )
2117
+ );
2118
+ }
2119
+ var luminance = (color) => sRGBtoY3(resolveToSrgbInts(color));
2120
+ var score = (foregroundY, backgroundY) => Math.abs(APCAcontrast3(foregroundY, backgroundY));
2121
+ function constraintsFor(map, pairs, contrast, endpointY) {
2122
+ const constraints = [];
2123
+ for (const pair of pairs) {
2124
+ const backgroundY = sRGBtoY3(pairBackground(map, pair));
2125
+ const reachable = score(endpointY, backgroundY);
2126
+ if (reachable < TIER_THRESHOLDS[pair.tier]) return void 0;
2127
+ const target = contrastTarget(pair.tier, contrast) + (pair.offset ?? 0);
2128
+ constraints.push({ backgroundY, target: Math.min(target, reachable) });
2129
+ }
2130
+ return constraints;
2131
+ }
2132
+ var keepsTextSeed = (mode, kind) => mode === "light" && kind === "text";
2133
+ function solveForeground(seed, constraints, endpoint, keepSeed) {
2134
+ const passes = (color) => {
2135
+ const y = luminance(color);
2136
+ return constraints.every((c) => score(y, c.backgroundY) >= c.target);
2137
+ };
2138
+ if (keepSeed && passes(seed)) return seed;
2139
+ const parsed = seedOklch(seed);
2140
+ const at = (l) => formatOklch(clampChroma5({ ...parsed, l, alpha: 1 }, "oklch", "p3"));
2141
+ return at(
2142
+ bisect(keepSeed ? parsed.l : 1 - endpoint, endpoint, (l) => passes(at(l)))
2143
+ );
2144
+ }
2145
+ var SOLVE_GROUPS = /* @__PURE__ */ new Map();
2146
+ for (const pair of APCA_PAIRS) {
2147
+ const key = pair.solveAs ?? pair.fg;
2148
+ const group = SOLVE_GROUPS.get(key);
2149
+ if (!group) {
2150
+ SOLVE_GROUPS.set(key, { kind: pair.kind, pairs: [pair] });
2151
+ continue;
2152
+ }
2153
+ if (group.kind !== pair.kind)
2154
+ throw new Error(
2155
+ `contrast: ${pair.fg} is '${pair.kind}' but joins '${group.kind}' group ${key}`
2156
+ );
2157
+ group.pairs.push(pair);
2158
+ }
2159
+ function constrainFamilyFills(map, name, pairs) {
2160
+ const basePair = pairs[0];
2161
+ if (!basePair) throw new Error(`contrast: missing pairs for ${name}`);
2162
+ const baseY = luminance(token(map, basePair.bg));
2163
+ const endpoint = score(1, baseY) >= score(0, baseY) ? 1 : 0;
2164
+ map[`--nx-color-${name}`] = `oklch(${endpoint} 0 0)`;
2165
+ for (const pair of pairs) {
2166
+ const floor = TIER_THRESHOLDS[pair.tier];
2167
+ const value = token(map, pair.bg);
2168
+ if (score(endpoint, luminance(value)) >= floor) continue;
2169
+ const fill = seedOklch(value);
2170
+ const at = (l) => formatOklch(clampChroma5({ ...fill, l }, "oklch", "p3"));
2171
+ const readable = (l) => score(endpoint, luminance(at(l))) >= floor;
2172
+ map[`--nx-color-${pair.bg}`] = at(bisect(fill.l, 1 - endpoint, readable));
2173
+ }
2174
+ }
2175
+ function constrainColors(map, mode, contrast) {
2176
+ for (const [name, { kind, pairs }] of SOLVE_GROUPS)
2177
+ if (kind === "label") constrainFamilyFills(map, name, pairs);
2178
+ const endpoint = mode === "dark" ? 1 : 0;
2179
+ const endpointY = luminance(`oklch(${endpoint} 0 0)`);
2180
+ for (const [name, { kind, pairs }] of SOLVE_GROUPS) {
2181
+ if (kind === "label") continue;
2182
+ const constraints = constraintsFor(map, pairs, contrast, endpointY);
2183
+ if (constraints === void 0)
2184
+ throw new Error(`contrast: no readable ${name} in ${mode}`);
2185
+ const solved = solveForeground(
2186
+ token(map, name),
2187
+ constraints,
2188
+ endpoint,
2189
+ keepsTextSeed(mode, kind)
2190
+ );
2191
+ for (const fg of new Set(pairs.map((pair) => pair.fg).concat(name)))
2192
+ map[`--nx-color-${fg}`] = solved;
2193
+ }
2194
+ return map;
2195
+ }
2196
+
2197
+ // src/lib/runtime-layer.ts
2198
+ var RUNTIME_LAYER_ORDER = "@layer properties, theme, base, components, utilities, nx-runtime;";
2199
+ function inRuntimeLayer(css) {
2200
+ return `${RUNTIME_LAYER_ORDER}
2201
+ @layer nx-runtime {
2202
+ ${css}
2203
+ }
2204
+ `;
2205
+ }
2206
+
1892
2207
  // tokens/primitives/typography/typography-default.json
1893
2208
  var typography_default_default = {
1894
2209
  family: {
@@ -2027,6 +2342,13 @@ var typography_default_default = {
2027
2342
  unit: "px"
2028
2343
  },
2029
2344
  $type: "dimension"
2345
+ },
2346
+ code: {
2347
+ $value: {
2348
+ value: 12,
2349
+ unit: "px"
2350
+ },
2351
+ $type: "dimension"
2030
2352
  }
2031
2353
  },
2032
2354
  weight: {
@@ -2059,471 +2381,182 @@ var typography_default_default = {
2059
2381
  $type: "fontWeight"
2060
2382
  },
2061
2383
  extrabold: {
2062
- $value: 800,
2063
- $type: "fontWeight"
2064
- },
2065
- black: {
2066
- $value: 900,
2067
- $type: "fontWeight"
2068
- }
2069
- },
2070
- "line-height": {
2071
- xxs: {
2072
- $value: {
2073
- value: 12,
2074
- unit: "px"
2075
- },
2076
- $type: "dimension"
2077
- },
2078
- xs: {
2079
- $value: {
2080
- value: 16,
2081
- unit: "px"
2082
- },
2083
- $type: "dimension"
2084
- },
2085
- sm: {
2086
- $value: {
2087
- value: 20,
2088
- unit: "px"
2089
- },
2090
- $type: "dimension"
2091
- },
2092
- base: {
2093
- $value: {
2094
- value: 24,
2095
- unit: "px"
2096
- },
2097
- $type: "dimension"
2098
- },
2099
- lg: {
2100
- $value: {
2101
- value: 28,
2102
- unit: "px"
2103
- },
2104
- $type: "dimension"
2105
- },
2106
- xl: {
2107
- $value: {
2108
- value: 28,
2109
- unit: "px"
2110
- },
2111
- $type: "dimension"
2112
- },
2113
- "2xl": {
2114
- $value: {
2115
- value: 32,
2116
- unit: "px"
2117
- },
2118
- $type: "dimension"
2119
- },
2120
- "3xl": {
2121
- $value: {
2122
- value: 36,
2123
- unit: "px"
2124
- },
2125
- $type: "dimension"
2126
- },
2127
- "4xl": {
2128
- $value: {
2129
- value: 48,
2130
- unit: "px"
2131
- },
2132
- $type: "dimension"
2133
- },
2134
- "5xl": {
2135
- $value: {
2136
- value: 48,
2137
- unit: "px"
2138
- },
2139
- $type: "dimension"
2384
+ $value: 800,
2385
+ $type: "fontWeight"
2140
2386
  },
2141
- "6xl": {
2387
+ black: {
2388
+ $value: 900,
2389
+ $type: "fontWeight"
2390
+ }
2391
+ },
2392
+ "line-height": {
2393
+ xxs: {
2142
2394
  $value: {
2143
- value: 60,
2395
+ value: 12,
2144
2396
  unit: "px"
2145
2397
  },
2146
2398
  $type: "dimension"
2147
2399
  },
2148
- "7xl": {
2400
+ xs: {
2149
2401
  $value: {
2150
- value: 72,
2402
+ value: 16,
2151
2403
  unit: "px"
2152
2404
  },
2153
2405
  $type: "dimension"
2154
2406
  },
2155
- "8xl": {
2407
+ sm: {
2156
2408
  $value: {
2157
- value: 96,
2409
+ value: 20,
2158
2410
  unit: "px"
2159
2411
  },
2160
2412
  $type: "dimension"
2161
2413
  },
2162
- "9xl": {
2414
+ base: {
2163
2415
  $value: {
2164
- value: 128,
2416
+ value: 24,
2165
2417
  unit: "px"
2166
2418
  },
2167
2419
  $type: "dimension"
2168
- }
2169
- },
2170
- letterspacing: {
2171
- tighter: {
2420
+ },
2421
+ lg: {
2172
2422
  $value: {
2173
- value: -0.800000011920929,
2423
+ value: 28,
2174
2424
  unit: "px"
2175
2425
  },
2176
2426
  $type: "dimension"
2177
2427
  },
2178
- tight: {
2428
+ xl: {
2179
2429
  $value: {
2180
- value: -0.4000000059604645,
2430
+ value: 28,
2181
2431
  unit: "px"
2182
2432
  },
2183
2433
  $type: "dimension"
2184
2434
  },
2185
- normal: {
2435
+ "2xl": {
2186
2436
  $value: {
2187
- value: 0,
2437
+ value: 32,
2188
2438
  unit: "px"
2189
2439
  },
2190
2440
  $type: "dimension"
2191
2441
  },
2192
- wide: {
2442
+ "3xl": {
2193
2443
  $value: {
2194
- value: 0.4000000059604645,
2444
+ value: 36,
2195
2445
  unit: "px"
2196
2446
  },
2197
2447
  $type: "dimension"
2198
2448
  },
2199
- wider: {
2449
+ "4xl": {
2200
2450
  $value: {
2201
- value: 0.800000011920929,
2451
+ value: 48,
2202
2452
  unit: "px"
2203
2453
  },
2204
2454
  $type: "dimension"
2205
2455
  },
2206
- widest: {
2456
+ "5xl": {
2207
2457
  $value: {
2208
- value: 1.600000023841858,
2458
+ value: 48,
2209
2459
  unit: "px"
2210
2460
  },
2211
2461
  $type: "dimension"
2212
- }
2213
- }
2214
- };
2215
-
2216
- // src/lib/contrast.ts
2217
- import { APCAcontrast as APCAcontrast3, sRGBtoY as sRGBtoY3 } from "apca-w3";
2218
- import { clampChroma as clampChroma5 } from "culori";
2219
-
2220
- // src/lib/semantic-palette-references.ts
2221
- var STATUS_PALETTE_FAMILIES = {
2222
- success: "green",
2223
- warning: "orange",
2224
- error: "red",
2225
- information: "blue"
2226
- };
2227
- var CHART_PALETTE_REFERENCES = [
2228
- { palette: "teal", light: "700", dark: "200" },
2229
- { palette: "lime", light: "700", dark: "200" },
2230
- { palette: "orange", light: "600", dark: "200" },
2231
- { palette: "rose", light: "600", dark: "200" },
2232
- { palette: "indigo", light: "600", dark: "200" }
2233
- ];
2234
- var SYNTAX_PALETTE_REFERENCES = {
2235
- keyword: CHART_PALETTE_REFERENCES[3],
2236
- string: CHART_PALETTE_REFERENCES[1],
2237
- function: CHART_PALETTE_REFERENCES[4],
2238
- type: CHART_PALETTE_REFERENCES[0],
2239
- constant: CHART_PALETTE_REFERENCES[2]
2240
- };
2241
-
2242
- // src/lib/apca-pairs.ts
2243
- var COLOR_FAMILIES = [
2244
- "primary",
2245
- "secondary",
2246
- "success",
2247
- "warning",
2248
- "error",
2249
- "information"
2250
- ];
2251
- var CATEGORICAL_INDICES = CHART_PALETTE_REFERENCES.map(
2252
- (_, index) => index + 1
2253
- );
2254
- var CHART_STAGGER_LC = 4.5;
2255
- var DIFF_SURFACES = ["success-subtle", "error-subtle"];
2256
- var CODE_SURFACES = ["container", "container-hover", ...DIFF_SURFACES];
2257
- var FOCUS_SURFACES = [
2258
- "background",
2259
- "container",
2260
- "popover",
2261
- "nav-background",
2262
- "nav-item-hover",
2263
- "nav-item-active",
2264
- "nav-border"
2265
- ];
2266
- var text = (fg, bg, tier) => ({
2267
- fg,
2268
- bg,
2269
- tier,
2270
- kind: "text"
2271
- });
2272
- var label = (fg, bg, tier) => ({
2273
- fg,
2274
- bg,
2275
- tier,
2276
- kind: "label"
2277
- });
2278
- var ink = (fg, bg, tier) => ({
2279
- fg,
2280
- bg,
2281
- tier,
2282
- kind: "ink"
2283
- });
2284
- var APCA_PAIRS = [
2285
- text("foreground", "background", "body"),
2286
- text("foreground", "background-hover", "ui"),
2287
- text("foreground", "muted", "ui"),
2288
- text("muted-foreground", "muted", "ui"),
2289
- text("muted-foreground-subtle", "muted", "incidental"),
2290
- text("muted-foreground", "container", "ui"),
2291
- text("muted-foreground-subtle", "container", "incidental"),
2292
- text("foreground", "muted-extralight", "ui"),
2293
- text("muted-foreground", "muted-extralight", "ui"),
2294
- text("disabled-foreground", "disabled", "incidental"),
2295
- text("container-foreground", "container", "body"),
2296
- text("foreground", "container", "body"),
2297
- text("popover-foreground", "popover", "body"),
2298
- text("popover-foreground", "popover-hover", "ui"),
2299
- text("foreground", "control-background", "ui"),
2300
- text("foreground", "control-background-hover", "ui"),
2301
- text("nav-foreground", "nav-background", "ui"),
2302
- text("nav-muted-foreground", "nav-background", "incidental"),
2303
- text("nav-foreground", "nav-item-hover", "ui"),
2304
- text("nav-foreground", "nav-item-active", "ui"),
2305
- ink("error-subtle-foreground", "background", "ui"),
2306
- ink("error-subtle-foreground", "container", "ui"),
2307
- ...COLOR_FAMILIES.flatMap((family) => [
2308
- label(`${family}-foreground`, `${family}-background`, "ui"),
2309
- label(`${family}-foreground`, `${family}-background-hover`, "ui"),
2310
- label(`${family}-foreground`, `${family}-background-active`, "ui"),
2311
- ink(`${family}-subtle-foreground`, `${family}-subtle`, "ui"),
2312
- ink(`${family}-subtle-foreground`, `${family}-subtle-hover`, "ui"),
2313
- ink(`${family}-subtle-foreground`, `${family}-subtle-active`, "ui")
2314
- ]),
2315
- ...CATEGORICAL_INDICES.flatMap(
2316
- (index) => ["container", "container-hover", "background"].map((bg) => ({
2317
- ...ink(`chart-categorical-${index}`, bg, "ui"),
2318
- offset: (index - 1) * CHART_STAGGER_LC
2319
- }))
2320
- ),
2321
- ...CODE_SURFACES.flatMap(
2322
- (bg) => Object.keys(SYNTAX_PALETTE_REFERENCES).map(
2323
- (role) => ink(`syntax-${role}`, bg, "ui")
2324
- )
2325
- ),
2326
- // codeBlockTheme also colours code with the text tokens, which already read
2327
- // on container and container-hover.
2328
- ...DIFF_SURFACES.flatMap((bg) => [
2329
- text("foreground", bg, "ui"),
2330
- text("muted-foreground", bg, "ui"),
2331
- text("muted-foreground-subtle", bg, "incidental")
2332
- ]),
2333
- // Focus is the primary accent (components.md § Focus States), so it shares
2334
- // one solved color with primary-subtle-foreground.
2335
- ...FOCUS_SURFACES.flatMap((surface) => [
2336
- {
2337
- ...ink("focus-default", surface, "incidental"),
2338
- solveAs: "primary-subtle-foreground"
2339
2462
  },
2340
- ink("focus-error", surface, "incidental")
2341
- ]),
2342
- ...[
2343
- "background-active",
2344
- "container-hover",
2345
- "container-active",
2346
- "popover",
2347
- "popover-hover",
2348
- "popover-active",
2349
- "nav-background"
2350
- ].flatMap((surface) => [
2351
- text("foreground", surface, "ui"),
2352
- text("muted-foreground", surface, "ui"),
2353
- text("muted-foreground-subtle", surface, "incidental")
2354
- ]),
2355
- text("muted-foreground", "background", "ui"),
2356
- text("muted-foreground-subtle", "background", "incidental"),
2357
- text("container-foreground", "container-hover", "ui"),
2358
- text("container-foreground", "container-active", "ui"),
2359
- text("popover-foreground", "popover-active", "ui"),
2360
- ...["background", "container", "popover"].flatMap((backdrop) => [
2361
- { ...text("popover-foreground", "popover-alpha", "body"), backdrop },
2362
- { ...text("foreground", "background-hover-alpha", "ui"), backdrop }
2363
- ]),
2364
- ...["#000000", "#ffffff"].map((backdrop) => ({
2365
- ...text("popover-foreground", "popover-alpha", "body"),
2366
- backdrop
2367
- }))
2368
- ];
2369
-
2370
- // src/lib/perceptual-ramp.ts
2371
- import { clampChroma as clampChroma4, oklch as oklch5, parse as parse5 } from "culori";
2372
- var EMIT_GAMUT2 = "p3";
2373
- var CUSP_FRACTION2 = 0.95;
2374
- function isColor(value) {
2375
- return parse5(value) !== void 0;
2376
- }
2377
- function seedOklch(input) {
2378
- const parsed = parse5(input);
2379
- if (!parsed)
2380
- throw new Error(`perceptual-ramp: cannot parse color '${input}'`);
2381
- const converted = oklch5(parsed);
2382
- if (!converted)
2383
- throw new Error(`perceptual-ramp: cannot convert '${input}' to OKLCH`);
2384
- return converted;
2385
- }
2386
- function pinnedOklch(seedHex, shade, options = {}) {
2387
- const { capAtSeedChroma = true } = options;
2388
- const seed = seedOklch(seedHex);
2389
- const l = PERCEPTUAL_L_GRID[shade];
2390
- const hue = seed.h ?? 0;
2391
- const seedC = seed.c ?? 0;
2392
- const cuspC = clampChroma4({ mode: "oklch", l, c: 0.5, h: hue }, "oklch", EMIT_GAMUT2).c ?? 0;
2393
- let chroma = cuspC * CUSP_FRACTION2;
2394
- if (capAtSeedChroma) chroma = Math.min(chroma, seedC);
2395
- const target = { mode: "oklch", l, c: chroma, h: hue };
2396
- return formatOklch(clampChroma4(target, "oklch", EMIT_GAMUT2));
2397
- }
2398
- function rampFromSeed(seedHex, options = {}) {
2399
- const ramp = {};
2400
- for (const shade of SHADES)
2401
- ramp[shade] = pinnedOklch(seedHex, shade, options);
2402
- return ramp;
2403
- }
2404
-
2405
- // src/lib/contrast.ts
2406
- function normalizeContrast(value, fallback = 50) {
2407
- return typeof value === "number" && Number.isFinite(value) ? Math.round(Math.max(0, Math.min(100, value))) : fallback;
2408
- }
2409
- function contrastTarget(tier, contrast) {
2410
- return TIER_THRESHOLDS[tier] + 15 * contrast / 100;
2411
- }
2412
- var BISECT_STEPS = 12;
2413
- function bisect(fail, pass, passes) {
2414
- for (let i = 0; i < BISECT_STEPS; i++) {
2415
- const middle = (fail + pass) / 2;
2416
- if (passes(middle)) pass = middle;
2417
- else fail = middle;
2463
+ "6xl": {
2464
+ $value: {
2465
+ value: 60,
2466
+ unit: "px"
2467
+ },
2468
+ $type: "dimension"
2469
+ },
2470
+ "7xl": {
2471
+ $value: {
2472
+ value: 72,
2473
+ unit: "px"
2474
+ },
2475
+ $type: "dimension"
2476
+ },
2477
+ "8xl": {
2478
+ $value: {
2479
+ value: 96,
2480
+ unit: "px"
2481
+ },
2482
+ $type: "dimension"
2483
+ },
2484
+ "9xl": {
2485
+ $value: {
2486
+ value: 128,
2487
+ unit: "px"
2488
+ },
2489
+ $type: "dimension"
2490
+ }
2491
+ },
2492
+ letterspacing: {
2493
+ tighter: {
2494
+ $value: {
2495
+ value: -0.800000011920929,
2496
+ unit: "px"
2497
+ },
2498
+ $type: "dimension"
2499
+ },
2500
+ tight: {
2501
+ $value: {
2502
+ value: -0.4000000059604645,
2503
+ unit: "px"
2504
+ },
2505
+ $type: "dimension"
2506
+ },
2507
+ normal: {
2508
+ $value: {
2509
+ value: 0,
2510
+ unit: "px"
2511
+ },
2512
+ $type: "dimension"
2513
+ },
2514
+ wide: {
2515
+ $value: {
2516
+ value: 0.4000000059604645,
2517
+ unit: "px"
2518
+ },
2519
+ $type: "dimension"
2520
+ },
2521
+ wider: {
2522
+ $value: {
2523
+ value: 0.800000011920929,
2524
+ unit: "px"
2525
+ },
2526
+ $type: "dimension"
2527
+ },
2528
+ widest: {
2529
+ $value: {
2530
+ value: 1.600000023841858,
2531
+ unit: "px"
2532
+ },
2533
+ $type: "dimension"
2534
+ }
2418
2535
  }
2419
- return pass;
2420
- }
2421
- var token = (map, name) => {
2422
- const value = map[`--nx-color-${name}`];
2423
- if (value === void 0) throw new Error(`contrast: missing ${name}`);
2424
- return value;
2425
2536
  };
2426
- function pairBackground(map, pair) {
2427
- const backdrop = pair.backdrop ? resolveToSrgbInts(
2428
- pair.backdrop.startsWith("#") ? pair.backdrop : token(map, pair.backdrop)
2429
- ) : void 0;
2430
- return resolveToSrgbInts(token(map, pair.bg), backdrop);
2431
- }
2432
- function contrastForPair(map, pair) {
2433
- const background = pairBackground(map, pair);
2434
- return Math.abs(
2435
- APCAcontrast3(
2436
- sRGBtoY3(resolveToSrgbInts(token(map, pair.fg), background)),
2437
- sRGBtoY3(background)
2438
- )
2439
- );
2440
- }
2441
- var luminance = (color) => sRGBtoY3(resolveToSrgbInts(color));
2442
- var score = (foregroundY, backgroundY) => Math.abs(APCAcontrast3(foregroundY, backgroundY));
2443
- function constraintsFor(map, pairs, contrast, endpointY) {
2444
- const constraints = [];
2445
- for (const pair of pairs) {
2446
- const backgroundY = sRGBtoY3(pairBackground(map, pair));
2447
- const reachable = score(endpointY, backgroundY);
2448
- if (reachable < TIER_THRESHOLDS[pair.tier]) return void 0;
2449
- const target = contrastTarget(pair.tier, contrast) + (pair.offset ?? 0);
2450
- constraints.push({ backgroundY, target: Math.min(target, reachable) });
2451
- }
2452
- return constraints;
2453
- }
2454
- var keepsTextSeed = (mode, kind) => mode === "light" && kind === "text";
2455
- function solveForeground(seed, constraints, endpoint, keepSeed) {
2456
- const passes = (color) => {
2457
- const y = luminance(color);
2458
- return constraints.every((c) => score(y, c.backgroundY) >= c.target);
2459
- };
2460
- if (keepSeed && passes(seed)) return seed;
2461
- const parsed = seedOklch(seed);
2462
- const at = (l) => formatOklch(clampChroma5({ ...parsed, l, alpha: 1 }, "oklch", "p3"));
2463
- return at(
2464
- bisect(keepSeed ? parsed.l : 1 - endpoint, endpoint, (l) => passes(at(l)))
2465
- );
2466
- }
2467
- var SOLVE_GROUPS = /* @__PURE__ */ new Map();
2468
- for (const pair of APCA_PAIRS) {
2469
- const key = pair.solveAs ?? pair.fg;
2470
- const group = SOLVE_GROUPS.get(key);
2471
- if (!group) {
2472
- SOLVE_GROUPS.set(key, { kind: pair.kind, pairs: [pair] });
2473
- continue;
2474
- }
2475
- if (group.kind !== pair.kind)
2476
- throw new Error(
2477
- `contrast: ${pair.fg} is '${pair.kind}' but joins '${group.kind}' group ${key}`
2478
- );
2479
- group.pairs.push(pair);
2480
- }
2481
- function constrainFamilyFills(map, name, pairs) {
2482
- const basePair = pairs[0];
2483
- if (!basePair) throw new Error(`contrast: missing pairs for ${name}`);
2484
- const baseY = luminance(token(map, basePair.bg));
2485
- const endpoint = score(1, baseY) >= score(0, baseY) ? 1 : 0;
2486
- map[`--nx-color-${name}`] = `oklch(${endpoint} 0 0)`;
2487
- for (const pair of pairs) {
2488
- const floor = TIER_THRESHOLDS[pair.tier];
2489
- const value = token(map, pair.bg);
2490
- if (score(endpoint, luminance(value)) >= floor) continue;
2491
- const fill = seedOklch(value);
2492
- const at = (l) => formatOklch(clampChroma5({ ...fill, l }, "oklch", "p3"));
2493
- const readable = (l) => score(endpoint, luminance(at(l))) >= floor;
2494
- map[`--nx-color-${pair.bg}`] = at(bisect(fill.l, 1 - endpoint, readable));
2495
- }
2496
- }
2497
- function constrainColors(map, mode, contrast) {
2498
- for (const [name, { kind, pairs }] of SOLVE_GROUPS)
2499
- if (kind === "label") constrainFamilyFills(map, name, pairs);
2500
- const endpoint = mode === "dark" ? 1 : 0;
2501
- const endpointY = luminance(`oklch(${endpoint} 0 0)`);
2502
- for (const [name, { kind, pairs }] of SOLVE_GROUPS) {
2503
- if (kind === "label") continue;
2504
- const constraints = constraintsFor(map, pairs, contrast, endpointY);
2505
- if (constraints === void 0)
2506
- throw new Error(`contrast: no readable ${name} in ${mode}`);
2507
- const solved = solveForeground(
2508
- token(map, name),
2509
- constraints,
2510
- endpoint,
2511
- keepsTextSeed(mode, kind)
2512
- );
2513
- for (const fg of new Set(pairs.map((pair) => pair.fg).concat(name)))
2514
- map[`--nx-color-${fg}`] = solved;
2515
- }
2516
- return map;
2517
- }
2518
2537
 
2519
- // src/lib/runtime-layer.ts
2520
- var RUNTIME_LAYER_ORDER = "@layer properties, theme, base, components, utilities, nx-runtime;";
2521
- function inRuntimeLayer(css) {
2522
- return `${RUNTIME_LAYER_ORDER}
2523
- @layer nx-runtime {
2524
- ${css}
2538
+ // src/lib/typography-scale.ts
2539
+ var TYPOGRAPHY_BASE_PX = 14;
2540
+ function formatPx(value) {
2541
+ return `${Number(value.toFixed(4))}px`;
2525
2542
  }
2526
- `;
2543
+ var CODE_SIZE = "code";
2544
+ function typographyScale(uiPx, codePx) {
2545
+ const scale = uiPx / TYPOGRAPHY_BASE_PX;
2546
+ return new Map([
2547
+ ...Object.entries(typography_default_default.size).map(
2548
+ ([name, token2]) => [
2549
+ `--nx-typography-size-${name}`,
2550
+ formatPx(name === CODE_SIZE ? codePx : token2.$value.value * scale)
2551
+ ]
2552
+ ),
2553
+ ...Object.entries(typography_default_default["line-height"]).map(
2554
+ ([name, token2]) => [
2555
+ `--nx-typography-line-height-${name}`,
2556
+ formatPx(token2.$value.value * scale)
2557
+ ]
2558
+ )
2559
+ ]);
2527
2560
  }
2528
2561
 
2529
2562
  // src/lib/appearance-model.ts
@@ -2596,16 +2629,17 @@ var DEFAULT_NXDS_APPEARANCE = {
2596
2629
  stroke: "normal",
2597
2630
  prefs: {
2598
2631
  uiFont: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
2632
+ headingFont: null,
2599
2633
  codeFont: 'ui-monospace, "SF Mono", Menlo, monospace',
2600
- uiFontSize: 14,
2634
+ uiFontSize: TYPOGRAPHY_BASE_PX,
2601
2635
  codeFontSize: 12,
2602
2636
  reduceMotion: false,
2603
2637
  pointerCursors: false,
2604
2638
  fontSmoothing: false
2605
2639
  }
2606
2640
  };
2607
- var FONT_PX_MIN = 8;
2608
- var FONT_PX_MAX = 32;
2641
+ var FONT_SIZE_PX_MIN = 8;
2642
+ var FONT_SIZE_PX_MAX = 32;
2609
2643
  var APPEARANCE_MODES = /* @__PURE__ */ new Set([
2610
2644
  "light",
2611
2645
  "dark",
@@ -2631,19 +2665,9 @@ var FONT_FAMILY_UNSAFE = /[;{}<]/;
2631
2665
  var fontFamilyOr = (value, fallback) => typeof value === "string" && value.length > 0 && !FONT_FAMILY_UNSAFE.test(value) ? value : fallback;
2632
2666
  var boolOr = (value, fallback) => typeof value === "boolean" ? value : fallback;
2633
2667
  var enumOr = (value, values, fallback) => typeof value === "string" && values.has(value) ? value : fallback;
2634
- var clampFontSize = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(FONT_PX_MAX, Math.max(FONT_PX_MIN, value)) : fallback;
2635
- function formatPx(value) {
2636
- return `${Number(value.toFixed(4))}px`;
2637
- }
2638
- function typographyScaleVariables(uiPx) {
2639
- const scale = uiPx / DEFAULT_NXDS_APPEARANCE.prefs.uiFontSize;
2640
- const sizeVars = Object.entries(typography_default_default.size).map(
2641
- ([name, token2]) => ` --nx-typography-size-${name}: ${formatPx(token2.$value.value * scale)};`
2642
- );
2643
- const lineHeightVars = Object.entries(typography_default_default["line-height"]).map(
2644
- ([name, token2]) => ` --nx-typography-line-height-${name}: ${formatPx(token2.$value.value * scale)};`
2645
- );
2646
- return [...sizeVars, ...lineHeightVars].join("\n");
2668
+ var clampFontSize = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(FONT_SIZE_PX_MAX, Math.max(FONT_SIZE_PX_MIN, value)) : fallback;
2669
+ function typographyScaleVariables(uiPx, codePx) {
2670
+ return [...typographyScale(uiPx, codePx)].map(([name, value]) => ` ${name}: ${value};`).join("\n");
2647
2671
  }
2648
2672
  var PUBLIC_MODE_RENAME = {
2649
2673
  density: {
@@ -2682,6 +2706,7 @@ function sanitizeNxdsAppearancePrefs(raw) {
2682
2706
  const d = DEFAULT_NXDS_APPEARANCE.prefs;
2683
2707
  return {
2684
2708
  uiFont: fontFamilyOr(o.uiFont, d.uiFont),
2709
+ headingFont: fontFamilyOr(o.headingFont, d.headingFont),
2685
2710
  codeFont: fontFamilyOr(o.codeFont, d.codeFont),
2686
2711
  uiFontSize: clampFontSize(o.uiFontSize, d.uiFontSize),
2687
2712
  codeFontSize: clampFontSize(o.codeFontSize, d.codeFontSize),
@@ -2728,10 +2753,10 @@ function appearancePrefsToCss(prefs) {
2728
2753
  const blocks = [
2729
2754
  inRuntimeLayer(`:root {
2730
2755
  --nx-typography-family-font-sans: ${prefs.uiFont};
2756
+ --nx-typography-family-font-heading: ${prefs.headingFont ?? "var(--nx-typography-family-font-sans)"};
2731
2757
  --nx-typography-family-font-mono: ${prefs.codeFont};
2732
- ${typographyScaleVariables(uiPx)}
2733
- }`),
2734
- `.nx\\:font-mono, .nx\\:typography-code-block, .nx\\:typography-code-inline { font-size: ${codePx}px; }`
2758
+ ${typographyScaleVariables(uiPx, codePx)}
2759
+ }`)
2735
2760
  ];
2736
2761
  if (prefs.fontSmoothing) {
2737
2762
  blocks.push(
@@ -4828,6 +4853,127 @@ function findBrandColorPreset(color) {
4828
4853
  return BRAND_COLOR_PRESETS.find((preset) => preset.color === hex);
4829
4854
  }
4830
4855
 
4856
+ // src/lib/font-options.ts
4857
+ var SANS_FALLBACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
4858
+ var SERIF_FALLBACK = "Georgia, serif";
4859
+ var MONO_FALLBACK = 'ui-monospace, "SF Mono", Menlo, monospace';
4860
+ var FONT_OPTIONS = [
4861
+ {
4862
+ value: "geist",
4863
+ label: "Geist",
4864
+ category: "sans",
4865
+ family: `"Geist Variable", ${SANS_FALLBACK}`,
4866
+ package: "@fontsource-variable/geist"
4867
+ },
4868
+ {
4869
+ value: "inter",
4870
+ label: "Inter",
4871
+ category: "sans",
4872
+ family: `"Inter Variable", ${SANS_FALLBACK}`,
4873
+ package: "@fontsource-variable/inter"
4874
+ },
4875
+ {
4876
+ value: "figtree",
4877
+ label: "Figtree",
4878
+ category: "sans",
4879
+ family: `"Figtree Variable", ${SANS_FALLBACK}`,
4880
+ package: "@fontsource-variable/figtree"
4881
+ },
4882
+ {
4883
+ value: "dm-sans",
4884
+ label: "DM Sans",
4885
+ category: "sans",
4886
+ family: `"DM Sans Variable", ${SANS_FALLBACK}`,
4887
+ package: "@fontsource-variable/dm-sans"
4888
+ },
4889
+ {
4890
+ value: "manrope",
4891
+ label: "Manrope",
4892
+ category: "sans",
4893
+ family: `"Manrope Variable", ${SANS_FALLBACK}`,
4894
+ package: "@fontsource-variable/manrope"
4895
+ },
4896
+ {
4897
+ value: "ibm-plex-sans",
4898
+ label: "IBM Plex Sans",
4899
+ category: "sans",
4900
+ family: `"IBM Plex Sans Variable", ${SANS_FALLBACK}`,
4901
+ package: "@fontsource-variable/ibm-plex-sans"
4902
+ },
4903
+ {
4904
+ value: "lora",
4905
+ label: "Lora",
4906
+ category: "serif",
4907
+ family: `"Lora Variable", ${SERIF_FALLBACK}`,
4908
+ package: "@fontsource-variable/lora"
4909
+ },
4910
+ {
4911
+ value: "playfair-display",
4912
+ label: "Playfair Display",
4913
+ category: "serif",
4914
+ family: `"Playfair Display Variable", ${SERIF_FALLBACK}`,
4915
+ package: "@fontsource-variable/playfair-display"
4916
+ },
4917
+ {
4918
+ value: "geist-mono",
4919
+ label: "Geist Mono",
4920
+ category: "mono",
4921
+ family: `"Geist Mono Variable", ${MONO_FALLBACK}`,
4922
+ package: "@fontsource-variable/geist-mono"
4923
+ },
4924
+ {
4925
+ value: "jetbrains-mono",
4926
+ label: "JetBrains Mono",
4927
+ category: "mono",
4928
+ family: `"JetBrains Mono Variable", ${MONO_FALLBACK}`,
4929
+ package: "@fontsource-variable/jetbrains-mono"
4930
+ }
4931
+ ];
4932
+
4933
+ // src/lib/icon-libraries.ts
4934
+ var ICON_LIBRARY_OPTIONS = [
4935
+ { value: "phosphor", label: "Phosphor" },
4936
+ { value: "lucide", label: "Lucide" },
4937
+ { value: "tabler", label: "Tabler Icons" },
4938
+ { value: "hugeicons", label: "HugeIcons" },
4939
+ { value: "remix", label: "Remix Icon" }
4940
+ ];
4941
+ var DEFAULT_ICON_LIBRARY = "phosphor";
4942
+
4943
+ // src/lib/appearance-shuffle.ts
4944
+ function pick(options, random) {
4945
+ const option = options[Math.floor(random() * options.length)];
4946
+ if (option === void 0) throw new Error("No option to pick from.");
4947
+ return option;
4948
+ }
4949
+ function shuffleNxdsAppearance(state, { fonts = FONT_OPTIONS, random = Math.random } = {}) {
4950
+ const textFonts = fonts.filter((font) => font.category !== "mono").map((font) => font.family);
4951
+ if (textFonts.length === 0) {
4952
+ throw new Error(
4953
+ "shuffleNxdsAppearance: `fonts` needs at least one sans or serif font."
4954
+ );
4955
+ }
4956
+ return {
4957
+ ...state,
4958
+ brandColor: pick(BRAND_COLOR_PRESETS, random).color,
4959
+ surfaceTone: pick(BASE_TONE_OPTIONS, random).value,
4960
+ density: pick(DENSITY_OPTIONS, random).value,
4961
+ corners: pick(CORNER_OPTIONS, random).value,
4962
+ elevation: pick(ELEVATION_OPTIONS, random).value,
4963
+ stroke: pick(STROKE_OPTIONS, random).value,
4964
+ prefs: {
4965
+ ...state.prefs,
4966
+ uiFont: pick(textFonts, random),
4967
+ headingFont: pick([null, ...textFonts], random)
4968
+ }
4969
+ };
4970
+ }
4971
+ function shuffleNxdsIconLibrary({
4972
+ random = Math.random
4973
+ } = {}) {
4974
+ return pick(ICON_LIBRARY_OPTIONS, random).value;
4975
+ }
4976
+
4831
4977
  // src/lib/theme-contrast.ts
4832
4978
  var MODES = ["light", "dark"];
4833
4979
  var cssVar = (name) => `--nx-color-${name}`;
@@ -4853,6 +4999,211 @@ function measureThemeContrast(theme) {
4853
4999
  );
4854
5000
  }
4855
5001
 
5002
+ // src/lib/theme-fields.ts
5003
+ var DEFAULT_NXDS_THEME = {
5004
+ appearance: DEFAULT_NXDS_APPEARANCE,
5005
+ iconLibrary: DEFAULT_ICON_LIBRARY
5006
+ };
5007
+ function invalid(flag, value, accepted) {
5008
+ return new Error(`--${flag} "${value}" isn't ${accepted}.`);
5009
+ }
5010
+ function withAppearance(theme, patch) {
5011
+ return { ...theme, appearance: { ...theme.appearance, ...patch } };
5012
+ }
5013
+ function withPrefs(theme, patch) {
5014
+ return withAppearance(theme, {
5015
+ prefs: { ...theme.appearance.prefs, ...patch }
5016
+ });
5017
+ }
5018
+ function oneOf(flag, values, value) {
5019
+ const match = values.find((candidate) => candidate === value);
5020
+ if (match === void 0) {
5021
+ throw invalid(flag, value, `one of ${values.join(", ")}`);
5022
+ }
5023
+ return match;
5024
+ }
5025
+ function enumField(flag, key, description, values) {
5026
+ return {
5027
+ kind: "choice",
5028
+ flag,
5029
+ key,
5030
+ description,
5031
+ values,
5032
+ read: (theme) => theme.appearance[key],
5033
+ write: (theme, value) => withAppearance(theme, {
5034
+ [key]: oneOf(flag, values, value)
5035
+ })
5036
+ };
5037
+ }
5038
+ function integer(flag, value, min, max) {
5039
+ const parsed = Number(value);
5040
+ if (!/^\d+$/.test(value) || parsed < min || parsed > max) {
5041
+ throw invalid(flag, value, `a whole number from ${min} to ${max}`);
5042
+ }
5043
+ return parsed;
5044
+ }
5045
+ function contrastField(flag, key, description) {
5046
+ return {
5047
+ kind: "integer",
5048
+ flag,
5049
+ key,
5050
+ description,
5051
+ min: CONTRAST_MIN,
5052
+ max: CONTRAST_MAX,
5053
+ read: (theme) => String(theme.appearance[key]),
5054
+ write: (theme, value) => withAppearance(theme, {
5055
+ [key]: integer(flag, value, CONTRAST_MIN, CONTRAST_MAX)
5056
+ })
5057
+ };
5058
+ }
5059
+ function fontSizeField(flag, key, description) {
5060
+ return {
5061
+ kind: "integer",
5062
+ flag,
5063
+ key: `prefs.${key}`,
5064
+ description,
5065
+ min: FONT_SIZE_PX_MIN,
5066
+ max: FONT_SIZE_PX_MAX,
5067
+ read: (theme) => String(theme.appearance.prefs[key]),
5068
+ write: (theme, value) => withPrefs(theme, {
5069
+ [key]: integer(flag, value, FONT_SIZE_PX_MIN, FONT_SIZE_PX_MAX)
5070
+ })
5071
+ };
5072
+ }
5073
+ function fontField(flag, key, description, categories, fallback) {
5074
+ const fonts = FONT_OPTIONS.filter(
5075
+ (font) => categories.includes(font.category)
5076
+ );
5077
+ return {
5078
+ kind: "font",
5079
+ flag,
5080
+ key: `prefs.${key}`,
5081
+ description,
5082
+ values: [fallback.value, ...fonts.map((font) => font.value)],
5083
+ categories,
5084
+ fallback,
5085
+ read: (theme) => {
5086
+ const family = theme.appearance.prefs[key];
5087
+ if (family === fallback.family) return fallback.value;
5088
+ return fonts.find((font) => font.family === family)?.value;
5089
+ },
5090
+ write: (theme, value) => {
5091
+ const family = value === fallback.value ? fallback.family : fonts.find((font) => font.value === value)?.family;
5092
+ if (family === void 0) {
5093
+ throw invalid(
5094
+ flag,
5095
+ value,
5096
+ `one of ${[fallback.value, ...fonts.map((font) => font.value)].join(", ")}`
5097
+ );
5098
+ }
5099
+ return withPrefs(theme, { [key]: family });
5100
+ }
5101
+ };
5102
+ }
5103
+ var DEFAULT_PREFS = DEFAULT_NXDS_APPEARANCE.prefs;
5104
+ var TEXT_FONTS = ["sans", "serif"];
5105
+ var NXDS_THEME_FIELDS = [
5106
+ {
5107
+ kind: "color",
5108
+ flag: "brand-color",
5109
+ key: "brandColor",
5110
+ description: 'brand color, as any CSS color, such as "#4f46e5"',
5111
+ read: (theme) => theme.appearance.brandColor,
5112
+ write: (theme, value) => {
5113
+ if (!isColor(value)) throw invalid("brand-color", value, "a CSS color");
5114
+ return withAppearance(theme, { brandColor: value });
5115
+ }
5116
+ },
5117
+ enumField(
5118
+ "surface-tone",
5119
+ "surfaceTone",
5120
+ "neutral tone of backgrounds and borders",
5121
+ BASE_TONE_OPTIONS.map((option) => option.value)
5122
+ ),
5123
+ enumField(
5124
+ "density",
5125
+ "density",
5126
+ "spacing and control size",
5127
+ DENSITY_OPTIONS.map((option) => option.value)
5128
+ ),
5129
+ enumField(
5130
+ "corners",
5131
+ "corners",
5132
+ "corner radius",
5133
+ CORNER_OPTIONS.map((option) => option.value)
5134
+ ),
5135
+ enumField(
5136
+ "elevation",
5137
+ "elevation",
5138
+ "shadow depth",
5139
+ ELEVATION_OPTIONS.map((option) => option.value)
5140
+ ),
5141
+ enumField(
5142
+ "stroke",
5143
+ "stroke",
5144
+ "border width",
5145
+ STROKE_OPTIONS.map((option) => option.value)
5146
+ ),
5147
+ enumField("mode", "mode", "color scheme", ["light", "dark", "system"]),
5148
+ contrastField(
5149
+ "light-contrast",
5150
+ "lightContrast",
5151
+ "light mode contrast, 0 to 100"
5152
+ ),
5153
+ contrastField(
5154
+ "dark-contrast",
5155
+ "darkContrast",
5156
+ "dark mode contrast, 0 to 100"
5157
+ ),
5158
+ fontField("font", "uiFont", "font for UI text", TEXT_FONTS, {
5159
+ value: "system",
5160
+ family: DEFAULT_PREFS.uiFont
5161
+ }),
5162
+ fontField(
5163
+ "heading-font",
5164
+ "headingFont",
5165
+ 'font for headings, or "ui" for the UI font',
5166
+ TEXT_FONTS,
5167
+ { value: "ui", family: null }
5168
+ ),
5169
+ fontField("code-font", "codeFont", "font for code", ["mono"], {
5170
+ value: "system",
5171
+ family: DEFAULT_PREFS.codeFont
5172
+ }),
5173
+ fontSizeField("font-size", "uiFontSize", "UI text size in px"),
5174
+ fontSizeField("code-font-size", "codeFontSize", "code text size in px"),
5175
+ {
5176
+ kind: "choice",
5177
+ flag: "icons",
5178
+ description: "icon library nxds components draw",
5179
+ values: ICON_LIBRARY_OPTIONS.map((option) => option.value),
5180
+ read: (theme) => theme.iconLibrary,
5181
+ write: (theme, value) => ({
5182
+ ...theme,
5183
+ iconLibrary: oneOf(
5184
+ "icons",
5185
+ ICON_LIBRARY_OPTIONS.map((option) => option.value),
5186
+ value
5187
+ )
5188
+ })
5189
+ }
5190
+ ];
5191
+ function nxdsThemeFlags(theme) {
5192
+ return NXDS_THEME_FIELDS.flatMap((field) => {
5193
+ const value = field.read(theme);
5194
+ if (value === void 0 || value === field.read(DEFAULT_NXDS_THEME)) {
5195
+ return [];
5196
+ }
5197
+ return [{ flag: field.flag, value }];
5198
+ });
5199
+ }
5200
+ function applyNxdsThemeFlags(theme, values) {
5201
+ return NXDS_THEME_FIELDS.reduce((current, field) => {
5202
+ const value = values[field.flag];
5203
+ return value === void 0 ? current : field.write(current, value);
5204
+ }, theme);
5205
+ }
5206
+
4856
5207
  // src/lib/token-registry.ts
4857
5208
  var SURFACE_TOKEN_NAMES = [
4858
5209
  "background",
@@ -5002,12 +5353,19 @@ export {
5002
5353
  CORNER_OPTIONS,
5003
5354
  DEFAULT_BRAND_COLOR,
5004
5355
  DEFAULT_COOKIE_KEY,
5356
+ DEFAULT_ICON_LIBRARY,
5005
5357
  DEFAULT_NXDS_APPEARANCE,
5358
+ DEFAULT_NXDS_THEME,
5006
5359
  DEFAULT_STORAGE_KEY,
5007
5360
  DENSITY_OPTIONS,
5008
5361
  ELEVATION_OPTIONS,
5362
+ FONT_OPTIONS,
5363
+ FONT_SIZE_PX_MAX,
5364
+ FONT_SIZE_PX_MIN,
5365
+ ICON_LIBRARY_OPTIONS,
5009
5366
  NXDS_APPEARANCE_COOKIE_MAX_AGE_SECONDS,
5010
5367
  NXDS_APPEARANCE_DATA_ATTRS,
5368
+ NXDS_THEME_FIELDS,
5011
5369
  PALETTE_KEYS,
5012
5370
  RUNTIME_LAYER_ORDER,
5013
5371
  SEMANTIC_TOKEN_REGISTRY,
@@ -5018,6 +5376,7 @@ export {
5018
5376
  apcaLc,
5019
5377
  appearancePrefsToCss,
5020
5378
  applyNxdsAppearance,
5379
+ applyNxdsThemeFlags,
5021
5380
  createDefaultNxdsAppearanceSnapshot,
5022
5381
  createNxdsAppearanceBootstrapScript,
5023
5382
  createNxdsAppearanceSnapshot,
@@ -5030,6 +5389,7 @@ export {
5030
5389
  isColor,
5031
5390
  measureThemeContrast,
5032
5391
  normalizeAppearanceModeIds,
5392
+ nxdsThemeFlags,
5033
5393
  parseNxdsAppearanceStateCookie,
5034
5394
  persistNxdsAppearance,
5035
5395
  prefersDarkColorScheme,
@@ -5041,6 +5401,8 @@ export {
5041
5401
  sanitizeNxdsAppearancePrefs,
5042
5402
  sanitizeNxdsAppearanceSnapshot,
5043
5403
  serializeNxdsAppearanceStateCookie,
5404
+ shuffleNxdsAppearance,
5405
+ shuffleNxdsIconLibrary,
5044
5406
  themeToCss,
5045
5407
  watchColorScheme
5046
5408
  };