@zag-js/color-picker 0.23.0 → 0.25.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 +101 -32
- package/dist/index.d.ts +101 -32
- package/dist/index.js +516 -300
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +515 -303
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -12
- package/src/color-picker.anatomy.ts +11 -5
- package/src/color-picker.connect.ts +206 -124
- package/src/color-picker.dom.ts +26 -4
- package/src/color-picker.machine.ts +240 -128
- package/src/color-picker.parse.ts +14 -0
- package/src/color-picker.types.ts +110 -30
- package/src/index.ts +9 -6
- package/src/utils/get-channel-input-value.ts +45 -11
- package/src/utils/get-slider-background.ts +14 -12
- package/src/utils/get-channel-details.ts +0 -60
|
@@ -1,101 +1,147 @@
|
|
|
1
1
|
import { parseColor, type Color } from "@zag-js/color-utils"
|
|
2
|
-
import { createMachine } from "@zag-js/core"
|
|
2
|
+
import { createMachine, guards, ref } from "@zag-js/core"
|
|
3
|
+
import { trackDismissableElement } from "@zag-js/dismissable"
|
|
3
4
|
import { trackPointerMove } from "@zag-js/dom-event"
|
|
4
5
|
import { raf } from "@zag-js/dom-query"
|
|
5
|
-
import { trackFormControl } from "@zag-js/form-utils"
|
|
6
|
-
import {
|
|
6
|
+
import { dispatchInputValueEvent, trackFormControl } from "@zag-js/form-utils"
|
|
7
|
+
import { getPlacement } from "@zag-js/popper"
|
|
7
8
|
import { disableTextSelection } from "@zag-js/text-selection"
|
|
8
9
|
import { compact, tryCatch } from "@zag-js/utils"
|
|
9
10
|
import { dom } from "./color-picker.dom"
|
|
11
|
+
import { parse } from "./color-picker.parse"
|
|
10
12
|
import type {
|
|
11
|
-
ColorFormat,
|
|
12
13
|
ColorType,
|
|
13
14
|
ExtendedColorChannel,
|
|
14
15
|
MachineContext,
|
|
15
16
|
MachineState,
|
|
16
17
|
UserDefinedContext,
|
|
17
18
|
} from "./color-picker.types"
|
|
18
|
-
import {
|
|
19
|
-
|
|
19
|
+
import { getChannelValue } from "./utils/get-channel-input-value"
|
|
20
|
+
|
|
21
|
+
const { stateIn } = guards
|
|
20
22
|
|
|
21
23
|
export function machine(userContext: UserDefinedContext) {
|
|
22
24
|
const ctx = compact(userContext)
|
|
23
25
|
return createMachine<MachineContext, MachineState>(
|
|
24
26
|
{
|
|
25
27
|
id: "color-picker",
|
|
26
|
-
initial: "idle",
|
|
28
|
+
initial: ctx.open ? "open" : "idle",
|
|
27
29
|
context: {
|
|
28
30
|
dir: "ltr",
|
|
29
|
-
value: "#
|
|
31
|
+
value: parse("#000000"),
|
|
30
32
|
disabled: false,
|
|
31
33
|
...ctx,
|
|
32
34
|
activeId: null,
|
|
33
35
|
activeChannel: null,
|
|
34
36
|
activeOrientation: null,
|
|
35
37
|
fieldsetDisabled: false,
|
|
38
|
+
autoFocus: true,
|
|
39
|
+
positioning: {
|
|
40
|
+
...ctx.positioning,
|
|
41
|
+
placement: "bottom",
|
|
42
|
+
},
|
|
36
43
|
},
|
|
37
44
|
|
|
38
45
|
computed: {
|
|
39
46
|
isRtl: (ctx) => ctx.dir === "rtl",
|
|
40
47
|
isDisabled: (ctx) => !!ctx.disabled || ctx.fieldsetDisabled,
|
|
41
48
|
isInteractive: (ctx) => !(ctx.isDisabled || ctx.readOnly),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
on: {
|
|
46
|
-
"VALUE.SET": {
|
|
47
|
-
actions: ["setValue"],
|
|
49
|
+
valueAsString: (ctx) => {
|
|
50
|
+
const color = ctx.value.outputFormat ? ctx.value.toFormat(ctx.value.outputFormat) : ctx.value
|
|
51
|
+
return color.toString("css")
|
|
48
52
|
},
|
|
49
53
|
},
|
|
50
54
|
|
|
51
55
|
activities: ["trackFormControl"],
|
|
52
56
|
|
|
53
57
|
watch: {
|
|
54
|
-
|
|
58
|
+
valueAsString: ["syncInputElements"],
|
|
59
|
+
open: ["toggleVisibility"],
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
on: {
|
|
63
|
+
"VALUE.SET": {
|
|
64
|
+
actions: ["setValue"],
|
|
65
|
+
},
|
|
66
|
+
"CHANNEL_INPUT.FOCUS": [
|
|
67
|
+
{
|
|
68
|
+
guard: stateIn("idle"),
|
|
69
|
+
target: "focused",
|
|
70
|
+
actions: ["setActiveChannel"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
actions: ["setActiveChannel"],
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
"CHANNEL_INPUT.BLUR": [
|
|
77
|
+
{
|
|
78
|
+
guard: stateIn("focused"),
|
|
79
|
+
target: "idle",
|
|
80
|
+
actions: ["setChannelColorFromInput"],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
actions: ["setChannelColorFromInput"],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
"CHANNEL_INPUT.CHANGE": {
|
|
87
|
+
actions: ["setChannelColorFromInput"],
|
|
88
|
+
},
|
|
55
89
|
},
|
|
56
90
|
|
|
57
91
|
states: {
|
|
58
92
|
idle: {
|
|
93
|
+
tags: ["closed"],
|
|
59
94
|
on: {
|
|
95
|
+
OPEN: {
|
|
96
|
+
target: "open",
|
|
97
|
+
actions: ["setInitialFocus", "invokeOnOpen"],
|
|
98
|
+
},
|
|
99
|
+
"TRIGGER.CLICK": {
|
|
100
|
+
target: "open",
|
|
101
|
+
actions: ["setInitialFocus", "invokeOnOpen"],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
focused: {
|
|
107
|
+
tags: ["closed", "focused"],
|
|
108
|
+
on: {
|
|
109
|
+
OPEN: {
|
|
110
|
+
target: "open",
|
|
111
|
+
actions: ["setInitialFocus", "invokeOnOpen"],
|
|
112
|
+
},
|
|
113
|
+
"TRIGGER.CLICK": {
|
|
114
|
+
target: "open",
|
|
115
|
+
actions: ["setInitialFocus", "invokeOnOpen"],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
open: {
|
|
121
|
+
tags: ["open"],
|
|
122
|
+
activities: ["trackPositioning", "trackDismissableElement"],
|
|
123
|
+
on: {
|
|
124
|
+
"TRIGGER.CLICK": {
|
|
125
|
+
target: "idle",
|
|
126
|
+
actions: ["invokeOnClose"],
|
|
127
|
+
},
|
|
60
128
|
"EYEDROPPER.CLICK": {
|
|
61
129
|
actions: ["openEyeDropper"],
|
|
62
130
|
},
|
|
63
131
|
"AREA.POINTER_DOWN": {
|
|
64
|
-
target: "dragging",
|
|
132
|
+
target: "open:dragging",
|
|
65
133
|
actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"],
|
|
66
134
|
},
|
|
67
135
|
"AREA.FOCUS": {
|
|
68
|
-
target: "focused",
|
|
69
136
|
actions: ["setActiveChannel"],
|
|
70
137
|
},
|
|
71
138
|
"CHANNEL_SLIDER.POINTER_DOWN": {
|
|
72
|
-
target: "dragging",
|
|
139
|
+
target: "open:dragging",
|
|
73
140
|
actions: ["setActiveChannel", "setChannelColorFromPoint", "focusChannelThumb"],
|
|
74
141
|
},
|
|
75
142
|
"CHANNEL_SLIDER.FOCUS": {
|
|
76
|
-
target: "focused",
|
|
77
|
-
actions: ["setActiveChannel"],
|
|
78
|
-
},
|
|
79
|
-
"CHANNEL_INPUT.FOCUS": {
|
|
80
|
-
target: "focused",
|
|
81
143
|
actions: ["setActiveChannel"],
|
|
82
144
|
},
|
|
83
|
-
"CHANNEL_INPUT.CHANGE": {
|
|
84
|
-
actions: ["setChannelColorFromInput"],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
focused: {
|
|
90
|
-
on: {
|
|
91
|
-
"AREA.POINTER_DOWN": {
|
|
92
|
-
target: "dragging",
|
|
93
|
-
actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"],
|
|
94
|
-
},
|
|
95
|
-
"CHANNEL_SLIDER.POINTER_DOWN": {
|
|
96
|
-
target: "dragging",
|
|
97
|
-
actions: ["setActiveChannel", "setChannelColorFromPoint", "focusChannelThumb"],
|
|
98
|
-
},
|
|
99
145
|
"AREA.ARROW_LEFT": {
|
|
100
146
|
actions: ["decrementXChannel"],
|
|
101
147
|
},
|
|
@@ -138,45 +184,100 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
138
184
|
"CHANNEL_SLIDER.END": {
|
|
139
185
|
actions: ["setChannelToMax"],
|
|
140
186
|
},
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
187
|
+
INTERACT_OUTSIDE: [
|
|
188
|
+
{
|
|
189
|
+
guard: "shouldRestoreFocus",
|
|
190
|
+
target: "focused",
|
|
191
|
+
actions: ["setReturnFocus", "invokeOnClose"],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
target: "idle",
|
|
195
|
+
actions: ["invokeOnClose"],
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
CLOSE: {
|
|
151
199
|
target: "idle",
|
|
200
|
+
actions: ["invokeOnClose"],
|
|
152
201
|
},
|
|
153
202
|
},
|
|
154
203
|
},
|
|
155
204
|
|
|
156
|
-
dragging: {
|
|
205
|
+
"open:dragging": {
|
|
206
|
+
tags: ["open"],
|
|
157
207
|
exit: ["clearActiveChannel"],
|
|
158
|
-
activities: ["trackPointerMove", "disableTextSelection"],
|
|
208
|
+
activities: ["trackPointerMove", "disableTextSelection", "trackPositioning", "trackDismissableElement"],
|
|
159
209
|
on: {
|
|
160
210
|
"AREA.POINTER_MOVE": {
|
|
161
|
-
actions: ["setAreaColorFromPoint"],
|
|
211
|
+
actions: ["setAreaColorFromPoint", "focusAreaThumb"],
|
|
162
212
|
},
|
|
163
213
|
"AREA.POINTER_UP": {
|
|
164
|
-
target: "
|
|
214
|
+
target: "open",
|
|
165
215
|
actions: ["invokeOnChangeEnd"],
|
|
166
216
|
},
|
|
167
217
|
"CHANNEL_SLIDER.POINTER_MOVE": {
|
|
168
|
-
actions: ["setChannelColorFromPoint"],
|
|
218
|
+
actions: ["setChannelColorFromPoint", "focusChannelThumb"],
|
|
169
219
|
},
|
|
170
220
|
"CHANNEL_SLIDER.POINTER_UP": {
|
|
171
|
-
target: "
|
|
221
|
+
target: "open",
|
|
172
222
|
actions: ["invokeOnChangeEnd"],
|
|
173
223
|
},
|
|
224
|
+
INTERACT_OUTSIDE: [
|
|
225
|
+
{
|
|
226
|
+
guard: "shouldRestoreFocus",
|
|
227
|
+
target: "focused",
|
|
228
|
+
actions: ["setReturnFocus", "invokeOnClose"],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
target: "idle",
|
|
232
|
+
actions: ["invokeOnClose"],
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
CLOSE: {
|
|
236
|
+
target: "idle",
|
|
237
|
+
actions: ["invokeOnClose"],
|
|
238
|
+
},
|
|
174
239
|
},
|
|
175
240
|
},
|
|
176
241
|
},
|
|
177
242
|
},
|
|
178
243
|
{
|
|
244
|
+
guards: {
|
|
245
|
+
isTargetFocusable: (_ctx, evt) => evt.restoreFocus,
|
|
246
|
+
},
|
|
179
247
|
activities: {
|
|
248
|
+
trackPositioning(ctx) {
|
|
249
|
+
ctx.currentPlacement = ctx.positioning.placement
|
|
250
|
+
const anchorEl = dom.getTriggerEl(ctx)
|
|
251
|
+
const getPositionerEl = () => dom.getPositionerEl(ctx)
|
|
252
|
+
return getPlacement(anchorEl, getPositionerEl, {
|
|
253
|
+
...ctx.positioning,
|
|
254
|
+
defer: true,
|
|
255
|
+
onComplete(data) {
|
|
256
|
+
ctx.currentPlacement = data.placement
|
|
257
|
+
},
|
|
258
|
+
onCleanup() {
|
|
259
|
+
ctx.currentPlacement = undefined
|
|
260
|
+
},
|
|
261
|
+
})
|
|
262
|
+
},
|
|
263
|
+
trackDismissableElement(ctx, _evt, { send }) {
|
|
264
|
+
const getContentEl = () => dom.getContentEl(ctx)
|
|
265
|
+
let restoreFocus = true
|
|
266
|
+
return trackDismissableElement(getContentEl, {
|
|
267
|
+
exclude: dom.getTriggerEl(ctx),
|
|
268
|
+
defer: true,
|
|
269
|
+
onInteractOutside(event) {
|
|
270
|
+
ctx.onInteractOutside?.(event)
|
|
271
|
+
if (event.defaultPrevented) return
|
|
272
|
+
restoreFocus = !(event.detail.focusable || event.detail.contextmenu)
|
|
273
|
+
},
|
|
274
|
+
onPointerDownOutside: ctx.onPointerDownOutside,
|
|
275
|
+
onFocusOutside: ctx.onFocusOutside,
|
|
276
|
+
onDismiss() {
|
|
277
|
+
send({ type: "INTERACT_OUTSIDE", restoreFocus })
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
},
|
|
180
281
|
trackFormControl(ctx, _evt, { send, initialContext }) {
|
|
181
282
|
const inputEl = dom.getHiddenInputEl(ctx)
|
|
182
283
|
return trackFormControl(inputEl, {
|
|
@@ -213,21 +314,17 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
213
314
|
picker
|
|
214
315
|
.open()
|
|
215
316
|
.then(({ sRGBHex }: { sRGBHex: string }) => {
|
|
216
|
-
const format = ctx.
|
|
317
|
+
const format = ctx.value.getFormat()
|
|
217
318
|
const color = parseColor(sRGBHex).toFormat(format) as Color
|
|
218
319
|
set.value(ctx, color)
|
|
219
|
-
ctx.onValueChangeEnd?.({ value: ctx.value,
|
|
320
|
+
ctx.onValueChangeEnd?.({ value: ctx.value, valueAsString: ctx.valueAsString })
|
|
220
321
|
})
|
|
221
322
|
.catch(() => void 0)
|
|
222
323
|
},
|
|
223
324
|
setActiveChannel(ctx, evt) {
|
|
224
325
|
ctx.activeId = evt.id
|
|
225
|
-
if (evt.channel)
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
if (evt.orientation) {
|
|
229
|
-
ctx.activeOrientation = evt.orientation
|
|
230
|
-
}
|
|
326
|
+
if (evt.channel) ctx.activeChannel = evt.channel
|
|
327
|
+
if (evt.orientation) ctx.activeOrientation = evt.orientation
|
|
231
328
|
},
|
|
232
329
|
clearActiveChannel(ctx) {
|
|
233
330
|
ctx.activeChannel = null
|
|
@@ -240,10 +337,10 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
240
337
|
const percent = dom.getAreaValueFromPoint(ctx, evt.point)
|
|
241
338
|
if (!percent) return
|
|
242
339
|
|
|
243
|
-
const
|
|
244
|
-
const
|
|
340
|
+
const xValue = ctx.value.getChannelPercentValue(xChannel, percent.x)
|
|
341
|
+
const yValue = ctx.value.getChannelPercentValue(yChannel, 1 - percent.y)
|
|
245
342
|
|
|
246
|
-
|
|
343
|
+
const color = ctx.value.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue)
|
|
247
344
|
set.value(ctx, color)
|
|
248
345
|
},
|
|
249
346
|
setChannelColorFromPoint(ctx, evt) {
|
|
@@ -252,30 +349,18 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
252
349
|
const percent = dom.getChannelSliderValueFromPoint(ctx, evt.point, channel)
|
|
253
350
|
if (!percent) return
|
|
254
351
|
|
|
255
|
-
const { minValue, maxValue, step } = ctx.valueAsColor.getChannelRange(channel)
|
|
256
352
|
const orientation = ctx.activeOrientation || "horizontal"
|
|
353
|
+
const channelPercent = orientation === "horizontal" ? percent.x : percent.y
|
|
257
354
|
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
const value = snapValueToStep(channelValue - step, minValue, maxValue, step)
|
|
262
|
-
const newColor = ctx.valueAsColor.withChannelValue(channel, value)
|
|
263
|
-
|
|
264
|
-
set.value(ctx, newColor)
|
|
355
|
+
const value = ctx.value.getChannelPercentValue(channel, channelPercent)
|
|
356
|
+
const color = ctx.value.withChannelValue(channel, value)
|
|
357
|
+
set.value(ctx, color)
|
|
265
358
|
},
|
|
266
359
|
setValue(ctx, evt) {
|
|
267
360
|
set.value(ctx, evt.value)
|
|
268
361
|
},
|
|
269
362
|
syncInputElements(ctx) {
|
|
270
|
-
|
|
271
|
-
const inputs = dom.getChannelInputEls(ctx)
|
|
272
|
-
inputs.forEach((input) => {
|
|
273
|
-
const channel = input.dataset.channel as ExtendedColorChannel | null
|
|
274
|
-
dom.setValue(input, getChannelInputValue(ctx.valueAsColor, channel))
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
// sync hidden input
|
|
278
|
-
dom.setValue(dom.getHiddenInputEl(ctx), ctx.value)
|
|
363
|
+
sync.inputs(ctx)
|
|
279
364
|
},
|
|
280
365
|
invokeOnChangeEnd(ctx) {
|
|
281
366
|
invoke.changeEnd(ctx)
|
|
@@ -285,119 +370,146 @@ export function machine(userContext: UserDefinedContext) {
|
|
|
285
370
|
|
|
286
371
|
// handle alpha channel
|
|
287
372
|
if (channel === "alpha") {
|
|
288
|
-
const newColor = ctx.
|
|
373
|
+
const newColor = ctx.value.withChannelValue("alpha", parseFloat(value))
|
|
289
374
|
set.value(ctx, newColor)
|
|
290
375
|
return
|
|
291
376
|
}
|
|
292
377
|
|
|
293
378
|
// handle other text channels
|
|
294
379
|
if (isTextField) {
|
|
295
|
-
const
|
|
296
|
-
const currentAlpha = ctx.valueAsColor.getChannelValue("alpha")
|
|
380
|
+
const currentAlpha = ctx.value.getChannelValue("alpha")
|
|
297
381
|
|
|
298
|
-
const
|
|
299
|
-
() =>
|
|
300
|
-
() => ctx.
|
|
382
|
+
const color = tryCatch(
|
|
383
|
+
() => parse(value).withChannelValue("alpha", currentAlpha),
|
|
384
|
+
() => ctx.value,
|
|
301
385
|
)
|
|
302
386
|
|
|
303
387
|
// set channel input value immediately (in event user types native css color, we need to convert it to the current channel format)
|
|
304
388
|
const inputEl = dom.getChannelInputEl(ctx, channel)
|
|
305
|
-
dom.setValue(inputEl,
|
|
389
|
+
dom.setValue(inputEl, getChannelValue(color, channel))
|
|
306
390
|
|
|
307
|
-
set.value(ctx,
|
|
391
|
+
set.value(ctx, color)
|
|
308
392
|
return
|
|
309
393
|
}
|
|
310
394
|
|
|
311
395
|
// handle other channels
|
|
312
|
-
const
|
|
313
|
-
set.value(ctx,
|
|
396
|
+
const color = ctx.value.toFormat(ctx.value.outputFormat!).withChannelValue(channel, value)
|
|
397
|
+
set.value(ctx, color)
|
|
314
398
|
},
|
|
315
|
-
|
|
316
399
|
incrementChannel(ctx, evt) {
|
|
317
|
-
const
|
|
318
|
-
const channelValue = ctx.valueAsColor.getChannelValue(evt.channel)
|
|
319
|
-
const value = snapValueToStep(channelValue + evt.step, minValue, maxValue, step)
|
|
320
|
-
const color = ctx.valueAsColor.withChannelValue(evt.channel, clampValue(value, minValue, maxValue))
|
|
400
|
+
const color = ctx.value.incrementChannel(evt.channel, evt.step)
|
|
321
401
|
set.value(ctx, color)
|
|
322
402
|
},
|
|
323
403
|
decrementChannel(ctx, evt) {
|
|
324
|
-
const
|
|
325
|
-
const channelValue = ctx.valueAsColor.getChannelValue(evt.channel)
|
|
326
|
-
const value = snapValueToStep(channelValue - evt.step, minValue, maxValue, step)
|
|
327
|
-
const color = ctx.valueAsColor.withChannelValue(evt.channel, clampValue(value, minValue, maxValue))
|
|
404
|
+
const color = ctx.value.decrementChannel(evt.channel, evt.step)
|
|
328
405
|
set.value(ctx, color)
|
|
329
406
|
},
|
|
330
|
-
|
|
331
407
|
incrementXChannel(ctx, evt) {
|
|
332
|
-
const { xChannel
|
|
333
|
-
const
|
|
334
|
-
const color = ctx.valueAsColor.withChannelValue(xChannel, incrementX(evt.step))
|
|
408
|
+
const { xChannel } = evt.channel
|
|
409
|
+
const color = ctx.value.incrementChannel(xChannel, evt.step)
|
|
335
410
|
set.value(ctx, color)
|
|
336
411
|
},
|
|
337
412
|
decrementXChannel(ctx, evt) {
|
|
338
|
-
const { xChannel
|
|
339
|
-
const
|
|
340
|
-
const color = ctx.valueAsColor.withChannelValue(xChannel, decrementX(evt.step))
|
|
413
|
+
const { xChannel } = evt.channel
|
|
414
|
+
const color = ctx.value.decrementChannel(xChannel, evt.step)
|
|
341
415
|
set.value(ctx, color)
|
|
342
416
|
},
|
|
343
|
-
|
|
344
417
|
incrementYChannel(ctx, evt) {
|
|
345
|
-
const {
|
|
346
|
-
const
|
|
347
|
-
const color = ctx.valueAsColor.withChannelValue(yChannel, incrementY(evt.step))
|
|
418
|
+
const { yChannel } = evt.channel
|
|
419
|
+
const color = ctx.value.incrementChannel(yChannel, evt.step)
|
|
348
420
|
set.value(ctx, color)
|
|
349
421
|
},
|
|
350
422
|
decrementYChannel(ctx, evt) {
|
|
351
|
-
const {
|
|
352
|
-
const
|
|
353
|
-
const color = ctx.valueAsColor.withChannelValue(yChannel, decrementY(evt.step))
|
|
423
|
+
const { yChannel } = evt.channel
|
|
424
|
+
const color = ctx.value.decrementChannel(yChannel, evt.step)
|
|
354
425
|
set.value(ctx, color)
|
|
355
426
|
},
|
|
356
|
-
|
|
357
427
|
setChannelToMax(ctx, evt) {
|
|
358
|
-
const
|
|
359
|
-
const color = ctx.
|
|
428
|
+
const range = ctx.value.getChannelRange(evt.channel)
|
|
429
|
+
const color = ctx.value.withChannelValue(evt.channel, range.maxValue)
|
|
360
430
|
set.value(ctx, color)
|
|
361
431
|
},
|
|
362
432
|
setChannelToMin(ctx, evt) {
|
|
363
|
-
const
|
|
364
|
-
const color = ctx.
|
|
433
|
+
const range = ctx.value.getChannelRange(evt.channel)
|
|
434
|
+
const color = ctx.value.withChannelValue(evt.channel, range.minValue)
|
|
365
435
|
set.value(ctx, color)
|
|
366
436
|
},
|
|
367
437
|
focusAreaThumb(ctx) {
|
|
368
438
|
raf(() => {
|
|
369
|
-
dom.getAreaThumbEl(ctx)
|
|
439
|
+
dom?.focus(ctx, dom.getAreaThumbEl(ctx))
|
|
370
440
|
})
|
|
371
441
|
},
|
|
372
442
|
focusChannelThumb(ctx, evt) {
|
|
373
443
|
raf(() => {
|
|
374
|
-
dom.getChannelSliderThumbEl(ctx, evt.channel)
|
|
444
|
+
dom?.focus(ctx, dom.getChannelSliderThumbEl(ctx, evt.channel))
|
|
445
|
+
})
|
|
446
|
+
},
|
|
447
|
+
setInitialFocus(ctx) {
|
|
448
|
+
raf(() => {
|
|
449
|
+
dom.getInitialFocusEl(ctx)?.focus({ preventScroll: true })
|
|
450
|
+
})
|
|
451
|
+
},
|
|
452
|
+
setReturnFocus(ctx) {
|
|
453
|
+
raf(() => {
|
|
454
|
+
dom?.focus(ctx, dom.getTriggerEl(ctx))
|
|
375
455
|
})
|
|
376
456
|
},
|
|
457
|
+
invokeOnOpen(ctx) {
|
|
458
|
+
ctx.onOpenChange?.({ open: true })
|
|
459
|
+
},
|
|
460
|
+
invokeOnClose(ctx) {
|
|
461
|
+
ctx.onOpenChange?.({ open: false })
|
|
462
|
+
},
|
|
463
|
+
toggleVisibility(ctx, _evt, { send }) {
|
|
464
|
+
send({ type: ctx.open ? "OPEN" : "CLOSE", src: "controlled" })
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
compareFns: {
|
|
468
|
+
value: (a, b) => a.isEqual(b),
|
|
377
469
|
},
|
|
378
470
|
},
|
|
379
471
|
)
|
|
380
472
|
}
|
|
381
473
|
|
|
474
|
+
const sync = {
|
|
475
|
+
inputs(ctx: MachineContext) {
|
|
476
|
+
// sync channel inputs
|
|
477
|
+
const channelInputs = dom.getChannelInputEls(ctx)
|
|
478
|
+
channelInputs.forEach((inputEl) => {
|
|
479
|
+
const channel = inputEl.dataset.channel as ExtendedColorChannel | null
|
|
480
|
+
dom.setValue(inputEl, getChannelValue(ctx.value, channel))
|
|
481
|
+
})
|
|
482
|
+
},
|
|
483
|
+
}
|
|
484
|
+
|
|
382
485
|
const invoke = {
|
|
383
486
|
changeEnd(ctx: MachineContext) {
|
|
384
487
|
ctx.onValueChangeEnd?.({
|
|
385
488
|
value: ctx.value,
|
|
386
|
-
|
|
489
|
+
valueAsString: ctx.valueAsString,
|
|
387
490
|
})
|
|
388
491
|
},
|
|
389
492
|
change(ctx: MachineContext) {
|
|
390
493
|
ctx.onValueChange?.({
|
|
391
494
|
value: ctx.value,
|
|
392
|
-
|
|
495
|
+
valueAsString: ctx.valueAsString,
|
|
393
496
|
})
|
|
497
|
+
|
|
498
|
+
dispatchInputValueEvent(dom.getHiddenInputEl(ctx), { value: ctx.valueAsString })
|
|
394
499
|
},
|
|
395
500
|
}
|
|
396
501
|
|
|
397
502
|
const set = {
|
|
398
|
-
value(ctx: MachineContext, color: Color | ColorType) {
|
|
399
|
-
|
|
400
|
-
ctx.value
|
|
503
|
+
value(ctx: MachineContext, color: Color | ColorType | undefined) {
|
|
504
|
+
sync.inputs(ctx)
|
|
505
|
+
if (!color || ctx.value.isEqual(color)) return
|
|
506
|
+
const currentFormat = ctx.value.getFormat()
|
|
507
|
+
const outputColor = color.toFormat(currentFormat)
|
|
508
|
+
try {
|
|
509
|
+
outputColor.outputFormat = ctx.value.outputFormat
|
|
510
|
+
} catch {}
|
|
511
|
+
|
|
512
|
+
ctx.value = ref(outputColor) as Color
|
|
401
513
|
invoke.change(ctx)
|
|
402
514
|
},
|
|
403
515
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { parseColor, type Color } from "@zag-js/color-utils"
|
|
2
|
+
import { ref } from "@zag-js/core"
|
|
3
|
+
|
|
4
|
+
export const parse = (color: string): Color => {
|
|
5
|
+
const value = parseColor(color)
|
|
6
|
+
|
|
7
|
+
const colorFormat = value.getFormat()
|
|
8
|
+
const lockedFormat = colorFormat.startsWith("hsl") ? "hsla" : "hsba"
|
|
9
|
+
|
|
10
|
+
const result = value.toFormat(lockedFormat)
|
|
11
|
+
result.outputFormat = colorFormat
|
|
12
|
+
|
|
13
|
+
return ref(result)
|
|
14
|
+
}
|