@zag-js/color-picker 1.41.0 → 2.0.0-next.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.
Files changed (47) 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 +78 -42
  8. package/dist/color-picker.connect.mjs +79 -43
  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 +40 -17
  14. package/dist/color-picker.machine.mjs +40 -17
  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 +32 -1
  18. package/dist/color-picker.types.d.ts +32 -1
  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.js +7 -0
  26. package/dist/utils/get-channel-display-color.mjs +7 -0
  27. package/dist/utils/get-channel-input-value.d.mts +1 -0
  28. package/dist/utils/get-channel-input-value.d.ts +1 -0
  29. package/dist/utils/get-channel-input-value.js +39 -8
  30. package/dist/utils/get-channel-input-value.mjs +39 -8
  31. package/dist/utils/get-gamut-overlay-path.d.mts +33 -0
  32. package/dist/utils/get-gamut-overlay-path.d.ts +33 -0
  33. package/dist/utils/get-gamut-overlay-path.js +81 -0
  34. package/dist/utils/get-gamut-overlay-path.mjs +55 -0
  35. package/dist/utils/get-gamut-overlay-path.test.d.mts +2 -0
  36. package/dist/utils/get-gamut-overlay-path.test.d.ts +2 -0
  37. package/dist/utils/get-gamut-overlay-path.test.js +35 -0
  38. package/dist/utils/get-gamut-overlay-path.test.mjs +33 -0
  39. package/dist/utils/get-slider-background.d.mts +1 -0
  40. package/dist/utils/get-slider-background.d.ts +1 -0
  41. package/dist/utils/get-slider-background.js +16 -1
  42. package/dist/utils/get-slider-background.mjs +16 -1
  43. package/dist/utils/is-srgb-gamut.d.mts +6 -0
  44. package/dist/utils/is-srgb-gamut.d.ts +6 -0
  45. package/dist/utils/is-srgb-gamut.js +33 -0
  46. package/dist/utils/is-srgb-gamut.mjs +8 -0
  47. package/package.json +9 -9
@@ -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();
@@ -70,11 +71,14 @@ var machine = createMachine({
70
71
  activeId: bindable(() => ({ defaultValue: null })),
71
72
  activeChannel: bindable(() => ({ defaultValue: null })),
72
73
  activeOrientation: bindable(() => ({ defaultValue: null })),
74
+ areaBaseColor: bindable(() => ({ defaultValue: null })),
75
+ areaDragPosition: bindable(() => ({ defaultValue: null })),
73
76
  fieldsetDisabled: bindable(() => ({ defaultValue: false })),
74
77
  restoreFocus: bindable(() => ({ defaultValue: true })),
75
78
  currentPlacement: bindable(() => ({
76
79
  defaultValue: void 0
77
- }))
80
+ })),
81
+ positioned: bindable(() => ({ defaultValue: false }))
78
82
  };
79
83
  },
80
84
  computed: {
@@ -83,8 +87,7 @@ var machine = createMachine({
83
87
  interactive: ({ prop }) => !(prop("disabled") || prop("readOnly")),
84
88
  valueAsString: ({ context }) => context.get("value").toString(context.get("format")),
85
89
  areaValue: ({ context }) => {
86
- const format = context.get("format").startsWith("hsl") ? "hsla" : "hsba";
87
- return context.get("value").toFormat(format);
90
+ return context.get("value").toFormat(getAreaFormat(context.get("format")));
88
91
  }
89
92
  },
90
93
  effects: ["trackFormControl"],
@@ -362,6 +365,7 @@ var machine = createMachine({
362
365
  defer: true,
363
366
  onComplete(data) {
364
367
  context.set("currentPlacement", data.placement);
368
+ context.set("positioned", true);
365
369
  }
366
370
  });
367
371
  },
@@ -432,36 +436,47 @@ var machine = createMachine({
432
436
  });
433
437
  }).catch(() => void 0);
434
438
  },
