@zag-js/color-picker 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +46 -9
- package/dist/index.d.ts +46 -9
- package/dist/index.js +220 -89
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +220 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -14
- package/src/color-picker.anatomy.ts +3 -0
- package/src/color-picker.connect.ts +134 -28
- package/src/color-picker.dom.ts +3 -2
- package/src/color-picker.machine.ts +90 -49
- package/src/color-picker.parse.ts +2 -10
- package/src/color-picker.types.ts +48 -7
- package/src/index.ts +1 -0
- package/src/utils/get-channel-input-value.ts +5 -5
|
@@ -14,13 +14,22 @@ import type { NormalizeProps, PropTypes } from "@zag-js/types"
|
|
|
14
14
|
import { visuallyHiddenStyle } from "@zag-js/visually-hidden"
|
|
15
15
|
import { parts } from "./color-picker.anatomy"
|
|
16
16
|
import { dom } from "./color-picker.dom"
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
AreaProps,
|
|
19
|
+
ColorFormat,
|
|
20
|
+
MachineApi,
|
|
21
|
+
Send,
|
|
22
|
+
State,
|
|
23
|
+
SwatchTriggerProps,
|
|
24
|
+
SwatchTriggerState,
|
|
25
|
+
} from "./color-picker.types"
|
|
18
26
|
import { getChannelDisplayColor } from "./utils/get-channel-display-color"
|
|
19
27
|
import { getChannelRange, getChannelValue } from "./utils/get-channel-input-value"
|
|
20
28
|
import { getSliderBackground } from "./utils/get-slider-background"
|
|
21
29
|
|
|
22
30
|
export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>): MachineApi<T> {
|
|
23
31
|
const value = state.context.value
|
|
32
|
+
const areaValue = state.context.areaValue
|
|
24
33
|
const valueAsString = state.context.valueAsString
|
|
25
34
|
|
|
26
35
|
const isDisabled = state.context.isDisabled
|
|
@@ -28,9 +37,10 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
28
37
|
|
|
29
38
|
const isDragging = state.hasTag("dragging")
|
|
30
39
|
const isOpen = state.hasTag("open")
|
|
40
|
+
const isFocused = state.hasTag("focused")
|
|
31
41
|
|
|
32
42
|
const getAreaChannels = (props: AreaProps) => {
|
|
33
|
-
const channels =
|
|
43
|
+
const channels = areaValue.getChannels()
|
|
34
44
|
return {
|
|
35
45
|
xChannel: props.xChannel ?? channels[1],
|
|
36
46
|
yChannel: props.yChannel ?? channels[2],
|
|
@@ -43,11 +53,27 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
43
53
|
placement: currentPlacement,
|
|
44
54
|
})
|
|
45
55
|
|
|
56
|
+
function getSwatchTriggerState(props: SwatchTriggerProps): SwatchTriggerState {
|
|
57
|
+
const color = normalizeColor(props.value).toFormat(state.context.format)
|
|
58
|
+
return {
|
|
59
|
+
value: color,
|
|
60
|
+
valueAsString: color.toString("hex"),
|
|
61
|
+
isChecked: color.isEqual(value),
|
|
62
|
+
isDisabled: props.disabled || !isInteractive,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
return {
|
|
47
67
|
isDragging,
|
|
48
68
|
isOpen,
|
|
49
69
|
valueAsString,
|
|
50
70
|
value,
|
|
71
|
+
open() {
|
|
72
|
+
send({ type: "OPEN" })
|
|
73
|
+
},
|
|
74
|
+
close() {
|
|
75
|
+
send({ type: "CLOSE" })
|
|
76
|
+
},
|
|
51
77
|
setValue(value) {
|
|
52
78
|
send({ type: "VALUE.SET", value: normalizeColor(value), src: "set-color" })
|
|
53
79
|
},
|
|
@@ -58,7 +84,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
58
84
|
const color = value.withChannelValue(channel, channelValue)
|
|
59
85
|
send({ type: "VALUE.SET", value: color, src: "set-channel" })
|
|
60
86
|
},
|
|
61
|
-
format:
|
|
87
|
+
format: state.context.format,
|
|
62
88
|
setFormat(format) {
|
|
63
89
|
const formatValue = value.toFormat(format)
|
|
64
90
|
send({ type: "VALUE.SET", value: formatValue, src: "set-format" })
|
|
@@ -87,6 +113,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
87
113
|
htmlFor: dom.getHiddenInputId(state.context),
|
|
88
114
|
"data-disabled": dataAttr(isDisabled),
|
|
89
115
|
"data-readonly": dataAttr(state.context.readOnly),
|
|
116
|
+
"data-focus": dataAttr(isFocused),
|
|
90
117
|
onClick(event) {
|
|
91
118
|
event.preventDefault()
|
|
92
119
|
const inputEl = query(dom.getControlEl(state.context), "[data-channel=hex]")
|
|
@@ -100,21 +127,33 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
100
127
|
dir: state.context.dir,
|
|
101
128
|
"data-disabled": dataAttr(isDisabled),
|
|
102
129
|
"data-readonly": dataAttr(state.context.readOnly),
|
|
130
|
+
"data-state": isOpen ? "open" : "closed",
|
|
131
|
+
"data-focus": dataAttr(isFocused),
|
|
103
132
|
}),
|
|
104
133
|
|
|
105
134
|
triggerProps: normalize.button({
|
|
106
135
|
...parts.trigger.attrs,
|
|
107
136
|
id: dom.getTriggerId(state.context),
|
|
108
137
|
dir: state.context.dir,
|
|
138
|
+
disabled: isDisabled,
|
|
139
|
+
"aria-label": `select color. current color is ${valueAsString}`,
|
|
109
140
|
"aria-controls": dom.getContentId(state.context),
|
|
110
141
|
"aria-labelledby": dom.getLabelId(state.context),
|
|
111
142
|
"data-disabled": dataAttr(isDisabled),
|
|
112
143
|
"data-readonly": dataAttr(state.context.readOnly),
|
|
113
144
|
"data-placement": currentPlacement,
|
|
145
|
+
"aria-expanded": dataAttr(isOpen),
|
|
146
|
+
"data-state": isOpen ? "open" : "closed",
|
|
147
|
+
"data-focus": dataAttr(isFocused),
|
|
114
148
|
type: "button",
|
|
115
149
|
onClick() {
|
|
150
|
+
if (!isInteractive) return
|
|
116
151
|
send({ type: "TRIGGER.CLICK" })
|
|
117
152
|
},
|
|
153
|
+
onBlur() {
|
|
154
|
+
if (!isInteractive) return
|
|
155
|
+
send({ type: "TRIGGER.BLUR" })
|
|
156
|
+
},
|
|
118
157
|
style: {
|
|
119
158
|
position: "relative",
|
|
120
159
|
},
|
|
@@ -132,12 +171,13 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
132
171
|
id: dom.getContentId(state.context),
|
|
133
172
|
dir: state.context.dir,
|
|
134
173
|
"data-placement": currentPlacement,
|
|
174
|
+
"data-state": isOpen ? "open" : "closed",
|
|
135
175
|
hidden: !isOpen,
|
|
136
176
|
}),
|
|
137
177
|
|
|
138
178
|
getAreaProps(props = {}) {
|
|
139
179
|
const { xChannel, yChannel } = getAreaChannels(props)
|
|
140
|
-
const { areaStyles } = getColorAreaGradient(
|
|
180
|
+
const { areaStyles } = getColorAreaGradient(areaValue, {
|
|
141
181
|
xChannel,
|
|
142
182
|
yChannel,
|
|
143
183
|
dir: state.context.dir,
|
|
@@ -170,7 +210,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
170
210
|
|
|
171
211
|
getAreaBackgroundProps(props = {}) {
|
|
172
212
|
const { xChannel, yChannel } = getAreaChannels(props)
|
|
173
|
-
const { areaGradientStyles } = getColorAreaGradient(
|
|
213
|
+
const { areaGradientStyles } = getColorAreaGradient(areaValue, {
|
|
174
214
|
xChannel,
|
|
175
215
|
yChannel,
|
|
176
216
|
dir: state.context.dir,
|
|
@@ -192,8 +232,11 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
192
232
|
const { xChannel, yChannel } = getAreaChannels(props)
|
|
193
233
|
const channel = { xChannel, yChannel }
|
|
194
234
|
|
|
195
|
-
const
|
|
196
|
-
const
|
|
235
|
+
const xPercent = areaValue.getChannelValuePercent(xChannel)
|
|
236
|
+
const yPercent = 1 - areaValue.getChannelValuePercent(yChannel)
|
|
237
|
+
|
|
238
|
+
const xValue = areaValue.getChannelValue(xChannel)
|
|
239
|
+
const yValue = areaValue.getChannelValue(yChannel)
|
|
197
240
|
|
|
198
241
|
return normalize.element({
|
|
199
242
|
...parts.areaThumb.attrs,
|
|
@@ -201,17 +244,24 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
201
244
|
dir: state.context.dir,
|
|
202
245
|
tabIndex: isDisabled ? undefined : 0,
|
|
203
246
|
"data-disabled": dataAttr(isDisabled),
|
|
204
|
-
role: "
|
|
247
|
+
role: "slider",
|
|
248
|
+
"aria-valuemin": 0,
|
|
249
|
+
"aria-valuemax": 100,
|
|
250
|
+
"aria-valuenow": xValue,
|
|
251
|
+
"aria-label": `${xChannel} and ${yChannel}`,
|
|
252
|
+
"aria-roledescription": "2d slider",
|
|
253
|
+
"aria-valuetext": `${xChannel} ${xValue}, ${yChannel} ${yValue}`,
|
|
205
254
|
style: {
|
|
206
255
|
position: "absolute",
|
|
207
|
-
left: `${
|
|
208
|
-
top: `${
|
|
256
|
+
left: `${xPercent * 100}%`,
|
|
257
|
+
top: `${yPercent * 100}%`,
|
|
209
258
|
transform: "translate(-50%, -50%)",
|
|
210
259
|
touchAction: "none",
|
|
211
260
|
forcedColorAdjust: "none",
|
|
212
|
-
background:
|
|
261
|
+
background: areaValue.withChannelValue("alpha", 1).toString("css"),
|
|
213
262
|
},
|
|
214
263
|
onFocus() {
|
|
264
|
+
if (!isInteractive) return
|
|
215
265
|
send({ type: "AREA.FOCUS", id: "area", channel })
|
|
216
266
|
},
|
|
217
267
|
onKeyDown(event) {
|
|
@@ -313,7 +363,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
313
363
|
orientation,
|
|
314
364
|
channel,
|
|
315
365
|
dir: state.context.dir,
|
|
316
|
-
value:
|
|
366
|
+
value: areaValue,
|
|
317
367
|
}),
|
|
318
368
|
},
|
|
319
369
|
})
|
|
@@ -321,8 +371,8 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
321
371
|
|
|
322
372
|
getChannelSliderThumbProps(props) {
|
|
323
373
|
const { orientation = "horizontal", channel } = props
|
|
324
|
-
const { minValue, maxValue, step: stepValue } =
|
|
325
|
-
const channelValue =
|
|
374
|
+
const { minValue, maxValue, step: stepValue } = areaValue.getChannelRange(channel)
|
|
375
|
+
const channelValue = areaValue.getChannelValue(channel)
|
|
326
376
|
|
|
327
377
|
const offset = (channelValue - minValue) / (maxValue - minValue)
|
|
328
378
|
|
|
@@ -345,10 +395,11 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
345
395
|
"aria-valuemax": maxValue,
|
|
346
396
|
"aria-valuemin": minValue,
|
|
347
397
|
"aria-valuenow": channelValue,
|
|
398
|
+
"aria-valuetext": `${channel} ${channelValue}`,
|
|
348
399
|
style: {
|
|
349
400
|
forcedColorAdjust: "none",
|
|
350
401
|
position: "absolute",
|
|
351
|
-
background: getChannelDisplayColor(
|
|
402
|
+
background: getChannelDisplayColor(areaValue, channel).toString("css"),
|
|
352
403
|
...placementStyles,
|
|
353
404
|
},
|
|
354
405
|
onFocus() {
|
|
@@ -410,6 +461,8 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
410
461
|
type: isTextField ? "text" : "number",
|
|
411
462
|
"data-channel": channel,
|
|
412
463
|
"aria-label": channel,
|
|
464
|
+
spellCheck: false,
|
|
465
|
+
autoComplete: "off",
|
|
413
466
|
disabled: isDisabled,
|
|
414
467
|
"data-disabled": dataAttr(isDisabled),
|
|
415
468
|
readOnly: state.context.readOnly,
|
|
@@ -418,21 +471,24 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
418
471
|
max: range?.maxValue,
|
|
419
472
|
step: range?.step,
|
|
420
473
|
onBeforeInput(event) {
|
|
421
|
-
if (isTextField) return
|
|
474
|
+
if (isTextField || !isInteractive) return
|
|
422
475
|
const value = event.currentTarget.value
|
|
423
476
|
if (value.match(/[^0-9.]/g)) {
|
|
424
477
|
event.preventDefault()
|
|
425
478
|
}
|
|
426
479
|
},
|
|
427
480
|
onFocus(event) {
|
|
481
|
+
if (!isInteractive) return
|
|
428
482
|
send({ type: "CHANNEL_INPUT.FOCUS", channel })
|
|
429
483
|
event.target.select()
|
|
430
484
|
},
|
|
431
485
|
onBlur(event) {
|
|
486
|
+
if (!isInteractive) return
|
|
432
487
|
const value = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber
|
|
433
488
|
send({ type: "CHANNEL_INPUT.BLUR", channel, value, isTextField })
|
|
434
489
|
},
|
|
435
490
|
onKeyDown(event) {
|
|
491
|
+
if (!isInteractive) return
|
|
436
492
|
if (event.key === "Enter") {
|
|
437
493
|
const value = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber
|
|
438
494
|
send({ type: "CHANNEL_INPUT.CHANGE", channel, value, isTextField })
|
|
@@ -474,18 +530,22 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
474
530
|
role: "group",
|
|
475
531
|
}),
|
|
476
532
|
|
|
533
|
+
getSwatchTriggerState,
|
|
534
|
+
|
|
477
535
|
getSwatchTriggerProps(props) {
|
|
478
|
-
const
|
|
479
|
-
const color = normalizeColor(valueProp).toFormat(value.getFormat())
|
|
536
|
+
const triggerState = getSwatchTriggerState(props)
|
|
480
537
|
return normalize.button({
|
|
481
538
|
...parts.swatchTrigger.attrs,
|
|
482
|
-
disabled: isDisabled,
|
|
539
|
+
disabled: triggerState.isDisabled,
|
|
483
540
|
dir: state.context.dir,
|
|
484
541
|
type: "button",
|
|
485
|
-
"
|
|
542
|
+
"aria-label": `select ${triggerState.valueAsString} as the color`,
|
|
543
|
+
"data-state": triggerState.isChecked ? "checked" : "unchecked",
|
|
544
|
+
"data-value": triggerState.valueAsString,
|
|
545
|
+
"data-disabled": dataAttr(triggerState.isDisabled),
|
|
486
546
|
onClick() {
|
|
487
|
-
if (
|
|
488
|
-
send({ type: "
|
|
547
|
+
if (triggerState.isDisabled) return
|
|
548
|
+
send({ type: "SWATCH_TRIGGER.CLICK", value: triggerState.value })
|
|
489
549
|
},
|
|
490
550
|
style: {
|
|
491
551
|
position: "relative",
|
|
@@ -493,20 +553,66 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
|
|
|
493
553
|
})
|
|
494
554
|
},
|
|
495
555
|
|
|
556
|
+
getSwatchIndicatorProps(props) {
|
|
557
|
+
const triggerState = getSwatchTriggerState(props)
|
|
558
|
+
return normalize.element({
|
|
559
|
+
...parts.swatchIndicator.attrs,
|
|
560
|
+
dir: state.context.dir,
|
|
561
|
+
"aria-hidden": true,
|
|
562
|
+
"data-state": triggerState.isChecked ? "open" : "closed",
|
|
563
|
+
})
|
|
564
|
+
},
|
|
565
|
+
|
|
496
566
|
getSwatchProps(props) {
|
|
497
|
-
const {
|
|
498
|
-
const
|
|
499
|
-
const color = colorValue.toFormat(value.getFormat())
|
|
567
|
+
const { respectAlpha = true } = props
|
|
568
|
+
const triggerState = getSwatchTriggerState(props)
|
|
500
569
|
return normalize.element({
|
|
501
570
|
...parts.swatch.attrs,
|
|
502
571
|
dir: state.context.dir,
|
|
503
|
-
"data-state":
|
|
504
|
-
"data-value":
|
|
572
|
+
"data-state": triggerState.isChecked ? "checked" : "unchecked",
|
|
573
|
+
"data-value": triggerState.valueAsString,
|
|
505
574
|
style: {
|
|
506
575
|
position: "relative",
|
|
507
|
-
background:
|
|
576
|
+
background: triggerState.value.toString(respectAlpha ? "css" : "hex"),
|
|
508
577
|
},
|
|
509
578
|
})
|
|
510
579
|
},
|
|
580
|
+
|
|
581
|
+
formatTriggerProps: normalize.button({
|
|
582
|
+
...parts.formatTrigger.attrs,
|
|
583
|
+
dir: state.context.dir,
|
|
584
|
+
type: "button",
|
|
585
|
+
"aria-label": `change color format to ${getNextFormat(state.context.format)}`,
|
|
586
|
+
onClick(event) {
|
|
587
|
+
if (event.currentTarget.disabled) return
|
|
588
|
+
const nextFormat = getNextFormat(state.context.format)
|
|
589
|
+
send({ type: "FORMAT.SET", format: nextFormat, src: "format-trigger" })
|
|
590
|
+
},
|
|
591
|
+
}),
|
|
592
|
+
|
|
593
|
+
formatSelectProps: normalize.select({
|
|
594
|
+
...parts.formatSelect.attrs,
|
|
595
|
+
"aria-label": "change color format",
|
|
596
|
+
dir: state.context.dir,
|
|
597
|
+
defaultValue: state.context.format,
|
|
598
|
+
disabled: isDisabled,
|
|
599
|
+
onChange(event) {
|
|
600
|
+
const format = assertFormat(event.currentTarget.value)
|
|
601
|
+
send({ type: "FORMAT.SET", format, src: "format-select" })
|
|
602
|
+
},
|
|
603
|
+
}),
|
|
511
604
|
}
|
|
512
605
|
}
|
|
606
|
+
|
|
607
|
+
const formats: ColorFormat[] = ["hsba", "hsla", "rgba"]
|
|
608
|
+
const formatRegex = new RegExp(`^(${formats.join("|")})$`)
|
|
609
|
+
|
|
610
|
+
function getNextFormat(format: ColorFormat) {
|
|
611
|
+
const index = formats.indexOf(format)
|
|
612
|
+
return formats[index + 1] ?? formats[0]
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function assertFormat(format: string) {
|
|
616
|
+
if (formatRegex.test(format)) return format as ColorFormat
|
|
617
|
+
throw new Error(`Unsupported color format: ${format}`)
|
|
618
|
+
}
|
package/src/color-picker.dom.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const dom = createScope({
|
|
|
13
13
|
getTriggerId: (ctx: Ctx) => ctx.ids?.trigger ?? `color-picker:${ctx.id}:trigger`,
|
|
14
14
|
getContentId: (ctx: Ctx) => ctx.ids?.content ?? `color-picker:${ctx.id}:content`,
|
|
15
15
|
getPositionerId: (ctx: Ctx) => `color-picker:${ctx.id}:positioner`,
|
|
16
|
+
getFormatSelectId: (ctx: Ctx) => `color-picker:${ctx.id}:format-select`,
|
|
16
17
|
|
|
17
18
|
getAreaId: (ctx: Ctx) => ctx.ids?.area ?? `color-picker:${ctx.id}:area`,
|
|
18
19
|
getAreaGradientId: (ctx: Ctx) => ctx.ids?.areaGradient ?? `color-picker:${ctx.id}:area-gradient`,
|
|
@@ -33,6 +34,7 @@ export const dom = createScope({
|
|
|
33
34
|
...queryAll<HTMLInputElement>(dom.getControlEl(ctx), `input[data-channel="${channel}"]`),
|
|
34
35
|
]
|
|
35
36
|
},
|
|
37
|
+
getFormatSelectEl: (ctx: Ctx) => dom.getById<HTMLSelectElement>(ctx, dom.getFormatSelectId(ctx)),
|
|
36
38
|
|
|
37
39
|
getHiddenInputEl: (ctx: Ctx) => dom.getById<HTMLInputElement>(ctx, dom.getHiddenInputId(ctx)),
|
|
38
40
|
getAreaEl: (ctx: Ctx) => dom.getById(ctx, dom.getAreaId(ctx)),
|
|
@@ -64,8 +66,7 @@ export const dom = createScope({
|
|
|
64
66
|
getFirstFocusableEl: (ctx: Ctx) => getFirstFocusable(dom.getContentEl(ctx), "if-empty"),
|
|
65
67
|
getInitialFocusEl: (ctx: Ctx): HTMLElement | undefined => {
|
|
66
68
|
let el: any = runIfFn(ctx.initialFocusEl)
|
|
67
|
-
|
|
68
|
-
if (!el) el = dom.getContentEl(ctx)
|
|
69
|
+
el = dom.getFirstFocusableEl(ctx) ?? dom.getContentEl(ctx)
|
|
69
70
|
return el
|
|
70
71
|
},
|
|
71
72
|
})
|
|
@@ -10,6 +10,7 @@ import { compact, tryCatch } from "@zag-js/utils"
|
|
|
10
10
|
import { dom } from "./color-picker.dom"
|
|
11
11
|
import { parse } from "./color-picker.parse"
|
|
12
12
|
import type {
|
|
13
|
+
ColorFormat,
|
|
13
14
|
ColorType,
|
|
14
15
|
ExtendedColorChannel,
|
|
15
16
|
MachineContext,
|
|
@@ -27,13 +28,14 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
27
28
|
context: {
|
|
28
29
|
dir: "ltr",
|
|
29
30
|
value: parse("#000000"),
|
|
31
|
+
format: "rgba",
|
|
30
32
|
disabled: false,
|
|
33
|
+
closeOnSelect: false,
|
|
31
34
|
...ctx,
|
|
32
35
|
activeId: null,
|
|
33
36
|
activeChannel: null,
|
|
34
37
|
activeOrientation: null,
|
|
35
38
|
fieldsetDisabled: false,
|
|
36
|
-
autoFocus: true,
|
|
37
39
|
positioning: {
|
|
38
40
|
...ctx.positioning,
|
|
39
41
|
placement: "bottom",
|
|
@@ -44,9 +46,10 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
44
46
|
isRtl: (ctx) => ctx.dir === "rtl",
|
|
45
47
|
isDisabled: (ctx) => !!ctx.disabled || ctx.fieldsetDisabled,
|
|
46
48
|
isInteractive: (ctx) => !(ctx.isDisabled || ctx.readOnly),
|
|
47
|
-
valueAsString: (ctx) =>
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
valueAsString: (ctx) => ctx.value.toString(ctx.format),
|
|
50
|
+
areaValue: (ctx) => {
|
|
51
|
+
const format = ctx.format.startsWith("hsl") ? "hsla" : "hsba"
|
|
52
|
+
return ctx.value.toFormat(format)
|
|
50
53
|
},
|
|
51
54
|
},
|
|
52
55
|
|
|
@@ -54,6 +57,7 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
54
57
|
|
|
55
58
|
watch: {
|
|
56
59
|
value: ["syncInputElements"],
|
|
60
|
+
format: ["syncFormatSelectElement"],
|
|
57
61
|
open: ["toggleVisibility"],
|
|
58
62
|
},
|
|
59
63
|
|
|
@@ -61,9 +65,15 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
61
65
|
"VALUE.SET": {
|
|
62
66
|
actions: ["setValue"],
|
|
63
67
|
},
|
|
68
|
+
"FORMAT.SET": {
|
|
69
|
+
actions: ["setFormat"],
|
|
70
|
+
},
|
|
64
71
|
"CHANNEL_INPUT.CHANGE": {
|
|
65
72
|
actions: ["setChannelColorFromInput"],
|
|
66
73
|
},
|
|
74
|
+
"EYEDROPPER.CLICK": {
|
|
75
|
+
actions: ["openEyeDropper"],
|
|
76
|
+
},
|
|
67
77
|
},
|
|
68
78
|
|
|
69
79
|
states: {
|
|
@@ -103,6 +113,9 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
103
113
|
target: "idle",
|
|
104
114
|
actions: ["setChannelColorFromInput"],
|
|
105
115
|
},
|
|
116
|
+
"TRIGGER.BLUR": {
|
|
117
|
+
target: "idle",
|
|
118
|
+
},
|
|
106
119
|
},
|
|
107
120
|
},
|
|
108
121
|
|
|
@@ -114,9 +127,6 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
114
127
|
target: "idle",
|
|
115
128
|
actions: ["invokeOnClose"],
|
|
116
129
|
},
|
|
117
|
-
"EYEDROPPER.CLICK": {
|
|
118
|
-
actions: ["openEyeDropper"],
|
|
119
|
-
},
|
|
120
130
|
"AREA.POINTER_DOWN": {
|
|
121
131
|
target: "open:dragging",
|
|
122
132
|
actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"],
|
|
@@ -191,6 +201,16 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
191
201
|
target: "idle",
|
|
192
202
|
actions: ["invokeOnClose"],
|
|
193
203
|
},
|
|
204
|
+
"SWATCH_TRIGGER.CLICK": [
|
|
205
|
+
{
|
|
206
|
+
guard: "closeOnSelect",
|
|
207
|
+
target: "focused",
|
|
208
|
+
actions: ["setValue", "setReturnFocus", "invokeOnClose"],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
actions: ["setValue"],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
194
214
|
},
|
|
195
215
|
},
|
|
196
216
|
|
|
@@ -235,6 +255,7 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
235
255
|
{
|
|
236
256
|
guards: {
|
|
237
257
|
isTargetFocusable: (_ctx, evt) => evt.restoreFocus,
|
|
258
|
+
closeOnSelect: (ctx) => !!ctx.closeOnSelect,
|
|
238
259
|
},
|
|
239
260
|
activities: {
|
|
240
261
|
trackPositioning(ctx) {
|
|
@@ -329,10 +350,10 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
329
350
|
const percent = dom.getAreaValueFromPoint(ctx, evt.point)
|
|
330
351
|
if (!percent) return
|
|
331
352
|
|
|
332
|
-
const xValue = ctx.
|
|
333
|
-
const yValue = ctx.
|
|
353
|
+
const xValue = ctx.areaValue.getChannelPercentValue(xChannel, percent.x)
|
|
354
|
+
const yValue = ctx.areaValue.getChannelPercentValue(yChannel, 1 - percent.y)
|
|
334
355
|
|
|
335
|
-
const color = ctx.
|
|
356
|
+
const color = ctx.areaValue.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue)
|
|
336
357
|
set.value(ctx, color)
|
|
337
358
|
},
|
|
338
359
|
setChannelColorFromPoint(ctx, evt) {
|
|
@@ -344,13 +365,16 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
344
365
|
const orientation = ctx.activeOrientation || "horizontal"
|
|
345
366
|
const channelPercent = orientation === "horizontal" ? percent.x : percent.y
|
|
346
367
|
|
|
347
|
-
const value = ctx.
|
|
348
|
-
const color = ctx.
|
|
368
|
+
const value = ctx.areaValue.getChannelPercentValue(channel, channelPercent)
|
|
369
|
+
const color = ctx.areaValue.withChannelValue(channel, value)
|
|
349
370
|
set.value(ctx, color)
|
|
350
371
|
},
|
|
351
372
|
setValue(ctx, evt) {
|
|
352
373
|
set.value(ctx, evt.value)
|
|
353
374
|
},
|
|
375
|
+
setFormat(ctx, evt) {
|
|
376
|
+
set.format(ctx, evt.format)
|
|
377
|
+
},
|
|
354
378
|
syncInputElements(ctx) {
|
|
355
379
|
sync.inputs(ctx)
|
|
356
380
|
},
|
|
@@ -361,34 +385,35 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
361
385
|
const { channel, isTextField, value } = evt
|
|
362
386
|
const currentAlpha = ctx.value.getChannelValue("alpha")
|
|
363
387
|
|
|
388
|
+
// handle other text channels
|
|
389
|
+
let color: Color
|
|
390
|
+
|
|
364
391
|
// handle alpha channel
|
|
365
392
|
if (channel === "alpha") {
|
|
393
|
+
//
|
|
366
394
|
let valueAsNumber = parseFloat(value)
|
|
367
395
|
valueAsNumber = Number.isNaN(valueAsNumber) ? currentAlpha : valueAsNumber
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
// handle other text channels
|
|
374
|
-
if (isTextField) {
|
|
375
|
-
const color = tryCatch(
|
|
396
|
+
color = ctx.value.withChannelValue("alpha", valueAsNumber)
|
|
397
|
+
//
|
|
398
|
+
} else if (isTextField) {
|
|
399
|
+
//
|
|
400
|
+
color = tryCatch(
|
|
376
401
|
() => parse(value).withChannelValue("alpha", currentAlpha),
|
|
377
402
|
() => ctx.value,
|
|
378
403
|
)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
set.value(ctx, color)
|
|
387
|
-
return
|
|
404
|
+
//
|
|
405
|
+
} else {
|
|
406
|
+
//
|
|
407
|
+
const current = ctx.value.toFormat(ctx.format)
|
|
408
|
+
const valueAsNumber = Number.isNaN(value) ? current.getChannelValue(channel) : value
|
|
409
|
+
color = current.withChannelValue(channel, valueAsNumber)
|
|
410
|
+
//
|
|
388
411
|
}
|
|
389
412
|
|
|
390
|
-
//
|
|
391
|
-
|
|
413
|
+
// sync channel input value immediately (in event user types native css color, we need to convert it to the current channel format)
|
|
414
|
+
sync.inputs(ctx, color)
|
|
415
|
+
|
|
416
|
+
// set new color
|
|
392
417
|
set.value(ctx, color)
|
|
393
418
|
},
|
|
394
419
|
incrementChannel(ctx, evt) {
|
|
@@ -401,22 +426,22 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
401
426
|
},
|
|
402
427
|
incrementXChannel(ctx, evt) {
|
|
403
428
|
const { xChannel } = evt.channel
|
|
404
|
-
const color = ctx.
|
|
429
|
+
const color = ctx.areaValue.incrementChannel(xChannel, evt.step)
|
|
405
430
|
set.value(ctx, color)
|
|
406
431
|
},
|
|
407
432
|
decrementXChannel(ctx, evt) {
|
|
408
433
|
const { xChannel } = evt.channel
|
|
409
|
-
const color = ctx.
|
|
434
|
+
const color = ctx.areaValue.decrementChannel(xChannel, evt.step)
|
|
410
435
|
set.value(ctx, color)
|
|
411
436
|
},
|
|
412
437
|
incrementYChannel(ctx, evt) {
|
|
413
438
|
const { yChannel } = evt.channel
|
|
414
|
-
const color = ctx.
|
|
439
|
+
const color = ctx.areaValue.incrementChannel(yChannel, evt.step)
|
|
415
440
|
set.value(ctx, color)
|
|
416
441
|
},
|
|
417
442
|
decrementYChannel(ctx, evt) {
|
|
418
443
|
const { yChannel } = evt.channel
|
|
419
|
-
const color = ctx.
|
|
444
|
+
const color = ctx.areaValue.decrementChannel(yChannel, evt.step)
|
|
420
445
|
set.value(ctx, color)
|
|
421
446
|
},
|
|
422
447
|
setChannelToMax(ctx, evt) {
|
|
@@ -449,6 +474,9 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
449
474
|
dom?.focus(ctx, dom.getTriggerEl(ctx))
|
|
450
475
|
})
|
|
451
476
|
},
|
|
477
|
+
syncFormatSelectElement(ctx) {
|
|
478
|
+
sync.formatSelect(ctx)
|
|
479
|
+
},
|
|
452
480
|
invokeOnOpen(ctx) {
|
|
453
481
|
ctx.onOpenChange?.({ open: true })
|
|
454
482
|
},
|
|
@@ -467,43 +495,56 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
467
495
|
}
|
|
468
496
|
|
|
469
497
|
const sync = {
|
|
470
|
-
|
|
471
|
-
|
|
498
|
+
// sync channel inputs
|
|
499
|
+
inputs(ctx: MachineContext, color?: Color) {
|
|
472
500
|
const channelInputs = dom.getChannelInputEls(ctx)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
501
|
+
raf(() => {
|
|
502
|
+
channelInputs.forEach((inputEl) => {
|
|
503
|
+
const channel = inputEl.dataset.channel as ExtendedColorChannel | null
|
|
504
|
+
dom.setValue(inputEl, getChannelValue(color || ctx.value, channel))
|
|
505
|
+
})
|
|
506
|
+
})
|
|
507
|
+
},
|
|
508
|
+
// sync format select
|
|
509
|
+
formatSelect(ctx: MachineContext) {
|
|
510
|
+
const selectEl = dom.getFormatSelectEl(ctx)
|
|
511
|
+
raf(() => {
|
|
512
|
+
dom.setValue(selectEl, ctx.format)
|
|
476
513
|
})
|
|
477
514
|
},
|
|
478
515
|
}
|
|
479
516
|
|
|
480
517
|
const invoke = {
|
|
481
518
|
changeEnd(ctx: MachineContext) {
|
|
519
|
+
const value = ctx.value.toFormat(ctx.format)
|
|
482
520
|
ctx.onValueChangeEnd?.({
|
|
483
|
-
value
|
|
521
|
+
value,
|
|
484
522
|
valueAsString: ctx.valueAsString,
|
|
485
523
|
})
|
|
486
524
|
},
|
|
487
525
|
change(ctx: MachineContext) {
|
|
526
|
+
const value = ctx.value.toFormat(ctx.format)
|
|
488
527
|
ctx.onValueChange?.({
|
|
489
|
-
value
|
|
528
|
+
value,
|
|
490
529
|
valueAsString: ctx.valueAsString,
|
|
491
530
|
})
|
|
492
531
|
|
|
493
532
|
dispatchInputValueEvent(dom.getHiddenInputEl(ctx), { value: ctx.valueAsString })
|
|
494
533
|
},
|
|
534
|
+
formatChange(ctx: MachineContext) {
|
|
535
|
+
ctx.onFormatChange?.({ format: ctx.format })
|
|
536
|
+
},
|
|
495
537
|
}
|
|
496
538
|
|
|
497
539
|
const set = {
|
|
498
540
|
value(ctx: MachineContext, color: Color | ColorType | undefined) {
|
|
499
541
|
if (!color || ctx.value.isEqual(color)) return
|
|
500
|
-
|
|
501
|
-
const outputColor = color.toFormat(currentFormat)
|
|
502
|
-
try {
|
|
503
|
-
outputColor.outputFormat = ctx.value.outputFormat
|
|
504
|
-
} catch {}
|
|
505
|
-
|
|
506
|
-
ctx.value = outputColor
|
|
542
|
+
ctx.value = color
|
|
507
543
|
invoke.change(ctx)
|
|
508
544
|
},
|
|
545
|
+
format(ctx: MachineContext, format: ColorFormat) {
|
|
546
|
+
if (ctx.format === format) return
|
|
547
|
+
ctx.format = format
|
|
548
|
+
invoke.formatChange(ctx)
|
|
549
|
+
},
|
|
509
550
|
}
|