@zag-js/color-picker 1.43.0 → 2.0.0-next.1

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.
Files changed (49) hide show
  1. package/dist/color-picker.anatomy.d.mts +2 -2
  2. package/dist/color-picker.anatomy.d.ts +2 -2
  3. package/dist/color-picker.anatomy.js +1 -0
  4. package/dist/color-picker.anatomy.mjs +1 -0
  5. package/dist/color-picker.connect.d.mts +1 -0
  6. package/dist/color-picker.connect.d.ts +1 -0
  7. package/dist/color-picker.connect.js +130 -58
  8. package/dist/color-picker.connect.mjs +139 -60
  9. package/dist/color-picker.dom.js +23 -22
  10. package/dist/color-picker.dom.mjs +23 -22
  11. package/dist/color-picker.machine.d.mts +1 -0
  12. package/dist/color-picker.machine.d.ts +1 -0
  13. package/dist/color-picker.machine.js +43 -16
  14. package/dist/color-picker.machine.mjs +43 -16
  15. package/dist/color-picker.props.d.mts +1 -0
  16. package/dist/color-picker.props.d.ts +1 -0
  17. package/dist/color-picker.types.d.mts +115 -3
  18. package/dist/color-picker.types.d.ts +115 -3
  19. package/dist/index.d.mts +2 -1
  20. package/dist/index.d.ts +2 -1
  21. package/dist/utils/get-area-format.d.mts +6 -0
  22. package/dist/utils/get-area-format.d.ts +6 -0
  23. package/dist/utils/get-area-format.js +37 -0
  24. package/dist/utils/get-area-format.mjs +12 -0
  25. package/dist/utils/get-channel-display-color.d.mts +2 -1
  26. package/dist/utils/get-channel-display-color.d.ts +2 -1
  27. package/dist/utils/get-channel-display-color.js +7 -0
  28. package/dist/utils/get-channel-display-color.mjs +7 -0
  29. package/dist/utils/get-channel-input-value.d.mts +1 -0
  30. package/dist/utils/get-channel-input-value.d.ts +1 -0
  31. package/dist/utils/get-channel-input-value.js +39 -8
  32. package/dist/utils/get-channel-input-value.mjs +39 -8
  33. package/dist/utils/get-gamut-overlay-path.d.mts +33 -0
  34. package/dist/utils/get-gamut-overlay-path.d.ts +33 -0
  35. package/dist/utils/get-gamut-overlay-path.js +81 -0
  36. package/dist/utils/get-gamut-overlay-path.mjs +55 -0
  37. package/dist/utils/get-gamut-overlay-path.test.d.mts +2 -0
  38. package/dist/utils/get-gamut-overlay-path.test.d.ts +2 -0
  39. package/dist/utils/get-gamut-overlay-path.test.js +35 -0
  40. package/dist/utils/get-gamut-overlay-path.test.mjs +33 -0
  41. package/dist/utils/get-slider-background.d.mts +1 -0
  42. package/dist/utils/get-slider-background.d.ts +1 -0
  43. package/dist/utils/get-slider-background.js +16 -1
  44. package/dist/utils/get-slider-background.mjs +16 -1
  45. package/dist/utils/is-srgb-gamut.d.mts +6 -0
  46. package/dist/utils/is-srgb-gamut.d.ts +6 -0
  47. package/dist/utils/is-srgb-gamut.js +33 -0
  48. package/dist/utils/is-srgb-gamut.mjs +8 -0
  49. package/package.json +9 -9
@@ -41,6 +41,7 @@ var import_popper = require("@zag-js/popper");
41
41
  var import_utils = require("@zag-js/utils");
42
42
  var dom = __toESM(require("./color-picker.dom.js"));
43
43
  var import_color_picker = require("./color-picker.parse.js");
44
+ var import_get_area_format = require("./utils/get-area-format.js");
44
45
  var import_get_channel_input_value = require("./utils/get-channel-input-value.js");
45
46
  var import_is_valid_hex = require("./utils/is-valid-hex.js");
46
47
  var { and } = (0, import_core.createGuards)();
