@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
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, 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.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
+ };
@@ -1,5 +1,6 @@
1
+ import * as _zag_js_color_utils from '@zag-js/color-utils';
1
2
  import { Color, ColorChannel } from '@zag-js/color-utils';
2
3
 
3
- declare function getChannelDisplayColor(color: Color, channel: ColorChannel): Color;
4
+ declare function getChannelDisplayColor(color: Color, channel: ColorChannel): _zag_js_color_utils.ColorType;
4
5
 
5
6
  export { getChannelDisplayColor };
@@ -1,5 +1,6 @@
1
+ import * as _zag_js_color_utils from '@zag-js/color-utils';
1
2
  import { Color, ColorChannel } from '@zag-js/color-utils';
2
3
 
3
- declare function getChannelDisplayColor(color: Color, channel: ColorChannel): Color;
4
+ declare function getChannelDisplayColor(color: Color, channel: ColorChannel): _zag_js_color_utils.ColorType;
4
5
 
5
6
  export { getChannelDisplayColor };
@@ -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 };
@@ -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 };
@@ -0,0 +1,81 @@
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-gamut-overlay-path.ts
21
+ var get_gamut_overlay_path_exports = {};
22
+ __export(get_gamut_overlay_path_exports, {
23
+ gamutOverlayRowCount: () => gamutOverlayRowCount,
24
+ getGamutOverlayData: () => getGamutOverlayData
25
+ });
26
+ module.exports = __toCommonJS(get_gamut_overlay_path_exports);
27
+ var import_color_utils = require("@zag-js/color-utils");
28
+ function getGamutOverlayData(areaValue, axes, format, options) {
29
+ if (format !== "oklch" && format !== "oklab") return null;
30
+ if (axes.xChannel !== "saturation" || axes.yChannel !== "brightness") return null;
31
+ const hue = areaValue.getChannelValue("hue");
32
+ const pixelRatio = options?.pixelRatio ?? 1;
33
+ return buildDisplayP3GamutPath(hue, pixelRatio);
34
+ }
35
+ function gamutOverlayRowCount(pixelRatio) {
36
+ const pr = Math.max(1, pixelRatio);
37
+ return Math.min(200, Math.max(32, Math.round(80 * pr)));
38
+ }
39
+ function buildDisplayP3GamutPath(hue, pixelRatio) {
40
+ const steps = gamutOverlayRowCount(pixelRatio);
41
+ const points = [];
42
+ for (let i = 0; i <= steps; i++) {
43
+ const brightness = i / steps;
44
+ if (brightness < 0.01) continue;
45
+ let lo = 0;
46
+ let hi = 1;
47
+ for (let j = 0; j < 20; j++) {
48
+ const mid = (lo + hi) / 2;
49
+ if ((0, import_color_utils.isDisplayP3HsvInSrgbGamut)(hue, mid, brightness)) {
50
+ lo = mid;
51
+ } else {
52
+ hi = mid;
53
+ }
54
+ }
55
+ const xp = Math.min(lo * 100, 100);
56
+ const yp = 100 - brightness * 100;
57
+ points.push({ x: xp, y: yp });
58
+ }
59
+ if (points.length < 2) return null;
60
+ const cmds = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x.toFixed(2)} ${p.y.toFixed(2)}`);
61
+ const labelY = 96;
62
+ let labelX = points[0].x;
63
+ for (let i = 0; i < points.length - 1; i++) {
64
+ const a = points[i], b = points[i + 1];
65
+ if (a.y >= labelY && b.y <= labelY || a.y <= labelY && b.y >= labelY) {
66
+ const t = (labelY - a.y) / (b.y - a.y);
67
+ labelX = a.x + t * (b.x - a.x);
68
+ break;
69
+ }
70
+ }
71
+ if (points[0].y < labelY) labelX = points[0].x;
72
+ return {
73
+ path: cmds.join(" "),
74
+ labelPosition: { x: Math.min(labelX - 16, 99), y: labelY }
75
+ };
76
+ }
77
+ // Annotate the CommonJS export names for ESM import in node:
78
+ 0 && (module.exports = {
79
+ gamutOverlayRowCount,
80
+ getGamutOverlayData
81
+ });
@@ -0,0 +1,55 @@
1
+ // src/utils/get-gamut-overlay-path.ts
2
+ import { isDisplayP3HsvInSrgbGamut } from "@zag-js/color-utils";
3
+ function getGamutOverlayData(areaValue, axes, format, options) {
4
+ if (format !== "oklch" && format !== "oklab") return null;
5
+ if (axes.xChannel !== "saturation" || axes.yChannel !== "brightness") return null;
6
+ const hue = areaValue.getChannelValue("hue");
7
+ const pixelRatio = options?.pixelRatio ?? 1;
8
+ return buildDisplayP3GamutPath(hue, pixelRatio);
9
+ }
10
+ function gamutOverlayRowCount(pixelRatio) {
11
+ const pr = Math.max(1, pixelRatio);
12
+ return Math.min(200, Math.max(32, Math.round(80 * pr)));
13
+ }
14
+ function buildDisplayP3GamutPath(hue, pixelRatio) {
15
+ const steps = gamutOverlayRowCount(pixelRatio);
16
+ const points = [];
17
+ for (let i = 0; i <= steps; i++) {
18
+ const brightness = i / steps;
19
+ if (brightness < 0.01) continue;
20
+ let lo = 0;
21
+ let hi = 1;
22
+ for (let j = 0; j < 20; j++) {
23
+ const mid = (lo + hi) / 2;
24
+ if (isDisplayP3HsvInSrgbGamut(hue, mid, brightness)) {
25
+ lo = mid;
26
+ } else {
27
+ hi = mid;
28
+ }
29
+ }
30
+ const xp = Math.min(lo * 100, 100);
31
+ const yp = 100 - brightness * 100;
32
+ points.push({ x: xp, y: yp });
33
+ }
34
+ if (points.length < 2) return null;
35
+ const cmds = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x.toFixed(2)} ${p.y.toFixed(2)}`);
36
+ const labelY = 96;
37
+ let labelX = points[0].x;
38
+ for (let i = 0; i < points.length - 1; i++) {
39
+ const a = points[i], b = points[i + 1];
40
+ if (a.y >= labelY && b.y <= labelY || a.y <= labelY && b.y >= labelY) {
41
+ const t = (labelY - a.y) / (b.y - a.y);
42
+ labelX = a.x + t * (b.x - a.x);
43
+ break;
44
+ }
45
+ }
46
+ if (points[0].y < labelY) labelX = points[0].x;
47
+ return {
48
+ path: cmds.join(" "),
49
+ labelPosition: { x: Math.min(labelX - 16, 99), y: labelY }
50
+ };
51
+ }
52
+ export {
53
+ gamutOverlayRowCount,
54
+ getGamutOverlayData
55
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ // src/utils/get-gamut-overlay-path.test.ts
4
+ var import_vitest = require("vitest.js");
5
+ var import_color_utils = require("@zag-js/color-utils");
6
+ var import_get_gamut_overlay_path = require("./get-gamut-overlay-path.js");
7
+ (0, import_vitest.describe)("get-gamut-overlay-path", () => {
8
+ const hsba = (0, import_color_utils.parseColor)("hsba(0, 100%, 100%, 1)");
9
+ (0, import_vitest.test)("wrong area axes returns null", () => {
10
+ (0, import_vitest.expect)(
11
+ (0, import_get_gamut_overlay_path.getGamutOverlayData)(hsba, { xChannel: "hue", yChannel: "saturation" }, "oklch", { pixelRatio: 1 })
12
+ ).toBeNull();
13
+ });
14
+ (0, import_vitest.test)("non-wide format returns null", () => {
15
+ (0, import_vitest.expect)((0, import_get_gamut_overlay_path.getGamutOverlayData)(hsba, { xChannel: "saturation", yChannel: "brightness" }, "hsla")).toBeNull();
16
+ });
17
+ (0, import_vitest.test)("higher pixelRatio increases row count and path complexity", () => {
18
+ const low = (0, import_get_gamut_overlay_path.getGamutOverlayData)(hsba, { xChannel: "saturation", yChannel: "brightness" }, "oklch", {
19
+ pixelRatio: 1
20
+ });
21
+ const high = (0, import_get_gamut_overlay_path.getGamutOverlayData)(hsba, { xChannel: "saturation", yChannel: "brightness" }, "oklch", {
22
+ pixelRatio: 2
23
+ });
24
+ (0, import_vitest.expect)(low).not.toBeNull();
25
+ (0, import_vitest.expect)(high).not.toBeNull();
26
+ (0, import_vitest.expect)((0, import_get_gamut_overlay_path.gamutOverlayRowCount)(2)).toBeGreaterThan((0, import_get_gamut_overlay_path.gamutOverlayRowCount)(1));
27
+ (0, import_vitest.expect)(high.path.length).toBeGreaterThan(low.path.length);
28
+ });
29
+ (0, import_vitest.test)("gamutOverlayRowCount scales with DPR and caps at 200", () => {
30
+ (0, import_vitest.expect)((0, import_get_gamut_overlay_path.gamutOverlayRowCount)(1)).toBe(80);
31
+ (0, import_vitest.expect)((0, import_get_gamut_overlay_path.gamutOverlayRowCount)(2)).toBe(160);
32
+ (0, import_vitest.expect)((0, import_get_gamut_overlay_path.gamutOverlayRowCount)(100)).toBe(200);
33
+ (0, import_vitest.expect)((0, import_get_gamut_overlay_path.gamutOverlayRowCount)(0.5)).toBe(80);
34
+ });
35
+ });
@@ -0,0 +1,33 @@
1
+ // src/utils/get-gamut-overlay-path.test.ts
2
+ import { describe, expect, test } from "vitest.mjs";
3
+ import { parseColor } from "@zag-js/color-utils";
4
+ import { gamutOverlayRowCount, getGamutOverlayData } from "./get-gamut-overlay-path.mjs";
5
+ describe("get-gamut-overlay-path", () => {
6
+ const hsba = parseColor("hsba(0, 100%, 100%, 1)");
7
+ test("wrong area axes returns null", () => {
8
+ expect(
9
+ getGamutOverlayData(hsba, { xChannel: "hue", yChannel: "saturation" }, "oklch", { pixelRatio: 1 })
10
+ ).toBeNull();
11
+ });
12
+ test("non-wide format returns null", () => {
13
+ expect(getGamutOverlayData(hsba, { xChannel: "saturation", yChannel: "brightness" }, "hsla")).toBeNull();
14
+ });
15
+ test("higher pixelRatio increases row count and path complexity", () => {
16
+ const low = getGamutOverlayData(hsba, { xChannel: "saturation", yChannel: "brightness" }, "oklch", {
17
+ pixelRatio: 1
18
+ });
19
+ const high = getGamutOverlayData(hsba, { xChannel: "saturation", yChannel: "brightness" }, "oklch", {
20
+ pixelRatio: 2
21
+ });
22
+ expect(low).not.toBeNull();
23
+ expect(high).not.toBeNull();
24
+ expect(gamutOverlayRowCount(2)).toBeGreaterThan(gamutOverlayRowCount(1));
25
+ expect(high.path.length).toBeGreaterThan(low.path.length);
26
+ });
27
+ test("gamutOverlayRowCount scales with DPR and caps at 200", () => {
28
+ expect(gamutOverlayRowCount(1)).toBe(80);
29
+ expect(gamutOverlayRowCount(2)).toBe(160);
30
+ expect(gamutOverlayRowCount(100)).toBe(200);
31
+ expect(gamutOverlayRowCount(0.5)).toBe(80);
32
+ });
33
+ });
@@ -1,5 +1,6 @@
1
1
  import { ChannelProps, ColorPickerProps } from '../color-picker.types.mjs';
2
2
  import { Color } from '@zag-js/color-utils';
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 { ChannelProps, ColorPickerProps } from '../color-picker.types.js';
2
2
  import { Color } from '@zag-js/color-utils';
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';
@@ -36,8 +36,14 @@ var getSliderBackground = (props) => {
36
36
  const { channel, value, dir, orientation } = props;
37
37
  const bgDirection = getSliderBackgroundDirection(orientation, dir);
38
38
  const { minValue, maxValue } = value.getChannelRange(channel);
39
+ const fmt = value.getFormat();
39
40
  switch (channel) {
40
41
  case "hue":
42
+ if (fmt === "oklch") {
43
+ const L = value.getChannelValue("lightness");
44
+ const C = value.getChannelValue("chroma");
45
+ return `linear-gradient(to ${bgDirection} in oklch increasing hue, oklch(${L} ${C} 0), oklch(${L} ${C} 360))`;
46
+ }
41
47
  return `linear-gradient(to ${bgDirection}, rgb(255, 0, 0) 0%, rgb(255, 255, 0) 17%, rgb(0, 255, 0) 33%, rgb(0, 255, 255) 50%, rgb(0, 0, 255) 67%, rgb(255, 0, 255) 83%, rgb(255, 0, 0) 100%)`;
42
48
  case "lightness": {
43
49
  let start = value.withChannelValue(channel, minValue).toString("css");
@@ -50,9 +56,18 @@ var getSliderBackground = (props) => {
50
56
  case "red":
51
57
  case "green":
52
58
  case "blue":
53
- case "alpha": {
59
+ case "alpha":
60
+ case "a":
61
+ case "b":
62
+ case "chroma": {
54
63
  let start = value.withChannelValue(channel, minValue).toString("css");
55
64
  let end = value.withChannelValue(channel, maxValue).toString("css");
65
+ if (fmt === "oklab" && (channel === "a" || channel === "b")) {
66
+ return `linear-gradient(to ${bgDirection} in oklab, ${start}, ${end})`;
67
+ }
68
+ if (fmt === "oklch" && channel === "chroma") {
69
+ return `linear-gradient(to ${bgDirection} in oklch, ${start}, ${end})`;
70
+ }
56
71
  return `linear-gradient(to ${bgDirection}, ${start}, ${end})`;
57
72
  }
58
73
  default: