@zag-js/color-picker 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5,14 +5,15 @@ var anatomy = createAnatomy("color-picker", [
5
5
  "areaThumb",
6
6
  "areaGradient",
7
7
  "channelSliderTrack",
8
- "channelSliderTrackBg",
8
+ "channelSliderTrackBackground",
9
9
  "channelSliderThumb",
10
10
  "channelInput",
11
11
  "swatch",
12
- "swatchBg",
12
+ "swatchBackground",
13
13
  "content",
14
14
  "label",
15
- "eyeDropTrigger"
15
+ "hiddenInput",
16
+ "eyeDropperTrigger"
16
17
  ]);
17
18
  var parts = anatomy.build();
18
19
 
@@ -27,6 +28,7 @@ import {
27
28
  isModifiedEvent
28
29
  } from "@zag-js/dom-event";
29
30
  import { dataAttr } from "@zag-js/dom-query";
31
+ import { visuallyHiddenStyle } from "@zag-js/visually-hidden";
30
32
 
31
33
  // src/color-picker.dom.ts
32
34
  import { getRelativePoint } from "@zag-js/dom-event";
@@ -39,20 +41,28 @@ var dom = createScope({
39
41
  getChannelSliderTrackId: (ctx, channel) => ctx.ids?.channelSliderTrack?.(channel) ?? `color-picker:${ctx.id}:slider-track:${channel}`,
40
42
  getChannelInputId: (ctx, channel) => ctx.ids?.channelInput?.(channel) ?? `color-picker:${ctx.id}:input:${channel}`,
41
43
  getChannelSliderThumbId: (ctx, channel) => ctx.ids?.channelSliderThumb?.(channel) ?? `color-picker:${ctx.id}:slider-thumb:${channel}`,
42
- getContentEl: (ctx) => dom.queryById(ctx, dom.getContentId(ctx)),
43
- getAreaThumbEl: (ctx) => dom.queryById(ctx, dom.getAreaThumbId(ctx)),
44
- getChannelSliderThumbEl: (ctx, channel) => dom.queryById(ctx, dom.getChannelSliderThumbId(ctx, channel)),
45
- getChannelInputEl: (ctx, channel) => dom.queryById(ctx, dom.getChannelInputId(ctx, channel)),
46
- getAreaEl: (ctx) => dom.queryById(ctx, dom.getAreaId(ctx)),
44
+ getHiddenInputId: (ctx) => `color-picker:${ctx.id}:hidden-input`,
45
+ getContentEl: (ctx) => dom.getById(ctx, dom.getContentId(ctx)),
46
+ getAreaThumbEl: (ctx) => dom.getById(ctx, dom.getAreaThumbId(ctx)),
47
+ getChannelSliderThumbEl: (ctx, channel) => dom.getById(ctx, dom.getChannelSliderThumbId(ctx, channel)),
48
+ getChannelInputEl: (ctx, channel) => dom.getById(ctx, dom.getChannelInputId(ctx, channel)),
49
+ getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
50
+ getAreaEl: (ctx) => dom.getById(ctx, dom.getAreaId(ctx)),
47
51
  getAreaValueFromPoint(ctx, point) {
48
- const { percent } = getRelativePoint(point, dom.getAreaEl(ctx));
52
+ const areaEl = dom.getAreaEl(ctx);
53
+ if (!areaEl)
54
+ return;
55
+ const { percent } = getRelativePoint(point, areaEl);
49
56
  return percent;
50
57
  },
51
58
  getChannelSliderTrackEl: (ctx, channel) => {
52
- return dom.queryById(ctx, dom.getChannelSliderTrackId(ctx, channel));
59
+ return dom.getById(ctx, dom.getChannelSliderTrackId(ctx, channel));
53
60
  },
54
61
  getChannelSliderValueFromPoint(ctx, point, channel) {
55
- const { percent } = getRelativePoint(point, dom.getChannelSliderTrackEl(ctx, channel));
62
+ const trackEl = dom.getChannelSliderTrackEl(ctx, channel);
63
+ if (!trackEl)
64
+ return;
65
+ const { percent } = getRelativePoint(point, trackEl);
56
66
  return percent;
57
67
  },
58
68
  getChannelInputEls: (ctx) => {
@@ -376,25 +386,31 @@ var getSliderBgImage = (ctx, props) => {
376
386
  // src/color-picker.connect.ts
377
387
  function connect(state, send, normalize) {
378
388
  const valueAsColor = state.context.valueAsColor;
389
+ const value = state.context.value;
379
390
  const isDisabled = state.context.disabled;
380
391
  const isInteractive = state.context.isInteractive;
381
392
  const isDragging = state.matches("dragging");
382
393
  const channels = valueAsColor.getColorChannels();
383
394
  return {
384
395
  isDragging,
385
- value: state.context.value,
396
+ value,
386
397
  valueAsColor,
398
+ alpha: valueAsColor.getChannelValue("alpha"),
387
399
  channels,
388
- setColor(value) {
389
- send({ type: "VALUE.SET", value: normalizeColor(value), src: "set-color" });
400
+ setColor(value2) {
401
+ send({ type: "VALUE.SET", value: normalizeColor(value2), src: "set-color" });
390
402
  },
391
- setChannelValue(channel, value) {
392
- const color = valueAsColor.withChannelValue(channel, value);
403
+ setChannelValue(channel, value2) {
404
+ const color = valueAsColor.withChannelValue(channel, value2);
393
405
  send({ type: "VALUE.SET", value: color, src: "set-channel" });
394
406
  },
395
407
  setFormat(format) {
396
- const value = valueAsColor.toFormat(format);
397
- send({ type: "VALUE.SET", value, src: "set-format" });
408
+ const value2 = valueAsColor.toFormat(format);
409
+ send({ type: "VALUE.SET", value: value2, src: "set-format" });
410
+ },
411
+ setAlpha(value2) {
412
+ const color = valueAsColor.withChannelValue("alpha", value2);
413
+ send({ type: "VALUE.SET", value: color, src: "set-alpha" });
398
414
  },
399
415
  contentProps: normalize.element({
400
416
  ...parts.content.attrs,
@@ -525,7 +541,7 @@ function connect(state, send, normalize) {
525
541
  getChannelSliderBackgroundProps(props) {
526
542
  const { orientation = "horizontal", channel } = props;
527
543
  return normalize.element({
528
- ...parts.channelSliderTrackBg.attrs,
544
+ ...parts.channelSliderTrackBackground.attrs,
529
545
  "data-orientation": orientation,
530
546
  "data-channel": channel,
531
547
  style: {
@@ -641,19 +657,19 @@ function connect(state, send, normalize) {
641
657
  onChange(event) {
642
658
  if (isTextField)
643
659
  return;
644
- const value = event.currentTarget.value;
645
- send({ type: "CHANNEL_INPUT.CHANGE", channel, value, isTextField });
660
+ const value2 = event.currentTarget.value;
661
+ send({ type: "CHANNEL_INPUT.CHANGE", channel, value: value2, isTextField });
646
662
  },
647
663
  onBlur(event) {
648
- const value = event.currentTarget.value;
649
- send({ type: "CHANNEL_INPUT.BLUR", channel, value, isTextField });
664
+ const value2 = event.currentTarget.value;
665
+ send({ type: "CHANNEL_INPUT.BLUR", channel, value: value2, isTextField });
650
666
  },
651
667
  onKeyDown(event) {
652
668
  if (!isTextField)
653
669
  return;
654
670
  if (event.key === "Enter") {
655
- const value = event.currentTarget.value;
656
- send({ type: "CHANNEL_INPUT.CHANGE", channel, value, isTextField });
671
+ const value2 = event.currentTarget.value;
672
+ send({ type: "CHANNEL_INPUT.CHANGE", channel, value: value2, isTextField });
657
673
  }
658
674
  },
659
675
  style: {
@@ -663,17 +679,35 @@ function connect(state, send, normalize) {
663
679
  }
664
680
  });
665
681
  },
682
+ hiddenInputProps: normalize.input({
683
+ ...parts.hiddenInput.attrs,
684
+ type: "text",
685
+ disabled: isDisabled,
686
+ name: state.context.name,
687
+ id: dom.getHiddenInputId(state.context),
688
+ style: visuallyHiddenStyle,
689
+ defaultValue: value,
690
+ onChange(event) {
691
+ const value2 = event.currentTarget.value;
692
+ send({ type: "VALUE.SET", value: value2, src: "input.change" });
693
+ }
694
+ }),
666
695
  eyeDropperTriggerProps: normalize.button({
667
- ...parts.eyeDropTrigger.attrs,
696
+ ...parts.eyeDropperTrigger.attrs,
697
+ disabled: isDisabled,
698
+ "data-disabled": dataAttr(isDisabled),
699
+ "aria-label": "Pick a color from the screen",
668
700
  onClick() {
701
+ if (!isInteractive)
702
+ return;
669
703
  send("EYEDROPPER.CLICK");
670
704
  }
671
705
  }),
672
706
  getSwatchBackgroundProps(props) {
673
- const { value } = props;
674
- const alpha = normalizeColor(value).getChannelValue("alpha");
707
+ const { value: value2 } = props;
708
+ const alpha = normalizeColor(value2).getChannelValue("alpha");
675
709
  return normalize.element({
676
- ...parts.swatchBg.attrs,
710
+ ...parts.swatchBackground.attrs,
677
711
  "data-alpha": alpha,
678
712
  style: {
679
713
  width: "100%",
@@ -694,12 +728,12 @@ function connect(state, send, normalize) {
694
728
  });
695
729
  },
696
730
  getSwatchProps(props) {
697
- const { value, readOnly } = props;
698
- const color = normalizeColor(value).toFormat(valueAsColor.getColorSpace());
731
+ const { value: value2, readOnly } = props;
732
+ const color = normalizeColor(value2).toFormat(valueAsColor.getColorSpace());
699
733
  return normalize.element({
700
734
  ...parts.swatch.attrs,
701
735
  onClick() {
702
- if (readOnly)
736
+ if (readOnly || !isInteractive)
703
737
  return;
704
738
  send({ type: "VALUE.SET", value: color });
705
739
  },
@@ -717,6 +751,7 @@ import { parseColor as parseColor2 } from "@zag-js/color-utils";
717
751
  import { createMachine } from "@zag-js/core";
718
752
  import { trackPointerMove } from "@zag-js/dom-event";
719
753
  import { raf } from "@zag-js/dom-query";
754
+ import { trackFormControl } from "@zag-js/form-utils";
720
755
  import { clampValue, getPercentValue as getPercentValue2, snapValueToStep as snapValueToStep2 } from "@zag-js/numeric-range";
721
756
  import { disableTextSelection } from "@zag-js/text-selection";
722
757
  import { compact } from "@zag-js/utils";
@@ -732,21 +767,21 @@ function machine(userContext) {
732
767
  activeChannel: null,
733
768
  activeOrientation: null,
734
769
  value: "#D9D9D9",
735
- ...ctx,
736
- valueAsColor: parseColor2(ctx.value || "#D9D9D9")
770
+ ...ctx
737
771
  },
738
772
  computed: {
739
773
  isRtl: (ctx2) => ctx2.dir === "rtl",
740
- isInteractive: (ctx2) => !(ctx2.disabled || ctx2.readOnly)
774
+ isInteractive: (ctx2) => !(ctx2.disabled || ctx2.readOnly),
775
+ valueAsColor: (ctx2) => parseColor2(ctx2.value)
741
776
  },
742
777
  on: {
743
778
  "VALUE.SET": {
744
779
  actions: ["setValue"]
745
780
  }
746
781
  },
747
- created: ["setValueAsColor"],
782
+ activities: ["trackFormControl"],
748
783
  watch: {
749
- value: ["setValueAsColor", "syncChannelInputs", "invokeOnChange"]
784
+ value: ["syncInputElements", "invokeOnChange"]
750
785
  },
751
786
  states: {
752
787
  idle: {
@@ -766,6 +801,10 @@ function machine(userContext) {
766
801
  target: "dragging",
767
802
  actions: ["setActiveChannel", "setChannelColorFromPoint", "focusChannelThumb"]
768
803
  },
804
+ "CHANNEL_SLIDER.FOCUS": {
805
+ target: "focused",
806
+ actions: ["setActiveChannel"]
807
+ },
769
808
  "CHANNEL_INPUT.FOCUS": {
770
809
  target: "focused",
771
810
  actions: ["setActiveChannel"]
@@ -876,6 +915,17 @@ function machine(userContext) {
876
915
  isTextField: (_ctx, evt) => !!evt.isTextField
877
916
  },
878
917
  activities: {
918
+ trackFormControl(ctx2, _evt, { send, initialContext }) {
919
+ const inputEl = dom.getHiddenInputEl(ctx2);
920
+ return trackFormControl(inputEl, {
921
+ onFieldsetDisabled() {
922
+ ctx2.disabled = true;
923
+ },
924
+ onFormReset() {
925
+ send({ type: "VALUE.SET", value: initialContext.value, src: "form.reset" });
926
+ }
927
+ });
928
+ },
879
929
  trackPointerMove(ctx2, _evt, { send }) {
880
930
  return trackPointerMove(dom.getDoc(ctx2), {
881
931
  onPointerMove({ point }) {
@@ -923,6 +973,8 @@ function machine(userContext) {
923
973
  setAreaColorFromPoint(ctx2, evt) {
924
974
  const { xChannel, yChannel } = evt.channel || ctx2.activeChannel;
925
975
  const percent = dom.getAreaValueFromPoint(ctx2, evt.point);
976
+ if (!percent)
977
+ return;
926
978
  const { getColorFromPoint } = getChannelDetails(ctx2.valueAsColor, xChannel, yChannel);
927
979
  const color = getColorFromPoint(percent.x, percent.y);
928
980
  if (!color)
@@ -932,6 +984,8 @@ function machine(userContext) {
932
984
  setChannelColorFromPoint(ctx2, evt) {
933
985
  const channel = evt.channel || ctx2.activeId;
934
986
  const percent = dom.getChannelSliderValueFromPoint(ctx2, evt.point, channel);
987
+ if (!percent)
988
+ return;
935
989
  const { minValue, maxValue, step } = ctx2.valueAsColor.getChannelRange(channel);
936
990
  const orientation = ctx2.activeOrientation || "horizontal";
937
991
  const point = orientation === "horizontal" ? percent.x : percent.y;
@@ -943,24 +997,18 @@ function machine(userContext) {
943
997
  setValue(ctx2, evt) {
944
998
  setColor(ctx2, evt.value);
945
999
  },
946
- setValueAsColor(ctx2) {
947
- try {
948
- const color = parseColor2(ctx2.value);
949
- if (color.isEqual(ctx2.valueAsColor))
950
- return;
951
- ctx2.valueAsColor = color;
952
- } catch {
953
- }
954
- },
955
- syncChannelInputs(ctx2) {
1000
+ syncInputElements(ctx2) {
956
1001
  const inputs = dom.getChannelInputEls(ctx2);
957
1002
  inputs.forEach((input) => {
958
- const channel = input.getAttribute("data-channel");
1003
+ const channel = input.dataset.channel;
959
1004
  if (!channel)
960
1005
  return;
961
- const value = getChannelInputValue(ctx2.valueAsColor, channel);
962
- input.value = value.toString();
1006
+ input.value = getChannelInputValue(ctx2.valueAsColor, channel);
963
1007
  });
1008
+ const hiddenInput = dom.getHiddenInputEl(ctx2);
1009
+ if (!hiddenInput)
1010
+ return;
1011
+ hiddenInput.value = ctx2.value;
964
1012
  },
965
1013
  setChannelColorFromInput(ctx2, evt) {
966
1014
  const { channel, isTextField, value } = evt;
@@ -1045,7 +1093,6 @@ function machine(userContext) {
1045
1093
  }
1046
1094
  var setColor = (ctx, color) => {
1047
1095
  ctx.value = color.toString("css");
1048
- ctx.valueAsColor = color;
1049
1096
  };
1050
1097
  export {
1051
1098
  anatomy,