@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,14 +1,21 @@
1
1
  import type { Color, ColorAxes, ColorChannel, ColorFormat, ColorType } from "@zag-js/color-utils"
2
2
  import type { StateMachine as S } from "@zag-js/core"
3
- import type { CommonProperties, Context, Orientation, PropTypes, RequiredBy } from "@zag-js/types"
3
+ import type { InteractOutsideHandlers } from "@zag-js/dismissable"
4
+ import type { PositioningOptions } from "@zag-js/popper"
5
+ import type { CommonProperties, Context, MaybeElement, Orientation, PropTypes, RequiredBy } from "@zag-js/types"
6
+ import type { MaybeFunction } from "@zag-js/utils"
4
7
 
5
8
  /* -----------------------------------------------------------------------------
6
9
  * Callback details
7
10
  * -----------------------------------------------------------------------------*/
8
11
 
9
12
  export interface ValueChangeDetails {
10
- value: string
11
- valueAsColor: Color
13
+ value: Color
14
+ valueAsString: string
15
+ }
16
+
17
+ export interface OpenChangeDetails {
18
+ open: boolean
12
19
  }
13
20
 
14
21
  /* -----------------------------------------------------------------------------
@@ -18,6 +25,11 @@ export interface ValueChangeDetails {
18
25
  export type ExtendedColorChannel = ColorChannel | "hex" | "css"
19
26
 
20
27
  type ElementIds = Partial<{
28
+ root: string
29
+ control: string
30
+ trigger: string
31
+ label: string
32
+ input: string
21
33
  content: string
22
34
  area: string
23
35
  areaGradient: string
@@ -27,7 +39,7 @@ type ElementIds = Partial<{
27
39
  channelSliderThumb(id: ColorChannel): string
28
40
  }>
29
41
 
30
- interface PublicContext extends CommonProperties {
42
+ interface PublicContext extends CommonProperties, InteractOutsideHandlers {
31
43
  /**
32
44
  * The ids of the elements in the color picker. Useful for composition.
33
45
  */
