All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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