@ankhorage/surface 3.1.3 → 3.3.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 (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/components/navigation/helpers.d.ts.map +1 -1
  3. package/dist/components/navigation/helpers.js +16 -8
  4. package/dist/components/navigation/helpers.js.map +1 -1
  5. package/dist/internal/resolvers/resolveInteractiveColors.d.ts.map +1 -1
  6. package/dist/internal/resolvers/resolveInteractiveColors.js +19 -13
  7. package/dist/internal/resolvers/resolveInteractiveColors.js.map +1 -1
  8. package/dist/internal/resolvers/resolveSelectionControlColors.d.ts.map +1 -1
  9. package/dist/internal/resolvers/resolveSelectionControlColors.js +19 -10
  10. package/dist/internal/resolvers/resolveSelectionControlColors.js.map +1 -1
  11. package/dist/theme/colorEngine.d.ts +4 -3
  12. package/dist/theme/colorEngine.d.ts.map +1 -1
  13. package/dist/theme/colorEngine.js +23 -122
  14. package/dist/theme/colorEngine.js.map +1 -1
  15. package/dist/theme/createSurfaceColorDiagnosticCollector.d.ts +14 -0
  16. package/dist/theme/createSurfaceColorDiagnosticCollector.d.ts.map +1 -0
  17. package/dist/theme/createSurfaceColorDiagnosticCollector.js +53 -0
  18. package/dist/theme/createSurfaceColorDiagnosticCollector.js.map +1 -0
  19. package/dist/theme/createSurfaceSemanticPolicy.d.ts +30 -0
  20. package/dist/theme/createSurfaceSemanticPolicy.d.ts.map +1 -0
  21. package/dist/theme/createSurfaceSemanticPolicy.js +118 -0
  22. package/dist/theme/createSurfaceSemanticPolicy.js.map +1 -0
  23. package/dist/theme/createTheme.d.ts.map +1 -1
  24. package/dist/theme/createTheme.js +2 -1
  25. package/dist/theme/createTheme.js.map +1 -1
  26. package/dist/theme/defaultTokens.d.ts +1 -1
  27. package/dist/theme/defaultTokens.d.ts.map +1 -1
  28. package/dist/theme/defaultTokens.js.map +1 -1
  29. package/dist/theme/index.d.ts +1 -4
  30. package/dist/theme/index.d.ts.map +1 -1
  31. package/dist/theme/index.js +1 -4
  32. package/dist/theme/index.js.map +1 -1
  33. package/dist/theme/public.d.ts +5 -0
  34. package/dist/theme/public.d.ts.map +1 -0
  35. package/dist/theme/public.js +5 -0
  36. package/dist/theme/public.js.map +1 -0
  37. package/dist/theme/resolveGlobalTokens.d.ts +1 -1
  38. package/dist/theme/resolveGlobalTokens.d.ts.map +1 -1
  39. package/dist/theme/resolveGlobalTokens.js.map +1 -1
  40. package/dist/theme/resolveRoleSemantics.d.ts +19 -0
  41. package/dist/theme/resolveRoleSemantics.d.ts.map +1 -0
  42. package/dist/theme/resolveRoleSemantics.js +29 -0
  43. package/dist/theme/resolveRoleSemantics.js.map +1 -0
  44. package/dist/theme/resolveSurfaceSemanticModel.d.ts +16 -0
  45. package/dist/theme/resolveSurfaceSemanticModel.d.ts.map +1 -0
  46. package/dist/theme/resolveSurfaceSemanticModel.js +128 -0
  47. package/dist/theme/resolveSurfaceSemanticModel.js.map +1 -0
  48. package/dist/theme/surfaceColorPolicy.d.ts +8 -0
  49. package/dist/theme/surfaceColorPolicy.d.ts.map +1 -0
  50. package/dist/theme/surfaceColorPolicy.js +8 -0
  51. package/dist/theme/surfaceColorPolicy.js.map +1 -0
  52. package/dist/theme/types.d.ts +52 -1
  53. package/dist/theme/types.d.ts.map +1 -1
  54. package/dist/theme/types.js.map +1 -1
  55. package/package.json +9 -1
  56. package/src/components/navigation/helpers.test.ts +4 -4
  57. package/src/components/navigation/helpers.ts +17 -8
  58. package/src/index.test.ts +20 -0
  59. package/src/internal/resolvers/resolveInteractiveColors.test.ts +1 -0
  60. package/src/internal/resolvers/resolveInteractiveColors.ts +19 -13
  61. package/src/internal/resolvers/resolveSelectionControlColors.test.ts +3 -2
  62. package/src/internal/resolvers/resolveSelectionControlColors.ts +19 -10
  63. package/src/primitives/icon/PortableIcon.web.test.tsx +2 -7
  64. package/src/surfaceColor.test.ts +8 -10
  65. package/src/theme/colorEngine.test.ts +85 -34
  66. package/src/theme/colorEngine.ts +28 -147
  67. package/src/theme/createSurfaceColorDiagnosticCollector.ts +91 -0
  68. package/src/theme/createSurfaceSemanticPolicy.ts +174 -0
  69. package/src/theme/createTheme.ts +2 -1
  70. package/src/theme/defaultTokens.ts +4 -1
  71. package/src/theme/index.ts +1 -4
  72. package/src/theme/public.test.ts +43 -0
  73. package/src/theme/public.ts +4 -0
  74. package/src/theme/resolveGlobalTokens.ts +4 -1
  75. package/src/theme/resolveRoleSemantics.ts +96 -0
  76. package/src/theme/resolveSurfaceSemanticModel.ts +230 -0
  77. package/src/theme/surfaceColorPolicy.ts +7 -0
  78. package/src/theme/types.ts +65 -1
@@ -1,4 +1,4 @@
1
- import type { GeneratedThemeSwatches } from '@ankhorage/color-theory';
1
+ import type { ColorSwatchDiagnostics, ColorSwatchSelectionResult, DefaultSemanticStatusRole, GeneratedThemeModeColors, GeneratedThemeSwatches, HexColor, SemanticColorReferenceMap, ThemeColorMode } from '@ankhorage/color-theory';
2
2
  import type { ThemeConfig as ContractsThemeConfig } from '@ankhorage/contracts';
3
3
  export type { ColorHarmony, ColorSwatch, ColorSwatchStep, GeneratedColorRole, GeneratedThemeModeColors, GeneratedThemeSwatches, HexColor, SemanticColorReferenceMap, SemanticColorToken, } from '@ankhorage/color-theory';
4
4
  export type { ThemeConfig, ThemeModeConfig } from '@ankhorage/contracts';
@@ -14,6 +14,8 @@ export interface NeutralSemantics {
14
14
  text: string;
15
15
  textMuted: string;
16
16
  textSubtle: string;
17
+ disabledBg: string;
18
+ disabledText: string;
17
19
  }
18
20
  export interface RoleSemantics {
19
21
  base: string;
@@ -23,25 +25,48 @@ export interface RoleSemantics {
23
25
  softHover: string;
24
26
  softActive: string;
25
27
  outline: string;
28
+ onSurfaceText: string;
26
29
  onSolidText: string;
30
+ onHoverText: string;
31
+ onStrongText: string;
32
+ onSoftText: string;
33
+ onSoftHoverText: string;
34
+ onSoftActiveText: string;
35
+ disabledBg: string;
36
+ onDisabledText: string;
27
37
  }
28
38
  export interface SurfaceSemantics {
29
39
  default: string;
30
40
  subtle: string;
31
41
  raised: string;
42
+ sunken: string;
43
+ overlay: string;
44
+ scrim: string;
45
+ disabled: string;
32
46
  inverse: string;
33
47
  }
34
48
  export interface ContentSemantics {
35
49
  default: string;
36
50
  muted: string;
37
51
  subtle: string;
52
+ disabled: string;
53
+ icon: string;
54
+ link: string;
55
+ visited: string;
38
56
  inverse: string;
39
57
  }
40
58
  export interface BorderSemantics {
41
59
  default: string;
60
+ subtle: string;
42
61
  strong: string;
62
+ divider: string;
43
63
  focus: string;
44
64
  }
65
+ export interface SelectionSemantics {
66
+ background: string;
67
+ content: string;
68
+ border: string;
69
+ }
45
70
  export interface ActionSemantics {
46
71
  primary: RoleSemantics;
47
72
  neutral: RoleSemantics;
@@ -61,8 +86,32 @@ export interface ThemeSemantics {
61
86
  surface: SurfaceSemantics;
62
87
  content: ContentSemantics;
63
88
  border: BorderSemantics;
89
+ selection: SelectionSemantics;
64
90
  action: ActionSemantics;
65
91
  }
92
+ export interface SurfaceContrastDiagnostic {
93
+ id: string;
94
+ foreground: HexColor;
95
+ background: HexColor;
96
+ contrast: number;
97
+ minimumContrast: number;
98
+ passes: boolean;
99
+ }
100
+ /** A Color Theory swatch selection together with its stable Surface purpose. */
101
+ export interface SurfaceColorSelectionDiagnostic {
102
+ id: string;
103
+ result: ColorSwatchSelectionResult;
104
+ }
105
+ /** Owner-produced color evidence used to explain a resolved Surface theme. */
106
+ export interface SurfaceColorDiagnostics {
107
+ mode: ThemeColorMode;
108
+ generated: GeneratedThemeModeColors;
109
+ semanticReferences: SemanticColorReferenceMap;
110
+ statusSwatches: Record<DefaultSemanticStatusRole, ColorSwatchDiagnostics>;
111
+ selections: readonly SurfaceColorSelectionDiagnostic[];
112
+ contrasts: readonly SurfaceContrastDiagnostic[];
113
+ surfaceSeparation: readonly SurfaceContrastDiagnostic[];
114
+ }
66
115
  export type FontWeight = '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | 'bold' | 'normal';
67
116
  export interface ThemeTokens {
68
117
  colors: {
@@ -85,6 +134,8 @@ export interface ThemeTokens {
85
134
  };
86
135
  swatches: GeneratedThemeSwatches;
87
136
  semantics: ThemeSemantics;
137
+ /** Generated owner evidence; this is runtime output and is not persisted in ThemeConfig. */
138
+ colorDiagnostics: SurfaceColorDiagnostics;
88
139
  spacing: {
89
140
  none: 0;
90
141
  xs: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { GeneratedThemeSwatches } from '@ankhorage/color-theory';\nimport type { ThemeConfig as ContractsThemeConfig } from '@ankhorage/contracts';\n\nexport type {\n ColorHarmony,\n ColorSwatch,\n ColorSwatchStep,\n GeneratedColorRole,\n GeneratedThemeModeColors,\n GeneratedThemeSwatches,\n HexColor,\n SemanticColorReferenceMap,\n SemanticColorToken,\n} from '@ankhorage/color-theory';\nexport type { ThemeConfig, ThemeModeConfig } from '@ankhorage/contracts';\n\nexport interface NeutralSemantics {\n bg: string;\n bgSubtle: string;\n surface: string;\n surfaceHover: string;\n surfaceActive: string;\n border: string;\n borderStrong: string;\n divider: string;\n text: string;\n textMuted: string;\n textSubtle: string;\n}\n\nexport interface RoleSemantics {\n base: string;\n hover: string;\n strong: string;\n softBg: string;\n softHover: string;\n softActive: string;\n outline: string;\n onSolidText: string;\n}\n\nexport interface SurfaceSemantics {\n default: string;\n subtle: string;\n raised: string;\n inverse: string;\n}\n\nexport interface ContentSemantics {\n default: string;\n muted: string;\n subtle: string;\n inverse: string;\n}\n\nexport interface BorderSemantics {\n default: string;\n strong: string;\n focus: string;\n}\n\nexport interface ActionSemantics {\n primary: RoleSemantics;\n neutral: RoleSemantics;\n danger: RoleSemantics;\n}\n\nexport interface ThemeSemantics {\n neutral: NeutralSemantics;\n brand: RoleSemantics;\n secondary: RoleSemantics;\n accent: RoleSemantics;\n highlight: RoleSemantics;\n danger: RoleSemantics;\n success: RoleSemantics;\n warning: RoleSemantics;\n error: RoleSemantics;\n info: RoleSemantics;\n surface: SurfaceSemantics;\n content: ContentSemantics;\n border: BorderSemantics;\n action: ActionSemantics;\n}\n\nexport type FontWeight =\n '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | 'bold' | 'normal';\n\nexport interface ThemeTokens {\n colors: {\n primary: string;\n secondary: string;\n accent: string;\n highlight: string;\n tertiary: string;\n quaternary: string;\n background: string;\n surface: string;\n text: string;\n textSecondary: string;\n border: string;\n error: string;\n success: string;\n warning: string;\n info: string;\n [key: string]: string;\n };\n swatches: GeneratedThemeSwatches;\n semantics: ThemeSemantics;\n spacing: {\n none: 0;\n xs: number;\n s: number;\n m: number;\n l: number;\n xl: number;\n xxl: number;\n [key: string]: number;\n };\n radii: {\n none: 0;\n s: number;\n m: number;\n l: number;\n full: number;\n [key: string]: number;\n };\n typography: {\n headings: Record<\n 1 | 2 | 3 | 4 | 5 | 6,\n {\n size: number;\n lineHeight: number;\n weight: 'regular' | 'medium' | 'semiBold' | 'bold';\n }\n >;\n sizes: {\n xs: number;\n s: number;\n m: number;\n l: number;\n xl: number;\n xxl: number;\n '3xl': number;\n h1: number;\n h2: number;\n h3: number;\n h4: number;\n h5: number;\n h6: number;\n [key: string]: number;\n };\n weights: {\n thin: FontWeight;\n extraLight: FontWeight;\n light: FontWeight;\n regular: FontWeight;\n medium: FontWeight;\n semiBold: FontWeight;\n bold: FontWeight;\n extraBold: FontWeight;\n black: FontWeight;\n };\n fonts: {\n /** Map of weight -> fontFamily name for normal style */\n normal: Record<FontWeight, string | undefined>;\n /** Map of weight -> fontFamily name for italic style */\n italic: Record<FontWeight, string | undefined>;\n };\n };\n shadows: {\n soft: number;\n medium: number;\n hard: number;\n [key: string]: number;\n };\n}\n\nexport interface SurfaceTheme extends ThemeTokens {\n config: ContractsThemeConfig;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ColorSwatchDiagnostics,\n ColorSwatchSelectionResult,\n DefaultSemanticStatusRole,\n GeneratedThemeModeColors,\n GeneratedThemeSwatches,\n HexColor,\n SemanticColorReferenceMap,\n ThemeColorMode,\n} from '@ankhorage/color-theory';\nimport type { ThemeConfig as ContractsThemeConfig } from '@ankhorage/contracts';\n\nexport type {\n ColorHarmony,\n ColorSwatch,\n ColorSwatchStep,\n GeneratedColorRole,\n GeneratedThemeModeColors,\n GeneratedThemeSwatches,\n HexColor,\n SemanticColorReferenceMap,\n SemanticColorToken,\n} from '@ankhorage/color-theory';\nexport type { ThemeConfig, ThemeModeConfig } from '@ankhorage/contracts';\n\nexport interface NeutralSemantics {\n bg: string;\n bgSubtle: string;\n surface: string;\n surfaceHover: string;\n surfaceActive: string;\n border: string;\n borderStrong: string;\n divider: string;\n text: string;\n textMuted: string;\n textSubtle: string;\n disabledBg: string;\n disabledText: string;\n}\n\nexport interface RoleSemantics {\n base: string;\n hover: string;\n strong: string;\n softBg: string;\n softHover: string;\n softActive: string;\n outline: string;\n onSurfaceText: string;\n onSolidText: string;\n onHoverText: string;\n onStrongText: string;\n onSoftText: string;\n onSoftHoverText: string;\n onSoftActiveText: string;\n disabledBg: string;\n onDisabledText: string;\n}\n\nexport interface SurfaceSemantics {\n default: string;\n subtle: string;\n raised: string;\n sunken: string;\n overlay: string;\n scrim: string;\n disabled: string;\n inverse: string;\n}\n\nexport interface ContentSemantics {\n default: string;\n muted: string;\n subtle: string;\n disabled: string;\n icon: string;\n link: string;\n visited: string;\n inverse: string;\n}\n\nexport interface BorderSemantics {\n default: string;\n subtle: string;\n strong: string;\n divider: string;\n focus: string;\n}\n\nexport interface SelectionSemantics {\n background: string;\n content: string;\n border: string;\n}\n\nexport interface ActionSemantics {\n primary: RoleSemantics;\n neutral: RoleSemantics;\n danger: RoleSemantics;\n}\n\nexport interface ThemeSemantics {\n neutral: NeutralSemantics;\n brand: RoleSemantics;\n secondary: RoleSemantics;\n accent: RoleSemantics;\n highlight: RoleSemantics;\n danger: RoleSemantics;\n success: RoleSemantics;\n warning: RoleSemantics;\n error: RoleSemantics;\n info: RoleSemantics;\n surface: SurfaceSemantics;\n content: ContentSemantics;\n border: BorderSemantics;\n selection: SelectionSemantics;\n action: ActionSemantics;\n}\n\nexport interface SurfaceContrastDiagnostic {\n id: string;\n foreground: HexColor;\n background: HexColor;\n contrast: number;\n minimumContrast: number;\n passes: boolean;\n}\n\n/** A Color Theory swatch selection together with its stable Surface purpose. */\nexport interface SurfaceColorSelectionDiagnostic {\n id: string;\n result: ColorSwatchSelectionResult;\n}\n\n/** Owner-produced color evidence used to explain a resolved Surface theme. */\nexport interface SurfaceColorDiagnostics {\n mode: ThemeColorMode;\n generated: GeneratedThemeModeColors;\n semanticReferences: SemanticColorReferenceMap;\n statusSwatches: Record<DefaultSemanticStatusRole, ColorSwatchDiagnostics>;\n selections: readonly SurfaceColorSelectionDiagnostic[];\n contrasts: readonly SurfaceContrastDiagnostic[];\n surfaceSeparation: readonly SurfaceContrastDiagnostic[];\n}\n\nexport type FontWeight =\n '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | 'bold' | 'normal';\n\nexport interface ThemeTokens {\n colors: {\n primary: string;\n secondary: string;\n accent: string;\n highlight: string;\n tertiary: string;\n quaternary: string;\n background: string;\n surface: string;\n text: string;\n textSecondary: string;\n border: string;\n error: string;\n success: string;\n warning: string;\n info: string;\n [key: string]: string;\n };\n swatches: GeneratedThemeSwatches;\n semantics: ThemeSemantics;\n /** Generated owner evidence; this is runtime output and is not persisted in ThemeConfig. */\n colorDiagnostics: SurfaceColorDiagnostics;\n spacing: {\n none: 0;\n xs: number;\n s: number;\n m: number;\n l: number;\n xl: number;\n xxl: number;\n [key: string]: number;\n };\n radii: {\n none: 0;\n s: number;\n m: number;\n l: number;\n full: number;\n [key: string]: number;\n };\n typography: {\n headings: Record<\n 1 | 2 | 3 | 4 | 5 | 6,\n {\n size: number;\n lineHeight: number;\n weight: 'regular' | 'medium' | 'semiBold' | 'bold';\n }\n >;\n sizes: {\n xs: number;\n s: number;\n m: number;\n l: number;\n xl: number;\n xxl: number;\n '3xl': number;\n h1: number;\n h2: number;\n h3: number;\n h4: number;\n h5: number;\n h6: number;\n [key: string]: number;\n };\n weights: {\n thin: FontWeight;\n extraLight: FontWeight;\n light: FontWeight;\n regular: FontWeight;\n medium: FontWeight;\n semiBold: FontWeight;\n bold: FontWeight;\n extraBold: FontWeight;\n black: FontWeight;\n };\n fonts: {\n /** Map of weight -> fontFamily name for normal style */\n normal: Record<FontWeight, string | undefined>;\n /** Map of weight -> fontFamily name for italic style */\n italic: Record<FontWeight, string | undefined>;\n };\n };\n shadows: {\n soft: number;\n medium: number;\n hard: number;\n [key: string]: number;\n };\n}\n\nexport interface SurfaceTheme extends ThemeTokens {\n config: ContractsThemeConfig;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/surface",
3
3
  "type": "module",
4
- "version": "3.1.3",
4
+ "version": "3.3.0",
5
5
  "description": "Stable UI foundation for React Native and React Native Web.",
6
6
  "homepage": "https://github.com/ankhorage/surface#readme",
7
7
  "bugs": {
@@ -56,6 +56,14 @@
56
56
  "import": "./dist/index.js",
57
57
  "default": "./dist/index.js"
58
58
  },
59
+ "./theme": {
60
+ "bun": "./src/theme/public.ts",
61
+ "react-native": "./src/theme/public.ts",
62
+ "browser": "./src/theme/public.ts",
63
+ "types": "./dist/theme/public.d.ts",
64
+ "import": "./dist/theme/public.js",
65
+ "default": "./dist/theme/public.js"
66
+ },
59
67
  "./package.json": "./package.json"
60
68
  },
61
69
  "publishConfig": {
@@ -40,7 +40,7 @@ describe('navigation helpers', () => {
40
40
  });
41
41
 
42
42
  expect(resolved.backgroundColor).toBe(theme.semantics.action.primary.softHover);
43
- expect(resolved.contentColor).toBe(theme.semantics.action.primary.base);
43
+ expect(resolved.contentColor).toBe(theme.semantics.action.primary.onSoftHoverText);
44
44
  expect(resolved.opacity).toBe(1);
45
45
  });
46
46
 
@@ -67,8 +67,8 @@ describe('navigation helpers', () => {
67
67
  state,
68
68
  });
69
69
 
70
- expect(resolved.backgroundColor).toBe(theme.semantics.action.primary.softBg);
71
- expect(resolved.contentColor).toBe(theme.semantics.content.muted);
72
- expect(resolved.opacity).toBe(0.72);
70
+ expect(resolved.backgroundColor).toBe(theme.semantics.surface.disabled);
71
+ expect(resolved.contentColor).toBe(theme.semantics.content.disabled);
72
+ expect(resolved.opacity).toBe(1);
73
73
  });
74
74
  });
@@ -41,12 +41,21 @@ export function resolveNavigationItemPresentation(
41
41
  ): ResolvedNavigationItemPresentation {
42
42
  const interactionState = disabled ? { ...state, hovered: false, pressed: false } : state;
43
43
 
44
- const opacity = disabled ? 0.72 : 1;
45
- const contentColor = disabled
46
- ? theme.semantics.content.muted
47
- : active
48
- ? theme.semantics.action.primary.base
49
- : theme.semantics.content.default;
44
+ if (disabled) {
45
+ return {
46
+ backgroundColor: theme.semantics.surface.disabled,
47
+ contentColor: theme.semantics.content.disabled,
48
+ opacity: 1,
49
+ };
50
+ }
51
+
52
+ const contentColor = active
53
+ ? interactionState.pressed
54
+ ? theme.semantics.action.primary.onSoftActiveText
55
+ : interactionState.hovered
56
+ ? theme.semantics.action.primary.onSoftHoverText
57
+ : theme.semantics.action.primary.onSoftText
58
+ : theme.semantics.content.default;
50
59
 
51
60
  if (active) {
52
61
  return {
@@ -56,7 +65,7 @@ export function resolveNavigationItemPresentation(
56
65
  ? theme.semantics.action.primary.softHover
57
66
  : theme.semantics.action.primary.softBg,
58
67
  contentColor,
59
- opacity,
68
+ opacity: 1,
60
69
  };
61
70
  }
62
71
 
@@ -67,6 +76,6 @@ export function resolveNavigationItemPresentation(
67
76
  ? theme.semantics.neutral.surfaceHover
68
77
  : 'transparent',
69
78
  contentColor,
70
- opacity,
79
+ opacity: 1,
71
80
  };
72
81
  }
package/src/index.test.ts CHANGED
@@ -2,6 +2,9 @@ import { readFileSync } from 'node:fs';
2
2
 
3
3
  import { describe, expect, it } from 'bun:test';
4
4
 
5
+ import type { SelectionSemantics, SurfaceColorDiagnostics } from './index';
6
+ import { createTheme } from './theme/createTheme';
7
+
5
8
  const indexSource = readFileSync(new URL('./index.ts', import.meta.url), 'utf8');
6
9
  const packageJson = JSON.parse(
7
10
  readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
@@ -67,6 +70,14 @@ describe('public package contract', () => {
67
70
  import: './dist/index.js',
68
71
  types: './dist/index.d.ts',
69
72
  },
73
+ './theme': {
74
+ bun: './src/theme/public.ts',
75
+ 'react-native': './src/theme/public.ts',
76
+ browser: './src/theme/public.ts',
77
+ default: './dist/theme/public.js',
78
+ import: './dist/theme/public.js',
79
+ types: './dist/theme/public.d.ts',
80
+ },
70
81
  './package.json': './package.json',
71
82
  });
72
83
  });
