@patternmode/swatch 0.9.2 → 0.9.3

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