Initial Commit
This commit is contained in:
32
packages/media/image/colour-convert-hsluv/README.md
Normal file
32
packages/media/image/colour-convert-hsluv/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# @suaveplan/colour-convert-hsluv - HSLuv perceptually uniform colour space: conversions and harmony generators
|
||||
|
||||
HSLuv perceptually-uniform colour space with gamut-mapped ray intersection for sRGB. This plugin provides bidirectional RGB↔HSLuv/HPLuv conversions and uniform colour harmonies (complementary, triad, tetrad) that preserve perceptual lightness across hue rotations.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [colour-convert-hsluv](./src/colour-convert-hsluv/colour-convert-hsluv.md) — `colour-convert-hsluv.ts` implements the HSLuv perceptually uniform colour space. Unlike HSL, which stretches and compr…
|
||||
- [colour-convert-hsluv](./src/colour-convert-hsluv/colour-convert-hsluv.md) — `colour-convert-hsluv.ts` implements the HSLuv perceptually uniform colour space. Unlike HSL, which stretches and compr…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/colour-convert-hsluv
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```ts
|
||||
import { rgbToHsluv, hsluvToRgb, uniformTriad } from "@suaveplan/colour-convert-hsluv";
|
||||
|
||||
const hsluv = rgbToHsluv({ r: 255, g: 0, b: 0 });
|
||||
console.log(hsluv); // { h: ~12.2, s: ~100, l: ~53.2 }
|
||||
|
||||
const colours = uniformTriad({ r: 59, g: 130, b: 246 });
|
||||
console.log(colours); // [primary, secondary, tertiary]
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/colour-convert-hsluv/colour-convert-hsluv.md`](./src/colour-convert-hsluv/colour-convert-hsluv.md) — HSLuv/HPLuv conversion and uniform harmonies implementation
|
||||
61
packages/media/image/colour-convert-hsluv/package.json
Normal file
61
packages/media/image/colour-convert-hsluv/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@suaveplan/colour-convert-hsluv",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"description": "HSLuv perceptually uniform colour space: conversions and harmony generators",
|
||||
"license": "MIT",
|
||||
"author": "Suaveplan Team",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"./colour-convert-hsluv/colour-convert-hsluv": {
|
||||
"import": {
|
||||
"types": "./dist/colour-convert-hsluv/colour-convert-hsluv.d.ts",
|
||||
"default": "./dist/colour-convert-hsluv/colour-convert-hsluv.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage --passWithNoTests",
|
||||
"lint": "bunx biome check src/",
|
||||
"lint:fix": "bunx biome lint --fix --unsafe --max-diagnostics 500 src/",
|
||||
"format": "bunx biome format --write --max-diagnostics 500 src/",
|
||||
"check": "bunx biome check --write --unsafe --max-diagnostics 500 src/",
|
||||
"typecheck": "bunx tsc --noEmit --composite false -p tsconfig.lib.json",
|
||||
"docs:check": "bun run ../../../../scripts/docs-check.ts ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@suaveplan/colours": "workspace:^",
|
||||
"tslib": "catalog:build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@suaveplan/testing": "workspace:^",
|
||||
"@types/node": "catalog:",
|
||||
"@vitest/coverage-istanbul": "catalog:testing",
|
||||
"typescript": "catalog:",
|
||||
"vite": "catalog:build",
|
||||
"vite-plugin-dts": "catalog:build",
|
||||
"vitest": "catalog:testing"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://npmjs.fq.io/",
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# colour-convert-hsluv
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`colour-convert-hsluv.ts` implements the HSLuv perceptually uniform colour space. Unlike HSL, which stretches and compresses hue angles unevenly across lightness levels, HSLuv guarantees that a given saturation percentage produces the same visual intensity regardless of hue. This makes it ideal for generating harmonious colour palettes and accessible UI themes.
|
||||
|
||||
## Features
|
||||
|
||||
- RGB ↔ HSLuv bidirectional conversion with gamut mapping
|
||||
- RGB ↔ HPLuv (pastel variant) bidirectional conversion
|
||||
- Uniform colour harmonies: complementary, triad, and tetrad
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { rgbToHsluv, hsluvToRgb } from "@suaveplan/colour-convert-hsluv";
|
||||
|
||||
const hsluv = rgbToHsluv({ r: 255, g: 0, b: 0 });
|
||||
const back = hsluvToRgb(hsluv);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { uniformTriad } from "@suaveplan/colour-convert-hsluv";
|
||||
|
||||
const colours = uniformTriad({ r: 59, g: 130, b: 246 });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Conversions
|
||||
|
||||
- `rgbToHsluv(rgb: RGB): HSLUV`
|
||||
- `hsluvToRgb(hsluv: HSLUV): RGBA`
|
||||
- `rgbToHpluv(rgb: RGB): HPLUV`
|
||||
- `hpluvToRgb(hpluv: HPLUV): RGBA`
|
||||
|
||||
### Harmonies
|
||||
|
||||
- `uniformComplementary(rgb: RGB): RGB[]`
|
||||
- `uniformTriad(rgb: RGB): RGB[]`
|
||||
- `uniformTetrad(rgb: RGB): RGB[]`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The HSLuv algorithm computes the maximum chroma for a given hue by intersecting rays from the white point with the sRGB gamut boundary in CIELUV space. Six rays are tested (one per RGB cube edge) and the minimum positive intersection distance becomes the saturation scale factor. This ensures that 100% saturation always touches the gamut boundary, regardless of hue or lightness.
|
||||
|
||||
All conversion pipelines are deterministic and free of side effects. The module has no runtime dependencies beyond the base `@suaveplan/colours` types, making it suitable for both server-side rendering and client-side bundling. Gamut mapping is performed automatically during RGB to HSLuv conversion, so out-of-range inputs are handled gracefully without throwing exceptions.
|
||||
@@ -0,0 +1,114 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
hpluvToRgb,
|
||||
hsluvToRgb,
|
||||
rgbToHpluv,
|
||||
rgbToHsluv,
|
||||
uniformComplementary,
|
||||
uniformTetrad,
|
||||
uniformTriad,
|
||||
} from "./colour-convert-hsluv.js";
|
||||
|
||||
describe("colour-convert-hsluv", () => {
|
||||
it("round-trips red", { timeout: 5000 }, () => {
|
||||
const rgb = { r: 255, g: 0, b: 0 };
|
||||
const hsluv = rgbToHsluv(rgb);
|
||||
const back = hsluvToRgb(hsluv);
|
||||
expect(back.r).toBeCloseTo(rgb.r, -1);
|
||||
expect(back.g).toBeCloseTo(rgb.g, -1);
|
||||
expect(back.b).toBeCloseTo(rgb.b, -1);
|
||||
});
|
||||
|
||||
it("round-trips green", { timeout: 5000 }, () => {
|
||||
const rgb = { r: 0, g: 255, b: 0 };
|
||||
const hsluv = rgbToHsluv(rgb);
|
||||
const back = hsluvToRgb(hsluv);
|
||||
expect(back.r).toBeCloseTo(rgb.r, -1);
|
||||
expect(back.g).toBeCloseTo(rgb.g, -1);
|
||||
expect(back.b).toBeCloseTo(rgb.b, -1);
|
||||
});
|
||||
|
||||
it("round-trips blue", { timeout: 5000 }, () => {
|
||||
const rgb = { r: 0, g: 0, b: 255 };
|
||||
const hsluv = rgbToHsluv(rgb);
|
||||
const back = hsluvToRgb(hsluv);
|
||||
expect(back.r).toBeCloseTo(rgb.r, -1);
|
||||
expect(back.g).toBeCloseTo(rgb.g, -1);
|
||||
expect(back.b).toBeCloseTo(rgb.b, -1);
|
||||
});
|
||||
|
||||
it("round-trips gray", { timeout: 5000 }, () => {
|
||||
const rgb = { r: 128, g: 128, b: 128 };
|
||||
const hsluv = rgbToHsluv(rgb);
|
||||
const back = hsluvToRgb(hsluv);
|
||||
expect(back.r).toBeCloseTo(rgb.r, -1);
|
||||
expect(back.g).toBeCloseTo(rgb.g, -1);
|
||||
expect(back.b).toBeCloseTo(rgb.b, -1);
|
||||
});
|
||||
|
||||
it("uniformComplementary returns 2 colours", { timeout: 5000 }, () => {
|
||||
const colours = uniformComplementary({ r: 255, g: 0, b: 0 });
|
||||
expect(colours).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("uniformTriad returns 3 colours", { timeout: 5000 }, () => {
|
||||
const colours = uniformTriad({ r: 255, g: 0, b: 0 });
|
||||
expect(colours).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("uniformTetrad returns 4 colours", { timeout: 5000 }, () => {
|
||||
const colours = uniformTetrad({ r: 255, g: 0, b: 0 });
|
||||
expect(colours).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("handles black", { timeout: 5000 }, () => {
|
||||
const hsluv = rgbToHsluv({ r: 0, g: 0, b: 0 });
|
||||
expect(hsluv.l).toBeCloseTo(0, 0);
|
||||
});
|
||||
|
||||
it("handles white", { timeout: 5000 }, () => {
|
||||
const hsluv = rgbToHsluv({ r: 255, g: 255, b: 255 });
|
||||
expect(hsluv.l).toBeCloseTo(100, 0);
|
||||
});
|
||||
|
||||
it("round-trips hpluv red", { timeout: 5000 }, () => {
|
||||
const original = { r: 255, g: 0, b: 0 };
|
||||
const hpluv = rgbToHpluv(original);
|
||||
const back = hpluvToRgb(hpluv);
|
||||
expect(back.r).toBeCloseTo(original.r, -1);
|
||||
expect(back.g).toBeCloseTo(original.g, -1);
|
||||
expect(back.b).toBeCloseTo(original.b, -1);
|
||||
});
|
||||
|
||||
it("hpluv handles black", { timeout: 5000 }, () => {
|
||||
const hpluv = rgbToHpluv({ r: 0, g: 0, b: 0 });
|
||||
expect(hpluv.l).toBeCloseTo(0, 0);
|
||||
});
|
||||
|
||||
it("hpluv handles white", { timeout: 5000 }, () => {
|
||||
const hpluv = rgbToHpluv({ r: 255, g: 255, b: 255 });
|
||||
expect(hpluv.l).toBeCloseTo(100, 0);
|
||||
});
|
||||
|
||||
it("handles hsluv black (l=0)", { timeout: 5000 }, () => {
|
||||
const rgb = hsluvToRgb({ h: 0, s: 0, l: 0 });
|
||||
expect(rgb.r).toBe(0);
|
||||
expect(rgb.g).toBe(0);
|
||||
expect(rgb.b).toBe(0);
|
||||
});
|
||||
|
||||
it("handles hsluv dark grey (l=5)", { timeout: 5000 }, () => {
|
||||
const rgb = hsluvToRgb({ h: 0, s: 0, l: 5 });
|
||||
expect(rgb.r).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles hsluv hue zero", { timeout: 5000 }, () => {
|
||||
const rgb = hsluvToRgb({ h: 0, s: 100, l: 50 });
|
||||
expect(rgb.r).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles denom zero hue", { timeout: 5000 }, () => {
|
||||
const rgb = hsluvToRgb({ h: 149.5479321378151, s: 100, l: 50 });
|
||||
expect(rgb.g).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,220 @@
|
||||
import type { RGB, RGBA } from "@suaveplan/colours";
|
||||
|
||||
const INVERSE_SRGB_XYZ: [number[], number[], number[]] = [
|
||||
[3.2406, -1.5372, -0.4986],
|
||||
[-0.9689, 1.8758, 0.0415],
|
||||
[0.0557, -0.204, 1.057],
|
||||
];
|
||||
|
||||
const EPSILON = 0.008856;
|
||||
const KAPPA = 903.3;
|
||||
|
||||
export interface HSLUV {
|
||||
readonly h: number;
|
||||
readonly s: number;
|
||||
readonly l: number;
|
||||
}
|
||||
|
||||
export interface HPLUV {
|
||||
readonly h: number;
|
||||
readonly s: number;
|
||||
readonly l: number;
|
||||
}
|
||||
|
||||
function toRad(deg: number): number {
|
||||
return (deg * Math.PI) / 180;
|
||||
}
|
||||
|
||||
function maxChromaForLh(L: number, hue: number): number {
|
||||
if (L > 99.9999999) return 0;
|
||||
if (L < 0.00000001) return 0;
|
||||
|
||||
const s1 = (L + 16) / 116;
|
||||
const s2 = s1 > 6 / 29 ? s1 * s1 * s1 : L / KAPPA;
|
||||
const m = INVERSE_SRGB_XYZ;
|
||||
const rays: { m: number; b: number }[] = [];
|
||||
|
||||
for (let c = 0; c < 3; c++) {
|
||||
const row = m[c]!;
|
||||
const m1 = row[0]!;
|
||||
const m2 = row[1]!;
|
||||
const m3 = row[2]!;
|
||||
for (let t = 0; t < 2; t++) {
|
||||
const top1 = (284517 * m1 - 94839 * m3) * s2;
|
||||
const top2 =
|
||||
(838422 * m3 + 769860 * m2 + 731718 * m1) * L * s2 -
|
||||
769860 * t * L;
|
||||
const bottom = (632260 * m3 - 126452 * m2) * s2 + 126452 * t;
|
||||
rays.push({ m: top1 / bottom, b: top2 / bottom });
|
||||
}
|
||||
}
|
||||
|
||||
let min = Number.MAX_VALUE;
|
||||
const hrad = toRad(hue);
|
||||
|
||||
for (const ray of rays) {
|
||||
const denom = Math.sin(hrad) - ray.m * Math.cos(hrad);
|
||||
if (denom !== 0) {
|
||||
const length = ray.b / denom;
|
||||
if (length >= 0 && length < min) {
|
||||
min = length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
function xyzToLuv({ x, y, z }: { x: number; y: number; z: number }): {
|
||||
l: number;
|
||||
u: number;
|
||||
v: number;
|
||||
} {
|
||||
const refY = 1.0;
|
||||
const refU = 0.19783;
|
||||
const refV = 0.46832;
|
||||
const yr = y / refY;
|
||||
const l = yr > EPSILON ? 116 * Math.cbrt(yr) - 16 : KAPPA * yr;
|
||||
const up = x + 15 * y + 3 * z === 0 ? 0 : (4 * x) / (x + 15 * y + 3 * z);
|
||||
const vp = x + 15 * y + 3 * z === 0 ? 0 : (9 * y) / (x + 15 * y + 3 * z);
|
||||
return {
|
||||
l,
|
||||
u: 13 * l * (up - refU),
|
||||
v: 13 * l * (vp - refV),
|
||||
};
|
||||
}
|
||||
|
||||
function luvToXyz({ l, u, v }: { l: number; u: number; v: number }): {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
} {
|
||||
if (l === 0) return { x: 0, y: 0, z: 0 };
|
||||
const refY = 1.0;
|
||||
const refU = 0.19783;
|
||||
const refV = 0.46832;
|
||||
const up = u / (13 * l) + refU;
|
||||
const vp = v / (13 * l) + refV;
|
||||
const y = l > 8 ? refY * ((l + 16) / 116) ** 3 : (refY * l) / KAPPA;
|
||||
const x = y * ((9 * up) / (4 * vp));
|
||||
const z = y * ((12 - 3 * up - 20 * vp) / (4 * vp));
|
||||
return { x, y, z };
|
||||
}
|
||||
|
||||
function lchToLuv({ l, c, h }: { l: number; c: number; h: number }): {
|
||||
l: number;
|
||||
u: number;
|
||||
v: number;
|
||||
} {
|
||||
const hr = toRad(h);
|
||||
return { l, u: c * Math.cos(hr), v: c * Math.sin(hr) };
|
||||
}
|
||||
|
||||
function luvToLch({ l, u, v }: { l: number; u: number; v: number }): {
|
||||
l: number;
|
||||
c: number;
|
||||
h: number;
|
||||
} {
|
||||
const c = Math.sqrt(u * u + v * v);
|
||||
let h = (Math.atan2(v, u) * 180) / Math.PI;
|
||||
if (h < 0) h += 360;
|
||||
return { l, c, h };
|
||||
}
|
||||
|
||||
function xyzToRgb({ x, y, z }: { x: number; y: number; z: number }): RGB {
|
||||
const R = x * 3.2406 + y * -1.5372 + z * -0.4986;
|
||||
const G = x * -0.9689 + y * 1.8758 + z * 0.0415;
|
||||
const B = x * 0.0557 + y * -0.204 + z * 1.057;
|
||||
const fromLinear = (c: number) => {
|
||||
c = c > 0.0031308 ? 1.055 * c ** (1 / 2.4) - 0.055 : 12.92 * c;
|
||||
return Math.round(Math.max(0, Math.min(255, c * 255)));
|
||||
};
|
||||
return { r: fromLinear(R), g: fromLinear(G), b: fromLinear(B) };
|
||||
}
|
||||
|
||||
function rgbToXyz({ r, g, b }: RGB): { x: number; y: number; z: number } {
|
||||
const toLinear = (c: number) => {
|
||||
c /= 255;
|
||||
return c > 0.04045 ? ((c + 0.055) / 1.055) ** 2.4 : c / 12.92;
|
||||
};
|
||||
const R = toLinear(r);
|
||||
const G = toLinear(g);
|
||||
const B = toLinear(b);
|
||||
return {
|
||||
x: R * 0.4124 + G * 0.3576 + B * 0.1805,
|
||||
y: R * 0.2126 + G * 0.7152 + B * 0.0722,
|
||||
z: R * 0.0193 + G * 0.1192 + B * 0.9505,
|
||||
};
|
||||
}
|
||||
|
||||
export function hsluvToRgb(hsluv: HSLUV): RGBA {
|
||||
const max = maxChromaForLh(hsluv.l, hsluv.h);
|
||||
const lch = {
|
||||
l: hsluv.l,
|
||||
c: (max * hsluv.s) / 100,
|
||||
h: hsluv.h,
|
||||
};
|
||||
const luv = lchToLuv(lch);
|
||||
const xyz = luvToXyz(luv);
|
||||
return { ...xyzToRgb(xyz), a: 1 };
|
||||
}
|
||||
|
||||
export function rgbToHsluv(rgb: RGB): HSLUV {
|
||||
const xyz = rgbToXyz(rgb);
|
||||
const luv = xyzToLuv(xyz);
|
||||
const lch = luvToLch(luv);
|
||||
const max = maxChromaForLh(lch.l, lch.h);
|
||||
return {
|
||||
h: lch.h,
|
||||
s: (lch.c / max) * 100,
|
||||
l: lch.l,
|
||||
};
|
||||
}
|
||||
|
||||
export function hpluvToRgb(hpluv: HPLUV): RGBA {
|
||||
const max = maxChromaForLh(50, hpluv.h);
|
||||
const lch = {
|
||||
l: hpluv.l,
|
||||
c: (max * hpluv.s) / 100,
|
||||
h: hpluv.h,
|
||||
};
|
||||
const luv = lchToLuv(lch);
|
||||
const xyz = luvToXyz(luv);
|
||||
return { ...xyzToRgb(xyz), a: 1 };
|
||||
}
|
||||
|
||||
export function rgbToHpluv(rgb: RGB): HPLUV {
|
||||
const xyz = rgbToXyz(rgb);
|
||||
const luv = xyzToLuv(xyz);
|
||||
const lch = luvToLch(luv);
|
||||
const max = maxChromaForLh(50, lch.h);
|
||||
return {
|
||||
h: lch.h,
|
||||
s: (lch.c / max) * 100,
|
||||
l: lch.l,
|
||||
};
|
||||
}
|
||||
|
||||
export function uniformComplementary(rgb: RGB): RGB[] {
|
||||
const hsluv = rgbToHsluv(rgb);
|
||||
return [hsluvToRgb({ ...hsluv, h: (hsluv.h + 180) % 360 }), rgb];
|
||||
}
|
||||
|
||||
export function uniformTriad(rgb: RGB): RGB[] {
|
||||
const hsluv = rgbToHsluv(rgb);
|
||||
return [
|
||||
rgb,
|
||||
hsluvToRgb({ ...hsluv, h: (hsluv.h + 120) % 360 }),
|
||||
hsluvToRgb({ ...hsluv, h: (hsluv.h + 240) % 360 }),
|
||||
];
|
||||
}
|
||||
|
||||
export function uniformTetrad(rgb: RGB): RGB[] {
|
||||
const hsluv = rgbToHsluv(rgb);
|
||||
return [
|
||||
rgb,
|
||||
hsluvToRgb({ ...hsluv, h: (hsluv.h + 90) % 360 }),
|
||||
hsluvToRgb({ ...hsluv, h: (hsluv.h + 180) % 360 }),
|
||||
hsluvToRgb({ ...hsluv, h: (hsluv.h + 270) % 360 }),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export type {
|
||||
HPLUV,
|
||||
HSLUV,
|
||||
} from "./colour-convert-hsluv.js";
|
||||
export {
|
||||
hpluvToRgb,
|
||||
hsluvToRgb,
|
||||
rgbToHpluv,
|
||||
rgbToHsluv,
|
||||
uniformComplementary,
|
||||
uniformTetrad,
|
||||
uniformTriad,
|
||||
} from "./colour-convert-hsluv.js";
|
||||
10
packages/media/image/colour-convert-hsluv/src/index.ts
Normal file
10
packages/media/image/colour-convert-hsluv/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export type { HPLUV, HSLUV } from "./colour-convert-hsluv/index.js";
|
||||
export {
|
||||
hpluvToRgb,
|
||||
hsluvToRgb,
|
||||
rgbToHpluv,
|
||||
rgbToHsluv,
|
||||
uniformComplementary,
|
||||
uniformTetrad,
|
||||
uniformTriad,
|
||||
} from "./colour-convert-hsluv/index.js";
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("dep-graph", () => {
|
||||
it("only depends on allowed @suaveplan packages", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const pkg = await import("../package.json");
|
||||
const deps = Object.keys(pkg.default.dependencies || {});
|
||||
const allowed = ["@suaveplan/colours"];
|
||||
for (const dep of deps) {
|
||||
if (dep.startsWith("@suaveplan/")) {
|
||||
expect(allowed).toContain(dep);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
// no polyfills needed
|
||||
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("tree-shake", () => {
|
||||
it("has sideEffects false", { timeout: 5000 }, async () => {
|
||||
const pkg = await import("../package.json");
|
||||
expect(pkg.default.sideEffects).toBe(false);
|
||||
});
|
||||
|
||||
it("has at least one subpath export", { timeout: 5000 }, async () => {
|
||||
const pkg = await import("../package.json");
|
||||
const exports = Object.keys(pkg.default.exports || {});
|
||||
expect(exports.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
15
packages/media/image/colour-convert-hsluv/tsconfig.lib.json
Normal file
15
packages/media/image/colour-convert-hsluv/tsconfig.lib.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["src/**/*.test.ts", "dist", "node_modules"]
|
||||
}
|
||||
19
packages/media/image/colour-convert-hsluv/vite.config.ts
Normal file
19
packages/media/image/colour-convert-hsluv/vite.config.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: {
|
||||
index: "./src/index.ts",
|
||||
"colour-convert-hsluv/colour-convert-hsluv": "./src/colour-convert-hsluv/index.ts",
|
||||
},
|
||||
formats: ["es"],
|
||||
},
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
external: [/^@suaveplan\//, "tslib"],
|
||||
},
|
||||
},
|
||||
plugins: [dts({ rollupTypes: false, include: ["src/**/*.ts"], tsconfigPath: "./tsconfig.lib.json" })],
|
||||
});
|
||||
27
packages/media/image/colour-convert-hsluv/vitest.config.ts
Normal file
27
packages/media/image/colour-convert-hsluv/vitest.config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: false,
|
||||
environment: "node",
|
||||
pool: "forks",
|
||||
setupFiles: ["./tests/setup/polyfills.ts"],
|
||||
include: ["src/**/*.test.ts", "tests/**/*.test.ts"],
|
||||
exclude: ["src/__benchmarks__/**", "dist/**", "node_modules/**"],
|
||||
coverage: {
|
||||
provider: "istanbul",
|
||||
reporter: ["text", "html", "lcov"],
|
||||
include: ["src/**/*.ts"],
|
||||
exclude: [
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.bench.ts",
|
||||
"src/**/*.schemas.ts",
|
||||
"src/**/index.ts",
|
||||
"src/__benchmarks__/**",
|
||||
"tests/**",
|
||||
],
|
||||
thresholds: { statements: 100, branches: 100, functions: 100, lines: 100 },
|
||||
},
|
||||
testTimeout: 5000,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user