435
- setActiveChannel({ context, event }) {
439
+ setActiveChannel({ context, event, computed }) {
436
440
  context.set("activeId", event.id);
437
441
  if (event.channel) context.set("activeChannel", event.channel);
438
442
  if (event.orientation) context.set("activeOrientation", event.orientation);
443
+ const areaFormat = getAreaFormat(context.get("format"));
444
+ if (areaFormat !== context.get("format")) {
445
+ context.set("areaBaseColor", computed("areaValue"));
446
+ }
439
447
  },
440
448
  clearActiveChannel({ context }) {
441
449
  context.set("activeChannel", null);
442
450
  context.set("activeId", null);
443
451
  context.set("activeOrientation", null);
452
+ context.set("areaBaseColor", null);
453
+ context.set("areaDragPosition", null);
444
454
  },
445
455
  setAreaColorFromPoint({ context, event, computed, scope, prop }) {
446
- const v = event.format ? context.get("value").toFormat(event.format) : computed("areaValue");
456
+ const storeFormat = context.get("format");
457
+ const v = event.format ? context.get("value").toFormat(event.format) : context.get("areaBaseColor") ?? computed("areaValue");
447
458
  const { xChannel, yChannel } = event.channel || context.get("activeChannel");
448
459
  const percent = dom.getAreaValueFromPoint(scope, event.point, prop("dir"));
449
460
  if (!percent) return;
461
+ context.set("areaDragPosition", { x: percent.x, y: percent.y });
450
462
  const xValue = v.getChannelPercentValue(xChannel, percent.x);
451
463
  const yValue = v.getChannelPercentValue(yChannel, 1 - percent.y);
452
- const color = v.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue);
464
+ const color = v.withChannelValue(xChannel, xValue).withChannelValue(yChannel, yValue).toFormat(storeFormat);
453
465
  context.set("value", color);
454
466
  },
455
467
  setChannelColorFromPoint({ context, event, computed, scope, prop }) {
468
+ const storeFormat = context.get("format");
456
469
  const channel = event.channel || context.get("activeId");
457
- const normalizedValue = event.format ? context.get("value").toFormat(event.format) : computed("areaValue");
470
+ const baseColor = context.get("areaBaseColor");
471
+ const normalizedValue = event.format ? context.get("value").toFormat(event.format) : baseColor ?? computed("areaValue");
458
472
  const percent = dom.getChannelSliderValueFromPoint(scope, event.point, channel, prop("dir"));
459
473
  if (!percent) return;
460
474
  const orientation = event.orientation || context.get("activeOrientation") || "horizontal";
461
475
  const channelPercent = orientation === "horizontal" ? percent.x : percent.y;
462
476
  const value = normalizedValue.getChannelPercentValue(channel, channelPercent);
463
- const color = normalizedValue.withChannelValue(channel, value);
464
- context.set("value", color);
477
+ const updatedBase = normalizedValue.withChannelValue(channel, value);
478
+ context.set("areaBaseColor", updatedBase);
479
+ context.set("value", updatedBase.toFormat(storeFormat));
465
480
  },
