@clhaas/palette-kit 0.1.8 → 0.4.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 (118) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +80 -177
  3. package/dist/contrast/contrast.d.ts +16 -0
  4. package/dist/contrast/contrast.js +102 -0
  5. package/dist/core/intent-registry.d.ts +11 -0
  6. package/dist/core/intent-registry.js +70 -0
  7. package/dist/core/oklch.d.ts +16 -0
  8. package/dist/core/oklch.js +56 -0
  9. package/dist/create-palette-kit.d.ts +9 -0
  10. package/dist/create-palette-kit.js +67 -0
  11. package/dist/engine/context/context.d.ts +13 -0
  12. package/dist/engine/context/context.js +37 -0
  13. package/dist/engine/level/curves.d.ts +17 -0
  14. package/dist/engine/level/curves.js +49 -0
  15. package/dist/engine/level/level.d.ts +4 -0
  16. package/dist/engine/level/level.js +13 -0
  17. package/dist/engine/relation/relation.d.ts +105 -0
  18. package/dist/engine/relation/relation.js +137 -0
  19. package/dist/engine/resolve/resolve.d.ts +36 -0
  20. package/dist/engine/resolve/resolve.js +116 -0
  21. package/dist/engine/state/state.d.ts +46 -0
  22. package/dist/engine/state/state.js +68 -0
  23. package/dist/engine/usage/fill.d.ts +9 -0
  24. package/dist/engine/usage/fill.js +9 -0
  25. package/dist/engine/usage/lines.d.ts +9 -0
  26. package/dist/engine/usage/lines.js +9 -0
  27. package/dist/engine/usage/overlays.d.ts +9 -0
  28. package/dist/engine/usage/overlays.js +9 -0
  29. package/dist/engine/usage/strategy.d.ts +56 -0
  30. package/dist/engine/usage/strategy.js +30 -0
  31. package/dist/engine/usage/visualVocabulary.d.ts +9 -0
  32. package/dist/engine/usage/visualVocabulary.js +9 -0
  33. package/dist/export/serialize.d.ts +14 -0
  34. package/dist/export/serialize.js +89 -0
  35. package/dist/export/types.d.ts +37 -0
  36. package/dist/export/types.js +31 -0
  37. package/dist/index.d.ts +3 -22
  38. package/dist/index.js +2 -18
  39. package/dist/operators/convert.d.ts +32 -0
  40. package/dist/operators/convert.js +80 -0
  41. package/dist/presets/presets.d.ts +95 -0
  42. package/dist/presets/presets.js +308 -0
  43. package/dist/types/index.d.ts +111 -0
  44. package/dist/utils/errors/errors.d.ts +17 -0
  45. package/dist/utils/errors/errors.js +22 -0
  46. package/docs/API.md +167 -0
  47. package/docs/Alpha.md +14 -0
  48. package/docs/Architecture.md +56 -0
  49. package/docs/CLI.md +22 -0
  50. package/docs/Concepts.md +73 -0
  51. package/docs/Config.md +144 -0
  52. package/docs/Diagnostics.md +22 -0
  53. package/docs/Exporters.md +33 -0
  54. package/docs/FAQ.md +59 -0
  55. package/docs/Migration.md +61 -0
  56. package/docs/Overlays.md +33 -0
  57. package/docs/README.md +60 -0
  58. package/docs/Text.md +41 -0
  59. package/docs/Tokens.md +42 -0
  60. package/docs/Usage-JSON.md +39 -0
  61. package/docs/Usage-ReactNative.md +63 -0
  62. package/docs/Usage-Web.md +66 -0
  63. package/docs/Validation.md +97 -0
  64. package/docs/Why.md +37 -0
  65. package/docs/_api-surface.md +53 -0
  66. package/docs/snippets/serialize-oklch.md +9 -0
  67. package/docs/spec.md +98 -0
  68. package/package.json +74 -52
  69. package/dist/alpha/generateAlphaScale.d.ts +0 -5
  70. package/dist/alpha/generateAlphaScale.js +0 -34
  71. package/dist/cli.d.ts +0 -2
  72. package/dist/cli.js +0 -150
  73. package/dist/contrast/apca.d.ts +0 -2
  74. package/dist/contrast/apca.js +0 -5
  75. package/dist/contrast/onSolid.d.ts +0 -6
  76. package/dist/contrast/onSolid.js +0 -28
  77. package/dist/contrast/solveText.d.ts +0 -2
  78. package/dist/contrast/solveText.js +0 -31
  79. package/dist/createTheme.d.ts +0 -38
  80. package/dist/createTheme.js +0 -148
  81. package/dist/data/radixSeeds.d.ts +0 -3
  82. package/dist/data/radixSeeds.js +0 -34
  83. package/dist/diagnostics/analyzeScale.d.ts +0 -2
  84. package/dist/diagnostics/analyzeScale.js +0 -7
  85. package/dist/diagnostics/analyzeTheme.d.ts +0 -2
  86. package/dist/diagnostics/analyzeTheme.js +0 -35
  87. package/dist/diagnostics/warnings.d.ts +0 -2
  88. package/dist/diagnostics/warnings.js +0 -20
  89. package/dist/engine/curves.d.ts +0 -9
  90. package/dist/engine/curves.js +0 -48
  91. package/dist/engine/oklch.d.ts +0 -8
  92. package/dist/engine/oklch.js +0 -40
  93. package/dist/engine/templates.d.ts +0 -14
  94. package/dist/engine/templates.js +0 -45
  95. package/dist/exporters/selectColorMode.d.ts +0 -2
  96. package/dist/exporters/selectColorMode.js +0 -19
  97. package/dist/exporters/toCssVars.d.ts +0 -13
  98. package/dist/exporters/toCssVars.js +0 -108
  99. package/dist/exporters/toJson.d.ts +0 -3
  100. package/dist/exporters/toJson.js +0 -25
  101. package/dist/exporters/toReactNative.d.ts +0 -54
  102. package/dist/exporters/toReactNative.js +0 -33
  103. package/dist/exporters/toTailwind.d.ts +0 -17
  104. package/dist/exporters/toTailwind.js +0 -111
  105. package/dist/exporters/toTs.d.ts +0 -3
  106. package/dist/exporters/toTs.js +0 -43
  107. package/dist/generateScale.d.ts +0 -48
  108. package/dist/generateScale.js +0 -274
  109. package/dist/overlays/generateOverlayScale.d.ts +0 -2
  110. package/dist/overlays/generateOverlayScale.js +0 -34
  111. package/dist/text/generateTextScale.d.ts +0 -8
  112. package/dist/text/generateTextScale.js +0 -18
  113. package/dist/text/resolveOnBgText.d.ts +0 -9
  114. package/dist/text/resolveOnBgText.js +0 -28
  115. package/dist/tokens/presetRadixLikeUi.d.ts +0 -5
  116. package/dist/tokens/presetRadixLikeUi.js +0 -55
  117. package/dist/types.d.ts +0 -69
  118. /package/dist/{types.js → types/index.js} +0 -0
