@zag-js/color-picker 0.47.0 → 0.48.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.
@@ -5,13 +5,12 @@ import {
5
5
  getEventStep,
6
6
  getNativeEvent,
7
7
  isLeftClick,
8
- isModifiedEvent,
8
+ isModifierKey,
9
9
  type EventKeyMap,
10
10
  } from "@zag-js/dom-event"
11
- import { dataAttr, query } from "@zag-js/dom-query"
11
+ import { dataAttr, query, visuallyHiddenStyle } from "@zag-js/dom-query"
12
12
  import { getPlacementStyles } from "@zag-js/popper"
13
13
  import type { NormalizeProps, PropTypes } from "@zag-js/types"
14
- import { visuallyHiddenStyle } from "@zag-js/visually-hidden"
15
14
  import { parts } from "./color-picker.anatomy"
16
15
  import { dom } from "./color-picker.dom"
17
16
  import type {
@@ -32,12 +31,12 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
32
31
  const areaValue = state.context.areaValue
33
32
  const valueAsString = state.context.valueAsString
34
33
 
35
- const isDisabled = state.context.isDisabled
36
- const isInteractive = state.context.isInteractive
34
+ const disabled = state.context.isDisabled
35
+ const interactive = state.context.isInteractive
37
36
 
38
- const isDragging = state.hasTag("dragging")
39
- const isOpen = state.hasTag("open")
40
- const isFocused = state.hasTag("focused")
37
+ const dragging = state.hasTag("dragging")
38
+ const open = state.hasTag("open")
39
+ const focused = state.hasTag("focused")
41
40
 
42
41
  const getAreaChannels = (props: AreaProps) => {
43
42
  const channels = areaValue.getChannels()
@@ -58,21 +57,19 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
58
57
  return {
59
58
  value: color,
60
59
  valueAsString: color.toString("hex"),
61
- isChecked: color.isEqual(value),
62
- isDisabled: props.disabled || !isInteractive,
60
+ checked: color.isEqual(value),
61
+ disabled: props.disabled || !interactive,
63
62
  }
64
63
  }
65
64
 
66
65
  return {
67
- isDragging,
68
- isOpen,
66
+ dragging,
67
+ open,
69
68
  valueAsString,
70
69
  value,
71
- open() {
72
- send({ type: "OPEN" })
73
- },
74
- close() {
75
- send({ type: "CLOSE" })
70
+ setOpen(_open) {
71
+ if (_open === open) return
72
+ send({ type: _open ? "OPEN" : "CLOSE" })
76
73
  },
77
74
  setValue(value) {
78
75
  send({ type: "VALUE.SET", value: normalizeColor(value), src: "set-color" })
@@ -99,7 +96,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
99
96
  ...parts.root.attrs,
100
97
  dir: state.context.dir,
101
98
  id: dom.getRootId(state.context),
102
- "data-disabled": dataAttr(isDisabled),
99
+ "data-disabled": dataAttr(disabled),
103
100
  "data-readonly": dataAttr(state.context.readOnly),
104
101
  style: {
105
102
  "--value": value.toString("css"),
@@ -111,9 +108,9 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
111
108
  dir: state.context.dir,
112
109
  id: dom.getLabelId(state.context),
113
110
  htmlFor: dom.getHiddenInputId(state.context),
114
- "data-disabled": dataAttr(isDisabled),
111
+ "data-disabled": dataAttr(disabled),
115
112
  "data-readonly": dataAttr(state.context.readOnly),
116
- "data-focus": dataAttr(isFocused),
113
+ "data-focus": dataAttr(focused),
117
114
  onClick(event) {
118
115
  event.preventDefault()
119
116
  const inputEl = query(dom.getControlEl(state.context), "[data-channel=hex]")
@@ -125,33 +122,33 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
125
122
  ...parts.control.attrs,
126
123
  id: dom.getControlId(state.context),
127
124
  dir: state.context.dir,
128
- "data-disabled": dataAttr(isDisabled),
125
+ "data-disabled": dataAttr(disabled),
129
126
  "data-readonly": dataAttr(state.context.readOnly),
130
- "data-state": isOpen ? "open" : "closed",
131
- "data-focus": dataAttr(isFocused),
127
+ "data-state": open ? "open" : "closed",
128
+ "data-focus": dataAttr(focused),
132
129
  }),
133
130
 
134
131
  triggerProps: normalize.button({
135
132
  ...parts.trigger.attrs,
136
133
  id: dom.getTriggerId(state.context),
137
134
  dir: state.context.dir,
138
- disabled: isDisabled,
135
+ disabled: disabled,
139
136
  "aria-label": `select color. current color is ${valueAsString}`,
140
137
  "aria-controls": dom.getContentId(state.context),
141
138
  "aria-labelledby": dom.getLabelId(state.context),
142
- "data-disabled": dataAttr(isDisabled),
139
+ "data-disabled": dataAttr(disabled),
143
140
  "data-readonly": dataAttr(state.context.readOnly),
144
141
  "data-placement": currentPlacement,
145
- "aria-expanded": dataAttr(isOpen),
146
- "data-state": isOpen ? "open" : "closed",
147
- "data-focus": dataAttr(isFocused),
142
+ "aria-expanded": dataAttr(open),
143
+ "data-state": open ? "open" : "closed",
144
+ "data-focus": dataAttr(focused),
148
145
  type: "button",
149
146
  onClick() {
150
- if (!isInteractive) return
147
+ if (!interactive) return
151
148
  send({ type: "TRIGGER.CLICK" })
152
149
  },
153
150
  onBlur() {
154
- if (!isInteractive) return
151
+ if (!interactive) return
155
152
  send({ type: "TRIGGER.BLUR" })
156
153
  },
157
154
  style: {
@@ -171,8 +168,8 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
171
168
  id: dom.getContentId(state.context),
172
169
  dir: state.context.dir,
173
170
  "data-placement": currentPlacement,
174
- "data-state": isOpen ? "open" : "closed",
175
- hidden: !isOpen,
171
+ "data-state": open ? "open" : "closed",
172
+ hidden: !open,
176
173
  }),
177
174
 
178
175
  getAreaProps(props = {}) {
@@ -188,10 +185,10 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
188
185
  id: dom.getAreaId(state.context),
189
186
  role: "group",
190
187
  onPointerDown(event) {
191
- if (!isInteractive) return
188
+ if (!interactive) return
192
189
 
193
190
  const evt = getNativeEvent(event)
194
- if (!isLeftClick(evt) || isModifiedEvent(evt)) return
191
+ if (!isLeftClick(evt) || isModifierKey(evt)) return
195
192
 
196
193
  const point = getEventPoint(evt)
197
194
  const channel = { xChannel, yChannel }
@@ -242,8 +239,8 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
242
239
  ...parts.areaThumb.attrs,
243
240
  id: dom.getAreaThumbId(state.context),
244
241
  dir: state.context.dir,
245
- tabIndex: isDisabled ? undefined : 0,
246
- "data-disabled": dataAttr(isDisabled),
242
+ tabIndex: disabled ? undefined : 0,
243
+ "data-disabled": dataAttr(disabled),
247
244
  role: "slider",
248
245
  "aria-valuemin": 0,
249
246
  "aria-valuemax": 100,
@@ -261,11 +258,12 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
261
258
  background: areaValue.withChannelValue("alpha", 1).toString("css"),
262
259
  },
263
260
  onFocus() {
264
- if (!isInteractive) return
261
+ if (!interactive) return
265
262
  send({ type: "AREA.FOCUS", id: "area", channel })
266
263
  },
267
264
  onKeyDown(event) {
268
- if (!isInteractive) return
265
+ if (event.defaultPrevented) return
266
+ if (!interactive) return
269
267
 
270
268
  const step = getEventStep(event)
271
269
 
@@ -330,10 +328,10 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
330
328
  "data-orientation": orientation,
331
329
  role: "presentation",
332
330
  onPointerDown(event) {
333
- if (!isInteractive) return
331
+ if (!interactive) return
334
332
 
335
333
  const evt = getNativeEvent(event)
336
- if (!isLeftClick(evt) || isModifiedEvent(evt)) return
334
+ if (!isLeftClick(evt) || isModifierKey(evt)) return
337
335
 
338
336
  const point = getEventPoint(evt)
339
337
  send({ type: "CHANNEL_SLIDER.POINTER_DOWN", channel, point, id: channel, orientation })
@@ -386,11 +384,11 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
386
384
  id: dom.getChannelSliderThumbId(state.context, channel),
387
385
  role: "slider",
388
386
  "aria-label": channel,
389
- tabIndex: isDisabled ? undefined : 0,
387
+ tabIndex: disabled ? undefined : 0,
390
388
  "data-channel": channel,
391
- "data-disabled": dataAttr(isDisabled),
389
+ "data-disabled": dataAttr(disabled),
392
390
  "data-orientation": orientation,
393
- "aria-disabled": dataAttr(isDisabled),
391
+ "aria-disabled": dataAttr(disabled),
394
392
  "aria-orientation": orientation,
395
393
  "aria-valuemax": maxValue,
396
394
  "aria-valuemin": minValue,
@@ -403,11 +401,13 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
403
401
  ...placementStyles,
404
402
  },
405
403
  onFocus() {
406
- if (!isInteractive) return
404
+ if (!interactive) return
407
405
  send({ type: "CHANNEL_SLIDER.FOCUS", channel })
408
406
  },
409
407
  onKeyDown(event) {
410
- if (!isInteractive) return
408
+ if (event.defaultPrevented) return
409
+ if (!interactive) return
410
+
411
411
  const step = getEventStep(event) * stepValue
412
412
 
413
413
  const keyMap: EventKeyMap = {
@@ -463,32 +463,33 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
463
463
  "aria-label": channel,
464
464
  spellCheck: false,
465
465
  autoComplete: "off",
466
- disabled: isDisabled,
467
- "data-disabled": dataAttr(isDisabled),
466
+ disabled: disabled,
467
+ "data-disabled": dataAttr(disabled),
468
468
  readOnly: state.context.readOnly,
469
469
  defaultValue: getChannelValue(value, channel),
470
470
  min: range?.minValue,
471
471
  max: range?.maxValue,
472
472
  step: range?.step,
473
473
  onBeforeInput(event) {
474
- if (isTextField || !isInteractive) return
474
+ if (isTextField || !interactive) return
475
475
  const value = event.currentTarget.value
476
476
  if (value.match(/[^0-9.]/g)) {
477
477
  event.preventDefault()
478
478
  }
479
479
  },
480
480
  onFocus(event) {
481
- if (!isInteractive) return
481
+ if (!interactive) return
482
482
  send({ type: "CHANNEL_INPUT.FOCUS", channel })
483
483
  event.target.select()
484
484
  },
485
485
  onBlur(event) {
486
- if (!isInteractive) return
486
+ if (!interactive) return
487
487
  const value = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber
488
488
  send({ type: "CHANNEL_INPUT.BLUR", channel, value, isTextField })
489
489
  },
490
490
  onKeyDown(event) {
491
- if (!isInteractive) return
491
+ if (event.defaultPrevented) return
492
+ if (!interactive) return
492
493
  if (event.key === "Enter") {
493
494
  const value = isTextField ? event.currentTarget.value : event.currentTarget.valueAsNumber
494
495
  send({ type: "CHANNEL_INPUT.CHANGE", channel, value, isTextField })
@@ -505,7 +506,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
505
506
 
506
507
  hiddenInputProps: normalize.input({
507
508
  type: "text",
508
- disabled: isDisabled,
509
+ disabled: disabled,
509
510
  name: state.context.name,
510
511
  id: dom.getHiddenInputId(state.context),
511
512
  style: visuallyHiddenStyle,
@@ -516,11 +517,11 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
516
517
  ...parts.eyeDropperTrigger.attrs,
517
518
  type: "button",
518
519
  dir: state.context.dir,
519
- disabled: isDisabled,
520
- "data-disabled": dataAttr(isDisabled),
520
+ disabled: disabled,
521
+ "data-disabled": dataAttr(disabled),
521
522
  "aria-label": "Pick a color from the screen",
522
523
  onClick() {
523
- if (!isInteractive) return
524
+ if (!interactive) return
524
525
  send("EYEDROPPER.CLICK")
525
526
  },
526
527
  }),
@@ -536,15 +537,15 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
536
537
  const triggerState = getSwatchTriggerState(props)
537
538
  return normalize.button({
538
539
  ...parts.swatchTrigger.attrs,
539
- disabled: triggerState.isDisabled,
540
+ disabled: triggerState.disabled,
540
541
  dir: state.context.dir,
541
542
  type: "button",
542
543
  "aria-label": `select ${triggerState.valueAsString} as the color`,
543
- "data-state": triggerState.isChecked ? "checked" : "unchecked",
544
+ "data-state": triggerState.checked ? "checked" : "unchecked",
544
545
  "data-value": triggerState.valueAsString,
545
- "data-disabled": dataAttr(triggerState.isDisabled),
546
+ "data-disabled": dataAttr(triggerState.disabled),
546
547
  onClick() {
547
- if (triggerState.isDisabled) return
548
+ if (triggerState.disabled) return
548
549
  send({ type: "SWATCH_TRIGGER.CLICK", value: triggerState.value })
549
550
  },
550
551
  style: {
@@ -558,7 +559,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
558
559
  return normalize.element({
559
560
  ...parts.swatchIndicator.attrs,
560
561
  dir: state.context.dir,
561
- hidden: !triggerState.isChecked,
562
+ hidden: !triggerState.checked,
562
563
  })
563
564
  },
564
565
 
@@ -568,7 +569,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
568
569
  return normalize.element({
569
570
  ...parts.swatch.attrs,
570
571
  dir: state.context.dir,
571
- "data-state": triggerState.isChecked ? "checked" : "unchecked",
572
+ "data-state": triggerState.checked ? "checked" : "unchecked",
572
573
  "data-value": triggerState.valueAsString,
573
574
  style: {
574
575
  position: "relative",
@@ -594,7 +595,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
594
595
  "aria-label": "change color format",
595
596
  dir: state.context.dir,
596
597
  defaultValue: state.context.format,
597
- disabled: isDisabled,
598
+ disabled: disabled,
598
599
  onChange(event) {
599
600
  const format = assertFormat(event.currentTarget.value)
600
601
  send({ type: "FORMAT.SET", format, src: "format-select" })
@@ -404,11 +404,11 @@ export function machine(userContext: UserDefinedContext) {
404
404
  openEyeDropper(ctx) {
405
405
  const isSupported = "EyeDropper" in dom.getWin(ctx)
406
406
  if (!isSupported) return
407
- const win = dom.getWin(ctx) as any
407
+ const win = dom.getWin(ctx)
408
408
  const picker = new win.EyeDropper()
409
409
  picker
410
410
  .open()
411
- .then(({ sRGBHex }: { sRGBHex: string }) => {
411
+ .then(({ sRGBHex }) => {
412
412
  const format = ctx.value.getFormat()
413
413
  const color = parseColor(sRGBHex).toFormat(format) as Color
414
414
  set.value(ctx, color)
@@ -7,6 +7,20 @@ import type { MaybeFunction } from "@zag-js/utils"
7
7
 
8
8
  export type ExtendedColorChannel = ColorChannel | "hex" | "css"
9
9
 
10
+ // patch the global window object to include the EyeDropper API
11
+
12
+ interface EyeDropper {
13
+ new (): EyeDropper
14
+ open: (options?: { signal?: AbortSignal }) => Promise<{ sRGBHex: string }>
15
+ [Symbol.toStringTag]: "EyeDropper"
16
+ }
17
+
18
+ declare global {
19
+ interface Window {
20
+ EyeDropper: EyeDropper
21
+ }
22
+ }
23
+
10
24
  /* -----------------------------------------------------------------------------
11
25
  * Callback details
12
26
  * -----------------------------------------------------------------------------*/
@@ -218,8 +232,8 @@ export interface SwatchTriggerProps {
218
232
  export interface SwatchTriggerState {
219
233
  value: Color
220
234
  valueAsString: string
221
- isChecked: boolean
222
- isDisabled: boolean
235
+ checked: boolean
236
+ disabled: boolean
223
237
  }
224
238
 
225
239
  export interface SwatchProps {
@@ -241,11 +255,11 @@ export interface MachineApi<T extends PropTypes = PropTypes> {
241
255
  /**
242
256
  * Whether the color picker is being dragged
243
257
  */
244
- isDragging: boolean
258
+ dragging: boolean
245
259
  /**
246
260
  * Whether the color picker is open
247
261
  */
248
- isOpen: boolean
262
+ open: boolean
249
263
  /**
250
264
  * The current color value (as a string)
251
265
  */
@@ -283,13 +297,9 @@ export interface MachineApi<T extends PropTypes = PropTypes> {
283
297
  */
284
298
  setAlpha(value: number): void
285
299
  /**
286
- * Function to open the color picker
287
- */
288
- open(): void
289
- /**
290
- * Function to close the color picker
300
+ * Function to open or close the color picker
291
301
  */
292
- close(): void
302
+ setOpen(open: boolean): void
293
303
 
294
304
  rootProps: T["element"]
295
305
  labelProps: T["element"]