@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.
@@ -1,10 +1,4 @@
1
- import {
2
- getColorAreaGradient,
3
- normalizeColor,
4
- type Color,
5
- type ColorChannel,
6
- type ColorFormat,
7
- } from "@zag-js/color-utils"
1
+ import { getColorAreaGradient, normalizeColor } from "@zag-js/color-utils"
8
2
  import {
9
3
  getEventKey,
10
4
  getEventPoint,
@@ -14,69 +8,138 @@ import {
14
8
  isModifiedEvent,
15
9
  type EventKeyMap,
16
10
  } from "@zag-js/dom-event"
17
- import { dataAttr, raf } from "@zag-js/dom-query"
11
+ import { dataAttr, query } from "@zag-js/dom-query"
12
+ import { getPlacementStyles } from "@zag-js/popper"
18
13
  import type { NormalizeProps, PropTypes } from "@zag-js/types"
19
14
  import { visuallyHiddenStyle } from "@zag-js/visually-hidden"
20
15
  import { parts } from "./color-picker.anatomy"
21
16
  import { dom } from "./color-picker.dom"
22
- import type {
23
- ColorAreaProps,
24
- ColorChannelInputProps,
25
- ColorChannelProps,
26
- ColorSwatchProps,
27
- MachineApi,
28
- Send,
29
- State,
30
- } from "./color-picker.types"
31
- import { getChannelDetails } from "./utils/get-channel-details"
17
+ import type { AreaProps, MachineApi, Send, State } from "./color-picker.types"
32
18
  import { getChannelDisplayColor } from "./utils/get-channel-display-color"
33
- import { getChannelInputRange, getChannelInputValue } from "./utils/get-channel-input-value"
34
- import { getSliderBgImage } from "./utils/get-slider-background"
19
+ import { getChannelRange, getChannelValue } from "./utils/get-channel-input-value"
20
+ import { getSliderBackground } from "./utils/get-slider-background"
35
21
 
36
22
  export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>): MachineApi<T> {
37
- const valueAsColor = state.context.valueAsColor
38
23
  const value = state.context.value
24
+ const valueAsString = state.context.valueAsString
25
+
39
26
  const isDisabled = state.context.isDisabled
40
27
  const isInteractive = state.context.isInteractive
41
- const isDragging = state.matches("dragging")
42
28
 
43
- const channels = valueAsColor.getColorChannels()
29
+ const isDragging = state.hasTag("dragging")
30
+ const isOpen = state.hasTag("open")
31
+
32
+ const channels = value.getChannels()
33
+
34
+ const getAreaChannels = (props: AreaProps) => {
35
+ const channels = value.getChannels()
36
+ return {
37
+ xChannel: props.xChannel ?? channels[1],
38
+ yChannel: props.yChannel ?? channels[2],
39
+ }
40
+ }
41
+
42
+ const currentPlacement = state.context.currentPlacement
43
+ const popperStyles = getPlacementStyles({
44
+ ...state.context.positioning,
45
+ placement: currentPlacement,
46
+ })
44
47
 
45
48
  return {
46
49
  isDragging,
50
+ isOpen,
51
+ valueAsString,
47
52
  value,
48
- valueAsColor,
49
- alpha: valueAsColor.getChannelValue("alpha"),
50
53
  channels,
51
-
52
- setColor(value: string | Color) {
54
+ setColor(value) {
53
55
  send({ type: "VALUE.SET", value: normalizeColor(value), src: "set-color" })
54
56
  },
55
-
56
- setChannelValue(channel: ColorChannel, value: number) {
57
- const color = valueAsColor.withChannelValue(channel, value)
57
+ getChannelValue(channel) {
58
+ return getChannelValue(value, channel)
59
+ },
60
+ setChannelValue(channel, channelValue) {
61
+ const color = value.withChannelValue(channel, channelValue)
58
62
  send({ type: "VALUE.SET", value: color, src: "set-channel" })
59
63
  },
60
-
61
- setFormat(format: ColorFormat) {
62
- const value = valueAsColor.toFormat(format)
63
- send({ type: "VALUE.SET", value, src: "set-format" })
64
+ format: value.outputFormat ?? value.getFormat(),
65
+ setFormat(format) {
66
+ const formatValue = value.toFormat(format)
67
+ send({ type: "VALUE.SET", value: formatValue, src: "set-format" })
64
68
  },
65
-
66
- setAlpha(value: number) {
67
- const color = valueAsColor.withChannelValue("alpha", value)
69
+ alpha: value.getChannelValue("alpha"),
70
+ setAlpha(alphaValue) {
71
+ const color = value.withChannelValue("alpha", alphaValue)
68
72
  send({ type: "VALUE.SET", value: color, src: "set-alpha" })
69
73
  },
70
74
 
75
+ rootProps: normalize.element({
76
+ ...parts.root.attrs,
77
+ dir: state.context.dir,
78
+ id: dom.getRootId(state.context),
79
+ "data-disabled": dataAttr(isDisabled),
80
+ "data-readonly": dataAttr(state.context.readOnly),
81
+ style: {
82
+ "--value": value.toString("css"),
83
+ },
84
+ }),
85
+
86
+ labelProps: normalize.element({
87
+ ...parts.label.attrs,
88
+ dir: state.context.dir,
89
+ id: dom.getLabelId(state.context),
90
+ htmlFor: dom.getHiddenInputId(state.context),
91
+ "data-disabled": dataAttr(isDisabled),
92
+ "data-readonly": dataAttr(state.context.readOnly),
93
+ onClick(event) {
94
+ event.preventDefault()
95
+ const inputEl = query(dom.getControlEl(state.context), "[data-channel=hex]")
96
+ inputEl?.focus({ preventScroll: true })
97
+ },
98
+ }),
99
+
100
+ controlProps: normalize.element({
101
+ ...parts.control.attrs,
102
+ id: dom.getControlId(state.context),
103
+ dir: state.context.dir,
104
+ "data-disabled": dataAttr(isDisabled),
105
+ "data-readonly": dataAttr(state.context.readOnly),
106
+ }),
107
+
108
+ triggerProps: normalize.button({
109
+ ...parts.trigger.attrs,
110
+ id: dom.getTriggerId(state.context),
111
+ dir: state.context.dir,
112
+ "aria-labelledby": dom.getLabelId(state.context),
113
+ "data-disabled": dataAttr(isDisabled),
114
+ "data-readonly": dataAttr(state.context.readOnly),
115
+ "data-placement": currentPlacement,
116
+ type: "button",
117
+ onClick() {
118
+ send({ type: "TRIGGER.CLICK" })
119
+ },
120
+ style: {
121
+ position: "relative",
122
+ },
123
+ }),
124
+
125
+ positionerProps: normalize.element({
126
+ ...parts.positioner.attrs,
127
+ id: dom.getPositionerId(state.context),
128
+ dir: state.context.dir,
129
+ style: popperStyles.floating,
130
+ }),
131
+
71
132
  contentProps: normalize.element({
72
133
  ...parts.content.attrs,
73
134
  id: dom.getContentId(state.context),
74
135
  dir: state.context.dir,
136
+ "data-placement": currentPlacement,
137
+ hidden: !isOpen,
75
138
  }),
76
139
 
77
- getAreaProps(props: ColorAreaProps) {
78
- const { xChannel, yChannel } = props
79
- const { areaStyles } = getColorAreaGradient(state.context.valueAsColor, {
140
+ getAreaProps(props = {}) {
141
+ const { xChannel, yChannel } = getAreaChannels(props)
142
+ const { areaStyles } = getColorAreaGradient(value, {
80
143
  xChannel,
81
144
  yChannel,
82
145
  dir: state.context.dir,
@@ -96,6 +159,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
96
159
  const channel = { xChannel, yChannel }
97
160
 
98
161
  send({ type: "AREA.POINTER_DOWN", point, channel, id: "area" })
162
+ event.preventDefault()
99
163
  },
100
164
  style: {
101
165
  position: "relative",
@@ -106,16 +170,16 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
106
170
  })
107
171
  },
108
172
 
109
- getAreaGradientProps(props: ColorAreaProps) {
110
- const { xChannel, yChannel } = props
111
- const { areaGradientStyles } = getColorAreaGradient(valueAsColor, {
173
+ getAreaBackgroundProps(props = {}) {
174
+ const { xChannel, yChannel } = getAreaChannels(props)
175
+ const { areaGradientStyles } = getColorAreaGradient(value, {
112
176
  xChannel,
113
177
  yChannel,
114
178
  dir: state.context.dir,
115
179
  })
116
180
 
117
181
  return normalize.element({
118
- ...parts.areaGradient.attrs,
182
+ ...parts.areaBackground.attrs,
119
183
  id: dom.getAreaGradientId(state.context),
120
184
  style: {
121
185
  position: "relative",
@@ -126,16 +190,17 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
126
190
  })
127
191
  },
128
192
 
129
- getAreaThumbProps(props: ColorAreaProps) {
130
- const { xChannel, yChannel } = props
131
- const { getThumbPosition } = getChannelDetails(valueAsColor, xChannel, yChannel)
132
- const { x, y } = getThumbPosition()
133
-
193
+ getAreaThumbProps(props = {}) {
194
+ const { xChannel, yChannel } = getAreaChannels(props)
134
195
  const channel = { xChannel, yChannel }
135
196
 
197
+ const x = value.getChannelValuePercent(xChannel)
198
+ const y = 1 - value.getChannelValuePercent(yChannel)
199
+
136
200
  return normalize.element({
137
201
  ...parts.areaThumb.attrs,
138
202
  id: dom.getAreaThumbId(state.context),
203
+ dir: state.context.dir,
139
204
  tabIndex: isDisabled ? undefined : 0,
140
205
  "data-disabled": dataAttr(isDisabled),
141
206
  role: "presentation",
@@ -146,13 +211,10 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
146
211
  transform: "translate(-50%, -50%)",
147
212
  touchAction: "none",
148
213
  forcedColorAdjust: "none",
149
- background: valueAsColor.withChannelValue("alpha", 1).toString("css"),
150
- },
151
- onBlur() {
152
- send("AREA.BLUR")
214
+ background: value.withChannelValue("alpha", 1).toString("css"),
153
215
  },
154
216
  onFocus() {
155
- send({ type: "AREA.FOCUS", id: "area" })
217
+ send({ type: "AREA.FOCUS", id: "area", channel })
156
218
  },
157
219
  onKeyDown(event) {
158
220
  if (!isInteractive) return
@@ -178,6 +240,9 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
178
240
  PageDown() {
179
241
  send({ type: "AREA.PAGE_DOWN", channel, step })
180
242
  },
243
+ Escape(event) {
244
+ event.stopPropagation()
245
+ },
181
246
  }
182
247
 
183
248
  const exec = keyMap[getEventKey(event, state.context)]
@@ -190,15 +255,32 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
190
255
  })
191
256
  },
192
257
 
193
- getChannelSliderTrackProps(props: ColorChannelProps) {
194
- const { orientation = "horizontal", channel } = props
258
+ getTransparencyGridProps(props = {}) {
259
+ const { size = "12px" } = props
260
+ return normalize.element({
261
+ ...parts.transparencyGrid.attrs,
262
+ style: {
263
+ "--size": size,
264
+ width: "100%",
265
+ height: "100%",
266
+ position: "absolute",
267
+ backgroundColor: "#fff",
268
+ backgroundImage: "conic-gradient(#eeeeee 0 25%, transparent 0 50%, #eeeeee 0 75%, transparent 0)",
269
+ backgroundSize: "var(--size) var(--size)",
270
+ inset: "0px",
271
+ zIndex: "auto",
272
+ pointerEvents: "none",
273
+ },
274
+ })
275
+ },
195
276
 
277
+ getChannelSliderProps(props) {
278
+ const { orientation = "horizontal", channel } = props
196
279
  return normalize.element({
197
- ...parts.channelSliderTrack.attrs,
198
- id: dom.getChannelSliderTrackId(state.context, channel),
199
- role: "group",
280
+ ...parts.channelSlider.attrs,
200
281
  "data-channel": channel,
201
282
  "data-orientation": orientation,
283
+ role: "presentation",
202
284
  onPointerDown(event) {
203
285
  if (!isInteractive) return
204
286
 
@@ -207,43 +289,42 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
207
289
 
208
290
  const point = getEventPoint(evt)
209
291
  send({ type: "CHANNEL_SLIDER.POINTER_DOWN", channel, point, id: channel, orientation })
292
+
293
+ event.preventDefault()
210
294
  },
211
295
  style: {
212
296
  position: "relative",
213
297
  touchAction: "none",
214
- forcedColorAdjust: "none",
215
- backgroundImage: getSliderBgImage(state.context, { orientation, channel }),
216
298
  },
217
299
  })
218
300
  },
219
301
 
220
- getChannelSliderBackgroundProps(props: ColorChannelProps) {
302
+ getChannelSliderTrackProps(props) {
221
303
  const { orientation = "horizontal", channel } = props
304
+
222
305
  return normalize.element({
223
- ...parts.channelSliderTrackBackground.attrs,
224
- "data-orientation": orientation,
306
+ ...parts.channelSliderTrack.attrs,
307
+ id: dom.getChannelSliderId(state.context, channel),
308
+ role: "group",
225
309
  "data-channel": channel,
310
+ "data-orientation": orientation,
226
311
  style: {
227
- position: "absolute",
228
- backgroundColor: "#fff",
229
- backgroundImage: [
230
- "linear-gradient(-45deg,#0000 75.5%,#bcbcbc 75.5%)",
231
- "linear-gradient(45deg,#0000 75.5%,#bcbcbc 75.5%)",
232
- "linear-gradient(-45deg,#bcbcbc 25.5%,#0000 25.5%)",
233
- "linear-gradient(45deg,#bcbcbc 25.5%,#0000 25.5%)",
234
- ].join(","),
235
- backgroundSize: "16px 16px",
236
- backgroundPosition: "-2px -2px,-2px 6px,6px -10px,-10px -2px",
237
- inset: 0,
238
- zIndex: -1,
312
+ position: "relative",
313
+ forcedColorAdjust: "none",
314
+ backgroundImage: getSliderBackground({
315
+ orientation,
316
+ channel,
317
+ dir: state.context.dir,
318
+ value: value,
319
+ }),
239
320
  },
240
321
  })
241
322
  },
242
323
 
243
- getChannelSliderThumbProps(props: ColorChannelProps) {
324
+ getChannelSliderThumbProps(props) {
244
325
  const { orientation = "horizontal", channel } = props
245
- const { minValue, maxValue, step: stepValue } = valueAsColor.getChannelRange(channel)
246
- const channelValue = valueAsColor.getChannelValue(channel)
326
+ const { minValue, maxValue, step: stepValue } = value.getChannelRange(channel)
327
+ const channelValue = value.getChannelValue(channel)
247
328
 
248
329
  const offset = (channelValue - minValue) / (maxValue - minValue)
249
330
 
@@ -269,17 +350,13 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
269
350
  style: {
270
351
  forcedColorAdjust: "none",
271
352
  position: "absolute",
272
- background: getChannelDisplayColor(valueAsColor, channel).toString("css"),
353
+ background: getChannelDisplayColor(value, channel).toString("css"),
273
354
  ...placementStyles,
274
355
  },
275
356
  onFocus() {
276
357
  if (!isInteractive) return
277
358
  send({ type: "CHANNEL_SLIDER.FOCUS", channel })
278
359
  },
279
- onBlur() {
280
- if (!isInteractive) return
281
- send({ type: "CHANNEL_SLIDER.BLUR", channel })
282
- },
283
360
  onKeyDown(event) {
284
361
  if (!isInteractive) return
285
362
  const step = getEventStep(event) * stepValue
@@ -309,6 +386,9 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
309
386
  End() {
310
387
  send({ type: "CHANNEL_SLIDER.END", channel })
311
388
  },
389
+ Escape(event) {
390
+ event.stopPropagation()
391
+ },
312
392
  }
313
393
 
314
394
  const exec = keyMap[getEventKey(event, state.context)]
@@ -321,10 +401,10 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
321
401
  })
322
402
  },
323
403
 
324
- getChannelInputProps(props: ColorChannelInputProps) {
404
+ getChannelInputProps(props) {
325
405
  const { channel } = props
326
406
  const isTextField = channel === "hex" || channel === "css"
327
- const range = getChannelInputRange(valueAsColor, channel)
407
+ const range = getChannelRange(value, channel)
328
408
 
329
409
  return normalize.input({
330
410
  ...parts.channelInput.attrs,
@@ -335,22 +415,30 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
335
415
  "data-disabled": dataAttr(isDisabled),
336
416
  readOnly: state.context.readOnly,
337
417
  id: dom.getChannelInputId(state.context, channel),
338
- defaultValue: getChannelInputValue(valueAsColor, channel),
418
+ defaultValue: getChannelValue(value, channel),
339
419
  min: range?.minValue,
340
420
  max: range?.maxValue,
341
421
  step: range?.step,
422
+ onBeforeInput(event) {
423
+ if (isTextField) return
424
+ const value = event.currentTarget.value
425
+ if (value.match(/[^0-9.]/g)) {
426
+ event.preventDefault()
427
+ }
428
+ },
342
429
  onFocus(event) {
343
430
  send({ type: "CHANNEL_INPUT.FOCUS", channel })
344
- raf(() => event.target.select())
431
+ event.target.select()
345
432
  },
346
433
  onBlur(event) {
347
- const value = event.currentTarget.value
348
- send({ type: "CHANNEL_INPUT.CHANGE", channel, value, isTextField })
434
+ const value = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber
435
+ send({ type: "CHANNEL_INPUT.BLUR", channel, value, isTextField })
349
436
  },
350
437
  onKeyDown(event) {
351
438
  if (event.key === "Enter") {
352
- const value = event.currentTarget.value
439
+ const value = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber
353
440
  send({ type: "CHANNEL_INPUT.CHANGE", channel, value, isTextField })
441
+ event.preventDefault()
354
442
  }
355
443
  },
356
444
  style: {
@@ -367,15 +455,12 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
367
455
  name: state.context.name,
368
456
  id: dom.getHiddenInputId(state.context),
369
457
  style: visuallyHiddenStyle,
370
- defaultValue: value,
371
- onChange(event) {
372
- const value = event.currentTarget.value
373
- send({ type: "VALUE.SET", value, src: "input.change" })
374
- },
458
+ defaultValue: valueAsString,
375
459
  }),
376
460
 
377
461
  eyeDropperTriggerProps: normalize.button({
378
462
  ...parts.eyeDropperTrigger.attrs,
463
+ type: "button",
379
464
  disabled: isDisabled,
380
465
  "data-disabled": dataAttr(isDisabled),
381
466
  "aria-label": "Pick a color from the screen",
@@ -385,43 +470,40 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
385
470
  },
386
471
  }),
387
472
 
388
- getSwatchBackgroundProps(props: ColorSwatchProps) {
389
- const { value } = props
390
- const alpha = normalizeColor(value).getChannelValue("alpha")
391
- return normalize.element({
392
- ...parts.swatchBackground.attrs,
393
- "data-alpha": alpha,
473
+ swatchGroupProps: normalize.element({
474
+ ...parts.swatchGroup.attrs,
475
+ role: "group",
476
+ }),
477
+
478
+ getSwatchTriggerProps(props) {
479
+ const { value: valueProp } = props
480
+ const color = normalizeColor(valueProp).toFormat(value.getFormat())
481
+ return normalize.button({
482
+ ...parts.swatchTrigger.attrs,
483
+ disabled: isDisabled,
484
+ type: "button",
485
+ "data-value": color.toString("hex"),
486
+ onClick() {
487
+ if (!isInteractive) return
488
+ send({ type: "VALUE.SET", value: color })
489
+ },
394
490
  style: {
395
- width: "100%",
396
- height: "100%",
397
- background: "#fff",
398
- backgroundImage: [
399
- "linear-gradient(-45deg,#0000 75.5%,#bcbcbc 75.5%)",
400
- "linear-gradient(45deg,#0000 75.5%,#bcbcbc 75.5%)",
401
- "linear-gradient(-45deg,#bcbcbc 25.5%,#0000 25.5%)",
402
- "linear-gradient(45deg,#bcbcbc 25.5%,#0000 25.5%)",
403
- ].join(","),
404
- backgroundPosition: "-2px -2px,-2px 6px,6px -10px,-10px -2px",
405
- backgroundSize: "16px 16px",
406
- position: "absolute",
407
- inset: "0px",
408
- zIndex: -1,
491
+ position: "relative",
409
492
  },
410
493
  })
411
494
  },
412
495
 
413
- getSwatchProps(props: ColorSwatchProps) {
414
- const { value, readOnly } = props
415
- const color = normalizeColor(value).toFormat(valueAsColor.getColorFormat())
496
+ getSwatchProps(props) {
497
+ const { value: valueProp, respectAlpha = true } = props
498
+ const colorValue = normalizeColor(valueProp)
499
+ const color = colorValue.toFormat(value.getFormat())
416
500
  return normalize.element({
417
501
  ...parts.swatch.attrs,
418
- onClick() {
419
- if (readOnly || !isInteractive) return
420
- send({ type: "VALUE.SET", value: color })
421
- },
502
+ "data-state": colorValue.isEqual(value) ? "selected" : "unselected",
503
+ "data-value": color.toString("hex"),
422
504
  style: {
423
505
  position: "relative",
424
- background: color.toString("css"),
506
+ background: color.toString(respectAlpha ? "css" : "hex"),
425
507
  },
426
508
  })
427
509
  },
@@ -2,19 +2,28 @@ import type { ColorChannel } from "@zag-js/color-utils"
2
2
  import { getRelativePoint, type Point } from "@zag-js/dom-event"
3
3
  import { createScope, queryAll } from "@zag-js/dom-query"
4
4
  import type { MachineContext as Ctx } from "./color-picker.types"
5
+ import { getFirstFocusable } from "@zag-js/tabbable"
6
+ import { runIfFn } from "@zag-js/utils"
5
7
 
6
8
  export const dom = createScope({
9
+ getRootId: (ctx: Ctx) => ctx.ids?.root ?? `color-picker:${ctx.id}`,
10
+ getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `color-picker:${ctx.id}:label`,
11
+ getHiddenInputId: (ctx: Ctx) => `color-picker:${ctx.id}:hidden-input`,
12
+ getControlId: (ctx: Ctx) => ctx.ids?.control ?? `color-picker:${ctx.id}:control`,
13
+ getTriggerId: (ctx: Ctx) => ctx.ids?.trigger ?? `color-picker:${ctx.id}:trigger`,
7
14
  getContentId: (ctx: Ctx) => ctx.ids?.content ?? `color-picker:${ctx.id}:content`,
15
+ getPositionerId: (ctx: Ctx) => `color-picker:${ctx.id}:positioner`,
16
+
8
17
  getAreaId: (ctx: Ctx) => ctx.ids?.area ?? `color-picker:${ctx.id}:area`,
9
18
  getAreaGradientId: (ctx: Ctx) => ctx.ids?.areaGradient ?? `color-picker:${ctx.id}:area-gradient`,
10
19
  getAreaThumbId: (ctx: Ctx) => ctx.ids?.areaThumb ?? `color-picker:${ctx.id}:area-thumb`,
11
- getChannelSliderTrackId: (ctx: Ctx, channel: ColorChannel) =>
20
+
21
+ getChannelSliderId: (ctx: Ctx, channel: ColorChannel) =>
12
22
  ctx.ids?.channelSliderTrack?.(channel) ?? `color-picker:${ctx.id}:slider-track:${channel}`,
13
23
  getChannelInputId: (ctx: Ctx, channel: string) =>
14
24
  ctx.ids?.channelInput?.(channel) ?? `color-picker:${ctx.id}:input:${channel}`,
15
25
  getChannelSliderThumbId: (ctx: Ctx, channel: ColorChannel) =>
16
26
  ctx.ids?.channelSliderThumb?.(channel) ?? `color-picker:${ctx.id}:slider-thumb:${channel}`,
17
- getHiddenInputId: (ctx: Ctx) => `color-picker:${ctx.id}:hidden-input`,
18
27
 
19
28
  getContentEl: (ctx: Ctx) => dom.getById(ctx, dom.getContentId(ctx)),
20
29
  getAreaThumbEl: (ctx: Ctx) => dom.getById(ctx, dom.getAreaThumbId(ctx)),
@@ -32,8 +41,11 @@ export const dom = createScope({
32
41
  return percent
33
42
  },
34
43
 
44
+ getControlEl: (ctx: Ctx) => dom.getById(ctx, dom.getControlId(ctx)),
45
+ getTriggerEl: (ctx: Ctx) => dom.getById(ctx, dom.getTriggerId(ctx)),
46
+ getPositionerEl: (ctx: Ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
35
47
  getChannelSliderTrackEl: (ctx: Ctx, channel: ColorChannel) => {
36
- return dom.getById(ctx, dom.getChannelSliderTrackId(ctx, channel))
48
+ return dom.getById(ctx, dom.getChannelSliderId(ctx, channel))
37
49
  },
38
50
  getChannelSliderValueFromPoint(ctx: Ctx, point: Point, channel: ColorChannel) {
39
51
  const trackEl = dom.getChannelSliderTrackEl(ctx, channel)
@@ -42,6 +54,16 @@ export const dom = createScope({
42
54
  return percent
43
55
  },
44
56
  getChannelInputEls: (ctx: Ctx) => {
45
- return queryAll<HTMLInputElement>(dom.getContentEl(ctx), "input[data-channel]")
57
+ return [
58
+ ...queryAll<HTMLInputElement>(dom.getContentEl(ctx), "input[data-channel]"),
59
+ ...queryAll<HTMLInputElement>(dom.getControlEl(ctx), "input[data-channel]"),
60
+ ]
61
+ },
62
+ getFirstFocusableEl: (ctx: Ctx) => getFirstFocusable(dom.getContentEl(ctx), "if-empty"),
63
+ getInitialFocusEl: (ctx: Ctx): HTMLElement | undefined => {
64
+ let el: any = runIfFn(ctx.initialFocusEl)
65
+ if (!el && ctx.autoFocus) el = dom.getFirstFocusableEl(ctx)
66
+ if (!el) el = dom.getContentEl(ctx)
67
+ return el
46
68
  },
47
69
  })