package.dist.index.js Maven / Gradle / Ivy
'use strict';
var anatomy$1 = require('@zag-js/anatomy');
var colorUtils = require('@zag-js/color-utils');
var domEvent = require('@zag-js/dom-event');
var domQuery = require('@zag-js/dom-query');
var popper = require('@zag-js/popper');
var core = require('@zag-js/core');
var dismissable = require('@zag-js/dismissable');
var formUtils = require('@zag-js/form-utils');
var textSelection = require('@zag-js/text-selection');
var utils = require('@zag-js/utils');
// src/color-picker.anatomy.ts
var anatomy = anatomy$1.createAnatomy("color-picker", [
"root",
"label",
"control",
"trigger",
"positioner",
"content",
"area",
"areaThumb",
"valueText",
"areaBackground",
"channelSlider",
"channelSliderLabel",
"channelSliderTrack",
"channelSliderThumb",
"channelSliderValueText",
"channelInput",
"transparencyGrid",
"swatchGroup",
"swatchTrigger",
"swatchIndicator",
"swatch",
"eyeDropperTrigger",
"formatTrigger",
"formatSelect"
]);
var parts = anatomy.build();
var dom = domQuery.createScope({
getRootId: (ctx) => ctx.ids?.root ?? `color-picker:${ctx.id}`,
getLabelId: (ctx) => ctx.ids?.label ?? `color-picker:${ctx.id}:label`,
getHiddenInputId: (ctx) => ctx.ids?.hiddenInput ?? `color-picker:${ctx.id}:hidden-input`,
getControlId: (ctx) => ctx.ids?.control ?? `color-picker:${ctx.id}:control`,
getTriggerId: (ctx) => ctx.ids?.trigger ?? `color-picker:${ctx.id}:trigger`,
getContentId: (ctx) => ctx.ids?.content ?? `color-picker:${ctx.id}:content`,
getPositionerId: (ctx) => ctx.ids?.positioner ?? `color-picker:${ctx.id}:positioner`,
getFormatSelectId: (ctx) => ctx.ids?.formatSelect ?? `color-picker:${ctx.id}:format-select`,
getAreaId: (ctx) => ctx.ids?.area ?? `color-picker:${ctx.id}:area`,
getAreaGradientId: (ctx) => ctx.ids?.areaGradient ?? `color-picker:${ctx.id}:area-gradient`,
getAreaThumbId: (ctx) => ctx.ids?.areaThumb ?? `color-picker:${ctx.id}:area-thumb`,
getChannelSliderTrackId: (ctx, channel) => ctx.ids?.channelSliderTrack?.(channel) ?? `color-picker:${ctx.id}:slider-track:${channel}`,
getChannelSliderThumbId: (ctx, channel) => ctx.ids?.channelSliderThumb?.(channel) ?? `color-picker:${ctx.id}:slider-thumb:${channel}`,
getContentEl: (ctx) => dom.getById(ctx, dom.getContentId(ctx)),
getAreaThumbEl: (ctx) => dom.getById(ctx, dom.getAreaThumbId(ctx)),
getChannelSliderThumbEl: (ctx, channel) => dom.getById(ctx, dom.getChannelSliderThumbId(ctx, channel)),
getChannelInputEl: (ctx, channel) => {
const selector = `input[data-channel="${channel}"]`;
return [
...domQuery.queryAll(dom.getContentEl(ctx), selector),
...domQuery.queryAll(dom.getControlEl(ctx), selector)
];
},
getFormatSelectEl: (ctx) => dom.getById(ctx, dom.getFormatSelectId(ctx)),
getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
getAreaEl: (ctx) => dom.getById(ctx, dom.getAreaId(ctx)),
getAreaValueFromPoint(ctx, point) {
const areaEl = dom.getAreaEl(ctx);
if (!areaEl) return;
const { percent } = domEvent.getRelativePoint(point, areaEl);
return percent;
},
getControlEl: (ctx) => dom.getById(ctx, dom.getControlId(ctx)),
getTriggerEl: (ctx) => dom.getById(ctx, dom.getTriggerId(ctx)),
getPositionerEl: (ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
getChannelSliderTrackEl: (ctx, channel) => {
return dom.getById(ctx, dom.getChannelSliderTrackId(ctx, channel));
},
getChannelSliderValueFromPoint(ctx, point, channel) {
const trackEl = dom.getChannelSliderTrackEl(ctx, channel);
if (!trackEl) return;
const { percent } = domEvent.getRelativePoint(point, trackEl);
return percent;
},
getChannelInputEls: (ctx) => {
return [
...domQuery.queryAll(dom.getContentEl(ctx), "input[data-channel]"),
...domQuery.queryAll(dom.getControlEl(ctx), "input[data-channel]")
];
}
});
function getChannelDisplayColor(color, channel) {
switch (channel) {
case "hue":
return colorUtils.parseColor(`hsl(${color.getChannelValue("hue")}, 100%, 50%)`);
case "lightness":
case "brightness":
case "saturation":
case "red":
case "green":
case "blue":
return color.withChannelValue("alpha", 1);
case "alpha": {
return color;
}
default:
throw new Error("Unknown color channel: " + channel);
}
}
function getChannelValue(color, channel) {
if (channel == null) return "";
if (channel === "hex") {
return color.toString("hex");
}
if (channel === "css") {
return color.toString("css");
}
if (channel in color) {
return color.getChannelValue(channel).toString();
}
const isHSL = color.getFormat() === "hsla";
switch (channel) {
case "hue":
return isHSL ? color.toFormat("hsla").getChannelValue("hue").toString() : color.toFormat("hsba").getChannelValue("hue").toString();
case "saturation":
return isHSL ? color.toFormat("hsla").getChannelValue("saturation").toString() : color.toFormat("hsba").getChannelValue("saturation").toString();
case "lightness":
return color.toFormat("hsla").getChannelValue("lightness").toString();
case "brightness":
return color.toFormat("hsba").getChannelValue("brightness").toString();
case "red":
case "green":
case "blue":
return color.toFormat("rgba").getChannelValue(channel).toString();
default:
return color.getChannelValue(channel).toString();
}
}
function getChannelRange(color, channel) {
switch (channel) {
case "hex":
const minColor = colorUtils.parseColor("#000000");
const maxColor = colorUtils.parseColor("#FFFFFF");
return {
minValue: minColor.toHexInt(),
maxValue: maxColor.toHexInt(),
pageSize: 10,
step: 1
};
case "css":
return void 0;
case "hue":
case "saturation":
case "lightness":
return color.toFormat("hsla").getChannelRange(channel);
case "brightness":
return color.toFormat("hsba").getChannelRange(channel);
case "red":
case "green":
case "blue":
return color.toFormat("rgba").getChannelRange(channel);
default:
return color.getChannelRange(channel);
}
}
// src/utils/get-slider-background.ts
function getSliderBackgroundDirection(orientation, dir) {
if (orientation === "vertical") {
return "top";
} else if (dir === "ltr") {
return "right";
} else {
return "left";
}
}
var getSliderBackground = (props) => {
const { channel, value, dir, orientation } = props;
const bgDirection = getSliderBackgroundDirection(orientation, dir);
const { minValue, maxValue } = value.getChannelRange(channel);
switch (channel) {
case "hue":
return `linear-gradient(to ${bgDirection}, rgb(255, 0, 0) 0%, rgb(255, 255, 0) 17%, rgb(0, 255, 0) 33%, rgb(0, 255, 255) 50%, rgb(0, 0, 255) 67%, rgb(255, 0, 255) 83%, rgb(255, 0, 0) 100%)`;
case "lightness": {
let start = value.withChannelValue(channel, minValue).toString("css");
let middle = value.withChannelValue(channel, (maxValue - minValue) / 2).toString("css");
let end = value.withChannelValue(channel, maxValue).toString("css");
return `linear-gradient(to ${bgDirection}, ${start}, ${middle}, ${end})`;
}
case "saturation":
case "brightness":
case "red":
case "green":
case "blue":
case "alpha": {
let start = value.withChannelValue(channel, minValue).toString("css");
let end = value.withChannelValue(channel, maxValue).toString("css");
return `linear-gradient(to ${bgDirection}, ${start}, ${end})`;
}
default:
throw new Error("Unknown color channel: " + channel);
}
};
// src/color-picker.connect.ts
function connect(state, send, normalize) {
const value = state.context.value;
const areaValue = state.context.areaValue;
const valueAsString = state.context.valueAsString;
const disabled = state.context.isDisabled;
const interactive = state.context.isInteractive;
const dragging = state.hasTag("dragging");
const open = state.hasTag("open");
const focused = state.hasTag("focused");
const getAreaChannels = (props) => {
const channels = areaValue.getChannels();
return {
xChannel: props.xChannel ?? channels[1],
yChannel: props.yChannel ?? channels[2]
};
};
const currentPlacement = state.context.currentPlacement;
const popperStyles = popper.getPlacementStyles({
...state.context.positioning,
placement: currentPlacement
});
function getSwatchTriggerState(props) {
const color = colorUtils.normalizeColor(props.value).toFormat(state.context.format);
return {
value: color,
valueAsString: color.toString("hex"),
checked: color.isEqual(value),
disabled: props.disabled || !interactive
};
}
return {
dragging,
open,
valueAsString,
value,
setOpen(nextOpen) {
if (nextOpen === open) return;
send({ type: nextOpen ? "OPEN" : "CLOSE" });
},
setValue(value2) {
send({ type: "VALUE.SET", value: colorUtils.normalizeColor(value2), src: "set-color" });
},
getChannelValue(channel) {
return getChannelValue(value, channel);
},
getChannelValueText(channel, locale) {
return value.formatChannelValue(channel, locale);
},
setChannelValue(channel, channelValue) {
const color = value.withChannelValue(channel, channelValue);
send({ type: "VALUE.SET", value: color, src: "set-channel" });
},
format: state.context.format,
setFormat(format) {
const formatValue = value.toFormat(format);
send({ type: "VALUE.SET", value: formatValue, src: "set-format" });
},
alpha: value.getChannelValue("alpha"),
setAlpha(alphaValue) {
const color = value.withChannelValue("alpha", alphaValue);
send({ type: "VALUE.SET", value: color, src: "set-alpha" });
},
getRootProps() {
return normalize.element({
...parts.root.attrs,
dir: state.context.dir,
id: dom.getRootId(state.context),
"data-disabled": domQuery.dataAttr(disabled),
"data-readonly": domQuery.dataAttr(state.context.readOnly),
style: {
"--value": value.toString("css")
}
});
},
getLabelProps() {
return normalize.element({
...parts.label.attrs,
dir: state.context.dir,
id: dom.getLabelId(state.context),
htmlFor: dom.getHiddenInputId(state.context),
"data-disabled": domQuery.dataAttr(disabled),
"data-readonly": domQuery.dataAttr(state.context.readOnly),
"data-focus": domQuery.dataAttr(focused),
onClick(event) {
event.preventDefault();
const inputEl = domQuery.query(dom.getControlEl(state.context), "[data-channel=hex]");
inputEl?.focus({ preventScroll: true });
}
});
},
getControlProps() {
return normalize.element({
...parts.control.attrs,
id: dom.getControlId(state.context),
dir: state.context.dir,
"data-disabled": domQuery.dataAttr(disabled),
"data-readonly": domQuery.dataAttr(state.context.readOnly),
"data-state": open ? "open" : "closed",
"data-focus": domQuery.dataAttr(focused)
});
},
getTriggerProps() {
return normalize.button({
...parts.trigger.attrs,
id: dom.getTriggerId(state.context),
dir: state.context.dir,
disabled,
"aria-label": `select color. current color is ${valueAsString}`,
"aria-controls": dom.getContentId(state.context),
"aria-labelledby": dom.getLabelId(state.context),
"data-disabled": domQuery.dataAttr(disabled),
"data-readonly": domQuery.dataAttr(state.context.readOnly),
"data-placement": currentPlacement,
"aria-expanded": domQuery.dataAttr(open),
"data-state": open ? "open" : "closed",
"data-focus": domQuery.dataAttr(focused),
type: "button",
onClick() {
if (!interactive) return;
send({ type: "TRIGGER.CLICK" });
},
onBlur() {
if (!interactive) return;
send({ type: "TRIGGER.BLUR" });
},
style: {
position: "relative"
}
});
},
getPositionerProps() {
return normalize.element({
...parts.positioner.attrs,
id: dom.getPositionerId(state.context),
dir: state.context.dir,
style: popperStyles.floating
});
},
getContentProps() {
return normalize.element({
...parts.content.attrs,
id: dom.getContentId(state.context),
dir: state.context.dir,
"data-placement": currentPlacement,
"data-state": open ? "open" : "closed",
hidden: !open
});
},
getValueTextProps() {
return normalize.element({
...parts.valueText.attrs,
dir: state.context.dir,
"data-disabled": domQuery.dataAttr(disabled),
"data-focus": domQuery.dataAttr(focused)
});
},
getAreaProps(props = {}) {
const { xChannel, yChannel } = getAreaChannels(props);
const { areaStyles } = colorUtils.getColorAreaGradient(areaValue, {
xChannel,
yChannel,
dir: state.context.dir
});
return normalize.element({
...parts.area.attrs,
id: dom.getAreaId(state.context),
role: "group",
onPointerDown(event) {
if (!interactive) return;
if (!domEvent.isLeftClick(event)) return;
if (domEvent.isModifierKey(event)) return;
const point = domEvent.getEventPoint(event);
const channel = { xChannel, yChannel };
send({ type: "AREA.POINTER_DOWN", point, channel, id: "area" });
event.preventDefault();
},
style: {
position: "relative",
touchAction: "none",
forcedColorAdjust: "none",
...areaStyles
}
});
},
getAreaBackgroundProps(props = {}) {
const { xChannel, yChannel } = getAreaChannels(props);
const { areaGradientStyles } = colorUtils.getColorAreaGradient(areaValue, {
xChannel,
yChannel,
dir: state.context.dir
});
return normalize.element({
...parts.areaBackground.attrs,
id: dom.getAreaGradientId(state.context),
style: {
position: "relative",
touchAction: "none",
forcedColorAdjust: "none",
...areaGradientStyles
}
});
},
getAreaThumbProps(props = {}) {
const { xChannel, yChannel } = getAreaChannels(props);
const channel = { xChannel, yChannel };
const xPercent = areaValue.getChannelValuePercent(xChannel);
const yPercent = 1 - areaValue.getChannelValuePercent(yChannel);
const xValue = areaValue.getChannelValue(xChannel);
const yValue = areaValue.getChannelValue(yChannel);
return normalize.element({
...parts.areaThumb.attrs,
id: dom.getAreaThumbId(state.context),
dir: state.context.dir,
tabIndex: disabled ? void 0 : 0,
"data-disabled": domQuery.dataAttr(disabled),
role: "slider",
"aria-valuemin": 0,
"aria-valuemax": 100,
"aria-valuenow": xValue,
"aria-label": `${xChannel} and ${yChannel}`,
"aria-roledescription": "2d slider",
"aria-valuetext": `${xChannel} ${xValue}, ${yChannel} ${yValue}`,
style: {
position: "absolute",
left: `${xPercent * 100}%`,
top: `${yPercent * 100}%`,
transform: "translate(-50%, -50%)",
touchAction: "none",
forcedColorAdjust: "none",
background: areaValue.withChannelValue("alpha", 1).toString("css")
},
onFocus() {
if (!interactive) return;
send({ type: "AREA.FOCUS", id: "area", channel });
},
onKeyDown(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
const step = domEvent.getEventStep(event);
const keyMap = {
ArrowUp() {
send({ type: "AREA.ARROW_UP", channel, step });
},
ArrowDown() {
send({ type: "AREA.ARROW_DOWN", channel, step });
},
ArrowLeft() {
send({ type: "AREA.ARROW_LEFT", channel, step });
},
ArrowRight() {
send({ type: "AREA.ARROW_RIGHT", channel, step });
},
PageUp() {
send({ type: "AREA.PAGE_UP", channel, step });
},
PageDown() {
send({ type: "AREA.PAGE_DOWN", channel, step });
},
Escape(event2) {
event2.stopPropagation();
}
};
const exec = keyMap[domEvent.getEventKey(event, state.context)];
if (exec) {
exec(event);
event.preventDefault();
}
}
});
},
getTransparencyGridProps(props = {}) {
const { size = "12px" } = props;
return normalize.element({
...parts.transparencyGrid.attrs,
style: {
"--size": size,
width: "100%",
height: "100%",
position: "absolute",
backgroundColor: "#fff",
backgroundImage: "conic-gradient(#eeeeee 0 25%, transparent 0 50%, #eeeeee 0 75%, transparent 0)",
backgroundSize: "var(--size) var(--size)",
inset: "0px",
zIndex: "auto",
pointerEvents: "none"
}
});
},
getChannelSliderProps(props) {
const { orientation = "horizontal", channel, format } = props;
return normalize.element({
...parts.channelSlider.attrs,
"data-channel": channel,
"data-orientation": orientation,
role: "presentation",
onPointerDown(event) {
if (!interactive) return;
if (!domEvent.isLeftClick(event)) return;
if (domEvent.isModifierKey(event)) return;
const point = domEvent.getEventPoint(event);
send({ type: "CHANNEL_SLIDER.POINTER_DOWN", channel, format, point, id: channel, orientation });
event.preventDefault();
},
style: {
position: "relative",
touchAction: "none"
}
});
},
getChannelSliderTrackProps(props) {
const { orientation = "horizontal", channel, format } = props;
const normalizedValue = format ? value.toFormat(format) : areaValue;
return normalize.element({
...parts.channelSliderTrack.attrs,
id: dom.getChannelSliderTrackId(state.context, channel),
role: "group",
"data-channel": channel,
"data-orientation": orientation,
style: {
position: "relative",
forcedColorAdjust: "none",
backgroundImage: getSliderBackground({
orientation,
channel,
dir: state.context.dir,
value: normalizedValue
})
}
});
},
getChannelSliderLabelProps(props) {
const { channel } = props;
return normalize.element({
...parts.channelSliderLabel.attrs,
"data-channel": channel,
onClick(event) {
if (!interactive) return;
event.preventDefault();
const thumbId = dom.getChannelSliderThumbId(state.context, channel);
dom.getById(state.context, thumbId)?.focus({ preventScroll: true });
},
style: {
userSelect: "none",
WebkitUserSelect: "none"
}
});
},
getChannelSliderValueTextProps(props) {
return normalize.element({
...parts.channelSliderValueText.attrs,
"data-channel": props.channel
});
},
getChannelSliderThumbProps(props) {
const { orientation = "horizontal", channel, format } = props;
const normalizedValue = format ? value.toFormat(format) : areaValue;
const channelRange = normalizedValue.getChannelRange(channel);
const channelValue = normalizedValue.getChannelValue(channel);
const offset = (channelValue - channelRange.minValue) / (channelRange.maxValue - channelRange.minValue);
const placementStyles = orientation === "horizontal" ? { left: `${offset * 100}%`, top: "50%" } : { top: `${offset * 100}%`, left: "50%" };
return normalize.element({
...parts.channelSliderThumb.attrs,
id: dom.getChannelSliderThumbId(state.context, channel),
role: "slider",
"aria-label": channel,
tabIndex: disabled ? void 0 : 0,
"data-channel": channel,
"data-disabled": domQuery.dataAttr(disabled),
"data-orientation": orientation,
"aria-disabled": domQuery.dataAttr(disabled),
"aria-orientation": orientation,
"aria-valuemax": channelRange.maxValue,
"aria-valuemin": channelRange.minValue,
"aria-valuenow": channelValue,
"aria-valuetext": `${channel} ${channelValue}`,
style: {
forcedColorAdjust: "none",
position: "absolute",
background: getChannelDisplayColor(areaValue, channel).toString("css"),
...placementStyles
},
onFocus() {
if (!interactive) return;
send({ type: "CHANNEL_SLIDER.FOCUS", channel });
},
onKeyDown(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
const step = domEvent.getEventStep(event) * channelRange.step;
const keyMap = {
ArrowUp() {
send({ type: "CHANNEL_SLIDER.ARROW_UP", channel, step });
},
ArrowDown() {
send({ type: "CHANNEL_SLIDER.ARROW_DOWN", channel, step });
},
ArrowLeft() {
send({ type: "CHANNEL_SLIDER.ARROW_LEFT", channel, step });
},
ArrowRight() {
send({ type: "CHANNEL_SLIDER.ARROW_RIGHT", channel, step });
},
PageUp() {
send({ type: "CHANNEL_SLIDER.PAGE_UP", channel });
},
PageDown() {
send({ type: "CHANNEL_SLIDER.PAGE_DOWN", channel });
},
Home() {
send({ type: "CHANNEL_SLIDER.HOME", channel });
},
End() {
send({ type: "CHANNEL_SLIDER.END", channel });
},
Escape(event2) {
event2.stopPropagation();
}
};
const exec = keyMap[domEvent.getEventKey(event, state.context)];
if (exec) {
exec(event);
event.preventDefault();
}
}
});
},
getChannelInputProps(props) {
const { channel } = props;
const isTextField = channel === "hex" || channel === "css";
const channelRange = getChannelRange(value, channel);
return normalize.input({
...parts.channelInput.attrs,
dir: state.context.dir,
type: isTextField ? "text" : "number",
"data-channel": channel,
"aria-label": channel,
spellCheck: false,
autoComplete: "off",
disabled,
"data-disabled": domQuery.dataAttr(disabled),
readOnly: state.context.readOnly,
defaultValue: getChannelValue(value, channel),
min: channelRange?.minValue,
max: channelRange?.maxValue,
step: channelRange?.step,
onBeforeInput(event) {
if (isTextField || !interactive) return;
const value2 = event.currentTarget.value;
if (value2.match(/[^0-9.]/g)) {
event.preventDefault();
}
},
onFocus(event) {
if (!interactive) return;
send({ type: "CHANNEL_INPUT.FOCUS", channel });
event.currentTarget.select();
},
onBlur(event) {
if (!interactive) return;
const value2 = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber;
send({ type: "CHANNEL_INPUT.BLUR", channel, value: value2, isTextField });
},
onKeyDown(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
if (event.key === "Enter") {
const value2 = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber;
send({ type: "CHANNEL_INPUT.CHANGE", channel, value: value2, isTextField });
event.preventDefault();
}
},
style: {
appearance: "none",
WebkitAppearance: "none",
MozAppearance: "textfield"
}
});
},
getHiddenInputProps() {
return normalize.input({
type: "text",
disabled,
name: state.context.name,
readOnly: state.context.readOnly,
required: state.context.required,
id: dom.getHiddenInputId(state.context),
style: domQuery.visuallyHiddenStyle,
defaultValue: valueAsString
});
},
getEyeDropperTriggerProps() {
return normalize.button({
...parts.eyeDropperTrigger.attrs,
type: "button",
dir: state.context.dir,
disabled,
"data-disabled": domQuery.dataAttr(disabled),
"aria-label": "Pick a color from the screen",
onClick() {
if (!interactive) return;
send("EYEDROPPER.CLICK");
}
});
},
getSwatchGroupProps() {
return normalize.element({
...parts.swatchGroup.attrs,
role: "group"
});
},
getSwatchTriggerState,
getSwatchTriggerProps(props) {
const swatchState = getSwatchTriggerState(props);
return normalize.button({
...parts.swatchTrigger.attrs,
disabled: swatchState.disabled,
dir: state.context.dir,
type: "button",
"aria-label": `select ${swatchState.valueAsString} as the color`,
"data-state": swatchState.checked ? "checked" : "unchecked",
"data-value": swatchState.valueAsString,
"data-disabled": domQuery.dataAttr(swatchState.disabled),
onClick() {
if (swatchState.disabled) return;
send({ type: "SWATCH_TRIGGER.CLICK", value: swatchState.value });
},
style: {
"--color": swatchState.valueAsString,
position: "relative"
}
});
},
getSwatchIndicatorProps(props) {
const swatchState = getSwatchTriggerState(props);
return normalize.element({
...parts.swatchIndicator.attrs,
dir: state.context.dir,
hidden: !swatchState.checked
});
},
getSwatchProps(props) {
const { respectAlpha = true } = props;
const swatchState = getSwatchTriggerState(props);
const color = swatchState.value.toString(respectAlpha ? "css" : "hex");
return normalize.element({
...parts.swatch.attrs,
dir: state.context.dir,
"data-state": swatchState.checked ? "checked" : "unchecked",
"data-value": swatchState.valueAsString,
style: {
"--color": color,
position: "relative",
background: color
}
});
},
getFormatTriggerProps() {
return normalize.button({
...parts.formatTrigger.attrs,
dir: state.context.dir,
type: "button",
"aria-label": `change color format to ${getNextFormat(state.context.format)}`,
onClick(event) {
if (event.currentTarget.disabled) return;
const nextFormat = getNextFormat(state.context.format);
send({ type: "FORMAT.SET", format: nextFormat, src: "format-trigger" });
}
});
},
getFormatSelectProps() {
return normalize.select({
...parts.formatSelect.attrs,
"aria-label": "change color format",
dir: state.context.dir,
defaultValue: state.context.format,
disabled,
onChange(event) {
const format = assertFormat(event.currentTarget.value);
send({ type: "FORMAT.SET", format, src: "format-select" });
}
});
}
};
}
var formats = ["hsba", "hsla", "rgba"];
var formatRegex = new RegExp(`^(${formats.join("|")})$`);
function getNextFormat(format) {
const index = formats.indexOf(format);
return formats[index + 1] ?? formats[0];
}
function assertFormat(format) {
if (formatRegex.test(format)) return format;
throw new Error(`Unsupported color format: ${format}`);
}
var parse = (colorString) => {
return colorUtils.parseColor(colorString);
};
// src/color-picker.machine.ts
var { and } = core.guards;
function machine(userContext) {
const ctx = utils.compact(userContext);
return core.createMachine(
{
id: "color-picker",
initial: ctx.open ? "open" : "idle",
context: {
dir: "ltr",
value: parse("#000000"),
format: "rgba",
disabled: false,
closeOnSelect: false,
...ctx,
activeId: null,
activeChannel: null,
activeOrientation: null,
fieldsetDisabled: false,
restoreFocus: true,
positioning: {
...ctx.positioning,
placement: "bottom"
}
},
computed: {
isRtl: (ctx2) => ctx2.dir === "rtl",
isDisabled: (ctx2) => !!ctx2.disabled || ctx2.fieldsetDisabled,
isInteractive: (ctx2) => !(ctx2.isDisabled || ctx2.readOnly),
valueAsString: (ctx2) => ctx2.value.toString(ctx2.format),
areaValue: (ctx2) => {
const format = ctx2.format.startsWith("hsl") ? "hsla" : "hsba";
return ctx2.value.toFormat(format);
}
},
activities: ["trackFormControl"],
watch: {
value: ["syncInputElements"],
format: ["syncFormatSelectElement"],
open: ["toggleVisibility"]
},
on: {
"VALUE.SET": {
actions: ["setValue"]
},
"FORMAT.SET": {
actions: ["setFormat"]
},
"CHANNEL_INPUT.CHANGE": {
actions: ["setChannelColorFromInput"]
},
"EYEDROPPER.CLICK": {
actions: ["openEyeDropper"]
},
"SWATCH_TRIGGER.CLICK": {
actions: ["setValue"]
}
},
states: {
idle: {
tags: ["closed"],
on: {
"CONTROLLED.OPEN": {
target: "open",
actions: ["setInitialFocus"]
},
OPEN: [
{
guard: "isOpenControlled",
actions: ["invokeOnOpen"]
},
{
target: "open",
actions: ["invokeOnOpen", "setInitialFocus"]
}
],
"TRIGGER.CLICK": [
{
guard: "isOpenControlled",
actions: ["invokeOnOpen"]
},
{
target: "open",
actions: ["invokeOnOpen", "setInitialFocus"]
}
],
"CHANNEL_INPUT.FOCUS": {
target: "focused",
actions: ["setActiveChannel"]
}
}
},
focused: {
tags: ["closed", "focused"],
on: {
"CONTROLLED.OPEN": {
target: "open",
actions: ["setInitialFocus"]
},
OPEN: [
{
guard: "isOpenControlled",
actions: ["invokeOnOpen"]
},
{
target: "open",
actions: ["invokeOnOpen", "setInitialFocus"]
}
],
"TRIGGER.CLICK": [
{
guard: "isOpenControlled",
actions: ["invokeOnOpen"]
},
{
target: "open",
actions: ["invokeOnOpen", "setInitialFocus"]
}
],
"CHANNEL_INPUT.FOCUS": {
actions: ["setActiveChannel"]
},
"CHANNEL_INPUT.BLUR": {
target: "idle",
actions: ["setChannelColorFromInput"]
},
"TRIGGER.BLUR": {
target: "idle"
}
}
},
open: {
tags: ["open"],
activities: ["trackPositioning", "trackDismissableElement"],
on: {
"CONTROLLED.CLOSE": [
{
guard: "shouldRestoreFocus",
target: "focused",
actions: ["setReturnFocus"]
},
{
target: "idle"
}
],
"TRIGGER.CLICK": [
{
guard: "isOpenControlled",
actions: ["invokeOnClose"]
},
{
target: "idle",
actions: ["invokeOnClose"]
}
],
"AREA.POINTER_DOWN": {
target: "open:dragging",
actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"]
},
"AREA.FOCUS": {
actions: ["setActiveChannel"]
},
"CHANNEL_SLIDER.POINTER_DOWN": {
target: "open:dragging",
actions: ["setActiveChannel", "setChannelColorFromPoint", "focusChannelThumb"]
},
"CHANNEL_SLIDER.FOCUS": {
actions: ["setActiveChannel"]
},
"AREA.ARROW_LEFT": {
actions: ["decrementAreaXChannel"]
},
"AREA.ARROW_RIGHT": {
actions: ["incrementAreaXChannel"]
},
"AREA.ARROW_UP": {
actions: ["incrementAreaYChannel"]
},
"AREA.ARROW_DOWN": {
actions: ["decrementAreaYChannel"]
},
"AREA.PAGE_UP": {
actions: ["incrementAreaXChannel"]
},
"AREA.PAGE_DOWN": {
actions: ["decrementAreaXChannel"]
},
"CHANNEL_SLIDER.ARROW_LEFT": {
actions: ["decrementChannel"]
},
"CHANNEL_SLIDER.ARROW_RIGHT": {
actions: ["incrementChannel"]
},
"CHANNEL_SLIDER.ARROW_UP": {
actions: ["incrementChannel"]
},
"CHANNEL_SLIDER.ARROW_DOWN": {
actions: ["decrementChannel"]
},
"CHANNEL_SLIDER.PAGE_UP": {
actions: ["incrementChannel"]
},
"CHANNEL_SLIDER.PAGE_DOWN": {
actions: ["decrementChannel"]
},
"CHANNEL_SLIDER.HOME": {
actions: ["setChannelToMin"]
},
"CHANNEL_SLIDER.END": {
actions: ["setChannelToMax"]
},
"CHANNEL_INPUT.BLUR": {
actions: ["setChannelColorFromInput"]
},
INTERACT_OUTSIDE: [
{
guard: "isOpenControlled",
actions: ["invokeOnClose"]
},
{
guard: "shouldRestoreFocus",
target: "focused",
actions: ["invokeOnClose", "setReturnFocus"]
},
{
target: "idle",
actions: ["invokeOnClose"]
}
],
CLOSE: [
{
guard: "isOpenControlled",
actions: ["invokeOnClose"]
},
{
target: "idle",
actions: ["invokeOnClose"]
}
],
"SWATCH_TRIGGER.CLICK": [
{
guard: and("isOpenControlled", "closeOnSelect"),
actions: ["setValue", "invokeOnClose"]
},
{
guard: "closeOnSelect",
target: "focused",
actions: ["setValue", "invokeOnClose", "setReturnFocus"]
},
{
actions: ["setValue"]
}
]
}
},
"open:dragging": {
tags: ["open"],
exit: ["clearActiveChannel"],
activities: ["trackPointerMove", "disableTextSelection", "trackPositioning", "trackDismissableElement"],
on: {
"CONTROLLED.CLOSE": [
{
guard: "shouldRestoreFocus",
target: "focused",
actions: ["setReturnFocus"]
},
{
target: "idle"
}
],
"AREA.POINTER_MOVE": {
actions: ["setAreaColorFromPoint", "focusAreaThumb"]
},
"AREA.POINTER_UP": {
target: "open",
actions: ["invokeOnChangeEnd"]
},
"CHANNEL_SLIDER.POINTER_MOVE": {
actions: ["setChannelColorFromPoint", "focusChannelThumb"]
},
"CHANNEL_SLIDER.POINTER_UP": {
target: "open",
actions: ["invokeOnChangeEnd"]
},
INTERACT_OUTSIDE: [
{
guard: "isOpenControlled",
actions: ["invokeOnClose"]
},
{
guard: "shouldRestoreFocus",
target: "focused",
actions: ["invokeOnClose", "setReturnFocus"]
},
{
target: "idle",
actions: ["invokeOnClose"]
}
],
CLOSE: [
{
guard: "isOpenControlled",
actions: ["invokeOnClose"]
},
{
target: "idle",
actions: ["invokeOnClose"]
}
]
}
}
}
},
{
guards: {
closeOnSelect: (ctx2) => !!ctx2.closeOnSelect,
isOpenControlled: (ctx2) => !!ctx2["open.controlled"],
shouldRestoreFocus: (ctx2) => !!ctx2.restoreFocus
},
activities: {
trackPositioning(ctx2) {
ctx2.currentPlacement = ctx2.positioning.placement;
const anchorEl = dom.getTriggerEl(ctx2);
const getPositionerEl = () => dom.getPositionerEl(ctx2);
return popper.getPlacement(anchorEl, getPositionerEl, {
...ctx2.positioning,
defer: true,
onComplete(data) {
ctx2.currentPlacement = data.placement;
}
});
},
trackDismissableElement(ctx2, _evt, { send }) {
const getContentEl = () => dom.getContentEl(ctx2);
return dismissable.trackDismissableElement(getContentEl, {
exclude: dom.getTriggerEl(ctx2),
defer: true,
onInteractOutside(event) {
ctx2.onInteractOutside?.(event);
if (event.defaultPrevented) return;
ctx2.restoreFocus = !(event.detail.focusable || event.detail.contextmenu);
},
onPointerDownOutside: ctx2.onPointerDownOutside,
onFocusOutside: ctx2.onFocusOutside,
onDismiss() {
send({ type: "INTERACT_OUTSIDE" });
}
});
},
trackFormControl(ctx2, _evt, { send, initialContext }) {
const inputEl = dom.getHiddenInputEl(ctx2);
return formUtils.trackFormControl(inputEl, {
onFieldsetDisabledChange(disabled) {
ctx2.fieldsetDisabled = disabled;
},
onFormReset() {
send({ type: "VALUE.SET", value: initialContext.value, src: "form.reset" });
}
});
},
trackPointerMove(ctx2, evt, { send }) {
return domEvent.trackPointerMove(dom.getDoc(ctx2), {
onPointerMove({ point }) {
const type = ctx2.activeId === "area" ? "AREA.POINTER_MOVE" : "CHANNEL_SLIDER.POINTER_MOVE";
send({ type, point, format: evt.format });
},
onPointerUp() {
const type = ctx2.activeId === "area" ? "AREA.POINTER_UP" : "CHANNEL_SLIDER.POINTER_UP";
send({ type });
}
});
},
disableTextSelection(ctx2) {
return textSelection.disableTextSelection({ doc: dom.getDoc(ctx2), target: dom.getContentEl(ctx2) });
}
},
actions: {
openEyeDropper(ctx2) {
const isSupported = "EyeDropper" in dom.getWin(ctx2);
if (!isSupported) return;
const win = dom.getWin(ctx2);
const picker = new win.EyeDropper();
picker.open().then(({ sRGBHex }) => {
const format = ctx2.value.getFormat();
const color = colorUtils.parseColor(sRGBHex).toFormat(format);
set.value(ctx2, color);
ctx2.onValueChangeEnd?.({ value: ctx2.value, valueAsString: ctx2.valueAsString });
}).catch(() => void 0);
},
setActiveChannel(ctx2, evt) {
ctx2.activeId = evt.id;
if (evt.channel) ctx2.activeChannel = evt.channel;
if (evt.orientation) ctx2.activeOrientation = evt.orientation;
},
clearActiveChannel(ctx2) {
ctx2.activeChannel = null;
ctx2.activeId = null;
ctx2.activeOrientation = null;
},
setAreaColorFromPoint(ctx2, evt) {
const normalizedValue = evt.format ? ctx2.value.toFormat(evt.format) : ctx2.areaValue;
const { xChannel, yChannel } = evt.channel || ctx2.activeChannel;
const percent = dom.getAreaValueFromPoint(ctx2, evt.point);
if (!percent) return;
const xValue = normalizedValue.getChannelPercentValue(xChannel, percent.x);
const yValue = normalizedValue.getChannelPercentValue(yChannel, 1 - percent.y);
const color = normalizedValue.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue);
set.value(ctx2, color);
},
setChannelColorFromPoint(ctx2, evt) {
const channel = evt.channel || ctx2.activeId;
const normalizedValue = evt.format ? ctx2.value.toFormat(evt.format) : ctx2.areaValue;
const percent = dom.getChannelSliderValueFromPoint(ctx2, evt.point, channel);
if (!percent) return;
const orientation = ctx2.activeOrientation || "horizontal";
const channelPercent = orientation === "horizontal" ? percent.x : percent.y;
const value = normalizedValue.getChannelPercentValue(channel, channelPercent);
const color = normalizedValue.withChannelValue(channel, value);
set.value(ctx2, color);
},
setValue(ctx2, evt) {
set.value(ctx2, evt.value);
},
setFormat(ctx2, evt) {
set.format(ctx2, evt.format);
},
syncInputElements(ctx2) {
sync.inputs(ctx2);
},
invokeOnChangeEnd(ctx2) {
invoke.changeEnd(ctx2);
},
setChannelColorFromInput(ctx2, evt) {
const { channel, isTextField, value } = evt;
const currentAlpha = ctx2.value.getChannelValue("alpha");
let color;
if (channel === "alpha") {
let valueAsNumber = parseFloat(value);
valueAsNumber = Number.isNaN(valueAsNumber) ? currentAlpha : valueAsNumber;
color = ctx2.value.withChannelValue("alpha", valueAsNumber);
} else if (isTextField) {
color = utils.tryCatch(
() => parse(value).withChannelValue("alpha", currentAlpha),
() => ctx2.value
);
} else {
const current = ctx2.value.toFormat(ctx2.format);
const valueAsNumber = Number.isNaN(value) ? current.getChannelValue(channel) : value;
color = current.withChannelValue(channel, valueAsNumber);
}
sync.inputs(ctx2, color);
set.value(ctx2, color);
},
incrementChannel(ctx2, evt) {
const color = ctx2.value.incrementChannel(evt.channel, evt.step);
set.value(ctx2, color);
},
decrementChannel(ctx2, evt) {
const color = ctx2.value.decrementChannel(evt.channel, evt.step);
set.value(ctx2, color);
},
incrementAreaXChannel(ctx2, evt) {
const { xChannel } = evt.channel;
const color = ctx2.areaValue.incrementChannel(xChannel, evt.step);
set.value(ctx2, color);
},
decrementAreaXChannel(ctx2, evt) {
const { xChannel } = evt.channel;
const color = ctx2.areaValue.decrementChannel(xChannel, evt.step);
set.value(ctx2, color);
},
incrementAreaYChannel(ctx2, evt) {
const { yChannel } = evt.channel;
const color = ctx2.areaValue.incrementChannel(yChannel, evt.step);
set.value(ctx2, color);
},
decrementAreaYChannel(ctx2, evt) {
const { yChannel } = evt.channel;
const color = ctx2.areaValue.decrementChannel(yChannel, evt.step);
set.value(ctx2, color);
},
setChannelToMax(ctx2, evt) {
const range = ctx2.value.getChannelRange(evt.channel);
const color = ctx2.value.withChannelValue(evt.channel, range.maxValue);
set.value(ctx2, color);
},
setChannelToMin(ctx2, evt) {
const range = ctx2.value.getChannelRange(evt.channel);
const color = ctx2.value.withChannelValue(evt.channel, range.minValue);
set.value(ctx2, color);
},
focusAreaThumb(ctx2) {
domQuery.raf(() => {
dom.getAreaThumbEl(ctx2)?.focus({ preventScroll: true });
});
},
focusChannelThumb(ctx2, evt) {
domQuery.raf(() => {
dom.getChannelSliderThumbEl(ctx2, evt.channel)?.focus({ preventScroll: true });
});
},
setInitialFocus(ctx2) {
domQuery.raf(() => {
const element = domQuery.getInitialFocus({
root: dom.getContentEl(ctx2),
getInitialEl: ctx2.initialFocusEl
});
element?.focus({ preventScroll: true });
});
},
setReturnFocus(ctx2) {
domQuery.raf(() => {
dom.getTriggerEl(ctx2)?.focus({ preventScroll: true });
});
},
syncFormatSelectElement(ctx2) {
sync.formatSelect(ctx2);
},
invokeOnOpen(ctx2) {
ctx2.onOpenChange?.({ open: true });
},
invokeOnClose(ctx2) {
ctx2.onOpenChange?.({ open: false });
},
toggleVisibility(ctx2, evt, { send }) {
send({ type: ctx2.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt });
}
},
compareFns: {
value: (a, b) => a.isEqual(b)
}
}
);
}
var sync = {
// sync channel inputs
inputs(ctx, color) {
const channelInputs = dom.getChannelInputEls(ctx);
domQuery.raf(() => {
channelInputs.forEach((inputEl) => {
const channel = inputEl.dataset.channel;
dom.setValue(inputEl, getChannelValue(color || ctx.value, channel));
});
});
},
// sync format select
formatSelect(ctx) {
const selectEl = dom.getFormatSelectEl(ctx);
domQuery.raf(() => {
dom.setValue(selectEl, ctx.format);
});
}
};
var invoke = {
changeEnd(ctx) {
const value = ctx.value.toFormat(ctx.format);
ctx.onValueChangeEnd?.({
value,
valueAsString: ctx.valueAsString
});
},
change(ctx) {
const value = ctx.value.toFormat(ctx.format);
ctx.onValueChange?.({
value,
valueAsString: ctx.valueAsString
});
formUtils.dispatchInputValueEvent(dom.getHiddenInputEl(ctx), { value: ctx.valueAsString });
},
formatChange(ctx) {
ctx.onFormatChange?.({ format: ctx.format });
}
};
var set = {
value(ctx, color) {
if (!color || ctx.value.isEqual(color)) return;
ctx.value = color;
invoke.change(ctx);
},
format(ctx, format) {
if (ctx.format === format) return;
ctx.format = format;
invoke.formatChange(ctx);
}
};
exports.anatomy = anatomy;
exports.connect = connect;
exports.machine = machine;
exports.parse = parse;
© 2015 - 2025 Weber Informatics LLC | Privacy Policy