@ankhorage/surface 3.1.3 → 3.2.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 (67) hide show
  1. package/CHANGELOG.md +6 -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/resolveGlobalTokens.d.ts +1 -1
  30. package/dist/theme/resolveGlobalTokens.d.ts.map +1 -1
  31. package/dist/theme/resolveGlobalTokens.js.map +1 -1
  32. package/dist/theme/resolveRoleSemantics.d.ts +19 -0
  33. package/dist/theme/resolveRoleSemantics.d.ts.map +1 -0
  34. package/dist/theme/resolveRoleSemantics.js +29 -0
  35. package/dist/theme/resolveRoleSemantics.js.map +1 -0
  36. package/dist/theme/resolveSurfaceSemanticModel.d.ts +16 -0
  37. package/dist/theme/resolveSurfaceSemanticModel.d.ts.map +1 -0
  38. package/dist/theme/resolveSurfaceSemanticModel.js +128 -0
  39. package/dist/theme/resolveSurfaceSemanticModel.js.map +1 -0
  40. package/dist/theme/surfaceColorPolicy.d.ts +8 -0
  41. package/dist/theme/surfaceColorPolicy.d.ts.map +1 -0
  42. package/dist/theme/surfaceColorPolicy.js +8 -0
  43. package/dist/theme/surfaceColorPolicy.js.map +1 -0
  44. package/dist/theme/types.d.ts +52 -1
  45. package/dist/theme/types.d.ts.map +1 -1
  46. package/dist/theme/types.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/components/navigation/helpers.test.ts +4 -4
  49. package/src/components/navigation/helpers.ts +17 -8
  50. package/src/index.test.ts +12 -0
  51. package/src/internal/resolvers/resolveInteractiveColors.test.ts +1 -0
  52. package/src/internal/resolvers/resolveInteractiveColors.ts +19 -13
  53. package/src/internal/resolvers/resolveSelectionControlColors.test.ts +3 -2
  54. package/src/internal/resolvers/resolveSelectionControlColors.ts +19 -10
  55. package/src/primitives/icon/PortableIcon.web.test.tsx +2 -7
  56. package/src/surfaceColor.test.ts +8 -10
  57. package/src/theme/colorEngine.test.ts +85 -34
  58. package/src/theme/colorEngine.ts +28 -147
  59. package/src/theme/createSurfaceColorDiagnosticCollector.ts +91 -0
  60. package/src/theme/createSurfaceSemanticPolicy.ts +174 -0
  61. package/src/theme/createTheme.ts +2 -1
  62. package/src/theme/defaultTokens.ts +4 -1
  63. package/src/theme/resolveGlobalTokens.ts +4 -1
  64. package/src/theme/resolveRoleSemantics.ts +96 -0
  65. package/src/theme/resolveSurfaceSemanticModel.ts +230 -0
  66. package/src/theme/surfaceColorPolicy.ts +7 -0
  67. package/src/theme/types.ts +65 -1
