@zag-js/color-picker 0.69.0 → 0.71.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.
@@ -1,65 +0,0 @@
1
- import type { ColorChannel } from "@zag-js/color-utils"
2
- import { getRelativePoint, type Point } from "@zag-js/dom-event"
3
- import { createScope, queryAll } from "@zag-js/dom-query"
4
- import type { MachineContext as Ctx } from "./color-picker.types"
5
-
6
- export const dom = createScope({
7
- getRootId: (ctx: Ctx) => ctx.ids?.root ?? `color-picker:${ctx.id}`,
8
- getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `color-picker:${ctx.id}:label`,
9
- getHiddenInputId: (ctx: Ctx) => ctx.ids?.hiddenInput ?? `color-picker:${ctx.id}:hidden-input`,
10
- getControlId: (ctx: Ctx) => ctx.ids?.control ?? `color-picker:${ctx.id}:control`,
11
- getTriggerId: (ctx: Ctx) => ctx.ids?.trigger ?? `color-picker:${ctx.id}:trigger`,
12
- getContentId: (ctx: Ctx) => ctx.ids?.content ?? `color-picker:${ctx.id}:content`,
13
- getPositionerId: (ctx: Ctx) => ctx.ids?.positioner ?? `color-picker:${ctx.id}:positioner`,
14
- getFormatSelectId: (ctx: Ctx) => ctx.ids?.formatSelect ?? `color-picker:${ctx.id}:format-select`,
15
-
16
- getAreaId: (ctx: Ctx) => ctx.ids?.area ?? `color-picker:${ctx.id}:area`,
17
- getAreaGradientId: (ctx: Ctx) => ctx.ids?.areaGradient ?? `color-picker:${ctx.id}:area-gradient`,
18
- getAreaThumbId: (ctx: Ctx) => ctx.ids?.areaThumb ?? `color-picker:${ctx.id}:area-thumb`,
19
-
20
- getChannelSliderTrackId: (ctx: Ctx, channel: ColorChannel) =>
21
- ctx.ids?.channelSliderTrack?.(channel) ?? `color-picker:${ctx.id}:slider-track:${channel}`,
22
- getChannelSliderThumbId: (ctx: Ctx, channel: ColorChannel) =>
23
- ctx.ids?.channelSliderThumb?.(channel) ?? `color-picker:${ctx.id}:slider-thumb:${channel}`,
24
-
25
- getContentEl: (ctx: Ctx) => dom.getById(ctx, dom.getContentId(ctx)),
26
- getAreaThumbEl: (ctx: Ctx) => dom.getById(ctx, dom.getAreaThumbId(ctx)),
27
- getChannelSliderThumbEl: (ctx: Ctx, channel: ColorChannel) =>
28
- dom.getById(ctx, dom.getChannelSliderThumbId(ctx, channel)),
29
- getChannelInputEl: (ctx: Ctx, channel: string): HTMLInputElement[] => {
30
- const selector = `input[data-channel="${channel}"]`
31
- return [
32
- ...queryAll<HTMLInputElement>(dom.getContentEl(ctx), selector),
33
- ...queryAll<HTMLInputElement>(dom.getControlEl(ctx), selector),
34
- ]
35
- },
36
- getFormatSelectEl: (ctx: Ctx) => dom.getById<HTMLSelectElement>(ctx, dom.getFormatSelectId(ctx)),
37
-
38
- getHiddenInputEl: (ctx: Ctx) => dom.getById<HTMLInputElement>(ctx, dom.getHiddenInputId(ctx)),
39
- getAreaEl: (ctx: Ctx) => dom.getById(ctx, dom.getAreaId(ctx)),
40
- getAreaValueFromPoint(ctx: Ctx, point: Point) {
41
- const areaEl = dom.getAreaEl(ctx)
42
- if (!areaEl) return
43
- const { percent } = getRelativePoint(point, areaEl)
44
- return percent
45
- },
46
-
47
- getControlEl: (ctx: Ctx) => dom.getById(ctx, dom.getControlId(ctx)),
48
- getTriggerEl: (ctx: Ctx) => dom.getById(ctx, dom.getTriggerId(ctx)),
49
- getPositionerEl: (ctx: Ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
50
- getChannelSliderTrackEl: (ctx: Ctx, channel: ColorChannel) => {
51
- return dom.getById(ctx, dom.getChannelSliderTrackId(ctx, channel))
52
- },
53
- getChannelSliderValueFromPoint(ctx: Ctx, point: Point, channel: ColorChannel) {
54
- const trackEl = dom.getChannelSliderTrackEl(ctx, channel)
55
- if (!trackEl) return
56
- const { percent } = getRelativePoint(point, trackEl)
57
- return percent
58
- },
59
- getChannelInputEls: (ctx: Ctx) => {
60
- return [
61
- ...queryAll<HTMLInputElement>(dom.getContentEl(ctx), "input[data-channel]"),
62
- ...queryAll<HTMLInputElement>(dom.getControlEl(ctx), "input[data-channel]"),
63
- ]
64
- },
65
- })
@@ -1,638 +0,0 @@
1
- import { parseColor, type Color } from "@zag-js/color-utils"
2
- import { createMachine, guards } from "@zag-js/core"
3
- import { trackDismissableElement } from "@zag-js/dismissable"
4
- import { trackPointerMove } from "@zag-js/dom-event"
5
- import { getInitialFocus, raf } from "@zag-js/dom-query"
6
- import { dispatchInputValueEvent, trackFormControl } from "@zag-js/form-utils"
7
- import { getPlacement } from "@zag-js/popper"
8
- import { disableTextSelection } from "@zag-js/text-selection"
9
- import { compact, tryCatch } from "@zag-js/utils"
10
- import { dom } from "./color-picker.dom"
11
- import { parse } from "./color-picker.parse"
12
- import type {
13
- ColorFormat,
14
- ColorType,
15
- ExtendedColorChannel,
16
- MachineContext,
17
- MachineState,
18
- UserDefinedContext,
19
- } from "./color-picker.types"
20
- import { getChannelValue } from "./utils/get-channel-input-value"
21
-
22
- const { and } = guards
23
-
24
- export function machine(userContext: UserDefinedContext) {
25
- const ctx = compact(userContext)
26
- return createMachine<MachineContext, MachineState>(
27
- {
28
- id: "color-picker",
29
- initial: ctx.open ? "open" : "idle",
30
- context: {
31
- dir: "ltr",
32
- value: parse("#000000"),
33
- format: "rgba",
34
- disabled: false,
35
- closeOnSelect: false,
36
- ...ctx,
37
- activeId: null,
38
- activeChannel: null,
39
- activeOrientation: null,
40
- fieldsetDisabled: false,
41
- restoreFocus: true,
42
- positioning: {
43
- ...ctx.positioning,
44
- placement: "bottom",
45
- },
46
- },
47
-
48
- computed: {
49
- isRtl: (ctx) => ctx.dir === "rtl",
50
- isDisabled: (ctx) => !!ctx.disabled || ctx.fieldsetDisabled,
51
- isInteractive: (ctx) => !(ctx.isDisabled || ctx.readOnly),
52
- valueAsString: (ctx) => ctx.value.toString(ctx.format),
53
- areaValue: (ctx) => {
54
- const format = ctx.format.startsWith("hsl") ? "hsla" : "hsba"
55
- return ctx.value.toFormat(format)
56
- },
57
- },
58
-
59
- activities: ["trackFormControl"],
60
-
61
- watch: {
62
- value: ["syncInputElements"],
63
- format: ["syncFormatSelectElement"],
64
- open: ["toggleVisibility"],
65
- },
66
-
67
- on: {
68
- "VALUE.SET": {
69
- actions: ["setValue"],
70
- },
71
- "FORMAT.SET": {
72
- actions: ["setFormat"],
73
- },
74
- "CHANNEL_INPUT.CHANGE": {
75
- actions: ["setChannelColorFromInput"],
76
- },
77
- "EYEDROPPER.CLICK": {
78
- actions: ["openEyeDropper"],
79
- },
80
- },
81
-
82
- states: {
83
- idle: {
84
- tags: ["closed"],
85
- on: {
86
- "CONTROLLED.OPEN": {
87
- target: "open",
88
- actions: ["setInitialFocus"],
89
- },
90
- OPEN: [
91
- {
92
- guard: "isOpenControlled",
93
- actions: ["invokeOnOpen"],
94
- },
95
- {
96
- target: "open",
97
- actions: ["invokeOnOpen", "setInitialFocus"],
98
- },
99
- ],
100
- "TRIGGER.CLICK": [
101
- {
102
- guard: "isOpenControlled",
103
- actions: ["invokeOnOpen"],
104
- },
105
- {
106
- target: "open",
107
- actions: ["invokeOnOpen", "setInitialFocus"],
108
- },
109
- ],
110
- "CHANNEL_INPUT.FOCUS": {
111
- target: "focused",
112
- actions: ["setActiveChannel"],
113
- },
114
- },
115
- },
116
-
117
- focused: {
118
- tags: ["closed", "focused"],
119
- on: {
120
- "CONTROLLED.OPEN": {
121
- target: "open",
122
- actions: ["setInitialFocus"],
123
- },
124
- OPEN: [
125
- {
126
- guard: "isOpenControlled",
127
- actions: ["invokeOnOpen"],
128
- },
129
- {
130
- target: "open",
131
- actions: ["invokeOnOpen", "setInitialFocus"],
132
- },
133
- ],
134
- "TRIGGER.CLICK": [
135
- {
136
- guard: "isOpenControlled",
137
- actions: ["invokeOnOpen"],
138
- },
139
- {
140
- target: "open",
141
- actions: ["invokeOnOpen", "setInitialFocus"],
142
- },
143
- ],
144
- "CHANNEL_INPUT.FOCUS": {
145
- actions: ["setActiveChannel"],
146
- },
147
- "CHANNEL_INPUT.BLUR": {
148
- target: "idle",
149
- actions: ["setChannelColorFromInput"],
150
- },
151
- "TRIGGER.BLUR": {
152
- target: "idle",
153
- },
154
- },
155
- },
156
-
157
- open: {
158
- tags: ["open"],
159
- activities: ["trackPositioning", "trackDismissableElement"],
160
- on: {
161
- "CONTROLLED.CLOSE": [
162
- {
163
- guard: "shouldRestoreFocus",
164
- target: "focused",
165
- actions: ["setReturnFocus"],
166
- },
167
- {
168
- target: "idle",
169
- },
170
- ],
171
- "TRIGGER.CLICK": [
172
- {
173
- guard: "isOpenControlled",
174
- actions: ["invokeOnClose"],
175
- },
176
- {
177
- target: "idle",
178
- actions: ["invokeOnClose"],
179
- },
180
- ],
181
- "AREA.POINTER_DOWN": {
182
- target: "open:dragging",
183
- actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"],
184
- },
185
- "AREA.FOCUS": {
186
- actions: ["setActiveChannel"],
187
- },
188
- "CHANNEL_SLIDER.POINTER_DOWN": {
189
- target: "open:dragging",
190
- actions: ["setActiveChannel", "setChannelColorFromPoint", "focusChannelThumb"],
191
- },
192
- "CHANNEL_SLIDER.FOCUS": {
193
- actions: ["setActiveChannel"],
194
- },
195
- "AREA.ARROW_LEFT": {
196
- actions: ["decrementAreaXChannel"],
197
- },
198
- "AREA.ARROW_RIGHT": {
199
- actions: ["incrementAreaXChannel"],
200
- },
201
- "AREA.ARROW_UP": {
202
- actions: ["incrementAreaYChannel"],
203
- },
204
- "AREA.ARROW_DOWN": {
205
- actions: ["decrementAreaYChannel"],
206
- },
207
- "AREA.PAGE_UP": {
208
- actions: ["incrementAreaXChannel"],
209
- },
210
- "AREA.PAGE_DOWN": {
211
- actions: ["decrementAreaXChannel"],
212
- },
213
- "CHANNEL_SLIDER.ARROW_LEFT": {
214
- actions: ["decrementChannel"],
215
- },
216
- "CHANNEL_SLIDER.ARROW_RIGHT": {
217
- actions: ["incrementChannel"],
218
- },
219
- "CHANNEL_SLIDER.ARROW_UP": {
220
- actions: ["incrementChannel"],
221
- },
222
- "CHANNEL_SLIDER.ARROW_DOWN": {
223
- actions: ["decrementChannel"],
224
- },
225
- "CHANNEL_SLIDER.PAGE_UP": {
226
- actions: ["incrementChannel"],
227
- },
228
- "CHANNEL_SLIDER.PAGE_DOWN": {
229
- actions: ["decrementChannel"],
230
- },
231
- "CHANNEL_SLIDER.HOME": {
232
- actions: ["setChannelToMin"],
233
- },
234
- "CHANNEL_SLIDER.END": {
235
- actions: ["setChannelToMax"],
236
- },
237
- "CHANNEL_INPUT.BLUR": {
238
- actions: ["setChannelColorFromInput"],
239
- },
240
- INTERACT_OUTSIDE: [
241
- {
242
- guard: "isOpenControlled",
243
- actions: ["invokeOnClose"],
244
- },
245
- {
246
- guard: "shouldRestoreFocus",
247
- target: "focused",
248
- actions: ["invokeOnClose", "setReturnFocus"],
249
- },
250
- {
251
- target: "idle",
252
- actions: ["invokeOnClose"],
253
- },
254
- ],
255
- CLOSE: [
256
- {
257
- guard: "isOpenControlled",
258
- actions: ["invokeOnClose"],
259
- },
260
- {
261
- target: "idle",
262
- actions: ["invokeOnClose"],
263
- },
264
- ],
265
- "SWATCH_TRIGGER.CLICK": [
266
- {
267
- guard: and("isOpenControlled", "closeOnSelect"),
268
- actions: ["setValue", "invokeOnClose"],
269
- },
270
- {
271
- guard: "closeOnSelect",
272
- target: "focused",
273
- actions: ["setValue", "invokeOnClose", "setReturnFocus"],
274
- },
275
- {
276
- actions: ["setValue"],
277
- },
278
- ],
279
- },
280
- },
281
-
282
- "open:dragging": {
283
- tags: ["open"],
284
- exit: ["clearActiveChannel"],
285
- activities: ["trackPointerMove", "disableTextSelection", "trackPositioning", "trackDismissableElement"],
286
- on: {
287
- "CONTROLLED.CLOSE": [
288
- {
289
- guard: "shouldRestoreFocus",
290
- target: "focused",
291
- actions: ["setReturnFocus"],
292
- },
293
- {
294
- target: "idle",
295
- },
296
- ],
297
- "AREA.POINTER_MOVE": {
298
- actions: ["setAreaColorFromPoint", "focusAreaThumb"],
299
- },
300
- "AREA.POINTER_UP": {
301
- target: "open",
302
- actions: ["invokeOnChangeEnd"],
303
- },
304
- "CHANNEL_SLIDER.POINTER_MOVE": {
305
- actions: ["setChannelColorFromPoint", "focusChannelThumb"],
306
- },
307
- "CHANNEL_SLIDER.POINTER_UP": {
308
- target: "open",
309
- actions: ["invokeOnChangeEnd"],
310
- },
311
- INTERACT_OUTSIDE: [
312
- {
313
- guard: "isOpenControlled",
314
- actions: ["invokeOnClose"],
315
- },
316
- {
317
- guard: "shouldRestoreFocus",
318
- target: "focused",
319
- actions: ["invokeOnClose", "setReturnFocus"],
320
- },
321
- {
322
- target: "idle",
323
- actions: ["invokeOnClose"],
324
- },
325
- ],
326
- CLOSE: [
327
- {
328
- guard: "isOpenControlled",
329
- actions: ["invokeOnClose"],
330
- },
331
- {
332
- target: "idle",
333
- actions: ["invokeOnClose"],
334
- },
335
- ],
336
- },
337
- },
338
- },
339
- },
340
- {
341
- guards: {
342
- closeOnSelect: (ctx) => !!ctx.closeOnSelect,
343
- isOpenControlled: (ctx) => !!ctx["open.controlled"],
344
- shouldRestoreFocus: (ctx) => !!ctx.restoreFocus,
345
- },
346
- activities: {
347
- trackPositioning(ctx) {
348
- ctx.currentPlacement = ctx.positioning.placement
349
- const anchorEl = dom.getTriggerEl(ctx)
350
- const getPositionerEl = () => dom.getPositionerEl(ctx)
351
- return getPlacement(anchorEl, getPositionerEl, {
352
- ...ctx.positioning,
353
- defer: true,
354
- onComplete(data) {
355
- ctx.currentPlacement = data.placement
356
- },
357
- })
358
- },
359
- trackDismissableElement(ctx, _evt, { send }) {
360
- const getContentEl = () => dom.getContentEl(ctx)
361
- return trackDismissableElement(getContentEl, {
362
- exclude: dom.getTriggerEl(ctx),
363
- defer: true,
364
- onInteractOutside(event) {
365
- ctx.onInteractOutside?.(event)
366
- if (event.defaultPrevented) return
367
- ctx.restoreFocus = !(event.detail.focusable || event.detail.contextmenu)
368
- },
369
- onPointerDownOutside: ctx.onPointerDownOutside,
370
- onFocusOutside: ctx.onFocusOutside,
371
- onDismiss() {
372
- send({ type: "INTERACT_OUTSIDE" })
373
- },
374
- })
375
- },
376
- trackFormControl(ctx, _evt, { send, initialContext }) {
377
- const inputEl = dom.getHiddenInputEl(ctx)
378
- return trackFormControl(inputEl, {
379
- onFieldsetDisabledChange(disabled) {
380
- ctx.fieldsetDisabled = disabled
381
- },
382
- onFormReset() {
383
- send({ type: "VALUE.SET", value: initialContext.value, src: "form.reset" })
384
- },
385
- })
386
- },
387
- trackPointerMove(ctx, evt, { send }) {
388
- return trackPointerMove(dom.getDoc(ctx), {
389
- onPointerMove({ point }) {
390
- const type = ctx.activeId === "area" ? "AREA.POINTER_MOVE" : "CHANNEL_SLIDER.POINTER_MOVE"
391
- send({ type, point, format: evt.format })
392
- },
393
- onPointerUp() {
394
- const type = ctx.activeId === "area" ? "AREA.POINTER_UP" : "CHANNEL_SLIDER.POINTER_UP"
395
- send({ type })
396
- },
397
- })
398
- },
399
- disableTextSelection(ctx) {
400
- return disableTextSelection({ doc: dom.getDoc(ctx), target: dom.getContentEl(ctx) })
401
- },
402
- },
403
- actions: {
404
- openEyeDropper(ctx) {
405
- const isSupported = "EyeDropper" in dom.getWin(ctx)
406
- if (!isSupported) return
407
- const win = dom.getWin(ctx)
408
- const picker = new win.EyeDropper()
409
- picker
410
- .open()
411
- .then(({ sRGBHex }) => {
412
- const format = ctx.value.getFormat()
413
- const color = parseColor(sRGBHex).toFormat(format) as Color
414
- set.value(ctx, color)
415
- ctx.onValueChangeEnd?.({ value: ctx.value, valueAsString: ctx.valueAsString })
416
- })
417
- .catch(() => void 0)
418
- },
419
- setActiveChannel(ctx, evt) {
420
- ctx.activeId = evt.id
421
- if (evt.channel) ctx.activeChannel = evt.channel
422
- if (evt.orientation) ctx.activeOrientation = evt.orientation
423
- },
424
- clearActiveChannel(ctx) {
425
- ctx.activeChannel = null
426
- ctx.activeId = null
427
- ctx.activeOrientation = null
428
- },
429
- setAreaColorFromPoint(ctx, evt) {
430
- const normalizedValue = evt.format ? ctx.value.toFormat(evt.format) : ctx.areaValue
431
- const { xChannel, yChannel } = evt.channel || ctx.activeChannel
432
-
433
- const percent = dom.getAreaValueFromPoint(ctx, evt.point)
434
- if (!percent) return
435
-
436
- const xValue = normalizedValue.getChannelPercentValue(xChannel, percent.x)
437
- const yValue = normalizedValue.getChannelPercentValue(yChannel, 1 - percent.y)
438
-
439
- const color = normalizedValue.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue)
440
- set.value(ctx, color)
441
- },
442
- setChannelColorFromPoint(ctx, evt) {
443
- const channel = evt.channel || ctx.activeId
444
- const normalizedValue = evt.format ? ctx.value.toFormat(evt.format) : ctx.areaValue
445
-
446
- const percent = dom.getChannelSliderValueFromPoint(ctx, evt.point, channel)
447
- if (!percent) return
448
-
449
- const orientation = ctx.activeOrientation || "horizontal"
450
- const channelPercent = orientation === "horizontal" ? percent.x : percent.y
451
-
452
- const value = normalizedValue.getChannelPercentValue(channel, channelPercent)
453
- const color = normalizedValue.withChannelValue(channel, value)
454
- set.value(ctx, color)
455
- },
456
- setValue(ctx, evt) {
457
- set.value(ctx, evt.value)
458
- },
459
- setFormat(ctx, evt) {
460
- set.format(ctx, evt.format)
461
- },
462
- syncInputElements(ctx) {
463
- sync.inputs(ctx)
464
- },
465
- invokeOnChangeEnd(ctx) {
466
- invoke.changeEnd(ctx)
467
- },
468
- setChannelColorFromInput(ctx, evt) {
469
- const { channel, isTextField, value } = evt
470
- const currentAlpha = ctx.value.getChannelValue("alpha")
471
-
472
- // handle other text channels
473
- let color: Color
474
-
475
- // handle alpha channel
476
- if (channel === "alpha") {
477
- //
478
- let valueAsNumber = parseFloat(value)
479
- valueAsNumber = Number.isNaN(valueAsNumber) ? currentAlpha : valueAsNumber
480
- color = ctx.value.withChannelValue("alpha", valueAsNumber)
481
- //
482
- } else if (isTextField) {
483
- //
484
- color = tryCatch(
485
- () => parse(value).withChannelValue("alpha", currentAlpha),
486
- () => ctx.value,
487
- )
488
- //
489
- } else {
490
- //
491
- const current = ctx.value.toFormat(ctx.format)
492
- const valueAsNumber = Number.isNaN(value) ? current.getChannelValue(channel) : value
493
- color = current.withChannelValue(channel, valueAsNumber)
494
- //
495
- }
496
-
497
- // sync channel input value immediately (in event user types native css color, we need to convert it to the current channel format)
498
- sync.inputs(ctx, color)
499
-
500
- // set new color
501
- set.value(ctx, color)
502
- },
503
- incrementChannel(ctx, evt) {
504
- const color = ctx.value.incrementChannel(evt.channel, evt.step)
505
- set.value(ctx, color)
506
- },
507
- decrementChannel(ctx, evt) {
508
- const color = ctx.value.decrementChannel(evt.channel, evt.step)
509
- set.value(ctx, color)
510
- },
511
- incrementAreaXChannel(ctx, evt) {
512
- const { xChannel } = evt.channel
513
- const color = ctx.areaValue.incrementChannel(xChannel, evt.step)
514
- set.value(ctx, color)
515
- },
516
- decrementAreaXChannel(ctx, evt) {
517
- const { xChannel } = evt.channel
518
- const color = ctx.areaValue.decrementChannel(xChannel, evt.step)
519
- set.value(ctx, color)
520
- },
521
- incrementAreaYChannel(ctx, evt) {
522
- const { yChannel } = evt.channel
523
- const color = ctx.areaValue.incrementChannel(yChannel, evt.step)
524
- set.value(ctx, color)
525
- },
526
- decrementAreaYChannel(ctx, evt) {
527
- const { yChannel } = evt.channel
528
- const color = ctx.areaValue.decrementChannel(yChannel, evt.step)
529
- set.value(ctx, color)
530
- },
531
- setChannelToMax(ctx, evt) {
532
- const range = ctx.value.getChannelRange(evt.channel)
533
- const color = ctx.value.withChannelValue(evt.channel, range.maxValue)
534
- set.value(ctx, color)
535
- },
536
- setChannelToMin(ctx, evt) {
537
- const range = ctx.value.getChannelRange(evt.channel)
538
- const color = ctx.value.withChannelValue(evt.channel, range.minValue)
539
- set.value(ctx, color)
540
- },
541
- focusAreaThumb(ctx) {
542
- raf(() => {
543
- dom.getAreaThumbEl(ctx)?.focus({ preventScroll: true })
544
- })
545
- },
546
- focusChannelThumb(ctx, evt) {
547
- raf(() => {
548
- dom.getChannelSliderThumbEl(ctx, evt.channel)?.focus({ preventScroll: true })
549
- })
550
- },
551
- setInitialFocus(ctx) {
552
- raf(() => {
553
- const element = getInitialFocus({
554
- root: dom.getContentEl(ctx),
555
- getInitialEl: ctx.initialFocusEl,
556
- })
557
- element?.focus({ preventScroll: true })
558
- })
559
- },
560
- setReturnFocus(ctx) {
561
- raf(() => {
562
- dom.getTriggerEl(ctx)?.focus({ preventScroll: true })
563
- })
564
- },
565
- syncFormatSelectElement(ctx) {
566
- sync.formatSelect(ctx)
567
- },
568
- invokeOnOpen(ctx) {
569
- ctx.onOpenChange?.({ open: true })
570
- },
571
- invokeOnClose(ctx) {
572
- ctx.onOpenChange?.({ open: false })
573
- },
574
- toggleVisibility(ctx, evt, { send }) {
575
- send({ type: ctx.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt })
576
- },
577
- },
578
- compareFns: {
579
- value: (a, b) => a.isEqual(b),
580
- },
581
- },
582
- )
583
- }
584
-
585
- const sync = {
586
- // sync channel inputs
587
- inputs(ctx: MachineContext, color?: Color) {
588
- const channelInputs = dom.getChannelInputEls(ctx)
589
- raf(() => {
590
- channelInputs.forEach((inputEl) => {
591
- const channel = inputEl.dataset.channel as ExtendedColorChannel | null
592
- dom.setValue(inputEl, getChannelValue(color || ctx.value, channel))
593
- })
594
- })
595
- },
596
- // sync format select
597
- formatSelect(ctx: MachineContext) {
598
- const selectEl = dom.getFormatSelectEl(ctx)
599
- raf(() => {
600
- dom.setValue(selectEl, ctx.format)
601
- })
602
- },
603
- }
604
-
605
- const invoke = {
606
- changeEnd(ctx: MachineContext) {
607
- const value = ctx.value.toFormat(ctx.format)
608
- ctx.onValueChangeEnd?.({
609
- value,
610
- valueAsString: ctx.valueAsString,
611
- })
612
- },
613
- change(ctx: MachineContext) {
614
- const value = ctx.value.toFormat(ctx.format)
615
- ctx.onValueChange?.({
616
- value,
617
- valueAsString: ctx.valueAsString,
618
- })
619
-
620
- dispatchInputValueEvent(dom.getHiddenInputEl(ctx), { value: ctx.valueAsString })
621
- },
622
- formatChange(ctx: MachineContext) {
623
- ctx.onFormatChange?.({ format: ctx.format })
624
- },
625
- }
626
-
627
- const set = {
628
- value(ctx: MachineContext, color: Color | ColorType | undefined) {
629
- if (!color || ctx.value.isEqual(color)) return
630
- ctx.value = color
631
- invoke.change(ctx)
632
- },
633
- format(ctx: MachineContext, format: ColorFormat) {
634
- if (ctx.format === format) return
635
- ctx.format = format
636
- invoke.formatChange(ctx)
637
- },
638
- }
@@ -1,5 +0,0 @@
1
- import { parseColor, type Color } from "@zag-js/color-utils"
2
-
3
- export const parse = (colorString: string): Color => {
4
- return parseColor(colorString)
5
- }