@@ -39,7 +51,7 @@ interface PublicContext extends CommonProperties {
39
51
  /**
40
52
  * The current color value
41
53
  */
42
- value: string
54
+ value: Color
43
55
  /**
44
56
  * Whether the color picker is disabled
45
57
  */
@@ -57,9 +69,30 @@ interface PublicContext extends CommonProperties {
57
69
  */
58
70
  onValueChangeEnd?: (details: ValueChangeDetails) => void
59
71
  /**
60
- * The name for the form input
72
+ * Handler that is called when the user opens or closes the color picker.
73
+ */
74
+ onOpenChange?: (details: OpenChangeDetails) => void
75
+ /**
76
+ * The name for the form input
61
77
  */
62
78
  name?: string
79
+ /**
80
+ * The positioning options for the color picker
81
+ */
82
+ positioning: PositioningOptions
83
+ /**
84
+ * Whether to automatically set focus on the first focusable
85
+ * content within the color picker when opened.
86
+ */
87
+ autoFocus?: boolean
88
+ /**
89
+ * The initial focus element when the color picker is opened.
90
+ */
91
+ initialFocusEl?: MaybeFunction<MaybeElement>
92
+ /**
93
+ * Whether the color picker is open
94
+ */
95
+ open?: boolean
63
96
  }
64
97
 
65
98
  type PrivateContext = Context<{
@@ -83,6 +116,11 @@ type PrivateContext = Context<{
83
116
  * Whether the checkbox's fieldset is disabled
84
117
  */
85
118
  fieldsetDisabled: boolean
119
+ /**
120
+ * @internal
121
+ * The current placement of the color picker
122
+ */
123
+ currentPlacement?: PositioningOptions["placement"]
86
124
  }>
87
125
 
88
126
  type ComputedContext = Readonly<{
@@ -100,7 +138,7 @@ type ComputedContext = Readonly<{
100
138
  * @computed
101
139
  * The color value as a Color object
102
140
  */
103
- valueAsColor: Color
141
+ valueAsString: string
104
142
  /**
105
143
  * @computed
106
144
  * Whether the color picker is disabled
@@ -113,7 +151,8 @@ export type UserDefinedContext = RequiredBy<PublicContext, "id">
113
151
  export interface MachineContext extends PublicContext, PrivateContext, ComputedContext {}
114
152
 
115
153
  export interface MachineState {
116
- value: "idle" | "focused" | "dragging"
154
+ tags: "open" | "closed" | "dragging" | "focused"
155
+ value: "idle" | "focused" | "open" | "open:dragging"
117
156
  }
118
157
 
119
158
  export type State = S.State<MachineContext, MachineState>
@@ -124,43 +163,60 @@ export type Send = S.Send<S.AnyEventObject>
124
163
  * Component API
125
164
  * -----------------------------------------------------------------------------*/
126
165
 
127
- export interface ColorChannelProps {
166
+ export interface ChannelProps {
128
167
  channel: ColorChannel
129
168
  orientation?: Orientation
130
169
  }
131
170
 
132
- export interface ColorChannelInputProps {
171
+ export interface ChannelInputProps {
133
172
  channel: ExtendedColorChannel
134
173
  orientation?: Orientation
135
174
  }
136
175
 
137
- export interface ColorAreaProps {
138
- xChannel: ColorChannel
139
- yChannel: ColorChannel
176
+ export interface AreaProps {
177
+ xChannel?: ColorChannel
178
+ yChannel?: ColorChannel
140
179
  }
141
180
 
142
- export interface ColorSwatchProps {
143
- readOnly?: boolean
181
+ export interface SwatchTriggerProps {
182
+ /**
183
+ * The color value
184
+ */
144
185
  value: string | Color
145
186
  }
146
187
 
188
+ export interface SwatchProps {
189
+ /**
190
+ * The color value
191
+ */
192
+ value: string | Color
193
+ /**
194
+ * Whether to include the alpha channel in the color
195
+ */
196
+ respectAlpha?: boolean
197
+ }
198
+
199
+ export interface TransparencyGridProps {
200
+ size?: string
201
+ }
202
+
147
203
  export interface MachineApi<T extends PropTypes = PropTypes> {
148
204
  /**
149
205
  * Whether the color picker is being dragged
150
206
  */
151
207
  isDragging: boolean
152
208
  /**
153
- * The current color value (as a string)
209
+ * Whether the color picker is open
154
210
  */
155
- value: string
211
+ isOpen: boolean
156
212
  /**
157
- * The current color value (as a Color object)
213
+ * The current color value (as a string)
158
214
  */
159
- valueAsColor: Color
215
+ value: Color
160
216
  /**
161
- * The alpha value of the color
217
+ * The current color value (as a Color object)
162
218
  */
163
- alpha: number
219
+ valueAsString: string
164
220
  /**
165
221
  * The current color channels of the color
166
222
  */
@@ -169,31 +225,55 @@ export interface MachineApi<T extends PropTypes = PropTypes> {
169
225
  * Function to set the color value
170
226
  */
171
227
  setColor(value: string | Color): void
228
+ /**
229
+ * Function to set the color value
230
+ */
231
+ getChannelValue(channel: ColorChannel): string
172
232
  /**
173
233
  * Function to set the color value of a specific channel
174
234
  */
175
235
  setChannelValue(channel: ColorChannel, value: number): void
236
+ /**
237
+ * The current color format
238
+ */
239
+ format: ColorFormat
176
240
  /**
177
241
  * Function to set the color format
178
242
  */
179
243
  setFormat(format: ColorFormat): void
244
+ /**
245
+ * The alpha value of the color
246
+ */
247
+ alpha: number
180
248
  /**
181
249
  * Function to set the color alpha
182
250
  */
183
251
  setAlpha(value: number): void
184
252
 
253
+ rootProps: T["element"]
254
+ labelProps: T["element"]
255
+ controlProps: T["element"]
256
+ triggerProps: T["button"]
257
+ positionerProps: T["element"]
185
258
  contentProps: T["element"]
186
259
  hiddenInputProps: T["input"]
187
- getAreaProps(props: ColorAreaProps): T["element"]
188
- getAreaGradientProps(props: ColorAreaProps): T["element"]
189
- getAreaThumbProps(props: ColorAreaProps): T["element"]
190
- getChannelSliderTrackProps(props: ColorChannelProps): T["element"]
191
- getChannelSliderBackgroundProps(props: ColorChannelProps): T["element"]
192
- getChannelSliderThumbProps(props: ColorChannelProps): T["element"]
193
- getChannelInputProps(props: ColorChannelInputProps): T["input"]
260
+
261
+ getAreaProps(props?: AreaProps): T["element"]
262
+ getAreaBackgroundProps(props?: AreaProps): T["element"]
263
+ getAreaThumbProps(props?: AreaProps): T["element"]
264
+
265
+ getChannelSliderProps(props: ChannelProps): T["element"]
266
+ getChannelSliderTrackProps(props: ChannelProps): T["element"]
267
+ getChannelSliderThumbProps(props: ChannelProps): T["element"]
268
+ getChannelInputProps(props: ChannelInputProps): T["input"]
269
+
270
+ getTransparencyGridProps(props?: TransparencyGridProps): T["element"]
271
+
194
272
  eyeDropperTriggerProps: T["button"]
195
- getSwatchBackgroundProps(props: ColorSwatchProps): T["element"]
196
- getSwatchProps(props: ColorSwatchProps): T["element"]
273
+
274
+ swatchGroupProps: T["element"]
275
+ getSwatchTriggerProps(props: SwatchTriggerProps): T["button"]
276
+ getSwatchProps(props: SwatchProps): T["element"]
197
277
  }
198
278
 
199
279
  /* -----------------------------------------------------------------------------
package/src/index.ts CHANGED
@@ -1,16 +1,19 @@
1
1
  export { anatomy } from "./color-picker.anatomy"
2
2
  export { connect } from "./color-picker.connect"
3
3
  export { machine } from "./color-picker.machine"
4
+ export { parse } from "./color-picker.parse"
4
5
  export type {
6
+ MachineApi as Api,
7
+ AreaProps,
8
+ ChannelInputProps,
9
+ ChannelProps,
5
10
  Color,
6
11
  ColorAxes,
7
- ColorFormat,
8
- ColorAreaProps,
9
12
  ColorChannel,
10
- ColorChannelProps,
11
- ColorChannelInputProps,
12
- ColorSwatchProps,
13
+ ColorFormat,
13
14
  ColorType,
14
15
  UserDefinedContext as Context,
15
- MachineApi as Api,
16
+ SwatchProps,
17
+ TransparencyGridProps,
18
+ SwatchTriggerProps,
16
19
  } from "./color-picker.types"
@@ -1,44 +1,78 @@
1
- import type { Color } from "@zag-js/color-utils"
1
+ import { parseColor, type Color, type ColorChannelRange } from "@zag-js/color-utils"
2
2
  import type { ExtendedColorChannel } from "../color-picker.types"
3
3
 
4
- export function getChannelInputValue(color: Color, channel: ExtendedColorChannel | null | undefined) {
5
- if (channel == null) return
4
+ export function getChannelValue(color: Color, channel: ExtendedColorChannel | null | undefined): string {
5
+ if (channel == null) return ""
6
+
7
+ if (channel === "hex") {
8
+ return color.toString("hex")
9
+ }
10
+
11
+ if (channel === "css") {
12
+ return color.toString("css")
13
+ }
14
+
15
+ if (channel in color) {
16
+ return color.getChannelValue(channel).toString()
17
+ }
18
+
19
+ const isHSL = color.getFormat() === "hsl"
6
20
 
7
21
  switch (channel) {
8
- case "hex":
9
- return color.toString("hex")
10
- case "css":
11
- return color.toString("css")
12
22
  case "hue":
23
+ return isHSL
24
+ ? color.toFormat("hsla").getChannelValue("hue").toString()
25
+ : color.toFormat("hsba").getChannelValue("hue").toString()
26
+
13
27
  case "saturation":
28
+ return isHSL
29
+ ? color.toFormat("hsl").getChannelValue("saturation").toString()
30
+ : color.toFormat("hsb").getChannelValue("saturation").toString()
31
+
14
32
  case "lightness":
15
- return color.toFormat("hsl").getChannelValue("lightness").toString()
33
+ return color.toFormat("hsla").getChannelValue("lightness").toString()
34
+
16
35
  case "brightness":
17
- return color.toFormat("hsb").getChannelValue("brightness").toString()
36
+ return color.toFormat("hsba").getChannelValue("brightness").toString()
37
+
18
38
  case "red":
19
39
  case "green":
20
40
  case "blue":
21
- return color.toFormat("rgb").getChannelValue(channel).toString()
41
+ return color.toFormat("rgba").getChannelValue(channel).toString()
42
+
22
43
  default:
23
44
  return color.getChannelValue(channel).toString()
24
45
  }
25
46
  }
26
47
 
27
- export function getChannelInputRange(color: Color, channel: ExtendedColorChannel) {
48
+ export function getChannelRange(color: Color, channel: ExtendedColorChannel): ColorChannelRange | undefined {
28
49
  switch (channel) {
29
50
  case "hex":
51
+ const minColor = parseColor("#000000")
52
+ const maxColor = parseColor("#FFFFFF")
53
+ return {
54
+ minValue: minColor.toHexInt(),
55
+ maxValue: maxColor.toHexInt(),
56
+ pageSize: 10,
57
+ step: 1,
58
+ }
59
+
30
60
  case "css":
31
61
  return undefined
62
+
32
63
  case "hue":
33
64
  case "saturation":
34
65
  case "lightness":
35
66
  return color.toFormat("hsl").getChannelRange(channel)
67
+
36
68
  case "brightness":
37
69
  return color.toFormat("hsb").getChannelRange(channel)
70
+
38
71
  case "red":
39
72
  case "green":
40
73
  case "blue":
41
74
  return color.toFormat("rgb").getChannelRange(channel)
75
+
42
76
  default:
43
77
  return color.getChannelRange(channel)
44
78
  }
@@ -1,6 +1,6 @@
1
- import type { ColorChannelProps, MachineContext } from "../color-picker.types"
1
+ import type { ChannelProps, Color, MachineContext } from "../color-picker.types"
2
2
 
3
- function getSliderBgDirection(orientation: "vertical" | "horizontal", dir: "ltr" | "rtl") {
3
+ function getSliderBackgroundDirection(orientation: "vertical" | "horizontal", dir: "ltr" | "rtl") {
4
4
  if (orientation === "vertical") {
5
5
  return "top"
6
6
  } else if (dir === "ltr") {
@@ -10,22 +10,24 @@ function getSliderBgDirection(orientation: "vertical" | "horizontal", dir: "ltr"
10
10
  }
11
11
  }
12
12
 
13
- export const getSliderBgImage = (ctx: MachineContext, props: Required<ColorChannelProps>) => {
14
- const { channel } = props
15
-
16
- const dir = getSliderBgDirection(props.orientation, ctx.dir!)
17
- const value = ctx.valueAsColor
13
+ interface SliderBackgroundProps extends Required<ChannelProps> {
14
+ value: Color
15
+ dir: MachineContext["dir"]
16
+ }
18
17
 
19
- const { minValue, maxValue } = ctx.valueAsColor.getChannelRange(channel)
18
+ export const getSliderBackground = (props: SliderBackgroundProps) => {
19
+ const { channel, value, dir } = props
20
+ const bgDirection = getSliderBackgroundDirection(props.orientation, dir!)
21
+ const { minValue, maxValue } = value.getChannelRange(channel)
20
22
 
21
23
  switch (channel) {
22
24
  case "hue":
23
- return `linear-gradient(to ${dir}, rgb(255, 0, 0) 0%, rgb(255, 255, 0) 17%, rgb(0, 255, 0) 33%, rgb(0, 255, 255) 50%, rgb(0, 0, 255) 67%, rgb(255, 0, 255) 83%, rgb(255, 0, 0) 100%)`
25
+ return `linear-gradient(to ${bgDirection}, rgb(255, 0, 0) 0%, rgb(255, 255, 0) 17%, rgb(0, 255, 0) 33%, rgb(0, 255, 255) 50%, rgb(0, 0, 255) 67%, rgb(255, 0, 255) 83%, rgb(255, 0, 0) 100%)`
24
26
  case "lightness": {
25
- let start = ctx.valueAsColor.withChannelValue(channel, minValue).toString("css")
27
+ let start = value.withChannelValue(channel, minValue).toString("css")
26
28
  let middle = value.withChannelValue(channel, (maxValue - minValue) / 2).toString("css")
27
29
  let end = value.withChannelValue(channel, maxValue).toString("css")
28
- return `linear-gradient(to ${dir}, ${start}, ${middle}, ${end})`
30
+ return `linear-gradient(to ${bgDirection}, ${start}, ${middle}, ${end})`
29
31
  }
30
32
  case "saturation":
31
33
  case "brightness":
@@ -35,7 +37,7 @@ export const getSliderBgImage = (ctx: MachineContext, props: Required<ColorChann
35
37
  case "alpha": {
36
38
  let start = value.withChannelValue(channel, minValue).toString("css")
37
39
  let end = value.withChannelValue(channel, maxValue).toString("css")
38
- return `linear-gradient(to ${dir}, ${start}, ${end})`
40
+ return `linear-gradient(to ${bgDirection}, ${start}, ${end})`
39
41
  }
40
42
  default:
41
43
  throw new Error("Unknown color channel: " + channel)
@@ -1,60 +0,0 @@
1
- import type { Color, ColorChannel, ColorType } from "@zag-js/color-utils"
2
- import { getPercentValue, snapValueToStep } from "@zag-js/numeric-range"
3
-
4
- export function getChannelDetails(color: Color, xChannel: ColorChannel, yChannel: ColorChannel) {
5
- const channels = color.getColorSpaceAxes({ xChannel, yChannel })
6
-
7
- const xChannelRange = color.getChannelRange(channels.xChannel)
8
- const yChannelRange = color.getChannelRange(channels.yChannel)
9
-
10
- const { minValue: minValueX, maxValue: maxValueX, step: stepX, pageSize: pageSizeX } = xChannelRange
11
- const { minValue: minValueY, maxValue: maxValueY, step: stepY, pageSize: pageSizeY } = yChannelRange
12
-
13
- const xValue = color.getChannelValue(channels.xChannel)
14
- const yValue = color.getChannelValue(channels.yChannel)
15
-
16
- return {
17
- channels,
18
- xChannelStep: stepX,
19
- yChannelStep: stepY,
20
- xChannelPageStep: pageSizeX,
21
- yChannelPageStep: pageSizeY,
22
- xValue,
23
- yValue,
24
- getThumbPosition() {
25
- let x = (xValue - minValueX) / (maxValueX - minValueX)
26
- let y = 1 - (yValue - minValueY) / (maxValueY - minValueY)
27
- return { x, y }
28
- },
29
- incrementX(stepSize: number) {
30
- return xValue + stepSize > maxValueX ? maxValueX : snapValueToStep(xValue + stepSize, minValueX, maxValueX, stepX)
31
- },
32
- incrementY(stepSize: number) {
33
- return yValue + stepSize > maxValueY ? maxValueY : snapValueToStep(yValue + stepSize, minValueY, maxValueY, stepY)
34
- },
35
- decrementX(stepSize: number) {
36
- return snapValueToStep(xValue - stepSize, minValueX, maxValueX, stepX)
37
- },
38
- decrementY(stepSize: number) {
39
- return snapValueToStep(yValue - stepSize, minValueY, maxValueY, stepY)
40
- },
41
- getColorFromPoint(x: number, y: number) {
42
- let newXValue = getPercentValue(x, minValueX, maxValueX, stepX)
43
- let newYValue = getPercentValue(1 - y, minValueY, maxValueY, stepY)
44
-
45
- let newColor: ColorType | undefined
46
-
47
- if (newXValue !== xValue) {
48
- newXValue = snapValueToStep(newXValue, minValueX, maxValueX, stepX)
49
- newColor = color.withChannelValue(channels.xChannel, newXValue)
50
- }
51
-
52
- if (newYValue !== yValue) {
53
- newYValue = snapValueToStep(newYValue, minValueY, maxValueY, stepY)
54
- newColor = (newColor || color).withChannelValue(channels.yChannel, newYValue)
55
- }
56
-
57
- return newColor
58
- },
59
- }
60
- }