@@ -0,0 +1,230 @@
1
+ import type {
2
+ ColorSelectionTarget,
3
+ ColorSwatch,
4
+ GeneratedThemeModeColors,
5
+ GeneratedThemeSwatches,
6
+ HexColor,
7
+ SemanticColorReferenceMap,
8
+ SemanticColorToken,
9
+ ThemeColorMode,
10
+ } from '@ankhorage/color-theory';
11
+ import { createDefaultSemanticStatusSwatches } from '@ankhorage/color-theory';
12
+
13
+ import { createSurfaceColorDiagnosticCollector } from './createSurfaceColorDiagnosticCollector';
14
+ import { createSurfaceSemanticPolicy } from './createSurfaceSemanticPolicy';
15
+ import { resolveRoleSemantics } from './resolveRoleSemantics';
16
+ import type {
17
+ BorderSemantics,
18
+ ContentSemantics,
19
+ RoleSemantics,
20
+ SurfaceColorDiagnostics,
21
+ ThemeSemantics,
22
+ } from './types';
23
+
24
+ type ResolvedSemanticColors = Record<SemanticColorToken, HexColor>;
25
+
26
+ interface SemanticResolutionContext {
27
+ collector: ReturnType<typeof createSurfaceColorDiagnosticCollector>;
28
+ generated: GeneratedThemeModeColors;
29
+ isDark: boolean;
30
+ mode: ThemeColorMode;
31
+ neutralSwatch: ColorSwatch;
32
+ policy: ReturnType<typeof createSurfaceSemanticPolicy>;
33
+ quaternarySwatch: ColorSwatch;
34
+ references: SemanticColorReferenceMap;
35
+ resolved: ResolvedSemanticColors;
36
+ secondarySwatch: ColorSwatch;
37
+ statusSwatches: ReturnType<typeof createDefaultSemanticStatusSwatches>;
38
+ swatches: GeneratedThemeSwatches;
39
+ tertiarySwatch: ColorSwatch;
40
+ }
41
+
42
+ interface ResolveSurfaceSemanticModelInput {
43
+ generated: GeneratedThemeModeColors;
44
+ mode: ThemeColorMode;
45
+ references: SemanticColorReferenceMap;
46
+ resolved: ResolvedSemanticColors;
47
+ }
48
+
49
+ /** Resolve the complete Surface-owned semantic model from canonical Color Theory output. */
50
+ export function resolveSurfaceSemanticModel(input: ResolveSurfaceSemanticModelInput): {
51
+ semantics: ThemeSemantics;
52
+ colorDiagnostics: SurfaceColorDiagnostics;
53
+ } {
54
+ const context = createResolutionContext(input);
55
+ const roles = resolveSemanticRoles(context);
56
+ const inverseSurface = context.isDark ? context.neutralSwatch[50] : context.neutralSwatch[900];
57
+ const inverseContent = context.isDark ? context.neutralSwatch[900] : context.neutralSwatch[50];
58
+ const neutral = context.policy.buildNeutral(context.neutralSwatch);
59
+ const surface = context.policy.buildSurface(inverseSurface);
60
+ const content = resolveContentSemantics(context, neutral.textSubtle, inverseContent);
61
+ const border = resolveBorderSemantics(context, roles.brand.outline);
62
+ context.policy.measureContent(content, surface);
63
+
64
+ return {
65
+ semantics: {
66
+ neutral,
67
+ brand: roles.brand,
68
+ secondary: roles.secondary,
69
+ accent: roles.accent,
70
+ highlight: roles.highlight,
71
+ danger: roles.danger,
72
+ success: roles.success,
73
+ warning: roles.warning,
74
+ error: roles.danger,
75
+ info: roles.info,
76
+ surface,
77
+ content,
78
+ border,
79
+ selection: {
80
+ background: roles.brand.softBg,
81
+ content: roles.brand.onSoftText,
82
+ border: border.focus,
83
+ },
84
+ action: { primary: roles.brand, neutral: roles.neutralAction, danger: roles.danger },
85
+ },
86
+ colorDiagnostics: {
87
+ mode: context.mode,
88
+ generated: context.generated,
89
+ semanticReferences: context.references,
90
+ statusSwatches: context.statusSwatches.diagnostics,
91
+ selections: context.collector.selections,
92
+ contrasts: context.collector.contrasts,
93
+ surfaceSeparation: context.policy.measureSurfaceSeparation(surface),
94
+ },
95
+ };
96
+ }
97
+
98
+ function createResolutionContext(
99
+ input: ResolveSurfaceSemanticModelInput,
100
+ ): SemanticResolutionContext {
101
+ const { generated, mode, references, resolved } = input;
102
+ const { swatches } = generated;
103
+ const collector = createSurfaceColorDiagnosticCollector();
104
+ const isDark = mode === 'dark';
105
+ return {
106
+ collector,
107
+ generated,
108
+ isDark,
109
+ mode,
110
+ neutralSwatch: swatches.neutral,
111
+ policy: createSurfaceSemanticPolicy(resolved, isDark, collector),
112
+ quaternarySwatch: swatches.quaternary ?? swatches.primary,
113
+ references,
114
+ resolved,
115
+ secondarySwatch: swatches.secondary ?? swatches.primary,
116
+ statusSwatches: createDefaultSemanticStatusSwatches(),
117
+ swatches,
118
+ tertiarySwatch: swatches.tertiary ?? swatches.primary,
119
+ };
120
+ }
121
+
122
+ function resolveBorderSemantics(
123
+ context: SemanticResolutionContext,
124
+ focus: string,
125
+ ): BorderSemantics {
126
+ const { collector, neutralSwatch, policy, resolved } = context;
127
+ return {
128
+ default: collector.selectColor(
129
+ 'border.default',
130
+ neutralSwatch,
131
+ policy.neutralTarget(),
132
+ policy.adjacentSurfaceContexts,
133
+ policy.tiePolicy,
134
+ ),
135
+ subtle: resolved.border,
136
+ strong: collector.selectColor(
137
+ 'border.strong',
138
+ neutralSwatch,
139
+ policy.strongNeutralTarget(),
140
+ policy.adjacentSurfaceContexts,
141
+ policy.tiePolicy,
142
+ ),
143
+ divider: resolved.divider,
144
+ focus,
145
+ };
146
+ }
147
+
148
+ function resolveContentSemantics(
149
+ context: SemanticResolutionContext,
150
+ subtle: string,
151
+ inverse: string,
152
+ ): ContentSemantics {
153
+ const { collector, policy, resolved, secondarySwatch, swatches } = context;
154
+ return {
155
+ default: resolved.text,
156
+ muted: resolved.textMuted,
157
+ subtle,
158
+ disabled: resolved.disabledText,
159
+ icon: resolved.text,
160
+ link: collector.selectColor(
161
+ 'content.link',
162
+ swatches.primary,
163
+ policy.textTarget(),
164
+ policy.textSurfaceContexts,
165
+ policy.tiePolicy,
166
+ ),
167
+ visited: collector.selectColor(
168
+ 'content.visited',
169
+ secondarySwatch,
170
+ policy.textTarget(),
171
+ policy.textSurfaceContexts,
172
+ policy.tiePolicy,
173
+ ),
174
+ inverse,
175
+ };
176
+ }
177
+
178
+ function resolveRole(
179
+ context: SemanticResolutionContext,
180
+ id: string,
181
+ swatch: ColorSwatch,
182
+ options: { base?: HexColor; hover?: HexColor; target?: ColorSelectionTarget } = {},
183
+ ): RoleSemantics {
184
+ const { collector, isDark, policy, resolved } = context;
185
+ return resolveRoleSemantics({
186
+ base: options.base,
187
+ disabledBg: resolved.disabledBg,
188
+ disabledText: resolved.disabledText,
189
+ hover: options.hover,
190
+ id,
191
+ isDark,
192
+ measureForeground: collector.measureForeground,
193
+ onSurfaceText: collector.selectColor(
194
+ `${id}.content`,
195
+ swatch,
196
+ policy.textTarget(),
197
+ policy.textSurfaceContexts,
198
+ policy.tiePolicy,
199
+ ),
200
+ outline: collector.selectColor(
201
+ `${id}.outline`,
202
+ swatch,
203
+ options.target ?? policy.roleTarget(),
204
+ policy.adjacentSurfaceContexts,
205
+ policy.tiePolicy,
206
+ ),
207
+ selectForeground: collector.selectForeground,
208
+ swatch,
209
+ });
210
+ }
211
+
212
+ function resolveSemanticRoles(context: SemanticResolutionContext) {
213
+ const { neutralSwatch, resolved, secondarySwatch, statusSwatches, swatches } = context;
214
+ return {
215
+ brand: resolveRole(context, 'brand', swatches.primary, {
216
+ base: resolved.action,
217
+ hover: resolved.actionEmphasis,
218
+ }),
219
+ secondary: resolveRole(context, 'secondary', secondarySwatch),
220
+ accent: resolveRole(context, 'accent', context.tertiarySwatch),
221
+ highlight: resolveRole(context, 'highlight', context.quaternarySwatch),
222
+ danger: resolveRole(context, 'danger', statusSwatches.swatches.danger),
223
+ success: resolveRole(context, 'success', statusSwatches.swatches.success),
224
+ warning: resolveRole(context, 'warning', statusSwatches.swatches.warning),
225
+ info: resolveRole(context, 'info', statusSwatches.swatches.info),
226
+ neutralAction: resolveRole(context, 'neutral', neutralSwatch, {
227
+ target: context.policy.neutralTarget(),
228
+ }),
229
+ };
230
+ }
@@ -0,0 +1,7 @@
1
+ /** Canonical measurable thresholds used while Surface resolves runtime color semantics. */
2
+ export const SURFACE_COLOR_POLICY = {
3
+ disabledContrast: 3,
4
+ surfaceSeparation: 1.05,
5
+ textContrast: 4.5,
6
+ uiContrast: 3,
7
+ } as const;
@@ -1,4 +1,13 @@
1
- import type { GeneratedThemeSwatches } from '@ankhorage/color-theory';
1
+ import type {
2
+ ColorSwatchDiagnostics,
3
+ ColorSwatchSelectionResult,
4
+ DefaultSemanticStatusRole,
5
+ GeneratedThemeModeColors,
6
+ GeneratedThemeSwatches,
7
+ HexColor,
8
+ SemanticColorReferenceMap,
9
+ ThemeColorMode,
10
+ } from '@ankhorage/color-theory';
2
11
  import type { ThemeConfig as ContractsThemeConfig } from '@ankhorage/contracts';