@@ -75,4 +86,13 @@ describe('public package contract', () => {
75
86
  expect(packageJson.peerDependencies['react-native']).toBe('0.86.x');
76
87
  expect(packageJson.devDependencies['react-native']).toBe('0.86.3');
77
88
  });
89
+
90
+ it('exports the resolved semantic and diagnostic contracts from the public root', () => {
91
+ const theme = createTheme();
92
+ const selection: SelectionSemantics = theme.semantics.selection;
93
+ const diagnostics: SurfaceColorDiagnostics = theme.colorDiagnostics;
94
+
95
+ expect(selection.background).toBeDefined();
96
+ expect(diagnostics.generated.swatches).toBe(theme.swatches);
97
+ });
78
98
  });
@@ -32,6 +32,7 @@ describe('resolveInteractiveColors', () => {
32
32
  expect(solid.contentColor).toBe(theme.semantics.action.primary.onSolidText);
33
33
  expect(outline.borderColor).toBe(theme.semantics.action.danger.outline);
34
34
  expect(outline.backgroundColor).toBe(theme.semantics.action.danger.softHover);
35
+ expect(outline.contentColor).toBe(theme.semantics.action.danger.onSurfaceText);
35
36
  });
36
37
 
37
38
  it('derives input colors from interaction and validation state', () => {
@@ -27,10 +27,9 @@ export function resolveButtonColors(
27
27
  ): ResolvedInteractiveColors {
28
28
  if (state.disabled) {
29
29
  return {
30
- backgroundColor: theme.semantics.neutral.surface,
31
- borderColor: theme.semantics.neutral.divider,
32
- contentColor: theme.semantics.content.muted,
33
- opacity: 0.72,
30
+ backgroundColor: theme.semantics.surface.disabled,
31
+ borderColor: theme.semantics.border.subtle,
32
+ contentColor: theme.semantics.content.disabled,
34
33
  };
35
34
  }
36
35
 
@@ -45,7 +44,7 @@ export function resolveButtonColors(
45
44
  ? semanticTone.softHover
46
45
  : 'transparent',
47
46
  borderColor: semanticTone.outline,
48
- contentColor: semanticTone.base,
47
+ contentColor: semanticTone.onSurfaceText,
49
48
  };
50
49
  case 'ghost':
51
50
  return {
@@ -55,7 +54,7 @@ export function resolveButtonColors(
55
54
  ? semanticTone.softHover
56
55
  : 'transparent',
57
56
  borderColor: 'transparent',
58
- contentColor: semanticTone.base,
57
+ contentColor: semanticTone.onSurfaceText,
59
58
  };
60
59
  case 'soft':
61
60
  return {
@@ -65,7 +64,11 @@ export function resolveButtonColors(
65
64
  ? semanticTone.softHover
66
65
  : semanticTone.softBg,
67
66
  borderColor: 'transparent',
68
- contentColor: semanticTone.base,
67
+ contentColor: state.pressed
68
+ ? semanticTone.onSoftActiveText
69
+ : state.hovered
70
+ ? semanticTone.onSoftHoverText
71
+ : semanticTone.onSoftText,
69
72
  };
70
73
  case 'solid':
71
74
  default:
@@ -76,7 +79,11 @@ export function resolveButtonColors(
76
79
  ? semanticTone.hover
77
80
  : semanticTone.base,
78
81
  borderColor: semanticTone.base,
79
- contentColor: semanticTone.onSolidText,
82
+ contentColor: state.pressed
83
+ ? semanticTone.onStrongText
84
+ : state.hovered
85
+ ? semanticTone.onHoverText
86
+ : semanticTone.onSolidText,
80
87
  };
81
88
  }
82
89
  }
@@ -87,11 +94,10 @@ export function resolveInputColors(
87
94
  ): ResolvedInteractiveColors & { placeholderColor: string } {
88
95
  if (fieldState.disabled) {
89
96
  return {
90
- backgroundColor: theme.semantics.surface.subtle,
91
- borderColor: theme.semantics.border.default,
92
- contentColor: theme.semantics.content.muted,
93
- placeholderColor: theme.semantics.content.subtle,
94
- opacity: 0.72,
97
+ backgroundColor: theme.semantics.surface.disabled,
98
+ borderColor: theme.semantics.border.subtle,
99
+ contentColor: theme.semantics.content.disabled,
100
+ placeholderColor: theme.semantics.content.disabled,
95
101
  };
96
102
  }
97
103
 
@@ -38,7 +38,8 @@ describe('resolveSelectionControlColors', () => {
38
38
  color: 'danger',
39
39
  });
40
40
 
41
- expect(colors.labelColor).toBe(theme.semantics.content.muted);
42
- expect(colors.opacity).toBe(0.72);
41
+ expect(colors.backgroundColor).toBe(theme.semantics.surface.disabled);
42
+ expect(colors.labelColor).toBe(theme.semantics.content.disabled);
43
+ expect(colors.opacity).toBeUndefined();
43
44
  });
44
45
  });
