@edelstone/tints-and-shades 0.1.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.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @edelstone/tints-and-shades
2
+
3
+ Deterministic tint and shade generator for 6-character hex colors.
4
+ Used internally by the [Tint & Shade Generator](https://maketintsandshades.com) and published as a standalone API.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm install @edelstone/tints-and-shades
10
+ ```
11
+
12
+ ## API
13
+
14
+ ```ts
15
+ calculateTints(colorValue: string, steps?: number[]): ScaleColor[]
16
+ calculateShades(colorValue: string, steps?: number[]): ScaleColor[]
17
+ ```
18
+
19
+ ```ts
20
+ type ScaleColor = {
21
+ hex: string;
22
+ ratio: number;
23
+ percent: number;
24
+ };
25
+ ```
26
+
27
+ ### Parameters
28
+
29
+ - **colorValue**
30
+ 6-character hex string without `#`, e.g. `3b82f6`
31
+ Must be a valid 6-character hex value.
32
+
33
+ - **steps** (optional)
34
+ Array of finite numeric mix ratios.
35
+ Example: `[0, 0.1, 0.2, 0.3]`
36
+
37
+ If omitted, the default steps are:
38
+
39
+ ```js
40
+ [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
41
+ ```
42
+
43
+ ## Returns
44
+
45
+ An array of `ScaleColor` objects:
46
+
47
+ - `hex`: 6-character hex string (without `#`)
48
+ - `ratio`: numeric step ratio used for the mix
49
+ - `percent`: `ratio` expressed as a percentage (rounded to 1 decimal)
50
+
51
+ ## Example
52
+
53
+ ```js
54
+ import { calculateTints, calculateShades }
55
+ from "@edelstone/tints-and-shades";
56
+
57
+ const tints = calculateTints("000000", [0, 0.5, 1]);
58
+ const shades = calculateShades("ffffff", [0, 0.5, 1]);
59
+ ```
60
+
61
+ Tints output:
62
+
63
+ ```json
64
+ [
65
+ { "hex": "000000", "ratio": 0, "percent": 0 },
66
+ { "hex": "808080", "ratio": 0.5, "percent": 50 },
67
+ { "hex": "ffffff", "ratio": 1, "percent": 100 }
68
+ ]
69
+ ```
70
+
71
+ Shades output:
72
+
73
+ ```json
74
+ [
75
+ { "hex": "ffffff", "ratio": 0, "percent": 0 },
76
+ { "hex": "808080", "ratio": 0.5, "percent": 50 },
77
+ { "hex": "000000", "ratio": 1, "percent": 100 }
78
+ ]
79
+ ```
80
+
81
+ ## Validation
82
+
83
+ - `colorValue` must be a 6-character hex string (no `#`).
84
+ - Invalid values throw a `TypeError`.
85
+ - `steps` must be an array of finite numbers.
86
+ - Invalid `steps` input throws a `TypeError`.
87
+
88
+ ## Learn More
89
+
90
+ - Calculation method and rationale: [Tint & Shade Generator docs](https://maketintsandshades.com/about/#calculation-method)
91
+
92
+ ## Development
93
+
94
+ From repo root:
95
+
96
+ ```bash
97
+ npm run build:api
98
+ npm run test:api
99
+ ```
100
+
101
+ From package directory:
102
+
103
+ ```bash
104
+ npm run build
105
+ npm run test
106
+ ```
@@ -0,0 +1,7 @@
1
+ export type ScaleColor = {
2
+ hex: string;
3
+ ratio: number;
4
+ percent: number;
5
+ };
6
+ export declare const calculateShades: (colorValue: string, steps?: number[]) => ScaleColor[];
7
+ export declare const calculateTints: (colorValue: string, steps?: number[]) => ScaleColor[];
@@ -0,0 +1,59 @@
1
+ const pad = (number, length) => {
2
+ let str = number.toString();
3
+ while (str.length < length) {
4
+ str = "0" + str;
5
+ }
6
+ return str;
7
+ };
8
+ const hexToRGB = (colorValue) => ({
9
+ red: parseInt(colorValue.slice(0, 2), 16),
10
+ green: parseInt(colorValue.slice(2, 4), 16),
11
+ blue: parseInt(colorValue.slice(4, 6), 16)
12
+ });
13
+ const intToHex = (rgbint) => pad(Math.min(Math.max(Math.round(rgbint), 0), 255).toString(16), 2);
14
+ const rgbToHex = (rgb) => intToHex(rgb.red) + intToHex(rgb.green) + intToHex(rgb.blue);
15
+ const DEFAULT_STEPS = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
16
+ const validateColorValue = (colorValue) => {
17
+ if (typeof colorValue !== "string" || colorValue.length !== 6 || !/^[0-9a-fA-F]{6}$/.test(colorValue)) {
18
+ throw new TypeError("colorValue must be a 6-character hex string without '#'.");
19
+ }
20
+ };
21
+ const resolveSteps = (steps) => {
22
+ if (typeof steps === "undefined")
23
+ return DEFAULT_STEPS;
24
+ if (!Array.isArray(steps)) {
25
+ throw new TypeError("steps must be an array of numbers.");
26
+ }
27
+ if (!steps.every((step) => typeof step === "number" && Number.isFinite(step))) {
28
+ throw new TypeError("steps must be an array of numbers.");
29
+ }
30
+ return steps;
31
+ };
32
+ const mixChannel = (from, to, ratio) => from + (to - from) * ratio;
33
+ const calculateScale = (colorValue, steps, mixFn) => {
34
+ validateColorValue(colorValue);
35
+ const stepRatios = resolveSteps(steps);
36
+ const color = hexToRGB(colorValue);
37
+ const values = [];
38
+ for (const ratio of stepRatios) {
39
+ const rgb = mixFn(color, ratio);
40
+ values.push({
41
+ hex: rgbToHex(rgb),
42
+ ratio,
43
+ percent: Number((ratio * 100).toFixed(1))
44
+ });
45
+ }
46
+ return values;
47
+ };
48
+ const rgbShade = (rgb, ratio) => ({
49
+ red: mixChannel(rgb.red, 0, ratio),
50
+ green: mixChannel(rgb.green, 0, ratio),
51
+ blue: mixChannel(rgb.blue, 0, ratio)
52
+ });
53
+ const rgbTint = (rgb, ratio) => ({
54
+ red: mixChannel(rgb.red, 255, ratio),
55
+ green: mixChannel(rgb.green, 255, ratio),
56
+ blue: mixChannel(rgb.blue, 255, ratio)
57
+ });
58
+ export const calculateShades = (colorValue, steps) => calculateScale(colorValue, steps, rgbShade);
59
+ export const calculateTints = (colorValue, steps) => calculateScale(colorValue, steps, rgbTint);
@@ -0,0 +1,2 @@
1
+ export { calculateTints, calculateShades } from "./generator.js";
2
+ export type { ScaleColor } from "./generator.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { calculateTints, calculateShades } from "./generator.js";
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@edelstone/tints-and-shades",
3
+ "version": "0.1.0",
4
+ "description": "Core tint and shade calculation API used by tints-and-shades.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "sideEffects": false,
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "clean": "rm -rf dist",
25
+ "test": "npm run build && node --test test/*.test.mjs",
26
+ "prepublishOnly": "npm run clean && npm run build"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^5.7.0"
30
+ }
31
+ }