@@ -71,6 +72,9 @@ var machine = (0, import_core.createMachine)({
71
72
  },
72
73
  context({ prop, bindable, getContext }) {
73
74
  return {
75
+ layer: bindable(() => ({
76
+ defaultValue: null
77
+ })),
74
78
  value: bindable(() => ({
75
79
  defaultValue: prop("defaultValue").toFormat(prop("format") ?? prop("defaultFormat")),
76
80
  value: prop("value")?.toFormat(prop("format") ?? prop("defaultFormat")),
@@ -96,6 +100,8 @@ var machine = (0, import_core.createMachine)({
96
100
  activeId: bindable(() => ({ defaultValue: null })),
97
101
  activeChannel: bindable(() => ({ defaultValue: null })),
98
102
  activeOrientation: bindable(() => ({ defaultValue: null })),
103
+ areaBaseColor: bindable(() => ({ defaultValue: null })),
104
+ areaDragPosition: bindable(() => ({ defaultValue: null })),
99
105
  fieldsetDisabled: bindable(() => ({ defaultValue: false })),
100
106
  restoreFocus: bindable(() => ({ defaultValue: true })),
101
107
  currentPlacement: bindable(() => ({
@@ -109,8 +115,7 @@ var machine = (0, import_core.createMachine)({
109
115
  interactive: ({ prop }) => !(prop("disabled") || prop("readOnly")),
110
116
  valueAsString: ({ context }) => context.get("value").toString(context.get("format")),
111
117
  areaValue: ({ context }) => {
112
- const format = context.get("format").startsWith("hsl") ? "hsla" : "hsba";
113
- return context.get("value").toFormat(format);
118
+ return context.get("value").toFormat((0, import_get_area_format.getAreaFormat)(context.get("format")));
114
119
  }
115
120
  },
116
121
  effects: ["trackFormControl"],
@@ -396,6 +401,9 @@ var machine = (0, import_core.createMachine)({
396
401
  const getContentEl2 = () => dom.getContentEl(scope);
397
402
  return (0, import_dismissable.trackDismissableElement)(getContentEl2, {
398
403
  type: "popover",
404
+ onLayerChange(layer) {
405
+ context.set("layer", layer);
406
+ },
399
407
  exclude: dom.getTriggerEl(scope),
400
408
  defer: true,
401
409
  onInteractOutside(event) {
@@ -458,36 +466,47 @@ var machine = (0, import_core.createMachine)({
458
466
  });
459
467
  }).catch(() => void 0);
460
468
  },
461
- setActiveChannel({ context, event }) {
469
+ setActiveChannel({ context, event, computed }) {
462
470
  context.set("activeId", event.id);
463
471
  if (event.channel) context.set("activeChannel", event.channel);
464
472
  if (event.orientation) context.set("activeOrientation", event.orientation);
473
+ const areaFormat = (0, import_get_area_format.getAreaFormat)(context.get("format"));
474
+ if (areaFormat !== context.get("format")) {
475
+ context.set("areaBaseColor", computed("areaValue"));
476
+ }
465
477
  },
466
478
  clearActiveChannel({ context }) {
467
479
  context.set("activeChannel", null);
468
480
  context.set("activeId", null);
469
481
  context.set("activeOrientation", null);
482
+ context.set("areaBaseColor", null);
483
+ context.set("areaDragPosition", null);
470
484
  },
471
485
  setAreaColorFromPoint({ context, event, computed, scope, prop }) {
472
- const v = event.format ? context.get("value").toFormat(event.format) : computed("areaValue");
486
+ const storeFormat = context.get("format");
487
+ const v = event.format ? context.get("value").toFormat(event.format) : context.get("areaBaseColor") ?? computed("areaValue");
473
488
  const { xChannel, yChannel } = event.channel || context.get("activeChannel");
474
489
  const percent = dom.getAreaValueFromPoint(scope, event.point, prop("dir"));
475
490
  if (!percent) return;
491
+ context.set("areaDragPosition", { x: percent.x, y: percent.y });
476
492
  const xValue = v.getChannelPercentValue(xChannel, percent.x);
477
493
  const yValue = v.getChannelPercentValue(yChannel, 1 - percent.y);
478
- const color = v.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue);
494
+ const color = v.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue).toFormat(storeFormat);
479
495
  context.set("value", color);
480
496
  },
481
497
  setChannelColorFromPoint({ context, event, computed, scope, prop }) {
498
+ const storeFormat = context.get("format");
482
499
  const channel = event.channel || context.get("activeId");
483
- const normalizedValue = event.format ? context.get("value").toFormat(event.format) : computed("areaValue");
500
+ const baseColor = context.get("areaBaseColor");
501
+ const normalizedValue = event.format ? context.get("value").toFormat(event.format) : baseColor ?? computed("areaValue");
484
502
  const percent = dom.getChannelSliderValueFromPoint(scope, event.point, channel, prop("dir"));
485
503
  if (!percent) return;
486
504
  const orientation = event.orientation || context.get("activeOrientation") || "horizontal";
487
505
  const channelPercent = orientation === "horizontal" ? percent.x : percent.y;
488
506
  const value = normalizedValue.getChannelPercentValue(channel, channelPercent);
489
- const color = normalizedValue.withChannelValue(channel, value);
490
- context.set("value", color);
507
+ const updatedBase = normalizedValue.withChannelValue(channel, value);
508
+ context.set("areaBaseColor", updatedBase);
509
+ context.set("value", updatedBase.toFormat(storeFormat));
491
510
  },
492
511
  setValue({ context, event }) {
493
512
  const format = context.get("format");
@@ -544,24 +563,32 @@ var machine = (0, import_core.createMachine)({
544
563
  const color = context.get("value").decrementChannel(event.channel, event.step);
545
564
  context.set("value", color);
546
565
  },
547
- incrementAreaXChannel({ context, event, computed }) {
566
+ incrementAreaXChannel({ context, event }) {
548
567
  const { xChannel } = event.channel;
549
- const color = computed("areaValue").incrementChannel(xChannel, event.step);
568
+ const storeFormat = context.get("format");
569
+ const v = context.get("value").toFormat((0, import_get_area_format.getAreaFormat)(storeFormat));
570
+ const color = v.incrementChannel(xChannel, event.step).toFormat(storeFormat);
550
571
  context.set("value", color);
551
572
  },
552
- decrementAreaXChannel({ context, event, computed }) {
573
+ decrementAreaXChannel({ context, event }) {
553
574
  const { xChannel } = event.channel;
554
- const color = computed("areaValue").decrementChannel(xChannel, event.step);
575
+ const storeFormat = context.get("format");
576
+ const v = context.get("value").toFormat((0, import_get_area_format.getAreaFormat)(storeFormat));
577
+ const color = v.decrementChannel(xChannel, event.step).toFormat(storeFormat);
555
578
  context.set("value", color);
556
579
  },
557
- incrementAreaYChannel({ context, event, computed }) {
580
+ incrementAreaYChannel({ context, event }) {
558
581
  const { yChannel } = event.channel;
559
- const color = computed("areaValue").incrementChannel(yChannel, event.step);
582
+ const storeFormat = context.get("format");
583
+ const v = context.get("value").toFormat((0, import_get_area_format.getAreaFormat)(storeFormat));
584
+ const color = v.incrementChannel(yChannel, event.step).toFormat(storeFormat);
560
585
  context.set("value", color);
561
586
  },
562
- decrementAreaYChannel({ context, event, computed }) {
587
+ decrementAreaYChannel({ context, event }) {
563
588
  const { yChannel } = event.channel;
564
- const color = computed("areaValue").decrementChannel(yChannel, event.step);
589
+ const storeFormat = context.get("format");
590
+ const v = context.get("value").toFormat((0, import_get_area_format.getAreaFormat)(storeFormat));
591
+ const color = v.decrementChannel(yChannel, event.step).toFormat(storeFormat);
565
592
  context.set("value", color);
566
593
  },
567
594
  setChannelToMax({ context, event }) {
@@ -15,6 +15,7 @@ import { getPlacement } from "@zag-js/popper";
15
15
  import { tryCatch } from "@zag-js/utils";
16
16
  import * as dom from "./color-picker.dom.mjs";
17
17
  import { parse } from "./color-picker.parse.mjs";
18
+ import { getAreaFormat } from "./utils/get-area-format.mjs";
18
19
  import { getChannelValue } from "./utils/get-channel-input-value.mjs";
19
20
  import { prefixHex } from "./utils/is-valid-hex.mjs";
20
21
  var { and } = createGuards();
@@ -45,6 +46,9 @@ var machine = createMachine({
45
46
  },
46
47
  context({ prop, bindable, getContext }) {
47
48
  return {
49
+ layer: bindable(() => ({
50
+ defaultValue: null
51
+ })),
48
52
  value: bindable(() => ({
49
53
  defaultValue: prop("defaultValue").toFormat(prop("format") ?? prop("defaultFormat")),
50
54
  value: prop("value")?.toFormat(prop("format") ?? prop("defaultFormat")),
@@ -70,6 +74,8 @@ var machine = createMachine({
70
74
  activeId: bindable(() => ({ defaultValue: null })),
71
75
  activeChannel: bindable(() => ({ defaultValue: null })),
72
76
  activeOrientation: bindable(() => ({ defaultValue: null })),
77
+ areaBaseColor: bindable(() => ({ defaultValue: null })),
78
+ areaDragPosition: bindable(() => ({ defaultValue: null })),
73
79
  fieldsetDisabled: bindable(() => ({ defaultValue: false })),
74
80
  restoreFocus: bindable(() => ({ defaultValue: true })),
75
81
  currentPlacement: bindable(() => ({
@@ -83,8 +89,7 @@ var machine = createMachine({
83
89
  interactive: ({ prop }) => !(prop("disabled") || prop("readOnly")),
84
90
  valueAsString: ({ context }) => context.get("value").toString(context.get("format")),
85
91
  areaValue: ({ context }) => {
86
- const format = context.get("format").startsWith("hsl") ? "hsla" : "hsba";
87
- return context.get("value").toFormat(format);
92
+ return context.get("value").toFormat(getAreaFormat(context.get("format")));
88
93
  }
89
94
  },
90
95
  effects: ["trackFormControl"],
@@ -370,6 +375,9 @@ var machine = createMachine({
370
375
  const getContentEl2 = () => dom.getContentEl(scope);
371
376
  return trackDismissableElement(getContentEl2, {
372
377
  type: "popover",
378
+ onLayerChange(layer) {
379
+ context.set("layer", layer);
380
+ },
373
381
  exclude: dom.getTriggerEl(scope),
374
382
  defer: true,
375
383
  onInteractOutside(event) {
@@ -432,36 +440,47 @@ var machine = createMachine({
432
440
  });
433
441
  }).catch(() => void 0);
434
442
  },
435
- setActiveChannel({ context, event }) {
443
+ setActiveChannel({ context, event, computed }) {
436
444
  context.set("activeId", event.id);
437
445
  if (event.channel) context.set("activeChannel", event.channel);
438
446
  if (event.orientation) context.set("activeOrientation", event.orientation);
447
+ const areaFormat = getAreaFormat(context.get("format"));
448
+ if (areaFormat !== context.get("format")) {
449
+ context.set("areaBaseColor", computed("areaValue"));
450
+ }
439
451
  },
440
452
  clearActiveChannel({ context }) {
441
453
  context.set("activeChannel", null);
442
454
  context.set("activeId", null);
443
455
  context.set("activeOrientation", null);
456
+ context.set("areaBaseColor", null);
457
+ context.set("areaDragPosition", null);
444
458
  },
445
459
  setAreaColorFromPoint({ context, event, computed, scope, prop }) {
446
- const v = event.format ? context.get("value").toFormat(event.format) : computed("areaValue");
460
+ const storeFormat = context.get("format");
461
+ const v = event.format ? context.get("value").toFormat(event.format) : context.get("areaBaseColor") ?? computed("areaValue");
447
462
  const { xChannel, yChannel } = event.channel || context.get("activeChannel");
448
463
  const percent = dom.getAreaValueFromPoint(scope, event.point, prop("dir"));
449
464
  if (!percent) return;
465
+ context.set("areaDragPosition", { x: percent.x, y: percent.y });
450
466
  const xValue = v.getChannelPercentValue(xChannel, percent.x);
451
467
  const yValue = v.getChannelPercentValue(yChannel, 1 - percent.y);
452
- const color = v.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue);
468
+ const color = v.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue).toFormat(storeFormat);
453
469
  context.set("value", color);
454
470
  },
455
471
  setChannelColorFromPoint({ context, event, computed, scope, prop }) {
472
+ const storeFormat = context.get("format");
456
473
  const channel = event.channel || context.get("activeId");
457
- const normalizedValue = event.format ? context.get("value").toFormat(event.format) : computed("areaValue");
474
+ const baseColor = context.get("areaBaseColor");
475
+ const normalizedValue = event.format ? context.get("value").toFormat(event.format) : baseColor ?? computed("areaValue");
458
476
  const percent = dom.getChannelSliderValueFromPoint(scope, event.point, channel, prop("dir"));
459
477
  if (!percent) return;
460
478
  const orientation = event.orientation || context.get("activeOrientation") || "horizontal";
461
479
  const channelPercent = orientation === "horizontal" ? percent.x : percent.y;
462
480
  const value = normalizedValue.getChannelPercentValue(channel, channelPercent);
463
- const color = normalizedValue.withChannelValue(channel, value);
464
- context.set("value", color);
481
+ const updatedBase = normalizedValue.withChannelValue(channel, value);
482
+ context.set("areaBaseColor", updatedBase);
483
+ context.set("value", updatedBase.toFormat(storeFormat));
465
484
  },
466
485
  setValue({ context, event }) {
467
486
  const format = context.get("format");
@@ -518,24 +537,32 @@ var machine = createMachine({
518
537
  const color = context.get("value").decrementChannel(event.channel, event.step);
519
538
  context.set("value", color);
520
539
  },
521
- incrementAreaXChannel({ context, event, computed }) {
540
+ incrementAreaXChannel({ context, event }) {
522
541
  const { xChannel } = event.channel;
523
- const color = computed("areaValue").incrementChannel(xChannel, event.step);
542
+ const storeFormat = context.get("format");
543
+ const v = context.get("value").toFormat(getAreaFormat(storeFormat));
544
+ const color = v.incrementChannel(xChannel, event.step).toFormat(storeFormat);
524
545
  context.set("value", color);
525
546
  },
526
- decrementAreaXChannel({ context, event, computed }) {
547
+ decrementAreaXChannel({ context, event }) {
527
548
  const { xChannel } = event.channel;
528
- const color = computed("areaValue").decrementChannel(xChannel, event.step);
549
+ const storeFormat = context.get("format");
550
+ const v = context.get("value").toFormat(getAreaFormat(storeFormat));
551
+ const color = v.decrementChannel(xChannel, event.step).toFormat(storeFormat);
529
552
  context.set("value", color);
530
553
  },
531
- incrementAreaYChannel({ context, event, computed }) {
554
+ incrementAreaYChannel({ context, event }) {
532
555
  const { yChannel } = event.channel;
533
- const color = computed("areaValue").incrementChannel(yChannel, event.step);
556
+ const storeFormat = context.get("format");
557
+ const v = context.get("value").toFormat(getAreaFormat(storeFormat));
558
+ const color = v.incrementChannel(yChannel, event.step).toFormat(storeFormat);
534
559
  context.set("value", color);
535
560
  },
536
- decrementAreaYChannel({ context, event, computed }) {
561
+ decrementAreaYChannel({ context, event }) {
537
562
  const { yChannel } = event.channel;
538
- const color = computed("areaValue").decrementChannel(yChannel, event.step);
563
+ const storeFormat = context.get("format");
564
+ const v = context.get("value").toFormat(getAreaFormat(storeFormat));
565
+ const color = v.decrementChannel(yChannel, event.step).toFormat(storeFormat);
539
566
  context.set("value", color);
540
567
  },
541
568
  setChannelToMax({ context, event }) {
@@ -1,5 +1,6 @@
1
1
  import { AreaProps, ChannelProps, ColorPickerProps, SwatchProps, SwatchTriggerProps, TransparencyGridProps } from './color-picker.types.mjs';
2
2
  import '@zag-js/color-utils';
3
+ import './utils/get-gamut-overlay-path.mjs';
3
4
  import '@zag-js/core';
4
5
  import '@zag-js/dismissable';
5
6
  import '@zag-js/popper';
@@ -1,5 +1,6 @@
1
1
  import { AreaProps, ChannelProps, ColorPickerProps, SwatchProps, SwatchTriggerProps, TransparencyGridProps } from './color-picker.types.js';
2
2
  import '@zag-js/color-utils';
3
+ import './utils/get-gamut-overlay-path.js';
3
4
  import '@zag-js/core';
4
5
  import '@zag-js/dismissable';
5
6
  import '@zag-js/popper';
@@ -1,8 +1,9 @@
1
1
  import { ColorChannel, Color, ColorFormat, ColorAxes } from '@zag-js/color-utils';
2
2
  export { Color, ColorAxes, ColorChannel, ColorFormat, ColorType } from '@zag-js/color-utils';
3
+ import { GamutOverlayData } from './utils/get-gamut-overlay-path.mjs';
3
4
  import { Service, EventObject, Machine } from '@zag-js/core';
4
- import { InteractOutsideHandlers } from '@zag-js/dismissable';
5
- import { PositioningOptions } from '@zag-js/popper';
5
+ import { InteractOutsideHandlers, LayerSnapshot } from '@zag-js/dismissable';
6
+ import { PositioningOptions, Placement, PlacementSide } from '@zag-js/popper';
6
7
  export { PositioningOptions } from '@zag-js/popper';
7
8
  import { RequiredBy, CommonProperties, DirectionProperty, Orientation, PropTypes } from '@zag-js/types';
8
9
 
@@ -155,11 +156,20 @@ type ColorPickerSchema = {
155
156
  areaValue: Color;
156
157
  };
157
158
  context: {
159
+ /**
160
+ * The computed layer stack state used for declarative styles and attributes.
161
+ */
162
+ layer: LayerSnapshot | null;
158
163
  format: ColorFormat;
159
164
  value: Color;
160
165
  activeId: string | null;
161
166
  activeChannel: Partial<ColorAxes> | null;
162
167
  activeOrientation: Orientation | null;
168
+ areaBaseColor: Color | null;
169
+ areaDragPosition: {
170
+ x: number;
171
+ y: number;
172
+ } | null;
163
173
  fieldsetDisabled: boolean;
164
174
  currentPlacement: PositioningOptions["placement"] | undefined;
165
175
  restoreFocus: boolean;
@@ -186,6 +196,16 @@ interface AreaProps {
186
196
  xChannel?: ColorChannel | undefined;
187
197
  yChannel?: ColorChannel | undefined;
188
198
  }
199
+ /**
200
+ * Same channel overrides as {@link AreaProps}, plus optional DPR for overlay sampling.
201
+ * Pass the **same** object to `getAreaProps`, `getGamutOverlay`, and `getGamutOverlayProps`.
202
+ */
203
+ interface GamutOverlayProps extends AreaProps {
204
+ /**
205
+ * Overrides `globalThis.devicePixelRatio` for boundary resolution (default browser DPR in connect).
206
+ */
207
+ pixelRatio?: number | undefined;
208
+ }
189
209
  interface SwatchTriggerProps {
190
210
  /**
191
211
  * The color value
@@ -202,6 +222,72 @@ interface SwatchTriggerState {
202
222
  checked: boolean;
203
223
  disabled: boolean;
204
224
  }
225
+ interface RootState {
226
+ /**
227
+ * Whether the color picker is disabled
228
+ */
229
+ disabled: boolean;
230
+ /**
231
+ * Whether the color picker is read-only
232
+ */
233
+ readOnly: boolean;
234
+ /**
235
+ * Whether the color picker is invalid
236
+ */
237
+ invalid: boolean;
238
+ }
239
+ interface TriggerState {
240
+ /**
241
+ * Whether the color picker is open
242
+ */
243
+ open: boolean;
244
+ /**
245
+ * Whether the trigger is focused
246
+ */
247
+ focused: boolean;
248
+ /**
249
+ * Whether the trigger is disabled
250
+ */
251
+ disabled: boolean;
252
+ /**
253
+ * Whether the color picker is invalid
254
+ */
255
+ invalid: boolean;
256
+ /**
257
+ * Whether the color picker is read-only
258
+ */
259
+ readOnly: boolean;
260
+ /**
261
+ * The current placement of the content relative to the trigger
262
+ */
263
+ placement: Placement | undefined;
264
+ /**
265
+ * The side of the trigger the content is placed on
266
+ */
267
+ side: PlacementSide | undefined;
268
+ }
269
+ interface ContentState {
270
+ /**
271
+ * Whether the color picker is open
272
+ */
273
+ open: boolean;
274
+ /**
275
+ * Whether the content is nested within another layered element
276
+ */
277
+ nested: boolean;
278
+ /**
279
+ * Whether the content has nested layered elements within it
280
+ */
281
+ hasNested: boolean;
282
+ /**
283
+ * The current placement of the content relative to the trigger
284
+ */
285
+ placement: Placement | undefined;
286
+ /**
287
+ * The side of the trigger the content is placed on
288
+ */
289
+ side: PlacementSide | undefined;
290
+ }
205
291
  interface SwatchProps {
206
292
  /**
207
293
  * The color value
@@ -236,6 +322,11 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
236
322
  * The current color value (as a Color object)
237
323
  */
238
324
  valueAsString: string;
325
+ /**
326
+ * Whether the current color lies inside the sRGB gamut (linear RGB cube).
327
+ * When `false`, wide-gamut CSS strings may still be valid while 8-bit RGB / hex are clamped.
328
+ */
329
+ isInSrgbGamut: boolean;
239
330
  /**
240
331
  * Function to set the color value
241
332
  */
@@ -272,16 +363,37 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
272
363
  * Function to open or close the color picker
273
364
  */
274
365
  setOpen: (open: boolean) => void;
366
+ /**
367
+ * Returns the state of the root
368
+ */
369
+ getRootState: () => RootState;
275
370
  getRootProps: () => T["element"];
276
371
  getLabelProps: () => T["element"];
277
372
  getControlProps: () => T["element"];
373
+ /**
374
+ * Returns the state of the trigger
375
+ */
376
+ getTriggerState: () => TriggerState;
278
377
  getTriggerProps: () => T["button"];
279
378
  getPositionerProps: () => T["element"];
379
+ /**
380
+ * Returns the state of the content
381
+ */
382
+ getContentState: () => ContentState;
280
383
  getContentProps: () => T["element"];
281
384
  getHiddenInputProps: () => T["input"];
282
385
  getValueTextProps: () => T["element"];
283
386
  getAreaProps: (props?: AreaProps) => T["element"];
284
387
  getAreaBackgroundProps: (props?: AreaProps) => T["element"];
388
+ /**
389
+ * SVG path + label position for the sRGB gamut boundary (oklch/oklab + HSB area only); `null` when not shown.
390
+ * Use the **same** `props` as {@link getAreaProps} / {@link getGamutOverlayProps} (including `pixelRatio`).
391
+ */
392
+ getGamutOverlay: (props?: GamutOverlayProps) => GamutOverlayData | null;
393
+ /**
394
+ * Props for an `<svg>` gamut overlay; pair with {@link getGamutOverlay} using the same `props`.
395
+ */
396
+ getGamutOverlayProps: (props?: GamutOverlayProps) => T["svg"];
285
397
  getAreaThumbProps: (props?: AreaProps) => T["element"];
286
398
  getChannelInputProps: (props: ChannelInputProps) => T["input"];
287
399
  getChannelSliderProps: (props: ChannelSliderProps) => T["element"];
@@ -300,4 +412,4 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
300
412
  getFormatTriggerProps: () => T["button"];
301
413
  }
302
414
 
303
- export type { AreaProps, ChannelInputProps, ChannelProps, ChannelSliderProps, ColorPickerApi, ColorPickerMachine, ColorPickerProps, ColorPickerSchema, ColorPickerService, ElementIds, ExtendedColorChannel, FormatChangeDetails, OpenChangeDetails, SwatchProps, SwatchTriggerProps, SwatchTriggerState, TransparencyGridProps, ValueChangeDetails };
415
+ export { type AreaProps, type ChannelInputProps, type ChannelProps, type ChannelSliderProps, type ColorPickerApi, type ColorPickerMachine, type ColorPickerProps, type ColorPickerSchema, type ColorPickerService, type ContentState, type ElementIds, type ExtendedColorChannel, type FormatChangeDetails, GamutOverlayData, type GamutOverlayProps, type OpenChangeDetails, type RootState, type SwatchProps, type SwatchTriggerProps, type SwatchTriggerState, type TransparencyGridProps, type TriggerState, type ValueChangeDetails };
@@ -1,8 +1,9 @@
1
1
  import { ColorChannel, Color, ColorFormat, ColorAxes } from '@zag-js/color-utils';
2
2
  export { Color, ColorAxes, ColorChannel, ColorFormat, ColorType } from '@zag-js/color-utils';
3
+ import { GamutOverlayData } from './utils/get-gamut-overlay-path.js';
3
4
  import { Service, EventObject, Machine } from '@zag-js/core';
4
- import { InteractOutsideHandlers } from '@zag-js/dismissable';
5
- import { PositioningOptions } from '@zag-js/popper';
5
+ import { InteractOutsideHandlers, LayerSnapshot } from '@zag-js/dismissable';
6
+ import { PositioningOptions, Placement, PlacementSide } from '@zag-js/popper';
6
7
  export { PositioningOptions } from '@zag-js/popper';
7
8
  import { RequiredBy, CommonProperties, DirectionProperty, Orientation, PropTypes } from '@zag-js/types';
8
9
 
@@ -155,11 +156,20 @@ type ColorPickerSchema = {
155
156
  areaValue: Color;
156
157
  };
157
158
  context: {
159
+ /**
160
+ * The computed layer stack state used for declarative styles and attributes.
161
+ */
162
+ layer: LayerSnapshot | null;
158
163
  format: ColorFormat;
159
164
  value: Color;
160
165
  activeId: string | null;
161
166
  activeChannel: Partial<ColorAxes> | null;
162
167
  activeOrientation: Orientation | null;
168
+ areaBaseColor: Color | null;
169
+ areaDragPosition: {
170
+ x: number;
171
+ y: number;
172
+ } | null;
163
173
  fieldsetDisabled: boolean;
164
174
  currentPlacement: PositioningOptions["placement"] | undefined;
165
175
  restoreFocus: boolean;
@@ -186,6 +196,16 @@ interface AreaProps {
186
196
  xChannel?: ColorChannel | undefined;
187
197
  yChannel?: ColorChannel | undefined;
188
198
  }
199
+ /**
200
+ * Same channel overrides as {@link AreaProps}, plus optional DPR for overlay sampling.
201
+ * Pass the **same** object to `getAreaProps`, `getGamutOverlay`, and `getGamutOverlayProps`.
202
+ */
203
+ interface GamutOverlayProps extends AreaProps {
204
+ /**
205
+ * Overrides `globalThis.devicePixelRatio` for boundary resolution (default browser DPR in connect).
206
+ */
207
+ pixelRatio?: number | undefined;
208
+ }
189
209
  interface SwatchTriggerProps {
190
210
  /**
191
211
  * The color value
@@ -202,6 +222,72 @@ interface SwatchTriggerState {
202
222
  checked: boolean;
203
223
  disabled: boolean;
204
224
  }
225
+ interface RootState {
226
+ /**
227
+ * Whether the color picker is disabled
228
+ */
229
+ disabled: boolean;
230
+ /**
231
+ * Whether the color picker is read-only
232
+ */
233
+ readOnly: boolean;
234
+ /**
235
+ * Whether the color picker is invalid
236
+ */
237
+ invalid: boolean;
238
+ }
239
+ interface TriggerState {
240
+ /**
241
+ * Whether the color picker is open
242
+ */
243
+ open: boolean;
244
+ /**
245
+ * Whether the trigger is focused
246
+ */
247
+ focused: boolean;
248
+ /**
249
+ * Whether the trigger is disabled
250
+ */
251
+ disabled: boolean;
252
+ /**
253
+ * Whether the color picker is invalid
254
+ */
255
+ invalid: boolean;
256
+ /**
257
+ * Whether the color picker is read-only
258
+ */
259
+ readOnly: boolean;
260
+ /**
261
+ * The current placement of the content relative to the trigger
262
+ */
263
+ placement: Placement | undefined;
264
+ /**
265
+ * The side of the trigger the content is placed on
266
+ */
267
+ side: PlacementSide | undefined;
268
+ }
269
+ interface ContentState {
270
+ /**
271
+ * Whether the color picker is open
272
+ */
273
+ open: boolean;
274
+ /**
275
+ * Whether the content is nested within another layered element
276
+ */
277
+ nested: boolean;
278
+ /**
279
+ * Whether the content has nested layered elements within it
280
+ */
281
+ hasNested: boolean;
282
+ /**
283
+ * The current placement of the content relative to the trigger
284
+ */
285
+ placement: Placement | undefined;
286
+ /**
287
+ * The side of the trigger the content is placed on
288
+ */
289
+ side: PlacementSide | undefined;
290
+ }
205
291
  interface SwatchProps {
206
292
  /**
207
293
  * The color value
@@ -236,6 +322,11 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
236
322
  * The current color value (as a Color object)
237
323
  */
238
324
  valueAsString: string;
325
+ /**
326
+ * Whether the current color lies inside the sRGB gamut (linear RGB cube).
327
+ * When `false`, wide-gamut CSS strings may still be valid while 8-bit RGB / hex are clamped.
328
+ */
329
+ isInSrgbGamut: boolean;
239
330
  /**
240
331
  * Function to set the color value
241
332
  */
@@ -272,16 +363,37 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
272
363
  * Function to open or close the color picker
273
364
  */
274
365
  setOpen: (open: boolean) => void;
366
+ /**
367
+ * Returns the state of the root
368
+ */
369
+ getRootState: () => RootState;
275
370
  getRootProps: () => T["element"];
276
371
  getLabelProps: () => T["element"];
277
372
  getControlProps: () => T["element"];
373
+ /**
374
+ * Returns the state of the trigger
375
+ */
376
+ getTriggerState: () => TriggerState;
278
377
  getTriggerProps: () => T["button"];
279
378
  getPositionerProps: () => T["element"];
379
+ /**
380
+ * Returns the state of the content
381
+ */
382
+ getContentState: () => ContentState;
280
383
  getContentProps: () => T["element"];
281
384
  getHiddenInputProps: () => T["input"];
282
385
  getValueTextProps: () => T["element"];
283
386
  getAreaProps: (props?: AreaProps) => T["element"];
284
387
  getAreaBackgroundProps: (props?: AreaProps) => T["element"];
388
+ /**
389
+ * SVG path + label position for the sRGB gamut boundary (oklch/oklab + HSB area only); `null` when not shown.
390
+ * Use the **same** `props` as {@link getAreaProps} / {@link getGamutOverlayProps} (including `pixelRatio`).
391
+ */
392
+ getGamutOverlay: (props?: GamutOverlayProps) => GamutOverlayData | null;
393
+ /**
394
+ * Props for an `<svg>` gamut overlay; pair with {@link getGamutOverlay} using the same `props`.
395
+ */
396
+ getGamutOverlayProps: (props?: GamutOverlayProps) => T["svg"];
285
397
  getAreaThumbProps: (props?: AreaProps) => T["element"];
286
398
  getChannelInputProps: (props: ChannelInputProps) => T["input"];
287
399
  getChannelSliderProps: (props: ChannelSliderProps) => T["element"];
@@ -300,4 +412,4 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
300
412
  getFormatTriggerProps: () => T["button"];
301
413
  }
302
414
 
303
- export type { AreaProps, ChannelInputProps, ChannelProps, ChannelSliderProps, ColorPickerApi, ColorPickerMachine, ColorPickerProps, ColorPickerSchema, ColorPickerService, ElementIds, ExtendedColorChannel, FormatChangeDetails, OpenChangeDetails, SwatchProps, SwatchTriggerProps, SwatchTriggerState, TransparencyGridProps, ValueChangeDetails };
415
+ export { type AreaProps, type ChannelInputProps, type ChannelProps, type ChannelSliderProps, type ColorPickerApi, type ColorPickerMachine, type ColorPickerProps, type ColorPickerSchema, type ColorPickerService, type ContentState, type ElementIds, type ExtendedColorChannel, type FormatChangeDetails, GamutOverlayData, type GamutOverlayProps, type OpenChangeDetails, type RootState, type SwatchProps, type SwatchTriggerProps, type SwatchTriggerState, type TransparencyGridProps, type TriggerState, type ValueChangeDetails };
package/dist/index.d.mts CHANGED
@@ -4,8 +4,9 @@ export { connect } from './color-picker.connect.mjs';
4
4
  export { machine } from './color-picker.machine.mjs';
5
5
  export { parse } from './color-picker.parse.mjs';
6
6
  export { areaProps, channelProps, props, splitAreaProps, splitChannelProps, splitProps, splitSwatchProps, splitSwatchTriggerProps, splitTransparencyGridProps, swatchProps, swatchTriggerProps, transparencyGridProps } from './color-picker.props.mjs';
7
- export { ColorPickerApi as Api, AreaProps, ChannelInputProps, ChannelProps, ChannelSliderProps, ElementIds, ExtendedColorChannel, FormatChangeDetails, ColorPickerMachine as Machine, OpenChangeDetails, ColorPickerProps as Props, ColorPickerService as Service, SwatchProps, SwatchTriggerProps, SwatchTriggerState, TransparencyGridProps, ValueChangeDetails } from './color-picker.types.mjs';
7
+ export { ColorPickerApi as Api, AreaProps, ChannelInputProps, ChannelProps, ChannelSliderProps, ContentState, ElementIds, ExtendedColorChannel, FormatChangeDetails, GamutOverlayProps, ColorPickerMachine as Machine, OpenChangeDetails, ColorPickerProps as Props, RootState, ColorPickerService as Service, SwatchProps, SwatchTriggerProps, SwatchTriggerState, TransparencyGridProps, TriggerState, ValueChangeDetails } from './color-picker.types.mjs';
8
8
  export { Color, ColorAxes, ColorChannel, ColorFormat, ColorType } from '@zag-js/color-utils';
9
+ export { GamutOverlayData } from './utils/get-gamut-overlay-path.mjs';
9
10
  export { PositioningOptions } from '@zag-js/popper';
10
11
  import '@zag-js/anatomy';
11
12
  import '@zag-js/types';