@@ -35,15 +35,12 @@ export function resolveSelectionControlColors(
35
35
 
36
36
  if (isMuted) {
37
37
  return {
38
- backgroundColor: checked
39
- ? theme.semantics.neutral.surfaceActive
40
- : theme.semantics.surface.subtle,
41
- borderColor: theme.semantics.border.default,
42
- indicatorColor: checked ? theme.semantics.content.muted : 'transparent',
43
- labelColor: theme.semantics.content.muted,
44
- trackColor: checked ? theme.semantics.neutral.surfaceActive : theme.semantics.neutral.border,
38
+ backgroundColor: theme.semantics.surface.disabled,
39
+ borderColor: theme.semantics.border.subtle,
40
+ indicatorColor: checked ? theme.semantics.content.disabled : 'transparent',
41
+ labelColor: theme.semantics.content.disabled,
42
+ trackColor: theme.semantics.surface.disabled,
45
43
  thumbColor: theme.semantics.surface.default,
46
- opacity: 0.72,
47
44
  };
48
45
  }
49
46
 
@@ -71,12 +68,24 @@ export function resolveSelectionControlColors(
71
68
  return {
72
69
  backgroundColor: checked ? checkedBackgroundColor : uncheckedBackgroundColor,
73
70
  borderColor: checked ? semanticTone.base : uncheckedBorderColor,
74
- indicatorColor: checked ? semanticTone.onSolidText : 'transparent',
71
+ indicatorColor: checked
72
+ ? pressed
73
+ ? semanticTone.onStrongText
74
+ : hovered
75
+ ? semanticTone.onHoverText
76
+ : semanticTone.onSolidText
77
+ : 'transparent',
75
78
  labelColor: isInteractiveReadOnly
76
79
  ? theme.semantics.content.muted
77
80
  : theme.semantics.content.default,
78
81
  trackColor: checked ? checkedTrackColor : uncheckedBorderColor,
79
- thumbColor: checked ? semanticTone.onSolidText : theme.semantics.surface.default,
82
+ thumbColor: checked
83
+ ? pressed
84
+ ? semanticTone.onStrongText
85
+ : hovered
86
+ ? semanticTone.onHoverText
87
+ : semanticTone.onSolidText
88
+ : theme.semantics.surface.default,
80
89
  opacity: isInteractiveReadOnly ? 0.88 : 1,
81
90
  };
82
91
  }
@@ -60,13 +60,8 @@ describe('portable icon rendering', () => {
60
60
 
61
61
  describe('portable icon contract', () => {
62
62
  test('publishes the intentional provider inventory', () => {
63
- expect(SUPPORTED_ICON_PROVIDERS).toEqual([
64
- 'Ionicons',
65
- 'FontAwesome',
66
- 'FontAwesome5',
67
- 'FontAwesome6',
68
- 'MaterialDesignIcons',
69
- ]);
63
+ expect(SUPPORTED_ICON_PROVIDERS.length).toBeGreaterThan(0);
64
+ expect(new Set(SUPPORTED_ICON_PROVIDERS).size).toBe(SUPPORTED_ICON_PROVIDERS.length);
70
65
  });
71
66
  });
72
67
 
@@ -20,16 +20,14 @@ describe('surfaceColor public exports', () => {
20
20
  const indexSource = readFileSync(new URL('./index.ts', import.meta.url), 'utf8');
21
21
 
22
22
  expect(indexSource).toContain("from './surfaceColor';");
23
- expect(SURFACE_PALETTE_COLORS).toEqual([
24
- 'primary',
25
- 'secondary',
26
- 'tertiary',
27
- 'quaternary',
28
- 'neutral',
29
- ]);
30
- expect(SURFACE_STATUS_COLORS).toEqual(['success', 'warning', 'error', 'info']);
31
- expect(SURFACE_COLORS).toEqual([...SURFACE_PALETTE_COLORS, ...SURFACE_STATUS_COLORS, 'danger']);
32
- expect(SURFACE_EMPHASES).toEqual(['default', 'muted', 'subtle', 'inverse']);
23
+ expect(SURFACE_PALETTE_COLORS.length).toBeGreaterThan(0);
24
+ expect(SURFACE_STATUS_COLORS.length).toBeGreaterThan(0);
25
+ expect(SURFACE_EMPHASES.length).toBeGreaterThan(0);
26
+ expect(new Set(SURFACE_COLORS).size).toBe(SURFACE_COLORS.length);
27
+ expect(new Set(SURFACE_EMPHASES).size).toBe(SURFACE_EMPHASES.length);
28
+ for (const color of [...SURFACE_PALETTE_COLORS, ...SURFACE_STATUS_COLORS]) {
29
+ expect(SURFACE_COLORS).toContain(color);
30
+ }
33
31
  });
34
32
 
35
33
  it('keeps root-imported type contracts aligned with runtime lists', () => {
@@ -1,6 +1,8 @@
1
1
  import type { SemanticColorToken } from '@ankhorage/color-theory';
2
2
  import {
3
+ COLOR_HARMONIES,
3
4
  DARK_SEMANTIC_COLOR_REFERENCES,
5
+ DEFAULT_SEMANTIC_STATUS_COLOR_SEEDS,
4
6
  LIGHT_SEMANTIC_COLOR_REFERENCES,
5
7
  } from '@ankhorage/color-theory';
6
8
  import type { ThemeConfig } from '@ankhorage/contracts';
@@ -22,23 +24,18 @@ const mockConfig: ThemeConfig = {
22
24
  },
23
25
  };
24
26
 
25
- function expectRequiredSemanticRoles(semantics: ThemeSemantics) {
26
- expect(semantics.neutral.bg).toBeDefined();
27
- expect(semantics.neutral.surface).toBeDefined();
28
- expect(semantics.neutral.text).toBeDefined();
29
- expect(semantics.brand.base).toBeDefined();
30
- expect(semantics.secondary.base).toBeDefined();
31
- expect(semantics.danger.base).toBeDefined();
32
- expect(semantics.success.base).toBeDefined();
33
- expect(semantics.warning.base).toBeDefined();
34
- expect(semantics.error.base).toBeDefined();
35
- expect(semantics.info.base).toBeDefined();
36
- expect(semantics.surface.default).toBeDefined();
37
- expect(semantics.content.default).toBeDefined();
38
- expect(semantics.border.default).toBeDefined();
39
- expect(semantics.action.primary.base).toBeDefined();
40
- expect(semantics.action.neutral.base).toBeDefined();
41
- expect(semantics.action.danger.base).toBeDefined();
27
+ function expectCompleteSemanticValues(semantics: ThemeSemantics) {
28
+ visitSemanticValues(semantics);
29
+ }
30
+
31
+ function visitSemanticValues(value: object): void {
32
+ for (const entry of Object.values(value)) {
33
+ if (typeof entry === 'string') {
34
+ expect(entry.length).toBeGreaterThan(0);
35
+ } else {
36
+ visitSemanticValues(entry as object);
37
+ }
38
+ }
42
39
  }
43
40
 
44
41
  describe('colorEngine', () => {
@@ -74,21 +71,30 @@ describe('colorEngine', () => {
74
71
  expect(semantics.content.inverse).toBe(swatches.neutral[900]);
75
72
  });
76
73
 
77
- it('uses the color-theory default semantic status seeds', () => {
78
- const light = generatePalette(mockConfig, 'light');
79
- const dark = generatePalette(mockConfig, 'dark');
74
+ it('uses the Color Theory status owner independently from brand harmony', () => {
75
+ const statusConfig: ThemeConfig = {
76
+ ...mockConfig,
77
+ light: { primaryColor: '#dc2626', harmony: 'monochromatic' },
78
+ dark: { primaryColor: '#dc2626', harmony: 'monochromatic' },
79
+ };
80
+ const light = generatePalette(statusConfig, 'light');
81
+ const dark = generatePalette(statusConfig, 'dark');
80
82
 
81
83
  expect(light.semantics.danger.base).toBe('#ef4444');
82
84
  expect(light.semantics.success.base).toBe('#22c55e');
83
85
  expect(light.semantics.warning.base).toBe('#f59e0b');
84
86
  expect(light.semantics.error.base).toBe(light.semantics.danger.base);
85
- expect(light.semantics.info.base).toBeDefined();
87
+ expect(light.semantics.info.base).toBe(DEFAULT_SEMANTIC_STATUS_COLOR_SEEDS.info);
88
+ expect(light.semantics.info.base).not.toBe(light.semantics.brand.base);
86
89
 
87
90
  expect(dark.semantics.danger.base).toBe('#ef4444');
88
91
  expect(dark.semantics.success.base).toBe('#22c55e');
89
92
  expect(dark.semantics.warning.base).toBe('#f59e0b');
90
93
  expect(dark.semantics.error.base).toBe(dark.semantics.danger.base);
91
- expect(dark.semantics.info.base).toBeDefined();
94
+ expect(dark.semantics.info.base).toBe(DEFAULT_SEMANTIC_STATUS_COLOR_SEEDS.info);
95
+ expect(dark.semantics.info.base).not.toBe(dark.semantics.brand.base);
96
+ expect(light.semantics.error).toBe(light.semantics.danger);
97
+ expect(dark.semantics.error).toBe(dark.semantics.danger);
92
98
  });
93
99
 
94
100
  it('uses mode-aware role semantics for dark mode soft states', () => {
@@ -136,16 +142,7 @@ describe('colorEngine', () => {
136
142
  });
137
143
 
138
144
  it('emits required semantic roles for all harmonies', () => {
139
- const harmonies = [
140
- 'monochromatic',
141
- 'analogous',
142
- 'complementary',
143
- 'triadic',
144
- 'tetradic',
145
- 'splitComplementary',
146
- ] as const;
147
-
148
- for (const harmony of harmonies) {
145
+ for (const harmony of COLOR_HARMONIES) {
149
146
  const config = {
150
147
  ...mockConfig,
151
148
  light: { ...mockConfig.light, harmony },
@@ -153,11 +150,65 @@ describe('colorEngine', () => {
153
150
  };
154
151
  const light = generatePalette(config, 'light');
155
152
  const dark = generatePalette(config, 'dark');
156
- expectRequiredSemanticRoles(light.semantics);
157
- expectRequiredSemanticRoles(dark.semantics);
153
+ expectCompleteSemanticValues(light.semantics);
154
+ expectCompleteSemanticValues(dark.semantics);
155
+ }
156
+ });
157
+
158
+ it('resolves canonical semantic references and returns their selected steps', () => {
159
+ const light = generatePalette(mockConfig, 'light');
160
+ const dark = generatePalette(mockConfig, 'dark');
161
+
162
+ expect(light.colorDiagnostics.semanticReferences).toBe(LIGHT_SEMANTIC_COLOR_REFERENCES);
163
+ expect(dark.colorDiagnostics.semanticReferences).toBe(DARK_SEMANTIC_COLOR_REFERENCES);
164
+ expect(light.colorDiagnostics.generated.swatches).toBe(light.swatches);
165
+ expect(dark.colorDiagnostics.generated.swatches).toBe(dark.swatches);
166
+ });
167
+
168
+ it('returns measured selections and contrast diagnostics for both modes', () => {
169
+ for (const mode of ['light', 'dark'] as const) {
170
+ const palette = generatePalette(mockConfig, mode);
171
+
172
+ expect(palette.colorDiagnostics.selections.length).toBeGreaterThan(0);
173
+ expect(
174
+ palette.colorDiagnostics.selections.every(({ result }) => result.selected !== null),
175
+ ).toBe(true);
176
+ expect(palette.colorDiagnostics.contrasts.length).toBeGreaterThan(0);
177
+ expect(palette.colorDiagnostics.contrasts.every(({ passes }) => passes)).toBe(true);
178
+ expect(palette.colorDiagnostics.surfaceSeparation.length).toBeGreaterThan(0);
158
179
  }
159
180
  });
160
181
 
182
+ it('reports neutral and surface-separation evidence without hiding failed separation', () => {
183
+ const palette = generatePalette(mockConfig, 'light');
184
+ const failedSeparation = palette.colorDiagnostics.surfaceSeparation.find(
185
+ ({ passes }) => !passes,
186
+ );
187
+
188
+ expect(palette.colorDiagnostics.generated.neutral.neutralKeyColor).toBe(
189
+ palette.swatches.neutral[500],
190
+ );
191
+ expect(palette.colorDiagnostics.generated.neutral.diagnostics.isUsable).toBeBoolean();
192
+ expect(failedSeparation).toBeDefined();
193
+ expect(failedSeparation?.contrast).toBeLessThan(failedSeparation?.minimumContrast ?? 0);
194
+ });
195
+
196
+ it('preserves owner diagnostics for achromatic and low-chroma inputs', () => {
197
+ const achromaticConfig: ThemeConfig = {
198
+ ...mockConfig,
199
+ light: { primaryColor: '#777777', harmony: 'square' },
200
+ };
201
+ const palette = generatePalette(achromaticConfig, 'light');
202
+
203
+ expect(palette.colorDiagnostics.generated.harmonyRoleColors.diagnostics.isHueReliable).toBe(
204
+ false,
205
+ );
206
+ expect(
207
+ palette.colorDiagnostics.generated.harmonyRoleColors.diagnostics.warnings.length,
208
+ ).toBeGreaterThan(0);
209
+ expect(palette.colorDiagnostics.generated.neutral.diagnostics).toBeDefined();
210
+ });
211
+
161
212
  it('generates ordinal chromatic role swatches (no accent/highlight as swatch keys)', () => {
162
213
  const { swatches } = generatePalette(mockConfig, 'light');
163
214
  const keys = Object.keys(swatches);