@kobalte/core
Version:
Unstyled components and primitives for building accessible web apps and design systems with SolidJS.
637 lines (624 loc) • 19.2 kB
JSX
import {
createSize
} from "../chunk/KVL5CS3M.jsx";
import {
COLOR_INTL_TRANSLATIONS,
parseColor
} from "../chunk/KNLKPWUU.jsx";
import {
useLocale
} from "../chunk/LR7LBJN3.jsx";
import {
FORM_CONTROL_FIELD_PROP_NAMES,
createFormControlField
} from "../chunk/NGHEENNE.jsx";
import {
FormControlLabel
} from "../chunk/FOXVCQFV.jsx";
import {
createFormResetListener
} from "../chunk/QJIB6BDF.jsx";
import {
FormControlErrorMessage
} from "../chunk/ZZYKR3VO.jsx";
import {
FORM_CONTROL_PROP_NAMES,
FormControlContext,
FormControlDescription,
createFormControl,
useFormControlContext
} from "../chunk/XUUROM4M.jsx";
import "../chunk/JNCCF6MP.jsx";
import {
createControllableSignal
} from "../chunk/FN6EICGO.jsx";
import "../chunk/OYES4GOP.jsx";
import {
Polymorphic
} from "../chunk/FLVHQV4A.jsx";
import "../chunk/5WXHJDCZ.jsx";
// src/color-wheel/color-wheel-input.tsx
import {
callHandler,
mergeDefaultProps,
visuallyHiddenStyles
} from "@kobalte/utils";
import { splitProps } from "solid-js";
import { combineStyle } from "@solid-primitives/props";
// src/color-wheel/color-wheel-context.tsx
import { createContext, useContext } from "solid-js";
var ColorWheelContext = createContext();
function useColorWheelContext() {
const context = useContext(ColorWheelContext);
if (context === void 0) {
throw new Error(
"[kobalte]: `useColorWheelContext` must be used within a `ColorWheel` component"
);
}
return context;
}
// src/color-wheel/color-wheel-input.tsx
function ColorWheelInput(props) {
const formControlContext = useFormControlContext();
const context = useColorWheelContext();
const mergedProps = mergeDefaultProps(
{
id: context.generateId("input")
},
props
);
const [local, formControlFieldProps, others] = splitProps(
mergedProps,
["style", "onChange"],
FORM_CONTROL_FIELD_PROP_NAMES
);
const { fieldProps } = createFormControlField(formControlFieldProps);
const onChange = (e) => {
callHandler(e, local.onChange);
const target = e.target;
context.state.setHue(Number.parseFloat(target.value));
target.value = String(context.state.hue()) ?? "";
};
return <input
type="range"
id={fieldProps.id()}
name={formControlContext.name()}
tabIndex={context.state.isDisabled() ? void 0 : -1}
min={context.state.minValue()}
max={context.state.maxValue()}
step={context.state.step()}
value={context.state.hue()}
required={formControlContext.isRequired()}
disabled={formControlContext.isDisabled()}
readonly={formControlContext.isReadOnly()}
style={combineStyle({ ...visuallyHiddenStyles }, local.style)}
aria-valuetext={context.getThumbValueLabel()}
aria-label={fieldProps.ariaLabel()}
aria-labelledby={fieldProps.ariaLabelledBy()}
aria-describedby={fieldProps.ariaDescribedBy()}
aria-invalid={formControlContext.validationState() === "invalid" || void 0}
aria-required={formControlContext.isRequired() || void 0}
aria-disabled={formControlContext.isDisabled() || void 0}
aria-readonly={formControlContext.isReadOnly() || void 0}
onChange={onChange}
{...formControlContext.dataset()}
{...others}
/>;
}
// src/color-wheel/color-wheel-root.tsx
import {
access,
createGenerateId,
mergeDefaultProps as mergeDefaultProps3,
mergeRefs
} from "@kobalte/utils";
import {
createMemo as createMemo2,
createSignal as createSignal2,
createUniqueId,
splitProps as splitProps2
} from "solid-js";
// src/color-wheel/create-color-wheel-state.ts
import { mergeDefaultProps as mergeDefaultProps2 } from "@kobalte/utils";
import { createMemo, createSignal } from "solid-js";
// src/color-wheel/utils.ts
function roundToStep(value, step) {
return Math.round(value / step) * step;
}
function mod(n, m) {
return (n % m + m) % m;
}
function roundDown(v) {
const r = Math.floor(v);
if (r === v) {
return v - 1;
}
return r;
}
function degToRad(deg) {
return deg * Math.PI / 180;
}
function radToDeg(rad) {
return rad * 180 / Math.PI;
}
function angleToCartesian(angle, radius) {
const rad = degToRad(360 - angle + 90);
const x = Math.sin(rad) * radius;
const y = Math.cos(rad) * radius;
return { x, y };
}
function cartesianToAngle(x, y, radius) {
const deg = radToDeg(Math.atan2(y / radius, x / radius));
return (deg + 360) % 360;
}
// src/color-wheel/create-color-wheel-state.ts
function createColorWheelState(props) {
const mergedProps = mergeDefaultProps2(
{
isDisabled: () => false
},
props
);
const defaultValue = createMemo(() => {
return mergedProps.defaultValue() ?? parseColor("hsl(0, 100%, 50%)");
});
const [value, setValue] = createControllableSignal({
value: mergedProps.value,
defaultValue,
onChange: (value2) => mergedProps.onChange?.(value2)
});
const color = createMemo(() => {
const colorSpace = value().getColorSpace();
return colorSpace === "hsl" || colorSpace === "hsb" ? value() : value().toFormat("hsl");
});
const channelRange = () => color().getChannelRange("hue");
const step = () => channelRange().step;
const pageSize = () => channelRange().pageSize;
const maxValue = () => channelRange().maxValue;
const minValue = () => channelRange().minValue;
const [isDragging, setIsDragging] = createSignal(false);
const resetValue = () => {
setValue(defaultValue());
};
const hue = () => color().getChannelValue("hue");
const setHue = (value2) => {
let newValue = value2 > 360 ? 0 : value2;
newValue = roundToStep(mod(newValue, 360), step());
if (hue() !== newValue) {
setValue(color().withChannelValue("hue", newValue));
}
};
const increment = (stepSize = 1) => {
const newStepSize = Math.max(stepSize, step());
let newValue = hue() + newStepSize;
if (newValue >= maxValue()) {
newValue = minValue();
}
setHue(roundToStep(mod(newValue, 360), newStepSize));
};
const decrement = (stepSize = 1) => {
const newStepSize = Math.max(stepSize, step());
if (hue() === 0) {
setHue(roundDown(360 / newStepSize) * newStepSize);
} else {
setHue(roundToStep(mod(hue() - newStepSize, 360), newStepSize));
}
};
const getThumbPosition = () => angleToCartesian(hue(), mergedProps.thumbRadius());
const setThumbValue = (x, y, radius) => {
if (mergedProps.isDisabled())
return;
setHue(cartesianToAngle(x, y, radius));
};
const updateDragging = (dragging) => {
if (mergedProps.isDisabled())
return;
const wasDragging = isDragging();
setIsDragging(dragging);
if (wasDragging && !isDragging()) {
mergedProps.onChangeEnd?.(color());
}
};
return {
value: color,
setValue,
hue,
setHue,
step,
pageSize,
maxValue,
minValue,
increment,
decrement,
getThumbPosition,
setThumbValue,
isDragging,
setIsDragging: updateDragging,
resetValue,
isDisabled: mergedProps.isDisabled
};
}
// src/color-wheel/color-wheel-root.tsx
function ColorWheelRoot(props) {
const [ref, setRef] = createSignal2();
const defaultId = `colorwheel-${createUniqueId()}`;
const mergedProps = mergeDefaultProps3(
{
id: defaultId,
getValueLabel: (param) => param.formatChannelValue("hue"),
translations: COLOR_INTL_TRANSLATIONS,
disabled: false,
thickness: 30
},
props
);
const [local, formControlProps, others] = splitProps2(
mergedProps,
[
"ref",
"value",
"defaultValue",
"thickness",
"onChange",
"onChangeEnd",
"getValueLabel",
"translations",
"disabled"
],
FORM_CONTROL_PROP_NAMES
);
const { formControlContext } = createFormControl(formControlProps);
const { direction } = useLocale();
const [trackRef, setTrackRef] = createSignal2();
const [thumbRef, setThumbRef] = createSignal2();
const size = createSize(trackRef);
const outerRadius = createMemo2(() => {
if (size.width() === 0)
return void 0;
return size.width() / 2;
});
const thumbRadius = () => (139.75 - local.thickness / 100 * 70) * outerRadius() / 140;
const state = createColorWheelState({
value: () => local.value,
defaultValue: () => local.defaultValue,
thumbRadius,
onChange: local.onChange,
onChangeEnd: local.onChangeEnd,
isDisabled: () => formControlContext.isDisabled() ?? false
});
createFormResetListener(ref, () => state.resetValue());
const isLTR = () => direction() === "ltr";
let currentPosition = null;
const onDragStart = (value) => {
state.setIsDragging(true);
state.setThumbValue(
value[0],
value[1],
Math.sqrt(value[0] * value[0] + value[1] * value[1])
);
currentPosition = null;
};
const onDrag = ({ deltaX, deltaY }) => {
if (currentPosition === null) {
currentPosition = state.getThumbPosition();
}
currentPosition.x += deltaX;
currentPosition.y += deltaY;
state.setThumbValue(currentPosition.x, currentPosition.y, thumbRadius());
local.onChange?.(state.value());
};
const onDragEnd = () => {
state.setIsDragging(false);
thumbRef()?.focus();
};
const getThumbValueLabel = () => `${state.value().formatChannelValue("hue")}, ${context.state.value().getHueName(local.translations)}`;
const onHomeKeyDown = (event) => {
if (!formControlContext.isDisabled()) {
event.preventDefault();
event.stopPropagation();
state.setHue(state.minValue());
}
};
const onEndKeyDown = (event) => {
if (!formControlContext.isDisabled()) {
event.preventDefault();
event.stopPropagation();
state.setHue(state.maxValue());
}
};
const onStepKeyDown = (event) => {
if (!formControlContext.isDisabled()) {
switch (event.key) {
case "Left":
case "ArrowLeft":
event.preventDefault();
event.stopPropagation();
if (!isLTR()) {
state.increment(event.shiftKey ? state.pageSize() : state.step());
} else {
state.decrement(event.shiftKey ? state.pageSize() : state.step());
}
break;
case "Down":
case "ArrowDown":
event.preventDefault();
event.stopPropagation();
state.decrement(event.shiftKey ? state.pageSize() : state.step());
break;
case "Up":
case "ArrowUp":
event.preventDefault();
event.stopPropagation();
state.increment(event.shiftKey ? state.pageSize() : state.step());
break;
case "Right":
case "ArrowRight":
event.preventDefault();
event.stopPropagation();
if (!isLTR()) {
state.decrement(event.shiftKey ? state.pageSize() : state.step());
} else {
state.increment(event.shiftKey ? state.pageSize() : state.step());
}
break;
case "Home":
onHomeKeyDown(event);
break;
case "End":
onEndKeyDown(event);
break;
case "PageUp":
event.preventDefault();
event.stopPropagation();
state.increment(state.pageSize());
break;
case "PageDown":
event.preventDefault();
event.stopPropagation();
state.decrement(state.pageSize());
break;
}
}
};
const context = {
state,
outerRadius,
thickness: () => local.thickness,
onDragStart,
onDrag,
onDragEnd,
getThumbValueLabel,
getValueLabel: local.getValueLabel,
onStepKeyDown,
trackRef,
setTrackRef,
thumbRef,
setThumbRef,
generateId: createGenerateId(() => access(formControlProps.id))
};
return <FormControlContext.Provider value={formControlContext}><ColorWheelContext.Provider value={context}><Polymorphic
as="div"
ref={mergeRefs(setRef, local.ref)}
role="group"
id={access(formControlProps.id)}
{...formControlContext.dataset()}
{...others}
/></ColorWheelContext.Provider></FormControlContext.Provider>;
}
// src/color-wheel/color-wheel-thumb.tsx
import { callHandler as callHandler2, mergeDefaultProps as mergeDefaultProps4, mergeRefs as mergeRefs2 } from "@kobalte/utils";
import { combineStyle as combineStyle2 } from "@solid-primitives/props";
import {
createSignal as createSignal3,
splitProps as splitProps3
} from "solid-js";
function ColorWheelThumb(props) {
const context = useColorWheelContext();
const formControlContext = useFormControlContext();
const mergedProps = mergeDefaultProps4(
{
id: context.generateId("thumb")
},
props
);
const [local, formControlFieldProps, others] = splitProps3(
mergedProps,
["style", "onKeyDown", "onPointerDown", "onPointerMove", "onPointerUp"],
FORM_CONTROL_FIELD_PROP_NAMES
);
const { fieldProps } = createFormControlField(formControlFieldProps);
const onKeyDown = (e) => {
callHandler2(e, local.onKeyDown);
context.onStepKeyDown(e);
};
const [sRect, setRect] = createSignal3();
const getValueFromPointer = (pointerPosition) => {
const rect = sRect() || context.trackRef().getBoundingClientRect();
setRect(rect);
return [
pointerPosition.x - rect.left - rect.width / 2,
pointerPosition.y - rect.top - rect.height / 2
];
};
let startPosition = { x: 0, y: 0 };
const onPointerDown = (e) => {
callHandler2(e, local.onPointerDown);
const target = e.currentTarget;
e.preventDefault();
e.stopPropagation();
target.setPointerCapture(e.pointerId);
target.focus();
const value = getValueFromPointer({ x: e.clientX, y: e.clientY });
startPosition = { x: e.clientX, y: e.clientY };
context.onDragStart?.(value);
};
const onPointerMove = (e) => {
e.stopPropagation();
callHandler2(e, local.onPointerMove);
const target = e.currentTarget;
if (target.hasPointerCapture(e.pointerId)) {
const delta = {
deltaX: e.clientX - startPosition.x,
deltaY: e.clientY - startPosition.y
};
context.onDrag?.(delta);
startPosition = { x: e.clientX, y: e.clientY };
}
};
const onPointerUp = (e) => {
e.stopPropagation();
callHandler2(e, local.onPointerUp);
const target = e.currentTarget;
if (target.hasPointerCapture(e.pointerId)) {
target.releasePointerCapture(e.pointerId);
context.onDragEnd?.();
}
};
return <Polymorphic
as="span"
ref={mergeRefs2(context.setThumbRef, props.ref)}
role="slider"
id={fieldProps.id()}
tabIndex={context.state.isDisabled() ? void 0 : 0}
style={combineStyle2(
{
position: "absolute",
left: `${context.outerRadius() + context.state.getThumbPosition().x}px`,
top: `${context.outerRadius() + context.state.getThumbPosition().y}px`,
transform: "translate(-50%, -50%)",
"forced-color-adjust": "none",
"touch-action": "none",
opacity: context.outerRadius() ? 1 : 0,
transition: "opacity .1s linear",
"--kb-color-current": context.state.value().toString()
},
local.style
)}
aria-valuetext={context.getThumbValueLabel()}
aria-valuemin={context.state.minValue()}
aria-valuenow={context.state.hue()}
aria-valuemax={context.state.maxValue()}
aria-label={fieldProps.ariaLabel()}
aria-labelledby={fieldProps.ariaLabelledBy()}
aria-describedby={fieldProps.ariaDescribedBy()}
onKeyDown={onKeyDown}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
{...formControlContext.dataset()}
{...others}
/>;
}
// src/color-wheel/color-wheel-track.tsx
import { callHandler as callHandler3, mergeRefs as mergeRefs3 } from "@kobalte/utils";
import { combineStyle as combineStyle3 } from "@solid-primitives/props";
import {
createSignal as createSignal4,
splitProps as splitProps4
} from "solid-js";
function ColorWheelTrack(props) {
const context = useColorWheelContext();
const formControlContext = useFormControlContext();
const [local, others] = splitProps4(props, [
"style",
"onPointerDown",
"onPointerMove",
"onPointerUp"
]);
const [sRect, setRect] = createSignal4();
const getValueFromPointer = (pointerPosition) => {
const rect = sRect() || context.trackRef().getBoundingClientRect();
setRect(rect);
return [
pointerPosition.x - rect.left - rect.width / 2,
pointerPosition.y - rect.top - rect.height / 2
];
};
let startPosition = { x: 0, y: 0 };
const onPointerDown = (e) => {
callHandler3(e, local.onPointerDown);
const target = e.target;
target.setPointerCapture(e.pointerId);
e.preventDefault();
const value = getValueFromPointer({ x: e.clientX, y: e.clientY });
startPosition = { x: e.clientX, y: e.clientY };
context.onDragStart?.(value);
};
const onPointerMove = (e) => {
callHandler3(e, local.onPointerMove);
const target = e.target;
if (target.hasPointerCapture(e.pointerId)) {
context.onDrag?.({
deltaX: e.clientX - startPosition.x,
deltaY: e.clientY - startPosition.y
});
startPosition = { x: e.clientX, y: e.clientY };
}
};
const onPointerUp = (e) => {
callHandler3(e, local.onPointerUp);
const target = e.target;
if (target.hasPointerCapture(e.pointerId)) {
target.releasePointerCapture(e.pointerId);
setRect(void 0);
context.onDragEnd?.();
}
};
const backgroundStyle = `
conic-gradient(
from 90deg,
${[...Array(13).keys()].map((i) => `hsl(${i * 30} 100% 50%)`).join(",")}
)
`;
return <Polymorphic
as="div"
ref={mergeRefs3(context.setTrackRef, props.ref)}
style={combineStyle3(
{
"touch-action": "none",
"forced-color-adjust": "none",
background: backgroundStyle,
"clip-path": "circle(50%)",
mask: `radial-gradient(#0000 ${70 - context.thickness() / 100 * 70}%, #000 ${70.5 - context.thickness() / 100 * 70}%)`
},
local.style
)}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
{...formControlContext.dataset()}
{...others}
/>;
}
// src/color-wheel/color-wheel-value-label.tsx
function ColorWheelValueLabel(props) {
const context = useColorWheelContext();
const formControlContext = useFormControlContext();
return <Polymorphic
as="div"
{...formControlContext.dataset()}
{...props}
>{context.getValueLabel(context.state.value())}</Polymorphic>;
}
// src/color-wheel/index.tsx
var ColorWheel = Object.assign(ColorWheelRoot, {
Description: FormControlDescription,
ErrorMessage: FormControlErrorMessage,
Input: ColorWheelInput,
Label: FormControlLabel,
Thumb: ColorWheelThumb,
Track: ColorWheelTrack,
ValueLabel: ColorWheelValueLabel
});
export {
ColorWheel,
FormControlDescription as Description,
FormControlErrorMessage as ErrorMessage,
ColorWheelInput as Input,
FormControlLabel as Label,
ColorWheelRoot as Root,
ColorWheelThumb as Thumb,
ColorWheelTrack as Track,
ColorWheelValueLabel as ValueLabel,
useColorWheelContext
};