@patternmode/swatch 0.9.2 → 0.10.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 (34) hide show
  1. package/README.md +38 -0
  2. package/dist/DistributionBar/distribution-bar-math.d.ts +29 -0
  3. package/dist/DistributionBar/distribution-bar-math.d.ts.map +1 -0
  4. package/dist/DistributionBar/distribution-bar-root.d.ts +56 -0
  5. package/dist/DistributionBar/distribution-bar-root.d.ts.map +1 -0
  6. package/dist/DistributionBar/index.d.ts +2 -2
  7. package/dist/DistributionBar/index.d.ts.map +1 -1
  8. package/dist/Swatch/swatch-atmosphere.d.ts +9 -0
  9. package/dist/Swatch/swatch-atmosphere.d.ts.map +1 -0
  10. package/dist/Swatch/swatch-colors.d.ts +4 -0
  11. package/dist/Swatch/swatch-colors.d.ts.map +1 -0
  12. package/dist/Swatch/swatch-root.d.ts +3 -0
  13. package/dist/Swatch/swatch-root.d.ts.map +1 -0
  14. package/dist/Swatch/swatch-types.d.ts +158 -0
  15. package/dist/Swatch/swatch-types.d.ts.map +1 -0
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.mjs +267 -239
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/swatch.d.ts +4 -4
  21. package/dist/swatch.d.ts.map +1 -1
  22. package/package.json +4 -3
  23. package/dist/DistributionBar/DistributionBarMath.d.ts +0 -15
  24. package/dist/DistributionBar/DistributionBarMath.d.ts.map +0 -1
  25. package/dist/DistributionBar/DistributionBarRoot.d.ts +0 -28
  26. package/dist/DistributionBar/DistributionBarRoot.d.ts.map +0 -1
  27. package/dist/Swatch/SwatchAtmosphere.d.ts +0 -15
  28. package/dist/Swatch/SwatchAtmosphere.d.ts.map +0 -1
  29. package/dist/Swatch/SwatchColors.d.ts +0 -4
  30. package/dist/Swatch/SwatchColors.d.ts.map +0 -1
  31. package/dist/Swatch/SwatchRoot.d.ts +0 -3
  32. package/dist/Swatch/SwatchRoot.d.ts.map +0 -1
  33. package/dist/Swatch/SwatchTypes.d.ts +0 -69
  34. package/dist/Swatch/SwatchTypes.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -2,16 +2,27 @@ import { PATTERNMODE_SIZES, PATTERNMODE_SIZE_VALUES, getObjectSizingStyle, joinC
2
2
  import { LazyMotion, domMax, m } from "motion/react";
3
3
  import { useRef } from "react";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- //#region src/DistributionBar/DistributionBarMath.ts
6
- function getDistributionTotal(segments) {
7
- return segments.reduce((sum, segment) => sum + sanitizeValue(segment.value), 0);
8
- }
9
- function getDistributionBoundaryPercent(segments, boundaryIndex) {
5
+ import { Slot, Slottable } from "@radix-ui/react-slot";
6
+ //#region src/DistributionBar/distribution-bar-math.ts
7
+ const sanitizeValue = (value) => Number.isFinite(value) ? Math.max(0, value) : 0;
8
+ const clamp$1 = (value, min, max) => Math.min(Math.max(value, min), max);
9
+ const roundValue = (value) => Number(value.toFixed(1));
10
+ /** Sums sanitized segment weights, treating invalid or negative values as 0. */
11
+ const getDistributionTotal = (segments) => segments.reduce((sum, segment) => sum + sanitizeValue(segment.value), 0);
12
+ /** Returns the percentage position of the boundary after `boundaryIndex`. */
13
+ const getDistributionBoundaryPercent = (segments, boundaryIndex) => {
10
14
  const total = getDistributionTotal(segments);
11
15
  if (total <= 0) return 0;
12
16
  return roundValue(segments.slice(0, boundaryIndex + 1).reduce((sum, segment) => sum + sanitizeValue(segment.value), 0) / total * 100);
13
- }
14
- function moveDistributionBoundary(segments, boundaryIndex, deltaValue, minValue) {
17
+ };
18
+ /**
19
+ * Moves the boundary between two adjacent segments while preserving their sum.
20
+ *
21
+ * `deltaValue` is applied to the left segment and subtracted from the right
22
+ * segment. `minValue` prevents either side of the pair from collapsing below a
23
+ * caller-defined minimum.
24
+ */
25
+ const moveDistributionBoundary = (segments, boundaryIndex, deltaValue, minValue) => {
15
26
  const left = segments[boundaryIndex];
16
27
  const right = segments[boundaryIndex + 1];
17
28
  if (!(left && right)) return segments;
@@ -30,8 +41,9 @@ function moveDistributionBoundary(segments, boundaryIndex, deltaValue, minValue)
30
41
  };
31
42
  return segment;
32
43
  });
