@zag-js/color-picker 0.34.0 → 0.35.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,5 +1,5 @@
1
1
  import { parseColor, type Color } from "@zag-js/color-utils"
2
- import { createMachine } from "@zag-js/core"
2
+ import { createMachine, guards } from "@zag-js/core"
3
3
  import { trackDismissableElement } from "@zag-js/dismissable"
4
4
  import { trackPointerMove } from "@zag-js/dom-event"
5
5
  import { raf } from "@zag-js/dom-query"
@@ -19,6 +19,8 @@ import type {
19
19
  } from "./color-picker.types"
20
20
  import { getChannelValue } from "./utils/get-channel-input-value"
21
21
 
22
+ const { and } = guards
23
+
22
24
  export function machine(userContext: UserDefinedContext) {
23
25
  const ctx = compact(userContext)
24
26
  return createMachine<MachineContext, MachineState>(
@@ -36,6 +38,7 @@ export function machine(userContext: UserDefinedContext) {
36
38
  activeChannel: null,
37
39
  activeOrientation: null,
38
40
  fieldsetDisabled: false,
41
+ restoreFocus: true,
39
42
  positioning: {
40
43
  ...ctx.positioning,
41
44
  placement: "bottom",
@@ -80,14 +83,30 @@ export function machine(userContext: UserDefinedContext) {
80
83
  idle: {
81
84
  tags: ["closed"],
82
85
  on: {
83
- OPEN: {
84
- target: "open",
85
- actions: ["setInitialFocus", "invokeOnOpen"],
86
- },
87
- "TRIGGER.CLICK": {
86
+ "CONTROLLED.OPEN": {
88
87
  target: "open",
89
- actions: ["setInitialFocus", "invokeOnOpen"],
88
+ actions: ["setInitialFocus"],
90
89
  },
90
+ OPEN: [
91
+ {
92
+ guard: "isOpenControlled",
93
+ actions: ["invokeOnOpen"],
94
+ },
95
+ {
96
+ target: "open",
97
+ actions: ["invokeOnOpen", "setInitialFocus"],
98
+ },
99
+ ],
100
+ "TRIGGER.CLICK": [
101
+ {
102
+ guard: "isOpenControlled",
103
+ actions: ["invokeOnOpen"],
104
+ },
105
+ {
106
+ target: "open",
107
+ actions: ["invokeOnOpen", "setInitialFocus"],
108
+ },
109
+ ],
91
110
  "CHANNEL_INPUT.FOCUS": {
92
111
  target: "focused",
93
112
  actions: ["setActiveChannel"],
@@ -98,14 +117,30 @@ export function machine(userContext: UserDefinedContext) {
98
117
  focused: {
99
118
  tags: ["closed", "focused"],
100
119
  on: {
101
- OPEN: {
102
- target: "open",
103
- actions: ["setInitialFocus", "invokeOnOpen"],
104
- },
105
- "TRIGGER.CLICK": {
120
+ "CONTROLLED.OPEN": {
106
121
  target: "open",
107
- actions: ["setInitialFocus", "invokeOnOpen"],
122
+ actions: ["setInitialFocus"],
108
123
  },
124
+ OPEN: [
125
+ {
126
+ guard: "isOpenControlled",
127
+ actions: ["invokeOnOpen"],
128
+ },
129
+ {
130
+ target: "open",
131
+ actions: ["invokeOnOpen", "setInitialFocus"],
132
+ },
133
+ ],
134
+ "TRIGGER.CLICK": [
135
+ {
136
+ guard: "isOpenControlled",
137
+ actions: ["invokeOnOpen"],
138
+ },
139
+ {
140
+ target: "open",
141
+ actions: ["invokeOnOpen", "setInitialFocus"],
142
+ },
143
+ ],
109
144
  "CHANNEL_INPUT.FOCUS": {
110
145
  actions: ["setActiveChannel"],
111
146
  },
@@ -123,10 +158,26 @@ export function machine(userContext: UserDefinedContext) {
123
158
  tags: ["open"],
124
159
  activities: ["trackPositioning", "trackDismissableElement"],
125
160
  on: {
126
- "TRIGGER.CLICK": {
127
- target: "idle",
128
- actions: ["invokeOnClose"],
129
- },
161
+ "CONTROLLED.CLOSE": [
162
+ {
163
+ guard: "shouldRestoreFocus",
164
+ target: "focused",
165
+ actions: ["setReturnFocus"],
166
+ },
167
+ {
168
+ target: "idle",
169
+ },
170
+ ],
171
+ "TRIGGER.CLICK": [
172
+ {
173
+ guard: "isOpenControlled",
174
+ actions: ["invokeOnClose"],
175
+ },
176
+ {
177
+ target: "idle",
178
+ actions: ["invokeOnClose"],
179
+ },
180
+ ],
130
181
  "AREA.POINTER_DOWN": {
131
182
  target: "open:dragging",
132
183
  actions: ["setActiveChannel", "setAreaColorFromPoint", "focusAreaThumb"],
@@ -187,25 +238,39 @@ export function machine(userContext: UserDefinedContext) {
187
238
  actions: ["setChannelColorFromInput"],
188
239
  },
189
240
  INTERACT_OUTSIDE: [
241
+ {
242
+ guard: "isOpenControlled",
243
+ actions: ["invokeOnClose"],
244
+ },
190
245
  {
191
246
  guard: "shouldRestoreFocus",
192
247
  target: "focused",
193
- actions: ["setReturnFocus", "invokeOnClose"],
248
+ actions: ["invokeOnClose", "setReturnFocus"],
249
+ },
250
+ {
251
+ target: "idle",
252
+ actions: ["invokeOnClose"],
253
+ },
254
+ ],
255
+ CLOSE: [
256
+ {
257
+ guard: "isOpenControlled",
258
+ actions: ["invokeOnClose"],
194
259
  },
195
260
  {
196
261
  target: "idle",
197
262
  actions: ["invokeOnClose"],
198
263
  },
199
264
  ],
200
- CLOSE: {
201
- target: "idle",
202
- actions: ["invokeOnClose"],
203
- },
204
265
  "SWATCH_TRIGGER.CLICK": [
266
+ {
267
+ guard: and("isOpenControlled", "closeOnSelect"),
268
+ actions: ["setValue", "invokeOnClose"],
269
+ },
205
270
  {
206
271
  guard: "closeOnSelect",
207
272
  target: "focused",
208
- actions: ["setValue", "setReturnFocus", "invokeOnClose"],
273
+ actions: ["setValue", "invokeOnClose", "setReturnFocus"],
209
274
  },
210
275
  {
211
276
  actions: ["setValue"],
@@ -219,6 +284,16 @@ export function machine(userContext: UserDefinedContext) {
219
284
  exit: ["clearActiveChannel"],
220
285
  activities: ["trackPointerMove", "disableTextSelection", "trackPositioning", "trackDismissableElement"],
221
286
  on: {
287
+ "CONTROLLED.CLOSE": [
288
+ {
289
+ guard: "shouldRestoreFocus",
290
+ target: "focused",
291
+ actions: ["setReturnFocus"],
292
+ },
293
+ {
294
+ target: "idle",
295
+ },
296
+ ],
222
297
  "AREA.POINTER_MOVE": {
223
298
  actions: ["setAreaColorFromPoint", "focusAreaThumb"],
224
299
  },
@@ -234,28 +309,39 @@ export function machine(userContext: UserDefinedContext) {
234
309
  actions: ["invokeOnChangeEnd"],
235
310
  },
236
311
  INTERACT_OUTSIDE: [
312
+ {
313
+ guard: "isOpenControlled",
314
+ actions: ["invokeOnClose"],
315
+ },
237
316
  {
238
317
  guard: "shouldRestoreFocus",
239
318
  target: "focused",
240
- actions: ["setReturnFocus", "invokeOnClose"],
319
+ actions: ["invokeOnClose", "setReturnFocus"],
320
+ },
321
+ {
322
+ target: "idle",
323
+ actions: ["invokeOnClose"],
324
+ },
325
+ ],
326
+ CLOSE: [
327
+ {
328
+ guard: "isOpenControlled",
329
+ actions: ["invokeOnClose"],
241
330
  },
242
331
  {
243
332
  target: "idle",
244
333
  actions: ["invokeOnClose"],
245
334
  },
246
335
  ],
247
- CLOSE: {
248
- target: "idle",
249
- actions: ["invokeOnClose"],
250
- },
251
336
  },
252
337
  },
253
338
  },
254
339
  },
255
340
  {
256
341
  guards: {
257
- isTargetFocusable: (_ctx, evt) => evt.restoreFocus,
258
342
  closeOnSelect: (ctx) => !!ctx.closeOnSelect,
343
+ isOpenControlled: (ctx) => !!ctx["open.controlled"],
344
+ shouldRestoreFocus: (ctx) => !!ctx.restoreFocus,
259
345
  },
260
346
  activities: {
261
347
  trackPositioning(ctx) {
@@ -272,19 +358,18 @@ export function machine(userContext: UserDefinedContext) {
272
358
  },
273
359
  trackDismissableElement(ctx, _evt, { send }) {
274
360
  const getContentEl = () => dom.getContentEl(ctx)
275
- let restoreFocus = true
276
361
  return trackDismissableElement(getContentEl, {
277
362
  exclude: dom.getTriggerEl(ctx),
278
363
  defer: true,
279
364
  onInteractOutside(event) {
280
365
  ctx.onInteractOutside?.(event)
281
366
  if (event.defaultPrevented) return
282
- restoreFocus = !(event.detail.focusable || event.detail.contextmenu)
367
+ ctx.restoreFocus = !(event.detail.focusable || event.detail.contextmenu)
283
368
  },
284
369
  onPointerDownOutside: ctx.onPointerDownOutside,
285
370
  onFocusOutside: ctx.onFocusOutside,
286
371
  onDismiss() {
287
- send({ type: "INTERACT_OUTSIDE", restoreFocus })
372
+ send({ type: "INTERACT_OUTSIDE" })
288
373
  },
289
374
  })
290
375
  },
@@ -480,8 +565,8 @@ export function machine(userContext: UserDefinedContext) {
480
565
  invokeOnClose(ctx) {
481
566
  ctx.onOpenChange?.({ open: false })
482
567
  },
483
- toggleVisibility(ctx, _evt, { send }) {
484
- send({ type: ctx.open ? "OPEN" : "CLOSE", src: "controlled" })
568
+ toggleVisibility(ctx, evt, { send }) {
569
+ send({ type: ctx.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt })
485
570
  },
486
571
  },
487
572
  compareFns: {
@@ -93,6 +93,10 @@ interface PublicContext extends CommonProperties, InteractOutsideHandlers {
93
93
  * Whether the color picker is open
94
94
  */
95
95
  open?: boolean
96
+ /**
97
+ * Whether the color picker open state is controlled by the user
98
+ */
99
+ "open.controlled"?: boolean
96
100
  /**
97
101
  * The color format to use
98
102
  */
@@ -133,6 +137,11 @@ type PrivateContext = Context<{
133
137
  * The current placement of the color picker
134
138
  */
135
139
  currentPlacement?: PositioningOptions["placement"]
140
+ /**
141
+ * @internal
142
+ * Whether the color picker should return focus to the trigger when closed
143
+ */
144
+ restoreFocus?: boolean
136
145
  }>
137
146
 
138
147
  type ComputedContext = Readonly<{