@zag-js/color-picker 0.29.0 → 0.30.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.js CHANGED
@@ -46,8 +46,11 @@ var anatomy = (0, import_anatomy.createAnatomy)("color-picker", [
46
46
  "transparencyGrid",
47
47
  "swatchGroup",
48
48
  "swatchTrigger",
49
+ "swatchIndicator",
49
50
  "swatch",
50
- "eyeDropperTrigger"
51
+ "eyeDropperTrigger",
52
+ "formatTrigger",
53
+ "formatSelect"
51
54
  ]);
52
55
  var parts = anatomy.build();
53
56
 
@@ -71,6 +74,7 @@ var dom = (0, import_dom_query.createScope)({
71
74
  getTriggerId: (ctx) => ctx.ids?.trigger ?? `color-picker:${ctx.id}:trigger`,
72
75
  getContentId: (ctx) => ctx.ids?.content ?? `color-picker:${ctx.id}:content`,
73
76
  getPositionerId: (ctx) => `color-picker:${ctx.id}:positioner`,
77
+ getFormatSelectId: (ctx) => `color-picker:${ctx.id}:format-select`,
74
78
  getAreaId: (ctx) => ctx.ids?.area ?? `color-picker:${ctx.id}:area`,
75
79
  getAreaGradientId: (ctx) => ctx.ids?.areaGradient ?? `color-picker:${ctx.id}:area-gradient`,
76
80
  getAreaThumbId: (ctx) => ctx.ids?.areaThumb ?? `color-picker:${ctx.id}:area-thumb`,
@@ -85,6 +89,7 @@ var dom = (0, import_dom_query.createScope)({
85
89
  ...(0, import_dom_query.queryAll)(dom.getControlEl(ctx), `input[data-channel="${channel}"]`)
86
90
  ];
87
91
  },
92
+ getFormatSelectEl: (ctx) => dom.getById(ctx, dom.getFormatSelectId(ctx)),
88
93
  getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
89
94
  getAreaEl: (ctx) => dom.getById(ctx, dom.getAreaId(ctx)),
90
95
  getAreaValueFromPoint(ctx, point) {
@@ -116,10 +121,7 @@ var dom = (0, import_dom_query.createScope)({
116
121
  getFirstFocusableEl: (ctx) => (0, import_tabbable.getFirstFocusable)(dom.getContentEl(ctx), "if-empty"),
117
122
  getInitialFocusEl: (ctx) => {
118
123
  let el = (0, import_utils.runIfFn)(ctx.initialFocusEl);
119
- if (!el && ctx.autoFocus)
120
- el = dom.getFirstFocusableEl(ctx);
121
- if (!el)
122
- el = dom.getContentEl(ctx);
124
+ el = dom.getFirstFocusableEl(ctx) ?? dom.getContentEl(ctx);
123
125
  return el;
124
126
  }
125
127
  });
@@ -164,7 +166,7 @@ function getChannelValue(color, channel) {
164
166
  case "hue":
165
167
  return isHSL ? color.toFormat("hsla").getChannelValue("hue").toString() : color.toFormat("hsba").getChannelValue("hue").toString();
166
168
  case "saturation":
167
- return isHSL ? color.toFormat("hsl").getChannelValue("saturation").toString() : color.toFormat("hsb").getChannelValue("saturation").toString();
169
+ return isHSL ? color.toFormat("hsla").getChannelValue("saturation").toString() : color.toFormat("hsba").getChannelValue("saturation").toString();
168
170
  case "lightness":
169
171
  return color.toFormat("hsla").getChannelValue("lightness").toString();
170
172
  case "brightness":
@@ -193,13 +195,13 @@ function getChannelRange(color, channel) {
193
195
  case "hue":
194
196
  case "saturation":
195
197
  case "lightness":
196
- return color.toFormat("hsl").getChannelRange(channel);
198
+ return color.toFormat("hsla").getChannelRange(channel);
197
199
  case "brightness":
198
- return color.toFormat("hsb").getChannelRange(channel);
200
+ return color.toFormat("hsba").getChannelRange(channel);
199
201
  case "red":
200
202
  case "green":
201
203
  case "blue":
202
- return color.toFormat("rgb").getChannelRange(channel);
204
+ return color.toFormat("rgba").getChannelRange(channel);
203
205
  default:
204
206
  return color.getChannelRange(channel);
205
207
  }
@@ -246,13 +248,15 @@ var getSliderBackground = (props) => {
246
248
  // src/color-picker.connect.ts
247
249
  function connect(state, send, normalize) {
248
250
  const value = state.context.value;
251
+ const areaValue = state.context.areaValue;
249
252
  const valueAsString = state.context.valueAsString;
250
253
  const isDisabled = state.context.isDisabled;
251
254
  const isInteractive = state.context.isInteractive;
252
255
  const isDragging = state.hasTag("dragging");
253
256
  const isOpen = state.hasTag("open");
257
+ const isFocused = state.hasTag("focused");
254
258
  const getAreaChannels = (props) => {
255
- const channels = value.getChannels();
259
+ const channels = areaValue.getChannels();
256
260
  return {
257
261
  xChannel: props.xChannel ?? channels[1],
258
262
  yChannel: props.yChannel ?? channels[2]
@@ -263,11 +267,26 @@ function connect(state, send, normalize) {
263
267
  ...state.context.positioning,
264
268
  placement: currentPlacement
265
269
  });
270
+ function getSwatchTriggerState(props) {
271
+ const color = (0, import_color_utils3.normalizeColor)(props.value).toFormat(state.context.format);
272
+ return {
273
+ value: color,
274
+ valueAsString: color.toString("hex"),
275
+ isChecked: color.isEqual(value),
276
+ isDisabled: props.disabled || !isInteractive
277
+ };
278
+ }
266
279
  return {
267
280
  isDragging,
268
281
  isOpen,
269
282
  valueAsString,
270
283
  value,
284
+ open() {
285
+ send({ type: "OPEN" });
286
+ },
287
+ close() {
288
+ send({ type: "CLOSE" });
289
+ },
271
290
  setValue(value2) {
272
291
  send({ type: "VALUE.SET", value: (0, import_color_utils3.normalizeColor)(value2), src: "set-color" });
273
292
  },
@@ -278,7 +297,7 @@ function connect(state, send, normalize) {
278
297
  const color = value.withChannelValue(channel, channelValue);
279
298
  send({ type: "VALUE.SET", value: color, src: "set-channel" });
280
299
  },
281
- format: value.outputFormat ?? value.getFormat(),
300
+ format: state.context.format,
282
301
  setFormat(format) {
283
302
  const formatValue = value.toFormat(format);
284
303
  send({ type: "VALUE.SET", value: formatValue, src: "set-format" });
@@ -305,6 +324,7 @@ function connect(state, send, normalize) {
305
324
  htmlFor: dom.getHiddenInputId(state.context),
306
325
  "data-disabled": (0, import_dom_query2.dataAttr)(isDisabled),
307
326
  "data-readonly": (0, import_dom_query2.dataAttr)(state.context.readOnly),
327
+ "data-focus": (0, import_dom_query2.dataAttr)(isFocused),
308
328
  onClick(event) {
309
329
  event.preventDefault();
310
330
  const inputEl = (0, import_dom_query2.query)(dom.getControlEl(state.context), "[data-channel=hex]");
@@ -316,21 +336,35 @@ function connect(state, send, normalize) {
316
336
  id: dom.getControlId(state.context),
317
337
  dir: state.context.dir,
318
338
  "data-disabled": (0, import_dom_query2.dataAttr)(isDisabled),
319
- "data-readonly": (0, import_dom_query2.dataAttr)(state.context.readOnly)
339
+ "data-readonly": (0, import_dom_query2.dataAttr)(state.context.readOnly),
340
+ "data-state": isOpen ? "open" : "closed",
341
+ "data-focus": (0, import_dom_query2.dataAttr)(isFocused)
320
342
  }),
321
343
  triggerProps: normalize.button({
322
344
  ...parts.trigger.attrs,
323
345
  id: dom.getTriggerId(state.context),
324
346
  dir: state.context.dir,
347
+ disabled: isDisabled,
348
+ "aria-label": `select color. current color is ${valueAsString}`,
325
349
  "aria-controls": dom.getContentId(state.context),
326
350
  "aria-labelledby": dom.getLabelId(state.context),
327
351
  "data-disabled": (0, import_dom_query2.dataAttr)(isDisabled),
328
352
  "data-readonly": (0, import_dom_query2.dataAttr)(state.context.readOnly),
329
353
  "data-placement": currentPlacement,
354
+ "aria-expanded": (0, import_dom_query2.dataAttr)(isOpen),
355
+ "data-state": isOpen ? "open" : "closed",
356
+ "data-focus": (0, import_dom_query2.dataAttr)(isFocused),
330
357
  type: "button",
331
358
  onClick() {
359
+ if (!isInteractive)
360
+ return;
332
361
  send({ type: "TRIGGER.CLICK" });
333
362
  },
363
+ onBlur() {
364
+ if (!isInteractive)
365
+ return;
366
+ send({ type: "TRIGGER.BLUR" });
367
+ },
334
368
  style: {
335
369
  position: "relative"
336
370
  }
@@ -346,11 +380,12 @@ function connect(state, send, normalize) {
346
380
  id: dom.getContentId(state.context),
347
381
  dir: state.context.dir,
348
382
  "data-placement": currentPlacement,
383
+ "data-state": isOpen ? "open" : "closed",
349
384
  hidden: !isOpen
350
385
  }),
351
386
  getAreaProps(props = {}) {
352
387
  const { xChannel, yChannel } = getAreaChannels(props);
353
- const { areaStyles } = (0, import_color_utils3.getColorAreaGradient)(value, {
388
+ const { areaStyles } = (0, import_color_utils3.getColorAreaGradient)(areaValue, {
354
389
  xChannel,
355
390
  yChannel,
356
391
  dir: state.context.dir
@@ -380,7 +415,7 @@ function connect(state, send, normalize) {
380
415
  },
381
416
  getAreaBackgroundProps(props = {}) {
382
417
  const { xChannel, yChannel } = getAreaChannels(props);
383
- const { areaGradientStyles } = (0, import_color_utils3.getColorAreaGradient)(value, {
418
+ const { areaGradientStyles } = (0, import_color_utils3.getColorAreaGradient)(areaValue, {
384
419
  xChannel,
385
420
  yChannel,
386
421
  dir: state.context.dir
@@ -399,25 +434,35 @@ function connect(state, send, normalize) {
399
434
  getAreaThumbProps(props = {}) {
400
435
  const { xChannel, yChannel } = getAreaChannels(props);
401
436
  const channel = { xChannel, yChannel };
402
- const x = value.getChannelValuePercent(xChannel);
403
- const y = 1 - value.getChannelValuePercent(yChannel);
437
+ const xPercent = areaValue.getChannelValuePercent(xChannel);
438
+ const yPercent = 1 - areaValue.getChannelValuePercent(yChannel);
439
+ const xValue = areaValue.getChannelValue(xChannel);
440
+ const yValue = areaValue.getChannelValue(yChannel);
404
441
  return normalize.element({
405
442
  ...parts.areaThumb.attrs,
406
443
  id: dom.getAreaThumbId(state.context),
407
444
  dir: state.context.dir,
408
445
  tabIndex: isDisabled ? void 0 : 0,
409
446
  "data-disabled": (0, import_dom_query2.dataAttr)(isDisabled),
410
- role: "presentation",
447
+ role: "slider",
448
+ "aria-valuemin": 0,
449
+ "aria-valuemax": 100,
450
+ "aria-valuenow": xValue,
451
+ "aria-label": `${xChannel} and ${yChannel}`,
452
+ "aria-roledescription": "2d slider",
453
+ "aria-valuetext": `${xChannel} ${xValue}, ${yChannel} ${yValue}`,
411
454
  style: {
412
455
  position: "absolute",
413
- left: `${x * 100}%`,
414
- top: `${y * 100}%`,
456
+ left: `${xPercent * 100}%`,
457
+ top: `${yPercent * 100}%`,
415
458
  transform: "translate(-50%, -50%)",
416
459
  touchAction: "none",
417
460
  forcedColorAdjust: "none",
418
- background: value.withChannelValue("alpha", 1).toString("css")
461
+ background: areaValue.withChannelValue("alpha", 1).toString("css")
419
462
  },
420
463
  onFocus() {
464
+ if (!isInteractive)
465
+ return;
421
466
  send({ type: "AREA.FOCUS", id: "area", channel });
422
467
  },
423
468
  onKeyDown(event) {
@@ -511,15 +556,15 @@ function connect(state, send, normalize) {
511
556
  orientation,
512
557
  channel,
513
558
  dir: state.context.dir,
514
- value
559
+ value: areaValue
515
560
  })
516
561
  }
517
562
  });
518
563
  },
519
564
  getChannelSliderThumbProps(props) {
520
565
  const { orientation = "horizontal", channel } = props;
521
- const { minValue, maxValue, step: stepValue } = value.getChannelRange(channel);
522
- const channelValue = value.getChannelValue(channel);
566
+ const { minValue, maxValue, step: stepValue } = areaValue.getChannelRange(channel);
567
+ const channelValue = areaValue.getChannelValue(channel);
523
568
  const offset = (channelValue - minValue) / (maxValue - minValue);
524
569
  const placementStyles = orientation === "horizontal" ? { left: `${offset * 100}%`, top: "50%" } : { top: `${offset * 100}%`, left: "50%" };
525
570
  return normalize.element({
@@ -536,10 +581,11 @@ function connect(state, send, normalize) {
536
581
  "aria-valuemax": maxValue,
537
582
  "aria-valuemin": minValue,
538
583
  "aria-valuenow": channelValue,
584
+ "aria-valuetext": `${channel} ${channelValue}`,
539
585
  style: {
540
586
  forcedColorAdjust: "none",
541
587
  position: "absolute",
542
- background: getChannelDisplayColor(value, channel).toString("css"),
588
+ background: getChannelDisplayColor(areaValue, channel).toString("css"),
543
589
  ...placementStyles
544
590
  },
545
591
  onFocus() {
@@ -598,6 +644,8 @@ function connect(state, send, normalize) {
598
644
  type: isTextField ? "text" : "number",
599
645
  "data-channel": channel,
600
646
  "aria-label": channel,
647
+ spellCheck: false,
648
+ autoComplete: "off",
601
649
  disabled: isDisabled,
602
650
  "data-disabled": (0, import_dom_query2.dataAttr)(isDisabled),
603
651
  readOnly: state.context.readOnly,
@@ -606,7 +654,7 @@ function connect(state, send, normalize) {
606
654
  max: range?.maxValue,
607
655
  step: range?.step,
608
656
  onBeforeInput(event) {
609
- if (isTextField)
657
+ if (isTextField || !isInteractive)
610
658
  return;
611
659
  const value2 = event.currentTarget.value;
612
660
  if (value2.match(/[^0-9.]/g)) {
@@ -614,14 +662,20 @@ function connect(state, send, normalize) {
614
662
  }
615
663
  },
616
664
  onFocus(event) {
665
+ if (!isInteractive)
666
+ return;
617
667
  send({ type: "CHANNEL_INPUT.FOCUS", channel });
618
668
  event.target.select();
619
669
  },
620
670
  onBlur(event) {
671
+ if (!isInteractive)
672
+ return;
621
673
  const value2 = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber;
622
674
  send({ type: "CHANNEL_INPUT.BLUR", channel, value: value2, isTextField });
623
675
  },
624
676
  onKeyDown(event) {
677
+ if (!isInteractive)
678
+ return;
625
679
  if (event.key === "Enter") {
626
680
  const value2 = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber;
627
681
  send({ type: "CHANNEL_INPUT.CHANGE", channel, value: value2, isTextField });
@@ -660,42 +714,87 @@ function connect(state, send, normalize) {
660
714
  ...parts.swatchGroup.attrs,
661
715
  role: "group"
662
716
  }),
717
+ getSwatchTriggerState,
663
718
  getSwatchTriggerProps(props) {
664
- const { value: valueProp } = props;
665
- const color = (0, import_color_utils3.normalizeColor)(valueProp).toFormat(value.getFormat());
719
+ const triggerState = getSwatchTriggerState(props);
666
720
  return normalize.button({
667
721
  ...parts.swatchTrigger.attrs,
668
- disabled: isDisabled,
722
+ disabled: triggerState.isDisabled,
669
723
  dir: state.context.dir,
670
724
  type: "button",
671
- "data-value": color.toString("hex"),
725
+ "aria-label": `select ${triggerState.valueAsString} as the color`,
726
+ "data-state": triggerState.isChecked ? "checked" : "unchecked",
727
+ "data-value": triggerState.valueAsString,
728
+ "data-disabled": (0, import_dom_query2.dataAttr)(triggerState.isDisabled),
672
729
  onClick() {
673
- if (!isInteractive)
730
+ if (triggerState.isDisabled)
674
731
  return;
675
- send({ type: "VALUE.SET", value: color });
732
+ send({ type: "SWATCH_TRIGGER.CLICK", value: triggerState.value });
676
733
  },
677
734
  style: {
678
735
  position: "relative"
679
736
  }
680
737
  });
681
738
  },
739
+ getSwatchIndicatorProps(props) {
740
+ const triggerState = getSwatchTriggerState(props);
741
+ return normalize.element({
742
+ ...parts.swatchIndicator.attrs,
743
+ dir: state.context.dir,
744
+ "aria-hidden": true,
745
+ "data-state": triggerState.isChecked ? "open" : "closed"
746
+ });
747
+ },
682
748
  getSwatchProps(props) {
683
- const { value: valueProp, respectAlpha = true } = props;
684
- const colorValue = (0, import_color_utils3.normalizeColor)(valueProp);
685
- const color = colorValue.toFormat(value.getFormat());
749
+ const { respectAlpha = true } = props;
750
+ const triggerState = getSwatchTriggerState(props);
686
751
  return normalize.element({
687
752
  ...parts.swatch.attrs,
688
753
  dir: state.context.dir,
689
- "data-state": colorValue.isEqual(value) ? "selected" : "unselected",
690
- "data-value": color.toString("hex"),
754
+ "data-state": triggerState.isChecked ? "checked" : "unchecked",
755
+ "data-value": triggerState.valueAsString,
691
756
  style: {
692
757
  position: "relative",
693
- background: color.toString(respectAlpha ? "css" : "hex")
758
+ background: triggerState.value.toString(respectAlpha ? "css" : "hex")
694
759
  }
695
760
  });
696
- }
761
+ },
762
+ formatTriggerProps: normalize.button({
763
+ ...parts.formatTrigger.attrs,
764
+ dir: state.context.dir,
765
+ type: "button",
766
+ "aria-label": `change color format to ${getNextFormat(state.context.format)}`,
767
+ onClick(event) {
768
+ if (event.currentTarget.disabled)
769
+ return;
770
+ const nextFormat = getNextFormat(state.context.format);
771
+ send({ type: "FORMAT.SET", format: nextFormat, src: "format-trigger" });
772
+ }
773
+ }),
774
+ formatSelectProps: normalize.select({
775
+ ...parts.formatSelect.attrs,
776
+ "aria-label": "change color format",
777
+ dir: state.context.dir,
778
+ defaultValue: state.context.format,
779
+ disabled: isDisabled,
780
+ onChange(event) {
781
+ const format = assertFormat(event.currentTarget.value);
782
+ send({ type: "FORMAT.SET", format, src: "format-select" });
783
+ }
784
+ })
697
785
  };
698
786
  }
787
+ var formats = ["hsba", "hsla", "rgba"];
788
+ var formatRegex = new RegExp(`^(${formats.join("|")})$`);
789
+ function getNextFormat(format) {
790
+ const index = formats.indexOf(format);
791
+ return formats[index + 1] ?? formats[0];
792
+ }
793
+ function assertFormat(format) {
794
+ if (formatRegex.test(format))
795
+ return format;
796
+ throw new Error(`Unsupported color format: ${format}`);
797
+ }
699
798
 
700
799
  // src/color-picker.machine.ts
701
800
  var import_color_utils5 = require("@zag-js/color-utils");
@@ -710,13 +809,8 @@ var import_utils2 = require("@zag-js/utils");
710
809
 
711
810
  // src/color-picker.parse.ts
712
811
  var import_color_utils4 = require("@zag-js/color-utils");
713
- var parse = (color) => {
714
- const value = (0, import_color_utils4.parseColor)(color);
715
- const colorFormat = value.getFormat();
716
- const lockedFormat = colorFormat === "hsla" ? "hsla" : "hsba";
717
- const result = value.toFormat(lockedFormat);
718
- result.outputFormat = colorFormat;
719
- return result;
812
+ var parse = (colorString) => {
813
+ return (0, import_color_utils4.parseColor)(colorString);
720
814
  };
721
815
 
722
816
  // src/color-picker.machine.ts
@@ -729,13 +823,14 @@ function machine(userContext) {
729
823
  context: {
730
824
  dir: "ltr",
731
825
  value: parse("#000000"),
826
+ format: "rgba",
732
827
  disabled: false,
828
+ closeOnSelect: false,
733
829
  ...ctx,
734
830
  activeId: null,
735
831
  activeChannel: null,
736
832
  activeOrientation: null,
737
833
  fieldsetDisabled: false,
738
- autoFocus: true,
739
834
  positioning: {
740
835
  ...ctx.positioning,
741
836
  placement: "bottom"
@@ -745,22 +840,30 @@ function machine(userContext) {
745
840
  isRtl: (ctx2) => ctx2.dir === "rtl",
746
841
  isDisabled: (ctx2) => !!ctx2.disabled || ctx2.fieldsetDisabled,
747
842
  isInteractive: (ctx2) => !(ctx2.isDisabled || ctx2.readOnly),
748
- valueAsString: (ctx2) => {
749
- const color = ctx2.value.outputFormat ? ctx2.value.toFormat(ctx2.value.outputFormat) : ctx2.value;
750
- return color.toString("css");
843
+ valueAsString: (ctx2) => ctx2.value.toString(ctx2.format),
844
+ areaValue: (ctx2) => {
845
+ const format = ctx2.format.startsWith("hsl") ? "hsla" : "hsba";
846
+ return ctx2.value.toFormat(format);
751
847
  }
752
848
  },
753
849
  activities: ["trackFormControl"],
754
850
  watch: {
755
851
  value: ["syncInputElements"],
852
+ format: ["syncFormatSelectElement"],
756
853
  open: ["toggleVisibility"]
757
854
  },
758
855
  on: {
759
856
  "VALUE.SET": {
760
857
  actions: ["setValue"]
761
858
  },
859
+ "FORMAT.SET": {
860
+ actions: ["setFormat"]
861
+ },
762
862
  "CHANNEL_INPUT.CHANGE": {
763
863
  actions: ["setChannelColorFromInput"]
864
+ },
865
+ "EYEDROPPER.CLICK": {
866
+ actions: ["openEyeDropper"]
764
867
  }
765
868
  },
766
869
  states: {
@@ -798,6 +901,9 @@ function machine(userContext) {
798
901
  "CHANNEL_INPUT.BLUR": {
799
902
  target: "idle",
800
903
  actions: ["setChannelColorFromInput"]
904
+ },
905
+ "TRIGGER.BLUR": {
906
+ target: "idle"
801
907
  }
802
908
  }
803
909
  },
@@ -809,9 +915,6 @@ function machine(userContext) {
809
915
  target: "idle",
810
916
  actions: ["invokeOnClose"]
811
917
  },
812
- "EYEDROPPER.CLICK": {
813
- actions: ["openEyeDropper"]
814
- },
815
918
  "AREA.POINTER_DOWN": {
816
919
  target: "open:dragging",
817
920
  actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"]
@@ -885,7 +988,17 @@ function machine(userContext) {
885
988
  CLOSE: {
886
989
  target: "idle",
887
990
  actions: ["invokeOnClose"]
888
- }
991
+ },
992
+ "SWATCH_TRIGGER.CLICK": [
993
+ {
994
+ guard: "closeOnSelect",
995
+ target: "focused",
996
+ actions: ["setValue", "setReturnFocus", "invokeOnClose"]
997
+ },
998
+ {
999
+ actions: ["setValue"]
1000
+ }
1001
+ ]
889
1002
  }
890
1003
  },
891
1004
  "open:dragging": {
@@ -928,7 +1041,8 @@ function machine(userContext) {
928
1041
  },
929
1042
  {
930
1043
  guards: {
931
- isTargetFocusable: (_ctx, evt) => evt.restoreFocus
1044
+ isTargetFocusable: (_ctx, evt) => evt.restoreFocus,
1045
+ closeOnSelect: (ctx2) => !!ctx2.closeOnSelect
932
1046
  },
933
1047
  activities: {
934
1048
  trackPositioning(ctx2) {
@@ -1023,9 +1137,9 @@ function machine(userContext) {
1023
1137
  const percent = dom.getAreaValueFromPoint(ctx2, evt.point);
1024
1138
  if (!percent)
1025
1139
  return;
1026
- const xValue = ctx2.value.getChannelPercentValue(xChannel, percent.x);
1027
- const yValue = ctx2.value.getChannelPercentValue(yChannel, 1 - percent.y);
1028
- const color = ctx2.value.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue);
1140
+ const xValue = ctx2.areaValue.getChannelPercentValue(xChannel, percent.x);
1141
+ const yValue = ctx2.areaValue.getChannelPercentValue(yChannel, 1 - percent.y);
1142
+ const color = ctx2.areaValue.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue);
1029
1143
  set.value(ctx2, color);
1030
1144
  },
1031
1145
  setChannelColorFromPoint(ctx2, evt) {
@@ -1035,13 +1149,16 @@ function machine(userContext) {
1035
1149
  return;
1036
1150
  const orientation = ctx2.activeOrientation || "horizontal";
1037
1151
  const channelPercent = orientation === "horizontal" ? percent.x : percent.y;
1038
- const value = ctx2.value.getChannelPercentValue(channel, channelPercent);
1039
- const color = ctx2.value.withChannelValue(channel, value);
1152
+ const value = ctx2.areaValue.getChannelPercentValue(channel, channelPercent);
1153
+ const color = ctx2.areaValue.withChannelValue(channel, value);
1040
1154
  set.value(ctx2, color);
1041
1155
  },
1042
1156
  setValue(ctx2, evt) {
1043
1157
  set.value(ctx2, evt.value);
1044
1158
  },
1159
+ setFormat(ctx2, evt) {
1160
+ set.format(ctx2, evt.format);
1161
+ },
1045
1162
  syncInputElements(ctx2) {
1046
1163
  sync.inputs(ctx2);
1047
1164
  },
@@ -1051,26 +1168,22 @@ function machine(userContext) {
1051
1168
  setChannelColorFromInput(ctx2, evt) {
1052
1169
  const { channel, isTextField, value } = evt;
1053
1170
  const currentAlpha = ctx2.value.getChannelValue("alpha");
1171
+ let color;
1054
1172
  if (channel === "alpha") {
1055
1173
  let valueAsNumber = parseFloat(value);
1056
1174
  valueAsNumber = Number.isNaN(valueAsNumber) ? currentAlpha : valueAsNumber;
1057
- const newColor = ctx2.value.withChannelValue("alpha", valueAsNumber);
1058
- set.value(ctx2, newColor);
1059
- return;
1060
- }
1061
- if (isTextField) {
1062
- const color2 = (0, import_utils2.tryCatch)(
1175
+ color = ctx2.value.withChannelValue("alpha", valueAsNumber);
1176
+ } else if (isTextField) {
1177
+ color = (0, import_utils2.tryCatch)(
1063
1178
  () => parse(value).withChannelValue("alpha", currentAlpha),
1064
1179
  () => ctx2.value
1065
1180
  );
1066
- const inputEls = dom.getChannelInputEl(ctx2, channel);
1067
- inputEls.forEach((inputEl) => {
1068
- dom.setValue(inputEl, getChannelValue(color2, channel));
1069
- });
1070
- set.value(ctx2, color2);
1071
- return;
1181
+ } else {
1182
+ const current = ctx2.value.toFormat(ctx2.format);
1183
+ const valueAsNumber = Number.isNaN(value) ? current.getChannelValue(channel) : value;
1184
+ color = current.withChannelValue(channel, valueAsNumber);
1072
1185
  }
1073
- const color = ctx2.value.toFormat(ctx2.value.outputFormat).withChannelValue(channel, value);
1186
+ sync.inputs(ctx2, color);
1074
1187
  set.value(ctx2, color);
1075
1188
  },
1076
1189
  incrementChannel(ctx2, evt) {
@@ -1083,22 +1196,22 @@ function machine(userContext) {
1083
1196
  },
1084
1197
  incrementXChannel(ctx2, evt) {
1085
1198
  const { xChannel } = evt.channel;
1086
- const color = ctx2.value.incrementChannel(xChannel, evt.step);
1199
+ const color = ctx2.areaValue.incrementChannel(xChannel, evt.step);
1087
1200
  set.value(ctx2, color);
1088
1201
  },
1089
1202
  decrementXChannel(ctx2, evt) {
1090
1203
  const { xChannel } = evt.channel;
1091
- const color = ctx2.value.decrementChannel(xChannel, evt.step);
1204
+ const color = ctx2.areaValue.decrementChannel(xChannel, evt.step);
1092
1205
  set.value(ctx2, color);
1093
1206
  },
1094
1207
  incrementYChannel(ctx2, evt) {
1095
1208
  const { yChannel } = evt.channel;
1096
- const color = ctx2.value.incrementChannel(yChannel, evt.step);
1209
+ const color = ctx2.areaValue.incrementChannel(yChannel, evt.step);
1097
1210
  set.value(ctx2, color);
1098
1211
  },
1099
1212
  decrementYChannel(ctx2, evt) {
1100
1213
  const { yChannel } = evt.channel;
1101
- const color = ctx2.value.decrementChannel(yChannel, evt.step);
1214
+ const color = ctx2.areaValue.decrementChannel(yChannel, evt.step);
1102
1215
  set.value(ctx2, color);
1103
1216
  },
1104
1217
  setChannelToMax(ctx2, evt) {
@@ -1131,6 +1244,9 @@ function machine(userContext) {
1131
1244
  dom?.focus(ctx2, dom.getTriggerEl(ctx2));
1132
1245
  });
1133
1246
  },
1247
+ syncFormatSelectElement(ctx2) {
1248
+ sync.formatSelect(ctx2);
1249
+ },
1134
1250
  invokeOnOpen(ctx2) {
1135
1251
  ctx2.onOpenChange?.({ open: true });
1136
1252
  },
@@ -1148,41 +1264,56 @@ function machine(userContext) {
1148
1264
  );
1149
1265
  }
1150
1266
  var sync = {
1151
- inputs(ctx) {
1267
+ // sync channel inputs
1268
+ inputs(ctx, color) {
1152
1269
  const channelInputs = dom.getChannelInputEls(ctx);
1153
- channelInputs.forEach((inputEl) => {
1154
- const channel = inputEl.dataset.channel;
1155
- dom.setValue(inputEl, getChannelValue(ctx.value, channel));
1270
+ (0, import_dom_query3.raf)(() => {
1271
+ channelInputs.forEach((inputEl) => {
1272
+ const channel = inputEl.dataset.channel;
1273
+ dom.setValue(inputEl, getChannelValue(color || ctx.value, channel));
1274
+ });
1275
+ });
1276
+ },
1277
+ // sync format select
1278
+ formatSelect(ctx) {
1279
+ const selectEl = dom.getFormatSelectEl(ctx);
1280
+ (0, import_dom_query3.raf)(() => {
1281
+ dom.setValue(selectEl, ctx.format);
1156
1282
  });
1157
1283
  }
1158
1284
  };
1159
1285
  var invoke = {
1160
1286
  changeEnd(ctx) {
1287
+ const value = ctx.value.toFormat(ctx.format);
1161
1288
  ctx.onValueChangeEnd?.({
1162
- value: ctx.value,
1289
+ value,
1163
1290
  valueAsString: ctx.valueAsString
1164
1291
  });
1165
1292
  },
1166
1293
  change(ctx) {
1294
+ const value = ctx.value.toFormat(ctx.format);
1167
1295
  ctx.onValueChange?.({
1168
- value: ctx.value,
1296
+ value,
1169
1297
  valueAsString: ctx.valueAsString
1170
1298
  });
1171
1299
  (0, import_form_utils.dispatchInputValueEvent)(dom.getHiddenInputEl(ctx), { value: ctx.valueAsString });
1300
+ },
1301
+ formatChange(ctx) {
1302
+ ctx.onFormatChange?.({ format: ctx.format });
1172
1303
  }
1173
1304
  };
1174
1305
  var set = {
1175
1306
  value(ctx, color) {
1176
1307
  if (!color || ctx.value.isEqual(color))
1177
1308
  return;
1178
- const currentFormat = ctx.value.getFormat();
1179
- const outputColor = color.toFormat(currentFormat);
1180
- try {
1181
- outputColor.outputFormat = ctx.value.outputFormat;
1182
- } catch {
1183
- }
1184
- ctx.value = outputColor;
1309
+ ctx.value = color;
1185
1310
  invoke.change(ctx);
1311
+ },
1312
+ format(ctx, format) {
1313
+ if (ctx.format === format)
1314
+ return;
1315
+ ctx.format = format;
1316
+ invoke.formatChange(ctx);
1186
1317
  }
1187
1318
  };
1188
1319
  // Annotate the CommonJS export names for ESM import in node: