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