|
|
|
|
@ -20,6 +20,23 @@ import type {
|
|
|
|
|
} from "./types";
|
|
|
|
|
import { clamp } from '$lib';
|
|
|
|
|
|
|
|
|
|
type ColorPickerRootStateProps = {
|
|
|
|
|
value: Active<ColorValue | undefined>;
|
|
|
|
|
defaultValue: Active<ColorValue | undefined>;
|
|
|
|
|
onValueChange: Active<((value: ColorValue) => void) | undefined>;
|
|
|
|
|
disabled: Active<boolean>;
|
|
|
|
|
format: Active<ColorFormat>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ColorPickerInputStateProps = {
|
|
|
|
|
format: Active<ColorFormat>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ColorPickerSwatchesStateProps = {
|
|
|
|
|
colors: Active<string[]>;
|
|
|
|
|
onSelect: Active<((color: string) => void) | undefined>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ─── Root State ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export class ColorPickerRootState {
|
|
|
|
|
@ -37,15 +54,15 @@ export class ColorPickerRootState {
|
|
|
|
|
readonly disabled: Active<boolean>;
|
|
|
|
|
readonly format: Active<ColorFormat>;
|
|
|
|
|
|
|
|
|
|
readonly #onValueChange: ((v: ColorValue) => void) | undefined;
|
|
|
|
|
readonly #onValueChange: Active<((v: ColorValue) => void) | undefined>;
|
|
|
|
|
|
|
|
|
|
constructor(props: ColorPickerRootProps) {
|
|
|
|
|
const initial = props.defaultValue ?? props.value ?? DEFAULT_COLOR;
|
|
|
|
|
constructor(props: ColorPickerRootStateProps) {
|
|
|
|
|
const initial = props.defaultValue.current ?? props.value.current ?? DEFAULT_COLOR;
|
|
|
|
|
|
|
|
|
|
this.#hsv = state<HSVColor>(initial.hsv);
|
|
|
|
|
this.#onValueChange = props.onValueChange;
|
|
|
|
|
this.disabled = readableActive(() => props.disabled ?? false);
|
|
|
|
|
this.format = readableActive(() => props.format ?? "hex");
|
|
|
|
|
this.disabled = props.disabled;
|
|
|
|
|
this.format = props.format;
|
|
|
|
|
|
|
|
|
|
this.value = readableActive(() => colorValueFromHsv(this.#hsv.current));
|
|
|
|
|
this.hex = readableActive(() => hsvToHex(this.#hsv.current));
|
|
|
|
|
@ -58,7 +75,7 @@ export class ColorPickerRootState {
|
|
|
|
|
setHsv(hsv: Partial<HSVColor>) {
|
|
|
|
|
const next = { ...this.#hsv.current, ...hsv };
|
|
|
|
|
this.#hsv.current = next;
|
|
|
|
|
this.#onValueChange?.(colorValueFromHsv(next));
|
|
|
|
|
this.#onValueChange.current?.(colorValueFromHsv(next));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setHue(h: number) {
|
|
|
|
|
@ -77,18 +94,18 @@ export class ColorPickerRootState {
|
|
|
|
|
const hsv = parseColor(css);
|
|
|
|
|
if (!hsv) return false;
|
|
|
|
|
this.#hsv.current = hsv;
|
|
|
|
|
this.#onValueChange?.(colorValueFromHsv(hsv));
|
|
|
|
|
this.#onValueChange.current?.(colorValueFromHsv(hsv));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setFromColorValue(color: ColorValue) {
|
|
|
|
|
this.#hsv.current = color.hsv;
|
|
|
|
|
this.#onValueChange?.(color);
|
|
|
|
|
this.#onValueChange.current?.(color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Factory ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
static create(props: ColorPickerRootProps) {
|
|
|
|
|
static create(props: ColorPickerRootStateProps) {
|
|
|
|
|
return ColorPickerRootState.ctx.set(new ColorPickerRootState(props));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -221,7 +238,7 @@ export class ColorPickerAlphaSliderState {
|
|
|
|
|
|
|
|
|
|
export class ColorPickerInputState {
|
|
|
|
|
readonly #root: ColorPickerRootState;
|
|
|
|
|
readonly format: ColorFormat;
|
|
|
|
|
readonly format: Active<ColorFormat>;
|
|
|
|
|
|
|
|
|
|
// Displayed string — formatted according to `format`
|
|
|
|
|
readonly displayValue: Active<string>;
|
|
|
|
|
@ -231,13 +248,13 @@ export class ColorPickerInputState {
|
|
|
|
|
|
|
|
|
|
readonly inputValue: Active<string>;
|
|
|
|
|
|
|
|
|
|
constructor(props: ColorPickerInputProps) {
|
|
|
|
|
constructor(props: ColorPickerInputStateProps) {
|
|
|
|
|
this.#root = ColorPickerRootState.get();
|
|
|
|
|
this.format = props.format;
|
|
|
|
|
|
|
|
|
|
this.displayValue = readableActive(() => {
|
|
|
|
|
const color = this.#root.value.current;
|
|
|
|
|
switch (this.format) {
|
|
|
|
|
switch (this.format.current) {
|
|
|
|
|
case "hex": return color.hex;
|
|
|
|
|
case "rgb": {
|
|
|
|
|
const { r, g, b, a } = color.rgb;
|
|
|
|
|
@ -275,7 +292,7 @@ export class ColorPickerInputState {
|
|
|
|
|
if (e.key === "Escape") this.#editBuffer = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static create(props: ColorPickerInputProps) {
|
|
|
|
|
static create(props: ColorPickerInputStateProps) {
|
|
|
|
|
return new ColorPickerInputState(props);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -284,10 +301,10 @@ export class ColorPickerInputState {
|
|
|
|
|
|
|
|
|
|
export class ColorPickerSwatchesState {
|
|
|
|
|
readonly #root: ColorPickerRootState;
|
|
|
|
|
readonly #onSelect: ((color: string) => void) | undefined;
|
|
|
|
|
readonly colors: string[];
|
|
|
|
|
readonly #onSelect: Active<((color: string) => void) | undefined>;
|
|
|
|
|
readonly colors: Active<string[]>;
|
|
|
|
|
|
|
|
|
|
constructor(props: ColorPickerSwatchesProps) {
|
|
|
|
|
constructor(props: ColorPickerSwatchesStateProps) {
|
|
|
|
|
this.#root = ColorPickerRootState.get();
|
|
|
|
|
this.colors = props.colors;
|
|
|
|
|
this.#onSelect = props.onSelect;
|
|
|
|
|
@ -295,7 +312,7 @@ export class ColorPickerSwatchesState {
|
|
|
|
|
|
|
|
|
|
selectSwatch(color: string) {
|
|
|
|
|
const success = this.#root.setFromString(color);
|
|
|
|
|
if (success) this.#onSelect?.(color);
|
|
|
|
|
if (success) this.#onSelect.current?.(color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isSelected(color: string): boolean {
|
|
|
|
|
@ -305,7 +322,7 @@ export class ColorPickerSwatchesState {
|
|
|
|
|
return hsvToHex(hsv) === current;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static create(props: ColorPickerSwatchesProps) {
|
|
|
|
|
static create(props: ColorPickerSwatchesStateProps) {
|
|
|
|
|
return new ColorPickerSwatchesState(props);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|