3
12
 
4
13
  export type {
@@ -26,6 +35,8 @@ export interface NeutralSemantics {
26
35
  text: string;
27
36
  textMuted: string;
28
37
  textSubtle: string;
38
+ disabledBg: string;
39
+ disabledText: string;
29
40
  }
30
41
 
31
42
  export interface RoleSemantics {
@@ -36,13 +47,25 @@ export interface RoleSemantics {
36
47
  softHover: string;
37
48
  softActive: string;
38
49
  outline: string;
50
+ onSurfaceText: string;
39
51
  onSolidText: string;
52
+ onHoverText: string;
53
+ onStrongText: string;
54
+ onSoftText: string;
55
+ onSoftHoverText: string;
56
+ onSoftActiveText: string;
57
+ disabledBg: string;
58
+ onDisabledText: string;
40
59
  }
41
60
 
42
61
  export interface SurfaceSemantics {
43
62
  default: string;
44
63
  subtle: string;
45
64
  raised: string;
65
+ sunken: string;
66
+ overlay: string;
67
+ scrim: string;
68
+ disabled: string;
46
69
  inverse: string;
47
70
  }
48
71
 
@@ -50,15 +73,27 @@ export interface ContentSemantics {
50
73
  default: string;
51
74
  muted: string;
52
75
  subtle: string;
76
+ disabled: string;
77
+ icon: string;
78
+ link: string;
79
+ visited: string;
53
80
  inverse: string;
54
81
  }
55
82
 
56
83
  export interface BorderSemantics {
57
84
  default: string;
85
+ subtle: string;
58
86
  strong: string;
87
+ divider: string;
59
88
  focus: string;
60
89
  }
61
90
 
91
+ export interface SelectionSemantics {
92
+ background: string;
93
+ content: string;
94
+ border: string;
95
+ }
96
+
62
97
  export interface ActionSemantics {
63
98
  primary: RoleSemantics;
64
99
  neutral: RoleSemantics;
@@ -79,9 +114,36 @@ export interface ThemeSemantics {
79
114
  surface: SurfaceSemantics;
80
115
  content: ContentSemantics;
81
116
  border: BorderSemantics;
117
+ selection: SelectionSemantics;
82
118
  action: ActionSemantics;
83
119
  }
84
120
 
121
+ export interface SurfaceContrastDiagnostic {
122
+ id: string;
123
+ foreground: HexColor;
124
+ background: HexColor;
125
+ contrast: number;
126
+ minimumContrast: number;
127
+ passes: boolean;
128
+ }
129
+
130
+ /** A Color Theory swatch selection together with its stable Surface purpose. */
131
+ export interface SurfaceColorSelectionDiagnostic {
132
+ id: string;
133
+ result: ColorSwatchSelectionResult;
134
+ }
135
+
136
+ /** Owner-produced color evidence used to explain a resolved Surface theme. */
137
+ export interface SurfaceColorDiagnostics {
138
+ mode: ThemeColorMode;
139
+ generated: GeneratedThemeModeColors;
140
+ semanticReferences: SemanticColorReferenceMap;
141
+ statusSwatches: Record<DefaultSemanticStatusRole, ColorSwatchDiagnostics>;
142
+ selections: readonly SurfaceColorSelectionDiagnostic[];
143
+ contrasts: readonly SurfaceContrastDiagnostic[];
144
+ surfaceSeparation: readonly SurfaceContrastDiagnostic[];
145
+ }
146
+
85
147
  export type FontWeight =
86
148
  '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | 'bold' | 'normal';
87
149
 
@@ -106,6 +168,8 @@ export interface ThemeTokens {
106
168
  };
107
169
  swatches: GeneratedThemeSwatches;
108
170
  semantics: ThemeSemantics;
171
+ /** Generated owner evidence; this is runtime output and is not persisted in ThemeConfig. */
172
+ colorDiagnostics: SurfaceColorDiagnostics;
109
173
  spacing: {
110
174
  none: 0;
111
175
  xs: number;