@uni-design-system/uni-core 7.2.0 → 8.0.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.
Files changed (31) hide show
  1. package/dist/cjs/index.cjs +462 -7
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/esm/index.js +444 -8
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/types/concepts/animation/duration.helpers.d.ts +25 -0
  6. package/dist/types/concepts/animation/duration.helpers.d.ts.map +1 -0
  7. package/dist/types/concepts/animation/duration.helpers.spec.d.ts +2 -0
  8. package/dist/types/concepts/animation/duration.helpers.spec.d.ts.map +1 -0
  9. package/dist/types/concepts/animation/index.d.ts +1 -0
  10. package/dist/types/concepts/animation/index.d.ts.map +1 -1
  11. package/dist/types/concepts/component/component.types.d.ts +1 -1
  12. package/dist/types/concepts/component/component.types.d.ts.map +1 -1
  13. package/dist/types/concepts/generation/theme-file.emitter.d.ts.map +1 -1
  14. package/dist/types/concepts/generation/theme.generator.d.ts +9 -2
  15. package/dist/types/concepts/generation/theme.generator.d.ts.map +1 -1
  16. package/dist/types/concepts/style/index.d.ts +1 -0
  17. package/dist/types/concepts/style/index.d.ts.map +1 -1
  18. package/dist/types/concepts/style/selectors.constants.d.ts +18 -0
  19. package/dist/types/concepts/style/selectors.constants.d.ts.map +1 -0
  20. package/dist/types/concepts/theme/index.d.ts +1 -0
  21. package/dist/types/concepts/theme/index.d.ts.map +1 -1
  22. package/dist/types/concepts/theme/theme.types.d.ts +7 -0
  23. package/dist/types/concepts/theme/theme.types.d.ts.map +1 -1
  24. package/dist/types/concepts/theme/theme.validation.d.ts +51 -0
  25. package/dist/types/concepts/theme/theme.validation.d.ts.map +1 -0
  26. package/dist/types/concepts/theme/theme.validation.spec.d.ts +2 -0
  27. package/dist/types/concepts/theme/theme.validation.spec.d.ts.map +1 -0
  28. package/dist/types/concepts/theme/themes/base.theme.d.ts +16 -0
  29. package/dist/types/concepts/theme/themes/base.theme.d.ts.map +1 -1
  30. package/dist/types/concepts/typography/typeface.helpers.d.ts.map +1 -1
  31. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -1,3 +1,34 @@
1
+ //#region src/concepts/animation/duration.helpers.ts
2
+ /** Duration (seconds) the default theme assigns `expand`'s `transitionSpeed`. */
3
+ var EXPAND_DEFAULT_SPEED = .35;
4
+ /**
5
+ * Content height (px) at which a reveal runs at exactly its `transitionSpeed`.
6
+ * Roughly a few lines of card content — the size the default 0.35s was tuned on.
7
+ */
8
+ var EXPAND_REFERENCE_HEIGHT = 240;
9
+ /**
10
+ * Duration envelope at the default speed. A tiny region never blinks past
11
+ * faster than the min; a full-page region never drags longer than the max.
12
+ * Expressed at `EXPAND_DEFAULT_SPEED` and applied as scale-factor clamps, so a
13
+ * theme that slows `transitionSpeed` down widens its envelope proportionally
14
+ * instead of being capped back to the stock feel.
15
+ */
16
+ var EXPAND_MIN_DURATION = .15;
17
+ var EXPAND_MAX_DURATION = .6;
18
+ var MIN_SCALE = EXPAND_MIN_DURATION / EXPAND_DEFAULT_SPEED;
19
+ var MAX_SCALE = EXPAND_MAX_DURATION / EXPAND_DEFAULT_SPEED;
20
+ /**
21
+ * Size-aware reveal duration: `speed × √(height ÷ reference)`, clamped.
22
+ *
23
+ * A fixed duration reads as sluggish on a short region and rushed on a tall
24
+ * one; scaling by the square root of height keeps perceived speed steady —
25
+ * bigger reveals get more time, but sublinearly.
26
+ */
27
+ var expandDuration = (contentHeight, speed = EXPAND_DEFAULT_SPEED) => {
28
+ const scale = Math.sqrt((Number.isFinite(contentHeight) ? Math.max(contentHeight, 0) : 0) / 240);
29
+ return speed * Math.min(Math.max(scale, MIN_SCALE), MAX_SCALE);
30
+ };
31
+ //#endregion
1
32
  //#region src/concepts/animation/keyframes.constants.ts