33
- }
34
- function removeDistributionSegment(segments, segmentId) {
44
+ };
45
+ /** Removes a segment and redistributes its weight proportionally to the rest. */
46
+ const removeDistributionSegment = (segments, segmentId) => {
35
47
  if (segments.length <= 1) return segments;
36
48
  const removed = segments.find((segment) => segment.id === segmentId);
37
49
  if (!removed) return segments;
@@ -59,25 +71,111 @@ function removeDistributionSegment(segments, segmentId) {
59
71
  value: nextValue
60
72
  };
61
73
  });
62
- }
63
- function updateDistributionSegment(segments, segmentId, update) {
64
- return segments.map((segment) => segment.id === segmentId ? {
65
- ...segment,
66
- ...update
67
- } : segment);
68
- }
69
- function sanitizeValue(value) {
70
- return Number.isFinite(value) ? Math.max(0, value) : 0;
71
- }
72
- function clamp$1(value, min, max) {
73
- return Math.min(Math.max(value, min), max);
74
- }
75
- function roundValue(value) {
76
- return Number(value.toFixed(1));
77
- }
74
+ };
75
+ /** Updates non-weight segment metadata such as label or color. */
76
+ const updateDistributionSegment = (segments, segmentId, update) => segments.map((segment) => segment.id === segmentId ? {
77
+ ...segment,
78
+ ...update
79
+ } : segment);
78
80
  //#endregion
