@clhaas/palette-kit 0.4.0 → 0.4.1

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.
@@ -1,4 +1,5 @@
1
1
  import { type OklchColor } from '../../core/oklch.js';
2
+ import type { ColorOutput, ResolveOutput } from '../../export/types.js';
2
3
  import { type ResolverConfig } from '../../presets/presets.js';
3
4
  import type { Context } from '../context/context.js';
4
5
  import type { Level } from '../level/level.js';
@@ -6,11 +7,12 @@ import { type Usage } from '../usage/strategy.js';
6
7
  export declare const RELATIONS: readonly ["on", "over", "under"];
7
8
  export type Relation = (typeof RELATIONS)[number];
8
9
  export type RelationAvailability = 'required' | 'optional' | 'forbidden';
10
+ export type RelationInputTarget = ResolveOutput<ColorOutput>;
9
11
  export type RelationTarget = Readonly<OklchColor>;
10
12
  export type RelationOptions = Readonly<{
11
- on?: RelationTarget;
12
- over?: RelationTarget;
13
- under?: RelationTarget;
13
+ on?: RelationInputTarget;
14
+ over?: RelationInputTarget;
15
+ under?: RelationInputTarget;
14
16
  }>;
15
17
  export type ResolvedRelation<R extends Relation = Relation> = Readonly<{
16
18
  relation: R;
@@ -1,5 +1,6 @@
1
1
  import { resolveOnContrast } from '../../contrast/contrast.js';
2
2
  import { isOklchColor, normalizeOklch, } from '../../core/oklch.js';
3
+ import { linearRgbToOklab, oklabToOklch } from '../../operators/convert.js';
3
4
  import { defaultResolverConfig, } from '../../presets/presets.js';
4
5
  import { createForbiddenAxisCombinationError, createInvalidRelationTargetError, createMissingRequiredAxisError, createMultipleRelationsError, } from '../../utils/errors/errors.js';
5
6
  import { assertUsage } from '../usage/strategy.js';
@@ -32,6 +33,57 @@ export const relationCompatibility = Object.freeze({
32
33
  });
33
34
  const relationList = RELATIONS.join(', ');
34
35
  const formatInvalidRelationError = (value) => `Invalid relation "${String(value)}". Expected one of: ${relationList}.`;
36
+ const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
37
+ const isEncodedRgbChannel = (value) => typeof value === 'number' &&
38
+ Number.isInteger(value) &&
39
+ value >= 0 &&
40
+ value <= 255;
41
+ const isAlphaChannel = (value) => isFiniteNumber(value) && value >= 0 && value <= 1;
42
+ const isRecord = (value) => typeof value === 'object' && value !== null;
43
+ const isOklabRelationTarget = (value) => {
44
+ if (!isRecord(value)) {
45
+ return false;
46
+ }
47
+ return (value.space === 'oklab' &&
48
+ isFiniteNumber(value.l) &&
49
+ value.l >= 0 &&
50
+ value.l <= 1 &&
51
+ isFiniteNumber(value.a) &&
52
+ isFiniteNumber(value.b) &&
53
+ isAlphaChannel(value.alpha));
54
+ };
55
+ const isRgbRelationTarget = (value) => {
56
+ if (!isRecord(value)) {
57
+ return false;
58
+ }
59
+ return (isEncodedRgbChannel(value.r) &&
60
+ isEncodedRgbChannel(value.g) &&
61
+ isEncodedRgbChannel(value.b) &&
62
+ isAlphaChannel(value.alpha));
63
+ };
64
+ const isRgbaRelationTarget = (value) => {
65
+ if (!isRecord(value)) {
66
+ return false;
67
+ }
68
+ return (isEncodedRgbChannel(value.r) &&
69
+ isEncodedRgbChannel(value.g) &&
70
+ isEncodedRgbChannel(value.b) &&
71
+ isAlphaChannel(value.a));
72
+ };
73
+ const isHexRelationTarget = (value) => typeof value === 'string' && /^#[0-9a-f]{6}$/.test(value);
74
+ const parseHexChannel = (hex, start) => Number.parseInt(hex.slice(start, start + 2), 16);
75
+ const decodeSrgbChannel = (channel) => {
76
+ const encoded = channel / 255;
77
+ return encoded <= 0.04045
78
+ ? encoded / 12.92
79
+ : ((encoded + 0.055) / 1.055) ** 2.4;
80
+ };
81
+ const rgbTargetToOklch = (target) => oklabToOklch(linearRgbToOklab({
82
+ alpha: target.alpha,
83
+ b: decodeSrgbChannel(target.b),
84
+ g: decodeSrgbChannel(target.g),
85
+ r: decodeSrgbChannel(target.r),
86
+ }));
35
87
  export function isRelation(value) {
36
88
  return (typeof value === 'string' &&
37
89
  RELATIONS.includes(value));
@@ -41,10 +93,33 @@ export function assertRelation(value) {
41
93
  throw new Error(formatInvalidRelationError(value));
42
94
  }
43
95
  }
44
- function assertRelationTarget(relation, target) {
45
- if (!isOklchColor(target)) {
46
- throw createInvalidRelationTargetError(relation);
96
+ function normalizeRelationTarget(relation, target) {
97
+ if (isOklchColor(target)) {
98
+ return target;
99
+ }
100
+ if (isOklabRelationTarget(target)) {
101
+ return oklabToOklch(target);
102
+ }
103
+ if (isHexRelationTarget(target)) {
104
+ return rgbTargetToOklch({
105
+ alpha: 1,
106
+ b: parseHexChannel(target, 5),
107
+ g: parseHexChannel(target, 3),
108
+ r: parseHexChannel(target, 1),
109
+ });
110
+ }
111
+ if (isRgbaRelationTarget(target)) {
112
+ return rgbTargetToOklch({
113
+ alpha: target.a,
114
+ b: target.b,
115
+ g: target.g,
116
+ r: target.r,
117
+ });
118
+ }
119
+ if (isRgbRelationTarget(target)) {
120
+ return rgbTargetToOklch(target);
47
121
  }
122
+ throw createInvalidRelationTargetError(relation);
48
123
  }
49
124
  const getProvidedRelations = (relations) => RELATIONS.filter((relation) => relations[relation] !== undefined);
50
125
  export function validateRelationOptions(usage, relations = {}) {
@@ -69,10 +144,10 @@ export function validateRelationOptions(usage, relations = {}) {
69
144
  throw createForbiddenAxisCombinationError(`Relation "${relation}"`, usage);
70
145
  }
71
146
  const target = relations[relation];
72
- assertRelationTarget(relation, target);
147
+ const normalizedTarget = normalizeRelationTarget(relation, target);
73
148
  return Object.freeze({
74
149
  relation,
75
- target,
150
+ target: normalizedTarget,
76
151
  });
77
152
  }
78
153
  export const relationApplicationHooks = Object.freeze({
@@ -47,7 +47,7 @@ export type PaletteKitConfig<I extends string = string, PaletteOutput extends Co
47
47
  resolverConfig?: ResolverConfigOverrides;
48
48
  }>;
49
49
  export type PaletteDefaultOutput<PaletteOutput extends ColorOutput | undefined, SystemDefaultOutput extends ColorOutput | undefined> = PaletteOutput extends ColorOutput ? PaletteOutput : SystemDefaultOutput extends ColorOutput ? SystemDefaultOutput : 'oklch';
50
- type RelationTarget = OklchColor;
50
+ type RelationTarget = PaletteResolveOutput<ColorOutput>;
51
51
  type DefaultStateOptions = Readonly<{
52
52
  state?: 'default';
53
53
  stateDirection?: StateDeltaDirection;
@@ -15,7 +15,7 @@ export const createUnknownIntentError = (intent) => new PaletteKitError('UNKNOWN
15
15
  export const createMissingRequiredAxisError = (axis, usage, message) => new PaletteKitError('MISSING_REQUIRED_AXIS', 'resolver-input', message ?? `${axis} is required for usage "${usage}".`, { axis, usage });
16
16
  export const createForbiddenAxisCombinationError = (axis, usage, message) => new PaletteKitError('FORBIDDEN_AXIS_COMBINATION', 'resolver-input', message ?? `${axis} is not allowed for usage "${usage}".`, { axis, usage });
17
17
  export const createInvalidRelationTargetError = (relation, message) => new PaletteKitError('INVALID_RELATION_TARGET', 'resolver-input', message ??
18
- `Relation "${relation}" target must be a normalized OKLCH color.`, { relation });
18
+ `Relation "${relation}" target must be a normalized OKLCH color or a serialized Palette Kit color output.`, { relation });
19
19
  export const createMultipleRelationsError = (relations) => new PaletteKitError('MULTIPLE_RELATIONS', 'resolver-input', `Only one relation may be provided. Received: ${relations.join(', ')}.`, { relations: [...relations] });
20
20
  export const createUnresolvedContextError = () => new PaletteKitError('UNRESOLVED_CONTEXT', 'resolver-input', 'Context could not be resolved. Provide resolverContext, paletteContext, or systemDefaultContext.');
21
21
  export const createUnsupportedOutputError = (output, message) => new PaletteKitError('UNSUPPORTED_OUTPUT', 'serialization', message ?? `Unsupported color output "${output}" in Phase 10 serializer.`, { output });
package/docs/API.md CHANGED
@@ -80,6 +80,11 @@ const text = palette.resolve({
80
80
  meet the target after the configured luminance shift and chroma reduction, it
81
81
  throws.
82
82
 
83
+ Relation targets may be any color returned by Palette Kit outputs: `oklch`,
84
+ `oklab`, `srgb`, `p3`, `hex`, or `rgba`. Serialized targets are normalized back
85
+ to OKLCH internally before contrast or layering logic runs. CSS/RN strings such
86
+ as `rgba(...)` are not relation targets.
87
+
83
88
  ## State Rules
84
89
 
85
90
  `state` defaults to `"default"`.
package/docs/Text.md CHANGED
@@ -28,6 +28,10 @@ const text = palette.resolve({
28
28
  });
29
29
  ```
30
30
 
31
+ `on` may receive any color returned by Palette Kit outputs. This supports
32
+ palettes configured with `output: "rgba"` while keeping contrast resolution
33
+ internal to OKLCH.
34
+
31
35
  ## Current Behavior
32
36
 
33
37
  `on` enforces APCA contrast. The default target is Lc 60. APCA is the primary
@@ -43,6 +43,17 @@ Convert it to a React Native string if needed:
43
43
  const rnColor = `rgba(${background.r}, ${background.g}, ${background.b}, ${background.a})`;
44
44
  ```
45
45
 
46
+ Use the Palette Kit RGBA object itself for relation targets before converting it
47
+ to a React Native string:
48
+
49
+ ```ts
50
+ const text = palette.resolve({
51
+ usage: "visualVocabulary",
52
+ intent: "brand",
53
+ on: background,
54
+ });
55
+ ```
56
+
46
57
  ## Resolver Override
47
58
 
48
59
  You can override output per call:
@@ -53,7 +53,9 @@ Relation rules:
53
53
  - `overlays` forbids `on`.
54
54
  - `fill` and `lines` allow `on`.
55
55
 
56
- Relation targets must be normalized OKLCH colors.
56
+ Relation targets may be normalized OKLCH colors or serialized Palette Kit
57
+ outputs: `oklab`, `srgb`, `p3`, `hex`, or `rgba`. CSS/RN strings such as
58
+ `rgba(...)` are not accepted.
57
59
 
58
60
  ## State
59
61
 
package/docs/spec.md CHANGED
@@ -81,6 +81,8 @@ and the current explicit clip gamut strategy.
81
81
  - Forbidden axis combinations throw.
82
82
  - `on` enforces APCA contrast with a default Lc 60 target and exposes WCAG as a
83
83
  fallback diagnostic.
84
+ - Relation targets accept normalized OKLCH or any serialized Palette Kit output
85
+ and are normalized back to OKLCH before relation logic runs.
84
86
  - `over` applies configured alpha by level.
85
87
  - `under` applies configured alpha and luminance reduction by level.
86
88
  - State alpha deltas apply only where alpha is allowed, currently `overlays`.
package/package.json CHANGED
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "repository": {
58
58
  "type": "git",
59
- "url": "https://github.com/claushaas/palette-kit"
59
+ "url": "git+https://github.com/claushaas/palette-kit.git"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsc -p tsconfig.build.json",
@@ -72,5 +72,5 @@
72
72
  },
73
73
  "type": "module",
74
74
  "types": "dist/index.d.ts",
75
- "version": "0.4.0"
75
+ "version": "0.4.1"
76
76
  }