@marianmeres/stuic 2.1.7 → 2.1.9
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/dist/components/Backdrop/Backdrop.svelte +7 -27
- package/dist/components/Button/Button.svelte +1 -1
- package/dist/components/Button/Button.svelte.d.ts +1 -1
- package/dist/components/ModalDialog/ModalDialog.svelte +12 -24
- package/dist/index.css +2 -2
- package/dist/utils/body-scroll-locker.d.ts +9 -0
- package/dist/utils/body-scroll-locker.js +76 -0
- package/dist/utils/colors.d.ts +56 -0
- package/dist/utils/colors.js +92 -0
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +2 -2
- package/package.json +1 -1
- package/dist/utils/hex-to-oklch.d.ts +0 -1
- package/dist/utils/hex-to-oklch.js +0 -53
- package/dist/utils/hex-to-rgb.d.ts +0 -6
- package/dist/utils/hex-to-rgb.js +0 -12
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
const _instances = [];
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
1
|
<script lang="ts">
|
|
6
2
|
import {
|
|
3
|
+
BodyScroll,
|
|
7
4
|
focusTrap as focusTrapAction,
|
|
8
5
|
twMerge,
|
|
9
6
|
waitForNextRepaint,
|
|
@@ -11,7 +8,7 @@
|
|
|
11
8
|
} from "../../index.js";
|
|
12
9
|
import { createClog } from "@marianmeres/clog";
|
|
13
10
|
import { PressedKeys, watch } from "runed";
|
|
14
|
-
import { type Snippet } from "svelte";
|
|
11
|
+
import { onDestroy, onMount, tick, type Snippet } from "svelte";
|
|
15
12
|
import { fade } from "svelte/transition";
|
|
16
13
|
|
|
17
14
|
const clog = createClog("Backdrop").debug;
|
|
@@ -94,32 +91,15 @@
|
|
|
94
91
|
}
|
|
95
92
|
);
|
|
96
93
|
|
|
97
|
-
// lock body scroll when open and restore back
|
|
98
|
-
let _original: any = {};
|
|
99
94
|
$effect(() => {
|
|
100
95
|
if (noScrollLock) return;
|
|
101
|
-
|
|
102
|
-
_original = window.getComputedStyle(document.body);
|
|
103
|
-
const scrollY = window.scrollY;
|
|
104
|
-
|
|
105
|
-
document.body.style.position = "fixed";
|
|
106
|
-
document.body.style.top = `-${scrollY}px`;
|
|
107
|
-
document.body.style.width = "100%";
|
|
108
|
-
document.body.style.overflow = "hidden";
|
|
109
|
-
} else {
|
|
110
|
-
const scrollY = document.body.style.top;
|
|
111
|
-
|
|
112
|
-
document.body.style.position = _original.position;
|
|
113
|
-
document.body.style.position = "";
|
|
114
|
-
document.body.style.top = "";
|
|
115
|
-
document.body.style.width = "";
|
|
116
|
-
document.body.style.overflow = "";
|
|
117
|
-
|
|
118
|
-
// Restore scroll position
|
|
119
|
-
window.scrollTo(0, parseInt(scrollY || "0") * -1);
|
|
120
|
-
}
|
|
96
|
+
visible ? BodyScroll.lock() : BodyScroll.unlock();
|
|
121
97
|
});
|
|
122
98
|
|
|
99
|
+
// we need onDestroy as well
|
|
100
|
+
// Note, that this will also reset if nested... (which is not desired, but ignoring)
|
|
101
|
+
onDestroy(BodyScroll.unlock);
|
|
102
|
+
|
|
123
103
|
$effect(() => {
|
|
124
104
|
function onkeydown(e: KeyboardEvent) {
|
|
125
105
|
if (e.key === "Escape" && typeof onEscape === "function") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const BUTTON_STUIC_BASE_CLASSES = "\n\t\tbg-button-bg text-button-text \n\t\tdark:bg-button-bg-dark dark:text-button-text-dark\n\t\tfont-mono text-sm text-center \n\t\tleading-none\n\t\tborder-1\n\t\tborder-button-border dark:border-button-border-dark\n\t\trounded-md\n\t\tinline-flex items-center justify-center gap-x-2\n\t\tpx-3 py-2\n\t\tselect-none\n\n\t\thover:brightness-105\n\t\tactive:brightness-95\n\t\tdisabled:hover:brightness-100\n\n\t\tfocus:brightness-105\n\t\tfocus:border-button-border-focus focus:dark:border-button-border-focus-dark\n\n\t\t focus:outline-4 focus:outline-black/10 focus:dark:outline-white/20\n\t\tfocus-visible:outline-4 focus-visible:outline-black/10 focus-visible:dark:outline-white/20\n\t";
|
|
1
|
+
export declare const BUTTON_STUIC_BASE_CLASSES = "\n\t\tbg-button-bg text-button-text \n\t\tdark:bg-button-bg-dark dark:text-button-text-dark\n\t\tfont-mono text-sm text-center \n\t\tleading-none\n\t\tborder-1\n\t\tborder-button-border dark:border-button-border-dark\n\t\trounded-md\n\t\tinline-flex items-center justify-center gap-x-2\n\t\tpx-3 py-2\n\t\tselect-none\n\n\t\thover:brightness-105\n\t\tactive:brightness-95\n\t\tdisabled:hover:brightness-100 disabled:opacity-50\n\n\t\tfocus:brightness-105\n\t\tfocus:border-button-border-focus focus:dark:border-button-border-focus-dark\n\n\t\t focus:outline-4 focus:outline-black/10 focus:dark:outline-white/20\n\t\tfocus-visible:outline-4 focus-visible:outline-black/10 focus-visible:dark:outline-white/20\n\t";
|
|
2
2
|
export declare const BUTTON_STUIC_PRESET_CLASSES: any;
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
4
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onClickOutside } from "runed";
|
|
3
|
-
import { onMount, tick, type Snippet } from "svelte";
|
|
3
|
+
import { onDestroy, onMount, tick, type Snippet } from "svelte";
|
|
4
4
|
import { focusTrap } from "../../actions/focus-trap.js";
|
|
5
5
|
import { stopPropagation } from "../../utils/event-modifiers.js";
|
|
6
6
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
7
7
|
import { createClog } from "@marianmeres/clog";
|
|
8
8
|
import { waitForNextRepaint } from "../../utils/paint.js";
|
|
9
|
+
import { BodyScroll } from "../../utils/body-scroll-locker.js";
|
|
9
10
|
|
|
10
11
|
const clog = createClog("ModalDialog").debug;
|
|
11
12
|
|
|
@@ -35,7 +36,9 @@
|
|
|
35
36
|
preClose,
|
|
36
37
|
}: Props = $props();
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
// important to start as undefined (because of scroll save/restore)
|
|
40
|
+
let visible: boolean | undefined = $state(undefined);
|
|
41
|
+
|
|
39
42
|
let dialog = $state<HTMLDialogElement>()!;
|
|
40
43
|
let box = $state<HTMLDivElement>()!;
|
|
41
44
|
let _opener: undefined | null | HTMLElement = $state();
|
|
@@ -76,31 +79,16 @@
|
|
|
76
79
|
() => !noClickOutsideClose && close()
|
|
77
80
|
);
|
|
78
81
|
|
|
79
|
-
let _original: any = {};
|
|
80
82
|
$effect(() => {
|
|
81
|
-
// if (
|
|
82
|
-
if (visible)
|
|
83
|
-
|
|
84
|
-
const scrollY = window.scrollY;
|
|
85
|
-
|
|
86
|
-
document.body.style.position = "fixed";
|
|
87
|
-
document.body.style.top = `-${scrollY}px`;
|
|
88
|
-
document.body.style.width = "100%";
|
|
89
|
-
document.body.style.overflow = "hidden";
|
|
90
|
-
} else {
|
|
91
|
-
const scrollY = document.body.style.top;
|
|
92
|
-
|
|
93
|
-
document.body.style.position = _original.position;
|
|
94
|
-
document.body.style.position = "";
|
|
95
|
-
document.body.style.top = "";
|
|
96
|
-
document.body.style.width = "";
|
|
97
|
-
document.body.style.overflow = "";
|
|
98
|
-
|
|
99
|
-
// Restore scroll position
|
|
100
|
-
window.scrollTo(0, parseInt(scrollY || "0") * -1);
|
|
101
|
-
}
|
|
83
|
+
// noop if we're undefined ($effect runs immediately as onMount)
|
|
84
|
+
if (visible === undefined) return;
|
|
85
|
+
visible ? BodyScroll.lock() : BodyScroll.unlock();
|
|
102
86
|
});
|
|
103
87
|
|
|
88
|
+
// we need onDestroy as well
|
|
89
|
+
// Note, that this will also reset if nested... (which is not desired, but ignoring)
|
|
90
|
+
onDestroy(BodyScroll.unlock);
|
|
91
|
+
|
|
104
92
|
// $inspect("Modal dialog mounted, is visible:", visible).with(clog);
|
|
105
93
|
</script>
|
|
106
94
|
|
package/dist/index.css
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
@source "./";
|
|
7
7
|
|
|
8
8
|
/* "components" looks like a better fit here, but forms plugin uses "utilities"
|
|
9
|
-
so, since we need to override, sticking with that*/
|
|
9
|
+
so, since we need to override, sticking with that */
|
|
10
10
|
@layer utilities {
|
|
11
11
|
@import "./actions/tooltip/index.css";
|
|
12
12
|
@import "./components/Button/index.css";
|
|
@@ -29,6 +29,6 @@ so, since we need to override, sticking with that*/
|
|
|
29
29
|
[role="button"]:disabled,
|
|
30
30
|
input:disabled {
|
|
31
31
|
cursor: not-allowed !important;
|
|
32
|
-
/* opacity: 0.5 !important; */
|
|
32
|
+
/* opacity: 0.5 !important; moved to Button itself, so it can be overridden */
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createClog } from "@marianmeres/clog";
|
|
2
|
+
const clog = createClog("BodyScroll").debug;
|
|
3
|
+
/**
|
|
4
|
+
* Helper for "locking" and "unlocking" body scroll (window.scrollY) position
|
|
5
|
+
*/
|
|
6
|
+
export class BodyScroll {
|
|
7
|
+
static lock() {
|
|
8
|
+
const data = document.body.dataset;
|
|
9
|
+
// Only save the scroll position if it hasn't been saved already
|
|
10
|
+
if (data.originalScrollY === undefined) {
|
|
11
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
12
|
+
// Save body styles as serialized json
|
|
13
|
+
data.originalScrollStyleBackup = BodyScroll._get_body_style();
|
|
14
|
+
// Save the original scroll position in dataset
|
|
15
|
+
data.originalScrollY = `${scrollY}`;
|
|
16
|
+
data.scrollLockCount = "1";
|
|
17
|
+
// Apply the fixed positioning
|
|
18
|
+
document.body.style.position = "fixed";
|
|
19
|
+
document.body.style.top = `-${scrollY}px`;
|
|
20
|
+
document.body.style.width = "100%";
|
|
21
|
+
document.body.style.overflow = "hidden";
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Another component already locked the scroll, just increment the counter
|
|
25
|
+
const currentCount = parseInt(data.scrollLockCount, 10);
|
|
26
|
+
data.scrollLockCount = `${currentCount + 1}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
static unlock() {
|
|
30
|
+
const data = document.body.dataset;
|
|
31
|
+
// Only proceed if scroll is currently locked
|
|
32
|
+
if (data.scrollLockCount !== undefined) {
|
|
33
|
+
const count = parseInt(data.scrollLockCount, 10);
|
|
34
|
+
if (count > 1) {
|
|
35
|
+
// Other components still need the lock, just decrement the counter
|
|
36
|
+
data.scrollLockCount = `${count - 1}`;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// This is the last component, restore everything
|
|
40
|
+
const originalScrollY = parseInt(data.originalScrollY, 10);
|
|
41
|
+
BodyScroll._restore_body_styles(data.originalScrollStyleBackup);
|
|
42
|
+
// Remove our data attributes
|
|
43
|
+
delete data.originalScrollY;
|
|
44
|
+
delete data.originalScrollStyleBackup;
|
|
45
|
+
delete data.scrollLockCount;
|
|
46
|
+
// Restore the scroll position
|
|
47
|
+
window.scrollTo(0, originalScrollY);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
static _get_body_style() {
|
|
52
|
+
// we want only explicitly defined, not computed
|
|
53
|
+
const style = document.body.style;
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
position: style.position || null,
|
|
56
|
+
top: style.position || null,
|
|
57
|
+
width: style.width || null,
|
|
58
|
+
overflow: style.overflow || null,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
static _restore_body_styles(originalJsonString) {
|
|
62
|
+
let original = { position: null, top: null, width: null, overflow: null };
|
|
63
|
+
try {
|
|
64
|
+
original = JSON.parse(originalJsonString);
|
|
65
|
+
}
|
|
66
|
+
catch (e) { }
|
|
67
|
+
["position", "top", "width", "overflow"].forEach((k) => {
|
|
68
|
+
if (original[k] !== null) {
|
|
69
|
+
document.body.style[k] = original[k];
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
document.body.style.removeProperty(k);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a CSS HEX color string to an Oklch color string.
|
|
3
|
+
*/
|
|
4
|
+
export declare function hexToOklch(hex: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Converts a sRGB (0-255) to an Oklch color string.
|
|
7
|
+
*/
|
|
8
|
+
export declare function rgbToOklch(rgb: {
|
|
9
|
+
r: number;
|
|
10
|
+
g: number;
|
|
11
|
+
b: number;
|
|
12
|
+
}): string;
|
|
13
|
+
/**
|
|
14
|
+
* Converts a HEX string to an RGB object.
|
|
15
|
+
*/
|
|
16
|
+
export declare function hexToRgb(hex: string): {
|
|
17
|
+
r: number;
|
|
18
|
+
g: number;
|
|
19
|
+
b: number;
|
|
20
|
+
} | null;
|
|
21
|
+
/**
|
|
22
|
+
* Converts sRGB (0-255) to linear RGB (0.0-1.0).
|
|
23
|
+
*/
|
|
24
|
+
export declare function srgbToLinearRgb({ r, g, b }: {
|
|
25
|
+
r: number;
|
|
26
|
+
g: number;
|
|
27
|
+
b: number;
|
|
28
|
+
}): {
|
|
29
|
+
r: number;
|
|
30
|
+
g: number;
|
|
31
|
+
b: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Converts linear RGB (0.0-1.0) to Oklab.
|
|
35
|
+
*/
|
|
36
|
+
export declare function linearRgbToOklab({ r, g, b }: {
|
|
37
|
+
r: number;
|
|
38
|
+
g: number;
|
|
39
|
+
b: number;
|
|
40
|
+
}): {
|
|
41
|
+
l: number;
|
|
42
|
+
a: number;
|
|
43
|
+
b: number;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Converts Oklab to Oklch.
|
|
47
|
+
*/
|
|
48
|
+
export declare function oklabToOklch({ l, a, b }: {
|
|
49
|
+
l: number;
|
|
50
|
+
a: number;
|
|
51
|
+
b: number;
|
|
52
|
+
}): {
|
|
53
|
+
l: number;
|
|
54
|
+
c: number;
|
|
55
|
+
h: number;
|
|
56
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a CSS HEX color string to an Oklch color string.
|
|
3
|
+
*/
|
|
4
|
+
export function hexToOklch(hex) {
|
|
5
|
+
// 1. Parse HEX to RGB
|
|
6
|
+
const rgb = hexToRgb(hex);
|
|
7
|
+
if (!rgb) {
|
|
8
|
+
throw new Error("Invalid HEX color format.");
|
|
9
|
+
}
|
|
10
|
+
// 2. Convert sRGB (0-255) to oklab
|
|
11
|
+
return rgbToOklch(rgb);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Converts a sRGB (0-255) to an Oklch color string.
|
|
15
|
+
*/
|
|
16
|
+
export function rgbToOklch(rgb) {
|
|
17
|
+
// 1. Convert sRGB (0-255) to Linear RGB (0.0-1.0)
|
|
18
|
+
const linearRgb = srgbToLinearRgb(rgb);
|
|
19
|
+
// 2. Convert Linear RGB to Oklab
|
|
20
|
+
const oklab = linearRgbToOklab(linearRgb);
|
|
21
|
+
// 3. Convert Oklab to Oklch
|
|
22
|
+
const oklch = oklabToOklch(oklab);
|
|
23
|
+
// 4. Format as CSS string
|
|
24
|
+
// L is 0-1, formatted as 0-100%
|
|
25
|
+
// C is 0-0.4 (approx), formatted as a number
|
|
26
|
+
// h is 0-360, formatted as a number (degrees)
|
|
27
|
+
const l = (oklch.l * 100).toFixed(1);
|
|
28
|
+
const c = oklch.c.toFixed(3);
|
|
29
|
+
// Handle hue: NaN becomes 0
|
|
30
|
+
const h = isNaN(oklch.h) ? "0" : oklch.h.toFixed(1);
|
|
31
|
+
return `oklch(${l}% ${c} ${h})`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Converts a HEX string to an RGB object.
|
|
35
|
+
*/
|
|
36
|
+
export function hexToRgb(hex) {
|
|
37
|
+
let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
38
|
+
hex = hex.replace(shorthandRegex, (m, r, g, b) => {
|
|
39
|
+
return r + r + g + g + b + b;
|
|
40
|
+
});
|
|
41
|
+
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
42
|
+
return result
|
|
43
|
+
? {
|
|
44
|
+
r: parseInt(result[1], 16),
|
|
45
|
+
g: parseInt(result[2], 16),
|
|
46
|
+
b: parseInt(result[3], 16),
|
|
47
|
+
}
|
|
48
|
+
: null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Converts sRGB (0-255) to linear RGB (0.0-1.0).
|
|
52
|
+
*/
|
|
53
|
+
export function srgbToLinearRgb({ r, g, b }) {
|
|
54
|
+
const convert = (val) => {
|
|
55
|
+
let v = val / 255;
|
|
56
|
+
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
r: convert(r),
|
|
60
|
+
g: convert(g),
|
|
61
|
+
b: convert(b),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Converts linear RGB (0.0-1.0) to Oklab.
|
|
66
|
+
*/
|
|
67
|
+
export function linearRgbToOklab({ r, g, b }) {
|
|
68
|
+
// Based on the conversion matrices from the Oklab color space specification
|
|
69
|
+
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
70
|
+
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
71
|
+
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
72
|
+
const l_ = Math.cbrt(l);
|
|
73
|
+
const m_ = Math.cbrt(m);
|
|
74
|
+
const s_ = Math.cbrt(s);
|
|
75
|
+
return {
|
|
76
|
+
l: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
|
77
|
+
a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
|
|
78
|
+
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Converts Oklab to Oklch.
|
|
83
|
+
*/
|
|
84
|
+
export function oklabToOklch({ l, a, b }) {
|
|
85
|
+
const c = Math.sqrt(a * a + b * b);
|
|
86
|
+
let h = Math.atan2(b, a) * (180 / Math.PI);
|
|
87
|
+
// Normalize hue to be between 0 and 360
|
|
88
|
+
if (h < 0) {
|
|
89
|
+
h += 360;
|
|
90
|
+
}
|
|
91
|
+
return { l, c, h };
|
|
92
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
export * from "./body-scroll-locker.js";
|
|
1
2
|
export * from "./breakpoint.svelte.js";
|
|
3
|
+
export * from "./colors.js";
|
|
2
4
|
export * from "./debounce.js";
|
|
3
5
|
export * from "./device-pointer.svelte.js";
|
|
4
6
|
export * from "./escape-regex.js";
|
|
5
7
|
export * from "./event-emitter.js";
|
|
6
8
|
export * from "./event-modifiers.js";
|
|
7
9
|
export * from "./get-id.js";
|
|
8
|
-
export * from "./hex-to-oklch.js";
|
|
9
|
-
export * from "./hex-to-rgb.js";
|
|
10
10
|
export * from "./is-browser.js";
|
|
11
11
|
export * from "./is-mac.js";
|
|
12
12
|
export * from "./is-nullish.js";
|
package/dist/utils/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
export * from "./body-scroll-locker.js";
|
|
1
2
|
export * from "./breakpoint.svelte.js";
|
|
3
|
+
export * from "./colors.js";
|
|
2
4
|
export * from "./debounce.js";
|
|
3
5
|
export * from "./device-pointer.svelte.js";
|
|
4
6
|
export * from "./escape-regex.js";
|
|
5
7
|
export * from "./event-emitter.js";
|
|
6
8
|
export * from "./event-modifiers.js";
|
|
7
9
|
export * from "./get-id.js";
|
|
8
|
-
export * from "./hex-to-oklch.js";
|
|
9
|
-
export * from "./hex-to-rgb.js";
|
|
10
10
|
export * from "./is-browser.js";
|
|
11
11
|
export * from "./is-mac.js";
|
|
12
12
|
export * from "./is-nullish.js";
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Converts a hex color string to OKLCH
|
|
4
|
-
* @param {string} hex - The hex color string (with or without leading #)
|
|
5
|
-
* @returns {Object} An object with l (lightness), c (chroma), and h (hue) properties
|
|
6
|
-
*/
|
|
7
|
-
function hexToOklch(hex) {
|
|
8
|
-
// Remove the hash if it exists
|
|
9
|
-
hex = hex.replace(/^#/, "");
|
|
10
|
-
// Handle both 3-digit and 6-digit formats
|
|
11
|
-
if (hex.length === 3) {
|
|
12
|
-
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
13
|
-
}
|
|
14
|
-
// Parse the hex values to RGB (0-1)
|
|
15
|
-
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
16
|
-
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
17
|
-
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
18
|
-
// Convert RGB to linear RGB (removing gamma correction)
|
|
19
|
-
const linearR = r <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
|
|
20
|
-
const linearG = g <= 0.04045 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
|
|
21
|
-
const linearB = b <= 0.04045 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
|
|
22
|
-
// Convert to XYZ
|
|
23
|
-
const x = 0.4124 * linearR + 0.3576 * linearG + 0.1805 * linearB;
|
|
24
|
-
const y = 0.2126 * linearR + 0.7152 * linearG + 0.0722 * linearB;
|
|
25
|
-
const z = 0.0193 * linearR + 0.1192 * linearG + 0.9505 * linearB;
|
|
26
|
-
// Convert XYZ to LMS (cone response)
|
|
27
|
-
const l = 0.819 * x + 0.3619 * y - 0.1289 * z;
|
|
28
|
-
const m = 0.0329 * x + 0.9293 * y + 0.0361 * z;
|
|
29
|
-
const s = 0.0482 * x + 0.2645 * y + 0.6886 * z;
|
|
30
|
-
// Apply non-linearity to LMS
|
|
31
|
-
const lp = Math.cbrt(l);
|
|
32
|
-
const mp = Math.cbrt(m);
|
|
33
|
-
const sp = Math.cbrt(s);
|
|
34
|
-
// Convert to Oklab
|
|
35
|
-
const L = 0.2104 * lp + 0.7936 * mp - 0.004 * sp;
|
|
36
|
-
const a = 1.9779 * lp - 2.4285 * mp + 0.4505 * sp;
|
|
37
|
-
const bb = 0.0259 * lp + 0.7827 * mp - 0.8086 * sp;
|
|
38
|
-
// Convert Oklab to Oklch
|
|
39
|
-
const C = Math.sqrt(a * a + bb * bb);
|
|
40
|
-
let h = (Math.atan2(bb, a) * 180) / Math.PI;
|
|
41
|
-
// Ensure hue is positive
|
|
42
|
-
if (h < 0) {
|
|
43
|
-
h += 360;
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
l: parseFloat(L.toFixed(4)),
|
|
47
|
-
c: parseFloat(C.toFixed(4)),
|
|
48
|
-
h: parseFloat(h.toFixed(2)),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
// Example usage:
|
|
52
|
-
// const oklch = hexToOklch("#ff5733");
|
|
53
|
-
// console.log(oklch); // Example output: { l: 0.6321, c: 0.1549, h: 27.23 }
|
package/dist/utils/hex-to-rgb.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/** Will convert #fff or #ffffff to {r: 255, g: 255, b: 255} */
|
|
2
|
-
export function hexToRgb(hex) {
|
|
3
|
-
hex = hex.replace(/^#/, "");
|
|
4
|
-
// both 3-digit and 6-digit formats
|
|
5
|
-
if (hex.length === 3) {
|
|
6
|
-
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
7
|
-
}
|
|
8
|
-
const r = parseInt(hex.substring(0, 2), 16);
|
|
9
|
-
const g = parseInt(hex.substring(2, 4), 16);
|
|
10
|
-
const b = parseInt(hex.substring(4, 6), 16);
|
|
11
|
-
return { r, g, b };
|
|
12
|
-
}
|