79
- //#region src/DistributionBar/DistributionBarRoot.tsx
80
- function DistributionDisplay({ "aria-label": ariaLabel, assignedLabel = "assigned", className, emptyLabel = "unassigned", emptyValue = 0, legend = "segments", onSegmentSelect, segments, selectedSegmentId, ...props }) {
81
+ //#region src/DistributionBar/distribution-bar-root.tsx
82
+ const getRenderableDistributionValue = (value) => Number.isFinite(value) ? Math.max(0, value) : 0;
83
+ const getDerivedDistributionPercentage = (value, total) => {
84
+ if (!(total > 0 && Number.isFinite(value))) return 0;
85
+ return Math.round(Math.max(0, value) / total * 100);
86
+ };
87
+ const getDistributionDisplayTotal = (segments, emptyValue) => getDistributionTotal(segments) + getRenderableDistributionValue(emptyValue);
88
+ const getDistributionDisplayAccessibleLabel = (segments, emptyValue, emptyLabel, total) => {
89
+ const segmentLabels = segments.map((segment) => `${segment.label ?? segment.id} ${getDerivedDistributionPercentage(segment.value, total)}%`);
90
+ if (emptyValue > 0) segmentLabels.push(`${emptyLabel} ${getDerivedDistributionPercentage(emptyValue, total)}%`);
91
+ return segmentLabels.join(", ");
92
+ };
93
+ const DistributionSegments = ({ emptyValue = 0, onSegmentSelect, segments, selectedSegmentId, total }) => /* @__PURE__ */ jsxs("div", {
94
+ className: "patternmode-distribution-bar__segments",
95
+ children: [segments.map((segment) => {
96
+ const segmentStyle = {
97
+ "--patternmode-distribution-segment-color": segment.color,
98
+ width: total > 0 ? `${getRenderableDistributionValue(segment.value) / total * 100}%` : "0%"
99
+ };
100
+ const isSelected = selectedSegmentId === segment.id;
101
+ if (onSegmentSelect) return /* @__PURE__ */ jsx("button", {
102
+ "aria-label": `${segment.label ?? segment.id} ${getDerivedDistributionPercentage(segment.value, total)}%`,
103
+ "aria-pressed": isSelected,
104
+ className: "patternmode-distribution-bar__segment",
105
+ "data-selected": isSelected ? "true" : void 0,
106
+ onClick: () => onSegmentSelect(segment),
107
+ style: segmentStyle,
108
+ type: "button"
109
+ }, segment.id);
110
+ return /* @__PURE__ */ jsx("div", {
111
+ "aria-hidden": "true",
112
+ className: "patternmode-distribution-bar__segment",
113
+ "data-selected": isSelected ? "true" : void 0,
114
+ style: segmentStyle
115
+ }, segment.id);
116
+ }), emptyValue > 0 ? /* @__PURE__ */ jsx("div", {
117
+ "aria-hidden": "true",
118
+ className: "patternmode-distribution-bar__segment patternmode-distribution-bar__segment--empty",
119
+ style: { width: total > 0 ? `${getRenderableDistributionValue(emptyValue) / total * 100}%` : "0%" }
120
+ }) : null]
121
+ });
122
+ const DistributionSegmentLegend = ({ emptyLabel, emptyValue = 0, segments, total }) => /* @__PURE__ */ jsxs("div", {
123
+ className: "patternmode-distribution-bar__legend",
124
+ children: [segments.map((segment) => /* @__PURE__ */ jsxs("span", { children: [
125
+ /* @__PURE__ */ jsx("span", {
126
+ "aria-hidden": "true",
127
+ className: "patternmode-distribution-bar__swatch",
128
+ style: { "--patternmode-distribution-segment-color": segment.color }
129
+ }),
130
+ segment.label ?? segment.id,
131
+ " ",
132
+ getDerivedDistributionPercentage(segment.value, total),
133
+ "%"
134
+ ] }, segment.id)), emptyValue > 0 && emptyLabel ? /* @__PURE__ */ jsxs("span", { children: [
135
+ /* @__PURE__ */ jsx("span", {
136
+ "aria-hidden": "true",
137
+ className: "patternmode-distribution-bar__swatch patternmode-distribution-bar__swatch--empty"
138
+ }),
139
+ emptyLabel,
140
+ " ",
141
+ getDerivedDistributionPercentage(emptyValue, total),
142
+ "%"
143
+ ] }) : null]
144
+ });
145
+ const DistributionSummaryLegend = ({ assignedLabel, emptyLabel, emptyValue, total }) => {
146
+ const emptyPercentage = getDerivedDistributionPercentage(emptyValue, total);
147
+ return /* @__PURE__ */ jsxs("div", {
148
+ className: "patternmode-distribution-bar__legend",
149
+ children: [/* @__PURE__ */ jsxs("span", { children: [
150
+ Math.max(0, 100 - emptyPercentage),
151
+ "% ",
152
+ assignedLabel
153
+ ] }), emptyValue > 0 ? /* @__PURE__ */ jsxs("span", { children: [
154
+ emptyPercentage,
155
+ "% ",
156
+ emptyLabel
157
+ ] }) : null]
158
+ });
159
+ };
160
+ const DistributionBarHandle = ({ "aria-label": ariaLabel, boundaryPercent, onDrag, onDragEnd, onDragStart, onKeyDown }) => /* @__PURE__ */ jsx(LazyMotion, {
161
+ features: domMax,
162
+ children: /* @__PURE__ */ jsx(m.button, {
163
+ "aria-label": ariaLabel,
164
+ className: "patternmode-distribution-bar__handle",
165
+ drag: "x",
166
+ dragElastic: 0,
167
+ dragMomentum: false,
168
+ dragSnapToOrigin: true,
169
+ onDrag: (_event, info) => onDrag(info),
170
+ onDragEnd: (_event, info) => onDragEnd(info),
171
+ onDragStart,
172
+ onKeyDown,
173
+ style: { left: `calc(${boundaryPercent}% - 1.375rem)` },
174
+ transformTemplate: () => "none",
175
+ type: "button"
176
+ })
177
+ });
178
+ const DistributionDisplay = ({ "aria-label": ariaLabel, assignedLabel = "assigned", className, emptyLabel = "unassigned", emptyValue = 0, legend = "segments", onSegmentSelect, segments, selectedSegmentId, ...props }) => {
81
179
  const total = getDistributionDisplayTotal(segments, emptyValue);
82
180
  const interactive = Boolean(onSegmentSelect);
83
181
  const accessibleLabel = ariaLabel ?? getDistributionDisplayAccessibleLabel(segments, emptyValue, emptyLabel, total);
@@ -119,29 +217,29 @@ function DistributionDisplay({ "aria-label": ariaLabel, assignedLabel = "assigne
119
217
  "data-slot": "distribution-display",
120
218
  children: content
121
219
  });
122
- }
123
- function DistributionBar({ "aria-label": ariaLabel, className, legend = "segments", minValue = 4, onChange, segments, step = 1, ...props }) {
220
+ };
221
+ const DistributionBar = ({ "aria-label": ariaLabel, className, legend = "segments", minValue = 4, onChange, segments, step = 1, ...props }) => {
124
222
  const trackRef = useRef(null);
125
223
  const dragStartSegmentsRef = useRef(null);
126
224
  const total = getDistributionTotal(segments);
127
- function moveBoundary(boundaryIndex, deltaValue, sourceSegments = segments) {
225
+ const moveBoundary = (boundaryIndex, deltaValue, sourceSegments = segments) => {
128
226
  onChange?.(moveDistributionBoundary(sourceSegments, boundaryIndex, deltaValue, minValue));
129
- }
130
- function handleDragStart() {
227
+ };
228
+ const handleDragStart = () => {
131
229
  dragStartSegmentsRef.current = segments;
132
- }
133
- function handleDrag(boundaryIndex, info) {
230
+ };
231
+ const handleDrag = (boundaryIndex, info) => {
134
232
  const sourceSegments = dragStartSegmentsRef.current ?? segments;
135
233
  const sourceTotal = getDistributionTotal(sourceSegments);
136
234
  const trackWidth = trackRef.current?.getBoundingClientRect().width ?? 0;
137
235
  if (!(trackWidth > 0 && sourceTotal > 0)) return;
138
236
  moveBoundary(boundaryIndex, info.offset.x / trackWidth * sourceTotal, sourceSegments);
139
- }
140
- function handleDragEnd(boundaryIndex, info) {
237
+ };
238
+ const handleDragEnd = (boundaryIndex, info) => {
141
239
  handleDrag(boundaryIndex, info);
142
240
  dragStartSegmentsRef.current = null;
143
- }
144
- function handleKeyDown(event, boundaryIndex) {
241
+ };
242
+ const handleKeyDown = (event, boundaryIndex) => {
145
243
  if (event.key === "ArrowLeft") {
146
244
  event.preventDefault();
147
245
  moveBoundary(boundaryIndex, -step);
@@ -150,7 +248,7 @@ function DistributionBar({ "aria-label": ariaLabel, className, legend = "segment
150
248
  event.preventDefault();
151
249
  moveBoundary(boundaryIndex, step);
152
250
  }
153
- }
251
+ };
154
252
  return /* @__PURE__ */ jsxs("fieldset", {
155
253
  ...props,
156
254
  "aria-label": ariaLabel,
@@ -179,115 +277,9 @@ function DistributionBar({ "aria-label": ariaLabel, className, legend = "segment
179
277
  total
180
278
  }) : null]
181
279
  });
182
- }
183
- function DistributionSegments({ emptyValue = 0, onSegmentSelect, segments, selectedSegmentId, total }) {
184
- return /* @__PURE__ */ jsxs("div", {
185
- className: "patternmode-distribution-bar__segments",
186
- children: [segments.map((segment) => {
187
- const segmentStyle = {
188
- "--patternmode-distribution-segment-color": segment.color,
189
- width: total > 0 ? `${getRenderableDistributionValue(segment.value) / total * 100}%` : "0%"
190
- };
191
- const isSelected = selectedSegmentId === segment.id;
192
- if (onSegmentSelect) return /* @__PURE__ */ jsx("button", {
193
- "aria-label": `${segment.label ?? segment.id} ${getDerivedDistributionPercentage(segment.value, total)}%`,
194
- "aria-pressed": isSelected,
195
- className: "patternmode-distribution-bar__segment",
196
- "data-selected": isSelected ? "true" : void 0,
197
- onClick: () => onSegmentSelect(segment),
198
- style: segmentStyle,
199
- type: "button"
200
- }, segment.id);
201
- return /* @__PURE__ */ jsx("div", {
202
- "aria-hidden": "true",
203
- className: "patternmode-distribution-bar__segment",
204
- "data-selected": isSelected ? "true" : void 0,
205
- style: segmentStyle
206
- }, segment.id);
207
- }), emptyValue > 0 ? /* @__PURE__ */ jsx("div", {
208
- "aria-hidden": "true",
209
- className: "patternmode-distribution-bar__segment patternmode-distribution-bar__segment--empty",
210
- style: { width: total > 0 ? `${getRenderableDistributionValue(emptyValue) / total * 100}%` : "0%" }
211
- }) : null]
212
- });
213
- }
214
- function DistributionSegmentLegend({ emptyLabel, emptyValue = 0, segments, total }) {
215
- return /* @__PURE__ */ jsxs("div", {
216
- className: "patternmode-distribution-bar__legend",
217
- children: [segments.map((segment) => /* @__PURE__ */ jsxs("span", { children: [
218
- /* @__PURE__ */ jsx("span", {
219
- "aria-hidden": "true",
220
- className: "patternmode-distribution-bar__swatch",
221
- style: { "--patternmode-distribution-segment-color": segment.color }
222
- }),
223
- segment.label ?? segment.id,
224
- " ",
225
- getDerivedDistributionPercentage(segment.value, total),
226
- "%"
227
- ] }, segment.id)), emptyValue > 0 && emptyLabel ? /* @__PURE__ */ jsxs("span", { children: [
228
- /* @__PURE__ */ jsx("span", {
229
- "aria-hidden": "true",
230
- className: "patternmode-distribution-bar__swatch patternmode-distribution-bar__swatch--empty"
231
- }),
232
- emptyLabel,
233
- " ",
234
- getDerivedDistributionPercentage(emptyValue, total),
235
- "%"
236
- ] }) : null]
237
- });
238
- }
239
- function DistributionSummaryLegend({ assignedLabel, emptyLabel, emptyValue, total }) {
240
- const emptyPercentage = getDerivedDistributionPercentage(emptyValue, total);
241
- return /* @__PURE__ */ jsxs("div", {
242
- className: "patternmode-distribution-bar__legend",
243
- children: [/* @__PURE__ */ jsxs("span", { children: [
244
- Math.max(0, 100 - emptyPercentage),
245
- "% ",
246
- assignedLabel
247
- ] }), emptyValue > 0 ? /* @__PURE__ */ jsxs("span", { children: [
248
- emptyPercentage,
249
- "% ",
250
- emptyLabel
251
- ] }) : null]
252
- });
253
- }
254
- function getDistributionDisplayTotal(segments, emptyValue) {
255
- return getDistributionTotal(segments) + getRenderableDistributionValue(emptyValue);
256
- }
257
- function getDistributionDisplayAccessibleLabel(segments, emptyValue, emptyLabel, total) {
258
- const segmentLabels = segments.map((segment) => `${segment.label ?? segment.id} ${getDerivedDistributionPercentage(segment.value, total)}%`);
259
- if (emptyValue > 0) segmentLabels.push(`${emptyLabel} ${getDerivedDistributionPercentage(emptyValue, total)}%`);
260
- return segmentLabels.join(", ");
261
- }
262
- function getDerivedDistributionPercentage(value, total) {
263
- if (!(total > 0 && Number.isFinite(value))) return 0;
264
- return Math.round(Math.max(0, value) / total * 100);
265
- }
266
- function getRenderableDistributionValue(value) {
267
- return Number.isFinite(value) ? Math.max(0, value) : 0;
268
- }
269
- function DistributionBarHandle({ "aria-label": ariaLabel, boundaryPercent, onDrag, onDragEnd, onDragStart, onKeyDown }) {
270
- return /* @__PURE__ */ jsx(LazyMotion, {
271
- features: domMax,
272
- children: /* @__PURE__ */ jsx(m.button, {
273
- "aria-label": ariaLabel,
274
- className: "patternmode-distribution-bar__handle",
275
- drag: "x",
276
- dragElastic: 0,
277
- dragMomentum: false,
278
- dragSnapToOrigin: true,
279
- onDrag: (_event, info) => onDrag(info),
280
- onDragEnd: (_event, info) => onDragEnd(info),
281
- onDragStart,
282
- onKeyDown,
283
- style: { left: `calc(${boundaryPercent}% - 1.375rem)` },
284
- transformTemplate: () => "none",
285
- type: "button"
286
- })
287
- });
288
- }
280
+ };
289
281
  //#endregion
290
- //#region src/Swatch/SwatchAtmosphere.ts
282
+ //#region src/Swatch/swatch-atmosphere.ts
291
283
  /**
292
284
  * Per-pool layout for the atmosphere fill:
293
285
  * `[focal x%, focal y%, base alpha (0-255), radius delta %, gravity sign]`.
@@ -346,7 +338,19 @@ const POOLS = [
346
338
  * Density controls how far each pool reaches; gravity shifts the pools
347
339
  * vertically. Returns `undefined` when there are no colors.
348
340
  */
349
- function getSwatchAtmosphereBackground(colors, options = {}) {
341
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
342
+ const normalizeHex$1 = (color) => {
343
+ const value = color.trim().replace(/^#/u, "");
344
+ if (/^[\da-f]{3}$/iu.test(value)) return [...value].map((part) => part + part).join("");
345
+ if (/^[\da-f]{6}$/iu.test(value)) return value;
346
+ return null;
347
+ };
348
+ const withAlpha = (color, alpha) => {
349
+ const hex = normalizeHex$1(color);
350
+ if (hex) return `#${hex}${Math.round(clamp(alpha, 0, 255)).toString(16).padStart(2, "0")}`;
351
+ return `color-mix(in srgb, ${color} ${Math.round(clamp(alpha, 0, 255) / 255 * 100)}%, transparent)`;
352
+ };
353
+ const getSwatchAtmosphereBackground = (colors, options = {}) => {
350
354
  if (!colors || colors.length === 0) return;
351
355
  const density = clamp(options.density ?? .5, 0, 1);
352
356
  const gravity = clamp(options.gravity ?? 0, -1, 1);
@@ -361,32 +365,30 @@ function getSwatchAtmosphereBackground(colors, options = {}) {
361
365
  const radius = Math.max(8, reach + radiusDelta);
362
366
  return `radial-gradient(ellipse at ${x}% ${focalY}%, ${withAlpha(color, alpha)} 0%, transparent ${radius}%)`;
363
367
  }).join(", ");
364
- }
365
- function withAlpha(color, alpha) {
366
- const hex = normalizeHex$1(color);
367
- if (hex) return `#${hex}${Math.round(clamp(alpha, 0, 255)).toString(16).padStart(2, "0")}`;
368
- return `color-mix(in srgb, ${color} ${Math.round(clamp(alpha, 0, 255) / 255 * 100)}%, transparent)`;
369
- }
370
- function normalizeHex$1(color) {
371
- const value = color.trim().replace(/^#/, "");
372
- if (/^[\da-f]{3}$/i.test(value)) return [...value].map((part) => part + part).join("");
373
- if (/^[\da-f]{6}$/i.test(value)) return value;
374
- return null;
375
- }
376
- function clamp(value, min, max) {
377
- return Math.min(max, Math.max(min, value));
378
- }
368
+ };
379
369
  //#endregion
380
- //#region src/Swatch/SwatchColors.ts
381
- function isLightColor(color) {
370
+ //#region src/Swatch/swatch-colors.ts
371
+ const normalizeHex = (hex) => {
372
+ const value = hex.trim().replace(/^#/u, "");
373
+ if (/^[\da-f]{3}$/iu.test(value)) return [...value].map((part) => part + part).join("");
374
+ if (/^[\da-f]{6}$/iu.test(value)) return value;
375
+ return null;
376
+ };
377
+ const toColorStop = (stop) => typeof stop === "string" ? { color: stop } : stop;
378
+ const getRatioWeight = (ratio) => {
379
+ if (ratio === void 0) return 1;
380
+ return Number.isFinite(ratio) ? Math.max(0, ratio) : 0;
381
+ };
382
+ const formatPercent = (value) => `${Number.isInteger(value) ? value : Number(value.toFixed(2))}%`;
383
+ const isLightColor = (color) => {
382
384
  const normalized = normalizeHex(color);
383
385
  if (!normalized) return false;
384
386
  const red = Number.parseInt(normalized.slice(0, 2), 16);
385
387
  const green = Number.parseInt(normalized.slice(2, 4), 16);
386
388
  const blue = Number.parseInt(normalized.slice(4, 6), 16);
387
389
  return (.299 * red + .587 * green + .114 * blue) / 255 > .62;
388
- }
389
- function getSwatchColorsBackground(colors) {
390
+ };
391
+ const getSwatchColorsBackground = (colors) => {
390
392
  if (!colors || colors.length === 0) return;
391
393
  if (colors.length === 1) return toColorStop(colors[0]).color;
392
394
  const stops = colors.map(toColorStop);
@@ -402,25 +404,9 @@ function getSwatchColorsBackground(colors) {
402
404
  cursor = end;
403
405
  return `${stop.color} ${formatPercent(start)} ${formatPercent(end)}`;
404
406
  }).join(", ")})`;
405
- }
406
- function normalizeHex(hex) {
407
- const value = hex.trim().replace(/^#/, "");
408
- if (/^[\da-f]{3}$/i.test(value)) return [...value].map((part) => part + part).join("");
409
- if (/^[\da-f]{6}$/i.test(value)) return value;
410
- return null;
411
- }
412
- function toColorStop(stop) {
413
- return typeof stop === "string" ? { color: stop } : stop;
414
- }
415
- function getRatioWeight(ratio) {
416
- if (ratio === void 0) return 1;
417
- return Number.isFinite(ratio) ? Math.max(0, ratio) : 0;
418
- }
419
- function formatPercent(value) {
420
- return `${Number.isInteger(value) ? value : Number(value.toFixed(2))}%`;
421
- }
407
+ };
422
408
  //#endregion
423
- //#region src/Swatch/SwatchTypes.ts
409
+ //#region src/Swatch/swatch-types.ts
424
410
  const SWATCH_SIZES = [
425
411
  ...PATTERNMODE_SIZES,
426
412
  "4xl",
@@ -442,19 +428,77 @@ const SWATCH_SHAPES = [
442
428
  "block"
443
429
  ];
444
430
  const SWATCH_TEXTURES = ["atmosphere"];
445
- function getSwatchSizeVariableStyle(size, variableName = "--patternmode-swatch-size") {
446
- return { [variableName]: SWATCH_SIZE_VALUES[size] };
447
- }
431
+ const getSwatchSizeVariableStyle = (size, variableName = "--patternmode-swatch-size") => ({ [variableName]: SWATCH_SIZE_VALUES[size] });
448
432
  //#endregion
449
- //#region src/Swatch/SwatchRoot.tsx
450
- function Swatch({ "aria-label": ariaLabel, background, children, className, color, colors, density, flat = false, gravity, icon: Icon, isLight, objectFit, objectPosition, onRemove, raised = false, removeLabel, role: _role, selected = false, shape = "circle", showRing = true, size = "base", style, texture, unavailable = false, ...props }) {
433
+ //#region src/Swatch/swatch-root.tsx
434
+ const SwatchContent = ({ children, flat, Icon, mediaStyle, selected, unavailable }) => /* @__PURE__ */ jsxs(Fragment, { children: [
435
+ /* @__PURE__ */ jsx("span", {
436
+ "aria-hidden": "true",
437
+ className: "patternmode-swatch__fill"
438
+ }),
439
+ children ? /* @__PURE__ */ jsx("span", {
440
+ className: "patternmode-swatch__media",
441
+ style: mediaStyle,
442
+ children
443
+ }) : null,
444
+ flat ? null : /* @__PURE__ */ jsx("span", {
445
+ "aria-hidden": "true",
446
+ className: "patternmode-swatch__scrim"
447
+ }),
448
+ selected && Icon ? /* @__PURE__ */ jsx("span", {
449
+ className: "patternmode-swatch__icon",
450
+ children: /* @__PURE__ */ jsx(Icon, {
451
+ "aria-hidden": "true",
452
+ focusable: "false"
453
+ })
454
+ }) : null,
455
+ unavailable ? /* @__PURE__ */ jsx("span", {
456
+ "aria-hidden": "true",
457
+ className: "patternmode-swatch__slash"
458
+ }) : null
459
+ ] });
460
+ const getSwatchFill = ({ background, color, colors, density, gravity, texture }) => {
451
461
  const colorsBackground = getSwatchColorsBackground(colors);
452
462
  const atmosphereBackground = texture === "atmosphere" ? getSwatchAtmosphereBackground(colors, {
453
463
  density,
454
464
  gravity
455
465
  }) : void 0;
456
- const fill = background ?? atmosphereBackground ?? colorsBackground ?? color;
457
- const light = isLight ?? (color && !background && !colorsBackground ? isLightColor(color) : false);
466
+ return {
467
+ colorsBackground,
468
+ fill: background ?? atmosphereBackground ?? colorsBackground ?? color
469
+ };
470
+ };
471
+ const getSwatchTone = ({ background, color, colorsBackground, isLight }) => {
472
+ if (isLight !== void 0) return isLight ? "light" : "dark";
473
+ if (color && !background && !colorsBackground && isLightColor(color)) return "light";
474
+ return "dark";
475
+ };
476
+ const getSwatchDataProps = ({ flat, lightTone, raised, selected, shape, showRing, size, unavailable }) => ({
477
+ "data-flat": flat ? "true" : void 0,
478
+ "data-raised": raised ? "true" : void 0,
479
+ "data-selected": selected ? "true" : void 0,
480
+ "data-shape": shape,
481
+ "data-show-ring": showRing ? "true" : "false",
482
+ "data-size": size,
483
+ "data-slot": "swatch",
484
+ "data-tone": lightTone,
485
+ "data-unavailable": unavailable ? "true" : void 0
486
+ });
487
+ const Swatch = ({ "aria-label": ariaLabel, asChild = false, background, children, className, color, colors, density, flat = false, gravity, icon: Icon, isLight, objectFit, objectPosition, onRemove, raised = false, removeLabel, role: _role, selected = false, shape = "circle", showRing = true, size = "base", style, texture, unavailable = false, ...props }) => {
488
+ const { colorsBackground, fill } = getSwatchFill({
489
+ background,
490
+ color,
491
+ colors,
492
+ density,
493
+ gravity,
494
+ texture
495
+ });
496
+ const lightTone = getSwatchTone({
497
+ background,
498
+ color,
499
+ colorsBackground,
500
+ isLight
501
+ });
458
502
  const resolvedRemoveLabel = removeLabel ?? (ariaLabel ? `Remove ${ariaLabel}` : "Remove");
459
503
  const rootStyle = {
460
504
  ...getSwatchSizeVariableStyle(size),
@@ -465,49 +509,41 @@ function Swatch({ "aria-label": ariaLabel, background, children, className, colo
465
509
  fit: objectFit,
466
510
  position: objectPosition
467
511
  });
468
- function handleRemove(event) {
512
+ const dataProps = getSwatchDataProps({
513
+ flat,
514
+ lightTone,
515
+ raised,
516
+ selected,
517
+ shape,
518
+ showRing,
519
+ size,
520
+ unavailable
521
+ });
522
+ const handleRemove = (event) => {
469
523
  event.stopPropagation();
470
524
  onRemove?.();
471
- }
472
- const swatchContent = /* @__PURE__ */ jsxs(Fragment, { children: [
473
- /* @__PURE__ */ jsx("span", {
474
- "aria-hidden": "true",
475
- className: "patternmode-swatch__fill"
476
- }),
477
- children ? /* @__PURE__ */ jsx("span", {
478
- className: "patternmode-swatch__media",
479
- style: mediaStyle,
480
- children
481
- }) : null,
482
- flat ? null : /* @__PURE__ */ jsx("span", {
483
- "aria-hidden": "true",
484
- className: "patternmode-swatch__scrim"
485
- }),
486
- selected && Icon ? /* @__PURE__ */ jsx("span", {
487
- className: "patternmode-swatch__icon",
488
- children: /* @__PURE__ */ jsx(Icon, {
489
- "aria-hidden": "true",
490
- focusable: "false"
491
- })
492
- }) : null,
493
- unavailable ? /* @__PURE__ */ jsx("span", {
494
- "aria-hidden": "true",
495
- className: "patternmode-swatch__slash"
496
- }) : null
497
- ] });
525
+ };
526
+ const swatchContent = /* @__PURE__ */ jsx(SwatchContent, {
527
+ flat,
528
+ Icon,
529
+ mediaStyle,
530
+ selected,
531
+ unavailable,
532
+ children: asChild ? void 0 : children
533
+ });
534
+ if (asChild) return /* @__PURE__ */ jsxs(Slot, {
535
+ ...props,
536
+ ...dataProps,
537
+ "aria-label": ariaLabel,
538
+ className: joinClassNames("patternmode-swatch", className),
539
+ style: rootStyle,
540
+ children: [swatchContent, /* @__PURE__ */ jsx(Slottable, { children })]
541
+ });
498
542
  if (onRemove) return /* @__PURE__ */ jsxs("fieldset", {
499
543
  ...props,
544
+ ...dataProps,
500
545
  "aria-label": ariaLabel,
501
546
  className: joinClassNames("patternmode-swatch", className),
502
- "data-flat": flat ? "true" : void 0,
503
- "data-raised": raised ? "true" : void 0,
504
- "data-selected": selected ? "true" : void 0,
505
- "data-shape": shape,
506
- "data-show-ring": showRing ? "true" : "false",
507
- "data-size": size,
508
- "data-slot": "swatch",
509
- "data-tone": light ? "light" : "dark",
510
- "data-unavailable": unavailable ? "true" : void 0,
511
547
  style: rootStyle,
512
548
  children: [swatchContent, /* @__PURE__ */ jsx("button", {
513
549
  "aria-label": resolvedRemoveLabel,
@@ -524,21 +560,13 @@ function Swatch({ "aria-label": ariaLabel, background, children, className, colo
524
560
  });
525
561
  return /* @__PURE__ */ jsx("figure", {
526
562
  ...props,
563
+ ...dataProps,
527
564
  "aria-label": ariaLabel,
528
565
  className: joinClassNames("patternmode-swatch", className),
529
- "data-flat": flat ? "true" : void 0,
530
- "data-raised": raised ? "true" : void 0,
531
- "data-selected": selected ? "true" : void 0,
532
- "data-shape": shape,
533
- "data-show-ring": showRing ? "true" : "false",
534
- "data-size": size,
535
- "data-slot": "swatch",
536
- "data-tone": light ? "light" : "dark",
537
- "data-unavailable": unavailable ? "true" : void 0,
538
566
  style: rootStyle,
539
567
  children: swatchContent
540
568
  });
541
- }
569
+ };
542
570
  //#endregion
543
571
  export { DistributionBar, DistributionDisplay, SWATCH_SHAPES, SWATCH_SIZES, SWATCH_SIZE_VALUES, SWATCH_TEXTURES, Swatch, getDistributionBoundaryPercent, getDistributionTotal, getSwatchAtmosphereBackground, getSwatchColorsBackground, getSwatchSizeVariableStyle, moveDistributionBoundary, removeDistributionSegment, updateDistributionSegment };
544
572