@destyler/color-picker 0.0.7-beta.1

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 ADDED
@@ -0,0 +1,1448 @@
1
+ import { createAnatomy } from '@zag-js/anatomy';
2
+ import { parseColor, normalizeColor, getColorAreaGradient } from '@zag-js/color-utils';
3
+ import { getRelativePoint, isLeftClick, isModifierKey, getEventPoint, getEventStep, getEventKey, trackPointerMove } from '@zag-js/dom-event';
4
+ import { createScope, queryAll, dataAttr, query, visuallyHiddenStyle, raf, getInitialFocus } from '@zag-js/dom-query';
5
+ import { getPlacementStyles, getPlacement } from '@zag-js/popper';
6
+ import { createMachine, guards } from '@zag-js/core';
7
+ import { trackDismissableElement } from '@zag-js/dismissable';
8
+ import { trackFormControl, dispatchInputValueEvent } from '@zag-js/form-utils';
9
+ import { disableTextSelection } from '@zag-js/text-selection';
10
+ import { compact, tryCatch } from '@zag-js/utils';
11
+
12
+ const anatomy = createAnatomy("color-picker", [
13
+ "root",
14
+ "label",
15
+ "control",
16
+ "trigger",
17
+ "positioner",
18
+ "content",
19
+ "area",
20
+ "areaThumb",
21
+ "valueText",
22
+ "areaBackground",
23
+ "channelSlider",
24
+ "channelSliderLabel",
25
+ "channelSliderTrack",
26
+ "channelSliderThumb",
27
+ "channelSliderValueText",
28
+ "channelInput",
29
+ "transparencyGrid",
30
+ "swatchGroup",
31
+ "swatchTrigger",
32
+ "swatchIndicator",
33
+ "swatch",
34
+ "eyeDropperTrigger",
35
+ "formatTrigger",
36
+ "formatSelect"
37
+ ]);
38
+ const parts = anatomy.build();
39
+
40
+ const dom = createScope({
41
+ getRootId: (ctx) => ctx.ids?.root ?? `color-picker:${ctx.id}`,
42
+ getLabelId: (ctx) => ctx.ids?.label ?? `color-picker:${ctx.id}:label`,
43
+ getHiddenInputId: (ctx) => ctx.ids?.hiddenInput ?? `color-picker:${ctx.id}:hidden-input`,
44
+ getControlId: (ctx) => ctx.ids?.control ?? `color-picker:${ctx.id}:control`,
45
+ getTriggerId: (ctx) => ctx.ids?.trigger ?? `color-picker:${ctx.id}:trigger`,
46
+ getContentId: (ctx) => ctx.ids?.content ?? `color-picker:${ctx.id}:content`,
47
+ getPositionerId: (ctx) => ctx.ids?.positioner ?? `color-picker:${ctx.id}:positioner`,
48
+ getFormatSelectId: (ctx) => ctx.ids?.formatSelect ?? `color-picker:${ctx.id}:format-select`,
49
+ getAreaId: (ctx) => ctx.ids?.area ?? `color-picker:${ctx.id}:area`,
50
+ getAreaGradientId: (ctx) => ctx.ids?.areaGradient ?? `color-picker:${ctx.id}:area-gradient`,
51
+ getAreaThumbId: (ctx) => ctx.ids?.areaThumb ?? `color-picker:${ctx.id}:area-thumb`,
52
+ getChannelSliderTrackId: (ctx, channel) => ctx.ids?.channelSliderTrack?.(channel) ?? `color-picker:${ctx.id}:slider-track:${channel}`,
53
+ getChannelSliderThumbId: (ctx, channel) => ctx.ids?.channelSliderThumb?.(channel) ?? `color-picker:${ctx.id}:slider-thumb:${channel}`,
54
+ getContentEl: (ctx) => dom.getById(ctx, dom.getContentId(ctx)),
55
+ getAreaThumbEl: (ctx) => dom.getById(ctx, dom.getAreaThumbId(ctx)),
56
+ getChannelSliderThumbEl: (ctx, channel) => dom.getById(ctx, dom.getChannelSliderThumbId(ctx, channel)),
57
+ getChannelInputEl: (ctx, channel) => {
58
+ const selector = `input[data-channel="${channel}"]`;
59
+ return [
60
+ ...queryAll(dom.getContentEl(ctx), selector),
61
+ ...queryAll(dom.getControlEl(ctx), selector)
62
+ ];
63
+ },
64
+ getFormatSelectEl: (ctx) => dom.getById(ctx, dom.getFormatSelectId(ctx)),
65
+ getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
66
+ getAreaEl: (ctx) => dom.getById(ctx, dom.getAreaId(ctx)),
67
+ getAreaValueFromPoint(ctx, point) {
68
+ const areaEl = dom.getAreaEl(ctx);
69
+ if (!areaEl)
70
+ return;
71
+ const { percent } = getRelativePoint(point, areaEl);
72
+ return percent;
73
+ },
74
+ getControlEl: (ctx) => dom.getById(ctx, dom.getControlId(ctx)),
75
+ getTriggerEl: (ctx) => dom.getById(ctx, dom.getTriggerId(ctx)),
76
+ getPositionerEl: (ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
77
+ getChannelSliderTrackEl: (ctx, channel) => {
78
+ return dom.getById(ctx, dom.getChannelSliderTrackId(ctx, channel));
79
+ },
80
+ getChannelSliderValueFromPoint(ctx, point, channel) {
81
+ const trackEl = dom.getChannelSliderTrackEl(ctx, channel);
82
+ if (!trackEl)
83
+ return;
84
+ const { percent } = getRelativePoint(point, trackEl);
85
+ return percent;
86
+ },
87
+ getChannelInputEls: (ctx) => {
88
+ return [
89
+ ...queryAll(dom.getContentEl(ctx), "input[data-channel]"),
90
+ ...queryAll(dom.getControlEl(ctx), "input[data-channel]")
91
+ ];
92
+ }
93
+ });
94
+
95
+ function getChannelDisplayColor(color, channel) {
96
+ switch (channel) {
97
+ case "hue":
98
+ return parseColor(`hsl(${color.getChannelValue("hue")}, 100%, 50%)`);
99
+ case "lightness":
100
+ case "brightness":
101
+ case "saturation":
102
+ case "red":
103
+ case "green":
104
+ case "blue":
105
+ return color.withChannelValue("alpha", 1);
106
+ case "alpha": {
107
+ return color;
108
+ }
109
+ default:
110
+ throw new Error(`Unknown color channel: ${channel}`);
111
+ }
112
+ }
113
+
114
+ function getChannelValue(color, channel) {
115
+ if (channel == null)
116
+ return "";
117
+ if (channel === "hex") {
118
+ return color.toString("hex");
119
+ }
120
+ if (channel === "css") {
121
+ return color.toString("css");
122
+ }
123
+ if (channel in color) {
124
+ return color.getChannelValue(channel).toString();
125
+ }
126
+ const isHSL = color.getFormat() === "hsla";
127
+ switch (channel) {
128
+ case "hue":
129
+ return isHSL ? color.toFormat("hsla").getChannelValue("hue").toString() : color.toFormat("hsba").getChannelValue("hue").toString();
130
+ case "saturation":
131
+ return isHSL ? color.toFormat("hsla").getChannelValue("saturation").toString() : color.toFormat("hsba").getChannelValue("saturation").toString();
132
+ case "lightness":
133
+ return color.toFormat("hsla").getChannelValue("lightness").toString();
134
+ case "brightness":
135
+ return color.toFormat("hsba").getChannelValue("brightness").toString();
136
+ case "red":
137
+ case "green":
138
+ case "blue":
139
+ return color.toFormat("rgba").getChannelValue(channel).toString();
140
+ default:
141
+ return color.getChannelValue(channel).toString();
142
+ }
143
+ }
144
+ function getChannelRange(color, channel) {
145
+ switch (channel) {
146
+ case "hex":
147
+ const minColor = parseColor("#000000");
148
+ const maxColor = parseColor("#FFFFFF");
149
+ return {
150
+ minValue: minColor.toHexInt(),
151
+ maxValue: maxColor.toHexInt(),
152
+ pageSize: 10,
153
+ step: 1
154
+ };
155
+ case "css":
156
+ return void 0;
157
+ case "hue":
158
+ case "saturation":
159
+ case "lightness":
160
+ return color.toFormat("hsla").getChannelRange(channel);
161
+ case "brightness":
162
+ return color.toFormat("hsba").getChannelRange(channel);
163
+ case "red":
164
+ case "green":
165
+ case "blue":
166
+ return color.toFormat("rgba").getChannelRange(channel);
167
+ default:
168
+ return color.getChannelRange(channel);
169
+ }
170
+ }
171
+
172
+ function getSliderBackgroundDirection(orientation, dir) {
173
+ if (orientation === "vertical") {
174
+ return "top";
175
+ } else if (dir === "ltr") {
176
+ return "right";
177
+ } else {
178
+ return "left";
179
+ }
180
+ }
181
+ function getSliderBackground(props) {
182
+ const { channel, value, dir, orientation } = props;
183
+ const bgDirection = getSliderBackgroundDirection(orientation, dir);
184
+ const { minValue, maxValue } = value.getChannelRange(channel);
185
+ switch (channel) {
186
+ case "hue":
187
+ 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%)`;
188
+ case "lightness": {
189
+ const start = value.withChannelValue(channel, minValue).toString("css");
190
+ const middle = value.withChannelValue(channel, (maxValue - minValue) / 2).toString("css");
191
+ const end = value.withChannelValue(channel, maxValue).toString("css");
192
+ return `linear-gradient(to ${bgDirection}, ${start}, ${middle}, ${end})`;
193
+ }
194
+ case "saturation":
195
+ case "brightness":
196
+ case "red":
197
+ case "green":
198
+ case "blue":
199
+ case "alpha": {
200
+ const start = value.withChannelValue(channel, minValue).toString("css");
201
+ const end = value.withChannelValue(channel, maxValue).toString("css");
202
+ return `linear-gradient(to ${bgDirection}, ${start}, ${end})`;
203
+ }
204
+ default:
205
+ throw new Error(`Unknown color channel: ${channel}`);
206
+ }
207
+ }
208
+
209
+ function connect(state, send, normalize) {
210
+ const value = state.context.value;
211
+ const areaValue = state.context.areaValue;
212
+ const valueAsString = state.context.valueAsString;
213
+ const disabled = state.context.isDisabled;
214
+ const interactive = state.context.isInteractive;
215
+ const dragging = state.hasTag("dragging");
216
+ const open = state.hasTag("open");
217
+ const focused = state.hasTag("focused");
218
+ const getAreaChannels = (props) => {
219
+ const channels = areaValue.getChannels();
220
+ return {
221
+ xChannel: props.xChannel ?? channels[1],
222
+ yChannel: props.yChannel ?? channels[2]
223
+ };
224
+ };
225
+ const currentPlacement = state.context.currentPlacement;
226
+ const popperStyles = getPlacementStyles({
227
+ ...state.context.positioning,
228
+ placement: currentPlacement
229
+ });
230
+ function getSwatchTriggerState(props) {
231
+ const color = normalizeColor(props.value).toFormat(state.context.format);
232
+ return {
233
+ value: color,
234
+ valueAsString: color.toString("hex"),
235
+ checked: color.isEqual(value),
236
+ disabled: props.disabled || !interactive
237
+ };
238
+ }
239
+ return {
240
+ dragging,
241
+ open,
242
+ valueAsString,
243
+ value,
244
+ setOpen(nextOpen) {
245
+ if (nextOpen === open)
246
+ return;
247
+ send({ type: nextOpen ? "OPEN" : "CLOSE" });
248
+ },
249
+ setValue(value2) {
250
+ send({ type: "VALUE.SET", value: normalizeColor(value2), src: "set-color" });
251
+ },
252
+ getChannelValue(channel) {
253
+ return getChannelValue(value, channel);
254
+ },
255
+ getChannelValueText(channel, locale) {
256
+ return value.formatChannelValue(channel, locale);
257
+ },
258
+ setChannelValue(channel, channelValue) {
259
+ const color = value.withChannelValue(channel, channelValue);
260
+ send({ type: "VALUE.SET", value: color, src: "set-channel" });
261
+ },
262
+ format: state.context.format,
263
+ setFormat(format) {
264
+ const formatValue = value.toFormat(format);
265
+ send({ type: "VALUE.SET", value: formatValue, src: "set-format" });
266
+ },
267
+ alpha: value.getChannelValue("alpha"),
268
+ setAlpha(alphaValue) {
269
+ const color = value.withChannelValue("alpha", alphaValue);
270
+ send({ type: "VALUE.SET", value: color, src: "set-alpha" });
271
+ },
272
+ getRootProps() {
273
+ return normalize.element({
274
+ ...parts.root.attrs,
275
+ "dir": state.context.dir,
276
+ "id": dom.getRootId(state.context),
277
+ "data-disabled": dataAttr(disabled),
278
+ "data-readonly": dataAttr(state.context.readOnly),
279
+ "data-invalid": dataAttr(state.context.invalid),
280
+ "style": {
281
+ "--value": value.toString("css")
282
+ }
283
+ });
284
+ },
285
+ getLabelProps() {
286
+ return normalize.element({
287
+ ...parts.label.attrs,
288
+ "dir": state.context.dir,
289
+ "id": dom.getLabelId(state.context),
290
+ "htmlFor": dom.getHiddenInputId(state.context),
291
+ "data-disabled": dataAttr(disabled),
292
+ "data-readonly": dataAttr(state.context.readOnly),
293
+ "data-invalid": dataAttr(state.context.invalid),
294
+ "data-focus": dataAttr(focused),
295
+ onClick(event) {
296
+ event.preventDefault();
297
+ const inputEl = query(dom.getControlEl(state.context), "[data-channel=hex]");
298
+ inputEl?.focus({ preventScroll: true });
299
+ }
300
+ });
301
+ },
302
+ getControlProps() {
303
+ return normalize.element({
304
+ ...parts.control.attrs,
305
+ "id": dom.getControlId(state.context),
306
+ "dir": state.context.dir,
307
+ "data-disabled": dataAttr(disabled),
308
+ "data-readonly": dataAttr(state.context.readOnly),
309
+ "data-invalid": dataAttr(state.context.invalid),
310
+ "data-state": open ? "open" : "closed",
311
+ "data-focus": dataAttr(focused)
312
+ });
313
+ },
314
+ getTriggerProps() {
315
+ return normalize.button({
316
+ ...parts.trigger.attrs,
317
+ "id": dom.getTriggerId(state.context),
318
+ "dir": state.context.dir,
319
+ "disabled": disabled,
320
+ "aria-label": `select color. current color is ${valueAsString}`,
321
+ "aria-controls": dom.getContentId(state.context),
322
+ "aria-labelledby": dom.getLabelId(state.context),
323
+ "data-disabled": dataAttr(disabled),
324
+ "data-readonly": dataAttr(state.context.readOnly),
325
+ "data-invalid": dataAttr(state.context.invalid),
326
+ "data-placement": currentPlacement,
327
+ "aria-expanded": dataAttr(open),
328
+ "data-state": open ? "open" : "closed",
329
+ "data-focus": dataAttr(focused),
330
+ "type": "button",
331
+ onClick() {
332
+ if (!interactive)
333
+ return;
334
+ send({ type: "TRIGGER.CLICK" });
335
+ },
336
+ onBlur() {
337
+ if (!interactive)
338
+ return;
339
+ send({ type: "TRIGGER.BLUR" });
340
+ },
341
+ "style": {
342
+ position: "relative"
343
+ }
344
+ });
345
+ },
346
+ getPositionerProps() {
347
+ return normalize.element({
348
+ ...parts.positioner.attrs,
349
+ id: dom.getPositionerId(state.context),
350
+ dir: state.context.dir,
351
+ style: popperStyles.floating
352
+ });
353
+ },
354
+ getContentProps() {
355
+ return normalize.element({
356
+ ...parts.content.attrs,
357
+ "id": dom.getContentId(state.context),
358
+ "dir": state.context.dir,
359
+ "data-placement": currentPlacement,
360
+ "data-state": open ? "open" : "closed",
361
+ "hidden": !open
362
+ });
363
+ },
364
+ getValueTextProps() {
365
+ return normalize.element({
366
+ ...parts.valueText.attrs,
367
+ "dir": state.context.dir,
368
+ "data-disabled": dataAttr(disabled),
369
+ "data-focus": dataAttr(focused)
370
+ });
371
+ },
372
+ getAreaProps(props = {}) {
373
+ const { xChannel, yChannel } = getAreaChannels(props);
374
+ const { areaStyles } = getColorAreaGradient(areaValue, {
375
+ xChannel,
376
+ yChannel,
377
+ dir: state.context.dir
378
+ });
379
+ return normalize.element({
380
+ ...parts.area.attrs,
381
+ "id": dom.getAreaId(state.context),
382
+ "role": "group",
383
+ "data-invalid": dataAttr(state.context.invalid),
384
+ "data-disabled": dataAttr(disabled),
385
+ "data-readonly": dataAttr(state.context.readOnly),
386
+ onPointerDown(event) {
387
+ if (!interactive)
388
+ return;
389
+ if (!isLeftClick(event))
390
+ return;
391
+ if (isModifierKey(event))
392
+ return;
393
+ const point = getEventPoint(event);
394
+ const channel = { xChannel, yChannel };
395
+ send({ type: "AREA.POINTER_DOWN", point, channel, id: "area" });
396
+ event.preventDefault();
397
+ },
398
+ "style": {
399
+ position: "relative",
400
+ touchAction: "none",
401
+ forcedColorAdjust: "none",
402
+ ...areaStyles
403
+ }
404
+ });
405
+ },
406
+ getAreaBackgroundProps(props = {}) {
407
+ const { xChannel, yChannel } = getAreaChannels(props);
408
+ const { areaGradientStyles } = getColorAreaGradient(areaValue, {
409
+ xChannel,
410
+ yChannel,
411
+ dir: state.context.dir
412
+ });
413
+ return normalize.element({
414
+ ...parts.areaBackground.attrs,
415
+ "id": dom.getAreaGradientId(state.context),
416
+ "data-invalid": dataAttr(state.context.invalid),
417
+ "data-disabled": dataAttr(disabled),
418
+ "data-readonly": dataAttr(state.context.readOnly),
419
+ "style": {
420
+ position: "relative",
421
+ touchAction: "none",
422
+ forcedColorAdjust: "none",
423
+ ...areaGradientStyles
424
+ }
425
+ });
426
+ },
427
+ getAreaThumbProps(props = {}) {
428
+ const { xChannel, yChannel } = getAreaChannels(props);
429
+ const channel = { xChannel, yChannel };
430
+ const xPercent = areaValue.getChannelValuePercent(xChannel);
431
+ const yPercent = 1 - areaValue.getChannelValuePercent(yChannel);
432
+ const xValue = areaValue.getChannelValue(xChannel);
433
+ const yValue = areaValue.getChannelValue(yChannel);
434
+ const color = areaValue.withChannelValue("alpha", 1).toString("css");
435
+ return normalize.element({
436
+ ...parts.areaThumb.attrs,
437
+ "id": dom.getAreaThumbId(state.context),
438
+ "dir": state.context.dir,
439
+ "tabIndex": disabled ? void 0 : 0,
440
+ "data-disabled": dataAttr(disabled),
441
+ "data-invalid": dataAttr(state.context.invalid),
442
+ "data-readonly": dataAttr(state.context.readOnly),
443
+ "role": "slider",
444
+ "aria-valuemin": 0,
445
+ "aria-valuemax": 100,
446
+ "aria-valuenow": xValue,
447
+ "aria-label": `${xChannel} and ${yChannel}`,
448
+ "aria-roledescription": "2d slider",
449
+ "aria-valuetext": `${xChannel} ${xValue}, ${yChannel} ${yValue}`,
450
+ "style": {
451
+ "position": "absolute",
452
+ "left": `${xPercent * 100}%`,
453
+ "top": `${yPercent * 100}%`,
454
+ "transform": "translate(-50%, -50%)",
455
+ "touchAction": "none",
456
+ "forcedColorAdjust": "none",
457
+ "--color": color,
458
+ "background": color
459
+ },
460
+ onFocus() {
461
+ if (!interactive)
462
+ return;
463
+ send({ type: "AREA.FOCUS", id: "area", channel });
464
+ },
465
+ onKeyDown(event) {
466
+ if (event.defaultPrevented)
467
+ return;
468
+ if (!interactive)
469
+ return;
470
+ const step = getEventStep(event);
471
+ const keyMap = {
472
+ ArrowUp() {
473
+ send({ type: "AREA.ARROW_UP", channel, step });
474
+ },
475
+ ArrowDown() {
476
+ send({ type: "AREA.ARROW_DOWN", channel, step });
477
+ },
478
+ ArrowLeft() {
479
+ send({ type: "AREA.ARROW_LEFT", channel, step });
480
+ },
481
+ ArrowRight() {
482
+ send({ type: "AREA.ARROW_RIGHT", channel, step });
483
+ },
484
+ PageUp() {
485
+ send({ type: "AREA.PAGE_UP", channel, step });
486
+ },
487
+ PageDown() {
488
+ send({ type: "AREA.PAGE_DOWN", channel, step });
489
+ },
490
+ Escape(event2) {
491
+ event2.stopPropagation();
492
+ }
493
+ };
494
+ const exec = keyMap[getEventKey(event, state.context)];
495
+ if (exec) {
496
+ exec(event);
497
+ event.preventDefault();
498
+ }
499
+ }
500
+ });
501
+ },
502
+ getTransparencyGridProps(props = {}) {
503
+ const { size = "12px" } = props;
504
+ return normalize.element({
505
+ ...parts.transparencyGrid.attrs,
506
+ style: {
507
+ "--size": size,
508
+ "width": "100%",
509
+ "height": "100%",
510
+ "position": "absolute",
511
+ "backgroundColor": "#fff",
512
+ "backgroundImage": "conic-gradient(#eeeeee 0 25%, transparent 0 50%, #eeeeee 0 75%, transparent 0)",
513
+ "backgroundSize": "var(--size) var(--size)",
514
+ "inset": "0px",
515
+ "zIndex": "auto",
516
+ "pointerEvents": "none"
517
+ }
518
+ });
519
+ },
520
+ getChannelSliderProps(props) {
521
+ const { orientation = "horizontal", channel, format } = props;
522
+ return normalize.element({
523
+ ...parts.channelSlider.attrs,
524
+ "data-channel": channel,
525
+ "data-orientation": orientation,
526
+ "role": "presentation",
527
+ onPointerDown(event) {
528
+ if (!interactive)
529
+ return;
530
+ if (!isLeftClick(event))
531
+ return;
532
+ if (isModifierKey(event))
533
+ return;
534
+ const point = getEventPoint(event);
535
+ send({ type: "CHANNEL_SLIDER.POINTER_DOWN", channel, format, point, id: channel, orientation });
536
+ event.preventDefault();
537
+ },
538
+ "style": {
539
+ position: "relative",
540
+ touchAction: "none"
541
+ }
542
+ });
543
+ },
544
+ getChannelSliderTrackProps(props) {
545
+ const { orientation = "horizontal", channel, format } = props;
546
+ const normalizedValue = format ? value.toFormat(format) : areaValue;
547
+ return normalize.element({
548
+ ...parts.channelSliderTrack.attrs,
549
+ "id": dom.getChannelSliderTrackId(state.context, channel),
550
+ "role": "group",
551
+ "data-channel": channel,
552
+ "data-orientation": orientation,
553
+ "style": {
554
+ position: "relative",
555
+ forcedColorAdjust: "none",
556
+ backgroundImage: getSliderBackground({
557
+ orientation,
558
+ channel,
559
+ dir: state.context.dir,
560
+ value: normalizedValue
561
+ })
562
+ }
563
+ });
564
+ },
565
+ getChannelSliderLabelProps(props) {
566
+ const { channel } = props;
567
+ return normalize.element({
568
+ ...parts.channelSliderLabel.attrs,
569
+ "data-channel": channel,
570
+ onClick(event) {
571
+ if (!interactive)
572
+ return;
573
+ event.preventDefault();
574
+ const thumbId = dom.getChannelSliderThumbId(state.context, channel);
575
+ dom.getById(state.context, thumbId)?.focus({ preventScroll: true });
576
+ },
577
+ "style": {
578
+ userSelect: "none",
579
+ WebkitUserSelect: "none"
580
+ }
581
+ });
582
+ },
583
+ getChannelSliderValueTextProps(props) {
584
+ return normalize.element({
585
+ ...parts.channelSliderValueText.attrs,
586
+ "data-channel": props.channel
587
+ });
588
+ },
589
+ getChannelSliderThumbProps(props) {
590
+ const { orientation = "horizontal", channel, format } = props;
591
+ const normalizedValue = format ? value.toFormat(format) : areaValue;
592
+ const channelRange = normalizedValue.getChannelRange(channel);
593
+ const channelValue = normalizedValue.getChannelValue(channel);
594
+ const offset = (channelValue - channelRange.minValue) / (channelRange.maxValue - channelRange.minValue);
595
+ const placementStyles = orientation === "horizontal" ? { left: `${offset * 100}%`, top: "50%" } : { top: `${offset * 100}%`, left: "50%" };
596
+ return normalize.element({
597
+ ...parts.channelSliderThumb.attrs,
598
+ "id": dom.getChannelSliderThumbId(state.context, channel),
599
+ "role": "slider",
600
+ "aria-label": channel,
601
+ "tabIndex": disabled ? void 0 : 0,
602
+ "data-channel": channel,
603
+ "data-disabled": dataAttr(disabled),
604
+ "data-orientation": orientation,
605
+ "aria-disabled": dataAttr(disabled),
606
+ "aria-orientation": orientation,
607
+ "aria-valuemax": channelRange.maxValue,
608
+ "aria-valuemin": channelRange.minValue,
609
+ "aria-valuenow": channelValue,
610
+ "aria-valuetext": `${channel} ${channelValue}`,
611
+ "style": {
612
+ forcedColorAdjust: "none",
613
+ position: "absolute",
614
+ background: getChannelDisplayColor(areaValue, channel).toString("css"),
615
+ ...placementStyles
616
+ },
617
+ onFocus() {
618
+ if (!interactive)
619
+ return;
620
+ send({ type: "CHANNEL_SLIDER.FOCUS", channel });
621
+ },
622
+ onKeyDown(event) {
623
+ if (event.defaultPrevented)
624
+ return;
625
+ if (!interactive)
626
+ return;
627
+ const step = getEventStep(event) * channelRange.step;
628
+ const keyMap = {
629
+ ArrowUp() {
630
+ send({ type: "CHANNEL_SLIDER.ARROW_UP", channel, step });
631
+ },
632
+ ArrowDown() {
633
+ send({ type: "CHANNEL_SLIDER.ARROW_DOWN", channel, step });
634
+ },
635
+ ArrowLeft() {
636
+ send({ type: "CHANNEL_SLIDER.ARROW_LEFT", channel, step });
637
+ },
638
+ ArrowRight() {
639
+ send({ type: "CHANNEL_SLIDER.ARROW_RIGHT", channel, step });
640
+ },
641
+ PageUp() {
642
+ send({ type: "CHANNEL_SLIDER.PAGE_UP", channel });
643
+ },
644
+ PageDown() {
645
+ send({ type: "CHANNEL_SLIDER.PAGE_DOWN", channel });
646
+ },
647
+ Home() {
648
+ send({ type: "CHANNEL_SLIDER.HOME", channel });
649
+ },
650
+ End() {
651
+ send({ type: "CHANNEL_SLIDER.END", channel });
652
+ },
653
+ Escape(event2) {
654
+ event2.stopPropagation();
655
+ }
656
+ };
657
+ const exec = keyMap[getEventKey(event, state.context)];
658
+ if (exec) {
659
+ exec(event);
660
+ event.preventDefault();
661
+ }
662
+ }
663
+ });
664
+ },
665
+ getChannelInputProps(props) {
666
+ const { channel } = props;
667
+ const isTextField = channel === "hex" || channel === "css";
668
+ const channelRange = getChannelRange(value, channel);
669
+ return normalize.input({
670
+ ...parts.channelInput.attrs,
671
+ "dir": state.context.dir,
672
+ "type": isTextField ? "text" : "number",
673
+ "data-channel": channel,
674
+ "aria-label": channel,
675
+ "spellCheck": false,
676
+ "autoComplete": "off",
677
+ "disabled": disabled,
678
+ "data-disabled": dataAttr(disabled),
679
+ "data-invalid": dataAttr(state.context.invalid),
680
+ "data-readonly": dataAttr(state.context.readOnly),
681
+ "readOnly": state.context.readOnly,
682
+ "defaultValue": getChannelValue(value, channel),
683
+ "min": channelRange?.minValue,
684
+ "max": channelRange?.maxValue,
685
+ "step": channelRange?.step,
686
+ onBeforeInput(event) {
687
+ if (isTextField || !interactive)
688
+ return;
689
+ const value2 = event.currentTarget.value;
690
+ if (value2.match(/[^0-9.]/g)) {
691
+ event.preventDefault();
692
+ }
693
+ },
694
+ onFocus(event) {
695
+ if (!interactive)
696
+ return;
697
+ send({ type: "CHANNEL_INPUT.FOCUS", channel });
698
+ event.currentTarget.select();
699
+ },
700
+ onBlur(event) {
701
+ if (!interactive)
702
+ return;
703
+ const value2 = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber;
704
+ send({ type: "CHANNEL_INPUT.BLUR", channel, value: value2, isTextField });
705
+ },
706
+ onKeyDown(event) {
707
+ if (event.defaultPrevented)
708
+ return;
709
+ if (!interactive)
710
+ return;
711
+ if (event.key === "Enter") {
712
+ const value2 = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber;
713
+ send({ type: "CHANNEL_INPUT.CHANGE", channel, value: value2, isTextField });
714
+ event.preventDefault();
715
+ }
716
+ },
717
+ "style": {
718
+ appearance: "none",
719
+ WebkitAppearance: "none",
720
+ MozAppearance: "textfield"
721
+ }
722
+ });
723
+ },
724
+ getHiddenInputProps() {
725
+ return normalize.input({
726
+ type: "text",
727
+ disabled,
728
+ name: state.context.name,
729
+ tabIndex: -1,
730
+ readOnly: state.context.readOnly,
731
+ required: state.context.required,
732
+ id: dom.getHiddenInputId(state.context),
733
+ style: visuallyHiddenStyle,
734
+ defaultValue: valueAsString
735
+ });
736
+ },
737
+ getEyeDropperTriggerProps() {
738
+ return normalize.button({
739
+ ...parts.eyeDropperTrigger.attrs,
740
+ "type": "button",
741
+ "dir": state.context.dir,
742
+ "disabled": disabled,
743
+ "data-disabled": dataAttr(disabled),
744
+ "data-invalid": dataAttr(state.context.invalid),
745
+ "data-readonly": dataAttr(state.context.readOnly),
746
+ "aria-label": "Pick a color from the screen",
747
+ onClick() {
748
+ if (!interactive)
749
+ return;
750
+ send("EYEDROPPER.CLICK");
751
+ }
752
+ });
753
+ },
754
+ getSwatchGroupProps() {
755
+ return normalize.element({
756
+ ...parts.swatchGroup.attrs,
757
+ role: "group"
758
+ });
759
+ },
760
+ getSwatchTriggerState,
761
+ getSwatchTriggerProps(props) {
762
+ const swatchState = getSwatchTriggerState(props);
763
+ return normalize.button({
764
+ ...parts.swatchTrigger.attrs,
765
+ "disabled": swatchState.disabled,
766
+ "dir": state.context.dir,
767
+ "type": "button",
768
+ "aria-label": `select ${swatchState.valueAsString} as the color`,
769
+ "data-state": swatchState.checked ? "checked" : "unchecked",
770
+ "data-value": swatchState.valueAsString,
771
+ "data-disabled": dataAttr(swatchState.disabled),
772
+ onClick() {
773
+ if (swatchState.disabled)
774
+ return;
775
+ send({ type: "SWATCH_TRIGGER.CLICK", value: swatchState.value });
776
+ },
777
+ "style": {
778
+ "--color": swatchState.valueAsString,
779
+ "position": "relative"
780
+ }
781
+ });
782
+ },
783
+ getSwatchIndicatorProps(props) {
784
+ const swatchState = getSwatchTriggerState(props);
785
+ return normalize.element({
786
+ ...parts.swatchIndicator.attrs,
787
+ dir: state.context.dir,
788
+ hidden: !swatchState.checked
789
+ });
790
+ },
791
+ getSwatchProps(props) {
792
+ const { respectAlpha = true } = props;
793
+ const swatchState = getSwatchTriggerState(props);
794
+ const color = swatchState.value.toString(respectAlpha ? "css" : "hex");
795
+ return normalize.element({
796
+ ...parts.swatch.attrs,
797
+ "dir": state.context.dir,
798
+ "data-state": swatchState.checked ? "checked" : "unchecked",
799
+ "data-value": swatchState.valueAsString,
800
+ "style": {
801
+ "--color": color,
802
+ "position": "relative",
803
+ "background": color
804
+ }
805
+ });
806
+ },
807
+ getFormatTriggerProps() {
808
+ return normalize.button({
809
+ ...parts.formatTrigger.attrs,
810
+ "dir": state.context.dir,
811
+ "type": "button",
812
+ "aria-label": `change color format to ${getNextFormat(state.context.format)}`,
813
+ onClick(event) {
814
+ if (event.currentTarget.disabled)
815
+ return;
816
+ const nextFormat = getNextFormat(state.context.format);
817
+ send({ type: "FORMAT.SET", format: nextFormat, src: "format-trigger" });
818
+ }
819
+ });
820
+ },
821
+ getFormatSelectProps() {
822
+ return normalize.select({
823
+ ...parts.formatSelect.attrs,
824
+ "aria-label": "change color format",
825
+ "dir": state.context.dir,
826
+ "defaultValue": state.context.format,
827
+ "disabled": disabled,
828
+ onChange(event) {
829
+ const format = assertFormat(event.currentTarget.value);
830
+ send({ type: "FORMAT.SET", format, src: "format-select" });
831
+ }
832
+ });
833
+ }
834
+ };
835
+ }
836
+ const formats = ["hsba", "hsla", "rgba"];
837
+ const formatRegex = new RegExp(`^(?:${formats.join("|")})$`);
838
+ function getNextFormat(format) {
839
+ const index = formats.indexOf(format);
840
+ return formats[index + 1] ?? formats[0];
841
+ }
842
+ function assertFormat(format) {
843
+ if (formatRegex.test(format))
844
+ return format;
845
+ throw new Error(`Unsupported color format: ${format}`);
846
+ }
847
+
848
+ function parse(colorString) {
849
+ return parseColor(colorString);
850
+ }
851
+
852
+ const { and } = guards;
853
+ const sync = {
854
+ // sync channel inputs
855
+ inputs(ctx, color) {
856
+ const channelInputs = dom.getChannelInputEls(ctx);
857
+ raf(() => {
858
+ channelInputs.forEach((inputEl) => {
859
+ const channel = inputEl.dataset.channel;
860
+ dom.setValue(inputEl, getChannelValue(color || ctx.value, channel));
861
+ });
862
+ });
863
+ },
864
+ // sync format select
865
+ formatSelect(ctx) {
866
+ const selectEl = dom.getFormatSelectEl(ctx);
867
+ raf(() => {
868
+ dom.setValue(selectEl, ctx.format);
869
+ });
870
+ }
871
+ };
872
+ const invoke = {
873
+ changeEnd(ctx) {
874
+ const value = ctx.value.toFormat(ctx.format);
875
+ ctx.onValueChangeEnd?.({
876
+ value,
877
+ valueAsString: ctx.valueAsString
878
+ });
879
+ },
880
+ change(ctx) {
881
+ const value = ctx.value.toFormat(ctx.format);
882
+ ctx.onValueChange?.({
883
+ value,
884
+ valueAsString: ctx.valueAsString
885
+ });
886
+ dispatchInputValueEvent(dom.getHiddenInputEl(ctx), { value: ctx.valueAsString });
887
+ },
888
+ formatChange(ctx) {
889
+ ctx.onFormatChange?.({ format: ctx.format });
890
+ }
891
+ };
892
+ const set = {
893
+ value(ctx, color) {
894
+ if (!color || ctx.value.isEqual(color))
895
+ return;
896
+ ctx.value = color;
897
+ invoke.change(ctx);
898
+ },
899
+ format(ctx, format) {
900
+ if (ctx.format === format)
901
+ return;
902
+ ctx.format = format;
903
+ invoke.formatChange(ctx);
904
+ }
905
+ };
906
+ function machine(userContext) {
907
+ const ctx = compact(userContext);
908
+ return createMachine(
909
+ {
910
+ id: "color-picker",
911
+ initial: ctx.open ? "open" : "idle",
912
+ context: {
913
+ dir: "ltr",
914
+ value: parse("#000000"),
915
+ format: "rgba",
916
+ disabled: false,
917
+ closeOnSelect: false,
918
+ openAutoFocus: true,
919
+ ...ctx,
920
+ activeId: null,
921
+ activeChannel: null,
922
+ activeOrientation: null,
923
+ fieldsetDisabled: false,
924
+ restoreFocus: true,
925
+ positioning: {
926
+ ...ctx.positioning,
927
+ placement: "bottom"
928
+ }
929
+ },
930
+ computed: {
931
+ isRtl: (ctx2) => ctx2.dir === "rtl",
932
+ isDisabled: (ctx2) => !!ctx2.disabled || ctx2.fieldsetDisabled,
933
+ isInteractive: (ctx2) => !(ctx2.isDisabled || ctx2.readOnly),
934
+ valueAsString: (ctx2) => ctx2.value.toString(ctx2.format),
935
+ areaValue: (ctx2) => {
936
+ const format = ctx2.format.startsWith("hsl") ? "hsla" : "hsba";
937
+ return ctx2.value.toFormat(format);
938
+ }
939
+ },
940
+ activities: ["trackFormControl"],
941
+ watch: {
942
+ value: ["syncInputElements"],
943
+ format: ["syncFormatSelectElement"],
944
+ open: ["toggleVisibility"]
945
+ },
946
+ on: {
947
+ "VALUE.SET": {
948
+ actions: ["setValue"]
949
+ },
950
+ "FORMAT.SET": {
951
+ actions: ["setFormat"]
952
+ },
953
+ "CHANNEL_INPUT.CHANGE": {
954
+ actions: ["setChannelColorFromInput"]
955
+ },
956
+ "EYEDROPPER.CLICK": {
957
+ actions: ["openEyeDropper"]
958
+ },
959
+ "SWATCH_TRIGGER.CLICK": {
960
+ actions: ["setValue"]
961
+ }
962
+ },
963
+ states: {
964
+ "idle": {
965
+ tags: ["closed"],
966
+ on: {
967
+ "CONTROLLED.OPEN": {
968
+ target: "open",
969
+ actions: ["setInitialFocus"]
970
+ },
971
+ "OPEN": [
972
+ {
973
+ guard: "isOpenControlled",
974
+ actions: ["invokeOnOpen"]
975
+ },
976
+ {
977
+ target: "open",
978
+ actions: ["invokeOnOpen", "setInitialFocus"]
979
+ }
980
+ ],
981
+ "TRIGGER.CLICK": [
982
+ {
983
+ guard: "isOpenControlled",
984
+ actions: ["invokeOnOpen"]
985
+ },
986
+ {
987
+ target: "open",
988
+ actions: ["invokeOnOpen", "setInitialFocus"]
989
+ }
990
+ ],
991
+ "CHANNEL_INPUT.FOCUS": {
992
+ target: "focused",
993
+ actions: ["setActiveChannel"]
994
+ }
995
+ }
996
+ },
997
+ "focused": {
998
+ tags: ["closed", "focused"],
999
+ on: {
1000
+ "CONTROLLED.OPEN": {
1001
+ target: "open",
1002
+ actions: ["setInitialFocus"]
1003
+ },
1004
+ "OPEN": [
1005
+ {
1006
+ guard: "isOpenControlled",
1007
+ actions: ["invokeOnOpen"]
1008
+ },
1009
+ {
1010
+ target: "open",
1011
+ actions: ["invokeOnOpen", "setInitialFocus"]
1012
+ }
1013
+ ],
1014
+ "TRIGGER.CLICK": [
1015
+ {
1016
+ guard: "isOpenControlled",
1017
+ actions: ["invokeOnOpen"]
1018
+ },
1019
+ {
1020
+ target: "open",
1021
+ actions: ["invokeOnOpen", "setInitialFocus"]
1022
+ }
1023
+ ],
1024
+ "CHANNEL_INPUT.FOCUS": {
1025
+ actions: ["setActiveChannel"]
1026
+ },
1027
+ "CHANNEL_INPUT.BLUR": {
1028
+ target: "idle",
1029
+ actions: ["setChannelColorFromInput"]
1030
+ },
1031
+ "TRIGGER.BLUR": {
1032
+ target: "idle"
1033
+ }
1034
+ }
1035
+ },
1036
+ "open": {
1037
+ tags: ["open"],
1038
+ activities: ["trackPositioning", "trackDismissableElement"],
1039
+ on: {
1040
+ "CONTROLLED.CLOSE": [
1041
+ {
1042
+ guard: "shouldRestoreFocus",
1043
+ target: "focused",
1044
+ actions: ["setReturnFocus"]
1045
+ },
1046
+ {
1047
+ target: "idle"
1048
+ }
1049
+ ],
1050
+ "TRIGGER.CLICK": [
1051
+ {
1052
+ guard: "isOpenControlled",
1053
+ actions: ["invokeOnClose"]
1054
+ },
1055
+ {
1056
+ target: "idle",
1057
+ actions: ["invokeOnClose"]
1058
+ }
1059
+ ],
1060
+ "AREA.POINTER_DOWN": {
1061
+ target: "open:dragging",
1062
+ actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"]
1063
+ },
1064
+ "AREA.FOCUS": {
1065
+ actions: ["setActiveChannel"]
1066
+ },
1067
+ "CHANNEL_SLIDER.POINTER_DOWN": {
1068
+ target: "open:dragging",
1069
+ actions: ["setActiveChannel", "setChannelColorFromPoint", "focusChannelThumb"]
1070
+ },
1071
+ "CHANNEL_SLIDER.FOCUS": {
1072
+ actions: ["setActiveChannel"]
1073
+ },
1074
+ "AREA.ARROW_LEFT": {
1075
+ actions: ["decrementAreaXChannel"]
1076
+ },
1077
+ "AREA.ARROW_RIGHT": {
1078
+ actions: ["incrementAreaXChannel"]
1079
+ },
1080
+ "AREA.ARROW_UP": {
1081
+ actions: ["incrementAreaYChannel"]
1082
+ },
1083
+ "AREA.ARROW_DOWN": {
1084
+ actions: ["decrementAreaYChannel"]
1085
+ },
1086
+ "AREA.PAGE_UP": {
1087
+ actions: ["incrementAreaXChannel"]
1088
+ },
1089
+ "AREA.PAGE_DOWN": {
1090
+ actions: ["decrementAreaXChannel"]
1091
+ },
1092
+ "CHANNEL_SLIDER.ARROW_LEFT": {
1093
+ actions: ["decrementChannel"]
1094
+ },
1095
+ "CHANNEL_SLIDER.ARROW_RIGHT": {
1096
+ actions: ["incrementChannel"]
1097
+ },
1098
+ "CHANNEL_SLIDER.ARROW_UP": {
1099
+ actions: ["incrementChannel"]
1100
+ },
1101
+ "CHANNEL_SLIDER.ARROW_DOWN": {
1102
+ actions: ["decrementChannel"]
1103
+ },
1104
+ "CHANNEL_SLIDER.PAGE_UP": {
1105
+ actions: ["incrementChannel"]
1106
+ },
1107
+ "CHANNEL_SLIDER.PAGE_DOWN": {
1108
+ actions: ["decrementChannel"]
1109
+ },
1110
+ "CHANNEL_SLIDER.HOME": {
1111
+ actions: ["setChannelToMin"]
1112
+ },
1113
+ "CHANNEL_SLIDER.END": {
1114
+ actions: ["setChannelToMax"]
1115
+ },
1116
+ "CHANNEL_INPUT.BLUR": {
1117
+ actions: ["setChannelColorFromInput"]
1118
+ },
1119
+ "INTERACT_OUTSIDE": [
1120
+ {
1121
+ guard: "isOpenControlled",
1122
+ actions: ["invokeOnClose"]
1123
+ },
1124
+ {
1125
+ guard: "shouldRestoreFocus",
1126
+ target: "focused",
1127
+ actions: ["invokeOnClose", "setReturnFocus"]
1128
+ },
1129
+ {
1130
+ target: "idle",
1131
+ actions: ["invokeOnClose"]
1132
+ }
1133
+ ],
1134
+ "CLOSE": [
1135
+ {
1136
+ guard: "isOpenControlled",
1137
+ actions: ["invokeOnClose"]
1138
+ },
1139
+ {
1140
+ target: "idle",
1141
+ actions: ["invokeOnClose"]
1142
+ }
1143
+ ],
1144
+ "SWATCH_TRIGGER.CLICK": [
1145
+ {
1146
+ guard: and("isOpenControlled", "closeOnSelect"),
1147
+ actions: ["setValue", "invokeOnClose"]
1148
+ },
1149
+ {
1150
+ guard: "closeOnSelect",
1151
+ target: "focused",
1152
+ actions: ["setValue", "invokeOnClose", "setReturnFocus"]
1153
+ },
1154
+ {
1155
+ actions: ["setValue"]
1156
+ }
1157
+ ]
1158
+ }
1159
+ },
1160
+ "open:dragging": {
1161
+ tags: ["open"],
1162
+ exit: ["clearActiveChannel"],
1163
+ activities: ["trackPointerMove", "disableTextSelection", "trackPositioning", "trackDismissableElement"],
1164
+ on: {
1165
+ "CONTROLLED.CLOSE": [
1166
+ {
1167
+ guard: "shouldRestoreFocus",
1168
+ target: "focused",
1169
+ actions: ["setReturnFocus"]
1170
+ },
1171
+ {
1172
+ target: "idle"
1173
+ }
1174
+ ],
1175
+ "AREA.POINTER_MOVE": {
1176
+ actions: ["setAreaColorFromPoint", "focusAreaThumb"]
1177
+ },
1178
+ "AREA.POINTER_UP": {
1179
+ target: "open",
1180
+ actions: ["invokeOnChangeEnd"]
1181
+ },
1182
+ "CHANNEL_SLIDER.POINTER_MOVE": {
1183
+ actions: ["setChannelColorFromPoint", "focusChannelThumb"]
1184
+ },
1185
+ "CHANNEL_SLIDER.POINTER_UP": {
1186
+ target: "open",
1187
+ actions: ["invokeOnChangeEnd"]
1188
+ },
1189
+ "INTERACT_OUTSIDE": [
1190
+ {
1191
+ guard: "isOpenControlled",
1192
+ actions: ["invokeOnClose"]
1193
+ },
1194
+ {
1195
+ guard: "shouldRestoreFocus",
1196
+ target: "focused",
1197
+ actions: ["invokeOnClose", "setReturnFocus"]
1198
+ },
1199
+ {
1200
+ target: "idle",
1201
+ actions: ["invokeOnClose"]
1202
+ }
1203
+ ],
1204
+ "CLOSE": [
1205
+ {
1206
+ guard: "isOpenControlled",
1207
+ actions: ["invokeOnClose"]
1208
+ },
1209
+ {
1210
+ target: "idle",
1211
+ actions: ["invokeOnClose"]
1212
+ }
1213
+ ]
1214
+ }
1215
+ }
1216
+ }
1217
+ },
1218
+ {
1219
+ guards: {
1220
+ closeOnSelect: (ctx2) => !!ctx2.closeOnSelect,
1221
+ isOpenControlled: (ctx2) => !!ctx2["open.controlled"],
1222
+ shouldRestoreFocus: (ctx2) => !!ctx2.restoreFocus
1223
+ },
1224
+ activities: {
1225
+ trackPositioning(ctx2) {
1226
+ ctx2.currentPlacement = ctx2.positioning.placement;
1227
+ const anchorEl = dom.getTriggerEl(ctx2);
1228
+ const getPositionerEl = () => dom.getPositionerEl(ctx2);
1229
+ return getPlacement(anchorEl, getPositionerEl, {
1230
+ ...ctx2.positioning,
1231
+ defer: true,
1232
+ onComplete(data) {
1233
+ ctx2.currentPlacement = data.placement;
1234
+ }
1235
+ });
1236
+ },
1237
+ trackDismissableElement(ctx2, _evt, { send }) {
1238
+ const getContentEl = () => dom.getContentEl(ctx2);
1239
+ return trackDismissableElement(getContentEl, {
1240
+ exclude: dom.getTriggerEl(ctx2),
1241
+ defer: true,
1242
+ onInteractOutside(event) {
1243
+ ctx2.onInteractOutside?.(event);
1244
+ if (event.defaultPrevented)
1245
+ return;
1246
+ ctx2.restoreFocus = !(event.detail.focusable || event.detail.contextmenu);
1247
+ },
1248
+ onPointerDownOutside: ctx2.onPointerDownOutside,
1249
+ onFocusOutside: ctx2.onFocusOutside,
1250
+ onDismiss() {
1251
+ send({ type: "INTERACT_OUTSIDE" });
1252
+ }
1253
+ });
1254
+ },
1255
+ trackFormControl(ctx2, _evt, { send, initialContext }) {
1256
+ const inputEl = dom.getHiddenInputEl(ctx2);
1257
+ return trackFormControl(inputEl, {
1258
+ onFieldsetDisabledChange(disabled) {
1259
+ ctx2.fieldsetDisabled = disabled;
1260
+ },
1261
+ onFormReset() {
1262
+ send({ type: "VALUE.SET", value: initialContext.value, src: "form.reset" });
1263
+ }
1264
+ });
1265
+ },
1266
+ trackPointerMove(ctx2, evt, { send }) {
1267
+ return trackPointerMove(dom.getDoc(ctx2), {
1268
+ onPointerMove({ point }) {
1269
+ const type = ctx2.activeId === "area" ? "AREA.POINTER_MOVE" : "CHANNEL_SLIDER.POINTER_MOVE";
1270
+ send({ type, point, format: evt.format });
1271
+ },
1272
+ onPointerUp() {
1273
+ const type = ctx2.activeId === "area" ? "AREA.POINTER_UP" : "CHANNEL_SLIDER.POINTER_UP";
1274
+ send({ type });
1275
+ }
1276
+ });
1277
+ },
1278
+ disableTextSelection(ctx2) {
1279
+ return disableTextSelection({ doc: dom.getDoc(ctx2), target: dom.getContentEl(ctx2) });
1280
+ }
1281
+ },
1282
+ actions: {
1283
+ openEyeDropper(ctx2) {
1284
+ const isSupported = "EyeDropper" in dom.getWin(ctx2);
1285
+ if (!isSupported)
1286
+ return;
1287
+ const win = dom.getWin(ctx2);
1288
+ const picker = new win.EyeDropper();
1289
+ picker.open().then(({ sRGBHex }) => {
1290
+ const format = ctx2.value.getFormat();
1291
+ const color = parseColor(sRGBHex).toFormat(format);
1292
+ set.value(ctx2, color);
1293
+ ctx2.onValueChangeEnd?.({ value: ctx2.value, valueAsString: ctx2.valueAsString });
1294
+ }).catch(() => void 0);
1295
+ },
1296
+ setActiveChannel(ctx2, evt) {
1297
+ ctx2.activeId = evt.id;
1298
+ if (evt.channel)
1299
+ ctx2.activeChannel = evt.channel;
1300
+ if (evt.orientation)
1301
+ ctx2.activeOrientation = evt.orientation;
1302
+ },
1303
+ clearActiveChannel(ctx2) {
1304
+ ctx2.activeChannel = null;
1305
+ ctx2.activeId = null;
1306
+ ctx2.activeOrientation = null;
1307
+ },
1308
+ setAreaColorFromPoint(ctx2, evt) {
1309
+ const normalizedValue = evt.format ? ctx2.value.toFormat(evt.format) : ctx2.areaValue;
1310
+ const { xChannel, yChannel } = evt.channel || ctx2.activeChannel;
1311
+ const percent = dom.getAreaValueFromPoint(ctx2, evt.point);
1312
+ if (!percent)
1313
+ return;
1314
+ const xValue = normalizedValue.getChannelPercentValue(xChannel, percent.x);
1315
+ const yValue = normalizedValue.getChannelPercentValue(yChannel, 1 - percent.y);
1316
+ const color = normalizedValue.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue);
1317
+ set.value(ctx2, color);
1318
+ },
1319
+ setChannelColorFromPoint(ctx2, evt) {
1320
+ const channel = evt.channel || ctx2.activeId;
1321
+ const normalizedValue = evt.format ? ctx2.value.toFormat(evt.format) : ctx2.areaValue;
1322
+ const percent = dom.getChannelSliderValueFromPoint(ctx2, evt.point, channel);
1323
+ if (!percent)
1324
+ return;
1325
+ const orientation = ctx2.activeOrientation || "horizontal";
1326
+ const channelPercent = orientation === "horizontal" ? percent.x : percent.y;
1327
+ const value = normalizedValue.getChannelPercentValue(channel, channelPercent);
1328
+ const color = normalizedValue.withChannelValue(channel, value);
1329
+ set.value(ctx2, color);
1330
+ },
1331
+ setValue(ctx2, evt) {
1332
+ set.value(ctx2, evt.value);
1333
+ },
1334
+ setFormat(ctx2, evt) {
1335
+ set.format(ctx2, evt.format);
1336
+ },
1337
+ syncInputElements(ctx2) {
1338
+ sync.inputs(ctx2);
1339
+ },
1340
+ invokeOnChangeEnd(ctx2) {
1341
+ invoke.changeEnd(ctx2);
1342
+ },
1343
+ setChannelColorFromInput(ctx2, evt) {
1344
+ const { channel, isTextField, value } = evt;
1345
+ const currentAlpha = ctx2.value.getChannelValue("alpha");
1346
+ let color;
1347
+ if (channel === "alpha") {
1348
+ let valueAsNumber = Number.parseFloat(value);
1349
+ valueAsNumber = Number.isNaN(valueAsNumber) ? currentAlpha : valueAsNumber;
1350
+ color = ctx2.value.withChannelValue("alpha", valueAsNumber);
1351
+ } else if (isTextField) {
1352
+ color = tryCatch(
1353
+ () => parse(value).withChannelValue("alpha", currentAlpha),
1354
+ () => ctx2.value
1355
+ );
1356
+ } else {
1357
+ const current = ctx2.value.toFormat(ctx2.format);
1358
+ const valueAsNumber = Number.isNaN(value) ? current.getChannelValue(channel) : value;
1359
+ color = current.withChannelValue(channel, valueAsNumber);
1360
+ }
1361
+ sync.inputs(ctx2, color);
1362
+ set.value(ctx2, color);
1363
+ },
1364
+ incrementChannel(ctx2, evt) {
1365
+ const color = ctx2.value.incrementChannel(evt.channel, evt.step);
1366
+ set.value(ctx2, color);
1367
+ },
1368
+ decrementChannel(ctx2, evt) {
1369
+ const color = ctx2.value.decrementChannel(evt.channel, evt.step);
1370
+ set.value(ctx2, color);
1371
+ },
1372
+ incrementAreaXChannel(ctx2, evt) {
1373
+ const { xChannel } = evt.channel;
1374
+ const color = ctx2.areaValue.incrementChannel(xChannel, evt.step);
1375
+ set.value(ctx2, color);
1376
+ },
1377
+ decrementAreaXChannel(ctx2, evt) {
1378
+ const { xChannel } = evt.channel;
1379
+ const color = ctx2.areaValue.decrementChannel(xChannel, evt.step);
1380
+ set.value(ctx2, color);
1381
+ },
1382
+ incrementAreaYChannel(ctx2, evt) {
1383
+ const { yChannel } = evt.channel;
1384
+ const color = ctx2.areaValue.incrementChannel(yChannel, evt.step);
1385
+ set.value(ctx2, color);
1386
+ },
1387
+ decrementAreaYChannel(ctx2, evt) {
1388
+ const { yChannel } = evt.channel;
1389
+ const color = ctx2.areaValue.decrementChannel(yChannel, evt.step);
1390
+ set.value(ctx2, color);
1391
+ },
1392
+ setChannelToMax(ctx2, evt) {
1393
+ const range = ctx2.value.getChannelRange(evt.channel);
1394
+ const color = ctx2.value.withChannelValue(evt.channel, range.maxValue);
1395
+ set.value(ctx2, color);
1396
+ },
1397
+ setChannelToMin(ctx2, evt) {
1398
+ const range = ctx2.value.getChannelRange(evt.channel);
1399
+ const color = ctx2.value.withChannelValue(evt.channel, range.minValue);
1400
+ set.value(ctx2, color);
1401
+ },
1402
+ focusAreaThumb(ctx2) {
1403
+ raf(() => {
1404
+ dom.getAreaThumbEl(ctx2)?.focus({ preventScroll: true });
1405
+ });
1406
+ },
1407
+ focusChannelThumb(ctx2, evt) {
1408
+ raf(() => {
1409
+ dom.getChannelSliderThumbEl(ctx2, evt.channel)?.focus({ preventScroll: true });
1410
+ });
1411
+ },
1412
+ setInitialFocus(ctx2) {
1413
+ if (!ctx2.openAutoFocus)
1414
+ return;
1415
+ raf(() => {
1416
+ const element = getInitialFocus({
1417
+ root: dom.getContentEl(ctx2),
1418
+ getInitialEl: ctx2.initialFocusEl
1419
+ });
1420
+ element?.focus({ preventScroll: true });
1421
+ });
1422
+ },
1423
+ setReturnFocus(ctx2) {
1424
+ raf(() => {
1425
+ dom.getTriggerEl(ctx2)?.focus({ preventScroll: true });
1426
+ });
1427
+ },
1428
+ syncFormatSelectElement(ctx2) {
1429
+ sync.formatSelect(ctx2);
1430
+ },
1431
+ invokeOnOpen(ctx2) {
1432
+ ctx2.onOpenChange?.({ open: true });
1433
+ },
1434
+ invokeOnClose(ctx2) {
1435
+ ctx2.onOpenChange?.({ open: false });
1436
+ },
1437
+ toggleVisibility(ctx2, evt, { send }) {
1438
+ send({ type: ctx2.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt });
1439
+ }
1440
+ },
1441
+ compareFns: {
1442
+ value: (a, b) => a.isEqual(b)
1443
+ }
1444
+ }
1445
+ );
1446
+ }
1447
+
1448
+ export { anatomy, connect, machine, parse };