@zag-js/color-picker 0.22.0 → 0.24.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
@@ -1,26 +1,29 @@
1
1
  // src/color-picker.anatomy.ts
2
2
  import { createAnatomy } from "@zag-js/anatomy";
3
3
  var anatomy = createAnatomy("color-picker", [
4
+ "root",
5
+ "label",
6
+ "control",
7
+ "trigger",
8
+ "positioner",
9
+ "content",
4
10
  "area",
5
11
  "areaThumb",
6
- "areaGradient",
12
+ "areaBackground",
13
+ "channelSlider",
7
14
  "channelSliderTrack",
8
- "channelSliderTrackBackground",
9
15
  "channelSliderThumb",
10
16
  "channelInput",
17
+ "transparancyGrid",
18
+ "swatchGroup",
19
+ "swatchTrigger",
11
20
  "swatch",
12
- "swatchBackground",
13
- "content",
14
- "label",
15
21
  "eyeDropperTrigger"
16
22
  ]);
17
23
  var parts = anatomy.build();
18
24
 
19
25
  // src/color-picker.connect.ts
20
- import {
21
- getColorAreaGradient,
22
- normalizeColor
23
- } from "@zag-js/color-utils";
26
+ import { getColorAreaGradient, normalizeColor } from "@zag-js/color-utils";
24
27
  import {
25
28
  getEventKey,
26
29
  getEventPoint,
@@ -29,21 +32,29 @@ import {
29
32
  isLeftClick,
30
33
  isModifiedEvent
31
34
  } from "@zag-js/dom-event";
32
- import { dataAttr, raf } from "@zag-js/dom-query";
35
+ import { dataAttr, query } from "@zag-js/dom-query";
36
+ import { getPlacementStyles } from "@zag-js/popper";
33
37
  import { visuallyHiddenStyle } from "@zag-js/visually-hidden";
34
38
 
35
39
  // src/color-picker.dom.ts
36
40
  import { getRelativePoint } from "@zag-js/dom-event";
37
41
  import { createScope, queryAll } from "@zag-js/dom-query";
42
+ import { getFirstFocusable } from "@zag-js/tabbable";
43
+ import { runIfFn } from "@zag-js/utils";
38
44
  var dom = createScope({
45
+ getRootId: (ctx) => ctx.ids?.root ?? `color-picker:${ctx.id}`,
46
+ getLabelId: (ctx) => ctx.ids?.label ?? `color-picker:${ctx.id}:label`,
47
+ getHiddenInputId: (ctx) => `color-picker:${ctx.id}:hidden-input`,
48
+ getControlId: (ctx) => ctx.ids?.control ?? `color-picker:${ctx.id}:control`,
49
+ getTriggerId: (ctx) => ctx.ids?.trigger ?? `color-picker:${ctx.id}:trigger`,
39
50
  getContentId: (ctx) => ctx.ids?.content ?? `color-picker:${ctx.id}:content`,
51
+ getPositionerId: (ctx) => `color-picker:${ctx.id}:positioner`,
40
52
  getAreaId: (ctx) => ctx.ids?.area ?? `color-picker:${ctx.id}:area`,
41
53
  getAreaGradientId: (ctx) => ctx.ids?.areaGradient ?? `color-picker:${ctx.id}:area-gradient`,
42
54
  getAreaThumbId: (ctx) => ctx.ids?.areaThumb ?? `color-picker:${ctx.id}:area-thumb`,
43
- getChannelSliderTrackId: (ctx, channel) => ctx.ids?.channelSliderTrack?.(channel) ?? `color-picker:${ctx.id}:slider-track:${channel}`,
55
+ getChannelSliderId: (ctx, channel) => ctx.ids?.channelSliderTrack?.(channel) ?? `color-picker:${ctx.id}:slider-track:${channel}`,
44
56
  getChannelInputId: (ctx, channel) => ctx.ids?.channelInput?.(channel) ?? `color-picker:${ctx.id}:input:${channel}`,
45
57
  getChannelSliderThumbId: (ctx, channel) => ctx.ids?.channelSliderThumb?.(channel) ?? `color-picker:${ctx.id}:slider-thumb:${channel}`,
46
- getHiddenInputId: (ctx) => `color-picker:${ctx.id}:hidden-input`,
47
58
  getContentEl: (ctx) => dom.getById(ctx, dom.getContentId(ctx)),
48
59
  getAreaThumbEl: (ctx) => dom.getById(ctx, dom.getAreaThumbId(ctx)),
49
60
  getChannelSliderThumbEl: (ctx, channel) => dom.getById(ctx, dom.getChannelSliderThumbId(ctx, channel)),
@@ -57,8 +68,11 @@ var dom = createScope({
57
68
  const { percent } = getRelativePoint(point, areaEl);
58
69
  return percent;
59
70
  },
71
+ getControlEl: (ctx) => dom.getById(ctx, dom.getControlId(ctx)),
72
+ getTriggerEl: (ctx) => dom.getById(ctx, dom.getTriggerId(ctx)),
73
+ getPositionerEl: (ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
60
74
  getChannelSliderTrackEl: (ctx, channel) => {
61
- return dom.getById(ctx, dom.getChannelSliderTrackId(ctx, channel));
75
+ return dom.getById(ctx, dom.getChannelSliderId(ctx, channel));
62
76
  },
63
77
  getChannelSliderValueFromPoint(ctx, point, channel) {
64
78
  const trackEl = dom.getChannelSliderTrackEl(ctx, channel);
@@ -69,61 +83,18 @@ var dom = createScope({
69
83
  },
70
84
  getChannelInputEls: (ctx) => {
71
85
  return queryAll(dom.getContentEl(ctx), "input[data-channel]");
86
+ },
87
+ getFirstFocusableEl: (ctx) => getFirstFocusable(dom.getContentEl(ctx), "if-empty"),
88
+ getInitialFocusEl: (ctx) => {
89
+ let el = runIfFn(ctx.initialFocusEl);
90
+ if (!el && ctx.autoFocus)
91
+ el = dom.getFirstFocusableEl(ctx);
92
+ if (!el)
93
+ el = dom.getContentEl(ctx);
94
+ return el;
72
95
  }
73
96
  });
74
97
 
75
- // src/utils/get-channel-details.ts
76
- import { getPercentValue, snapValueToStep } from "@zag-js/numeric-range";
77
- function getChannelDetails(color, xChannel, yChannel) {
78
- const channels = color.getColorSpaceAxes({ xChannel, yChannel });
79
- const xChannelRange = color.getChannelRange(channels.xChannel);
80
- const yChannelRange = color.getChannelRange(channels.yChannel);
81
- const { minValue: minValueX, maxValue: maxValueX, step: stepX, pageSize: pageSizeX } = xChannelRange;
82
- const { minValue: minValueY, maxValue: maxValueY, step: stepY, pageSize: pageSizeY } = yChannelRange;
83
- const xValue = color.getChannelValue(channels.xChannel);
84
- const yValue = color.getChannelValue(channels.yChannel);
85
- return {
86
- channels,
87
- xChannelStep: stepX,
88
- yChannelStep: stepY,
89
- xChannelPageStep: pageSizeX,
90
- yChannelPageStep: pageSizeY,
91
- xValue,
92
- yValue,
93
- getThumbPosition() {
94
- let x = (xValue - minValueX) / (maxValueX - minValueX);
95
- let y = 1 - (yValue - minValueY) / (maxValueY - minValueY);
96
- return { x, y };
97
- },
98
- incrementX(stepSize) {
99
- return xValue + stepSize > maxValueX ? maxValueX : snapValueToStep(xValue + stepSize, minValueX, maxValueX, stepX);
100
- },
101
- incrementY(stepSize) {
102
- return yValue + stepSize > maxValueY ? maxValueY : snapValueToStep(yValue + stepSize, minValueY, maxValueY, stepY);
103
- },
104
- decrementX(stepSize) {
105
- return snapValueToStep(xValue - stepSize, minValueX, maxValueX, stepX);
106
- },
107
- decrementY(stepSize) {
108
- return snapValueToStep(yValue - stepSize, minValueY, maxValueY, stepY);
109
- },
110
- getColorFromPoint(x, y) {
111
- let newXValue = getPercentValue(x, minValueX, maxValueX, stepX);
112
- let newYValue = getPercentValue(1 - y, minValueY, maxValueY, stepY);
113
- let newColor;
114
- if (newXValue !== xValue) {
115
- newXValue = snapValueToStep(newXValue, minValueX, maxValueX, stepX);
116
- newColor = color.withChannelValue(channels.xChannel, newXValue);
117
- }
118
- if (newYValue !== yValue) {
119
- newYValue = snapValueToStep(newYValue, minValueY, maxValueY, stepY);
120
- newColor = (newColor || color).withChannelValue(channels.yChannel, newYValue);
121
- }
122
- return newColor;
123
- }
124
- };
125
- }
126
-
127
98
  // src/utils/get-channel-display-color.ts
128
99
  import { parseColor } from "@zag-js/color-utils";
129
100
  function getChannelDisplayColor(color, channel) {
@@ -146,31 +117,48 @@ function getChannelDisplayColor(color, channel) {
146
117
  }
147
118
 
148
119
  // src/utils/get-channel-input-value.ts
149
- function getChannelInputValue(color, channel) {
120
+ import { parseColor as parseColor2 } from "@zag-js/color-utils";
121
+ function getChannelValue(color, channel) {
150
122
  if (channel == null)
151
- return;
123
+ return "";
124
+ if (channel === "hex") {
125
+ return color.toString("hex");
126
+ }
127
+ if (channel === "css") {
128
+ return color.toString("css");
129
+ }
130
+ if (channel in color) {
131
+ return color.getChannelValue(channel).toString();
132
+ }
133
+ const isHSL = color.getFormat() === "hsl";
152
134
  switch (channel) {
153
- case "hex":
154
- return color.toString("hex");
155
- case "css":
156
- return color.toString("css");
157
135
  case "hue":
136
+ return isHSL ? color.toFormat("hsla").getChannelValue("hue").toString() : color.toFormat("hsba").getChannelValue("hue").toString();
158
137
  case "saturation":
138
+ return isHSL ? color.toFormat("hsl").getChannelValue("saturation").toString() : color.toFormat("hsb").getChannelValue("saturation").toString();
159
139
  case "lightness":
160
- return color.toFormat("hsl").getChannelValue("lightness").toString();
140
+ return color.toFormat("hsla").getChannelValue("lightness").toString();
161
141
  case "brightness":
162
- return color.toFormat("hsb").getChannelValue("brightness").toString();
142
+ return color.toFormat("hsba").getChannelValue("brightness").toString();
163
143
  case "red":
164
144
  case "green":
165
145
  case "blue":
166
- return color.toFormat("rgb").getChannelValue(channel).toString();
146
+ return color.toFormat("rgba").getChannelValue(channel).toString();
167
147
  default:
168
148
  return color.getChannelValue(channel).toString();
169
149
  }
170
150
  }
171
- function getChannelInputRange(color, channel) {
151
+ function getChannelRange(color, channel) {
172
152
  switch (channel) {
173
153
  case "hex":
154
+ const minColor = parseColor2("#000000");
155
+ const maxColor = parseColor2("#FFFFFF");
156
+ return {
157
+ minValue: minColor.toHexInt(),
158
+ maxValue: maxColor.toHexInt(),
159
+ pageSize: 10,
160
+ step: 1
161
+ };
174
162
  case "css":
175
163
  return void 0;
176
164
  case "hue":
@@ -189,7 +177,7 @@ function getChannelInputRange(color, channel) {
189
177
  }
190
178
 
191
179
  // src/utils/get-slider-background.ts
192
- function getSliderBgDirection(orientation, dir) {
180
+ function getSliderBackgroundDirection(orientation, dir) {
193
181
  if (orientation === "vertical") {
194
182
  return "top";
195
183
  } else if (dir === "ltr") {
@@ -198,16 +186,16 @@ function getSliderBgDirection(orientation, dir) {
198
186
  return "left";
199
187
  }
200
188
  }
201
- var getSliderBgImage = (ctx, props) => {
189
+ var getSliderBackground = (ctx, props) => {
202
190
  const { channel } = props;
203
- const dir = getSliderBgDirection(props.orientation, ctx.dir);
204
- const value = ctx.valueAsColor;
205
- const { minValue, maxValue } = ctx.valueAsColor.getChannelRange(channel);
191
+ const dir = getSliderBackgroundDirection(props.orientation, ctx.dir);
192
+ const value = ctx.value;
193
+ const { minValue, maxValue } = value.getChannelRange(channel);
206
194
  switch (channel) {
207
195
  case "hue":
208
196
  return `linear-gradient(to ${dir}, 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%)`;
209
197
  case "lightness": {
210
- let start = ctx.valueAsColor.withChannelValue(channel, minValue).toString("css");
198
+ let start = value.withChannelValue(channel, minValue).toString("css");
211
199
  let middle = value.withChannelValue(channel, (maxValue - minValue) / 2).toString("css");
212
200
  let end = value.withChannelValue(channel, maxValue).toString("css");
213
201
  return `linear-gradient(to ${dir}, ${start}, ${middle}, ${end})`;
@@ -229,41 +217,112 @@ var getSliderBgImage = (ctx, props) => {
229
217
 
230
218
  // src/color-picker.connect.ts
231
219
  function connect(state, send, normalize) {
232
- const valueAsColor = state.context.valueAsColor;
233
220
  const value = state.context.value;
221
+ const valueAsString = state.context.valueAsString;
234
222
  const isDisabled = state.context.isDisabled;
235
223
  const isInteractive = state.context.isInteractive;
236
- const isDragging = state.matches("dragging");
237
- const channels = valueAsColor.getColorChannels();
224
+ const isDragging = state.hasTag("dragging");
225
+ const isOpen = state.hasTag("open");
226
+ const channels = value.getChannels();
227
+ const getAreaChannels = (props) => ({
228
+ value: value.toFormat("hsl"),
229
+ xChannel: props.xChannel ?? "saturation",
230
+ yChannel: props.yChannel ?? "lightness"
231
+ });
232
+ const currentPlacement = state.context.currentPlacement;
233
+ const popperStyles = getPlacementStyles({
234
+ ...state.context.positioning,
235
+ placement: currentPlacement
236
+ });
238
237
  return {
239
238
  isDragging,
239
+ isOpen,
240
+ valueAsString,
240
241
  value,
241
- valueAsColor,
242
- alpha: valueAsColor.getChannelValue("alpha"),
243
242
  channels,
244
243
  setColor(value2) {
245
244
  send({ type: "VALUE.SET", value: normalizeColor(value2), src: "set-color" });
246
245
  },
247
- setChannelValue(channel, value2) {
248
- const color = valueAsColor.withChannelValue(channel, value2);
246
+ getChannelValue(channel) {
247
+ return getChannelValue(value, channel);
248
+ },
249
+ setChannelValue(channel, channelValue) {
250
+ const color = value.withChannelValue(channel, channelValue);
249
251
  send({ type: "VALUE.SET", value: color, src: "set-channel" });
250
252
  },
251
253
  setFormat(format) {
252
- const value2 = valueAsColor.toFormat(format);
253
- send({ type: "VALUE.SET", value: value2, src: "set-format" });
254
+ const formatValue = value.toFormat(format);
255
+ send({ type: "VALUE.SET", value: formatValue, src: "set-format" });
256
+ },
257
+ getAlpha() {
258
+ return value.getChannelValue("alpha");
254
259
  },
255
- setAlpha(value2) {
256
- const color = valueAsColor.withChannelValue("alpha", value2);
260
+ setAlpha(alphaValue) {
261
+ const color = value.withChannelValue("alpha", alphaValue);
257
262
  send({ type: "VALUE.SET", value: color, src: "set-alpha" });
258
263
  },
264
+ rootProps: normalize.element({
265
+ ...parts.root.attrs,
266
+ dir: state.context.dir,
267
+ id: dom.getRootId(state.context),
268
+ "data-disabled": dataAttr(isDisabled),
269
+ "data-readonly": dataAttr(state.context.readOnly),
270
+ style: {
271
+ "--value": value.toString("css")
272
+ }
273
+ }),
274
+ labelProps: normalize.element({
275
+ ...parts.label.attrs,
276
+ dir: state.context.dir,
277
+ id: dom.getLabelId(state.context),
278
+ htmlFor: dom.getHiddenInputId(state.context),
279
+ "data-disabled": dataAttr(isDisabled),
280
+ "data-readonly": dataAttr(state.context.readOnly),
281
+ onClick(event) {
282
+ event.preventDefault();
283
+ const inputEl = query(dom.getControlEl(state.context), "[data-channel=hex]");
284
+ inputEl?.focus({ preventScroll: true });
285
+ }
286
+ }),
287
+ controlProps: normalize.element({
288
+ ...parts.control.attrs,
289
+ id: dom.getControlId(state.context),
290
+ dir: state.context.dir,
291
+ "data-disabled": dataAttr(isDisabled),
292
+ "data-readonly": dataAttr(state.context.readOnly)
293
+ }),
294
+ triggerProps: normalize.button({
295
+ ...parts.trigger.attrs,
296
+ id: dom.getTriggerId(state.context),
297
+ dir: state.context.dir,
298
+ "aria-labelledby": dom.getLabelId(state.context),
299
+ "data-disabled": dataAttr(isDisabled),
300
+ "data-readonly": dataAttr(state.context.readOnly),
301
+ "data-placement": currentPlacement,
302
+ type: "button",
303
+ onClick() {
304
+ send({ type: "TRIGGER.CLICK" });
305
+ },
306
+ style: {
307
+ position: "relative"
308
+ }
309
+ }),
310
+ positionerProps: normalize.element({
311
+ ...parts.positioner.attrs,
312
+ id: dom.getPositionerId(state.context),
313
+ dir: state.context.dir,
314
+ style: popperStyles.floating
315
+ }),
259
316
  contentProps: normalize.element({
260
317
  ...parts.content.attrs,
261
318
  id: dom.getContentId(state.context),
262
- dir: state.context.dir
319
+ dir: state.context.dir,
320
+ "data-placement": currentPlacement,
321
+ hidden: !isOpen
263
322
  }),
264
- getAreaProps(props) {
265
- const { xChannel, yChannel } = props;
266
- const { areaStyles } = getColorAreaGradient(state.context.valueAsColor, {
323
+ getAreaProps(props = {}) {
324
+ const { xChannel, yChannel } = getAreaChannels(props);
325
+ const { areaStyles } = getColorAreaGradient(value, {
267
326
  xChannel,
268
327
  yChannel,
269
328
  dir: state.context.dir
@@ -281,6 +340,7 @@ function connect(state, send, normalize) {
281
340
  const point = getEventPoint(evt);
282
341
  const channel = { xChannel, yChannel };
283
342
  send({ type: "AREA.POINTER_DOWN", point, channel, id: "area" });
343
+ event.preventDefault();
284
344
  },
285
345
  style: {
286
346
  position: "relative",
@@ -290,15 +350,15 @@ function connect(state, send, normalize) {
290
350
  }
291
351
  });
292
352
  },
293
- getAreaGradientProps(props) {
294
- const { xChannel, yChannel } = props;
295
- const { areaGradientStyles } = getColorAreaGradient(valueAsColor, {
353
+ getAreaBackgroundProps(props = {}) {
354
+ const { xChannel, yChannel } = getAreaChannels(props);
355
+ const { areaGradientStyles } = getColorAreaGradient(value, {
296
356
  xChannel,
297
357
  yChannel,
298
358
  dir: state.context.dir
299
359
  });
300
360
  return normalize.element({
301
- ...parts.areaGradient.attrs,
361
+ ...parts.areaBackground.attrs,
302
362
  id: dom.getAreaGradientId(state.context),
303
363
  style: {
304
364
  position: "relative",
@@ -308,14 +368,15 @@ function connect(state, send, normalize) {
308
368
  }
309
369
  });
310
370
  },
311
- getAreaThumbProps(props) {
312
- const { xChannel, yChannel } = props;
313
- const { getThumbPosition } = getChannelDetails(valueAsColor, xChannel, yChannel);
314
- const { x, y } = getThumbPosition();
371
+ getAreaThumbProps(props = {}) {
372
+ const { xChannel, yChannel, value: valueAsHSL } = getAreaChannels(props);
315
373
  const channel = { xChannel, yChannel };
374
+ const x = valueAsHSL.getChannelValuePercent(xChannel);
375
+ const y = 1 - valueAsHSL.getChannelValuePercent(yChannel);
316
376
  return normalize.element({
317
377
  ...parts.areaThumb.attrs,
318
378
  id: dom.getAreaThumbId(state.context),
379
+ dir: state.context.dir,
319
380
  tabIndex: isDisabled ? void 0 : 0,
320
381
  "data-disabled": dataAttr(isDisabled),
321
382
  role: "presentation",
@@ -326,13 +387,10 @@ function connect(state, send, normalize) {
326
387
  transform: "translate(-50%, -50%)",
327
388
  touchAction: "none",
328
389
  forcedColorAdjust: "none",
329
- background: valueAsColor.withChannelValue("alpha", 1).toString("css")
330
- },
331
- onBlur() {
332
- send("AREA.BLUR");
390
+ background: value.withChannelValue("alpha", 1).toString("css")
333
391
  },
334
392
  onFocus() {
335
- send({ type: "AREA.FOCUS", id: "area" });
393
+ send({ type: "AREA.FOCUS", id: "area", channel });
336
394
  },
337
395
  onKeyDown(event) {
338
396
  if (!isInteractive)
@@ -356,6 +414,9 @@ function connect(state, send, normalize) {
356
414
  },
357
415
  PageDown() {
358
416
  send({ type: "AREA.PAGE_DOWN", channel, step });
417
+ },
418
+ Escape(event2) {
419
+ event2.stopPropagation();
359
420
  }
360
421
  };
361
422
  const exec = keyMap[getEventKey(event, state.context)];
@@ -366,14 +427,31 @@ function connect(state, send, normalize) {
366
427
  }
367
428
  });
368
429
  },
369
- getChannelSliderTrackProps(props) {
430
+ getTransparencyGridProps(props) {
431
+ const { size } = props;
432
+ return normalize.element({
433
+ ...parts.transparancyGrid.attrs,
434
+ style: {
435
+ "--size": size,
436
+ width: "100%",
437
+ height: "100%",
438
+ position: "absolute",
439
+ backgroundColor: "#fff",
440
+ backgroundImage: "conic-gradient(#eeeeee 0 25%, transparent 0 50%, #eeeeee 0 75%, transparent 0)",
441
+ backgroundSize: "var(--size) var(--size)",
442
+ inset: "0px",
443
+ zIndex: "auto",
444
+ pointerEvents: "none"
445
+ }
446
+ });
447
+ },
448
+ getChannelSliderProps(props) {
370
449
  const { orientation = "horizontal", channel } = props;
371
450
  return normalize.element({
372
- ...parts.channelSliderTrack.attrs,
373
- id: dom.getChannelSliderTrackId(state.context, channel),
374
- role: "group",
451
+ ...parts.channelSlider.attrs,
375
452
  "data-channel": channel,
376
453
  "data-orientation": orientation,
454
+ role: "presentation",
377
455
  onPointerDown(event) {
378
456
  if (!isInteractive)
379
457
  return;
@@ -382,41 +460,33 @@ function connect(state, send, normalize) {
382
460
  return;
383
461
  const point = getEventPoint(evt);
384
462
  send({ type: "CHANNEL_SLIDER.POINTER_DOWN", channel, point, id: channel, orientation });
463
+ event.preventDefault();
385
464
  },
386
465
  style: {
387
466
  position: "relative",
388
- touchAction: "none",
389
- forcedColorAdjust: "none",
390
- backgroundImage: getSliderBgImage(state.context, { orientation, channel })
467
+ touchAction: "none"
391
468
  }
392
469
  });
393
470
  },
394
- getChannelSliderBackgroundProps(props) {
471
+ getChannelSliderTrackProps(props) {
395
472
  const { orientation = "horizontal", channel } = props;
396
473
  return normalize.element({
397
- ...parts.channelSliderTrackBackground.attrs,
398
- "data-orientation": orientation,
474
+ ...parts.channelSliderTrack.attrs,
475
+ id: dom.getChannelSliderId(state.context, channel),
476
+ role: "group",
399
477
  "data-channel": channel,
478
+ "data-orientation": orientation,
400
479
  style: {
401
- position: "absolute",
402
- backgroundColor: "#fff",
403
- backgroundImage: [
404
- "linear-gradient(-45deg,#0000 75.5%,#bcbcbc 75.5%)",
405
- "linear-gradient(45deg,#0000 75.5%,#bcbcbc 75.5%)",
406
- "linear-gradient(-45deg,#bcbcbc 25.5%,#0000 25.5%)",
407
- "linear-gradient(45deg,#bcbcbc 25.5%,#0000 25.5%)"
408
- ].join(","),
409
- backgroundSize: "16px 16px",
410
- backgroundPosition: "-2px -2px,-2px 6px,6px -10px,-10px -2px",
411
- inset: 0,
412
- zIndex: -1
480
+ position: "relative",
481
+ forcedColorAdjust: "none",
482
+ backgroundImage: getSliderBackground(state.context, { orientation, channel })
413
483
  }
414
484
  });
415
485
  },
416
486
  getChannelSliderThumbProps(props) {
417
487
  const { orientation = "horizontal", channel } = props;
418
- const { minValue, maxValue, step: stepValue } = valueAsColor.getChannelRange(channel);
419
- const channelValue = valueAsColor.getChannelValue(channel);
488
+ const { minValue, maxValue, step: stepValue } = value.getChannelRange(channel);
489
+ const channelValue = value.getChannelValue(channel);
420
490
  const offset = (channelValue - minValue) / (maxValue - minValue);
421
491
  const placementStyles = orientation === "horizontal" ? { left: `${offset * 100}%`, top: "50%" } : { top: `${offset * 100}%`, left: "50%" };
422
492
  return normalize.element({
@@ -436,7 +506,7 @@ function connect(state, send, normalize) {
436
506
  style: {
437
507
  forcedColorAdjust: "none",
438
508
  position: "absolute",
439
- background: getChannelDisplayColor(valueAsColor, channel).toString("css"),
509
+ background: getChannelDisplayColor(value, channel).toString("css"),
440
510
  ...placementStyles
441
511
  },
442
512
  onFocus() {
@@ -444,11 +514,6 @@ function connect(state, send, normalize) {
444
514
  return;
445
515
  send({ type: "CHANNEL_SLIDER.FOCUS", channel });
446
516
  },
447
- onBlur() {
448
- if (!isInteractive)
449
- return;
450
- send({ type: "CHANNEL_SLIDER.BLUR", channel });
451
- },
452
517
  onKeyDown(event) {
453
518
  if (!isInteractive)
454
519
  return;
@@ -477,6 +542,9 @@ function connect(state, send, normalize) {
477
542
  },
478
543
  End() {
479
544
  send({ type: "CHANNEL_SLIDER.END", channel });
545
+ },
546
+ Escape(event2) {
547
+ event2.stopPropagation();
480
548
  }
481
549
  };
482
550
  const exec = keyMap[getEventKey(event, state.context)];
@@ -490,7 +558,7 @@ function connect(state, send, normalize) {
490
558
  getChannelInputProps(props) {
491
559
  const { channel } = props;
492
560
  const isTextField = channel === "hex" || channel === "css";
493
- const range = getChannelInputRange(valueAsColor, channel);
561
+ const range = getChannelRange(value, channel);
494
562
  return normalize.input({
495
563
  ...parts.channelInput.attrs,
496
564
  type: isTextField ? "text" : "number",
@@ -500,22 +568,31 @@ function connect(state, send, normalize) {
500
568
  "data-disabled": dataAttr(isDisabled),
501
569
  readOnly: state.context.readOnly,
502
570
  id: dom.getChannelInputId(state.context, channel),
503
- defaultValue: getChannelInputValue(valueAsColor, channel),
571
+ defaultValue: getChannelValue(value, channel),
504
572
  min: range?.minValue,
505
573
  max: range?.maxValue,
506
574
  step: range?.step,
575
+ onBeforeInput(event) {
576
+ if (isTextField)
577
+ return;
578
+ const value2 = event.currentTarget.value;
579
+ if (value2.match(/[^0-9.]/g)) {
580
+ event.preventDefault();
581
+ }
582
+ },
507
583
  onFocus(event) {
508
584
  send({ type: "CHANNEL_INPUT.FOCUS", channel });
509
- raf(() => event.target.select());
585
+ event.target.select();
510
586
  },
511
587
  onBlur(event) {
512
- const value2 = event.currentTarget.value;
513
- send({ type: "CHANNEL_INPUT.CHANGE", channel, value: value2, isTextField });
588
+ const value2 = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber;
589
+ send({ type: "CHANNEL_INPUT.BLUR", channel, value: value2, isTextField });
514
590
  },
515
591
  onKeyDown(event) {
516
592
  if (event.key === "Enter") {
517
- const value2 = event.currentTarget.value;
593
+ const value2 = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber;
518
594
  send({ type: "CHANNEL_INPUT.CHANGE", channel, value: value2, isTextField });
595
+ event.preventDefault();
519
596
  }
520
597
  },
521
598
  style: {
@@ -531,14 +608,11 @@ function connect(state, send, normalize) {
531
608
  name: state.context.name,
532
609
  id: dom.getHiddenInputId(state.context),
533
610
  style: visuallyHiddenStyle,
534
- defaultValue: value,
535
- onChange(event) {
536
- const value2 = event.currentTarget.value;
537
- send({ type: "VALUE.SET", value: value2, src: "input.change" });
538
- }
611
+ defaultValue: valueAsString
539
612
  }),
540
613
  eyeDropperTriggerProps: normalize.button({
541
614
  ...parts.eyeDropperTrigger.attrs,
615
+ type: "button",
542
616
  disabled: isDisabled,
543
617
  "data-disabled": dataAttr(isDisabled),
544
618
  "aria-label": "Pick a color from the screen",
@@ -548,43 +622,39 @@ function connect(state, send, normalize) {
548
622
  send("EYEDROPPER.CLICK");
549
623
  }
550
624
  }),
551
- getSwatchBackgroundProps(props) {
552
- const { value: value2 } = props;
553
- const alpha = normalizeColor(value2).getChannelValue("alpha");
554
- return normalize.element({
555
- ...parts.swatchBackground.attrs,
556
- "data-alpha": alpha,
625
+ swatchGroupProps: normalize.element({
626
+ ...parts.swatchGroup.attrs,
627
+ role: "group"
628
+ }),
629
+ getSwatchTriggerProps(props) {
630
+ const { value: valueProp } = props;
631
+ const color = normalizeColor(valueProp).toFormat(value.getFormat());
632
+ return normalize.button({
633
+ ...parts.swatchTrigger.attrs,
634
+ disabled: isDisabled,
635
+ type: "button",
636
+ "data-value": color.toString("hex"),
637
+ onClick() {
638
+ if (!isInteractive)
639
+ return;
640
+ send({ type: "VALUE.SET", value: color });
641
+ },
557
642
  style: {
558
- width: "100%",
559
- height: "100%",
560
- background: "#fff",
561
- backgroundImage: [
562
- "linear-gradient(-45deg,#0000 75.5%,#bcbcbc 75.5%)",
563
- "linear-gradient(45deg,#0000 75.5%,#bcbcbc 75.5%)",
564
- "linear-gradient(-45deg,#bcbcbc 25.5%,#0000 25.5%)",
565
- "linear-gradient(45deg,#bcbcbc 25.5%,#0000 25.5%)"
566
- ].join(","),
567
- backgroundPosition: "-2px -2px,-2px 6px,6px -10px,-10px -2px",
568
- backgroundSize: "16px 16px",
569
- position: "absolute",
570
- inset: "0px",
571
- zIndex: -1
643
+ position: "relative"
572
644
  }
573
645
  });
574
646
  },
575
647
  getSwatchProps(props) {
576
- const { value: value2, readOnly } = props;
577
- const color = normalizeColor(value2).toFormat(valueAsColor.getColorFormat());
648
+ const { value: valueProp, respectAlpha = true } = props;
649
+ const colorValue = normalizeColor(valueProp);
650
+ const color = colorValue.toFormat(value.getFormat());
578
651
  return normalize.element({
579
652
  ...parts.swatch.attrs,
580
- onClick() {
581
- if (readOnly || !isInteractive)
582
- return;
583
- send({ type: "VALUE.SET", value: color });
584
- },
653
+ "data-state": colorValue.isEqual(value) ? "selected" : "unselected",
654
+ "data-value": color.toString("hex"),
585
655
  style: {
586
656
  position: "relative",
587
- background: color.toString("css")
657
+ background: color.toString(respectAlpha ? "css" : "hex")
588
658
  }
589
659
  });
590
660
  }
@@ -592,86 +662,135 @@ function connect(state, send, normalize) {
592
662
  }
593
663
 
594
664
  // src/color-picker.machine.ts
595
- import { parseColor as parseColor2 } from "@zag-js/color-utils";
596
- import { createMachine } from "@zag-js/core";
665
+ import { parseColor as parseColor4 } from "@zag-js/color-utils";
666
+ import { createMachine, guards, ref as ref2 } from "@zag-js/core";
667
+ import { trackDismissableElement } from "@zag-js/dismissable";
597
668
  import { trackPointerMove } from "@zag-js/dom-event";
598
- import { raf as raf2 } from "@zag-js/dom-query";
599
- import { trackFormControl } from "@zag-js/form-utils";
600
- import { clampValue, getPercentValue as getPercentValue2, snapValueToStep as snapValueToStep2 } from "@zag-js/numeric-range";
669
+ import { raf } from "@zag-js/dom-query";
670
+ import { dispatchInputValueEvent, trackFormControl } from "@zag-js/form-utils";
671
+ import { getPlacement } from "@zag-js/popper";
601
672
  import { disableTextSelection } from "@zag-js/text-selection";
602
673
  import { compact, tryCatch } from "@zag-js/utils";
674
+
675
+ // src/color-picker.parse.ts
676
+ import { parseColor as parseColor3 } from "@zag-js/color-utils";
677
+ import { ref } from "@zag-js/core";
678
+ var parse = (color) => ref(parseColor3(color));
679
+
680
+ // src/color-picker.machine.ts
681
+ var { stateIn } = guards;
603
682
  function machine(userContext) {
604
683
  const ctx = compact(userContext);
605
684
  return createMachine(
606
685
  {
607
686
  id: "color-picker",
608
- initial: "idle",
687
+ initial: ctx.open ? "open" : "idle",
609
688
  context: {
610
689
  dir: "ltr",
611
- value: "#D9D9D9",
690
+ value: parse("#000000"),
612
691
  disabled: false,
613
692
  ...ctx,
614
693
  activeId: null,
615
694
  activeChannel: null,
616
695
  activeOrientation: null,
617
- fieldsetDisabled: false
696
+ fieldsetDisabled: false,
697
+ autoFocus: true,
698
+ positioning: {
699
+ ...ctx.positioning,
700
+ placement: "bottom"
701
+ }
618
702
  },
619
703
  computed: {
620
704
  isRtl: (ctx2) => ctx2.dir === "rtl",
621
705
  isDisabled: (ctx2) => !!ctx2.disabled || ctx2.fieldsetDisabled,
622
706
  isInteractive: (ctx2) => !(ctx2.isDisabled || ctx2.readOnly),
623
- valueAsColor: (ctx2) => parseColor2(ctx2.value)
707
+ valueAsString: (ctx2) => ctx2.value.toString("css")
708
+ },
709
+ activities: ["trackFormControl"],
710
+ watch: {
711
+ valueAsString: ["syncInputElements"],
712
+ open: ["toggleVisibility"]
624
713
  },
625
714
  on: {
626
715
  "VALUE.SET": {
627
716
  actions: ["setValue"]
717
+ },
718
+ "CHANNEL_INPUT.FOCUS": [
719
+ {
720
+ guard: stateIn("idle"),
721
+ target: "focused",
722
+ actions: ["setActiveChannel"]
723
+ },
724
+ {
725
+ actions: ["setActiveChannel"]
726
+ }
727
+ ],
728
+ "CHANNEL_INPUT.BLUR": [
729
+ {
730
+ guard: stateIn("focused"),
731
+ target: "idle",
732
+ actions: ["setChannelColorFromInput"]
733
+ },
734
+ {
735
+ actions: ["setChannelColorFromInput"]
736
+ }
737
+ ],
738
+ "CHANNEL_INPUT.CHANGE": {
739
+ actions: ["setChannelColorFromInput"]
628
740
  }
629
741
  },
630
- activities: ["trackFormControl"],
631
- watch: {
632
- value: ["syncInputElements"]
633
- },
634
742
  states: {
635
743
  idle: {
744
+ tags: ["closed"],
636
745
  on: {
746
+ OPEN: {
747
+ target: "open",
748
+ actions: ["setInitialFocus", "invokeOnOpen"]
749
+ },
750
+ "TRIGGER.CLICK": {
751
+ target: "open",
752
+ actions: ["setInitialFocus", "invokeOnOpen"]
753
+ }
754
+ }
755
+ },
756
+ focused: {
757
+ tags: ["closed", "focused"],
758
+ on: {
759
+ OPEN: {
760
+ target: "open",
761
+ actions: ["setInitialFocus", "invokeOnOpen"]
762
+ },
763
+ "TRIGGER.CLICK": {
764
+ target: "open",
765
+ actions: ["setInitialFocus", "invokeOnOpen"]
766
+ }
767
+ }
768
+ },
769
+ open: {
770
+ tags: ["open"],
771
+ activities: ["trackPositioning", "trackDismissableElement"],
772
+ on: {
773
+ "TRIGGER.CLICK": {
774
+ target: "idle",
775
+ actions: ["invokeOnClose"]
776
+ },
637
777
  "EYEDROPPER.CLICK": {
638
778
  actions: ["openEyeDropper"]
639
779
  },
640
780
  "AREA.POINTER_DOWN": {
641
- target: "dragging",
781
+ target: "open:dragging",
642
782
  actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"]
643
783
  },
644
784
  "AREA.FOCUS": {
645
- target: "focused",
646
785
  actions: ["setActiveChannel"]
647
786
  },
648
787
  "CHANNEL_SLIDER.POINTER_DOWN": {
649
- target: "dragging",
788
+ target: "open:dragging",
650
789
  actions: ["setActiveChannel", "setChannelColorFromPoint", "focusChannelThumb"]
651
790
  },
652
791
  "CHANNEL_SLIDER.FOCUS": {
653
- target: "focused",
654
- actions: ["setActiveChannel"]
655
- },
656
- "CHANNEL_INPUT.FOCUS": {
657
- target: "focused",
658
792
  actions: ["setActiveChannel"]
659
793
  },
660
- "CHANNEL_INPUT.CHANGE": {
661
- actions: ["setChannelColorFromInput"]
662
- }
663
- }
664
- },
665
- focused: {
666
- on: {
667
- "AREA.POINTER_DOWN": {
668
- target: "dragging",
669
- actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"]
670
- },
671
- "CHANNEL_SLIDER.POINTER_DOWN": {
672
- target: "dragging",
673
- actions: ["setActiveChannel", "setChannelColorFromPoint", "focusChannelThumb"]
674
- },
675
794
  "AREA.ARROW_LEFT": {
676
795
  actions: ["decrementXChannel"]
677
796
  },
@@ -714,44 +833,100 @@ function machine(userContext) {
714
833
  "CHANNEL_SLIDER.END": {
715
834
  actions: ["setChannelToMax"]
716
835
  },
717
- "CHANNEL_INPUT.FOCUS": {
718
- actions: ["setActiveChannel"]
719
- },
720
- "CHANNEL_INPUT.CHANGE": {
721
- actions: ["setChannelColorFromInput"]
722
- },
723
- "CHANNEL_SLIDER.BLUR": {
724
- target: "idle"
725
- },
726
- "AREA.BLUR": {
727
- target: "idle"
836
+ INTERACT_OUTSIDE: [
837
+ {
838
+ guard: "shouldRestoreFocus",
839
+ target: "focused",
840
+ actions: ["setReturnFocus", "invokeOnClose"]
841
+ },
842
+ {
843
+ target: "idle",
844
+ actions: ["invokeOnClose"]
845
+ }
846
+ ],
847
+ CLOSE: {
848
+ target: "idle",
849
+ actions: ["invokeOnClose"]
728
850
  }
729
851
  }
730
852
  },
731
- dragging: {
853
+ "open:dragging": {
854
+ tags: ["open"],
732
855
  exit: ["clearActiveChannel"],
733
- activities: ["trackPointerMove", "disableTextSelection"],
856
+ activities: ["trackPointerMove", "disableTextSelection", "trackPositioning", "trackDismissableElement"],
734
857
  on: {
735
858
  "AREA.POINTER_MOVE": {
736
- actions: ["setAreaColorFromPoint"]
859
+ actions: ["setAreaColorFromPoint", "focusAreaThumb"]
737
860
  },
738
861
  "AREA.POINTER_UP": {
739
- target: "focused",
862
+ target: "open",
740
863
  actions: ["invokeOnChangeEnd"]
741
864
  },
742
865
  "CHANNEL_SLIDER.POINTER_MOVE": {
743
- actions: ["setChannelColorFromPoint"]
866
+ actions: ["setChannelColorFromPoint", "focusChannelThumb"]
744
867
  },
745
868
  "CHANNEL_SLIDER.POINTER_UP": {
746
- target: "focused",
869
+ target: "open",
747
870
  actions: ["invokeOnChangeEnd"]
871
+ },
872
+ INTERACT_OUTSIDE: [
873
+ {
874
+ guard: "shouldRestoreFocus",
875
+ target: "focused",
876
+ actions: ["setReturnFocus", "invokeOnClose"]
877
+ },
878
+ {
879
+ target: "idle",
880
+ actions: ["invokeOnClose"]
881
+ }
882
+ ],
883
+ CLOSE: {
884
+ target: "idle",
885
+ actions: ["invokeOnClose"]
748
886
  }
749
887
  }
750
888
  }
751
889
  }
752
890
  },
753
891
  {
892
+ guards: {
893
+ isTargetFocusable: (_ctx, evt) => evt.restoreFocus
894
+ },
754
895
  activities: {
896
+ trackPositioning(ctx2) {
897
+ ctx2.currentPlacement = ctx2.positioning.placement;
898
+ const anchorEl = dom.getTriggerEl(ctx2);
899
+ const getPositionerEl = () => dom.getPositionerEl(ctx2);
900
+ return getPlacement(anchorEl, getPositionerEl, {
901
+ ...ctx2.positioning,
902
+ defer: true,
903
+ onComplete(data) {
904
+ ctx2.currentPlacement = data.placement;
905
+ },
906
+ onCleanup() {
907
+ ctx2.currentPlacement = void 0;
908
+ }
909
+ });
910
+ },
911
+ trackDismissableElement(ctx2, _evt, { send }) {
912
+ const getContentEl = () => dom.getContentEl(ctx2);
913
+ let restoreFocus = true;
914
+ return trackDismissableElement(getContentEl, {
915
+ exclude: dom.getTriggerEl(ctx2),
916
+ defer: true,
917
+ onInteractOutside(event) {
918
+ ctx2.onInteractOutside?.(event);
919
+ if (event.defaultPrevented)
920
+ return;
921
+ restoreFocus = !(event.detail.focusable || event.detail.contextmenu);
922
+ },
923
+ onPointerDownOutside: ctx2.onPointerDownOutside,
924
+ onFocusOutside: ctx2.onFocusOutside,
925
+ onDismiss() {
926
+ send({ type: "INTERACT_OUTSIDE", restoreFocus });
927
+ }
928
+ });
929
+ },
755
930
  trackFormControl(ctx2, _evt, { send, initialContext }) {
756
931
  const inputEl = dom.getHiddenInputEl(ctx2);
757
932
  return trackFormControl(inputEl, {
@@ -787,20 +962,18 @@ function machine(userContext) {
787
962
  const win = dom.getWin(ctx2);
788
963
  const picker = new win.EyeDropper();
789
964
  picker.open().then(({ sRGBHex }) => {
790
- const format = ctx2.valueAsColor.getColorFormat();
791
- const color = parseColor2(sRGBHex).toFormat(format);
965
+ const format = ctx2.value.getFormat();
966
+ const color = parseColor4(sRGBHex).toFormat(format);
792
967
  set.value(ctx2, color);
793
- ctx2.onValueChangeEnd?.({ value: ctx2.value, valueAsColor: color });
968
+ ctx2.onValueChangeEnd?.({ value: ctx2.value, valueAsString: ctx2.valueAsString });
794
969
  }).catch(() => void 0);
795
970
  },
796
971
  setActiveChannel(ctx2, evt) {
797
972
  ctx2.activeId = evt.id;
798
- if (evt.channel) {
973
+ if (evt.channel)
799
974
  ctx2.activeChannel = evt.channel;
800
- }
801
- if (evt.orientation) {
975
+ if (evt.orientation)
802
976
  ctx2.activeOrientation = evt.orientation;
803
- }
804
977
  },
805
978
  clearActiveChannel(ctx2) {
806
979
  ctx2.activeChannel = null;
@@ -812,10 +985,9 @@ function machine(userContext) {
812
985
  const percent = dom.getAreaValueFromPoint(ctx2, evt.point);
813
986
  if (!percent)
814
987
  return;
815
- const { getColorFromPoint } = getChannelDetails(ctx2.valueAsColor, xChannel, yChannel);
816
- const color = getColorFromPoint(percent.x, percent.y);
817
- if (!color)
818
- return;
988
+ const xValue = ctx2.value.getChannelPercentValue(xChannel, percent.x);
989
+ const yValue = ctx2.value.getChannelPercentValue(yChannel, 1 - percent.y);
990
+ const color = ctx2.value.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue);
819
991
  set.value(ctx2, color);
820
992
  },
821
993
  setChannelColorFromPoint(ctx2, evt) {
@@ -823,24 +995,17 @@ function machine(userContext) {
823
995
  const percent = dom.getChannelSliderValueFromPoint(ctx2, evt.point, channel);
824
996
  if (!percent)
825
997
  return;
826
- const { minValue, maxValue, step } = ctx2.valueAsColor.getChannelRange(channel);
827
998
  const orientation = ctx2.activeOrientation || "horizontal";
828
- const point = orientation === "horizontal" ? percent.x : percent.y;
829
- const channelValue = getPercentValue2(point, minValue, maxValue, step);
830
- const value = snapValueToStep2(channelValue - step, minValue, maxValue, step);
831
- const newColor = ctx2.valueAsColor.withChannelValue(channel, value);
832
- set.value(ctx2, newColor);
999
+ const channelPercent = orientation === "horizontal" ? percent.x : percent.y;
1000
+ const value = ctx2.value.getChannelPercentValue(channel, channelPercent);
1001
+ const color = ctx2.value.withChannelValue(channel, value);
1002
+ set.value(ctx2, color);
833
1003
  },
834
1004
  setValue(ctx2, evt) {
835
1005
  set.value(ctx2, evt.value);
836
1006
  },
837
1007
  syncInputElements(ctx2) {
838
- const inputs = dom.getChannelInputEls(ctx2);
839
- inputs.forEach((input) => {
840
- const channel = input.dataset.channel;
841
- dom.setValue(input, getChannelInputValue(ctx2.valueAsColor, channel));
842
- });
843
- dom.setValue(dom.getHiddenInputEl(ctx2), ctx2.value);
1008
+ sync.inputs(ctx2);
844
1009
  },
845
1010
  invokeOnChangeEnd(ctx2) {
846
1011
  invoke.changeEnd(ctx2);
@@ -848,112 +1013,133 @@ function machine(userContext) {
848
1013
  setChannelColorFromInput(ctx2, evt) {
849
1014
  const { channel, isTextField, value } = evt;
850
1015
  if (channel === "alpha") {
851
- const newColor2 = ctx2.valueAsColor.withChannelValue("alpha", value);
852
- set.value(ctx2, newColor2);
1016
+ const newColor = ctx2.value.withChannelValue("alpha", parseFloat(value));
1017
+ set.value(ctx2, newColor);
853
1018
  return;
854
1019
  }
855
1020
  if (isTextField) {
856
1021
  const format = "hsl";
857
- const currentAlpha = ctx2.valueAsColor.getChannelValue("alpha");
858
- const newColor2 = tryCatch(
859
- () => parseColor2(value).toFormat(format).withChannelValue("alpha", currentAlpha),
860
- () => ctx2.valueAsColor
1022
+ const currentAlpha = ctx2.value.getChannelValue("alpha");
1023
+ const color2 = tryCatch(
1024
+ () => parseColor4(value).toFormat(format).withChannelValue("alpha", currentAlpha),
1025
+ () => ctx2.value
861
1026
  );
862
1027
  const inputEl = dom.getChannelInputEl(ctx2, channel);
863
- dom.setValue(inputEl, getChannelInputValue(ctx2.valueAsColor, channel));
864
- set.value(ctx2, newColor2);
1028
+ dom.setValue(inputEl, getChannelValue(color2, channel));
1029
+ set.value(ctx2, color2);
865
1030
  return;
866
1031
  }
867
- const newColor = ctx2.valueAsColor.withChannelValue(channel, value);
868
- set.value(ctx2, newColor);
1032
+ const color = ctx2.value.withChannelValue(channel, value);
1033
+ set.value(ctx2, color);
869
1034
  },
870
1035
  incrementChannel(ctx2, evt) {
871
- const { minValue, maxValue, step } = ctx2.valueAsColor.getChannelRange(evt.channel);
872
- const channelValue = ctx2.valueAsColor.getChannelValue(evt.channel);
873
- const value = snapValueToStep2(channelValue + evt.step, minValue, maxValue, step);
874
- const color = ctx2.valueAsColor.withChannelValue(evt.channel, clampValue(value, minValue, maxValue));
1036
+ const color = ctx2.value.incrementChannel(evt.channel, evt.step);
875
1037
  set.value(ctx2, color);
876
1038
  },
877
1039
  decrementChannel(ctx2, evt) {
878
- const { minValue, maxValue, step } = ctx2.valueAsColor.getChannelRange(evt.channel);
879
- const channelValue = ctx2.valueAsColor.getChannelValue(evt.channel);
880
- const value = snapValueToStep2(channelValue - evt.step, minValue, maxValue, step);
881
- const color = ctx2.valueAsColor.withChannelValue(evt.channel, clampValue(value, minValue, maxValue));
1040
+ const color = ctx2.value.decrementChannel(evt.channel, evt.step);
882
1041
  set.value(ctx2, color);
883
1042
  },
884
1043
  incrementXChannel(ctx2, evt) {
885
- const { xChannel, yChannel } = evt.channel;
886
- const { incrementX } = getChannelDetails(ctx2.valueAsColor, xChannel, yChannel);
887
- const color = ctx2.valueAsColor.withChannelValue(xChannel, incrementX(evt.step));
1044
+ const { xChannel } = evt.channel;
1045
+ const color = ctx2.value.incrementChannel(xChannel, evt.step);
888
1046
  set.value(ctx2, color);
889
1047
  },
890
1048
  decrementXChannel(ctx2, evt) {
891
- const { xChannel, yChannel } = evt.channel;
892
- const { decrementX } = getChannelDetails(ctx2.valueAsColor, xChannel, yChannel);
893
- const color = ctx2.valueAsColor.withChannelValue(xChannel, decrementX(evt.step));
1049
+ const { xChannel } = evt.channel;
1050
+ const color = ctx2.value.decrementChannel(xChannel, evt.step);
894
1051
  set.value(ctx2, color);
895
1052
  },
896
1053
  incrementYChannel(ctx2, evt) {
897
- const { xChannel, yChannel } = evt.channel;
898
- const { incrementY } = getChannelDetails(ctx2.valueAsColor, xChannel, yChannel);
899
- const color = ctx2.valueAsColor.withChannelValue(yChannel, incrementY(evt.step));
1054
+ const { yChannel } = evt.channel;
1055
+ const color = ctx2.value.incrementChannel(yChannel, evt.step);
900
1056
  set.value(ctx2, color);
901
1057
  },
902
1058
  decrementYChannel(ctx2, evt) {
903
- const { xChannel, yChannel } = evt.channel;
904
- const { decrementY } = getChannelDetails(ctx2.valueAsColor, xChannel, yChannel);
905
- const color = ctx2.valueAsColor.withChannelValue(yChannel, decrementY(evt.step));
1059
+ const { yChannel } = evt.channel;
1060
+ const color = ctx2.value.decrementChannel(yChannel, evt.step);
906
1061
  set.value(ctx2, color);
907
1062
  },
908
1063
  setChannelToMax(ctx2, evt) {
909
- const { maxValue } = ctx2.valueAsColor.getChannelRange(evt.channel);
910
- const color = ctx2.valueAsColor.withChannelValue(evt.channel, maxValue);
1064
+ const range = ctx2.value.getChannelRange(evt.channel);
1065
+ const color = ctx2.value.withChannelValue(evt.channel, range.maxValue);
911
1066
  set.value(ctx2, color);
912
1067
  },
913
1068
  setChannelToMin(ctx2, evt) {
914
- const { minValue } = ctx2.valueAsColor.getChannelRange(evt.channel);
915
- const color = ctx2.valueAsColor.withChannelValue(evt.channel, minValue);
1069
+ const range = ctx2.value.getChannelRange(evt.channel);
1070
+ const color = ctx2.value.withChannelValue(evt.channel, range.minValue);
916
1071
  set.value(ctx2, color);
917
1072
  },
918
1073
  focusAreaThumb(ctx2) {
919
- raf2(() => {
920
- dom.getAreaThumbEl(ctx2)?.focus({ preventScroll: true });
1074
+ raf(() => {
1075
+ dom?.focus(ctx2, dom.getAreaThumbEl(ctx2));
921
1076
  });
922
1077
  },
923
1078
  focusChannelThumb(ctx2, evt) {
924
- raf2(() => {
925
- dom.getChannelSliderThumbEl(ctx2, evt.channel)?.focus({ preventScroll: true });
1079
+ raf(() => {
1080
+ dom?.focus(ctx2, dom.getChannelSliderThumbEl(ctx2, evt.channel));
926
1081
  });
1082
+ },
1083
+ setInitialFocus(ctx2) {
1084
+ raf(() => {
1085
+ dom.getInitialFocusEl(ctx2)?.focus({ preventScroll: true });
1086
+ });
1087
+ },
1088
+ setReturnFocus(ctx2) {
1089
+ raf(() => {
1090
+ dom?.focus(ctx2, dom.getTriggerEl(ctx2));
1091
+ });
1092
+ },
1093
+ invokeOnOpen(ctx2) {
1094
+ ctx2.onOpenChange?.({ open: true });
1095
+ },
1096
+ invokeOnClose(ctx2) {
1097
+ ctx2.onOpenChange?.({ open: false });
1098
+ },
1099
+ toggleVisibility(ctx2, _evt, { send }) {
1100
+ send({ type: ctx2.open ? "OPEN" : "CLOSE", src: "controlled" });
927
1101
  }
928
1102
  }
929
1103
  }
930
1104
  );
931
1105
  }
1106
+ var sync = {
1107
+ inputs(ctx) {
1108
+ const channelInputs = dom.getChannelInputEls(ctx);
1109
+ channelInputs.forEach((inputEl) => {
1110
+ const channel = inputEl.dataset.channel;
1111
+ dom.setValue(inputEl, getChannelValue(ctx.value, channel));
1112
+ });
1113
+ }
1114
+ };
932
1115
  var invoke = {
933
1116
  changeEnd(ctx) {
934
1117
  ctx.onValueChangeEnd?.({
935
1118
  value: ctx.value,
936
- valueAsColor: ctx.valueAsColor
1119
+ valueAsString: ctx.valueAsString
937
1120
  });
938
1121
  },
939
1122
  change(ctx) {
940
1123
  ctx.onValueChange?.({
941
1124
  value: ctx.value,
942
- valueAsColor: ctx.valueAsColor
1125
+ valueAsString: ctx.valueAsString
943
1126
  });
1127
+ dispatchInputValueEvent(dom.getHiddenInputEl(ctx), { value: ctx.valueAsString });
944
1128
  }
945
1129
  };
946
1130
  var set = {
947
1131
  value(ctx, color) {
948
- if (ctx.valueAsColor.isEqual(color))
1132
+ sync.inputs(ctx);
1133
+ if (!color || ctx.value.isEqual(color))
949
1134
  return;
950
- ctx.value = color.toString("css");
1135
+ ctx.value = ref2(color);
951
1136
  invoke.change(ctx);
952
1137
  }
953
1138
  };
954
1139
  export {
955
1140
  anatomy,
956
1141
  connect,
957
- machine
1142
+ machine,
1143
+ parse
958
1144
  };
959
1145
  //# sourceMappingURL=index.mjs.map