@@ -0,0 +1,67 @@
1
+ import { createIntentRegistry, } from './core/intent-registry.js';
2
+ import { assertContext } from './engine/context/context.js';
3
+ import { resolveColor } from './engine/resolve/resolve.js';
4
+ import { serializeColor } from './export/serialize.js';
5
+ import { assertColorOutput, resolveOutput, } from './export/types.js';
6
+ import { defaultResolverConfig, getResolverPresetConfig, mergeResolverConfig, } from './presets/presets.js';
7
+ function resolveSerializedOutput(color, output) {
8
+ if (output === 'oklch') {
9
+ return color;
10
+ }
11
+ return serializeColor(color, output);
12
+ }
13
+ function validateOptionalContext(context) {
14
+ if (context !== undefined) {
15
+ assertContext(context);
16
+ }
17
+ }
18
+ function validateOptionalOutput(output) {
19
+ if (output !== undefined) {
20
+ assertColorOutput(output);
21
+ }
22
+ }
23
+ function createResolveFunction(intentRegistry, paletteContext, systemDefaultContext, paletteOutput, systemDefaultOutput, resolverConfig) {
24
+ return (options) => {
25
+ const output = resolveOutput({
26
+ paletteOutput,
27
+ resolverOutput: options.output,
28
+ systemDefaultOutput,
29
+ });
30
+ const resolved = resolveColor({
31
+ intent: options.intent,
32
+ intentRegistry,
33
+ level: options.level,
34
+ on: options.on,
35
+ over: options.over,
36
+ paletteContext,
37
+ resolverConfig,
38
+ resolverContext: options.context,
39
+ state: options.state,
40
+ stateDirection: options.stateDirection,
41
+ systemDefaultContext,
42
+ under: options.under,
43
+ usage: options.usage,
44
+ });
45
+ return resolveSerializedOutput(resolved.color, output);
46
+ };
47
+ }
48
+ /**
49
+ * Creates an immutable Palette Kit resolver instance.
50
+ *
51
+ * The factory normalizes the provided intent registry once, keeps context and
52
+ * output defaults explicit, and never reads ambient platform state.
53
+ */
54
+ export function createPaletteKit(config) {
55
+ validateOptionalContext(config.context);
56
+ validateOptionalContext(config.systemDefaultContext);
57
+ validateOptionalOutput(config.output);
58
+ validateOptionalOutput(config.systemDefaultOutput);
59
+ const presetConfig = config.preset === undefined
60
+ ? defaultResolverConfig
61
+ : getResolverPresetConfig(config.preset);
62
+ const resolverConfig = mergeResolverConfig(presetConfig, config.resolverConfig);
63
+ const intentRegistry = createIntentRegistry(config.intents);
64
+ return Object.freeze({
65
+ resolve: createResolveFunction(intentRegistry, config.context, config.systemDefaultContext, config.output, config.systemDefaultOutput, resolverConfig),
66
+ });
67
+ }
@@ -0,0 +1,13 @@
1
+ export declare const CONTEXTS: readonly ["light", "dark"];
2
+ export type Context = (typeof CONTEXTS)[number];
3
+ export type ContextResolutionInput = Readonly<{
4
+ resolverContext?: unknown;
5
+ paletteContext?: unknown;
6
+ systemDefaultContext?: unknown;
7
+ }>;
8
+ export type ContextCurveValues<T> = Readonly<Record<Context, T>>;
9
+ export type ContextCurveHook<T> = (context: Context) => T;
10
+ export declare function isContext(value: unknown): value is Context;
11
+ export declare function assertContext(value: unknown): asserts value is Context;
12
+ export declare function resolveContext({ resolverContext, paletteContext, systemDefaultContext, }: ContextResolutionInput): Context;
13
+ export declare function createContextCurveHook<T>(values: ContextCurveValues<T>): ContextCurveHook<T>;
@@ -0,0 +1,37 @@
1
+ import { createUnresolvedContextError } from '../../utils/errors/errors.js';
2
+ export const CONTEXTS = Object.freeze(['light', 'dark']);
3
+ const contextList = CONTEXTS.join(', ');
4
+ const formatInvalidContextError = (value) => `Invalid context "${String(value)}". Expected one of: ${contextList}.`;
5
+ export function isContext(value) {
6
+ return (typeof value === 'string' && CONTEXTS.includes(value));
7
+ }
8
+ export function assertContext(value) {
9
+ if (!isContext(value)) {
10
+ throw new Error(formatInvalidContextError(value));
11
+ }
12
+ }
13
+ export function resolveContext({ resolverContext, paletteContext, systemDefaultContext, }) {
14
+ if (resolverContext !== undefined) {
15
+ assertContext(resolverContext);
16
+ return resolverContext;
17
+ }
18
+ if (paletteContext !== undefined) {
19
+ assertContext(paletteContext);
20
+ return paletteContext;
21
+ }
22
+ if (systemDefaultContext !== undefined) {
23
+ assertContext(systemDefaultContext);
24
+ return systemDefaultContext;
25
+ }
26
+ throw createUnresolvedContextError();
27
+ }
28
+ export function createContextCurveHook(values) {
29
+ const frozenValues = Object.freeze({
30
+ dark: values.dark,
31
+ light: values.light,
32
+ });
33
+ return Object.freeze((context) => {
34
+ assertContext(context);
35
+ return frozenValues[context];
36
+ });
37
+ }
@@ -0,0 +1,17 @@
1
+ import type { Context } from '../context/context.js';
2
+ import type { Usage } from '../usage/strategy.js';
3
+ import { type Level } from './level.js';
4
+ export type LevelDrivenUsage = Exclude<Usage, 'visualVocabulary'>;
5
+ export type LevelCurve<T> = (level: Level, context: Context) => T;
6
+ export type FillLevelCurve = LevelCurve<number>;
7
+ export type LinesLevelCurve = LevelCurve<number>;
8
+ export type OverlayLevelResult = Readonly<{
9
+ luminanceDelta: number;
10
+ }>;
11
+ export type OverlaysLevelCurve = LevelCurve<OverlayLevelResult>;
12
+ export type LevelCurveConfig = Readonly<{
13
+ fill: FillLevelCurve;
14
+ lines: LinesLevelCurve;
15
+ overlays: OverlaysLevelCurve;
16
+ }>;
17
+ export declare const defaultLevelCurves: LevelCurveConfig;
@@ -0,0 +1,49 @@
1
+ import { assertLevel } from './level.js';
2
+ const fillLevelTargets = Object.freeze({
3
+ 1: 98,
4
+ 2: 96,
5
+ 3: 94,
6
+ 4: 91,
7
+ 5: 88,
8
+ 6: 84,
9
+ 7: 79,
10
+ 8: 73,
11
+ 9: 66,
12
+ });
13
+ const linesLevelTargets = Object.freeze({
14
+ 1: 96,
15
+ 2: 95,
16
+ 3: 94,
17
+ 4: 92,
18
+ 5: 90,
19
+ 6: 88,
20
+ 7: 86,
21
+ 8: 84,
22
+ 9: 82,
23
+ });
24
+ const overlayLevelTargets = Object.freeze({
25
+ 1: Object.freeze({ luminanceDelta: 1 }),
26
+ 2: Object.freeze({ luminanceDelta: 2 }),
27
+ 3: Object.freeze({ luminanceDelta: 3 }),
28
+ 4: Object.freeze({ luminanceDelta: 4 }),
29
+ 5: Object.freeze({ luminanceDelta: 5 }),
30
+ 6: Object.freeze({ luminanceDelta: 6 }),
31
+ 7: Object.freeze({ luminanceDelta: 7 }),
32
+ 8: Object.freeze({ luminanceDelta: 8 }),
33
+ 9: Object.freeze({ luminanceDelta: 9 }),
34
+ });
35
+ const resolveContextualLightness = (lightness, context) => context === 'dark' ? 100 - lightness : lightness;
36
+ export const defaultLevelCurves = Object.freeze({
37
+ fill(level, context) {
38
+ assertLevel(level);
39
+ return resolveContextualLightness(fillLevelTargets[level], context);
40
+ },
41
+ lines(level, context) {
42
+ assertLevel(level);
43
+ return resolveContextualLightness(linesLevelTargets[level], context);
44
+ },
45
+ overlays(level, _context) {
46
+ assertLevel(level);
47
+ return overlayLevelTargets[level];
48
+ },
49
+ });
@@ -0,0 +1,4 @@
1
+ export declare const LEVELS: readonly [1, 2, 3, 4, 5, 6, 7, 8, 9];
2
+ export type Level = (typeof LEVELS)[number];
3
+ export declare function isLevel(value: unknown): value is Level;
4
+ export declare function assertLevel(value: unknown): asserts value is Level;
@@ -0,0 +1,13 @@
1
+ export const LEVELS = Object.freeze([1, 2, 3, 4, 5, 6, 7, 8, 9]);
2
+ const formatInvalidLevelError = (value) => `Invalid level "${String(value)}". Expected an integer from 1 to 9.`;
3
+ export function isLevel(value) {
4
+ return (typeof value === 'number' &&
5
+ Number.isInteger(value) &&
6
+ value >= 1 &&
7
+ value <= 9);
8
+ }
9
+ export function assertLevel(value) {
10
+ if (!isLevel(value)) {
11
+ throw new Error(formatInvalidLevelError(value));
12
+ }
13
+ }
@@ -0,0 +1,105 @@
1
+ import { type OklchColor } from '../../core/oklch.js';
2
+ import { type ResolverConfig } from '../../presets/presets.js';
3
+ import type { Context } from '../context/context.js';
4
+ import type { Level } from '../level/level.js';
5
+ import { type Usage } from '../usage/strategy.js';
6
+ export declare const RELATIONS: readonly ["on", "over", "under"];
7
+ export type Relation = (typeof RELATIONS)[number];
8
+ export type RelationAvailability = 'required' | 'optional' | 'forbidden';
9
+ export type RelationTarget = Readonly<OklchColor>;
10
+ export type RelationOptions = Readonly<{
11
+ on?: RelationTarget;
12
+ over?: RelationTarget;
13
+ under?: RelationTarget;
14
+ }>;
15
+ export type ResolvedRelation<R extends Relation = Relation> = Readonly<{
16
+ relation: R;
17
+ target: RelationTarget;
18
+ }>;
19
+ export type RelationApplicationInput = Readonly<{
20
+ usage: Usage;
21
+ color: RelationTarget;
22
+ context?: Context;
23
+ level?: Level;
24
+ resolverConfig?: ResolverConfig;
25
+ relations?: RelationOptions;
26
+ }>;
27
+ export type RelationApplicationResult = Readonly<{
28
+ color: RelationTarget;
29
+ relation?: ResolvedRelation;
30
+ }>;
31
+ export type RelationApplicationHookInput = Readonly<{
32
+ color: RelationTarget;
33
+ context: Context;
34
+ relation: ResolvedRelation;
35
+ level?: Level;
36
+ resolverConfig: ResolverConfig;
37
+ }>;
38
+ export type RelationApplicationHook = (input: RelationApplicationHookInput) => RelationApplicationResult;
39
+ export declare const relationCompatibility: Readonly<{
40
+ fill: Readonly<{
41
+ on: "optional";
42
+ over: "forbidden";
43
+ under: "forbidden";
44
+ }>;
45
+ lines: Readonly<{
46
+ on: "optional";
47
+ over: "forbidden";
48
+ under: "forbidden";
49
+ }>;
50
+ overlays: Readonly<{
51
+ on: "forbidden";
52
+ over: "optional";
53
+ under: "optional";
54
+ }>;
55
+ visualVocabulary: Readonly<{
56
+ on: "required";
57
+ over: "forbidden";
58
+ under: "forbidden";
59
+ }>;
60
+ }>;
61
+ export declare function isRelation(value: unknown): value is Relation;
62
+ export declare function assertRelation(value: unknown): asserts value is Relation;
63
+ export declare function validateRelationOptions(usage: Usage, relations?: RelationOptions): ResolvedRelation | undefined;
64
+ export declare const relationApplicationHooks: Readonly<{
65
+ on(input: Readonly<{
66
+ color: RelationTarget;
67
+ context: Context;
68
+ relation: ResolvedRelation;
69
+ level?: Level;
70
+ resolverConfig: ResolverConfig;
71
+ }>): Readonly<{
72
+ color: OklchColor;
73
+ relation: Readonly<{
74
+ relation: "on" | "over" | "under";
75
+ target: RelationTarget;
76
+ }>;
77
+ }>;
78
+ over(input: Readonly<{
79
+ color: RelationTarget;
80
+ context: Context;
81
+ relation: ResolvedRelation;
82
+ level?: Level;
83
+ resolverConfig: ResolverConfig;
84
+ }>): Readonly<{
85
+ color: OklchColor;
86
+ relation: Readonly<{
87
+ relation: "on" | "over" | "under";
88
+ target: RelationTarget;
89
+ }>;
90
+ }>;
91
+ under(input: Readonly<{
92
+ color: RelationTarget;
93
+ context: Context;
94
+ relation: ResolvedRelation;
95
+ level?: Level;
96
+ resolverConfig: ResolverConfig;
97
+ }>): Readonly<{
98
+ color: OklchColor;
99
+ relation: Readonly<{
100
+ relation: "on" | "over" | "under";
101
+ target: RelationTarget;
102
+ }>;
103
+ }>;
104
+ }>;
105
+ export declare function applyRelation(input: RelationApplicationInput): RelationApplicationResult;
@@ -0,0 +1,137 @@
1
+ import { resolveOnContrast } from '../../contrast/contrast.js';
2
+ import { isOklchColor, normalizeOklch, } from '../../core/oklch.js';
3
+ import { defaultResolverConfig, } from '../../presets/presets.js';
4
+ import { createForbiddenAxisCombinationError, createInvalidRelationTargetError, createMissingRequiredAxisError, createMultipleRelationsError, } from '../../utils/errors/errors.js';
5
+ import { assertUsage } from '../usage/strategy.js';
6
+ export const RELATIONS = Object.freeze(['on', 'over', 'under']);
7
+ const fillRelationCompatibility = Object.freeze({
8
+ on: 'optional',
9
+ over: 'forbidden',
10
+ under: 'forbidden',
11
+ });
12
+ const visualVocabularyRelationCompatibility = Object.freeze({
13
+ on: 'required',
14
+ over: 'forbidden',
15
+ under: 'forbidden',
16
+ });
17
+ const linesRelationCompatibility = Object.freeze({
18
+ on: 'optional',
19
+ over: 'forbidden',
20
+ under: 'forbidden',
21
+ });
22
+ const overlaysRelationCompatibility = Object.freeze({
23
+ on: 'forbidden',
24
+ over: 'optional',
25
+ under: 'optional',
26
+ });
27
+ export const relationCompatibility = Object.freeze({
28
+ fill: fillRelationCompatibility,
29
+ lines: linesRelationCompatibility,
30
+ overlays: overlaysRelationCompatibility,
31
+ visualVocabulary: visualVocabularyRelationCompatibility,
32
+ });
33
+ const relationList = RELATIONS.join(', ');
34
+ const formatInvalidRelationError = (value) => `Invalid relation "${String(value)}". Expected one of: ${relationList}.`;
35
+ export function isRelation(value) {
36
+ return (typeof value === 'string' &&
37
+ RELATIONS.includes(value));
38
+ }
39
+ export function assertRelation(value) {
40
+ if (!isRelation(value)) {
41
+ throw new Error(formatInvalidRelationError(value));
42
+ }
43
+ }
44
+ function assertRelationTarget(relation, target) {
45
+ if (!isOklchColor(target)) {
46
+ throw createInvalidRelationTargetError(relation);
47
+ }
48
+ }
49
+ const getProvidedRelations = (relations) => RELATIONS.filter((relation) => relations[relation] !== undefined);
50
+ export function validateRelationOptions(usage, relations = {}) {
51
+ assertUsage(usage);
52
+ const providedRelations = getProvidedRelations(relations);
53
+ if (providedRelations.length > 1) {
54
+ throw createMultipleRelationsError(providedRelations);
55
+ }
56
+ const requiredRelation = RELATIONS.find((relation) => relationCompatibility[usage][relation] === 'required');
57
+ if (providedRelations.length === 0) {
58
+ if (requiredRelation !== undefined) {
59
+ throw createMissingRequiredAxisError(`Relation "${requiredRelation}"`, usage);
60
+ }
61
+ return undefined;
62
+ }
63
+ const relation = providedRelations[0];
64
+ if (relation === undefined) {
65
+ return undefined;
66
+ }
67
+ const availability = relationCompatibility[usage][relation];
68
+ if (availability === 'forbidden') {
69
+ throw createForbiddenAxisCombinationError(`Relation "${relation}"`, usage);
70
+ }
71
+ const target = relations[relation];
72
+ assertRelationTarget(relation, target);
73
+ return Object.freeze({
74
+ relation,
75
+ target,
76
+ });
77
+ }
78
+ export const relationApplicationHooks = Object.freeze({
79
+ on(input) {
80
+ return Object.freeze({
81
+ color: resolveOnContrast({
82
+ color: input.color,
83
+ config: {
84
+ chromaLimits: input.resolverConfig.chromaLimits,
85
+ on: input.resolverConfig.relationParams.on,
86
+ },
87
+ context: input.context,
88
+ target: input.relation.target,
89
+ }),
90
+ relation: input.relation,
91
+ });
92
+ },
93
+ over(input) {
94
+ const alpha = input.level === undefined
95
+ ? input.color.alpha
96
+ : input.resolverConfig.relationParams.over.baseAlphaByLevel[input.level];
97
+ return Object.freeze({
98
+ color: normalizeOklch({
99
+ ...input.color,
100
+ alpha,
101
+ }),
102
+ relation: input.relation,
103
+ });
104
+ },
105
+ under(input) {
106
+ const alpha = input.level === undefined
107
+ ? input.color.alpha
108
+ : input.resolverConfig.relationParams.under.baseAlphaByLevel[input.level];
109
+ const luminanceReduction = input.resolverConfig.relationParams.under.luminanceReduction;
110
+ return Object.freeze({
111
+ color: normalizeOklch({
112
+ ...input.color,
113
+ alpha,
114
+ l: Math.max(0, input.color.l - luminanceReduction),
115
+ }),
116
+ relation: input.relation,
117
+ });
118
+ },
119
+ });
120
+ export function applyRelation(input) {
121
+ if (!isOklchColor(input.color)) {
122
+ throw createInvalidRelationTargetError('input', 'Relation input color must be a normalized OKLCH color.');
123
+ }
124
+ const relation = validateRelationOptions(input.usage, input.relations);
125
+ if (relation === undefined) {
126
+ return Object.freeze({
127
+ color: input.color,
128
+ });
129
+ }
130
+ return relationApplicationHooks[relation.relation]({
131
+ color: input.color,
132
+ context: input.context ?? 'light',
133
+ level: input.level,
134
+ relation,
135
+ resolverConfig: input.resolverConfig ?? defaultResolverConfig,
136
+ });
137
+ }
@@ -0,0 +1,36 @@
1
+ import { type IntentName, type IntentRegistry } from '../../core/intent-registry.js';
2
+ import { type OklchColor } from '../../core/oklch.js';
3
+ import { type ResolverConfig } from '../../presets/presets.js';
4
+ import { type Context } from '../context/context.js';
5
+ import { type Level } from '../level/level.js';
6
+ import { type RelationOptions, type ResolvedRelation } from '../relation/relation.js';
7
+ import { type State, type StateDeltaDirection } from '../state/state.js';
8
+ import { type Usage } from '../usage/strategy.js';
9
+ export type ResolveColorOptions = Readonly<{
10
+ intentRegistry: IntentRegistry;
11
+ usage: unknown;
12
+ intent: IntentName;
13
+ level?: unknown;
14
+ on?: RelationOptions['on'];
15
+ over?: RelationOptions['over'];
16
+ under?: RelationOptions['under'];
17
+ state?: unknown;
18
+ stateDirection?: StateDeltaDirection;
19
+ resolverContext?: unknown;
20
+ paletteContext?: unknown;
21
+ systemDefaultContext?: unknown;
22
+ resolverConfig?: ResolverConfig;
23
+ }>;
24
+ export type ResolvedColorAxes = Readonly<{
25
+ usage: Usage;
26
+ intent: IntentName;
27
+ context: Context;
28
+ level?: Level;
29
+ state: State;
30
+ relation?: ResolvedRelation;
31
+ }>;
32
+ export type ResolvedColor = Readonly<{
33
+ color: OklchColor;
34
+ axes: ResolvedColorAxes;
35
+ }>;
36
+ export declare function resolveColor(options: ResolveColorOptions): ResolvedColor;
@@ -0,0 +1,116 @@
1
+ import { getIntent, } from '../../core/intent-registry.js';
2
+ import { normalizeOklch } from '../../core/oklch.js';
3
+ import { defaultResolverConfig, } from '../../presets/presets.js';
4
+ import { createForbiddenAxisCombinationError, createMissingRequiredAxisError, } from '../../utils/errors/errors.js';
5
+ import { createContextCurveHook, resolveContext, } from '../context/context.js';
6
+ import { assertLevel } from '../level/level.js';
7
+ import { applyRelation, } from '../relation/relation.js';
8
+ import { applyStateAlphaDelta, applyStateDelta, assertState, } from '../state/state.js';
9
+ import { assertUsage, getUsageStrategy, } from '../usage/strategy.js';
10
+ const contextCurve = createContextCurveHook({
11
+ dark: 0,
12
+ light: 0,
13
+ });
14
+ function assertStateDeltaDirection(value) {
15
+ if (value !== 'increase' && value !== 'decrease') {
16
+ throw new Error(`Invalid stateDirection "${String(value)}". Expected one of: increase, decrease.`);
17
+ }
18
+ }
19
+ const resolveLevel = (usage, level) => {
20
+ if (usage === 'visualVocabulary') {
21
+ if (level !== undefined) {
22
+ throw createForbiddenAxisCombinationError('Level', 'visualVocabulary');
23
+ }
24
+ return undefined;
25
+ }
26
+ if (level === undefined) {
27
+ throw createMissingRequiredAxisError('Level', usage);
28
+ }
29
+ assertLevel(level);
30
+ return level;
31
+ };
32
+ const resolveBaseLightness = (usage, level, context, resolverConfig) => {
33
+ if (usage === 'fill') {
34
+ return resolverConfig.levelCurves.fill(level, context);
35
+ }
36
+ if (usage === 'lines') {
37
+ return resolverConfig.levelCurves.lines(level, context);
38
+ }
39
+ if (usage === 'overlays') {
40
+ return (50 +
41
+ resolverConfig.levelCurves.overlays(level, context)
42
+ .luminanceDelta);
43
+ }
44
+ return 50;
45
+ };
46
+ const resolveStateDirection = (state, stateDirection) => {
47
+ if (state === 'default') {
48
+ if (stateDirection !== undefined) {
49
+ assertStateDeltaDirection(stateDirection);
50
+ }
51
+ return stateDirection ?? 'increase';
52
+ }
53
+ if (stateDirection === undefined) {
54
+ throw createMissingRequiredAxisError('stateDirection', 'state', 'stateDirection is required when state is not "default".');
55
+ }
56
+ assertStateDeltaDirection(stateDirection);
57
+ return stateDirection;
58
+ };
59
+ export function resolveColor(options) {
60
+ assertUsage(options.usage);
61
+ const resolverConfig = options.resolverConfig ?? defaultResolverConfig;
62
+ const stateInput = options.state ?? 'default';
63
+ assertState(stateInput);
64
+ const context = resolveContext({
65
+ paletteContext: options.paletteContext,
66
+ resolverContext: options.resolverContext,
67
+ systemDefaultContext: options.systemDefaultContext,
68
+ });
69
+ const level = resolveLevel(options.usage, options.level);
70
+ const intent = getIntent(options.intentRegistry, options.intent);
71
+ const usageResult = getUsageStrategy(options.usage).resolve({ intent });
72
+ const baseLightness = resolveBaseLightness(options.usage, level, context, resolverConfig);
73
+ const baseColor = normalizeOklch({
74
+ alpha: 1,
75
+ c: usageResult.intent.chroma,
76
+ h: usageResult.intent.hue,
77
+ l: baseLightness,
78
+ });
79
+ const relationResult = applyRelation({
80
+ color: baseColor,
81
+ context,
82
+ level,
83
+ relations: {
84
+ on: options.on,
85
+ over: options.over,
86
+ under: options.under,
87
+ },
88
+ resolverConfig,
89
+ usage: options.usage,
90
+ });
91
+ const stateDirection = resolveStateDirection(stateInput, options.stateDirection);
92
+ const stateLightness = applyStateDelta(relationResult.color.l, stateInput, stateDirection, resolverConfig.stateDeltas.luminance);
93
+ const contextDelta = contextCurve(context);
94
+ const alpha = options.usage === 'overlays'
95
+ ? applyStateAlphaDelta(relationResult.color.alpha, stateInput, stateDirection, resolverConfig.stateDeltas.alpha)
96
+ : relationResult.color.alpha;
97
+ const color = Object.freeze(normalizeOklch({
98
+ ...relationResult.color,
99
+ alpha,
100
+ l: stateLightness + contextDelta,
101
+ }));
102
+ const axes = Object.freeze({
103
+ context,
104
+ intent: options.intent,
105
+ usage: options.usage,
106
+ ...(level === undefined ? {} : { level }),
107
+ state: stateInput,
108
+ ...(relationResult.relation === undefined
109
+ ? {}
110
+ : { relation: relationResult.relation }),
111
+ });
112
+ return Object.freeze({
113
+ axes,
114
+ color,
115
+ });
116
+ }
@@ -0,0 +1,46 @@
1
+ export declare const STATES: readonly ["default", "hover", "active", "focus", "selected", "disabled"];
2
+ export type State = (typeof STATES)[number];
3
+ export type StateDeltaDirection = 'increase' | 'decrease';
4
+ export type StateDeltaTable = Readonly<Record<State, number>>;
5
+ export type StateDeltaConfig = Readonly<{
6
+ luminance: StateDeltaTable;
7
+ alpha: StateDeltaTable;
8
+ }>;
9
+ export declare const defaultStateLuminanceDeltas: Readonly<{
10
+ active: number;
11
+ default: number;
12
+ disabled: number;
13
+ focus: number;
14
+ hover: number;
15
+ selected: number;
16
+ }>;
17
+ export declare const defaultStateAlphaDeltas: Readonly<{
18
+ active: number;
19
+ default: number;
20
+ disabled: number;
21
+ focus: number;
22
+ hover: number;
23
+ selected: number;
24
+ }>;
25
+ export declare const defaultStateDeltas: Readonly<{
26
+ alpha: Readonly<{
27
+ active: number;
28
+ default: number;
29
+ disabled: number;
30
+ focus: number;
31
+ hover: number;
32
+ selected: number;
33
+ }>;
34
+ luminance: Readonly<{
35
+ active: number;
36
+ default: number;
37
+ disabled: number;
38
+ focus: number;
39
+ hover: number;
40
+ selected: number;
41
+ }>;
42
+ }>;
43
+ export declare function isState(value: unknown): value is State;
44
+ export declare function assertState(value: unknown): asserts value is State;
45
+ export declare function applyStateDelta(value: number, state: State, direction: StateDeltaDirection, deltas?: StateDeltaTable): number;
46
+ export declare function applyStateAlphaDelta(value: number, state: State, direction: StateDeltaDirection, deltas?: StateDeltaTable): number;