2
33
  var fadeIn = {
3
34
  "0%": { opacity: 0 },
@@ -898,6 +929,25 @@ var BaseIcons = {
898
929
  spinner: "data:image/svg+xml;charset=UTF-8,%3csvg stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3e%3cstyle%3e .oui_spinner %7b transform-origin: center; animation: rotate_360 2s linear infinite;%7d .oui_spinner circle %7b stroke-linecap: round; animation: dash 1.5s ease-in-out infinite;%7d %40keyframes rotate_360 %7b 100%25 %7b transform: rotate(360deg);%7d %7d %40keyframes dash %7b 0%25 %7b stroke-dasharray: 0 150; stroke-dashoffset: 0;%7d 47.5%25 %7b stroke-dasharray: 42 150; stroke-dashoffset: -16;%7d 95%25, 100%25 %7b stroke-dasharray: 42 150; stroke-dashoffset: -59;%7d %7d %3c/style%3e%3cg class='oui_spinner'%3e%3ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3e%3c/circle%3e%3c/g%3e%3c/svg%3e "
899
930
  };
900
931
  //#endregion
932
+ //#region src/concepts/style/selectors.constants.ts
933
+ /**
934
+ * The highlight state for roving-focus composites (menus, and any other widget
935
+ * that moves focus programmatically): pointer hover, or focus the user drove
936
+ * from the keyboard.
937
+ *
938
+ * Why not plain `:focus` — a roving-focus composite calls `.focus()` on an item
939
+ * every time it opens, including pointer opens, and `:focus` would paint that
940
+ * as a highlight the mouse user never asked for, reading as a preselected
941
+ * item. `:focus-visible` excludes programmatic focus that follows a click while
942
+ * still matching keyboard navigation.
943
+ *
944
+ * Shared as a constant because Emotion merges styles by **exact selector
945
+ * text**: a component's base rule and any theme variant restyling that rule
946
+ * must key both with this value, or the variant silently fails to override and
947
+ * reintroduces the phantom highlight.
948
+ */
949
+ var HOVER_OR_KEYBOARD_FOCUS = "&:hover, &:focus-visible";
950
+ //#endregion
901
951
  //#region src/concepts/theme/themes/base.theme.ts
902
952
  var BaseTypography = {
903
953
  "display-large": {
@@ -1095,6 +1145,25 @@ var buildBorders = (c) => ({
1095
1145
  dark: `1px solid ${c["on-background"]}`,
1096
1146
  dotted: `1px dotted ${c["on-background"]}`
1097
1147
  });
1148
+ /**
1149
+ * One tag colour role across all three tones. `soft` is the resting look (the
1150
+ * container pair), `solid` fills with the role itself, `outline` keeps the
1151
+ * surface and draws the edge — so every role stays consistent and a theme can
1152
+ * still override any single cell.
1153
+ */
1154
+ var tagVariant = (c, role) => ({ [role]: {
1155
+ backgroundColor: c[`${role}-container`],
1156
+ color: c[`on-${role}-container`],
1157
+ "&.tone-solid": {
1158
+ backgroundColor: c[role],
1159
+ color: c[`on-${role}`]
1160
+ },
1161
+ "&.tone-outline": {
1162
+ backgroundColor: "transparent",
1163
+ color: c[role],
1164
+ borderColor: c[`on-${role}-container-border`] ?? c[role]
1165
+ }
1166
+ } });
1098
1167
  var buildComponents = (c) => ({
1099
1168
  alert: { options: {
1100
1169
  topPosition: 40,
@@ -1240,6 +1309,38 @@ var buildComponents = (c) => ({
1240
1309
  color: "primary-surface",
1241
1310
  shadow: "menu"
1242
1311
  } },
1312
+ menu: { options: {
1313
+ minWidth: 184,
1314
+ color: void 0,
1315
+ border: void 0,
1316
+ borderRadius: void 0,
1317
+ shadow: void 0,
1318
+ paddingVertical: "xs",
1319
+ paddingHorizontal: "none",
1320
+ dividerBorder: "light",
1321
+ dividerSpacing: "xs"
1322
+ } },
1323
+ menuItem: {
1324
+ options: {
1325
+ height: 38,
1326
+ paddingHorizontal: "md",
1327
+ gap: "md",
1328
+ borderRadius: "none",
1329
+ typeface: "label",
1330
+ textColor: void 0,
1331
+ hoverColor: "primary-container",
1332
+ activeSymbol: "check",
1333
+ transitionSpeed: .35
1334
+ },
1335
+ variants: { warn: {
1336
+ color: c.warn,
1337
+ [HOVER_OR_KEYBOARD_FOCUS]: {
1338
+ backgroundColor: c["warn-container"],
1339
+ color: c["on-warn-container"]
1340
+ }
1341
+ } }
1342
+ },
1343
+ expand: { options: { transitionSpeed: .35 } },
1243
1344
  footer: { options: {
1244
1345
  height: 52,
1245
1346
  color: "primary",
@@ -1287,6 +1388,79 @@ var buildComponents = (c) => ({
1287
1388
  focusOutlineOffset: 2
1288
1389
  } },
1289
1390
  badge: { options: { borderRadius: "xxs" } },
1391
+ tag: {
1392
+ options: {
1393
+ borderRadius: "max",
1394
+ typeface: "tag",
1395
+ gap: "xs",
1396
+ removeIcon: "close",
1397
+ selectedIcon: "check"
1398
+ },
1399
+ fixed: {
1400
+ display: "inline-flex",
1401
+ alignItems: "center",
1402
+ maxWidth: "100%",
1403
+ border: "1px solid transparent",
1404
+ transition: "background-color .2s ease, color .2s ease"
1405
+ },
1406
+ variants: {
1407
+ ...tagVariant(c, "primary"),
1408
+ ...tagVariant(c, "secondary"),
1409
+ ...tagVariant(c, "tertiary"),
1410
+ ...tagVariant(c, "warn"),
1411
+ ...tagVariant(c, "success"),
1412
+ ghost: {
1413
+ backgroundColor: "transparent",
1414
+ color: c["on-background"],
1415
+ "&.tone-solid": {
1416
+ backgroundColor: c["surface-variant"],
1417
+ color: c["on-surface-variant"]
1418
+ },
1419
+ "&.tone-outline": {
1420
+ backgroundColor: "transparent",
1421
+ borderColor: c.outline
1422
+ }
1423
+ },
1424
+ disabled: {
1425
+ backgroundColor: c["disabled-container"],
1426
+ color: c["on-disabled"],
1427
+ "&.tone-solid": {
1428
+ backgroundColor: c.disabled,
1429
+ color: c["on-disabled"]
1430
+ },
1431
+ "&.tone-outline": {
1432
+ backgroundColor: "transparent",
1433
+ borderColor: c.disabled
1434
+ }
1435
+ }
1436
+ },
1437
+ sizes: {
1438
+ sm: {
1439
+ height: 20,
1440
+ fontSize: 12,
1441
+ padding: "0 8px"
1442
+ },
1443
+ md: {
1444
+ height: 24,
1445
+ fontSize: 13,
1446
+ padding: "0 10px"
1447
+ },
1448
+ lg: {
1449
+ height: 32,
1450
+ fontSize: 15,
1451
+ padding: "0 12px"
1452
+ }
1453
+ }
1454
+ },
1455
+ tagInput: { options: {
1456
+ chipGap: "xs",
1457
+ chipSize: "md",
1458
+ minInputWidth: "12ch",
1459
+ listColor: "primary-surface",
1460
+ listShadow: "menu",
1461
+ listBorderRadius: "xs",
1462
+ maxSuggestions: 8
1463
+ } },
1290
1464
  button: {
1291
1465
  options: {
1292
1466
  borderRadius: "max",
@@ -1590,11 +1764,11 @@ var buildComponents = (c) => ({
1590
1764
  typeface: "label"
1591
1765
  } }
1592
1766
  });
1593
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1767
+ var isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1594
1768
  var deepMerge = (base, override) => {
1595
1769
  if (!override) return base;
1596
1770
  const out = { ...base };
1597
- for (const [key, value] of Object.entries(override)) out[key] = isRecord(value) && isRecord(out[key]) ? deepMerge(out[key], value) : value;
1771
+ for (const [key, value] of Object.entries(override)) out[key] = isRecord$1(value) && isRecord$1(out[key]) ? deepMerge(out[key], value) : value;
1598
1772
  return out;
1599
1773
  };
1600
1774
  var createTheme = ({ id, name, colors, icons = {}, radii = BaseRadii, shadows = BaseShadows, borders, components }) => ({
@@ -1614,6 +1788,31 @@ var createTheme = ({ id, name, colors, icons = {}, radii = BaseRadii, shadows =
1614
1788
  components: deepMerge(buildComponents(colors), components)
1615
1789
  });
1616
1790
  /**
1791
+ * Re-attach the built-in icon set to a theme that traveled without it.
1792
+ *
1793
+ * A serialized theme is ~50 KB, ~71% of which is {@link BaseIcons} — bytes
1794
+ * every uni-core consumer already ships. Transports (MCP tool results, theme
1795
+ * JSON over HTTP) send the {@link dehydrateTheme} form and the receiver
1796
+ * hydrates, applying exactly the `{...BaseIcons, ...icons}` contract
1797
+ * {@link createTheme} applies at construction: the theme's own icons win,
1798
+ * built-ins fill the rest.
1799
+ */
1800
+ var hydrateTheme = (theme) => ({
1801
+ ...theme,
1802
+ icons: {
1803
+ ...BaseIcons,
1804
+ ...theme.icons
1805
+ }
1806
+ });
1807
+ /**
1808
+ * The wire form of a theme: icons identical to the built-in set are dropped,
1809
+ * so only genuine overrides travel. Reverse with {@link hydrateTheme}.
1810
+ */
1811
+ var dehydrateTheme = (theme) => ({
1812
+ ...theme,
1813
+ icons: Object.fromEntries(Object.entries(theme.icons).filter(([name, uri]) => BaseIcons[name] !== uri))
1814
+ });
1815
+ /**
1617
1816
  * Build a full {@link UniTheme} straight from a {@link PaletteConfig} — the
1618
1817
  * one-call path a theme builder uses to turn a brand color (or a seed +
1619
1818
  * scheme + category) into a complete, ready-to-apply theme.
@@ -1761,11 +1960,17 @@ var generateThemes = (input) => {
1761
1960
  };
1762
1961
  };
1763
1962
  /**
1963
+ * One-line human summary of a {@link ContrastReport}. Shared so the generated
1964
+ * theme file's header and the MCP's theme tools report identical wording.
1965
+ */
1966
+ var summarizeContrast = (report) => `${report.checks.length} contrast pairs checked · worst ${report.worstRatio}:1 · ${report.pass ? "all AA" : `${report.checks.filter((check) => !check.pass).length} failing`}`;
1967
+ /**
1764
1968
  * Convenience wrapper: {@link generateThemes} piped through `createTheme()`,
1765
- * returning a registration-ready light/dark {@link UniTheme} pair.
1969
+ * returning a registration-ready light/dark {@link UniTheme} pair alongside
1970
+ * the contrast report the colors were audited against.
1766
1971
  */
1767
1972
  var generateUniThemes = (input) => {
1768
- const { lightColors, darkColors, radii, lightShadows, darkShadows } = generateThemes(input);
1973
+ const { lightColors, darkColors, radii, lightShadows, darkShadows, report } = generateThemes(input);
1769
1974
  const name = input.name ?? "Brand";
1770
1975
  const id = name.replace(/\W+/g, "") || "Brand";
1771
1976
  return {
@@ -1782,7 +1987,8 @@ var generateUniThemes = (input) => {
1782
1987
  colors: darkColors,
1783
1988
  radii,
1784
1989
  shadows: darkShadows
1785
- })
1990
+ }),
1991
+ report
1786
1992
  };
1787
1993
  };
1788
1994
  //#endregion
@@ -1841,7 +2047,7 @@ var emitThemeFile = (input) => {
1841
2047
  input.shape && `--shape=${input.shape}`,
1842
2048
  !darkMode && "--dark-mode=false"
1843
2049
  ].filter(Boolean).join(" ");
1844
- const reportSummary = `${report.checks.length} contrast pairs checked · worst ${report.worstRatio}:1 · ${report.pass ? "all AA" : `${report.checks.filter((c) => !c.pass).length} failing`}`;
2050
+ const reportSummary = summarizeContrast(report);
1845
2051
  const modes = [{
1846
2052
  exportName: `${id}Light`,
1847
2053
  displayName: `${name} Light`,
@@ -2134,6 +2340,235 @@ var UniThemes = {
2134
2340
  };
2135
2341
  var DefaultThemeId = Object.keys(UniThemes)[0];
2136
2342
  //#endregion
2343
+ //#region src/concepts/theme/theme.validation.ts
2344
+ /**
2345
+ * Color tokens components cannot render without: the default variant pairs,
2346
+ * the base surfaces, and the disabled/warn states. Themes may define any
2347
+ * further tokens; these must exist.
2348
+ */
2349
+ var REQUIRED_COLOR_TOKENS = [
2350
+ "primary",
2351
+ "on-primary",
2352
+ "primary-container",
2353
+ "on-primary-container",
2354
+ "primary-surface",
2355
+ "on-primary-surface",
2356
+ "surface",
2357
+ "on-surface",
2358
+ "background",
2359
+ "on-background",
2360
+ "warn",
2361
+ "on-warn",
2362
+ "disabled",
2363
+ "on-disabled",
2364
+ "outline",
2365
+ "transparent"
2366
+ ];
2367
+ /** The canonical type scale; every theme must style each role. */
2368
+ var REQUIRED_TEXT_ROLES = [
2369
+ "display-large",
2370
+ "display-medium",
2371
+ "display-small",
2372
+ "headline-large",
2373
+ "headline-medium",
2374
+ "headline-small",
2375
+ "title-large",
2376
+ "title-medium",
2377
+ "title-small",
2378
+ "body-1-long",
2379
+ "body-1-short",
2380
+ "body-2-long",
2381
+ "body-2-short",
2382
+ "subtitle-1",
2383
+ "subtitle-2",
2384
+ "label",
2385
+ "button",
2386
+ "caption",
2387
+ "overline",
2388
+ "paragraph",
2389
+ "quote",
2390
+ "note"
2391
+ ];
2392
+ var REQUIRED_SPACING_SIZES = [
2393
+ "none",
2394
+ "xxs",
2395
+ "xs",
2396
+ "sm",
2397
+ "md",
2398
+ "lg",
2399
+ "xl"
2400
+ ];
2401
+ var REQUIRED_RADII_SIZES = [
2402
+ "none",
2403
+ "xxs",
2404
+ "xs",
2405
+ "sm",
2406
+ "md",
2407
+ "lg",
2408
+ "max"
2409
+ ];
2410
+ var REQUIRED_SHADOWS = [
2411
+ "raised",
2412
+ "menu",
2413
+ "dialog",
2414
+ "warn"
2415
+ ];
2416
+ var REQUIRED_THICKNESSES = [
2417
+ "thin",
2418
+ "standard",
2419
+ "thick"
2420
+ ];
2421
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2422
+ var isCssValue = (value) => typeof value === "string" || typeof value === "number";
2423
+ /** Nested style expression: string/number leaves, objects for selectors. */
2424
+ var checkStyleExpression = (value, path, issues) => {
2425
+ if (!isRecord(value)) {
2426
+ issues.push({
2427
+ path,
2428
+ message: "must be a style object"
2429
+ });
2430
+ return;
2431
+ }
2432
+ for (const [key, entry] of Object.entries(value)) {
2433
+ if (entry === void 0 || isCssValue(entry)) continue;
2434
+ if (isRecord(entry)) checkStyleExpression(entry, `${path}.${key}`, issues);
2435
+ else issues.push({
2436
+ path: `${path}.${key}`,
2437
+ message: "must be a string, number, or nested style object"
2438
+ });
2439
+ }
2440
+ };
2441
+ var checkStringRecord = (value, path, issues, required = [], allowNumbers = false) => {
2442
+ if (!isRecord(value)) {
2443
+ issues.push({
2444
+ path,
2445
+ message: "must be an object"
2446
+ });
2447
+ return;
2448
+ }
2449
+ for (const key of required) if (!(key in value)) issues.push({
2450
+ path: `${path}.${key}`,
2451
+ message: "is required"
2452
+ });
2453
+ for (const [key, entry] of Object.entries(value)) {
2454
+ if (entry === void 0) continue;
2455
+ if (!(allowNumbers ? isCssValue(entry) : typeof entry === "string")) issues.push({
2456
+ path: `${path}.${key}`,
2457
+ message: allowNumbers ? "must be a string or number" : "must be a string"
2458
+ });
2459
+ }
2460
+ };
2461
+ var checkTypography = (value, issues) => {
2462
+ if (!isRecord(value)) {
2463
+ issues.push({
2464
+ path: "typography",
2465
+ message: "must be an object"
2466
+ });
2467
+ return;
2468
+ }
2469
+ for (const role of REQUIRED_TEXT_ROLES) if (!(role in value)) issues.push({
2470
+ path: `typography.${role}`,
2471
+ message: "is required"
2472
+ });
2473
+ for (const [role, style] of Object.entries(value)) {
2474
+ if (!isRecord(style)) {
2475
+ issues.push({
2476
+ path: `typography.${role}`,
2477
+ message: "must be a TextStyle object"
2478
+ });
2479
+ continue;
2480
+ }
2481
+ if (typeof style["fontFamily"] !== "string") issues.push({
2482
+ path: `typography.${role}.fontFamily`,
2483
+ message: "must be a string"
2484
+ });
2485
+ for (const field of ["fontSize", "lineHeight"]) if (!isCssValue(style[field])) issues.push({
2486
+ path: `typography.${role}.${field}`,
2487
+ message: "must be a string or number"
2488
+ });
2489
+ }
2490
+ };
2491
+ var checkComponents = (value, issues) => {
2492
+ if (!isRecord(value)) {
2493
+ issues.push({
2494
+ path: "components",
2495
+ message: "must be an object"
2496
+ });
2497
+ return;
2498
+ }
2499
+ for (const [name, entry] of Object.entries(value)) {
2500
+ if (entry === void 0) continue;
2501
+ if (!isRecord(entry)) {
2502
+ issues.push({
2503
+ path: `components.${name}`,
2504
+ message: "must be a component theme object"
2505
+ });
2506
+ continue;
2507
+ }
2508
+ if (entry["fixed"] !== void 0) checkStyleExpression(entry["fixed"], `components.${name}.fixed`, issues);
2509
+ for (const section of ["variants", "sizes"]) {
2510
+ const styles = entry[section];
2511
+ if (styles === void 0) continue;
2512
+ if (!isRecord(styles)) {
2513
+ issues.push({
2514
+ path: `components.${name}.${section}`,
2515
+ message: "must be an object"
2516
+ });
2517
+ continue;
2518
+ }
2519
+ for (const [key, style] of Object.entries(styles)) if (style !== void 0) checkStyleExpression(style, `components.${name}.${section}.${key}`, issues);
2520
+ }
2521
+ if (entry["options"] !== void 0 && !isRecord(entry["options"])) issues.push({
2522
+ path: `components.${name}.options`,
2523
+ message: "must be an object"
2524
+ });
2525
+ }
2526
+ };
2527
+ /**
2528
+ * Validate a candidate theme, collecting every structural issue. Never
2529
+ * throws; a failed result carries the complete repair list.
2530
+ */
2531
+ var parseTheme = (input) => {
2532
+ const issues = [];
2533
+ if (!isRecord(input)) return {
2534
+ success: false,
2535
+ issues: [{
2536
+ path: "",
2537
+ message: "theme must be an object"
2538
+ }]
2539
+ };
2540
+ for (const field of ["id", "name"]) if (typeof input[field] !== "string" || input[field] === "") issues.push({
2541
+ path: field,
2542
+ message: "must be a non-empty string"
2543
+ });
2544
+ checkStringRecord(input["colors"], "colors", issues, REQUIRED_COLOR_TOKENS);
2545
+ checkTypography(input["typography"], issues);
2546
+ checkStringRecord(input["borders"], "borders", issues);
2547
+ checkStringRecord(input["radii"], "radii", issues, REQUIRED_RADII_SIZES);
2548
+ checkStringRecord(input["shadows"], "shadows", issues, REQUIRED_SHADOWS);
2549
+ checkStringRecord(input["spacing"], "spacing", issues, REQUIRED_SPACING_SIZES, true);
2550
+ checkStringRecord(input["thicknesses"], "thicknesses", issues, REQUIRED_THICKNESSES, true);
2551
+ checkStringRecord(input["icons"], "icons", issues);
2552
+ checkComponents(input["components"], issues);
2553
+ return issues.length === 0 ? {
2554
+ success: true,
2555
+ theme: input,
2556
+ issues: []
2557
+ } : {
2558
+ success: false,
2559
+ issues
2560
+ };
2561
+ };
2562
+ /** Format issues for logs and error messages. */
2563
+ var formatThemeIssues = (issues) => issues.map(({ path, message }) => path ? `${path} ${message}` : message).join("; ");
2564
+ /** Validate or throw, with every reason in the error message. */
2565
+ var assertTheme = (input) => {
2566
+ const result = parseTheme(input);
2567
+ if (!result.success) throw new Error(`Invalid UniTheme: ${formatThemeIssues(result.issues)}`);
2568
+ return result.theme;
2569
+ };
2570
+ var isUniTheme = (input) => parseTheme(input).success;
2571
+ //#endregion
2137
2572
  //#region src/concepts/typography/typography.records.ts
2138
2573
  var FontWeightMap = {
2139
2574
  thin: 100,
@@ -2150,6 +2585,7 @@ var FontWeightMap = {
2150
2585
  //#endregion
2151
2586
  //#region src/concepts/typography/typeface.helpers.ts
2152
2587
  var px = (value) => typeof value === "number" ? `${value}px` : value;
2588
+ var weight = (value) => typeof value === "number" ? value : FontWeightMap[value];
2153
2589
  /**
2154
2590
  * Converts a TextStyle (numeric, design-token oriented) into a
2155
2591
  * TypeFaceDefinition (CSS-ready) so a theme's `typefaces` map can be
@@ -2161,7 +2597,7 @@ var toTypeface = (style) => ({
2161
2597
  lineHeight: px(style.lineHeight),
2162
2598
  ...style.letterSpacing !== void 0 && { letterSpacing: px(style.letterSpacing) },
2163
2599
  ...style.textTransform === "uppercase" && { textTransform: "uppercase" },
2164
- ...style.fontWeight !== void 0 && { fontWeight: style.fontWeight },
2600
+ ...style.fontWeight !== void 0 && { fontWeight: weight(style.fontWeight) },
2165
2601
  ...style.fontStyle && { fontStyle: style.fontStyle }
2166
2602
  });
2167
2603
  var toTypefaces = (typography) => Object.fromEntries(Object.entries(typography).map(([role, style]) => [role, toTypeface(style)]));
@@ -2183,6 +2619,6 @@ var debounce = (callback, timeout) => {
2183
2619
  };
2184
2620
  };
2185
2621
  //#endregion
2186
- export { BASE_PALETTE_CONFIG, BaseIcons, BaseTheme, CategoryChroma, CategoryLightness, CategorySaturation, DarkTheme, DefaultThemeId, FontWeightMap, HSLAToString, HSLToHex, HSLToRGB, LightTheme, RGBToHSL, RGBToString, RoleHues, ShadowCssMap, ShadowMap, ShapeRadii, UniThemes, Z_INDEX, classifyScheme, collapseFadeOut, contrastRatio, createTheme, createThemeFromPalette, cycle, darkColors, debounce, emitDtcgTokens, emitThemeFile, expandFadeIn, fadeIn, fadeOut, generateColors, generatePalette, generateShadows, generateThemes, generateUniThemes, getAnalogousHues, getComplimentaryHue, getDeviceOrientation, getDeviceSize, getSplitComplimentaryHues, getTriadicHues, getValue, gradient, hexToHSL, hexToOklch, hexToRgb, inferCategory, lightColors, oklchToHex, randomRangeValue, relativeLuminance, removeInputPlatformStyling, rgbToHex, schemeHues, svgToIconUri, toTypeface, toTypefaces, uniColor };
2622
+ export { BASE_PALETTE_CONFIG, BaseIcons, BaseTheme, CategoryChroma, CategoryLightness, CategorySaturation, DarkTheme, DefaultThemeId, EXPAND_DEFAULT_SPEED, EXPAND_MAX_DURATION, EXPAND_MIN_DURATION, EXPAND_REFERENCE_HEIGHT, FontWeightMap, HOVER_OR_KEYBOARD_FOCUS, HSLAToString, HSLToHex, HSLToRGB, LightTheme, REQUIRED_COLOR_TOKENS, REQUIRED_RADII_SIZES, REQUIRED_SHADOWS, REQUIRED_SPACING_SIZES, REQUIRED_TEXT_ROLES, REQUIRED_THICKNESSES, RGBToHSL, RGBToString, RoleHues, ShadowCssMap, ShadowMap, ShapeRadii, UniThemes, Z_INDEX, assertTheme, classifyScheme, collapseFadeOut, contrastRatio, createTheme, createThemeFromPalette, cycle, darkColors, debounce, dehydrateTheme, emitDtcgTokens, emitThemeFile, expandDuration, expandFadeIn, fadeIn, fadeOut, formatThemeIssues, generateColors, generatePalette, generateShadows, generateThemes, generateUniThemes, getAnalogousHues, getComplimentaryHue, getDeviceOrientation, getDeviceSize, getSplitComplimentaryHues, getTriadicHues, getValue, gradient, hexToHSL, hexToOklch, hexToRgb, hydrateTheme, inferCategory, isUniTheme, lightColors, oklchToHex, parseTheme, randomRangeValue, relativeLuminance, removeInputPlatformStyling, rgbToHex, schemeHues, summarizeContrast, svgToIconUri, toTypeface, toTypefaces, uniColor };
2187
2623
 
2188
2624
  //# sourceMappingURL=index.js.map