@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,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,40 @@ 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 color format to use (rgb, hsl, hsb)
81
+ * @default "hsl"
82
+ */
83
+ format?: ColorFormat
84
+ /**
85
+ * The alpha format to use (decimal, percent)
86
+ * @default "decimal"
87
+ */
88
+ alphaFormat?: "decimal" | "percent"
89
+ /**
90
+ * The positioning options for the color picker
91
+ */
92
+ positioning: PositioningOptions
93
+ /**
94
+ * Whether to automatically set focus on the first focusable
95
+ * content within the color picker when opened.
96
+ */
97
+ autoFocus?: boolean
98
+ /**
99
+ * The initial focus element when the color picker is opened.
100
+ */
101
+ initialFocusEl?: MaybeFunction<MaybeElement>
102
+ /**
103
+ * Whether the color picker is open
104
+ */
105
+ open?: boolean
63
106
  }
64
107
 
65
108
  type PrivateContext = Context<{
@@ -83,6 +126,11 @@ type PrivateContext = Context<{
83
126
  * Whether the checkbox's fieldset is disabled
84
127
  */
85
128
  fieldsetDisabled: boolean
129
+ /**
130
+ * @internal
131
+ * The current placement of the color picker
132
+ */
133
+ currentPlacement?: PositioningOptions["placement"]
86
134
  }>
87
135
 
88
136
  type ComputedContext = Readonly<{
@@ -100,7 +148,7 @@ type ComputedContext = Readonly<{
100
148
  * @computed
101
149
  * The color value as a Color object
102
150
  */
103
- valueAsColor: Color
151
+ valueAsString: string
104
152
  /**
105
153
  * @computed
106
154
  * Whether the color picker is disabled
@@ -113,7 +161,8 @@ export type UserDefinedContext = RequiredBy<PublicContext, "id">
113
161
  export interface MachineContext extends PublicContext, PrivateContext, ComputedContext {}
114
162
 
115
163
  export interface MachineState {
116
- value: "idle" | "focused" | "dragging"
164
+ tags: "open" | "closed" | "dragging" | "focused"
165
+ value: "idle" | "focused" | "open" | "open:dragging"
117
166
  }
118
167
 
119
168
  export type State = S.State<MachineContext, MachineState>
@@ -124,43 +173,60 @@ export type Send = S.Send<S.AnyEventObject>
124
173
  * Component API
125
174
  * -----------------------------------------------------------------------------*/
126
175
 
127
- export interface ColorChannelProps {
176
+ export interface ChannelProps {
128
177
  channel: ColorChannel
129
178
  orientation?: Orientation
130
179
  }
131
180
 
132
- export interface ColorChannelInputProps {
181
+ export interface ChannelInputProps {
133
182
  channel: ExtendedColorChannel
134
183
  orientation?: Orientation
135
184
  }
136
185
 
137
- export interface ColorAreaProps {
138
- xChannel: ColorChannel
139
- yChannel: ColorChannel
186
+ export interface AreaProps {
187
+ xChannel?: ColorChannel
188
+ yChannel?: ColorChannel
140
189
  }
141
190
 
142
- export interface ColorSwatchProps {
143
- readOnly?: boolean
191
+ export interface SwatchTriggerProps {
192
+ /**
193
+ * The color value
194
+ */
144
195
  value: string | Color
145
196
  }
146
197
 
198
+ export interface SwatchProps {
199
+ /**
200
+ * The color value
201
+ */
202
+ value: string | Color
203
+ /**
204
+ * Whether to include the alpha channel in the color
205
+ */
206
+ respectAlpha?: boolean
207
+ }
208
+
209
+ export interface TransparancyGridProps {
210
+ size: string
211
+ }
212
+
147
213
  export interface MachineApi<T extends PropTypes = PropTypes> {
148
214
  /**
149
215
  * Whether the color picker is being dragged
150
216
  */
151
217
  isDragging: boolean
152
218
  /**
153
- * The current color value (as a string)
219
+ * Whether the color picker is open
154
220
  */
155
- value: string
221
+ isOpen: boolean
156
222
  /**
157
- * The current color value (as a Color object)
223
+ * The current color value (as a string)
158
224
  */
159
- valueAsColor: Color
225
+ value: Color
160
226
  /**
161
- * The alpha value of the color
227
+ * The current color value (as a Color object)
162
228
  */
163
- alpha: number
229
+ valueAsString: string
164
230
  /**
165
231
  * The current color channels of the color
166
232
  */
@@ -169,6 +235,10 @@ export interface MachineApi<T extends PropTypes = PropTypes> {
169
235
  * Function to set the color value
170
236
  */
171
237
  setColor(value: string | Color): void
238
+ /**
239
+ * Function to set the color value
240
+ */
241
+ getChannelValue(channel: ColorChannel): string
172
242
  /**
173
243
  * Function to set the color value of a specific channel
174
244
  */
@@ -177,23 +247,39 @@ export interface MachineApi<T extends PropTypes = PropTypes> {
177
247
  * Function to set the color format
178
248
  */
179
249
  setFormat(format: ColorFormat): void
250
+ /**
251
+ * The alpha value of the color
252
+ */
253
+ getAlpha(): number
180
254
  /**
181
255
  * Function to set the color alpha
182
256
  */
183
257
  setAlpha(value: number): void
184
258
 
259
+ rootProps: T["element"]
260
+ labelProps: T["element"]
261
+ controlProps: T["element"]
262
+ triggerProps: T["button"]
263
+ positionerProps: T["element"]
185
264
  contentProps: T["element"]
186
265
  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"]
266
+
267
+ getAreaProps(props?: AreaProps): T["element"]
268
+ getAreaBackgroundProps(props?: AreaProps): T["element"]
269
+ getAreaThumbProps(props?: AreaProps): T["element"]
270
+
271
+ getChannelSliderProps(props: ChannelProps): T["element"]
272
+ getChannelSliderTrackProps(props: ChannelProps): T["element"]
273
+ getChannelSliderThumbProps(props: ChannelProps): T["element"]
274
+ getChannelInputProps(props: ChannelInputProps): T["input"]
275
+
276
+ getTransparencyGridProps(props: TransparancyGridProps): T["element"]
277
+
194
278
  eyeDropperTriggerProps: T["button"]
195
- getSwatchBackgroundProps(props: ColorSwatchProps): T["element"]
196
- getSwatchProps(props: ColorSwatchProps): T["element"]
279
+
280
+ swatchGroupProps: T["element"]
281
+ getSwatchTriggerProps(props: SwatchTriggerProps): T["button"]
282
+ getSwatchProps(props: SwatchProps): T["element"]
197
283
  }
198
284
 
199
285
  /* -----------------------------------------------------------------------------
package/src/index.ts CHANGED
@@ -1,16 +1,17 @@
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,
16
17
  } 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, 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,19 +10,19 @@ function getSliderBgDirection(orientation: "vertical" | "horizontal", dir: "ltr"
10
10
  }
11
11
  }
12
12
 
13
- export const getSliderBgImage = (ctx: MachineContext, props: Required<ColorChannelProps>) => {
13
+ export const getSliderBackground = (ctx: MachineContext, props: Required<ChannelProps>) => {
14
14
  const { channel } = props
15
15
 
16
- const dir = getSliderBgDirection(props.orientation, ctx.dir!)
17
- const value = ctx.valueAsColor
16
+ const dir = getSliderBackgroundDirection(props.orientation, ctx.dir!)
17
+ const value = ctx.value
18
18
 
19
- const { minValue, maxValue } = ctx.valueAsColor.getChannelRange(channel)
19
+ const { minValue, maxValue } = value.getChannelRange(channel)
20
20
 
21
21
  switch (channel) {
22
22
  case "hue":
23
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%)`
24
24
  case "lightness": {
25
- let start = ctx.valueAsColor.withChannelValue(channel, minValue).toString("css")
25
+ let start = value.withChannelValue(channel, minValue).toString("css")
26
26
  let middle = value.withChannelValue(channel, (maxValue - minValue) / 2).toString("css")
27
27
  let end = value.withChannelValue(channel, maxValue).toString("css")
28
28
  return `linear-gradient(to ${dir}, ${start}, ${middle}, ${end})`
@@ -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
- }