@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.
@@ -26,12 +26,19 @@ __export(src_exports, {
26
26
  CORNER_OPTIONS: () => CORNER_OPTIONS,
27
27
  DEFAULT_BRAND_COLOR: () => DEFAULT_BRAND_COLOR,
28
28
  DEFAULT_COOKIE_KEY: () => DEFAULT_COOKIE_KEY,
29
+ DEFAULT_ICON_LIBRARY: () => DEFAULT_ICON_LIBRARY,
29
30
  DEFAULT_NXDS_APPEARANCE: () => DEFAULT_NXDS_APPEARANCE,
31
+ DEFAULT_NXDS_THEME: () => DEFAULT_NXDS_THEME,
30
32
  DEFAULT_STORAGE_KEY: () => DEFAULT_STORAGE_KEY,
31
33
  DENSITY_OPTIONS: () => DENSITY_OPTIONS,
32
34
  ELEVATION_OPTIONS: () => ELEVATION_OPTIONS,
35
+ FONT_OPTIONS: () => FONT_OPTIONS,
36
+ FONT_SIZE_PX_MAX: () => FONT_SIZE_PX_MAX,
37
+ FONT_SIZE_PX_MIN: () => FONT_SIZE_PX_MIN,
38
+ ICON_LIBRARY_OPTIONS: () => ICON_LIBRARY_OPTIONS,
33
39
  NXDS_APPEARANCE_COOKIE_MAX_AGE_SECONDS: () => NXDS_APPEARANCE_COOKIE_MAX_AGE_SECONDS,
34
40
  NXDS_APPEARANCE_DATA_ATTRS: () => NXDS_APPEARANCE_DATA_ATTRS,
41
+ NXDS_THEME_FIELDS: () => NXDS_THEME_FIELDS,
35
42
  PALETTE_KEYS: () => PALETTE_KEYS,
36
43
  RUNTIME_LAYER_ORDER: () => RUNTIME_LAYER_ORDER,
37
44
  SEMANTIC_TOKEN_REGISTRY: () => SEMANTIC_TOKEN_REGISTRY,
@@ -42,6 +49,7 @@ __export(src_exports, {
42
49
  apcaLc: () => apcaLc,
43
50
  appearancePrefsToCss: () => appearancePrefsToCss,
44
51
  applyNxdsAppearance: () => applyNxdsAppearance,
52
+ applyNxdsThemeFlags: () => applyNxdsThemeFlags,
45
53
  createDefaultNxdsAppearanceSnapshot: () => createDefaultNxdsAppearanceSnapshot,
46
54
  createNxdsAppearanceBootstrapScript: () => createNxdsAppearanceBootstrapScript,
47
55
  createNxdsAppearanceSnapshot: () => createNxdsAppearanceSnapshot,
@@ -54,6 +62,7 @@ __export(src_exports, {
54
62
  isColor: () => isColor,
55
63
  measureThemeContrast: () => measureThemeContrast,
56
64
  normalizeAppearanceModeIds: () => normalizeAppearanceModeIds,
65
+ nxdsThemeFlags: () => nxdsThemeFlags,
57
66
  parseNxdsAppearanceStateCookie: () => parseNxdsAppearanceStateCookie,
58
67
  persistNxdsAppearance: () => persistNxdsAppearance,
59
68
  prefersDarkColorScheme: () => prefersDarkColorScheme,
@@ -65,6 +74,8 @@ __export(src_exports, {
65
74
  sanitizeNxdsAppearancePrefs: () => sanitizeNxdsAppearancePrefs,
66
75
  sanitizeNxdsAppearanceSnapshot: () => sanitizeNxdsAppearanceSnapshot,
67
76
  serializeNxdsAppearanceStateCookie: () => serializeNxdsAppearanceStateCookie,
77
+ shuffleNxdsAppearance: () => shuffleNxdsAppearance,
78
+ shuffleNxdsIconLibrary: () => shuffleNxdsIconLibrary,
68
79
  themeToCss: () => themeToCss,
69
80
  watchColorScheme: () => watchColorScheme
70
81
  });
@@ -1961,6 +1972,321 @@ function apcaLc(foreground, background) {
1961
1972
  );
1962
1973
  }
1963
1974
 
1975
+ // src/lib/contrast.ts
1976
+ var import_apca_w33 = require("apca-w3");
1977
+ var import_culori6 = require("culori");
1978
+
1979
+ // src/lib/semantic-palette-references.ts
1980
+ var STATUS_PALETTE_FAMILIES = {
1981
+ success: "green",
1982
+ warning: "orange",
1983
+ error: "red",
1984
+ information: "blue"
1985
+ };
1986
+ var CHART_PALETTE_REFERENCES = [
1987
+ { palette: "teal", light: "700", dark: "200" },
1988
+ { palette: "lime", light: "700", dark: "200" },
1989
+ { palette: "orange", light: "600", dark: "200" },
1990
+ { palette: "rose", light: "600", dark: "200" },
1991
+ { palette: "indigo", light: "600", dark: "200" }
1992
+ ];
1993
+ var SYNTAX_PALETTE_REFERENCES = {
1994
+ keyword: CHART_PALETTE_REFERENCES[3],
1995
+ string: CHART_PALETTE_REFERENCES[1],
1996
+ function: CHART_PALETTE_REFERENCES[4],
1997
+ type: CHART_PALETTE_REFERENCES[0],
1998
+ constant: CHART_PALETTE_REFERENCES[2]
1999
+ };
2000
+
2001
+ // src/lib/apca-pairs.ts
2002
+ var COLOR_FAMILIES = [
2003
+ "primary",
2004
+ "secondary",
2005
+ "success",
2006
+ "warning",
2007
+ "error",
2008
+ "information"
2009
+ ];
2010
+ var CATEGORICAL_INDICES = CHART_PALETTE_REFERENCES.map(
2011
+ (_, index) => index + 1
2012
+ );
2013
+ var CHART_STAGGER_LC = 4.5;
2014
+ var DIFF_SURFACES = ["success-subtle", "error-subtle"];
2015
+ var CODE_SURFACES = ["container", "container-hover", ...DIFF_SURFACES];
2016
+ var FOCUS_SURFACES = [
2017
+ "background",
2018
+ "container",
2019
+ "popover",
2020
+ "nav-background",
2021
+ "nav-item-hover",
2022
+ "nav-item-active",
2023
+ "nav-border"
2024
+ ];
2025
+ var text = (fg, bg, tier) => ({
2026
+ fg,
2027
+ bg,
2028
+ tier,
2029
+ kind: "text"
2030
+ });
2031
+ var label = (fg, bg, tier) => ({
2032
+ fg,
2033
+ bg,
2034
+ tier,
2035
+ kind: "label"
2036
+ });
2037
+ var ink = (fg, bg, tier) => ({
2038
+ fg,
2039
+ bg,
2040
+ tier,
2041
+ kind: "ink"
2042
+ });
2043
+ var APCA_PAIRS = [
2044
+ text("foreground", "background", "body"),
2045
+ text("foreground", "background-hover", "ui"),
2046
+ text("foreground", "muted", "ui"),
2047
+ text("muted-foreground", "muted", "ui"),
2048
+ text("muted-foreground-subtle", "muted", "incidental"),
2049
+ text("muted-foreground", "container", "ui"),
2050
+ text("muted-foreground-subtle", "container", "incidental"),
2051
+ text("foreground", "muted-extralight", "ui"),
2052
+ text("muted-foreground", "muted-extralight", "ui"),
2053
+ text("disabled-foreground", "disabled", "incidental"),
2054
+ text("container-foreground", "container", "body"),
2055
+ text("foreground", "container", "body"),
2056
+ text("popover-foreground", "popover", "body"),
2057
+ text("popover-foreground", "popover-hover", "ui"),
2058
+ text("foreground", "control-background", "ui"),
2059
+ text("foreground", "control-background-hover", "ui"),
2060
+ text("nav-foreground", "nav-background", "ui"),
2061
+ text("nav-muted-foreground", "nav-background", "incidental"),
2062
+ text("nav-foreground", "nav-item-hover", "ui"),
2063
+ text("nav-foreground", "nav-item-active", "ui"),
2064
+ ink("error-subtle-foreground", "background", "ui"),
2065
+ ink("error-subtle-foreground", "container", "ui"),
2066
+ ...COLOR_FAMILIES.flatMap((family) => [
2067
+ label(`${family}-foreground`, `${family}-background`, "ui"),
2068
+ label(`${family}-foreground`, `${family}-background-hover`, "ui"),
2069
+ label(`${family}-foreground`, `${family}-background-active`, "ui"),
2070
+ ink(`${family}-subtle-foreground`, `${family}-subtle`, "ui"),
2071
+ ink(`${family}-subtle-foreground`, `${family}-subtle-hover`, "ui"),
2072
+ ink(`${family}-subtle-foreground`, `${family}-subtle-active`, "ui")
2073
+ ]),
2074
+ ...CATEGORICAL_INDICES.flatMap(
2075
+ (index) => ["container", "container-hover", "background"].map((bg) => ({
2076
+ ...ink(`chart-categorical-${index}`, bg, "ui"),
2077
+ offset: (index - 1) * CHART_STAGGER_LC
2078
+ }))
2079
+ ),
2080
+ ...CODE_SURFACES.flatMap(
2081
+ (bg) => Object.keys(SYNTAX_PALETTE_REFERENCES).map(
2082
+ (role) => ink(`syntax-${role}`, bg, "ui")
2083
+ )
2084
+ ),
2085
+ // codeBlockTheme also colours code with the text tokens, which already read
2086
+ // on container and container-hover.
2087
+ ...DIFF_SURFACES.flatMap((bg) => [
2088
+ text("foreground", bg, "ui"),
2089
+ text("muted-foreground", bg, "ui"),
2090
+ text("muted-foreground-subtle", bg, "incidental")
2091
+ ]),
2092
+ // Focus is the primary accent (components.md § Focus States), so it shares
2093
+ // one solved color with primary-subtle-foreground.
2094
+ ...FOCUS_SURFACES.flatMap((surface) => [
2095
+ {
2096
+ ...ink("focus-default", surface, "incidental"),
2097
+ solveAs: "primary-subtle-foreground"
2098
+ },
2099
+ ink("focus-error", surface, "incidental")
2100
+ ]),
2101
+ ...[
2102
+ "background-active",
2103
+ "container-hover",
2104
+ "container-active",
2105
+ "popover",
2106
+ "popover-hover",
2107
+ "popover-active",
2108
+ "nav-background"
2109
+ ].flatMap((surface) => [
2110
+ text("foreground", surface, "ui"),
2111
+ text("muted-foreground", surface, "ui"),
2112
+ text("muted-foreground-subtle", surface, "incidental")
2113
+ ]),
2114
+ text("muted-foreground", "background", "ui"),
2115
+ text("muted-foreground-subtle", "background", "incidental"),
2116
+ text("container-foreground", "container-hover", "ui"),
2117
+ text("container-foreground", "container-active", "ui"),
2118
+ text("popover-foreground", "popover-active", "ui"),
2119
+ ...["background", "container", "popover"].flatMap((backdrop) => [
2120
+ { ...text("popover-foreground", "popover-alpha", "body"), backdrop },
2121
+ { ...text("foreground", "background-hover-alpha", "ui"), backdrop }
2122
+ ]),
2123
+ ...["#000000", "#ffffff"].map((backdrop) => ({
2124
+ ...text("popover-foreground", "popover-alpha", "body"),
2125
+ backdrop
2126
+ }))
2127
+ ];
2128
+
2129
+ // src/lib/perceptual-ramp.ts
2130
+ var import_culori5 = require("culori");
2131
+ var EMIT_GAMUT2 = "p3";
2132
+ var CUSP_FRACTION2 = 0.95;
2133
+ function isColor(value) {
2134
+ return (0, import_culori5.parse)(value) !== void 0;
2135
+ }
2136
+ function seedOklch(input) {
2137
+ const parsed = (0, import_culori5.parse)(input);
2138
+ if (!parsed)
2139
+ throw new Error(`perceptual-ramp: cannot parse color '${input}'`);
2140
+ const converted = (0, import_culori5.oklch)(parsed);
2141
+ if (!converted)
2142
+ throw new Error(`perceptual-ramp: cannot convert '${input}' to OKLCH`);
2143
+ return converted;
2144
+ }
2145
+ function pinnedOklch(seedHex, shade, options = {}) {
2146
+ const { capAtSeedChroma = true } = options;
2147
+ const seed = seedOklch(seedHex);
2148
+ const l = PERCEPTUAL_L_GRID[shade];
2149
+ const hue = seed.h ?? 0;
2150
+ const seedC = seed.c ?? 0;
2151
+ const cuspC = (0, import_culori5.clampChroma)({ mode: "oklch", l, c: 0.5, h: hue }, "oklch", EMIT_GAMUT2).c ?? 0;
2152
+ let chroma = cuspC * CUSP_FRACTION2;
2153
+ if (capAtSeedChroma) chroma = Math.min(chroma, seedC);
2154
+ const target = { mode: "oklch", l, c: chroma, h: hue };
2155
+ return formatOklch((0, import_culori5.clampChroma)(target, "oklch", EMIT_GAMUT2));
2156
+ }
2157
+ function rampFromSeed(seedHex, options = {}) {
2158
+ const ramp = {};
2159
+ for (const shade of SHADES)
2160
+ ramp[shade] = pinnedOklch(seedHex, shade, options);
2161
+ return ramp;
2162
+ }
2163
+
2164
+ // src/lib/contrast.ts
2165
+ var CONTRAST_MIN = 0;
2166
+ var CONTRAST_MAX = 100;
2167
+ function normalizeContrast(value, fallback = 50) {
2168
+ return typeof value === "number" && Number.isFinite(value) ? Math.round(Math.max(CONTRAST_MIN, Math.min(CONTRAST_MAX, value))) : fallback;
2169
+ }
2170
+ function contrastTarget(tier, contrast) {
2171
+ return TIER_THRESHOLDS[tier] + 15 * contrast / 100;
2172
+ }
2173
+ var BISECT_STEPS = 12;
2174
+ function bisect(fail, pass, passes) {
2175
+ for (let i = 0; i < BISECT_STEPS; i++) {
2176
+ const middle = (fail + pass) / 2;
2177
+ if (passes(middle)) pass = middle;
2178
+ else fail = middle;
2179
+ }
2180
+ return pass;
2181
+ }
2182
+ var token = (map, name) => {
2183
+ const value = map[`--nx-color-${name}`];
2184
+ if (value === void 0) throw new Error(`contrast: missing ${name}`);
2185
+ return value;
2186
+ };
2187
+ function pairBackground(map, pair) {
2188
+ const backdrop = pair.backdrop ? resolveToSrgbInts(
2189
+ pair.backdrop.startsWith("#") ? pair.backdrop : token(map, pair.backdrop)
2190
+ ) : void 0;
2191
+ return resolveToSrgbInts(token(map, pair.bg), backdrop);
2192
+ }
2193
+ function contrastForPair(map, pair) {
2194
+ const background = pairBackground(map, pair);
2195
+ return Math.abs(
2196
+ (0, import_apca_w33.APCAcontrast)(
2197
+ (0, import_apca_w33.sRGBtoY)(resolveToSrgbInts(token(map, pair.fg), background)),
2198
+ (0, import_apca_w33.sRGBtoY)(background)
2199
+ )
2200
+ );
2201
+ }
2202
+ var luminance = (color) => (0, import_apca_w33.sRGBtoY)(resolveToSrgbInts(color));
2203
+ var score = (foregroundY, backgroundY) => Math.abs((0, import_apca_w33.APCAcontrast)(foregroundY, backgroundY));
2204
+ function constraintsFor(map, pairs, contrast, endpointY) {
2205
+ const constraints = [];
2206
+ for (const pair of pairs) {
2207
+ const backgroundY = (0, import_apca_w33.sRGBtoY)(pairBackground(map, pair));
2208
+ const reachable = score(endpointY, backgroundY);
2209
+ if (reachable < TIER_THRESHOLDS[pair.tier]) return void 0;
2210
+ const target = contrastTarget(pair.tier, contrast) + (pair.offset ?? 0);
2211
+ constraints.push({ backgroundY, target: Math.min(target, reachable) });
2212
+ }
2213
+ return constraints;
2214
+ }
2215
+ var keepsTextSeed = (mode, kind) => mode === "light" && kind === "text";
2216
+ function solveForeground(seed, constraints, endpoint, keepSeed) {
2217
+ const passes = (color) => {
2218
+ const y = luminance(color);
2219
+ return constraints.every((c) => score(y, c.backgroundY) >= c.target);
2220
+ };
2221
+ if (keepSeed && passes(seed)) return seed;
2222
+ const parsed = seedOklch(seed);
2223
+ const at = (l) => formatOklch((0, import_culori6.clampChroma)({ ...parsed, l, alpha: 1 }, "oklch", "p3"));
2224
+ return at(
2225
+ bisect(keepSeed ? parsed.l : 1 - endpoint, endpoint, (l) => passes(at(l)))
2226
+ );
2227
+ }
2228
+ var SOLVE_GROUPS = /* @__PURE__ */ new Map();
2229
+ for (const pair of APCA_PAIRS) {
2230
+ const key = pair.solveAs ?? pair.fg;
2231
+ const group = SOLVE_GROUPS.get(key);
2232
+ if (!group) {
2233
+ SOLVE_GROUPS.set(key, { kind: pair.kind, pairs: [pair] });
2234
+ continue;
2235
+ }
2236
+ if (group.kind !== pair.kind)
2237
+ throw new Error(
2238
+ `contrast: ${pair.fg} is '${pair.kind}' but joins '${group.kind}' group ${key}`
2239
+ );
2240
+ group.pairs.push(pair);
2241
+ }
2242
+ function constrainFamilyFills(map, name, pairs) {
2243
+ const basePair = pairs[0];
2244
+ if (!basePair) throw new Error(`contrast: missing pairs for ${name}`);
2245
+ const baseY = luminance(token(map, basePair.bg));
2246
+ const endpoint = score(1, baseY) >= score(0, baseY) ? 1 : 0;
2247
+ map[`--nx-color-${name}`] = `oklch(${endpoint} 0 0)`;
2248
+ for (const pair of pairs) {
2249
+ const floor = TIER_THRESHOLDS[pair.tier];
2250
+ const value = token(map, pair.bg);
2251
+ if (score(endpoint, luminance(value)) >= floor) continue;
2252
+ const fill = seedOklch(value);
2253
+ const at = (l) => formatOklch((0, import_culori6.clampChroma)({ ...fill, l }, "oklch", "p3"));
2254
+ const readable = (l) => score(endpoint, luminance(at(l))) >= floor;
2255
+ map[`--nx-color-${pair.bg}`] = at(bisect(fill.l, 1 - endpoint, readable));
2256
+ }
2257
+ }
2258
+ function constrainColors(map, mode, contrast) {
2259
+ for (const [name, { kind, pairs }] of SOLVE_GROUPS)
2260
+ if (kind === "label") constrainFamilyFills(map, name, pairs);
2261
+ const endpoint = mode === "dark" ? 1 : 0;
2262
+ const endpointY = luminance(`oklch(${endpoint} 0 0)`);
2263
+ for (const [name, { kind, pairs }] of SOLVE_GROUPS) {
2264
+ if (kind === "label") continue;
2265
+ const constraints = constraintsFor(map, pairs, contrast, endpointY);
2266
+ if (constraints === void 0)
2267
+ throw new Error(`contrast: no readable ${name} in ${mode}`);
2268
+ const solved = solveForeground(
2269
+ token(map, name),
2270
+ constraints,
2271
+ endpoint,
2272
+ keepsTextSeed(mode, kind)
2273
+ );
2274
+ for (const fg of new Set(pairs.map((pair) => pair.fg).concat(name)))
2275
+ map[`--nx-color-${fg}`] = solved;
2276
+ }
2277
+ return map;
2278
+ }
2279
+
2280
+ // src/lib/runtime-layer.ts
2281
+ var RUNTIME_LAYER_ORDER = "@layer properties, theme, base, components, utilities, nx-runtime;";
2282
+ function inRuntimeLayer(css) {
2283
+ return `${RUNTIME_LAYER_ORDER}
2284
+ @layer nx-runtime {
2285
+ ${css}
2286
+ }
2287
+ `;
2288
+ }
2289
+
1964
2290
  // tokens/primitives/typography/typography-default.json
1965
2291
  var typography_default_default = {
1966
2292
  family: {
@@ -2099,6 +2425,13 @@ var typography_default_default = {
2099
2425
  unit: "px"
2100
2426
  },
2101
2427
  $type: "dimension"
2428
+ },
2429
+ code: {
2430
+ $value: {
2431
+ value: 12,
2432
+ unit: "px"
2433
+ },
2434
+ $type: "dimension"
2102
2435
  }
2103
2436
  },
2104
2437
  weight: {
@@ -2131,471 +2464,182 @@ var typography_default_default = {
2131
2464
  $type: "fontWeight"
2132
2465
  },
2133
2466
  extrabold: {
2134
- $value: 800,
2135
- $type: "fontWeight"
2136
- },
2137
- black: {
2138
- $value: 900,
2139
- $type: "fontWeight"
2140
- }
2141
- },
2142
- "line-height": {
2143
- xxs: {
2144
- $value: {
2145
- value: 12,
2146
- unit: "px"
2147
- },
2148
- $type: "dimension"
2149
- },
2150
- xs: {
2151
- $value: {
2152
- value: 16,
2153
- unit: "px"
2154
- },
2155
- $type: "dimension"
2156
- },
2157
- sm: {
2158
- $value: {
2159
- value: 20,
2160
- unit: "px"
2161
- },
2162
- $type: "dimension"
2163
- },
2164
- base: {
2165
- $value: {
2166
- value: 24,
2167
- unit: "px"
2168
- },
2169
- $type: "dimension"
2170
- },
2171
- lg: {
2172
- $value: {
2173
- value: 28,
2174
- unit: "px"
2175
- },
2176
- $type: "dimension"
2177
- },
2178
- xl: {
2179
- $value: {
2180
- value: 28,
2181
- unit: "px"
2182
- },
2183
- $type: "dimension"
2184
- },
2185
- "2xl": {
2186
- $value: {
2187
- value: 32,
2188
- unit: "px"
2189
- },
2190
- $type: "dimension"
2191
- },
2192
- "3xl": {
2193
- $value: {
2194
- value: 36,
2195
- unit: "px"
2196
- },
2197
- $type: "dimension"
2198
- },
2199
- "4xl": {
2200
- $value: {
2201
- value: 48,
2202
- unit: "px"
2203
- },
2204
- $type: "dimension"
2205
- },
2206
- "5xl": {
2207
- $value: {
2208
- value: 48,
2209
- unit: "px"
2210
- },
2211
- $type: "dimension"
2467
+ $value: 800,
2468
+ $type: "fontWeight"
2212
2469
  },
2213
- "6xl": {
2470
+ black: {
2471
+ $value: 900,
2472
+ $type: "fontWeight"
2473
+ }
2474
+ },
2475
+ "line-height": {
2476
+ xxs: {
2214
2477
  $value: {
2215
- value: 60,
2478
+ value: 12,
2216
2479
  unit: "px"
2217
2480
  },
2218
2481
  $type: "dimension"
2219
2482
  },
2220
- "7xl": {
2483
+ xs: {
2221
2484
  $value: {
2222
- value: 72,
2485
+ value: 16,
2223
2486
  unit: "px"
2224
2487
  },
2225
2488
  $type: "dimension"
2226
2489
  },
2227
- "8xl": {
2490
+ sm: {
2228
2491
  $value: {
2229
- value: 96,
2492
+ value: 20,
2230
2493
  unit: "px"
2231
2494
  },
2232
2495
  $type: "dimension"
2233
2496
  },
2234
- "9xl": {
2497
+ base: {
2235
2498
  $value: {
2236
- value: 128,
2499
+ value: 24,
2237
2500
  unit: "px"
2238
2501
  },
2239
2502
  $type: "dimension"
2240
- }
2241
- },
2242
- letterspacing: {
2243
- tighter: {
2503
+ },
2504
+ lg: {
2244
2505
  $value: {
2245
- value: -0.800000011920929,
2506
+ value: 28,
2246
2507
  unit: "px"
2247
2508
  },
2248
2509
  $type: "dimension"
2249
2510
  },
2250
- tight: {
2511
+ xl: {
2251
2512
  $value: {
2252
- value: -0.4000000059604645,
2513
+ value: 28,
2253
2514
  unit: "px"
2254
2515
  },
2255
2516
  $type: "dimension"
2256
2517
  },
2257
- normal: {
2518
+ "2xl": {
2258
2519
  $value: {
2259
- value: 0,
2520
+ value: 32,
2260
2521
  unit: "px"
2261
2522
  },
2262
2523
  $type: "dimension"
2263
2524
  },
2264
- wide: {
2525
+ "3xl": {
2265
2526
  $value: {
2266
- value: 0.4000000059604645,
2527
+ value: 36,
2267
2528
  unit: "px"
2268
2529
  },
2269
2530
  $type: "dimension"
2270
2531
  },
2271
- wider: {
2532
+ "4xl": {
2272
2533
  $value: {
2273
- value: 0.800000011920929,
2534
+ value: 48,
2274
2535
  unit: "px"
2275
2536
  },
2276
2537
  $type: "dimension"
2277
2538
  },
2278
- widest: {
2539
+ "5xl": {
2279
2540
  $value: {
2280
- value: 1.600000023841858,
2541
+ value: 48,
2281
2542
  unit: "px"
2282
2543
  },
2283
2544
  $type: "dimension"
2284
- }
2285
- }
2286
- };
2287
-
2288
- // src/lib/contrast.ts
2289
- var import_apca_w33 = require("apca-w3");
2290
- var import_culori6 = require("culori");
2291
-
2292
- // src/lib/semantic-palette-references.ts
2293
- var STATUS_PALETTE_FAMILIES = {
2294
- success: "green",
2295
- warning: "orange",
2296
- error: "red",
2297
- information: "blue"
2298
- };
2299
- var CHART_PALETTE_REFERENCES = [
2300
- { palette: "teal", light: "700", dark: "200" },
2301
- { palette: "lime", light: "700", dark: "200" },
2302
- { palette: "orange", light: "600", dark: "200" },
2303
- { palette: "rose", light: "600", dark: "200" },
2304
- { palette: "indigo", light: "600", dark: "200" }
2305
- ];
2306
- var SYNTAX_PALETTE_REFERENCES = {
2307
- keyword: CHART_PALETTE_REFERENCES[3],
2308
- string: CHART_PALETTE_REFERENCES[1],
2309
- function: CHART_PALETTE_REFERENCES[4],
2310
- type: CHART_PALETTE_REFERENCES[0],
2311
- constant: CHART_PALETTE_REFERENCES[2]
2312
- };
2313
-
2314
- // src/lib/apca-pairs.ts
2315
- var COLOR_FAMILIES = [
2316
- "primary",
2317
- "secondary",
2318
- "success",
2319
- "warning",
2320
- "error",
2321
- "information"
2322
- ];
2323
- var CATEGORICAL_INDICES = CHART_PALETTE_REFERENCES.map(
2324
- (_, index) => index + 1
2325
- );
2326
- var CHART_STAGGER_LC = 4.5;
2327
- var DIFF_SURFACES = ["success-subtle", "error-subtle"];
2328
- var CODE_SURFACES = ["container", "container-hover", ...DIFF_SURFACES];
2329
- var FOCUS_SURFACES = [
2330
- "background",
2331
- "container",
2332
- "popover",
2333
- "nav-background",
2334
- "nav-item-hover",
2335
- "nav-item-active",
2336
- "nav-border"
2337
- ];
2338
- var text = (fg, bg, tier) => ({
2339
- fg,
2340
- bg,
2341
- tier,
2342
- kind: "text"
2343
- });
2344
- var label = (fg, bg, tier) => ({
2345
- fg,
2346
- bg,
2347
- tier,
2348
- kind: "label"
2349
- });
2350
- var ink = (fg, bg, tier) => ({
2351
- fg,
2352
- bg,
2353
- tier,
2354
- kind: "ink"
2355
- });
2356
- var APCA_PAIRS = [
2357
- text("foreground", "background", "body"),
2358
- text("foreground", "background-hover", "ui"),
2359
- text("foreground", "muted", "ui"),
2360
- text("muted-foreground", "muted", "ui"),
2361
- text("muted-foreground-subtle", "muted", "incidental"),
2362
- text("muted-foreground", "container", "ui"),
2363
- text("muted-foreground-subtle", "container", "incidental"),
2364
- text("foreground", "muted-extralight", "ui"),
2365
- text("muted-foreground", "muted-extralight", "ui"),
2366
- text("disabled-foreground", "disabled", "incidental"),
2367
- text("container-foreground", "container", "body"),
2368
- text("foreground", "container", "body"),
2369
- text("popover-foreground", "popover", "body"),
2370
- text("popover-foreground", "popover-hover", "ui"),
2371
- text("foreground", "control-background", "ui"),
2372
- text("foreground", "control-background-hover", "ui"),
2373
- text("nav-foreground", "nav-background", "ui"),
2374
- text("nav-muted-foreground", "nav-background", "incidental"),
2375
- text("nav-foreground", "nav-item-hover", "ui"),
2376
- text("nav-foreground", "nav-item-active", "ui"),
2377
- ink("error-subtle-foreground", "background", "ui"),
2378
- ink("error-subtle-foreground", "container", "ui"),
2379
- ...COLOR_FAMILIES.flatMap((family) => [
2380
- label(`${family}-foreground`, `${family}-background`, "ui"),
2381
- label(`${family}-foreground`, `${family}-background-hover`, "ui"),
2382
- label(`${family}-foreground`, `${family}-background-active`, "ui"),
2383
- ink(`${family}-subtle-foreground`, `${family}-subtle`, "ui"),
2384
- ink(`${family}-subtle-foreground`, `${family}-subtle-hover`, "ui"),
2385
- ink(`${family}-subtle-foreground`, `${family}-subtle-active`, "ui")
2386
- ]),
2387
- ...CATEGORICAL_INDICES.flatMap(
2388
- (index) => ["container", "container-hover", "background"].map((bg) => ({
2389
- ...ink(`chart-categorical-${index}`, bg, "ui"),
2390
- offset: (index - 1) * CHART_STAGGER_LC
2391
- }))
2392
- ),
2393
- ...CODE_SURFACES.flatMap(
2394
- (bg) => Object.keys(SYNTAX_PALETTE_REFERENCES).map(
2395
- (role) => ink(`syntax-${role}`, bg, "ui")
2396
- )
2397
- ),
2398
- // codeBlockTheme also colours code with the text tokens, which already read
2399
- // on container and container-hover.
2400
- ...DIFF_SURFACES.flatMap((bg) => [
2401
- text("foreground", bg, "ui"),
2402
- text("muted-foreground", bg, "ui"),
2403
- text("muted-foreground-subtle", bg, "incidental")
2404
- ]),
2405
- // Focus is the primary accent (components.md § Focus States), so it shares
2406
- // one solved color with primary-subtle-foreground.
2407
- ...FOCUS_SURFACES.flatMap((surface) => [
2408
- {
2409
- ...ink("focus-default", surface, "incidental"),
2410
- solveAs: "primary-subtle-foreground"
2411
2545
  },
2412
- ink("focus-error", surface, "incidental")
2413
- ]),
2414
- ...[
2415
- "background-active",
2416
- "container-hover",
2417
- "container-active",
2418
- "popover",
2419
- "popover-hover",
2420
- "popover-active",
2421
- "nav-background"
2422
- ].flatMap((surface) => [
2423
- text("foreground", surface, "ui"),
2424
- text("muted-foreground", surface, "ui"),
2425
- text("muted-foreground-subtle", surface, "incidental")
2426
- ]),
2427
- text("muted-foreground", "background", "ui"),
2428
- text("muted-foreground-subtle", "background", "incidental"),
2429
- text("container-foreground", "container-hover", "ui"),
2430
- text("container-foreground", "container-active", "ui"),
2431
- text("popover-foreground", "popover-active", "ui"),
2432
- ...["background", "container", "popover"].flatMap((backdrop) => [
2433
- { ...text("popover-foreground", "popover-alpha", "body"), backdrop },
2434
- { ...text("foreground", "background-hover-alpha", "ui"), backdrop }
2435
- ]),
2436
- ...["#000000", "#ffffff"].map((backdrop) => ({
2437
- ...text("popover-foreground", "popover-alpha", "body"),
2438
- backdrop
2439
- }))
2440
- ];
2441
-
2442
- // src/lib/perceptual-ramp.ts
2443
- var import_culori5 = require("culori");
2444
- var EMIT_GAMUT2 = "p3";
2445
- var CUSP_FRACTION2 = 0.95;
2446
- function isColor(value) {
2447
- return (0, import_culori5.parse)(value) !== void 0;
2448
- }
2449
- function seedOklch(input) {
2450
- const parsed = (0, import_culori5.parse)(input);
2451
- if (!parsed)
2452
- throw new Error(`perceptual-ramp: cannot parse color '${input}'`);
2453
- const converted = (0, import_culori5.oklch)(parsed);
2454
- if (!converted)
2455
- throw new Error(`perceptual-ramp: cannot convert '${input}' to OKLCH`);
2456
- return converted;
2457
- }
2458
- function pinnedOklch(seedHex, shade, options = {}) {
2459
- const { capAtSeedChroma = true } = options;
2460
- const seed = seedOklch(seedHex);
2461
- const l = PERCEPTUAL_L_GRID[shade];
2462
- const hue = seed.h ?? 0;
2463
- const seedC = seed.c ?? 0;
2464
- const cuspC = (0, import_culori5.clampChroma)({ mode: "oklch", l, c: 0.5, h: hue }, "oklch", EMIT_GAMUT2).c ?? 0;
2465
- let chroma = cuspC * CUSP_FRACTION2;
2466
- if (capAtSeedChroma) chroma = Math.min(chroma, seedC);
2467
- const target = { mode: "oklch", l, c: chroma, h: hue };
2468
- return formatOklch((0, import_culori5.clampChroma)(target, "oklch", EMIT_GAMUT2));
2469
- }
2470
- function rampFromSeed(seedHex, options = {}) {
2471
- const ramp = {};
2472
- for (const shade of SHADES)
2473
- ramp[shade] = pinnedOklch(seedHex, shade, options);
2474
- return ramp;
2475
- }
2476
-
2477
- // src/lib/contrast.ts
2478
- function normalizeContrast(value, fallback = 50) {
2479
- return typeof value === "number" && Number.isFinite(value) ? Math.round(Math.max(0, Math.min(100, value))) : fallback;
2480
- }
2481
- function contrastTarget(tier, contrast) {
2482
- return TIER_THRESHOLDS[tier] + 15 * contrast / 100;
2483
- }
2484
- var BISECT_STEPS = 12;
2485
- function bisect(fail, pass, passes) {
2486
- for (let i = 0; i < BISECT_STEPS; i++) {
2487
- const middle = (fail + pass) / 2;
2488
- if (passes(middle)) pass = middle;
2489
- else fail = middle;
2546
+ "6xl": {
2547
+ $value: {
2548
+ value: 60,
2549
+ unit: "px"
2550
+ },
2551
+ $type: "dimension"
2552
+ },
2553
+ "7xl": {
2554
+ $value: {
2555
+ value: 72,
2556
+ unit: "px"
2557
+ },
2558
+ $type: "dimension"
2559
+ },
2560
+ "8xl": {
2561
+ $value: {
2562
+ value: 96,
2563
+ unit: "px"
2564
+ },
2565
+ $type: "dimension"
2566
+ },
2567
+ "9xl": {
2568
+ $value: {
2569
+ value: 128,
2570
+ unit: "px"
2571
+ },
2572
+ $type: "dimension"
2573
+ }
2574
+ },
2575
+ letterspacing: {
2576
+ tighter: {
2577
+ $value: {
2578
+ value: -0.800000011920929,
2579
+ unit: "px"
2580
+ },
2581
+ $type: "dimension"
2582
+ },
2583
+ tight: {
2584
+ $value: {
2585
+ value: -0.4000000059604645,
2586
+ unit: "px"
2587
+ },
2588
+ $type: "dimension"
2589
+ },
2590
+ normal: {
2591
+ $value: {
2592
+ value: 0,
2593
+ unit: "px"
2594
+ },
2595
+ $type: "dimension"
2596
+ },
2597
+ wide: {
2598
+ $value: {
2599
+ value: 0.4000000059604645,
2600
+ unit: "px"
2601
+ },
2602
+ $type: "dimension"
2603
+ },
2604
+ wider: {
2605
+ $value: {
2606
+ value: 0.800000011920929,
2607
+ unit: "px"
2608
+ },
2609
+ $type: "dimension"
2610
+ },
2611
+ widest: {
2612
+ $value: {
2613
+ value: 1.600000023841858,
2614
+ unit: "px"
2615
+ },
2616
+ $type: "dimension"
2617
+ }
2490
2618
  }
2491
- return pass;
2492
- }
2493
- var token = (map, name) => {
2494
- const value = map[`--nx-color-${name}`];
2495
- if (value === void 0) throw new Error(`contrast: missing ${name}`);
2496
- return value;
2497
2619
  };
2498
- function pairBackground(map, pair) {
2499
- const backdrop = pair.backdrop ? resolveToSrgbInts(
2500
- pair.backdrop.startsWith("#") ? pair.backdrop : token(map, pair.backdrop)
2501
- ) : void 0;
2502
- return resolveToSrgbInts(token(map, pair.bg), backdrop);
2503
- }
2504
- function contrastForPair(map, pair) {
2505
- const background = pairBackground(map, pair);
2506
- return Math.abs(
2507
- (0, import_apca_w33.APCAcontrast)(
2508
- (0, import_apca_w33.sRGBtoY)(resolveToSrgbInts(token(map, pair.fg), background)),
2509
- (0, import_apca_w33.sRGBtoY)(background)
2510
- )
2511
- );
2512
- }
2513
- var luminance = (color) => (0, import_apca_w33.sRGBtoY)(resolveToSrgbInts(color));
2514
- var score = (foregroundY, backgroundY) => Math.abs((0, import_apca_w33.APCAcontrast)(foregroundY, backgroundY));
2515
- function constraintsFor(map, pairs, contrast, endpointY) {
2516
- const constraints = [];
2517
- for (const pair of pairs) {
2518
- const backgroundY = (0, import_apca_w33.sRGBtoY)(pairBackground(map, pair));
2519
- const reachable = score(endpointY, backgroundY);
2520
- if (reachable < TIER_THRESHOLDS[pair.tier]) return void 0;
2521
- const target = contrastTarget(pair.tier, contrast) + (pair.offset ?? 0);
2522
- constraints.push({ backgroundY, target: Math.min(target, reachable) });
2523
- }
2524
- return constraints;
2525
- }
2526
- var keepsTextSeed = (mode, kind) => mode === "light" && kind === "text";
2527
- function solveForeground(seed, constraints, endpoint, keepSeed) {
2528
- const passes = (color) => {
2529
- const y = luminance(color);
2530
- return constraints.every((c) => score(y, c.backgroundY) >= c.target);
2531
- };
2532
- if (keepSeed && passes(seed)) return seed;
2533
- const parsed = seedOklch(seed);
2534
- const at = (l) => formatOklch((0, import_culori6.clampChroma)({ ...parsed, l, alpha: 1 }, "oklch", "p3"));
2535
- return at(
2536
- bisect(keepSeed ? parsed.l : 1 - endpoint, endpoint, (l) => passes(at(l)))
2537
- );
2538
- }
2539
- var SOLVE_GROUPS = /* @__PURE__ */ new Map();
2540
- for (const pair of APCA_PAIRS) {
2541
- const key = pair.solveAs ?? pair.fg;
2542
- const group = SOLVE_GROUPS.get(key);
2543
- if (!group) {
2544
- SOLVE_GROUPS.set(key, { kind: pair.kind, pairs: [pair] });
2545
- continue;
2546
- }
2547
- if (group.kind !== pair.kind)
2548
- throw new Error(
2549
- `contrast: ${pair.fg} is '${pair.kind}' but joins '${group.kind}' group ${key}`
2550
- );
2551
- group.pairs.push(pair);
2552
- }
2553
- function constrainFamilyFills(map, name, pairs) {
2554
- const basePair = pairs[0];
2555
- if (!basePair) throw new Error(`contrast: missing pairs for ${name}`);
2556
- const baseY = luminance(token(map, basePair.bg));
2557
- const endpoint = score(1, baseY) >= score(0, baseY) ? 1 : 0;
2558
- map[`--nx-color-${name}`] = `oklch(${endpoint} 0 0)`;
2559
- for (const pair of pairs) {
2560
- const floor = TIER_THRESHOLDS[pair.tier];
2561
- const value = token(map, pair.bg);
2562
- if (score(endpoint, luminance(value)) >= floor) continue;
2563
- const fill = seedOklch(value);
2564
- const at = (l) => formatOklch((0, import_culori6.clampChroma)({ ...fill, l }, "oklch", "p3"));
2565
- const readable = (l) => score(endpoint, luminance(at(l))) >= floor;
2566
- map[`--nx-color-${pair.bg}`] = at(bisect(fill.l, 1 - endpoint, readable));
2567
- }
2568
- }
2569
- function constrainColors(map, mode, contrast) {
2570
- for (const [name, { kind, pairs }] of SOLVE_GROUPS)
2571
- if (kind === "label") constrainFamilyFills(map, name, pairs);
2572
- const endpoint = mode === "dark" ? 1 : 0;
2573
- const endpointY = luminance(`oklch(${endpoint} 0 0)`);
2574
- for (const [name, { kind, pairs }] of SOLVE_GROUPS) {
2575
- if (kind === "label") continue;
2576
- const constraints = constraintsFor(map, pairs, contrast, endpointY);
2577
- if (constraints === void 0)
2578
- throw new Error(`contrast: no readable ${name} in ${mode}`);
2579
- const solved = solveForeground(
2580
- token(map, name),
2581
- constraints,
2582
- endpoint,
2583
- keepsTextSeed(mode, kind)
2584
- );
2585
- for (const fg of new Set(pairs.map((pair) => pair.fg).concat(name)))
2586
- map[`--nx-color-${fg}`] = solved;
2587
- }
2588
- return map;
2589
- }
2590
2620
 
2591
- // src/lib/runtime-layer.ts
2592
- var RUNTIME_LAYER_ORDER = "@layer properties, theme, base, components, utilities, nx-runtime;";
2593
- function inRuntimeLayer(css) {
2594
- return `${RUNTIME_LAYER_ORDER}
2595
- @layer nx-runtime {
2596
- ${css}
2621
+ // src/lib/typography-scale.ts
2622
+ var TYPOGRAPHY_BASE_PX = 14;
2623
+ function formatPx(value) {
2624
+ return `${Number(value.toFixed(4))}px`;
2597
2625
  }
2598
- `;
2626
+ var CODE_SIZE = "code";
2627
+ function typographyScale(uiPx, codePx) {
2628
+ const scale = uiPx / TYPOGRAPHY_BASE_PX;
2629
+ return new Map([
2630
+ ...Object.entries(typography_default_default.size).map(
2631
+ ([name, token2]) => [
2632
+ `--nx-typography-size-${name}`,
2633
+ formatPx(name === CODE_SIZE ? codePx : token2.$value.value * scale)
2634
+ ]
2635
+ ),
2636
+ ...Object.entries(typography_default_default["line-height"]).map(
2637
+ ([name, token2]) => [
2638
+ `--nx-typography-line-height-${name}`,
2639
+ formatPx(token2.$value.value * scale)
2640
+ ]
2641
+ )
2642
+ ]);
2599
2643
  }
2600
2644
 
2601
2645
  // src/lib/appearance-model.ts
@@ -2668,16 +2712,17 @@ var DEFAULT_NXDS_APPEARANCE = {
2668
2712
  stroke: "normal",
2669
2713
  prefs: {
2670
2714
  uiFont: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
2715
+ headingFont: null,
2671
2716
  codeFont: 'ui-monospace, "SF Mono", Menlo, monospace',
2672
- uiFontSize: 14,
2717
+ uiFontSize: TYPOGRAPHY_BASE_PX,
2673
2718
  codeFontSize: 12,
2674
2719
  reduceMotion: false,
2675
2720
  pointerCursors: false,
2676
2721
  fontSmoothing: false
2677
2722
  }
2678
2723
  };
2679
- var FONT_PX_MIN = 8;
2680
- var FONT_PX_MAX = 32;
2724
+ var FONT_SIZE_PX_MIN = 8;
2725
+ var FONT_SIZE_PX_MAX = 32;
2681
2726
  var APPEARANCE_MODES = /* @__PURE__ */ new Set([
2682
2727
  "light",
2683
2728
  "dark",
@@ -2703,19 +2748,9 @@ var FONT_FAMILY_UNSAFE = /[;{}<]/;
2703
2748
  var fontFamilyOr = (value, fallback) => typeof value === "string" && value.length > 0 && !FONT_FAMILY_UNSAFE.test(value) ? value : fallback;
2704
2749
  var boolOr = (value, fallback) => typeof value === "boolean" ? value : fallback;
2705
2750
  var enumOr = (value, values, fallback) => typeof value === "string" && values.has(value) ? value : fallback;
2706
- var clampFontSize = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(FONT_PX_MAX, Math.max(FONT_PX_MIN, value)) : fallback;
2707
- function formatPx(value) {
2708
- return `${Number(value.toFixed(4))}px`;
2709
- }
2710
- function typographyScaleVariables(uiPx) {
2711
- const scale = uiPx / DEFAULT_NXDS_APPEARANCE.prefs.uiFontSize;
2712
- const sizeVars = Object.entries(typography_default_default.size).map(
2713
- ([name, token2]) => ` --nx-typography-size-${name}: ${formatPx(token2.$value.value * scale)};`
2714
- );
2715
- const lineHeightVars = Object.entries(typography_default_default["line-height"]).map(
2716
- ([name, token2]) => ` --nx-typography-line-height-${name}: ${formatPx(token2.$value.value * scale)};`
2717
- );
2718
- return [...sizeVars, ...lineHeightVars].join("\n");
2751
+ 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;
2752
+ function typographyScaleVariables(uiPx, codePx) {
2753
+ return [...typographyScale(uiPx, codePx)].map(([name, value]) => ` ${name}: ${value};`).join("\n");
2719
2754
  }
2720
2755
  var PUBLIC_MODE_RENAME = {
2721
2756
  density: {
@@ -2754,6 +2789,7 @@ function sanitizeNxdsAppearancePrefs(raw) {
2754
2789
  const d = DEFAULT_NXDS_APPEARANCE.prefs;
2755
2790
  return {
2756
2791
  uiFont: fontFamilyOr(o.uiFont, d.uiFont),
2792
+ headingFont: fontFamilyOr(o.headingFont, d.headingFont),
2757
2793
  codeFont: fontFamilyOr(o.codeFont, d.codeFont),
2758
2794
  uiFontSize: clampFontSize(o.uiFontSize, d.uiFontSize),
2759
2795
  codeFontSize: clampFontSize(o.codeFontSize, d.codeFontSize),
@@ -2800,10 +2836,10 @@ function appearancePrefsToCss(prefs) {
2800
2836
  const blocks = [
2801
2837
  inRuntimeLayer(`:root {
2802
2838
  --nx-typography-family-font-sans: ${prefs.uiFont};
2839
+ --nx-typography-family-font-heading: ${prefs.headingFont ?? "var(--nx-typography-family-font-sans)"};
2803
2840
  --nx-typography-family-font-mono: ${prefs.codeFont};
2804
- ${typographyScaleVariables(uiPx)}
2805
- }`),
2806
- `.nx\\:font-mono, .nx\\:typography-code-block, .nx\\:typography-code-inline { font-size: ${codePx}px; }`
2841
+ ${typographyScaleVariables(uiPx, codePx)}
2842
+ }`)
2807
2843
  ];
2808
2844
  if (prefs.fontSmoothing) {
2809
2845
  blocks.push(
@@ -4900,6 +4936,127 @@ function findBrandColorPreset(color) {
4900
4936
  return BRAND_COLOR_PRESETS.find((preset) => preset.color === hex);
4901
4937
  }
4902
4938
 
4939
+ // src/lib/font-options.ts
4940
+ var SANS_FALLBACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
4941
+ var SERIF_FALLBACK = "Georgia, serif";
4942
+ var MONO_FALLBACK = 'ui-monospace, "SF Mono", Menlo, monospace';
4943
+ var FONT_OPTIONS = [
4944
+ {
4945
+ value: "geist",
4946
+ label: "Geist",
4947
+ category: "sans",
4948
+ family: `"Geist Variable", ${SANS_FALLBACK}`,
4949
+ package: "@fontsource-variable/geist"
4950
+ },
4951
+ {
4952
+ value: "inter",
4953
+ label: "Inter",
4954
+ category: "sans",
4955
+ family: `"Inter Variable", ${SANS_FALLBACK}`,
4956
+ package: "@fontsource-variable/inter"
4957
+ },
4958
+ {
4959
+ value: "figtree",
4960
+ label: "Figtree",
4961
+ category: "sans",
4962
+ family: `"Figtree Variable", ${SANS_FALLBACK}`,
4963
+ package: "@fontsource-variable/figtree"
4964
+ },
4965
+ {
4966
+ value: "dm-sans",
4967
+ label: "DM Sans",
4968
+ category: "sans",
4969
+ family: `"DM Sans Variable", ${SANS_FALLBACK}`,
4970
+ package: "@fontsource-variable/dm-sans"
4971
+ },
4972
+ {
4973
+ value: "manrope",
4974
+ label: "Manrope",
4975
+ category: "sans",
4976
+ family: `"Manrope Variable", ${SANS_FALLBACK}`,
4977
+ package: "@fontsource-variable/manrope"
4978
+ },
4979
+ {
4980
+ value: "ibm-plex-sans",
4981
+ label: "IBM Plex Sans",
4982
+ category: "sans",
4983
+ family: `"IBM Plex Sans Variable", ${SANS_FALLBACK}`,
4984
+ package: "@fontsource-variable/ibm-plex-sans"
4985
+ },
4986
+ {
4987
+ value: "lora",
4988
+ label: "Lora",
4989
+ category: "serif",
4990
+ family: `"Lora Variable", ${SERIF_FALLBACK}`,
4991
+ package: "@fontsource-variable/lora"
4992
+ },
4993
+ {
4994
+ value: "playfair-display",
4995
+ label: "Playfair Display",
4996
+ category: "serif",
4997
+ family: `"Playfair Display Variable", ${SERIF_FALLBACK}`,
4998
+ package: "@fontsource-variable/playfair-display"
4999
+ },
5000
+ {
5001
+ value: "geist-mono",
5002
+ label: "Geist Mono",
5003
+ category: "mono",
5004
+ family: `"Geist Mono Variable", ${MONO_FALLBACK}`,
5005
+ package: "@fontsource-variable/geist-mono"
5006
+ },
5007
+ {
5008
+ value: "jetbrains-mono",
5009
+ label: "JetBrains Mono",
5010
+ category: "mono",
5011
+ family: `"JetBrains Mono Variable", ${MONO_FALLBACK}`,
5012
+ package: "@fontsource-variable/jetbrains-mono"
5013
+ }
5014
+ ];
5015
+
5016
+ // src/lib/icon-libraries.ts
5017
+ var ICON_LIBRARY_OPTIONS = [
5018
+ { value: "phosphor", label: "Phosphor" },
5019
+ { value: "lucide", label: "Lucide" },
5020
+ { value: "tabler", label: "Tabler Icons" },
5021
+ { value: "hugeicons", label: "HugeIcons" },
5022
+ { value: "remix", label: "Remix Icon" }
5023
+ ];
5024
+ var DEFAULT_ICON_LIBRARY = "phosphor";
5025
+
5026
+ // src/lib/appearance-shuffle.ts
5027
+ function pick(options, random) {
5028
+ const option = options[Math.floor(random() * options.length)];
5029
+ if (option === void 0) throw new Error("No option to pick from.");
5030
+ return option;
5031
+ }
5032
+ function shuffleNxdsAppearance(state, { fonts = FONT_OPTIONS, random = Math.random } = {}) {
5033
+ const textFonts = fonts.filter((font) => font.category !== "mono").map((font) => font.family);
5034
+ if (textFonts.length === 0) {
5035
+ throw new Error(
5036
+ "shuffleNxdsAppearance: `fonts` needs at least one sans or serif font."
5037
+ );
5038
+ }
5039
+ return {
5040
+ ...state,
5041
+ brandColor: pick(BRAND_COLOR_PRESETS, random).color,
5042
+ surfaceTone: pick(BASE_TONE_OPTIONS, random).value,
5043
+ density: pick(DENSITY_OPTIONS, random).value,
5044
+ corners: pick(CORNER_OPTIONS, random).value,
5045
+ elevation: pick(ELEVATION_OPTIONS, random).value,
5046
+ stroke: pick(STROKE_OPTIONS, random).value,
5047
+ prefs: {
5048
+ ...state.prefs,
5049
+ uiFont: pick(textFonts, random),
5050
+ headingFont: pick([null, ...textFonts], random)
5051
+ }
5052
+ };
5053
+ }
5054
+ function shuffleNxdsIconLibrary({
5055
+ random = Math.random
5056
+ } = {}) {
5057
+ return pick(ICON_LIBRARY_OPTIONS, random).value;
5058
+ }
5059
+
4903
5060
  // src/lib/theme-contrast.ts
4904
5061
  var MODES = ["light", "dark"];
4905
5062
  var cssVar = (name) => `--nx-color-${name}`;
@@ -4925,6 +5082,211 @@ function measureThemeContrast(theme) {
4925
5082
  );
4926
5083
  }
4927
5084
 
5085
+ // src/lib/theme-fields.ts
5086
+ var DEFAULT_NXDS_THEME = {
5087
+ appearance: DEFAULT_NXDS_APPEARANCE,
5088
+ iconLibrary: DEFAULT_ICON_LIBRARY
5089
+ };
5090
+ function invalid(flag, value, accepted) {
5091
+ return new Error(`--${flag} "${value}" isn't ${accepted}.`);
5092
+ }
5093
+ function withAppearance(theme, patch) {
5094
+ return { ...theme, appearance: { ...theme.appearance, ...patch } };
5095
+ }
5096
+ function withPrefs(theme, patch) {
5097
+ return withAppearance(theme, {
5098
+ prefs: { ...theme.appearance.prefs, ...patch }
5099
+ });
5100
+ }
5101
+ function oneOf(flag, values, value) {
5102
+ const match = values.find((candidate) => candidate === value);
5103
+ if (match === void 0) {
5104
+ throw invalid(flag, value, `one of ${values.join(", ")}`);
5105
+ }
5106
+ return match;
5107
+ }
5108
+ function enumField(flag, key, description, values) {
5109
+ return {
5110
+ kind: "choice",
5111
+ flag,
5112
+ key,
5113
+ description,
5114
+ values,
5115
+ read: (theme) => theme.appearance[key],
5116
+ write: (theme, value) => withAppearance(theme, {
5117
+ [key]: oneOf(flag, values, value)
5118
+ })
5119
+ };
5120
+ }
5121
+ function integer(flag, value, min, max) {
5122
+ const parsed = Number(value);
5123
+ if (!/^\d+$/.test(value) || parsed < min || parsed > max) {
5124
+ throw invalid(flag, value, `a whole number from ${min} to ${max}`);
5125
+ }
5126
+ return parsed;
5127
+ }
5128
+ function contrastField(flag, key, description) {
5129
+ return {
5130
+ kind: "integer",
5131
+ flag,
5132
+ key,
5133
+ description,
5134
+ min: CONTRAST_MIN,
5135
+ max: CONTRAST_MAX,
5136
+ read: (theme) => String(theme.appearance[key]),
5137
+ write: (theme, value) => withAppearance(theme, {
5138
+ [key]: integer(flag, value, CONTRAST_MIN, CONTRAST_MAX)
5139
+ })
5140
+ };
5141
+ }
5142
+ function fontSizeField(flag, key, description) {
5143
+ return {
5144
+ kind: "integer",
5145
+ flag,
5146
+ key: `prefs.${key}`,
5147
+ description,
5148
+ min: FONT_SIZE_PX_MIN,
5149
+ max: FONT_SIZE_PX_MAX,
5150
+ read: (theme) => String(theme.appearance.prefs[key]),
5151
+ write: (theme, value) => withPrefs(theme, {
5152
+ [key]: integer(flag, value, FONT_SIZE_PX_MIN, FONT_SIZE_PX_MAX)
5153
+ })
5154
+ };
5155
+ }
5156
+ function fontField(flag, key, description, categories, fallback) {
5157
+ const fonts = FONT_OPTIONS.filter(
5158
+ (font) => categories.includes(font.category)
5159
+ );
5160
+ return {
5161
+ kind: "font",
5162
+ flag,
5163
+ key: `prefs.${key}`,
5164
+ description,
5165
+ values: [fallback.value, ...fonts.map((font) => font.value)],
5166
+ categories,
5167
+ fallback,
5168
+ read: (theme) => {
5169
+ const family = theme.appearance.prefs[key];
5170
+ if (family === fallback.family) return fallback.value;
5171
+ return fonts.find((font) => font.family === family)?.value;
5172
+ },
5173
+ write: (theme, value) => {
5174
+ const family = value === fallback.value ? fallback.family : fonts.find((font) => font.value === value)?.family;
5175
+ if (family === void 0) {
5176
+ throw invalid(
5177
+ flag,
5178
+ value,
5179
+ `one of ${[fallback.value, ...fonts.map((font) => font.value)].join(", ")}`
5180
+ );
5181
+ }
5182
+ return withPrefs(theme, { [key]: family });
5183
+ }
5184
+ };
5185
+ }
5186
+ var DEFAULT_PREFS = DEFAULT_NXDS_APPEARANCE.prefs;
5187
+ var TEXT_FONTS = ["sans", "serif"];
5188
+ var NXDS_THEME_FIELDS = [
5189
+ {
5190
+ kind: "color",
5191
+ flag: "brand-color",
5192
+ key: "brandColor",
5193
+ description: 'brand color, as any CSS color, such as "#4f46e5"',
5194
+ read: (theme) => theme.appearance.brandColor,
5195
+ write: (theme, value) => {
5196
+ if (!isColor(value)) throw invalid("brand-color", value, "a CSS color");
5197
+ return withAppearance(theme, { brandColor: value });
5198
+ }
5199
+ },
5200
+ enumField(
5201
+ "surface-tone",
5202
+ "surfaceTone",
5203
+ "neutral tone of backgrounds and borders",
5204
+ BASE_TONE_OPTIONS.map((option) => option.value)
5205
+ ),
5206
+ enumField(
5207
+ "density",
5208
+ "density",
5209
+ "spacing and control size",
5210
+ DENSITY_OPTIONS.map((option) => option.value)
5211
+ ),
5212
+ enumField(
5213
+ "corners",
5214
+ "corners",
5215
+ "corner radius",
5216
+ CORNER_OPTIONS.map((option) => option.value)
5217
+ ),
5218
+ enumField(
5219
+ "elevation",
5220
+ "elevation",
5221
+ "shadow depth",
5222
+ ELEVATION_OPTIONS.map((option) => option.value)
5223
+ ),
5224
+ enumField(
5225
+ "stroke",
5226
+ "stroke",
5227
+ "border width",
5228
+ STROKE_OPTIONS.map((option) => option.value)
5229
+ ),
5230
+ enumField("mode", "mode", "color scheme", ["light", "dark", "system"]),
5231
+ contrastField(
5232
+ "light-contrast",
5233
+ "lightContrast",
5234
+ "light mode contrast, 0 to 100"
5235
+ ),
5236
+ contrastField(
5237
+ "dark-contrast",
5238
+ "darkContrast",
5239
+ "dark mode contrast, 0 to 100"
5240
+ ),
5241
+ fontField("font", "uiFont", "font for UI text", TEXT_FONTS, {
5242
+ value: "system",
5243
+ family: DEFAULT_PREFS.uiFont
5244
+ }),
5245
+ fontField(
5246
+ "heading-font",
5247
+ "headingFont",
5248
+ 'font for headings, or "ui" for the UI font',
5249
+ TEXT_FONTS,
5250
+ { value: "ui", family: null }
5251
+ ),
5252
+ fontField("code-font", "codeFont", "font for code", ["mono"], {
5253
+ value: "system",
5254
+ family: DEFAULT_PREFS.codeFont
5255
+ }),
5256
+ fontSizeField("font-size", "uiFontSize", "UI text size in px"),
5257
+ fontSizeField("code-font-size", "codeFontSize", "code text size in px"),
5258
+ {
5259
+ kind: "choice",
5260
+ flag: "icons",
5261
+ description: "icon library nxds components draw",
5262
+ values: ICON_LIBRARY_OPTIONS.map((option) => option.value),
5263
+ read: (theme) => theme.iconLibrary,
5264
+ write: (theme, value) => ({
5265
+ ...theme,
5266
+ iconLibrary: oneOf(
5267
+ "icons",
5268
+ ICON_LIBRARY_OPTIONS.map((option) => option.value),
5269
+ value
5270
+ )
5271
+ })
5272
+ }
5273
+ ];
5274
+ function nxdsThemeFlags(theme) {
5275
+ return NXDS_THEME_FIELDS.flatMap((field) => {
5276
+ const value = field.read(theme);
5277
+ if (value === void 0 || value === field.read(DEFAULT_NXDS_THEME)) {
5278
+ return [];
5279
+ }
5280
+ return [{ flag: field.flag, value }];
5281
+ });
5282
+ }
5283
+ function applyNxdsThemeFlags(theme, values) {
5284
+ return NXDS_THEME_FIELDS.reduce((current, field) => {
5285
+ const value = values[field.flag];
5286
+ return value === void 0 ? current : field.write(current, value);
5287
+ }, theme);
5288
+ }
5289
+
4928
5290
  // src/lib/token-registry.ts
4929
5291
  var SURFACE_TOKEN_NAMES = [
4930
5292
  "background",
@@ -5075,12 +5437,19 @@ var SEMANTIC_TOKEN_REGISTRY = [
5075
5437
  CORNER_OPTIONS,
5076
5438
  DEFAULT_BRAND_COLOR,
5077
5439
  DEFAULT_COOKIE_KEY,
5440
+ DEFAULT_ICON_LIBRARY,
5078
5441
  DEFAULT_NXDS_APPEARANCE,
5442
+ DEFAULT_NXDS_THEME,
5079
5443
  DEFAULT_STORAGE_KEY,
5080
5444
  DENSITY_OPTIONS,
5081
5445
  ELEVATION_OPTIONS,
5446
+ FONT_OPTIONS,
5447
+ FONT_SIZE_PX_MAX,
5448
+ FONT_SIZE_PX_MIN,
5449
+ ICON_LIBRARY_OPTIONS,
5082
5450
  NXDS_APPEARANCE_COOKIE_MAX_AGE_SECONDS,
5083
5451
  NXDS_APPEARANCE_DATA_ATTRS,
5452
+ NXDS_THEME_FIELDS,
5084
5453
  PALETTE_KEYS,
5085
5454
  RUNTIME_LAYER_ORDER,
5086
5455
  SEMANTIC_TOKEN_REGISTRY,
@@ -5091,6 +5460,7 @@ var SEMANTIC_TOKEN_REGISTRY = [
5091
5460
  apcaLc,
5092
5461
  appearancePrefsToCss,
5093
5462
  applyNxdsAppearance,
5463
+ applyNxdsThemeFlags,
5094
5464
  createDefaultNxdsAppearanceSnapshot,
5095
5465
  createNxdsAppearanceBootstrapScript,
5096
5466
  createNxdsAppearanceSnapshot,
@@ -5103,6 +5473,7 @@ var SEMANTIC_TOKEN_REGISTRY = [
5103
5473
  isColor,
5104
5474
  measureThemeContrast,
5105
5475
  normalizeAppearanceModeIds,
5476
+ nxdsThemeFlags,
5106
5477
  parseNxdsAppearanceStateCookie,
5107
5478
  persistNxdsAppearance,
5108
5479
  prefersDarkColorScheme,
@@ -5114,6 +5485,8 @@ var SEMANTIC_TOKEN_REGISTRY = [
5114
5485
  sanitizeNxdsAppearancePrefs,
5115
5486
  sanitizeNxdsAppearanceSnapshot,
5116
5487
  serializeNxdsAppearanceStateCookie,
5488
+ shuffleNxdsAppearance,
5489
+ shuffleNxdsIconLibrary,
5117
5490
  themeToCss,
5118
5491
  watchColorScheme
5119
5492
  });