@ankhorage/color-theory 0.0.1 → 0.0.2
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.
- package/CHANGELOG.md +9 -0
- package/README.md +2 -0
- package/dist/contrast.d.ts +3 -0
- package/dist/contrast.d.ts.map +1 -1
- package/dist/contrast.js +3 -0
- package/dist/contrast.js.map +1 -1
- package/dist/harmony.d.ts +3 -0
- package/dist/harmony.d.ts.map +1 -1
- package/dist/harmony.js +3 -0
- package/dist/harmony.js.map +1 -1
- package/dist/hex.d.ts +12 -2
- package/dist/hex.d.ts.map +1 -1
- package/dist/hex.js +12 -2
- package/dist/hex.js.map +1 -1
- package/dist/internal-culori.d.ts +17 -12
- package/dist/internal-culori.d.ts.map +1 -1
- package/dist/internal-culori.js +19 -27
- package/dist/internal-culori.js.map +1 -1
- package/dist/neutral.d.ts +3 -0
- package/dist/neutral.d.ts.map +1 -1
- package/dist/neutral.js +9 -0
- package/dist/neutral.js.map +1 -1
- package/dist/swatches.d.ts +3 -0
- package/dist/swatches.d.ts.map +1 -1
- package/dist/swatches.js +9 -0
- package/dist/swatches.js.map +1 -1
- package/dist/theme-colors.d.ts +9 -0
- package/dist/theme-colors.d.ts.map +1 -1
- package/dist/theme-colors.js +9 -0
- package/dist/theme-colors.js.map +1 -1
- package/package.json +1 -1
- package/src/contrast.ts +3 -0
- package/src/harmony.ts +3 -0
- package/src/hex.ts +12 -3
- package/src/internal-culori.ts +21 -42
- package/src/neutral.ts +9 -0
- package/src/swatches.ts +9 -0
- package/src/theme-colors.ts +9 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @ankhorage/color-theory
|
|
2
|
+
|
|
3
|
+
## 0.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 971b60c: Add the initial standalone color theory API with harmony role colors, swatch generation, neutral color generation, semantic color references, theme color helpers, explicit Culori subpath exports, and test coverage.
|
|
8
|
+
- a133273: fix knip & run paradox
|
|
9
|
+
- e1c392a: fix paradox
|
package/README.md
CHANGED
package/dist/contrast.d.ts
CHANGED
|
@@ -3,5 +3,8 @@ export interface ReadableForegroundResult {
|
|
|
3
3
|
foreground: HexColor;
|
|
4
4
|
contrast: number;
|
|
5
5
|
}
|
|
6
|
+
/***
|
|
7
|
+
Return the readable black or white foreground color with the stronger contrast against a background color.
|
|
8
|
+
*/
|
|
6
9
|
export declare function getReadableForeground(background: HexColor): ReadableForegroundResult;
|
|
7
10
|
//# sourceMappingURL=contrast.d.ts.map
|
package/dist/contrast.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contrast.d.ts","sourceRoot":"","sources":["../src/contrast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAItC,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,QAAQ,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,QAAQ,GAAG,wBAAwB,CASpF"}
|
|
1
|
+
{"version":3,"file":"contrast.d.ts","sourceRoot":"","sources":["../src/contrast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAItC,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,QAAQ,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD;;EAEE;AACF,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,QAAQ,GAAG,wBAAwB,CASpF"}
|
package/dist/contrast.js
CHANGED
|
@@ -2,6 +2,9 @@ import { parseHexColorOrThrow } from './hex';
|
|
|
2
2
|
import { contrastRatio } from './internal-culori';
|
|
3
3
|
const BLACK = parseHexColorOrThrow('#000000');
|
|
4
4
|
const WHITE = parseHexColorOrThrow('#FFFFFF');
|
|
5
|
+
/***
|
|
6
|
+
Return the readable black or white foreground color with the stronger contrast against a background color.
|
|
7
|
+
*/
|
|
5
8
|
export function getReadableForeground(background) {
|
|
6
9
|
const contrastOnBlack = contrastRatio(background, BLACK);
|
|
7
10
|
const contrastOnWhite = contrastRatio(background, WHITE);
|
package/dist/contrast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contrast.js","sourceRoot":"","sources":["../src/contrast.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAOlD,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;AAC9C,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;AAE9C,MAAM,UAAU,qBAAqB,CAAC,UAAoB;IACxD,MAAM,eAAe,GAAG,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACzD,MAAM,eAAe,GAAG,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAEzD,IAAI,eAAe,IAAI,eAAe,EAAE,CAAC;QACvC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC1D,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;AAC1D,CAAC","sourcesContent":["import type { HexColor } from './hex';\nimport { parseHexColorOrThrow } from './hex';\nimport { contrastRatio } from './internal-culori';\n\nexport interface ReadableForegroundResult {\n foreground: HexColor;\n contrast: number;\n}\n\nconst BLACK = parseHexColorOrThrow('#000000');\nconst WHITE = parseHexColorOrThrow('#FFFFFF');\n\nexport function getReadableForeground(background: HexColor): ReadableForegroundResult {\n const contrastOnBlack = contrastRatio(background, BLACK);\n const contrastOnWhite = contrastRatio(background, WHITE);\n\n if (contrastOnBlack >= contrastOnWhite) {\n return { foreground: BLACK, contrast: contrastOnBlack };\n }\n\n return { foreground: WHITE, contrast: contrastOnWhite };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"contrast.js","sourceRoot":"","sources":["../src/contrast.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAOlD,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;AAC9C,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;AAE9C;;EAEE;AACF,MAAM,UAAU,qBAAqB,CAAC,UAAoB;IACxD,MAAM,eAAe,GAAG,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACzD,MAAM,eAAe,GAAG,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAEzD,IAAI,eAAe,IAAI,eAAe,EAAE,CAAC;QACvC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC1D,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;AAC1D,CAAC","sourcesContent":["import type { HexColor } from './hex';\nimport { parseHexColorOrThrow } from './hex';\nimport { contrastRatio } from './internal-culori';\n\nexport interface ReadableForegroundResult {\n foreground: HexColor;\n contrast: number;\n}\n\nconst BLACK = parseHexColorOrThrow('#000000');\nconst WHITE = parseHexColorOrThrow('#FFFFFF');\n\n/***\n Return the readable black or white foreground color with the stronger contrast against a background color.\n*/\nexport function getReadableForeground(background: HexColor): ReadableForegroundResult {\n const contrastOnBlack = contrastRatio(background, BLACK);\n const contrastOnWhite = contrastRatio(background, WHITE);\n\n if (contrastOnBlack >= contrastOnWhite) {\n return { foreground: BLACK, contrast: contrastOnBlack };\n }\n\n return { foreground: WHITE, contrast: contrastOnWhite };\n}\n"]}
|
package/dist/harmony.d.ts
CHANGED
|
@@ -16,5 +16,8 @@ export interface GeneratedHarmonyRoleColors {
|
|
|
16
16
|
tertiary?: GeneratedHarmonyRoleColor;
|
|
17
17
|
quaternary?: GeneratedHarmonyRoleColor;
|
|
18
18
|
}
|
|
19
|
+
/***
|
|
20
|
+
Generate role-based harmony colors from a primary color and harmony strategy.
|
|
21
|
+
*/
|
|
19
22
|
export declare function generateHarmonyRoleColors(primaryColor: HexColor, harmony: ColorHarmony): GeneratedHarmonyRoleColors;
|
|
20
23
|
//# sourceMappingURL=harmony.d.ts.map
|
package/dist/harmony.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harmony.d.ts","sourceRoot":"","sources":["../src/harmony.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC,eAAO,MAAM,eAAe,uGAOlB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,YAAY,CAAC;AAErF,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,kBAAkB,CAAC;IACzB,GAAG,EAAE,QAAQ,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;CAClC;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAC7C,OAAO,EAAE,yBAAyB,CAAC;IACnC,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,UAAU,CAAC,EAAE,yBAAyB,CAAC;CACxC;AAoBD,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,QAAQ,EACtB,OAAO,EAAE,YAAY,GACpB,0BAA0B,CAsC5B"}
|
|
1
|
+
{"version":3,"file":"harmony.d.ts","sourceRoot":"","sources":["../src/harmony.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC,eAAO,MAAM,eAAe,uGAOlB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,YAAY,CAAC;AAErF,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,kBAAkB,CAAC;IACzB,GAAG,EAAE,QAAQ,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;CAClC;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAC7C,OAAO,EAAE,yBAAyB,CAAC;IACnC,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,UAAU,CAAC,EAAE,yBAAyB,CAAC;CACxC;AAoBD;;EAEE;AACF,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,QAAQ,EACtB,OAAO,EAAE,YAAY,GACpB,0BAA0B,CAsC5B"}
|
package/dist/harmony.js
CHANGED
|
@@ -23,6 +23,9 @@ const OFFSETS_BY_HARMONY = {
|
|
|
23
23
|
triadic: [0, 120, 240],
|
|
24
24
|
tetradic: [0, 90, 180, 270],
|
|
25
25
|
};
|
|
26
|
+
/***
|
|
27
|
+
Generate role-based harmony colors from a primary color and harmony strategy.
|
|
28
|
+
*/
|
|
26
29
|
export function generateHarmonyRoleColors(primaryColor, harmony) {
|
|
27
30
|
const base = parseHexToOklch(primaryColor);
|
|
28
31
|
const roles = ROLE_ORDER_BY_HARMONY[harmony];
|
package/dist/harmony.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harmony.js","sourceRoot":"","sources":["../src/harmony.ts"],"names":[],"mappings":"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","sourcesContent":["import type { HexColor } from './hex';\nimport { normalizeHueDegrees, oklchToHex, parseHexToOklch } from './internal-culori';\n\nexport const COLOR_HARMONIES = [\n 'monochromatic',\n 'analogous',\n 'complementary',\n 'triadic',\n 'tetradic',\n 'splitComplementary',\n] as const;\n\nexport type ColorHarmony = (typeof COLOR_HARMONIES)[number];\n\nexport type GeneratedColorRole = 'primary' | 'secondary' | 'tertiary' | 'quaternary';\n\nexport interface GeneratedHarmonyRoleColor {\n role: GeneratedColorRole;\n hex: HexColor;\n hueDegrees: number;\n source: 'selected' | 'generated';\n}\n\nexport interface GeneratedHarmonyRoleColors {\n harmony: ColorHarmony;\n colors: readonly GeneratedHarmonyRoleColor[];\n primary: GeneratedHarmonyRoleColor;\n secondary?: GeneratedHarmonyRoleColor;\n tertiary?: GeneratedHarmonyRoleColor;\n quaternary?: GeneratedHarmonyRoleColor;\n}\n\nconst ROLE_ORDER_BY_HARMONY: Record<ColorHarmony, readonly GeneratedColorRole[]> = {\n monochromatic: ['primary'],\n complementary: ['primary', 'secondary'],\n analogous: ['primary', 'secondary', 'tertiary'],\n splitComplementary: ['primary', 'secondary', 'tertiary'],\n triadic: ['primary', 'secondary', 'tertiary'],\n tetradic: ['primary', 'secondary', 'tertiary', 'quaternary'],\n};\n\nconst OFFSETS_BY_HARMONY: Record<ColorHarmony, readonly number[]> = {\n monochromatic: [0],\n analogous: [0, -30, 30],\n complementary: [0, 180],\n splitComplementary: [0, 150, 210],\n triadic: [0, 120, 240],\n tetradic: [0, 90, 180, 270],\n};\n\nexport function generateHarmonyRoleColors(\n primaryColor: HexColor,\n harmony: ColorHarmony,\n): GeneratedHarmonyRoleColors {\n const base = parseHexToOklch(primaryColor);\n const roles = ROLE_ORDER_BY_HARMONY[harmony];\n const offsets = OFFSETS_BY_HARMONY[harmony];\n const baseHue = normalizeHueDegrees(base.h);\n\n const colors: GeneratedHarmonyRoleColor[] = [];\n\n for (let index = 0; index < roles.length; index++) {\n const role = roles[index];\n if (!role) continue;\n\n const hueDegrees = normalizeHueDegrees(baseHue + (offsets[index] ?? 0));\n colors.push({\n role,\n hex: role === 'primary' ? primaryColor : oklchToHex({ ...base, h: hueDegrees }),\n hueDegrees,\n source: role === 'primary' ? 'selected' : 'generated',\n });\n }\n\n const primary = colors.find((color) => color.role === 'primary');\n if (!primary) {\n throw new Error('[color-theory] Expected generated harmony role colors to include primary.');\n }\n\n const secondary = colors.find((color) => color.role === 'secondary');\n const tertiary = colors.find((color) => color.role === 'tertiary');\n const quaternary = colors.find((color) => color.role === 'quaternary');\n\n return {\n harmony,\n colors,\n primary,\n ...(secondary ? { secondary } : {}),\n ...(tertiary ? { tertiary } : {}),\n ...(quaternary ? { quaternary } : {}),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"harmony.js","sourceRoot":"","sources":["../src/harmony.ts"],"names":[],"mappings":"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","sourcesContent":["import type { HexColor } from './hex';\nimport { normalizeHueDegrees, oklchToHex, parseHexToOklch } from './internal-culori';\n\nexport const COLOR_HARMONIES = [\n 'monochromatic',\n 'analogous',\n 'complementary',\n 'triadic',\n 'tetradic',\n 'splitComplementary',\n] as const;\n\nexport type ColorHarmony = (typeof COLOR_HARMONIES)[number];\n\nexport type GeneratedColorRole = 'primary' | 'secondary' | 'tertiary' | 'quaternary';\n\nexport interface GeneratedHarmonyRoleColor {\n role: GeneratedColorRole;\n hex: HexColor;\n hueDegrees: number;\n source: 'selected' | 'generated';\n}\n\nexport interface GeneratedHarmonyRoleColors {\n harmony: ColorHarmony;\n colors: readonly GeneratedHarmonyRoleColor[];\n primary: GeneratedHarmonyRoleColor;\n secondary?: GeneratedHarmonyRoleColor;\n tertiary?: GeneratedHarmonyRoleColor;\n quaternary?: GeneratedHarmonyRoleColor;\n}\n\nconst ROLE_ORDER_BY_HARMONY: Record<ColorHarmony, readonly GeneratedColorRole[]> = {\n monochromatic: ['primary'],\n complementary: ['primary', 'secondary'],\n analogous: ['primary', 'secondary', 'tertiary'],\n splitComplementary: ['primary', 'secondary', 'tertiary'],\n triadic: ['primary', 'secondary', 'tertiary'],\n tetradic: ['primary', 'secondary', 'tertiary', 'quaternary'],\n};\n\nconst OFFSETS_BY_HARMONY: Record<ColorHarmony, readonly number[]> = {\n monochromatic: [0],\n analogous: [0, -30, 30],\n complementary: [0, 180],\n splitComplementary: [0, 150, 210],\n triadic: [0, 120, 240],\n tetradic: [0, 90, 180, 270],\n};\n\n/***\n Generate role-based harmony colors from a primary color and harmony strategy.\n*/\nexport function generateHarmonyRoleColors(\n primaryColor: HexColor,\n harmony: ColorHarmony,\n): GeneratedHarmonyRoleColors {\n const base = parseHexToOklch(primaryColor);\n const roles = ROLE_ORDER_BY_HARMONY[harmony];\n const offsets = OFFSETS_BY_HARMONY[harmony];\n const baseHue = normalizeHueDegrees(base.h);\n\n const colors: GeneratedHarmonyRoleColor[] = [];\n\n for (let index = 0; index < roles.length; index++) {\n const role = roles[index];\n if (!role) continue;\n\n const hueDegrees = normalizeHueDegrees(baseHue + (offsets[index] ?? 0));\n colors.push({\n role,\n hex: role === 'primary' ? primaryColor : oklchToHex({ ...base, h: hueDegrees }),\n hueDegrees,\n source: role === 'primary' ? 'selected' : 'generated',\n });\n }\n\n const primary = colors.find((color) => color.role === 'primary');\n if (!primary) {\n throw new Error('[color-theory] Expected generated harmony role colors to include primary.');\n }\n\n const secondary = colors.find((color) => color.role === 'secondary');\n const tertiary = colors.find((color) => color.role === 'tertiary');\n const quaternary = colors.find((color) => color.role === 'quaternary');\n\n return {\n harmony,\n colors,\n primary,\n ...(secondary ? { secondary } : {}),\n ...(tertiary ? { tertiary } : {}),\n ...(quaternary ? { quaternary } : {}),\n };\n}\n"]}
|
package/dist/hex.d.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
export type HexColor = string & {
|
|
2
2
|
readonly __hexColorBrand: unique symbol;
|
|
3
3
|
};
|
|
4
|
+
/***
|
|
5
|
+
Check whether a string is a valid six-digit hex color.
|
|
6
|
+
*/
|
|
4
7
|
export declare function isHexColor(value: string): value is HexColor;
|
|
8
|
+
/***
|
|
9
|
+
Parse a string as a six-digit hex color and return null when it is invalid.
|
|
10
|
+
*/
|
|
5
11
|
export declare function parseHexColor(value: string): HexColor | null;
|
|
12
|
+
/***
|
|
13
|
+
Parse a string as a six-digit hex color or throw when it is invalid.
|
|
14
|
+
*/
|
|
6
15
|
export declare function parseHexColorOrThrow(value: string): HexColor;
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
/***
|
|
17
|
+
Assert that a string is a valid six-digit hex color.
|
|
18
|
+
*/
|
|
9
19
|
export declare function assertHexColor(value: string): asserts value is HexColor;
|
|
10
20
|
//# sourceMappingURL=hex.d.ts.map
|
package/dist/hex.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hex.d.ts","sourceRoot":"","sources":["../src/hex.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,MAAM,CAAA;CAAE,CAAC;AAI5E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,QAAQ,CAE3D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAG5D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAM5D;AAED
|
|
1
|
+
{"version":3,"file":"hex.d.ts","sourceRoot":"","sources":["../src/hex.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,MAAM,CAAA;CAAE,CAAC;AAI5E;;EAEE;AACF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,QAAQ,CAE3D;AAED;;EAEE;AACF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAG5D;AAED;;EAEE;AACF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAM5D;AAED;;EAEE;AACF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,QAAQ,CAIvE"}
|
package/dist/hex.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
const HEX6_CASE_INSENSITIVE_REGEX = /^#[0-9a-fA-F]{6}$/;
|
|
2
|
+
/***
|
|
3
|
+
Check whether a string is a valid six-digit hex color.
|
|
4
|
+
*/
|
|
2
5
|
export function isHexColor(value) {
|
|
3
6
|
return HEX6_CASE_INSENSITIVE_REGEX.test(value);
|
|
4
7
|
}
|
|
8
|
+
/***
|
|
9
|
+
Parse a string as a six-digit hex color and return null when it is invalid.
|
|
10
|
+
*/
|
|
5
11
|
export function parseHexColor(value) {
|
|
6
12
|
if (!HEX6_CASE_INSENSITIVE_REGEX.test(value))
|
|
7
13
|
return null;
|
|
8
14
|
return value;
|
|
9
15
|
}
|
|
16
|
+
/***
|
|
17
|
+
Parse a string as a six-digit hex color or throw when it is invalid.
|
|
18
|
+
*/
|
|
10
19
|
export function parseHexColorOrThrow(value) {
|
|
11
20
|
const parsed = parseHexColor(value);
|
|
12
21
|
if (!parsed) {
|
|
@@ -14,8 +23,9 @@ export function parseHexColorOrThrow(value) {
|
|
|
14
23
|
}
|
|
15
24
|
return parsed;
|
|
16
25
|
}
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
/***
|
|
27
|
+
Assert that a string is a valid six-digit hex color.
|
|
28
|
+
*/
|
|
19
29
|
export function assertHexColor(value) {
|
|
20
30
|
if (!isHexColor(value)) {
|
|
21
31
|
throw new Error(`Invalid hex color (expected #RRGGBB or #rrggbb): ${JSON.stringify(value)}`);
|
package/dist/hex.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hex.js","sourceRoot":"","sources":["../src/hex.ts"],"names":[],"mappings":"AAEA,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAExD,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1D,OAAO,KAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,oDAAoD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"hex.js","sourceRoot":"","sources":["../src/hex.ts"],"names":[],"mappings":"AAEA,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAExD;;EAEE;AACF,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACjD,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1D,OAAO,KAAiB,CAAC;AAC3B,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,oDAAoD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,oDAAoD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;AACH,CAAC","sourcesContent":["export type HexColor = string & { readonly __hexColorBrand: unique symbol };\n\nconst HEX6_CASE_INSENSITIVE_REGEX = /^#[0-9a-fA-F]{6}$/;\n\n/***\n Check whether a string is a valid six-digit hex color.\n*/\nexport function isHexColor(value: string): value is HexColor {\n return HEX6_CASE_INSENSITIVE_REGEX.test(value);\n}\n\n/***\n Parse a string as a six-digit hex color and return null when it is invalid.\n*/\nexport function parseHexColor(value: string): HexColor | null {\n if (!HEX6_CASE_INSENSITIVE_REGEX.test(value)) return null;\n return value as HexColor;\n}\n\n/***\n Parse a string as a six-digit hex color or throw when it is invalid.\n*/\nexport function parseHexColorOrThrow(value: string): HexColor {\n const parsed = parseHexColor(value);\n if (!parsed) {\n throw new Error(`Invalid hex color (expected #RRGGBB or #rrggbb): ${JSON.stringify(value)}`);\n }\n return parsed;\n}\n\n/***\n Assert that a string is a valid six-digit hex color.\n*/\nexport function assertHexColor(value: string): asserts value is HexColor {\n if (!isHexColor(value)) {\n throw new Error(`Invalid hex color (expected #RRGGBB or #rrggbb): ${JSON.stringify(value)}`);\n }\n}\n"]}
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
import type { HexColor } from './hex';
|
|
2
|
-
|
|
2
|
+
interface OklchColor {
|
|
3
3
|
mode: 'oklch';
|
|
4
4
|
l: number;
|
|
5
5
|
c: number;
|
|
6
6
|
h: number;
|
|
7
7
|
alpha?: number;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
g: number;
|
|
13
|
-
b: number;
|
|
14
|
-
alpha?: number;
|
|
15
|
-
}
|
|
9
|
+
/***
|
|
10
|
+
Normalize hue degrees into the zero-to-three-sixty range.
|
|
11
|
+
*/
|
|
16
12
|
export declare function normalizeHueDegrees(hueDegrees: number): number;
|
|
13
|
+
/***
|
|
14
|
+
Convert a hex color into a normalized OKLCH color object.
|
|
15
|
+
*/
|
|
17
16
|
export declare function parseHexToOklch(hex: HexColor): OklchColor;
|
|
17
|
+
/***
|
|
18
|
+
Convert an OKLCH color object into an in-gamut hex color.
|
|
19
|
+
*/
|
|
18
20
|
export declare function oklchToHex(oklch: OklchColor): HexColor;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
/***
|
|
22
|
+
Calculate the perceptual OKLCH distance between two OKLCH colors.
|
|
23
|
+
*/
|
|
21
24
|
export declare function deltaEoklch(a: OklchColor, b: OklchColor): number;
|
|
25
|
+
/***
|
|
26
|
+
Calculate the WCAG contrast ratio between two color inputs.
|
|
27
|
+
*/
|
|
22
28
|
export declare function contrastRatio(colorA: string | object, colorB: string | object): number;
|
|
23
|
-
export
|
|
24
|
-
export declare function rgbFrom(input: string | object): RgbColor;
|
|
29
|
+
export {};
|
|
25
30
|
//# sourceMappingURL=internal-culori.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-culori.d.ts","sourceRoot":"","sources":["../src/internal-culori.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"internal-culori.d.ts","sourceRoot":"","sources":["../src/internal-culori.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC,UAAU,UAAU;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsBD;;EAEE;AACF,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;EAEE;AACF,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,GAAG,UAAU,CAWzD;AAED;;EAEE;AACF,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,CAItD;AAED;;EAEE;AACF,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,MAAM,CAEhE;AAED;;EAEE;AACF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEtF"}
|
package/dist/internal-culori.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
import { converter, differenceEuclidean, formatHex, toGamut, wcagContrast
|
|
1
|
+
import { converter, differenceEuclidean, formatHex, toGamut, wcagContrast } from 'culori';
|
|
2
2
|
import { parseHexColorOrThrow } from './hex';
|
|
3
3
|
const toOklch = converter('oklch');
|
|
4
|
-
const toRgb = converter('rgb');
|
|
5
4
|
const toRgbGamut = toGamut('rgb', 'oklch');
|
|
6
5
|
const deltaE = differenceEuclidean('oklch');
|
|
6
|
+
/***
|
|
7
|
+
Clamp a number to a finite minimum and maximum range.
|
|
8
|
+
*/
|
|
7
9
|
function clampNumber(value, min, max) {
|
|
8
10
|
if (!Number.isFinite(value))
|
|
9
11
|
return min;
|
|
10
12
|
return Math.min(max, Math.max(min, value));
|
|
11
13
|
}
|
|
14
|
+
/***
|
|
15
|
+
Normalize hue degrees into the zero-to-three-sixty range.
|
|
16
|
+
*/
|
|
12
17
|
export function normalizeHueDegrees(hueDegrees) {
|
|
13
18
|
const hue = ((hueDegrees % 360) + 360) % 360;
|
|
14
19
|
return hue === 360 ? 0 : hue;
|
|
15
20
|
}
|
|
21
|
+
/***
|
|
22
|
+
Convert a hex color into a normalized OKLCH color object.
|
|
23
|
+
*/
|
|
16
24
|
export function parseHexToOklch(hex) {
|
|
17
25
|
const color = toOklch(hex);
|
|
18
26
|
if (color?.mode !== 'oklch') {
|
|
@@ -23,40 +31,24 @@ export function parseHexToOklch(hex) {
|
|
|
23
31
|
const h = normalizeHueDegrees(Number.isFinite(color.h) ? color.h : 0);
|
|
24
32
|
return { mode: 'oklch', l, c, h, alpha: color.alpha };
|
|
25
33
|
}
|
|
34
|
+
/***
|
|
35
|
+
Convert an OKLCH color object into an in-gamut hex color.
|
|
36
|
+
*/
|
|
26
37
|
export function oklchToHex(oklch) {
|
|
27
38
|
const gamutRgb = toRgbGamut(oklch);
|
|
28
39
|
const hex = formatHex(gamutRgb ?? oklch);
|
|
29
40
|
return parseHexColorOrThrow(hex);
|
|
30
41
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
throw new Error(`Failed to parse/convert to OKLCH: ${JSON.stringify(input)}`);
|
|
35
|
-
}
|
|
36
|
-
const l = clampNumber(color.l, 0, 1);
|
|
37
|
-
const c = clampNumber(color.c, 0, 0.4);
|
|
38
|
-
const h = normalizeHueDegrees(Number.isFinite(color.h) ? color.h : 0);
|
|
39
|
-
return { mode: 'oklch', l, c, h, alpha: color.alpha };
|
|
40
|
-
}
|
|
41
|
-
export function toHexColor(input) {
|
|
42
|
-
const gamutRgb = toRgbGamut(input);
|
|
43
|
-
const hex = formatHex(gamutRgb ?? input);
|
|
44
|
-
return parseHexColorOrThrow(hex);
|
|
45
|
-
}
|
|
42
|
+
/***
|
|
43
|
+
Calculate the perceptual OKLCH distance between two OKLCH colors.
|
|
44
|
+
*/
|
|
46
45
|
export function deltaEoklch(a, b) {
|
|
47
46
|
return deltaE(a, b);
|
|
48
47
|
}
|
|
48
|
+
/***
|
|
49
|
+
Calculate the WCAG contrast ratio between two color inputs.
|
|
50
|
+
*/
|
|
49
51
|
export function contrastRatio(colorA, colorB) {
|
|
50
52
|
return wcagContrast(colorA, colorB);
|
|
51
53
|
}
|
|
52
|
-
export function relativeLuminance(color) {
|
|
53
|
-
return wcagLuminance(color);
|
|
54
|
-
}
|
|
55
|
-
export function rgbFrom(input) {
|
|
56
|
-
const rgb = toRgb(input);
|
|
57
|
-
if (rgb?.mode !== 'rgb') {
|
|
58
|
-
throw new Error(`Failed to convert to rgb: ${JSON.stringify(input)}`);
|
|
59
|
-
}
|
|
60
|
-
return rgb;
|
|
61
|
-
}
|
|
62
54
|
//# sourceMappingURL=internal-culori.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-culori.js","sourceRoot":"","sources":["../src/internal-culori.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"internal-culori.js","sourceRoot":"","sources":["../src/internal-culori.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAG1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAkB7C,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;AACnC,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAC3C,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAE5C;;EAEE;AACF,SAAS,WAAW,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,MAAM,GAAG,GAAG,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IAC7C,OAAO,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAC/B,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,eAAe,CAAC,GAAa;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAsC,CAAC;IAChE,IAAI,KAAK,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,MAAM,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,mBAAmB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEtE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;AACxD,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,UAAU,CAAC,KAAiB;IAC1C,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAoC,CAAC;IACtE,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC;IACzC,OAAO,oBAAoB,CAAC,GAAG,CAAC,CAAC;AACnC,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,WAAW,CAAC,CAAa,EAAE,CAAa;IACtD,OAAO,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACtB,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,aAAa,CAAC,MAAuB,EAAE,MAAuB;IAC5E,OAAO,YAAY,CAAC,MAAe,EAAE,MAAe,CAAC,CAAC;AACxD,CAAC","sourcesContent":["import { converter, differenceEuclidean, formatHex, toGamut, wcagContrast } from 'culori';\n\nimport type { HexColor } from './hex';\nimport { parseHexColorOrThrow } from './hex';\n\ninterface OklchColor {\n mode: 'oklch';\n l: number;\n c: number;\n h: number;\n alpha?: number;\n}\n\ninterface RgbColor {\n mode: 'rgb';\n r: number;\n g: number;\n b: number;\n alpha?: number;\n}\n\nconst toOklch = converter('oklch');\nconst toRgbGamut = toGamut('rgb', 'oklch');\nconst deltaE = differenceEuclidean('oklch');\n\n/***\n Clamp a number to a finite minimum and maximum range.\n*/\nfunction clampNumber(value: number, min: number, max: number): number {\n if (!Number.isFinite(value)) return min;\n return Math.min(max, Math.max(min, value));\n}\n\n/***\n Normalize hue degrees into the zero-to-three-sixty range.\n*/\nexport function normalizeHueDegrees(hueDegrees: number): number {\n const hue = ((hueDegrees % 360) + 360) % 360;\n return hue === 360 ? 0 : hue;\n}\n\n/***\n Convert a hex color into a normalized OKLCH color object.\n*/\nexport function parseHexToOklch(hex: HexColor): OklchColor {\n const color = toOklch(hex) as unknown as OklchColor | undefined;\n if (color?.mode !== 'oklch') {\n throw new Error(`Failed to convert hex to OKLCH: ${hex}`);\n }\n\n const l = clampNumber(color.l, 0, 1);\n const c = clampNumber(color.c, 0, 0.4);\n const h = normalizeHueDegrees(Number.isFinite(color.h) ? color.h : 0);\n\n return { mode: 'oklch', l, c, h, alpha: color.alpha };\n}\n\n/***\n Convert an OKLCH color object into an in-gamut hex color.\n*/\nexport function oklchToHex(oklch: OklchColor): HexColor {\n const gamutRgb = toRgbGamut(oklch) as unknown as RgbColor | undefined;\n const hex = formatHex(gamutRgb ?? oklch);\n return parseHexColorOrThrow(hex);\n}\n\n/***\n Calculate the perceptual OKLCH distance between two OKLCH colors.\n*/\nexport function deltaEoklch(a: OklchColor, b: OklchColor): number {\n return deltaE(a, b);\n}\n\n/***\n Calculate the WCAG contrast ratio between two color inputs.\n*/\nexport function contrastRatio(colorA: string | object, colorB: string | object): number {\n return wcagContrast(colorA as never, colorB as never);\n}\n"]}
|
package/dist/neutral.d.ts
CHANGED
|
@@ -11,5 +11,8 @@ export interface GeneratedNeutralMetadata {
|
|
|
11
11
|
neutralKeyColor: HexColor;
|
|
12
12
|
diagnostics: ColorSwatchDiagnostics;
|
|
13
13
|
}
|
|
14
|
+
/***
|
|
15
|
+
Generate a softly tinted neutral swatch from generated harmony role colors.
|
|
16
|
+
*/
|
|
14
17
|
export declare function generateNeutralSwatch(roleColors: GeneratedHarmonyRoleColors): NeutralSwatchResult;
|
|
15
18
|
//# sourceMappingURL=neutral.d.ts.map
|
package/dist/neutral.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"neutral.d.ts","sourceRoot":"","sources":["../src/neutral.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,sBAAsB,EAAuB,MAAM,YAAY,CAAC;AAEhG,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,QAAQ,CAAC;IAC1B,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,sBAAsB,CAAC;CACrC;AAED,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,QAAQ,CAAC;IAC1B,WAAW,EAAE,sBAAsB,CAAC;CACrC;
|
|
1
|
+
{"version":3,"file":"neutral.d.ts","sourceRoot":"","sources":["../src/neutral.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,sBAAsB,EAAuB,MAAM,YAAY,CAAC;AAEhG,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,QAAQ,CAAC;IAC1B,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,sBAAsB,CAAC;CACrC;AAED,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,QAAQ,CAAC;IAC1B,WAAW,EAAE,sBAAsB,CAAC;CACrC;AAkCD;;EAEE;AACF,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,0BAA0B,GAAG,mBAAmB,CAuBjG"}
|
package/dist/neutral.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { normalizeHueDegrees, oklchToHex, parseHexToOklch } from './internal-culori';
|
|
2
2
|
import { generateColorSwatch } from './swatches';
|
|
3
3
|
export const MIN_HUEFUL_CHROMA = 0.015;
|
|
4
|
+
/***
|
|
5
|
+
Pick the harmony color that should tint the generated neutral swatch.
|
|
6
|
+
*/
|
|
4
7
|
function pickTintSourceHex(roleColors, harmony) {
|
|
5
8
|
const byMapping = {
|
|
6
9
|
monochromatic: roleColors.primary.hex,
|
|
@@ -15,11 +18,17 @@ function pickTintSourceHex(roleColors, harmony) {
|
|
|
15
18
|
roleColors.secondary?.hex ??
|
|
16
19
|
roleColors.primary.hex);
|
|
17
20
|
}
|
|
21
|
+
/***
|
|
22
|
+
Clamp a number to a finite minimum and maximum range.
|
|
23
|
+
*/
|
|
18
24
|
function clampNumber(value, min, max) {
|
|
19
25
|
if (!Number.isFinite(value))
|
|
20
26
|
return min;
|
|
21
27
|
return Math.min(max, Math.max(min, value));
|
|
22
28
|
}
|
|
29
|
+
/***
|
|
30
|
+
Generate a softly tinted neutral swatch from generated harmony role colors.
|
|
31
|
+
*/
|
|
23
32
|
export function generateNeutralSwatch(roleColors) {
|
|
24
33
|
const tintSourceHex = pickTintSourceHex(roleColors, roleColors.harmony);
|
|
25
34
|
const tintSource = parseHexToOklch(tintSourceHex);
|
package/dist/neutral.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"neutral.js","sourceRoot":"","sources":["../src/neutral.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAiD,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEhG,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAavC,SAAS,iBAAiB,CACxB,UAAsC,EACtC,OAAqB;IAErB,MAAM,SAAS,GAAwD;QACrE,aAAa,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG;QACrC,aAAa,EAAE,UAAU,CAAC,SAAS,EAAE,GAAG;QACxC,SAAS,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;QACnC,kBAAkB,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;QAC5C,OAAO,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;QACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;KACnC,CAAC;IAEF,OAAO,CACL,SAAS,CAAC,OAAO,CAAC;QAClB,UAAU,CAAC,QAAQ,EAAE,GAAG;QACxB,UAAU,CAAC,SAAS,EAAE,GAAG;QACzB,UAAU,CAAC,OAAO,CAAC,GAAG,CACvB,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,UAAsC;IAC1E,MAAM,aAAa,GAAG,iBAAiB,CAAC,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACxE,MAAM,UAAU,GAAG,eAAe,CAAC,aAAa,CAAC,CAAC;IAElD,MAAM,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAExD,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,GAAG,iBAAiB,CAAC;IAEpD,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,KAAK,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAC9E,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,KAAK,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACvF,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC7D,MAAM,aAAa,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAEtF,MAAM,eAAe,GAAG,UAAU,CAAC;QACjC,IAAI,EAAE,OAAO;QACb,CAAC,EAAE,GAAG;QACN,CAAC,EAAE,aAAa;QAChB,CAAC,EAAE,UAAU;KACd,CAAC,CAAC;IAEH,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,mBAAmB,CAAC,eAAe,CAAC,CAAC;IAErE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAC3D,CAAC","sourcesContent":["import type { ColorHarmony, GeneratedHarmonyRoleColors } from './harmony';\nimport type { HexColor } from './hex';\nimport { normalizeHueDegrees, oklchToHex, parseHexToOklch } from './internal-culori';\nimport { type ColorSwatch, type ColorSwatchDiagnostics, generateColorSwatch } from './swatches';\n\nexport const MIN_HUEFUL_CHROMA = 0.015;\n\nexport interface NeutralSwatchResult {\n neutralKeyColor: HexColor;\n neutral: ColorSwatch;\n diagnostics: ColorSwatchDiagnostics;\n}\n\nexport interface GeneratedNeutralMetadata {\n neutralKeyColor: HexColor;\n diagnostics: ColorSwatchDiagnostics;\n}\n\nfunction pickTintSourceHex(\n roleColors: GeneratedHarmonyRoleColors,\n harmony: ColorHarmony,\n): HexColor {\n const byMapping: Partial<Record<ColorHarmony, HexColor | undefined>> = {\n monochromatic: roleColors.primary.hex,\n complementary: roleColors.secondary?.hex,\n analogous: roleColors.tertiary?.hex,\n splitComplementary: roleColors.tertiary?.hex,\n triadic: roleColors.tertiary?.hex,\n tetradic: roleColors.tertiary?.hex,\n };\n\n return (\n byMapping[harmony] ??\n roleColors.tertiary?.hex ??\n roleColors.secondary?.hex ??\n roleColors.primary.hex\n );\n}\n\nfunction clampNumber(value: number, min: number, max: number): number {\n if (!Number.isFinite(value)) return min;\n return Math.min(max, Math.max(min, value));\n}\n\nexport function generateNeutralSwatch(roleColors: GeneratedHarmonyRoleColors): NeutralSwatchResult {\n const tintSourceHex = pickTintSourceHex(roleColors, roleColors.harmony);\n const tintSource = parseHexToOklch(tintSourceHex);\n\n const primary = parseHexToOklch(roleColors.primary.hex);\n\n const preferGray = tintSource.c < MIN_HUEFUL_CHROMA;\n\n const hue = roleColors.harmony === 'monochromatic' ? primary.h : tintSource.h;\n const sourceChroma = roleColors.harmony === 'monochromatic' ? primary.c : tintSource.c;\n const neutralHue = preferGray ? 0 : normalizeHueDegrees(hue);\n const neutralChroma = preferGray ? 0 : clampNumber(sourceChroma * 0.06, 0.004, 0.012);\n\n const neutralKeyColor = oklchToHex({\n mode: 'oklch',\n l: 0.6,\n c: neutralChroma,\n h: neutralHue,\n });\n\n const { swatch, diagnostics } = generateColorSwatch(neutralKeyColor);\n\n return { neutralKeyColor, neutral: swatch, diagnostics };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"neutral.js","sourceRoot":"","sources":["../src/neutral.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAiD,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEhG,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAavC;;EAEE;AACF,SAAS,iBAAiB,CACxB,UAAsC,EACtC,OAAqB;IAErB,MAAM,SAAS,GAAwD;QACrE,aAAa,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG;QACrC,aAAa,EAAE,UAAU,CAAC,SAAS,EAAE,GAAG;QACxC,SAAS,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;QACnC,kBAAkB,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;QAC5C,OAAO,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;QACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG;KACnC,CAAC;IAEF,OAAO,CACL,SAAS,CAAC,OAAO,CAAC;QAClB,UAAU,CAAC,QAAQ,EAAE,GAAG;QACxB,UAAU,CAAC,SAAS,EAAE,GAAG;QACzB,UAAU,CAAC,OAAO,CAAC,GAAG,CACvB,CAAC;AACJ,CAAC;AAED;;EAEE;AACF,SAAS,WAAW,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,qBAAqB,CAAC,UAAsC;IAC1E,MAAM,aAAa,GAAG,iBAAiB,CAAC,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACxE,MAAM,UAAU,GAAG,eAAe,CAAC,aAAa,CAAC,CAAC;IAElD,MAAM,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAExD,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,GAAG,iBAAiB,CAAC;IAEpD,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,KAAK,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAC9E,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,KAAK,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACvF,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC7D,MAAM,aAAa,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAEtF,MAAM,eAAe,GAAG,UAAU,CAAC;QACjC,IAAI,EAAE,OAAO;QACb,CAAC,EAAE,GAAG;QACN,CAAC,EAAE,aAAa;QAChB,CAAC,EAAE,UAAU;KACd,CAAC,CAAC;IAEH,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,mBAAmB,CAAC,eAAe,CAAC,CAAC;IAErE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAC3D,CAAC","sourcesContent":["import type { ColorHarmony, GeneratedHarmonyRoleColors } from './harmony';\nimport type { HexColor } from './hex';\nimport { normalizeHueDegrees, oklchToHex, parseHexToOklch } from './internal-culori';\nimport { type ColorSwatch, type ColorSwatchDiagnostics, generateColorSwatch } from './swatches';\n\nexport const MIN_HUEFUL_CHROMA = 0.015;\n\nexport interface NeutralSwatchResult {\n neutralKeyColor: HexColor;\n neutral: ColorSwatch;\n diagnostics: ColorSwatchDiagnostics;\n}\n\nexport interface GeneratedNeutralMetadata {\n neutralKeyColor: HexColor;\n diagnostics: ColorSwatchDiagnostics;\n}\n\n/***\n Pick the harmony color that should tint the generated neutral swatch.\n*/\nfunction pickTintSourceHex(\n roleColors: GeneratedHarmonyRoleColors,\n harmony: ColorHarmony,\n): HexColor {\n const byMapping: Partial<Record<ColorHarmony, HexColor | undefined>> = {\n monochromatic: roleColors.primary.hex,\n complementary: roleColors.secondary?.hex,\n analogous: roleColors.tertiary?.hex,\n splitComplementary: roleColors.tertiary?.hex,\n triadic: roleColors.tertiary?.hex,\n tetradic: roleColors.tertiary?.hex,\n };\n\n return (\n byMapping[harmony] ??\n roleColors.tertiary?.hex ??\n roleColors.secondary?.hex ??\n roleColors.primary.hex\n );\n}\n\n/***\n Clamp a number to a finite minimum and maximum range.\n*/\nfunction clampNumber(value: number, min: number, max: number): number {\n if (!Number.isFinite(value)) return min;\n return Math.min(max, Math.max(min, value));\n}\n\n/***\n Generate a softly tinted neutral swatch from generated harmony role colors.\n*/\nexport function generateNeutralSwatch(roleColors: GeneratedHarmonyRoleColors): NeutralSwatchResult {\n const tintSourceHex = pickTintSourceHex(roleColors, roleColors.harmony);\n const tintSource = parseHexToOklch(tintSourceHex);\n\n const primary = parseHexToOklch(roleColors.primary.hex);\n\n const preferGray = tintSource.c < MIN_HUEFUL_CHROMA;\n\n const hue = roleColors.harmony === 'monochromatic' ? primary.h : tintSource.h;\n const sourceChroma = roleColors.harmony === 'monochromatic' ? primary.c : tintSource.c;\n const neutralHue = preferGray ? 0 : normalizeHueDegrees(hue);\n const neutralChroma = preferGray ? 0 : clampNumber(sourceChroma * 0.06, 0.004, 0.012);\n\n const neutralKeyColor = oklchToHex({\n mode: 'oklch',\n l: 0.6,\n c: neutralChroma,\n h: neutralHue,\n });\n\n const { swatch, diagnostics } = generateColorSwatch(neutralKeyColor);\n\n return { neutralKeyColor, neutral: swatch, diagnostics };\n}\n"]}
|
package/dist/swatches.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ export interface ColorSwatchDiagnostics {
|
|
|
20
20
|
max: number;
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
/***
|
|
24
|
+
Generate a full color swatch and diagnostics from a base color.
|
|
25
|
+
*/
|
|
23
26
|
export declare function generateColorSwatch(baseColor: HexColor): {
|
|
24
27
|
swatch: ColorSwatch;
|
|
25
28
|
diagnostics: ColorSwatchDiagnostics;
|
package/dist/swatches.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swatches.d.ts","sourceRoot":"","sources":["../src/swatches.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC,eAAO,MAAM,kBAAkB,iEAAkE,CAAC;AAClG,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,sBAAsB,EAAG,GAAY,CAAC;AAEnD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;AAE5D,MAAM,MAAM,sBAAsB,GAC9B,WAAW,GACX,qBAAqB,GACrB,yBAAyB,CAAC;AAE9B,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"swatches.d.ts","sourceRoot":"","sources":["../src/swatches.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC,eAAO,MAAM,kBAAkB,iEAAkE,CAAC;AAClG,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,sBAAsB,EAAG,GAAY,CAAC;AAEnD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;AAE5D,MAAM,MAAM,sBAAsB,GAC9B,WAAW,GACX,qBAAqB,GACrB,yBAAyB,CAAC;AAE9B,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;CACH;AAsCD;;EAEE;AACF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,QAAQ,GAAG;IACxD,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,sBAAsB,CAAC;CACrC,CAoFA"}
|
package/dist/swatches.js
CHANGED
|
@@ -16,11 +16,17 @@ const BASELINE_LIGHTNESS_BY_STEP = {
|
|
|
16
16
|
};
|
|
17
17
|
const MIN_USABLE_ADJACENT_DELTA = 0.012;
|
|
18
18
|
const MIN_USABLE_LIGHTNESS_RANGE = 0.35;
|
|
19
|
+
/***
|
|
20
|
+
Clamp a number into the normalized zero-to-one range.
|
|
21
|
+
*/
|
|
19
22
|
function clamp01(value) {
|
|
20
23
|
if (!Number.isFinite(value))
|
|
21
24
|
return 0;
|
|
22
25
|
return Math.min(1, Math.max(0, value));
|
|
23
26
|
}
|
|
27
|
+
/***
|
|
28
|
+
Return the chroma multiplier used for a swatch step.
|
|
29
|
+
*/
|
|
24
30
|
function chromaMultiplierForStep(step) {
|
|
25
31
|
if (step <= 200)
|
|
26
32
|
return 0.55;
|
|
@@ -32,6 +38,9 @@ function chromaMultiplierForStep(step) {
|
|
|
32
38
|
return 0.9;
|
|
33
39
|
return 0.8;
|
|
34
40
|
}
|
|
41
|
+
/***
|
|
42
|
+
Generate a full color swatch and diagnostics from a base color.
|
|
43
|
+
*/
|
|
35
44
|
export function generateColorSwatch(baseColor) {
|
|
36
45
|
const baseOklch = parseHexToOklch(baseColor);
|
|
37
46
|
const baseOffset = baseOklch.l - BASELINE_LIGHTNESS_BY_STEP[COLOR_SWATCH_BASE_STEP];
|
package/dist/swatches.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swatches.js","sourceRoot":"","sources":["../src/swatches.ts"],"names":[],"mappings":"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","sourcesContent":["import type { HexColor } from './hex';\nimport { deltaEoklch, oklchToHex, parseHexToOklch } from './internal-culori';\n\nexport const COLOR_SWATCH_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;\nexport type ColorSwatchStep = (typeof COLOR_SWATCH_STEPS)[number];\n\nexport const COLOR_SWATCH_BASE_STEP = 500 as const;\n\nexport type ColorSwatch = Record<ColorSwatchStep, HexColor>;\n\nexport type ColorSwatchWarningCode =\n | 'weak_step'\n | 'weak_adjacent_delta'\n | 'limited_lightness_range';\n\nexport interface ColorSwatchWarning {\n code: ColorSwatchWarningCode;\n step?: ColorSwatchStep;\n message: string;\n deltaEFromBase?: number;\n}\n\nexport interface ColorSwatchDiagnostics {\n isUsable: boolean;\n warnings: readonly ColorSwatchWarning[];\n minAdjacentDelta: number;\n maxAdjacentDelta: number;\n lightnessRange: {\n min: number;\n max: number;\n };\n}\n\nconst BASELINE_LIGHTNESS_BY_STEP: Record<ColorSwatchStep, number> = {\n 50: 0.985,\n 100: 0.967,\n 200: 0.928,\n 300: 0.872,\n 400: 0.707,\n 500: 0.551,\n 600: 0.446,\n 700: 0.373,\n 800: 0.278,\n 900: 0.21,\n 950: 0.13,\n};\n\nconst MIN_USABLE_ADJACENT_DELTA = 0.012;\nconst MIN_USABLE_LIGHTNESS_RANGE = 0.35;\n\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.min(1, Math.max(0, value));\n}\n\nfunction chromaMultiplierForStep(step: ColorSwatchStep): number {\n if (step <= 200) return 0.55;\n if (step <= 400) return 0.75;\n if (step === COLOR_SWATCH_BASE_STEP) return 1;\n if (step <= 700) return 0.9;\n return 0.8;\n}\n\nexport function generateColorSwatch(baseColor: HexColor): {\n swatch: ColorSwatch;\n diagnostics: ColorSwatchDiagnostics;\n} {\n const baseOklch = parseHexToOklch(baseColor);\n const baseOffset = baseOklch.l - BASELINE_LIGHTNESS_BY_STEP[COLOR_SWATCH_BASE_STEP];\n\n const warnings: ColorSwatchWarning[] = [];\n const swatch = {} as ColorSwatch;\n const lightnessEntries: number[] = [];\n const adjacentDeltas: number[] = [];\n\n for (const step of COLOR_SWATCH_STEPS) {\n if (step === COLOR_SWATCH_BASE_STEP) {\n swatch[step] = baseColor;\n lightnessEntries.push(baseOklch.l);\n continue;\n }\n\n const targetL = clamp01(BASELINE_LIGHTNESS_BY_STEP[step] + baseOffset);\n const targetC = clamp01(baseOklch.c * chromaMultiplierForStep(step));\n const target = { ...baseOklch, l: targetL, c: targetC };\n const hex = oklchToHex(target);\n swatch[step] = hex;\n\n const candidateOklch = parseHexToOklch(hex);\n lightnessEntries.push(candidateOklch.l);\n\n const deltaEFromBase = deltaEoklch(baseOklch, candidateOklch);\n if (deltaEFromBase < 0.02) {\n warnings.push({\n code: 'weak_step',\n step,\n deltaEFromBase,\n message: `Swatch step ${step} is visually close to the base color.`,\n });\n }\n }\n\n for (let index = 1; index < COLOR_SWATCH_STEPS.length; index++) {\n const previousStep = COLOR_SWATCH_STEPS[index - 1];\n const currentStep = COLOR_SWATCH_STEPS[index];\n if (previousStep === undefined || currentStep === undefined) continue;\n\n const previous = parseHexToOklch(swatch[previousStep]);\n const current = parseHexToOklch(swatch[currentStep]);\n const adjacentDelta = deltaEoklch(previous, current);\n adjacentDeltas.push(adjacentDelta);\n\n if (adjacentDelta < MIN_USABLE_ADJACENT_DELTA) {\n warnings.push({\n code: 'weak_adjacent_delta',\n step: currentStep,\n deltaEFromBase: adjacentDelta,\n message: `Swatch step ${currentStep} is visually close to adjacent step ${previousStep}.`,\n });\n }\n }\n\n const minAdjacentDelta = Math.min(...adjacentDeltas);\n const maxAdjacentDelta = Math.max(...adjacentDeltas);\n const minLightness = Math.min(...lightnessEntries);\n const maxLightness = Math.max(...lightnessEntries);\n const lightnessRange = maxLightness - minLightness;\n\n if (lightnessRange < MIN_USABLE_LIGHTNESS_RANGE) {\n warnings.push({\n code: 'limited_lightness_range',\n message: 'Generated swatch has a limited lightness range.',\n });\n }\n\n return {\n swatch,\n diagnostics: {\n isUsable:\n minAdjacentDelta >= MIN_USABLE_ADJACENT_DELTA &&\n lightnessRange >= MIN_USABLE_LIGHTNESS_RANGE,\n warnings,\n minAdjacentDelta,\n maxAdjacentDelta,\n lightnessRange: {\n min: minLightness,\n max: maxLightness,\n },\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"swatches.js","sourceRoot":"","sources":["../src/swatches.ts"],"names":[],"mappings":"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","sourcesContent":["import type { HexColor } from './hex';\nimport { deltaEoklch, oklchToHex, parseHexToOklch } from './internal-culori';\n\nexport const COLOR_SWATCH_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;\nexport type ColorSwatchStep = (typeof COLOR_SWATCH_STEPS)[number];\n\nexport const COLOR_SWATCH_BASE_STEP = 500 as const;\n\nexport type ColorSwatch = Record<ColorSwatchStep, HexColor>;\n\nexport type ColorSwatchWarningCode =\n | 'weak_step'\n | 'weak_adjacent_delta'\n | 'limited_lightness_range';\n\nexport interface ColorSwatchWarning {\n code: ColorSwatchWarningCode;\n step?: ColorSwatchStep;\n message: string;\n deltaEFromBase?: number;\n}\n\nexport interface ColorSwatchDiagnostics {\n isUsable: boolean;\n warnings: readonly ColorSwatchWarning[];\n minAdjacentDelta: number;\n maxAdjacentDelta: number;\n lightnessRange: {\n min: number;\n max: number;\n };\n}\n\nconst BASELINE_LIGHTNESS_BY_STEP: Record<ColorSwatchStep, number> = {\n 50: 0.985,\n 100: 0.967,\n 200: 0.928,\n 300: 0.872,\n 400: 0.707,\n 500: 0.551,\n 600: 0.446,\n 700: 0.373,\n 800: 0.278,\n 900: 0.21,\n 950: 0.13,\n};\n\nconst MIN_USABLE_ADJACENT_DELTA = 0.012;\nconst MIN_USABLE_LIGHTNESS_RANGE = 0.35;\n\n/***\n Clamp a number into the normalized zero-to-one range.\n*/\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.min(1, Math.max(0, value));\n}\n\n/***\n Return the chroma multiplier used for a swatch step.\n*/\nfunction chromaMultiplierForStep(step: ColorSwatchStep): number {\n if (step <= 200) return 0.55;\n if (step <= 400) return 0.75;\n if (step === COLOR_SWATCH_BASE_STEP) return 1;\n if (step <= 700) return 0.9;\n return 0.8;\n}\n\n/***\n Generate a full color swatch and diagnostics from a base color.\n*/\nexport function generateColorSwatch(baseColor: HexColor): {\n swatch: ColorSwatch;\n diagnostics: ColorSwatchDiagnostics;\n} {\n const baseOklch = parseHexToOklch(baseColor);\n const baseOffset = baseOklch.l - BASELINE_LIGHTNESS_BY_STEP[COLOR_SWATCH_BASE_STEP];\n\n const warnings: ColorSwatchWarning[] = [];\n const swatch = {} as ColorSwatch;\n const lightnessEntries: number[] = [];\n const adjacentDeltas: number[] = [];\n\n for (const step of COLOR_SWATCH_STEPS) {\n if (step === COLOR_SWATCH_BASE_STEP) {\n swatch[step] = baseColor;\n lightnessEntries.push(baseOklch.l);\n continue;\n }\n\n const targetL = clamp01(BASELINE_LIGHTNESS_BY_STEP[step] + baseOffset);\n const targetC = clamp01(baseOklch.c * chromaMultiplierForStep(step));\n const target = { ...baseOklch, l: targetL, c: targetC };\n const hex = oklchToHex(target);\n swatch[step] = hex;\n\n const candidateOklch = parseHexToOklch(hex);\n lightnessEntries.push(candidateOklch.l);\n\n const deltaEFromBase = deltaEoklch(baseOklch, candidateOklch);\n if (deltaEFromBase < 0.02) {\n warnings.push({\n code: 'weak_step',\n step,\n deltaEFromBase,\n message: `Swatch step ${step} is visually close to the base color.`,\n });\n }\n }\n\n for (let index = 1; index < COLOR_SWATCH_STEPS.length; index++) {\n const previousStep = COLOR_SWATCH_STEPS[index - 1];\n const currentStep = COLOR_SWATCH_STEPS[index];\n if (previousStep === undefined || currentStep === undefined) continue;\n\n const previous = parseHexToOklch(swatch[previousStep]);\n const current = parseHexToOklch(swatch[currentStep]);\n const adjacentDelta = deltaEoklch(previous, current);\n adjacentDeltas.push(adjacentDelta);\n\n if (adjacentDelta < MIN_USABLE_ADJACENT_DELTA) {\n warnings.push({\n code: 'weak_adjacent_delta',\n step: currentStep,\n deltaEFromBase: adjacentDelta,\n message: `Swatch step ${currentStep} is visually close to adjacent step ${previousStep}.`,\n });\n }\n }\n\n const minAdjacentDelta = Math.min(...adjacentDeltas);\n const maxAdjacentDelta = Math.max(...adjacentDeltas);\n const minLightness = Math.min(...lightnessEntries);\n const maxLightness = Math.max(...lightnessEntries);\n const lightnessRange = maxLightness - minLightness;\n\n if (lightnessRange < MIN_USABLE_LIGHTNESS_RANGE) {\n warnings.push({\n code: 'limited_lightness_range',\n message: 'Generated swatch has a limited lightness range.',\n });\n }\n\n return {\n swatch,\n diagnostics: {\n isUsable:\n minAdjacentDelta >= MIN_USABLE_ADJACENT_DELTA &&\n lightnessRange >= MIN_USABLE_LIGHTNESS_RANGE,\n warnings,\n minAdjacentDelta,\n maxAdjacentDelta,\n lightnessRange: {\n min: minLightness,\n max: maxLightness,\n },\n },\n };\n}\n"]}
|
package/dist/theme-colors.d.ts
CHANGED
|
@@ -22,8 +22,17 @@ export interface GeneratedThemeModeColors {
|
|
|
22
22
|
swatches: GeneratedThemeSwatches;
|
|
23
23
|
neutral: GeneratedNeutralMetadata;
|
|
24
24
|
}
|
|
25
|
+
/***
|
|
26
|
+
Parse the primary color configured for a theme mode.
|
|
27
|
+
*/
|
|
25
28
|
export declare function getThemeModePrimaryHex(mode: ThemeModeColorInput): HexColor;
|
|
29
|
+
/***
|
|
30
|
+
Generate harmony role colors, swatches, and neutral metadata for a theme mode.
|
|
31
|
+
*/
|
|
26
32
|
export declare function generateThemeModeColors(mode: ThemeModeColorInput): GeneratedThemeModeColors;
|
|
33
|
+
/***
|
|
34
|
+
Generate light and dark theme color outputs from theme color input.
|
|
35
|
+
*/
|
|
27
36
|
export declare function generateThemeColors(theme: ThemeColorInput): {
|
|
28
37
|
light: GeneratedThemeModeColors;
|
|
29
38
|
dark: GeneratedThemeModeColors;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-colors.d.ts","sourceRoot":"","sources":["../src/theme-colors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,0BAA0B,EAA6B,MAAM,WAAW,CAAC;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAE,KAAK,wBAAwB,EAAyB,MAAM,WAAW,CAAC;AACjF,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,YAAY,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,0BAA0B,CAAC,SAAS,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,IAAI,EAAE,mBAAmB,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,iBAAiB,EAAE,0BAA0B,CAAC;IAC9C,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,QAAQ,CAE1E;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,mBAAmB,GAAG,wBAAwB,CAgC3F;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG;IAC3D,KAAK,EAAE,wBAAwB,CAAC;IAChC,IAAI,EAAE,wBAAwB,CAAC;CAChC,CAKA"}
|
|
1
|
+
{"version":3,"file":"theme-colors.d.ts","sourceRoot":"","sources":["../src/theme-colors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,0BAA0B,EAA6B,MAAM,WAAW,CAAC;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAE,KAAK,wBAAwB,EAAyB,MAAM,WAAW,CAAC;AACjF,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,YAAY,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,0BAA0B,CAAC,SAAS,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,IAAI,EAAE,mBAAmB,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,iBAAiB,EAAE,0BAA0B,CAAC;IAC9C,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAED;;EAEE;AACF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,QAAQ,CAE1E;AAED;;EAEE;AACF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,mBAAmB,GAAG,wBAAwB,CAgC3F;AAED;;EAEE;AACF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG;IAC3D,KAAK,EAAE,wBAAwB,CAAC;IAChC,IAAI,EAAE,wBAAwB,CAAC;CAChC,CAKA"}
|
package/dist/theme-colors.js
CHANGED
|
@@ -2,9 +2,15 @@ import { generateHarmonyRoleColors } from './harmony';
|
|
|
2
2
|
import { parseHexColorOrThrow } from './hex';
|
|
3
3
|
import { generateNeutralSwatch } from './neutral';
|
|
4
4
|
import { generateColorSwatch } from './swatches';
|
|
5
|
+
/***
|
|
6
|
+
Parse the primary color configured for a theme mode.
|
|
7
|
+
*/
|
|
5
8
|
export function getThemeModePrimaryHex(mode) {
|
|
6
9
|
return parseHexColorOrThrow(mode.primaryColor);
|
|
7
10
|
}
|
|
11
|
+
/***
|
|
12
|
+
Generate harmony role colors, swatches, and neutral metadata for a theme mode.
|
|
13
|
+
*/
|
|
8
14
|
export function generateThemeModeColors(mode) {
|
|
9
15
|
const primaryHex = getThemeModePrimaryHex(mode);
|
|
10
16
|
const harmonyRoleColors = generateHarmonyRoleColors(primaryHex, mode.harmony);
|
|
@@ -35,6 +41,9 @@ export function generateThemeModeColors(mode) {
|
|
|
35
41
|
},
|
|
36
42
|
};
|
|
37
43
|
}
|
|
44
|
+
/***
|
|
45
|
+
Generate light and dark theme color outputs from theme color input.
|
|
46
|
+
*/
|
|
38
47
|
export function generateThemeColors(theme) {
|
|
39
48
|
return {
|
|
40
49
|
light: generateThemeModeColors(theme.light),
|
package/dist/theme-colors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-colors.js","sourceRoot":"","sources":["../src/theme-colors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEvF,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAiC,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EAAoB,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA0BnE,MAAM,UAAU,sBAAsB,CAAC,IAAyB;IAC9D,OAAO,oBAAoB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAyB;IAC/D,MAAM,UAAU,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,iBAAiB,GAAG,yBAAyB,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAE9E,MAAM,aAAa,GAAG,mBAAmB,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAChF,MAAM,eAAe,GAAG,iBAAiB,CAAC,SAAS;QACjD,CAAC,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,MAAM;QAC7D,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,cAAc,GAAG,iBAAiB,CAAC,QAAQ;QAC/C,CAAC,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM;QAC5D,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,UAAU;QACnD,CAAC,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM;QAC9D,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,OAAO,GAAG,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;IACzD,MAAM,QAAQ,GAA2B;QACvC,OAAO,EAAE,aAAa;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9D,CAAC;IAEF,OAAO;QACL,iBAAiB;QACjB,QAAQ;QACR,OAAO,EAAE;YACP,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAsB;IAIxD,OAAO;QACL,KAAK,EAAE,uBAAuB,CAAC,KAAK,CAAC,KAAK,CAAC;QAC3C,IAAI,EAAE,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC;KAC1C,CAAC;AACJ,CAAC","sourcesContent":["import { type GeneratedHarmonyRoleColors, generateHarmonyRoleColors } from './harmony';\nimport type { HexColor } from './hex';\nimport { parseHexColorOrThrow } from './hex';\nimport { type GeneratedNeutralMetadata, generateNeutralSwatch } from './neutral';\nimport { type ColorSwatch, generateColorSwatch } from './swatches';\n\nexport interface ThemeModeColorInput {\n primaryColor: string;\n harmony: GeneratedHarmonyRoleColors['harmony'];\n}\n\nexport interface ThemeColorInput {\n light: ThemeModeColorInput;\n dark: ThemeModeColorInput;\n}\n\nexport interface GeneratedThemeSwatches {\n primary: ColorSwatch;\n secondary?: ColorSwatch;\n tertiary?: ColorSwatch;\n quaternary?: ColorSwatch;\n neutral: ColorSwatch;\n}\n\nexport interface GeneratedThemeModeColors {\n harmonyRoleColors: GeneratedHarmonyRoleColors;\n swatches: GeneratedThemeSwatches;\n neutral: GeneratedNeutralMetadata;\n}\n\nexport function getThemeModePrimaryHex(mode: ThemeModeColorInput): HexColor {\n return parseHexColorOrThrow(mode.primaryColor);\n}\n\nexport function generateThemeModeColors(mode: ThemeModeColorInput): GeneratedThemeModeColors {\n const primaryHex = getThemeModePrimaryHex(mode);\n const harmonyRoleColors = generateHarmonyRoleColors(primaryHex, mode.harmony);\n\n const primarySwatch = generateColorSwatch(harmonyRoleColors.primary.hex).swatch;\n const secondarySwatch = harmonyRoleColors.secondary\n ? generateColorSwatch(harmonyRoleColors.secondary.hex).swatch\n : undefined;\n const tertiarySwatch = harmonyRoleColors.tertiary\n ? generateColorSwatch(harmonyRoleColors.tertiary.hex).swatch\n : undefined;\n const quaternarySwatch = harmonyRoleColors.quaternary\n ? generateColorSwatch(harmonyRoleColors.quaternary.hex).swatch\n : undefined;\n\n const neutral = generateNeutralSwatch(harmonyRoleColors);\n const swatches: GeneratedThemeSwatches = {\n primary: primarySwatch,\n neutral: neutral.neutral,\n ...(secondarySwatch ? { secondary: secondarySwatch } : {}),\n ...(tertiarySwatch ? { tertiary: tertiarySwatch } : {}),\n ...(quaternarySwatch ? { quaternary: quaternarySwatch } : {}),\n };\n\n return {\n harmonyRoleColors,\n swatches,\n neutral: {\n neutralKeyColor: neutral.neutralKeyColor,\n diagnostics: neutral.diagnostics,\n },\n };\n}\n\nexport function generateThemeColors(theme: ThemeColorInput): {\n light: GeneratedThemeModeColors;\n dark: GeneratedThemeModeColors;\n} {\n return {\n light: generateThemeModeColors(theme.light),\n dark: generateThemeModeColors(theme.dark),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"theme-colors.js","sourceRoot":"","sources":["../src/theme-colors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEvF,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAiC,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EAAoB,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA0BnE;;EAEE;AACF,MAAM,UAAU,sBAAsB,CAAC,IAAyB;IAC9D,OAAO,oBAAoB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AACjD,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,uBAAuB,CAAC,IAAyB;IAC/D,MAAM,UAAU,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,iBAAiB,GAAG,yBAAyB,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAE9E,MAAM,aAAa,GAAG,mBAAmB,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAChF,MAAM,eAAe,GAAG,iBAAiB,CAAC,SAAS;QACjD,CAAC,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,MAAM;QAC7D,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,cAAc,GAAG,iBAAiB,CAAC,QAAQ;QAC/C,CAAC,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM;QAC5D,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,UAAU;QACnD,CAAC,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM;QAC9D,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,OAAO,GAAG,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;IACzD,MAAM,QAAQ,GAA2B;QACvC,OAAO,EAAE,aAAa;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9D,CAAC;IAEF,OAAO;QACL,iBAAiB;QACjB,QAAQ;QACR,OAAO,EAAE;YACP,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC;KACF,CAAC;AACJ,CAAC;AAED;;EAEE;AACF,MAAM,UAAU,mBAAmB,CAAC,KAAsB;IAIxD,OAAO;QACL,KAAK,EAAE,uBAAuB,CAAC,KAAK,CAAC,KAAK,CAAC;QAC3C,IAAI,EAAE,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC;KAC1C,CAAC;AACJ,CAAC","sourcesContent":["import { type GeneratedHarmonyRoleColors, generateHarmonyRoleColors } from './harmony';\nimport type { HexColor } from './hex';\nimport { parseHexColorOrThrow } from './hex';\nimport { type GeneratedNeutralMetadata, generateNeutralSwatch } from './neutral';\nimport { type ColorSwatch, generateColorSwatch } from './swatches';\n\nexport interface ThemeModeColorInput {\n primaryColor: string;\n harmony: GeneratedHarmonyRoleColors['harmony'];\n}\n\nexport interface ThemeColorInput {\n light: ThemeModeColorInput;\n dark: ThemeModeColorInput;\n}\n\nexport interface GeneratedThemeSwatches {\n primary: ColorSwatch;\n secondary?: ColorSwatch;\n tertiary?: ColorSwatch;\n quaternary?: ColorSwatch;\n neutral: ColorSwatch;\n}\n\nexport interface GeneratedThemeModeColors {\n harmonyRoleColors: GeneratedHarmonyRoleColors;\n swatches: GeneratedThemeSwatches;\n neutral: GeneratedNeutralMetadata;\n}\n\n/***\n Parse the primary color configured for a theme mode.\n*/\nexport function getThemeModePrimaryHex(mode: ThemeModeColorInput): HexColor {\n return parseHexColorOrThrow(mode.primaryColor);\n}\n\n/***\n Generate harmony role colors, swatches, and neutral metadata for a theme mode.\n*/\nexport function generateThemeModeColors(mode: ThemeModeColorInput): GeneratedThemeModeColors {\n const primaryHex = getThemeModePrimaryHex(mode);\n const harmonyRoleColors = generateHarmonyRoleColors(primaryHex, mode.harmony);\n\n const primarySwatch = generateColorSwatch(harmonyRoleColors.primary.hex).swatch;\n const secondarySwatch = harmonyRoleColors.secondary\n ? generateColorSwatch(harmonyRoleColors.secondary.hex).swatch\n : undefined;\n const tertiarySwatch = harmonyRoleColors.tertiary\n ? generateColorSwatch(harmonyRoleColors.tertiary.hex).swatch\n : undefined;\n const quaternarySwatch = harmonyRoleColors.quaternary\n ? generateColorSwatch(harmonyRoleColors.quaternary.hex).swatch\n : undefined;\n\n const neutral = generateNeutralSwatch(harmonyRoleColors);\n const swatches: GeneratedThemeSwatches = {\n primary: primarySwatch,\n neutral: neutral.neutral,\n ...(secondarySwatch ? { secondary: secondarySwatch } : {}),\n ...(tertiarySwatch ? { tertiary: tertiarySwatch } : {}),\n ...(quaternarySwatch ? { quaternary: quaternarySwatch } : {}),\n };\n\n return {\n harmonyRoleColors,\n swatches,\n neutral: {\n neutralKeyColor: neutral.neutralKeyColor,\n diagnostics: neutral.diagnostics,\n },\n };\n}\n\n/***\n Generate light and dark theme color outputs from theme color input.\n*/\nexport function generateThemeColors(theme: ThemeColorInput): {\n light: GeneratedThemeModeColors;\n dark: GeneratedThemeModeColors;\n} {\n return {\n light: generateThemeModeColors(theme.light),\n dark: generateThemeModeColors(theme.dark),\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/color-theory",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"description": "Standalone color theory, harmony, swatch, contrast, and theme color generation utilities.",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/color-theory#readme",
|
|
7
7
|
"bugs": {
|
package/src/contrast.ts
CHANGED
|
@@ -10,6 +10,9 @@ export interface ReadableForegroundResult {
|
|
|
10
10
|
const BLACK = parseHexColorOrThrow('#000000');
|
|
11
11
|
const WHITE = parseHexColorOrThrow('#FFFFFF');
|
|
12
12
|
|
|
13
|
+
/***
|
|
14
|
+
Return the readable black or white foreground color with the stronger contrast against a background color.
|
|
15
|
+
*/
|
|
13
16
|
export function getReadableForeground(background: HexColor): ReadableForegroundResult {
|
|
14
17
|
const contrastOnBlack = contrastRatio(background, BLACK);
|
|
15
18
|
const contrastOnWhite = contrastRatio(background, WHITE);
|
package/src/harmony.ts
CHANGED
|
@@ -48,6 +48,9 @@ const OFFSETS_BY_HARMONY: Record<ColorHarmony, readonly number[]> = {
|
|
|
48
48
|
tetradic: [0, 90, 180, 270],
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
/***
|
|
52
|
+
Generate role-based harmony colors from a primary color and harmony strategy.
|
|
53
|
+
*/
|
|
51
54
|
export function generateHarmonyRoleColors(
|
|
52
55
|
primaryColor: HexColor,
|
|
53
56
|
harmony: ColorHarmony,
|
package/src/hex.ts
CHANGED
|
@@ -2,15 +2,24 @@ export type HexColor = string & { readonly __hexColorBrand: unique symbol };
|
|
|
2
2
|
|
|
3
3
|
const HEX6_CASE_INSENSITIVE_REGEX = /^#[0-9a-fA-F]{6}$/;
|
|
4
4
|
|
|
5
|
+
/***
|
|
6
|
+
Check whether a string is a valid six-digit hex color.
|
|
7
|
+
*/
|
|
5
8
|
export function isHexColor(value: string): value is HexColor {
|
|
6
9
|
return HEX6_CASE_INSENSITIVE_REGEX.test(value);
|
|
7
10
|
}
|
|
8
11
|
|
|
12
|
+
/***
|
|
13
|
+
Parse a string as a six-digit hex color and return null when it is invalid.
|
|
14
|
+
*/
|
|
9
15
|
export function parseHexColor(value: string): HexColor | null {
|
|
10
16
|
if (!HEX6_CASE_INSENSITIVE_REGEX.test(value)) return null;
|
|
11
17
|
return value as HexColor;
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
/***
|
|
21
|
+
Parse a string as a six-digit hex color or throw when it is invalid.
|
|
22
|
+
*/
|
|
14
23
|
export function parseHexColorOrThrow(value: string): HexColor {
|
|
15
24
|
const parsed = parseHexColor(value);
|
|
16
25
|
if (!parsed) {
|
|
@@ -19,9 +28,9 @@ export function parseHexColorOrThrow(value: string): HexColor {
|
|
|
19
28
|
return parsed;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
/***
|
|
32
|
+
Assert that a string is a valid six-digit hex color.
|
|
33
|
+
*/
|
|
25
34
|
export function assertHexColor(value: string): asserts value is HexColor {
|
|
26
35
|
if (!isHexColor(value)) {
|
|
27
36
|
throw new Error(`Invalid hex color (expected #RRGGBB or #rrggbb): ${JSON.stringify(value)}`);
|
package/src/internal-culori.ts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
converter,
|
|
3
|
-
differenceEuclidean,
|
|
4
|
-
formatHex,
|
|
5
|
-
toGamut,
|
|
6
|
-
wcagContrast,
|
|
7
|
-
wcagLuminance,
|
|
8
|
-
} from 'culori';
|
|
1
|
+
import { converter, differenceEuclidean, formatHex, toGamut, wcagContrast } from 'culori';
|
|
9
2
|
|
|
10
3
|
import type { HexColor } from './hex';
|
|
11
4
|
import { parseHexColorOrThrow } from './hex';
|
|
12
5
|
|
|
13
|
-
|
|
6
|
+
interface OklchColor {
|
|
14
7
|
mode: 'oklch';
|
|
15
8
|
l: number;
|
|
16
9
|
c: number;
|
|
@@ -18,7 +11,7 @@ export interface OklchColor {
|
|
|
18
11
|
alpha?: number;
|
|
19
12
|
}
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
interface RgbColor {
|
|
22
15
|
mode: 'rgb';
|
|
23
16
|
r: number;
|
|
24
17
|
g: number;
|
|
@@ -27,20 +20,28 @@ export interface RgbColor {
|
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
const toOklch = converter('oklch');
|
|
30
|
-
const toRgb = converter('rgb');
|
|
31
23
|
const toRgbGamut = toGamut('rgb', 'oklch');
|
|
32
24
|
const deltaE = differenceEuclidean('oklch');
|
|
33
25
|
|
|
26
|
+
/***
|
|
27
|
+
Clamp a number to a finite minimum and maximum range.
|
|
28
|
+
*/
|
|
34
29
|
function clampNumber(value: number, min: number, max: number): number {
|
|
35
30
|
if (!Number.isFinite(value)) return min;
|
|
36
31
|
return Math.min(max, Math.max(min, value));
|
|
37
32
|
}
|
|
38
33
|
|
|
34
|
+
/***
|
|
35
|
+
Normalize hue degrees into the zero-to-three-sixty range.
|
|
36
|
+
*/
|
|
39
37
|
export function normalizeHueDegrees(hueDegrees: number): number {
|
|
40
38
|
const hue = ((hueDegrees % 360) + 360) % 360;
|
|
41
39
|
return hue === 360 ? 0 : hue;
|
|
42
40
|
}
|
|
43
41
|
|
|
42
|
+
/***
|
|
43
|
+
Convert a hex color into a normalized OKLCH color object.
|
|
44
|
+
*/
|
|
44
45
|
export function parseHexToOklch(hex: HexColor): OklchColor {
|
|
45
46
|
const color = toOklch(hex) as unknown as OklchColor | undefined;
|
|
46
47
|
if (color?.mode !== 'oklch') {
|
|
@@ -54,47 +55,25 @@ export function parseHexToOklch(hex: HexColor): OklchColor {
|
|
|
54
55
|
return { mode: 'oklch', l, c, h, alpha: color.alpha };
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/***
|
|
59
|
+
Convert an OKLCH color object into an in-gamut hex color.
|
|
60
|
+
*/
|
|
57
61
|
export function oklchToHex(oklch: OklchColor): HexColor {
|
|
58
62
|
const gamutRgb = toRgbGamut(oklch) as unknown as RgbColor | undefined;
|
|
59
63
|
const hex = formatHex(gamutRgb ?? oklch);
|
|
60
64
|
return parseHexColorOrThrow(hex);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
throw new Error(`Failed to parse/convert to OKLCH: ${JSON.stringify(input)}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const l = clampNumber(color.l, 0, 1);
|
|
70
|
-
const c = clampNumber(color.c, 0, 0.4);
|
|
71
|
-
const h = normalizeHueDegrees(Number.isFinite(color.h) ? color.h : 0);
|
|
72
|
-
|
|
73
|
-
return { mode: 'oklch', l, c, h, alpha: color.alpha };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function toHexColor(input: string | object): HexColor {
|
|
77
|
-
const gamutRgb = toRgbGamut(input as never) as unknown as RgbColor | undefined;
|
|
78
|
-
const hex = formatHex(gamutRgb ?? (input as never));
|
|
79
|
-
return parseHexColorOrThrow(hex);
|
|
80
|
-
}
|
|
81
|
-
|
|
67
|
+
/***
|
|
68
|
+
Calculate the perceptual OKLCH distance between two OKLCH colors.
|
|
69
|
+
*/
|
|
82
70
|
export function deltaEoklch(a: OklchColor, b: OklchColor): number {
|
|
83
71
|
return deltaE(a, b);
|
|
84
72
|
}
|
|
85
73
|
|
|
74
|
+
/***
|
|
75
|
+
Calculate the WCAG contrast ratio between two color inputs.
|
|
76
|
+
*/
|
|
86
77
|
export function contrastRatio(colorA: string | object, colorB: string | object): number {
|
|
87
78
|
return wcagContrast(colorA as never, colorB as never);
|
|
88
79
|
}
|
|
89
|
-
|
|
90
|
-
export function relativeLuminance(color: string | object): number {
|
|
91
|
-
return wcagLuminance(color as never);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function rgbFrom(input: string | object): RgbColor {
|
|
95
|
-
const rgb = toRgb(input as never) as unknown as RgbColor | undefined;
|
|
96
|
-
if (rgb?.mode !== 'rgb') {
|
|
97
|
-
throw new Error(`Failed to convert to rgb: ${JSON.stringify(input)}`);
|
|
98
|
-
}
|
|
99
|
-
return rgb;
|
|
100
|
-
}
|
package/src/neutral.ts
CHANGED
|
@@ -16,6 +16,9 @@ export interface GeneratedNeutralMetadata {
|
|
|
16
16
|
diagnostics: ColorSwatchDiagnostics;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/***
|
|
20
|
+
Pick the harmony color that should tint the generated neutral swatch.
|
|
21
|
+
*/
|
|
19
22
|
function pickTintSourceHex(
|
|
20
23
|
roleColors: GeneratedHarmonyRoleColors,
|
|
21
24
|
harmony: ColorHarmony,
|
|
@@ -37,11 +40,17 @@ function pickTintSourceHex(
|
|
|
37
40
|
);
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
/***
|
|
44
|
+
Clamp a number to a finite minimum and maximum range.
|
|
45
|
+
*/
|
|
40
46
|
function clampNumber(value: number, min: number, max: number): number {
|
|
41
47
|
if (!Number.isFinite(value)) return min;
|
|
42
48
|
return Math.min(max, Math.max(min, value));
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
/***
|
|
52
|
+
Generate a softly tinted neutral swatch from generated harmony role colors.
|
|
53
|
+
*/
|
|
45
54
|
export function generateNeutralSwatch(roleColors: GeneratedHarmonyRoleColors): NeutralSwatchResult {
|
|
46
55
|
const tintSourceHex = pickTintSourceHex(roleColors, roleColors.harmony);
|
|
47
56
|
const tintSource = parseHexToOklch(tintSourceHex);
|
package/src/swatches.ts
CHANGED
|
@@ -48,11 +48,17 @@ const BASELINE_LIGHTNESS_BY_STEP: Record<ColorSwatchStep, number> = {
|
|
|
48
48
|
const MIN_USABLE_ADJACENT_DELTA = 0.012;
|
|
49
49
|
const MIN_USABLE_LIGHTNESS_RANGE = 0.35;
|
|
50
50
|
|
|
51
|
+
/***
|
|
52
|
+
Clamp a number into the normalized zero-to-one range.
|
|
53
|
+
*/
|
|
51
54
|
function clamp01(value: number): number {
|
|
52
55
|
if (!Number.isFinite(value)) return 0;
|
|
53
56
|
return Math.min(1, Math.max(0, value));
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
/***
|
|
60
|
+
Return the chroma multiplier used for a swatch step.
|
|
61
|
+
*/
|
|
56
62
|
function chromaMultiplierForStep(step: ColorSwatchStep): number {
|
|
57
63
|
if (step <= 200) return 0.55;
|
|
58
64
|
if (step <= 400) return 0.75;
|
|
@@ -61,6 +67,9 @@ function chromaMultiplierForStep(step: ColorSwatchStep): number {
|
|
|
61
67
|
return 0.8;
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
/***
|
|
71
|
+
Generate a full color swatch and diagnostics from a base color.
|
|
72
|
+
*/
|
|
64
73
|
export function generateColorSwatch(baseColor: HexColor): {
|
|
65
74
|
swatch: ColorSwatch;
|
|
66
75
|
diagnostics: ColorSwatchDiagnostics;
|
package/src/theme-colors.ts
CHANGED
|
@@ -28,10 +28,16 @@ export interface GeneratedThemeModeColors {
|
|
|
28
28
|
neutral: GeneratedNeutralMetadata;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/***
|
|
32
|
+
Parse the primary color configured for a theme mode.
|
|
33
|
+
*/
|
|
31
34
|
export function getThemeModePrimaryHex(mode: ThemeModeColorInput): HexColor {
|
|
32
35
|
return parseHexColorOrThrow(mode.primaryColor);
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
/***
|
|
39
|
+
Generate harmony role colors, swatches, and neutral metadata for a theme mode.
|
|
40
|
+
*/
|
|
35
41
|
export function generateThemeModeColors(mode: ThemeModeColorInput): GeneratedThemeModeColors {
|
|
36
42
|
const primaryHex = getThemeModePrimaryHex(mode);
|
|
37
43
|
const harmonyRoleColors = generateHarmonyRoleColors(primaryHex, mode.harmony);
|
|
@@ -66,6 +72,9 @@ export function generateThemeModeColors(mode: ThemeModeColorInput): GeneratedThe
|
|
|
66
72
|
};
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
/***
|
|
76
|
+
Generate light and dark theme color outputs from theme color input.
|
|
77
|
+
*/
|
|
69
78
|
export function generateThemeColors(theme: ThemeColorInput): {
|
|
70
79
|
light: GeneratedThemeModeColors;
|
|
71
80
|
dark: GeneratedThemeModeColors;
|