466
481
  setValue({ context, event }) {
467
482
  const format = context.get("format");
@@ -518,24 +533,32 @@ var machine = createMachine({
518
533
  const color = context.get("value").decrementChannel(event.channel, event.step);
519
534
  context.set("value", color);
520
535
  },
521
- incrementAreaXChannel({ context, event, computed }) {
536
+ incrementAreaXChannel({ context, event }) {
522
537
  const { xChannel } = event.channel;
523
- const color = computed("areaValue").incrementChannel(xChannel, event.step);
538
+ const storeFormat = context.get("format");
539
+ const v = context.get("value").toFormat(getAreaFormat(storeFormat));
540
+ const color = v.incrementChannel(xChannel, event.step).toFormat(storeFormat);
524
541
  context.set("value", color);
525
542
  },
526
- decrementAreaXChannel({ context, event, computed }) {
543
+ decrementAreaXChannel({ context, event }) {
527
544
  const { xChannel } = event.channel;
528
- const color = computed("areaValue").decrementChannel(xChannel, event.step);
545
+ const storeFormat = context.get("format");
546
+ const v = context.get("value").toFormat(getAreaFormat(storeFormat));
547
+ const color = v.decrementChannel(xChannel, event.step).toFormat(storeFormat);
529
548
  context.set("value", color);
530
549
  },
531
- incrementAreaYChannel({ context, event, computed }) {
550
+ incrementAreaYChannel({ context, event }) {
532
551
  const { yChannel } = event.channel;
533
- const color = computed("areaValue").incrementChannel(yChannel, event.step);
552
+ const storeFormat = context.get("format");
553
+ const v = context.get("value").toFormat(getAreaFormat(storeFormat));
554
+ const color = v.incrementChannel(yChannel, event.step).toFormat(storeFormat);
534
555
  context.set("value", color);
535
556
  },
536
- decrementAreaYChannel({ context, event, computed }) {
557
+ decrementAreaYChannel({ context, event }) {
537
558
  const { yChannel } = event.channel;
538
- const color = computed("areaValue").decrementChannel(yChannel, event.step);
559
+ const storeFormat = context.get("format");
560
+ const v = context.get("value").toFormat(getAreaFormat(storeFormat));
561
+ const color = v.decrementChannel(yChannel, event.step).toFormat(storeFormat);
539
562
  context.set("value", color);
540
563
  },
541
564
  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,5 +1,6 @@
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
5
  import { InteractOutsideHandlers } from '@zag-js/dismissable';
5
6
  import { PositioningOptions } from '@zag-js/popper';
@@ -160,8 +161,14 @@ type ColorPickerSchema = {
160
161
  activeId: string | null;
161
162
  activeChannel: Partial<ColorAxes> | null;
162
163
  activeOrientation: Orientation | null;
164
+ areaBaseColor: Color | null;
165
+ areaDragPosition: {
166
+ x: number;
167
+ y: number;
168
+ } | null;
163
169
  fieldsetDisabled: boolean;
164
170
  currentPlacement: PositioningOptions["placement"] | undefined;
171
+ positioned: boolean;
165
172
  restoreFocus: boolean;
166
173
  };
167
174
  event: EventObject;
@@ -186,6 +193,16 @@ interface AreaProps {
186
193
  xChannel?: ColorChannel | undefined;
187
194
  yChannel?: ColorChannel | undefined;
188
195
  }
196
+ /**
197
+ * Same channel overrides as {@link AreaProps}, plus optional DPR for overlay sampling.
198
+ * Pass the **same** object to `getAreaProps`, `getGamutOverlay`, and `getGamutOverlayProps`.
199
+ */
200
+ interface GamutOverlayProps extends AreaProps {
201
+ /**
202
+ * Overrides `globalThis.devicePixelRatio` for boundary resolution (default browser DPR in connect).
203
+ */
204
+ pixelRatio?: number | undefined;
205
+ }
189
206
  interface SwatchTriggerProps {
190
207
  /**
191
208
  * The color value
@@ -236,6 +253,11 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
236
253
  * The current color value (as a Color object)
237
254
  */
238
255
  valueAsString: string;
256
+ /**
257
+ * Whether the current color lies inside the sRGB gamut (linear RGB cube).
258
+ * When `false`, wide-gamut CSS strings may still be valid while 8-bit RGB / hex are clamped.
259
+ */
260
+ isInSrgbGamut: boolean;
239
261
  /**
240
262
  * Function to set the color value
241
263
  */
@@ -282,6 +304,15 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
282
304
  getValueTextProps: () => T["element"];
283
305
  getAreaProps: (props?: AreaProps) => T["element"];
284
306
  getAreaBackgroundProps: (props?: AreaProps) => T["element"];
307
+ /**
308
+ * SVG path + label position for the sRGB gamut boundary (oklch/oklab + HSB area only); `null` when not shown.
309
+ * Use the **same** `props` as {@link getAreaProps} / {@link getGamutOverlayProps} (including `pixelRatio`).
310
+ */
311
+ getGamutOverlay: (props?: GamutOverlayProps) => GamutOverlayData | null;
312
+ /**
313
+ * Props for an `<svg>` gamut overlay; pair with {@link getGamutOverlay} using the same `props`.
314
+ */
315
+ getGamutOverlayProps: (props?: GamutOverlayProps) => T["svg"];
285
316
  getAreaThumbProps: (props?: AreaProps) => T["element"];
286
317
  getChannelInputProps: (props: ChannelInputProps) => T["input"];
287
318
  getChannelSliderProps: (props: ChannelSliderProps) => T["element"];
@@ -300,4 +331,4 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
300
331
  getFormatTriggerProps: () => T["button"];
301
332
  }
302
333
 
303
- export type { AreaProps, ChannelInputProps, ChannelProps, ChannelSliderProps, ColorPickerApi, ColorPickerMachine, ColorPickerProps, ColorPickerSchema, ColorPickerService, ElementIds, ExtendedColorChannel, FormatChangeDetails, OpenChangeDetails, SwatchProps, SwatchTriggerProps, SwatchTriggerState, TransparencyGridProps, ValueChangeDetails };
334
+ export { type AreaProps, type ChannelInputProps, type ChannelProps, type ChannelSliderProps, type ColorPickerApi, type ColorPickerMachine, type ColorPickerProps, type ColorPickerSchema, type ColorPickerService, type ElementIds, type ExtendedColorChannel, type FormatChangeDetails, GamutOverlayData, type GamutOverlayProps, type OpenChangeDetails, type SwatchProps, type SwatchTriggerProps, type SwatchTriggerState, type TransparencyGridProps, type ValueChangeDetails };
@@ -1,5 +1,6 @@
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
5
  import { InteractOutsideHandlers } from '@zag-js/dismissable';
5
6
  import { PositioningOptions } from '@zag-js/popper';
@@ -160,8 +161,14 @@ type ColorPickerSchema = {
160
161
  activeId: string | null;
161
162
  activeChannel: Partial<ColorAxes> | null;
162
163
  activeOrientation: Orientation | null;
164
+ areaBaseColor: Color | null;
165
+ areaDragPosition: {
166
+ x: number;
167
+ y: number;
168
+ } | null;
163
169
  fieldsetDisabled: boolean;
164
170
  currentPlacement: PositioningOptions["placement"] | undefined;
171
+ positioned: boolean;
165
172
  restoreFocus: boolean;
166
173
  };
167
174
  event: EventObject;
@@ -186,6 +193,16 @@ interface AreaProps {
186
193
  xChannel?: ColorChannel | undefined;
187
194
  yChannel?: ColorChannel | undefined;
188
195
  }
196
+ /**
197
+ * Same channel overrides as {@link AreaProps}, plus optional DPR for overlay sampling.
198
+ * Pass the **same** object to `getAreaProps`, `getGamutOverlay`, and `getGamutOverlayProps`.
199
+ */
200
+ interface GamutOverlayProps extends AreaProps {
201
+ /**
202
+ * Overrides `globalThis.devicePixelRatio` for boundary resolution (default browser DPR in connect).
203
+ */
204
+ pixelRatio?: number | undefined;
205
+ }
189
206
  interface SwatchTriggerProps {
190
207
  /**
191
208
  * The color value
@@ -236,6 +253,11 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
236
253
  * The current color value (as a Color object)
237
254
  */
238
255
  valueAsString: string;
256
+ /**
257
+ * Whether the current color lies inside the sRGB gamut (linear RGB cube).
258
+ * When `false`, wide-gamut CSS strings may still be valid while 8-bit RGB / hex are clamped.
259
+ */
260
+ isInSrgbGamut: boolean;
239
261
  /**
240
262
  * Function to set the color value
241
263
  */
@@ -282,6 +304,15 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
282
304
  getValueTextProps: () => T["element"];
283
305
  getAreaProps: (props?: AreaProps) => T["element"];
284
306
  getAreaBackgroundProps: (props?: AreaProps) => T["element"];
307
+ /**
308
+ * SVG path + label position for the sRGB gamut boundary (oklch/oklab + HSB area only); `null` when not shown.
309
+ * Use the **same** `props` as {@link getAreaProps} / {@link getGamutOverlayProps} (including `pixelRatio`).
310
+ */
311
+ getGamutOverlay: (props?: GamutOverlayProps) => GamutOverlayData | null;
312
+ /**
313
+ * Props for an `<svg>` gamut overlay; pair with {@link getGamutOverlay} using the same `props`.
314
+ */
315
+ getGamutOverlayProps: (props?: GamutOverlayProps) => T["svg"];
285
316
  getAreaThumbProps: (props?: AreaProps) => T["element"];
286
317
  getChannelInputProps: (props: ChannelInputProps) => T["input"];
287
318
  getChannelSliderProps: (props: ChannelSliderProps) => T["element"];
@@ -300,4 +331,4 @@ interface ColorPickerApi<T extends PropTypes = PropTypes> {
300
331
  getFormatTriggerProps: () => T["button"];
301
332
  }
302
333
 
303
- export type { AreaProps, ChannelInputProps, ChannelProps, ChannelSliderProps, ColorPickerApi, ColorPickerMachine, ColorPickerProps, ColorPickerSchema, ColorPickerService, ElementIds, ExtendedColorChannel, FormatChangeDetails, OpenChangeDetails, SwatchProps, SwatchTriggerProps, SwatchTriggerState, TransparencyGridProps, ValueChangeDetails };
334
+ export { type AreaProps, type ChannelInputProps, type ChannelProps, type ChannelSliderProps, type ColorPickerApi, type ColorPickerMachine, type ColorPickerProps, type ColorPickerSchema, type ColorPickerService, type ElementIds, type ExtendedColorChannel, type FormatChangeDetails, GamutOverlayData, type GamutOverlayProps, type OpenChangeDetails, type SwatchProps, type SwatchTriggerProps, type SwatchTriggerState, type TransparencyGridProps, 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, ElementIds, ExtendedColorChannel, FormatChangeDetails, GamutOverlayProps, ColorPickerMachine as Machine, OpenChangeDetails, ColorPickerProps as Props, ColorPickerService as Service, SwatchProps, SwatchTriggerProps, SwatchTriggerState, TransparencyGridProps, 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';
package/dist/index.d.ts CHANGED
@@ -4,8 +4,9 @@ export { connect } from './color-picker.connect.js';
4
4
  export { machine } from './color-picker.machine.js';
5
5
  export { parse } from './color-picker.parse.js';
6
6
  export { areaProps, channelProps, props, splitAreaProps, splitChannelProps, splitProps, splitSwatchProps, splitSwatchTriggerProps, splitTransparencyGridProps, swatchProps, swatchTriggerProps, transparencyGridProps } from './color-picker.props.js';
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.js';
7
+ export { ColorPickerApi as Api, AreaProps, ChannelInputProps, ChannelProps, ChannelSliderProps, ElementIds, ExtendedColorChannel, FormatChangeDetails, GamutOverlayProps, ColorPickerMachine as Machine, OpenChangeDetails, ColorPickerProps as Props, ColorPickerService as Service, SwatchProps, SwatchTriggerProps, SwatchTriggerState, TransparencyGridProps, ValueChangeDetails } from './color-picker.types.js';
8
8
  export { Color, ColorAxes, ColorChannel, ColorFormat, ColorType } from '@zag-js/color-utils';
9
+ export { GamutOverlayData } from './utils/get-gamut-overlay-path.js';
9
10
  export { PositioningOptions } from '@zag-js/popper';
10
11
  import '@zag-js/anatomy';
11
12
  import '@zag-js/types';
@@ -0,0 +1,6 @@
1
+ import { ColorFormat } from '@zag-js/color-utils';
2
+
3
+ /** Color space used for the 2D area for the active output format. */
4
+ declare function getAreaFormat(format: ColorFormat): ColorFormat;
5
+
6
+ export { getAreaFormat };
@@ -0,0 +1,6 @@
1
+ import { ColorFormat } from '@zag-js/color-utils';
2
+
3
+ /** Color space used for the 2D area for the active output format. */
4
+ declare function getAreaFormat(format: ColorFormat): ColorFormat;
5
+
6
+ export { getAreaFormat };
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/utils/get-area-format.ts
21
+ var get_area_format_exports = {};
22
+ __export(get_area_format_exports, {
23
+ getAreaFormat: () => getAreaFormat
24
+ });
25
+ module.exports = __toCommonJS(get_area_format_exports);
26
+ function getAreaFormat(format) {
27
+ if (format.startsWith("hsl")) return "hsla";
28
+ if (format.startsWith("hsb")) return "hsba";
29
+ if (format.startsWith("rgb")) return "rgba";
30
+ if (format === "oklab") return "hsba";
31
+ if (format === "oklch") return "hsba";
32
+ return "hsba";
33
+ }
34
+ // Annotate the CommonJS export names for ESM import in node:
35
+ 0 && (module.exports = {
36
+ getAreaFormat
37
+ });
@@ -0,0 +1,12 @@
1
+ // src/utils/get-area-format.ts
2
+ function getAreaFormat(format) {
3
+ if (format.startsWith("hsl")) return "hsla";
4
+ if (format.startsWith("hsb")) return "hsba";
5
+ if (format.startsWith("rgb")) return "rgba";
6
+ if (format === "oklab") return "hsba";
7
+ if (format === "oklch") return "hsba";
8
+ return "hsba";
9
+ }
10
+ export {
11
+ getAreaFormat
12
+ };
@@ -25,10 +25,17 @@ __export(get_channel_display_color_exports, {
25
25
  module.exports = __toCommonJS(get_channel_display_color_exports);
26
26
  var import_color_utils = require("@zag-js/color-utils");
27
27
  function getChannelDisplayColor(color, channel) {
28
+ const fmt = color.getFormat();
28
29
  switch (channel) {
29
30
  case "hue":
31
+ if (fmt === "oklch") {
32
+ return color.withChannelValue("alpha", 1);
33
+ }
30
34
  return (0, import_color_utils.parseColor)(`hsl(${color.getChannelValue("hue")}, 100%, 50%)`);
31
35
  case "lightness":
36
+ case "chroma":
37
+ case "a":
38
+ case "b":
32
39
  case "brightness":
33
40
  case "saturation":
34
41
  case "red":
@@ -1,10 +1,17 @@
1
1
  // src/utils/get-channel-display-color.ts
2
2
  import { parseColor } from "@zag-js/color-utils";
3
3
  function getChannelDisplayColor(color, channel) {
4
+ const fmt = color.getFormat();
4
5
  switch (channel) {
5
6
  case "hue":
7
+ if (fmt === "oklch") {
8
+ return color.withChannelValue("alpha", 1);
9
+ }
6
10
  return parseColor(`hsl(${color.getChannelValue("hue")}, 100%, 50%)`);
7
11
  case "lightness":
12
+ case "chroma":
13
+ case "a":
14
+ case "b":
8
15
  case "brightness":
9
16
  case "saturation":
10
17
  case "red":
@@ -1,5 +1,6 @@
1
1
  import { Color, ColorChannelRange } from '@zag-js/color-utils';
2
2
  import { ExtendedColorChannel } from '../color-picker.types.mjs';
3
+ import './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 { Color, ColorChannelRange } from '@zag-js/color-utils';
2
2
  import { ExtendedColorChannel } from '../color-picker.types.js';
3
+ import './get-gamut-overlay-path.js';
3
4
  import '@zag-js/core';
4
5
  import '@zag-js/dismissable';
5
6
  import '@zag-js/popper';
@@ -34,26 +34,45 @@ function getChannelValue(color, channel) {
34
34
  return color.toString("css");
35
35
  }
36
36
  if (channel in color) {
37
- return color.getChannelValue(channel).toString();
37
+ return roundedValue(color, channel);
38
38
  }
39
- const isHSL = color.getFormat() === "hsla";
39
+ const fmt = color.getFormat();
40
+ const isHSL = fmt === "hsla";
40
41
  switch (channel) {
41
42
  case "hue":
42
- return isHSL ? color.toFormat("hsla").getChannelValue("hue").toString() : color.toFormat("hsba").getChannelValue("hue").toString();
43
+ if (fmt === "oklch") {
44
+ return roundedValue(color.toFormat("oklch"), "hue");
45
+ }
46
+ return isHSL ? roundedValue(color.toFormat("hsla"), "hue") : roundedValue(color.toFormat("hsba"), "hue");
43
47
  case "saturation":
44
- return isHSL ? color.toFormat("hsla").getChannelValue("saturation").toString() : color.toFormat("hsba").getChannelValue("saturation").toString();
48
+ return isHSL ? roundedValue(color.toFormat("hsla"), "saturation") : roundedValue(color.toFormat("hsba"), "saturation");
45
49
  case "lightness":
46
- return color.toFormat("hsla").getChannelValue("lightness").toString();
50
+ if (fmt === "oklab" || fmt === "oklch") {
51
+ return roundedValue(color, "lightness");
52
+ }
53
+ return roundedValue(color.toFormat("hsla"), "lightness");
47
54
  case "brightness":
48
- return color.toFormat("hsba").getChannelValue("brightness").toString();
55
+ return roundedValue(color.toFormat("hsba"), "brightness");
56
+ case "a":
57
+ case "b":
58
+ return roundedValue(color.toFormat("oklab"), channel);
59
+ case "chroma":
60
+ return roundedValue(color.toFormat("oklch"), channel);
49
61
  case "red":
50
62
  case "green":
51
63
  case "blue":
52
- return color.toFormat("rgba").getChannelValue(channel).toString();
64
+ return roundedValue(color.toFormat("rgba"), channel);
53
65
  default:
54
- return color.getChannelValue(channel).toString();
66
+ return roundedValue(color, channel);
55
67
  }
56
68
  }
69
+ function roundedValue(color, channel) {
70
+ const value = color.getChannelValue(channel);
71
+ const { step } = color.getChannelRange(channel);
72
+ if (step >= 1) return Math.round(value).toString();
73
+ const decimals = Math.min(4, Math.max(0, Math.ceil(-Math.log10(step))));
74
+ return parseFloat(value.toFixed(decimals)).toString();
75
+ }
57
76
  function getChannelRange(color, channel) {
58
77
  switch (channel) {
59
78
  case "hex":
@@ -68,11 +87,23 @@ function getChannelRange(color, channel) {
68
87
  case "css":
69
88
  return void 0;
70
89
  case "hue":
90
+ if (color.getFormat() === "oklch") {
91
+ return color.toFormat("oklch").getChannelRange("hue");
92
+ }
93
+ return color.toFormat("hsla").getChannelRange(channel);
71
94
  case "saturation":
72
95
  case "lightness":
96
+ if (color.getFormat() === "oklab" || color.getFormat() === "oklch") {
97
+ return color.getChannelRange(channel);
98
+ }
73
99
  return color.toFormat("hsla").getChannelRange(channel);
74
100
  case "brightness":
75
101
  return color.toFormat("hsba").getChannelRange(channel);
102
+ case "a":
103
+ case "b":
104
+ return color.toFormat("oklab").getChannelRange(channel);
105
+ case "chroma":
106
+ return color.toFormat("oklch").getChannelRange(channel);
76
107
  case "red":
77
108
  case "green":
78
109
  case "blue":
@@ -9,26 +9,45 @@ function getChannelValue(color, channel) {
9
9
  return color.toString("css");
10
10
  }
11
11
  if (channel in color) {
12
- return color.getChannelValue(channel).toString();
12
+ return roundedValue(color, channel);
13
13
  }
14
- const isHSL = color.getFormat() === "hsla";
14
+ const fmt = color.getFormat();
15
+ const isHSL = fmt === "hsla";
15
16
  switch (channel) {
16
17
  case "hue":
17
- return isHSL ? color.toFormat("hsla").getChannelValue("hue").toString() : color.toFormat("hsba").getChannelValue("hue").toString();
18
+ if (fmt === "oklch") {
19
+ return roundedValue(color.toFormat("oklch"), "hue");
20
+ }
21
+ return isHSL ? roundedValue(color.toFormat("hsla"), "hue") : roundedValue(color.toFormat("hsba"), "hue");
18
22
  case "saturation":
19
- return isHSL ? color.toFormat("hsla").getChannelValue("saturation").toString() : color.toFormat("hsba").getChannelValue("saturation").toString();
23
+ return isHSL ? roundedValue(color.toFormat("hsla"), "saturation") : roundedValue(color.toFormat("hsba"), "saturation");
20
24
  case "lightness":
21
- return color.toFormat("hsla").getChannelValue("lightness").toString();
25
+ if (fmt === "oklab" || fmt === "oklch") {
26
+ return roundedValue(color, "lightness");
27
+ }
28
+ return roundedValue(color.toFormat("hsla"), "lightness");
22
29
  case "brightness":
23
- return color.toFormat("hsba").getChannelValue("brightness").toString();
30
+ return roundedValue(color.toFormat("hsba"), "brightness");
31
+ case "a":
32
+ case "b":
33
+ return roundedValue(color.toFormat("oklab"), channel);
34
+ case "chroma":
35
+ return roundedValue(color.toFormat("oklch"), channel);
24
36
  case "red":
25
37
  case "green":
26
38
  case "blue":
27
- return color.toFormat("rgba").getChannelValue(channel).toString();
39
+ return roundedValue(color.toFormat("rgba"), channel);
28
40
  default:
29
- return color.getChannelValue(channel).toString();
41
+ return roundedValue(color, channel);
30
42
  }
31
43
  }
44
+ function roundedValue(color, channel) {
45
+ const value = color.getChannelValue(channel);
46
+ const { step } = color.getChannelRange(channel);
47
+ if (step >= 1) return Math.round(value).toString();
48
+ const decimals = Math.min(4, Math.max(0, Math.ceil(-Math.log10(step))));
49
+ return parseFloat(value.toFixed(decimals)).toString();
50
+ }
32
51
  function getChannelRange(color, channel) {
33
52
  switch (channel) {
34
53
  case "hex":
@@ -43,11 +62,23 @@ function getChannelRange(color, channel) {
43
62
  case "css":
44
63
  return void 0;
45
64
  case "hue":
65
+ if (color.getFormat() === "oklch") {
66
+ return color.toFormat("oklch").getChannelRange("hue");
67
+ }
68
+ return color.toFormat("hsla").getChannelRange(channel);
46
69
  case "saturation":
47
70
  case "lightness":
71
+ if (color.getFormat() === "oklab" || color.getFormat() === "oklch") {
72
+ return color.getChannelRange(channel);
73
+ }
48
74
  return color.toFormat("hsla").getChannelRange(channel);
49
75
  case "brightness":
50
76
  return color.toFormat("hsba").getChannelRange(channel);
77
+ case "a":
78
+ case "b":
79
+ return color.toFormat("oklab").getChannelRange(channel);
80
+ case "chroma":
81
+ return color.toFormat("oklch").getChannelRange(channel);
51
82
  case "red":
52
83
  case "green":
53
84
  case "blue":
@@ -0,0 +1,33 @@
1
+ import { Color, ColorChannel, ColorFormat } from '@zag-js/color-utils';
2
+
3
+ interface GamutOverlayOptions {
4
+ xChannel: ColorChannel;
5
+ yChannel: ColorChannel;
6
+ }
7
+ interface GamutOverlayData {
8
+ path: string;
9
+ labelPosition: {
10
+ x: number;
11
+ y: number;
12
+ };
13
+ }
14
+ interface GamutOverlayComputeOptions {
15
+ /**
16
+ * Device pixel ratio; higher values sample more rows along brightness (Chrome DevTools–style).
17
+ * Default `1` when not passed (callers may pass `globalThis.devicePixelRatio` in browsers).
18
+ */
19
+ pixelRatio?: number | undefined;
20
+ }
21
+ /**
22
+ * Computes an SVG path (0–100 user units) tracing the sRGB gamut boundary
23
+ * over the HSB color area when in a wide-gamut output format (oklch/oklab).
24
+ *
25
+ * Approach (matching Chrome DevTools): treat the HSV area as Display P3 gamut,
26
+ * then for each brightness row, sweep saturation left→right to find where
27
+ * the P3 color exits the sRGB gamut.
28
+ */
29
+ declare function getGamutOverlayData(areaValue: Color, axes: GamutOverlayOptions, format: ColorFormat, options?: GamutOverlayComputeOptions): GamutOverlayData | null;
30
+ /** Visible boundary rows scale with DPR (analogous to Chrome `step = 1 / devicePixelRatio` in px space). */
31
+ declare function gamutOverlayRowCount(pixelRatio: number): number;
32
+
33
+ export { type GamutOverlayComputeOptions, type GamutOverlayData, gamutOverlayRowCount, getGamutOverlayData };