aporia 0.1.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,7 +1,425 @@
1
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import { useState, useEffect, useContext, createContext, useCallback, useRef, useLayoutEffect } from "react";
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import t, { useContext, createContext, useId, useState, useCallback, useEffect, useRef, useLayoutEffect, useMemo } from "react";
3
6
  import { useMotionValue, useSpring, useTransform, motion } from "motion/react";
4
7
  import { Popover } from "@base-ui/react";
8
+ function Panel({ children, className, ...rest }) {
9
+ return /* @__PURE__ */ jsx(
10
+ "div",
11
+ {
12
+ className: ["aporiaPanel", className].filter(Boolean).join(" "),
13
+ ...rest,
14
+ children
15
+ }
16
+ );
17
+ }
18
+ const CategoryDisabledContext = createContext(false);
19
+ function CategoryDisabledProvider({
20
+ value,
21
+ children
22
+ }) {
23
+ return /* @__PURE__ */ jsx(CategoryDisabledContext.Provider, { value, children });
24
+ }
25
+ function useCategoryDisabled() {
26
+ return useContext(CategoryDisabledContext);
27
+ }
28
+ function Category({
29
+ title,
30
+ children,
31
+ disabled = false,
32
+ onDisabledChange,
33
+ collapsed: collapsedProp,
34
+ onCollapsedChange,
35
+ defaultCollapsed = false,
36
+ className
37
+ }) {
38
+ const bodyId = useId();
39
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(defaultCollapsed);
40
+ const collapsed = collapsedProp !== void 0 ? collapsedProp : uncontrolledCollapsed;
41
+ const setCollapsed = useCallback(
42
+ (next) => {
43
+ onCollapsedChange == null ? void 0 : onCollapsedChange(next);
44
+ if (collapsedProp === void 0) {
45
+ setUncontrolledCollapsed(next);
46
+ }
47
+ },
48
+ [collapsedProp, onCollapsedChange]
49
+ );
50
+ return /* @__PURE__ */ jsxs(
51
+ "section",
52
+ {
53
+ className: ["category", className].filter(Boolean).join(" "),
54
+ "data-disabled": disabled ? "true" : "false",
55
+ children: [
56
+ /* @__PURE__ */ jsxs("div", { className: "categoryHeader", children: [
57
+ /* @__PURE__ */ jsxs("div", { className: "categoryHeaderStart", children: [
58
+ /* @__PURE__ */ jsx("div", { className: "categoryChevronReveal", children: /* @__PURE__ */ jsx(
59
+ "button",
60
+ {
61
+ type: "button",
62
+ className: "categoryChevronBtn",
63
+ "aria-expanded": !collapsed,
64
+ "aria-controls": bodyId,
65
+ "aria-label": collapsed ? "Expand category" : "Collapse category",
66
+ onClick: () => setCollapsed(!collapsed),
67
+ children: /* @__PURE__ */ jsx(
68
+ "svg",
69
+ {
70
+ className: "categoryChevronIcon",
71
+ "data-collapsed": collapsed ? "true" : "false",
72
+ width: "12",
73
+ height: "12",
74
+ viewBox: "0 0 12 12",
75
+ "aria-hidden": true,
76
+ children: /* @__PURE__ */ jsx(
77
+ "path",
78
+ {
79
+ d: "M3 4.5L6 7.5L9 4.5",
80
+ fill: "none",
81
+ stroke: "currentColor",
82
+ strokeWidth: "1.5",
83
+ strokeLinecap: "round",
84
+ strokeLinejoin: "round"
85
+ }
86
+ )
87
+ }
88
+ )
89
+ }
90
+ ) }),
91
+ /* @__PURE__ */ jsx("h2", { className: "categoryTitle", id: `${bodyId}-label`, children: title })
92
+ ] }),
93
+ onDisabledChange ? /* @__PURE__ */ jsx(
94
+ "button",
95
+ {
96
+ type: "button",
97
+ className: "categoryDisabledDot",
98
+ "aria-label": disabled ? "Enable category" : "Disable category",
99
+ "aria-pressed": disabled,
100
+ onClick: (e) => {
101
+ e.stopPropagation();
102
+ onDisabledChange(!disabled);
103
+ }
104
+ }
105
+ ) : null
106
+ ] }),
107
+ /* @__PURE__ */ jsx(CategoryDisabledProvider, { value: disabled, children: /* @__PURE__ */ jsx(
108
+ "div",
109
+ {
110
+ id: bodyId,
111
+ className: "categoryBody sliderGroup",
112
+ role: "region",
113
+ "aria-labelledby": `${bodyId}-label`,
114
+ hidden: collapsed,
115
+ "aria-hidden": collapsed,
116
+ children
117
+ }
118
+ ) })
119
+ ]
120
+ }
121
+ );
122
+ }
123
+ function $(n2, e, t2) {
124
+ if (t2 < 1) {
125
+ let u = e * Math.sqrt(1 - t2 * t2);
126
+ return 1 - Math.exp(-t2 * e * n2) * (Math.cos(u * n2) + t2 * e / u * Math.sin(u * n2));
127
+ }
128
+ let i = Math.sqrt(t2 * t2 - 1), s = -e * (t2 + i), r = -e * (t2 - i), o = -s / (r - s);
129
+ return 1 - (1 - o) * Math.exp(s * n2) - o * Math.exp(r * n2);
130
+ }
131
+ function j(n2, e, t2) {
132
+ let r = 0;
133
+ for (let o = 0; o < 10; o += 1e-3) if (Math.abs($(o, n2, e) - 1) > t2) r = 0;
134
+ else if (r += 1e-3, r > 0.1) return Math.ceil((o - r + 1e-3) * 1e3);
135
+ return Math.ceil(10 * 1e3);
136
+ }
137
+ var O = /* @__PURE__ */ new Map();
138
+ function D(n2) {
139
+ let { stiffness: e = 100, damping: t2 = 10, mass: i = 1, precision: s = 1e-3 } = n2 ?? {}, r = `${e}:${t2}:${i}:${s}`, o = O.get(r);
140
+ if (o) return o;
141
+ let a = Math.sqrt(e / i), u = t2 / (2 * Math.sqrt(e * i)), l2 = j(a, u, s), c = Math.min(100, Math.max(32, Math.round(l2 / 15))), m = [];
142
+ for (let g = 0; g < c; g++) {
143
+ let A = g / (c - 1) * (l2 / 1e3), S = g === c - 1 ? 1 : $(A, a, u);
144
+ m.push(Math.round(S * 1e4) / 1e4 + "");
145
+ }
146
+ for (; m.length > 2 && m[m.length - 2] === "1"; ) m.splice(m.length - 2, 1);
147
+ let p = { easing: `linear(${m.join(", ")})`, duration: l2 };
148
+ return O.set(r, p), p;
149
+ }
150
+ function H(n2, e) {
151
+ let t2 = n2.includes(" ");
152
+ if (typeof Intl.Segmenter < "u") {
153
+ let s = new Intl.Segmenter(e, { granularity: t2 ? "word" : "grapheme" }).segment(n2)[Symbol.iterator]();
154
+ return q(s);
155
+ }
156
+ return Y(n2, t2);
157
+ }
158
+ function q(n2) {
159
+ return Array.from(n2).reduce((e, t2) => t2.segment === " " ? [...e, { id: `space-${t2.index}`, string: " " }] : e.find((s) => s.string === t2.segment) ? [...e, { id: `${t2.segment}-${t2.index}`, string: t2.segment }] : [...e, { id: t2.segment, string: t2.segment }], []);
160
+ }
161
+ function K(n2, e, t2) {
162
+ let i = n2.find((s) => s.string === e);
163
+ n2.push(i ? { id: `${e}-${t2}`, string: e } : { id: e, string: e });
164
+ }
165
+ function Y(n2, e) {
166
+ let t2 = e ? n2.split(" ") : n2.split(""), i = [];
167
+ return t2.forEach((s, r) => {
168
+ e && r > 0 && i.push({ id: `space-${r}`, string: " " }), K(i, s, r);
169
+ }), i;
170
+ }
171
+ var y = "torph-root", T = "torph-item", d = "torph-id", f$1 = "torph-exiting", M = "torph-debug";
172
+ function w(n2) {
173
+ let e = Array.from(n2.children), t2 = {};
174
+ return e.forEach((i, s) => {
175
+ if (i.hasAttribute(f$1)) return;
176
+ let r = i.getAttribute(d) || `child-${s}`;
177
+ t2[r] = { x: i.offsetLeft, y: i.offsetTop };
178
+ }), t2;
179
+ }
180
+ function R(n2, e, t2) {
181
+ let i = n2[t2], s = e[t2];
182
+ return !i || !s ? { dx: 0, dy: 0 } : { dx: i.x - s.x, dy: i.y - s.y };
183
+ }
184
+ function v(n2, e, t2, i = "backward-first") {
185
+ let [s, r] = i === "backward-first" ? ["backward", "forward"] : ["forward", "backward"], o = (a) => {
186
+ if (a === "backward") {
187
+ for (let u = n2 - 1; u >= 0; u--) if (t2.has(e[u])) return e[u];
188
+ } else for (let u = n2 + 1; u < e.length; u++) if (t2.has(e[u])) return e[u];
189
+ return null;
190
+ };
191
+ return o(s) ?? o(r);
192
+ }
193
+ function _(n2, e, t2, i) {
194
+ let s = new Set(t2.filter((o, a) => i.has(o) && !e.has(n2[a]))), r = /* @__PURE__ */ new Map();
195
+ for (let o = 0; o < n2.length; o++) {
196
+ let a = n2[o];
197
+ e.has(a) && r.set(a, v(o, t2, s, "forward-first"));
198
+ }
199
+ return r;
200
+ }
201
+ function C(n2, e) {
202
+ return Math.min(n2 * e, 150);
203
+ }
204
+ function L(n2) {
205
+ let e = getComputedStyle(n2).transform;
206
+ if (!e || e === "none") return { tx: 0, ty: 0 };
207
+ let t2 = e.match(/matrix\(([^)]+)\)/);
208
+ if (!t2) return { tx: 0, ty: 0 };
209
+ let i = t2[1].split(",").map(Number);
210
+ return { tx: i[4] || 0, ty: i[5] || 0 };
211
+ }
212
+ function Q(n2) {
213
+ let { tx: e, ty: t2 } = L(n2), i = Number(getComputedStyle(n2).opacity) || 1;
214
+ return n2.getAnimations().forEach((s) => s.cancel()), { tx: e, ty: t2, opacity: i };
215
+ }
216
+ function N(n2, e) {
217
+ let { dx: t2, dy: i, duration: s, ease: r, scale: o } = e;
218
+ n2.animate({ transform: o ? `translate(${t2}px, ${i}px) scale(0.95)` : `translate(${t2}px, ${i}px)`, offset: 1 }, { duration: s, easing: r, fill: "both" });
219
+ let a = n2.animate({ opacity: 0, offset: 1 }, { duration: C(s, 0.25), easing: "linear", fill: "both" });
220
+ a.onfinish = () => n2.remove();
221
+ }
222
+ function k(n2, e) {
223
+ let { deltaX: t2, deltaY: i, isNew: s, duration: r, ease: o } = e, a = Q(n2), u = t2 + a.tx, l2 = i + a.ty;
224
+ n2.animate({ transform: `translate(${u}px, ${l2}px) scale(${s ? 0.95 : 1})`, offset: 0 }, { duration: r, easing: o, fill: "both" });
225
+ let c = s && a.opacity >= 1 ? 0 : a.opacity;
226
+ c < 1 && n2.animate([{ opacity: c }, { opacity: 1 }], { duration: C(r, s ? 0.5 : 0.25), easing: "linear", fill: "both" });
227
+ }
228
+ var x = null;
229
+ function F(n2, e, t2, i, s) {
230
+ if (x && (x(), x = null), e === 0 || t2 === 0) return;
231
+ n2.style.width = "auto", n2.style.height = "auto", n2.offsetWidth;
232
+ let r = n2.offsetWidth, o = n2.offsetHeight;
233
+ n2.style.width = `${e}px`, n2.style.height = `${t2}px`, n2.offsetWidth, n2.style.width = `${r}px`, n2.style.height = `${o}px`;
234
+ function a() {
235
+ n2.removeEventListener("transitionend", u), clearTimeout(l2), x = null, n2.style.width = "auto", n2.style.height = "auto", s == null ? void 0 : s();
236
+ }
237
+ function u(c) {
238
+ c.target === n2 && (c.propertyName !== "width" && c.propertyName !== "height" || a());
239
+ }
240
+ n2.addEventListener("transitionend", u);
241
+ let l2 = setTimeout(a, i + 50);
242
+ x = () => {
243
+ n2.removeEventListener("transitionend", u), clearTimeout(l2), x = null;
244
+ };
245
+ }
246
+ function P(n2) {
247
+ let e = n2.map((t2) => {
248
+ let { tx: i, ty: s } = L(t2), r = Number(getComputedStyle(t2).opacity) || 1;
249
+ return t2.getAnimations().forEach((o) => o.cancel()), { left: t2.offsetLeft + i, top: t2.offsetTop + s, width: t2.offsetWidth, height: t2.offsetHeight, opacity: r };
250
+ });
251
+ n2.forEach((t2, i) => {
252
+ let s = e[i];
253
+ t2.setAttribute(f$1, ""), t2.style.position = "absolute", t2.style.pointerEvents = "none", t2.style.left = `${s.left}px`, t2.style.top = `${s.top}px`, t2.style.width = `${s.width}px`, t2.style.height = `${s.height}px`, t2.style.opacity = String(s.opacity);
254
+ });
255
+ }
256
+ function X(n2, e, t2, i) {
257
+ let s = /* @__PURE__ */ new Map();
258
+ e.forEach((r) => {
259
+ let o = r.getAttribute(d);
260
+ t2.has(o) && !r.hasAttribute(f$1) && (s.set(o, r), r.remove());
261
+ }), Array.from(n2.childNodes).forEach((r) => {
262
+ r.nodeType === Node.TEXT_NODE && r.remove();
263
+ }), i.forEach((r) => {
264
+ let o = s.get(r.id);
265
+ if (o) o.textContent = r.string, n2.appendChild(o);
266
+ else {
267
+ let a = document.createElement("span");
268
+ a.setAttribute(T, ""), a.setAttribute(d, r.id), a.textContent = r.string, n2.appendChild(a);
269
+ }
270
+ });
271
+ }
272
+ var J = `
273
+ [${y}] {
274
+ display: inline-block;
275
+ position: relative;
276
+ will-change: width, height;
277
+ transition-property: width, height;
278
+ white-space: nowrap;
279
+ text-align: left;
280
+ }
281
+
282
+ [${T}] {
283
+ display: inline-block;
284
+ will-change: opacity, transform;
285
+ transform: none;
286
+ opacity: 1;
287
+ }
288
+
289
+ [${y}][${M}] {
290
+ outline: 2px solid magenta;
291
+ [${T}] {
292
+ outline: 2px solid cyan;
293
+ outline-offset: -4px;
294
+ }
295
+ }`, h$1 = null, I = 0;
296
+ function G() {
297
+ I++, !h$1 && (h$1 = document.createElement("style"), h$1.dataset.torph = "true", h$1.textContent = J, document.head.appendChild(h$1));
298
+ }
299
+ function U() {
300
+ I--, !(I > 0 || !h$1) && (h$1.remove(), h$1 = null);
301
+ }
302
+ function W() {
303
+ if (typeof window > "u") return { prefersReducedMotion: false, destroy: () => {
304
+ } };
305
+ let n2 = window.matchMedia("(prefers-reduced-motion: reduce)"), e = { prefersReducedMotion: n2.matches, destroy: i };
306
+ function t2(s) {
307
+ e.prefersReducedMotion = s.matches;
308
+ }
309
+ function i() {
310
+ n2.removeEventListener("change", t2);
311
+ }
312
+ return n2.addEventListener("change", t2), e;
313
+ }
314
+ var E = class n {
315
+ constructor() {
316
+ __publicField(this, "instance", null);
317
+ __publicField(this, "lastText", "");
318
+ __publicField(this, "configKey", "");
319
+ }
320
+ attach(e, t2) {
321
+ var _a;
322
+ (_a = this.instance) == null ? void 0 : _a.destroy(), this.instance = new b({ element: e, ...t2 }), this.configKey = n.serializeConfig(t2), this.lastText && this.instance.update(this.lastText);
323
+ }
324
+ update(e) {
325
+ var _a;
326
+ this.lastText = e, (_a = this.instance) == null ? void 0 : _a.update(e);
327
+ }
328
+ needsRecreate(e) {
329
+ return n.serializeConfig(e) !== this.configKey;
330
+ }
331
+ destroy() {
332
+ var _a;
333
+ (_a = this.instance) == null ? void 0 : _a.destroy(), this.instance = null;
334
+ }
335
+ static serializeConfig(e) {
336
+ return JSON.stringify({ ease: e.ease, duration: e.duration, locale: e.locale, scale: e.scale, debug: e.debug, disabled: e.disabled, respectReducedMotion: e.respectReducedMotion });
337
+ }
338
+ };
339
+ var V = "span", B = { debug: false, locale: "en", duration: 400, scale: true, ease: "cubic-bezier(0.19, 1, 0.22, 1)", disabled: false, respectReducedMotion: true }, b = class {
340
+ constructor(e) {
341
+ __publicField(this, "element");
342
+ __publicField(this, "options", {});
343
+ __publicField(this, "data");
344
+ __publicField(this, "currentMeasures", {});
345
+ __publicField(this, "prevMeasures", {});
346
+ __publicField(this, "isInitialRender", true);
347
+ __publicField(this, "reducedMotion", null);
348
+ let { ease: t2, ...i } = { ...B, ...e }, s, r;
349
+ if (typeof t2 == "object") {
350
+ let o = D(t2);
351
+ s = o.easing, r = o.duration;
352
+ } else s = t2, r = i.duration;
353
+ this.options = { ...i, ease: s, duration: r }, this.element = e.element, this.options.respectReducedMotion && (this.reducedMotion = W()), this.isDisabled() || (this.element.setAttribute(y, ""), this.element.style.transitionDuration = `${this.options.duration}ms`, this.element.style.transitionTimingFunction = this.options.ease, e.debug && this.element.setAttribute(M, "")), this.data = "", this.isDisabled() || G();
354
+ }
355
+ destroy() {
356
+ var _a;
357
+ (_a = this.reducedMotion) == null ? void 0 : _a.destroy(), this.element.getAnimations().forEach((e) => e.cancel()), this.element.removeAttribute(y), this.element.removeAttribute(M), U();
358
+ }
359
+ isDisabled() {
360
+ var _a;
361
+ return !!(this.options.disabled || ((_a = this.reducedMotion) == null ? void 0 : _a.prefersReducedMotion));
362
+ }
363
+ update(e) {
364
+ if (e !== this.data) {
365
+ if (this.data = e, this.isDisabled()) {
366
+ typeof e == "string" && (this.element.textContent = e);
367
+ return;
368
+ }
369
+ if (this.data instanceof HTMLElement) throw new Error("HTMLElement not yet supported");
370
+ this.options.onAnimationStart && !this.isInitialRender && this.options.onAnimationStart(), this.createTextGroup(this.data, this.element);
371
+ }
372
+ }
373
+ createTextGroup(e, t2) {
374
+ let i = t2.offsetWidth, s = t2.offsetHeight, r = H(e, this.options.locale);
375
+ this.prevMeasures = w(this.element);
376
+ let o = Array.from(t2.children), a = new Set(r.map((p) => p.id)), u = o.filter((p) => !a.has(p.getAttribute(d)) && !p.hasAttribute(f$1)), l2 = new Set(u), c = o.map((p) => p.getAttribute(d)), m = _(o, l2, c, a);
377
+ if (P(u), X(t2, o, a, r), this.currentMeasures = w(this.element), this.updateStyles(r), u.forEach((p) => {
378
+ if (this.isInitialRender) {
379
+ p.remove();
380
+ return;
381
+ }
382
+ let g = m.get(p), { dx: A, dy: S } = g ? R(this.currentMeasures, this.prevMeasures, g) : { dx: 0, dy: 0 };
383
+ N(p, { dx: A, dy: S, duration: this.options.duration, ease: this.options.ease, scale: this.options.scale });
384
+ }), this.isInitialRender) {
385
+ this.isInitialRender = false, t2.style.width = "auto", t2.style.height = "auto";
386
+ return;
387
+ }
388
+ F(t2, i, s, this.options.duration, this.options.onAnimationComplete);
389
+ }
390
+ updateStyles(e) {
391
+ if (this.isInitialRender) return;
392
+ let t2 = Array.from(this.element.children), i = e.map((r) => r.id), s = new Set(i.filter((r) => this.prevMeasures[r]));
393
+ t2.forEach((r, o) => {
394
+ if (r.hasAttribute(f$1)) return;
395
+ let a = r.getAttribute(d) || `child-${o}`, u = !this.prevMeasures[a], l2 = u ? v(e.findIndex((p) => p.id === a), i, s) : a, { dx: c, dy: m } = l2 ? R(this.prevMeasures, this.currentMeasures, l2) : { dx: 0, dy: 0 };
396
+ k(r, { deltaX: c, deltaY: m, isNew: u, duration: this.options.duration, ease: this.options.ease });
397
+ });
398
+ }
399
+ };
400
+ function f(e) {
401
+ if (typeof e == "string") return e;
402
+ if (typeof e == "number") return String(e);
403
+ if (!e || typeof e == "boolean") return "";
404
+ if (Array.isArray(e)) return e.map(f).join("");
405
+ throw t.isValidElement(e) ? new Error("TextMorph only accepts text content. Found a React element — use strings, numbers, or expressions instead.") : new Error(`TextMorph received an unsupported child of type "${typeof e}".`);
406
+ }
407
+ var h = ({ children: e, className: r, style: o, as: n2 = V, ...s }) => {
408
+ let { ref: p, update: c } = l(s), i = f(e), a = t.useRef({ __html: i });
409
+ return t.useEffect(() => {
410
+ c(i);
411
+ }, [i, c]), t.createElement(n2, { ref: p, className: r, style: o, dangerouslySetInnerHTML: a.current });
412
+ };
413
+ function l(e) {
414
+ let r = t.useRef(null), o = t.useRef(new E()), n2 = E.serializeConfig(e);
415
+ t.useEffect(() => (r.current && o.current.attach(r.current, e), () => {
416
+ o.current.destroy();
417
+ }), [n2]);
418
+ let s = t.useCallback((p) => {
419
+ o.current.update(p);
420
+ }, []);
421
+ return { ref: r, update: s };
422
+ }
5
423
  function isTextInputTarget(target) {
6
424
  if (!(target instanceof HTMLElement)) return false;
7
425
  if (target.isContentEditable) return true;
@@ -20,7 +438,7 @@ function SliderOverlapDebugProvider({ children }) {
20
438
  if (e.code !== "KeyD") return;
21
439
  if (isTextInputTarget(e.target)) return;
22
440
  e.preventDefault();
23
- setEnabled((v) => !v);
441
+ setEnabled((v2) => !v2);
24
442
  };
25
443
  window.addEventListener("keydown", onKeyDown);
26
444
  return () => window.removeEventListener("keydown", onKeyDown);
@@ -30,41 +448,13 @@ function SliderOverlapDebugProvider({ children }) {
30
448
  function useSliderOverlapDebugEnabled() {
31
449
  return useContext(SliderOverlapDebugContext);
32
450
  }
33
- const STORAGE_KEY = "aporia-theme";
34
451
  const ThemeContext = createContext(null);
35
- function readStoredTheme() {
36
- try {
37
- const s = localStorage.getItem(STORAGE_KEY);
38
- if (s === "light" || s === "dark") return s;
39
- } catch {
40
- }
41
- return "dark";
42
- }
43
452
  function ThemeProvider({ children }) {
44
- const [theme, setThemeState] = useState(readStoredTheme);
453
+ const theme = "dark";
45
454
  useEffect(() => {
46
455
  document.documentElement.dataset.theme = theme;
47
- try {
48
- localStorage.setItem(STORAGE_KEY, theme);
49
- } catch {
50
- }
51
456
  }, [theme]);
52
- const setTheme = useCallback((t) => setThemeState(t), []);
53
- const toggleTheme = useCallback(() => {
54
- setThemeState((t) => t === "dark" ? "light" : "dark");
55
- }, []);
56
- useEffect(() => {
57
- const onKeyDown = (e) => {
58
- if (!e.shiftKey || e.repeat) return;
59
- if (e.code !== "KeyT") return;
60
- if (isTextInputTarget(e.target)) return;
61
- e.preventDefault();
62
- toggleTheme();
63
- };
64
- window.addEventListener("keydown", onKeyDown);
65
- return () => window.removeEventListener("keydown", onKeyDown);
66
- }, [toggleTheme]);
67
- return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, setTheme, toggleTheme }, children });
457
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme }, children });
68
458
  }
69
459
  function useTheme() {
70
460
  const ctx = useContext(ThemeContext);
@@ -73,6 +463,210 @@ function useTheme() {
73
463
  }
74
464
  return ctx;
75
465
  }
466
+ function ValueInput({
467
+ value,
468
+ prefix,
469
+ suffix,
470
+ onCommit,
471
+ sanitize = (v2) => v2,
472
+ validate = () => true,
473
+ maxLength,
474
+ inputMode = "text",
475
+ align = "left",
476
+ ariaLabel,
477
+ isActive = false,
478
+ className,
479
+ autoWidth = false,
480
+ valueTextRef,
481
+ onPaste,
482
+ renderValue,
483
+ inputRef: externalInputRef,
484
+ onEditingChange,
485
+ editing: externalEditing,
486
+ draft: externalDraft,
487
+ onDraftChange,
488
+ arrowStep,
489
+ arrowMin,
490
+ arrowMax,
491
+ disabled = false
492
+ }) {
493
+ const [internalEditing, setInternalEditing] = useState(false);
494
+ const [internalDraft, setInternalDraft] = useState("");
495
+ const internalInputRef = useRef(null);
496
+ const measureRef = useRef(null);
497
+ const internalValueTextRef = useRef(null);
498
+ const [inputWidth, setInputWidth] = useState(null);
499
+ const editing = externalEditing !== void 0 ? externalEditing : internalEditing;
500
+ const draft = externalDraft !== void 0 ? externalDraft : internalDraft;
501
+ const inputRef = externalInputRef ?? internalInputRef;
502
+ const setEditing = (value2) => {
503
+ if (externalEditing === void 0) {
504
+ setInternalEditing(value2);
505
+ }
506
+ onEditingChange == null ? void 0 : onEditingChange(value2);
507
+ };
508
+ const setDraft = (value2) => {
509
+ if (externalDraft === void 0) {
510
+ setInternalDraft(value2);
511
+ }
512
+ onDraftChange == null ? void 0 : onDraftChange(value2);
513
+ };
514
+ const actualValueTextRef = valueTextRef ?? internalValueTextRef;
515
+ useLayoutEffect(() => {
516
+ var _a, _b;
517
+ if (!editing) return;
518
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
519
+ (_b = inputRef.current) == null ? void 0 : _b.select();
520
+ }, [editing, inputRef]);
521
+ useLayoutEffect(() => {
522
+ if (!editing || !autoWidth) {
523
+ setInputWidth(null);
524
+ return;
525
+ }
526
+ const measure = () => {
527
+ const m = measureRef.current;
528
+ if (!m) return;
529
+ const w2 = m.getBoundingClientRect().width;
530
+ const next = Math.max(Math.ceil(w2 + 6), 22);
531
+ setInputWidth((prev) => prev === next ? prev : next);
532
+ };
533
+ measure();
534
+ }, [editing, draft, autoWidth]);
535
+ const startEdit = (e) => {
536
+ e.stopPropagation();
537
+ setDraft(value);
538
+ setEditing(true);
539
+ };
540
+ const commit = () => {
541
+ const sanitized = sanitize(draft);
542
+ if (validate(sanitized)) {
543
+ onCommit(sanitized);
544
+ }
545
+ setEditing(false);
546
+ };
547
+ const stepDecimals = (s) => {
548
+ const txt = String(s);
549
+ const i = txt.indexOf(".");
550
+ return i === -1 ? 0 : Math.max(0, txt.length - i - 1);
551
+ };
552
+ const handleKeyDown = (e) => {
553
+ if ((e.key === "ArrowUp" || e.key === "ArrowDown") && arrowStep != null) {
554
+ const dir = e.key === "ArrowUp" ? 1 : -1;
555
+ const mult = e.shiftKey ? 10 : 1;
556
+ const current = Number(sanitize(draft));
557
+ if (Number.isFinite(current) && Number.isFinite(arrowStep) && arrowStep !== 0) {
558
+ e.preventDefault();
559
+ let nextNum = current + dir * arrowStep * mult;
560
+ if (Number.isFinite(arrowMin)) nextNum = Math.max(arrowMin, nextNum);
561
+ if (Number.isFinite(arrowMax)) nextNum = Math.min(arrowMax, nextNum);
562
+ const decimals = stepDecimals(arrowStep);
563
+ const nextTxt = decimals > 0 ? nextNum.toFixed(decimals) : String(Math.round(nextNum));
564
+ setDraft(sanitize(nextTxt));
565
+ return;
566
+ }
567
+ }
568
+ if (e.key === "Enter") {
569
+ e.preventDefault();
570
+ commit();
571
+ }
572
+ if (e.key === "Escape") {
573
+ setEditing(false);
574
+ }
575
+ e.stopPropagation();
576
+ };
577
+ const handleChange = (e) => {
578
+ const next = sanitize(e.target.value);
579
+ setDraft(next);
580
+ };
581
+ const handlePaste = (e) => {
582
+ const pasted = e.clipboardData.getData("text");
583
+ if (onPaste) {
584
+ const result = onPaste(pasted);
585
+ if (result !== null) {
586
+ e.preventDefault();
587
+ setDraft(result);
588
+ return;
589
+ }
590
+ }
591
+ };
592
+ const containerClass = [
593
+ "valueInput",
594
+ className,
595
+ align === "right" && "valueInput--right"
596
+ ].filter(Boolean).join(" ");
597
+ if (disabled) {
598
+ return /* @__PURE__ */ jsxs(
599
+ "span",
600
+ {
601
+ className: containerClass,
602
+ "data-disabled": "true",
603
+ "aria-label": ariaLabel ?? `Value, ${value}`,
604
+ children: [
605
+ prefix && /* @__PURE__ */ jsx("span", { className: "valueInputPrefix", children: prefix }),
606
+ /* @__PURE__ */ jsx("span", { className: "valueInputText", children: renderValue ? renderValue(value) : value }),
607
+ suffix && /* @__PURE__ */ jsx("span", { className: "valueInputSuffix", children: suffix })
608
+ ]
609
+ }
610
+ );
611
+ }
612
+ if (editing) {
613
+ return /* @__PURE__ */ jsxs("span", { className: containerClass, "data-editing": "true", children: [
614
+ prefix && /* @__PURE__ */ jsx("span", { className: "valueInputPrefix", children: prefix }),
615
+ autoWidth && /* @__PURE__ */ jsx(
616
+ "span",
617
+ {
618
+ ref: measureRef,
619
+ className: "valueInputMeasure",
620
+ "aria-hidden": true,
621
+ children: draft.length > 0 ? draft : "0"
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsx(
625
+ "input",
626
+ {
627
+ ref: inputRef,
628
+ className: "valueInputField",
629
+ value: draft,
630
+ onChange: handleChange,
631
+ onPaste: handlePaste,
632
+ onBlur: commit,
633
+ onKeyDown: handleKeyDown,
634
+ onClick: (e) => e.stopPropagation(),
635
+ maxLength,
636
+ inputMode,
637
+ autoComplete: "off",
638
+ spellCheck: false,
639
+ "aria-label": ariaLabel,
640
+ style: inputWidth != null ? { width: inputWidth } : void 0
641
+ }
642
+ ),
643
+ suffix && /* @__PURE__ */ jsx("span", { className: "valueInputSuffix", children: suffix })
644
+ ] });
645
+ }
646
+ return /* @__PURE__ */ jsxs(
647
+ "span",
648
+ {
649
+ className: containerClass,
650
+ "data-active": isActive ? "true" : "false",
651
+ role: "button",
652
+ tabIndex: 0,
653
+ "aria-label": ariaLabel ?? `Edit value, ${value}`,
654
+ onClick: startEdit,
655
+ onKeyDown: (e) => {
656
+ if (e.key === "Enter" || e.key === " ") {
657
+ e.preventDefault();
658
+ setDraft(value);
659
+ setEditing(true);
660
+ }
661
+ },
662
+ children: [
663
+ prefix && /* @__PURE__ */ jsx("span", { className: "valueInputPrefix", children: prefix }),
664
+ /* @__PURE__ */ jsx("span", { ref: actualValueTextRef, className: "valueInputText", children: renderValue ? renderValue(value) : value }),
665
+ suffix && /* @__PURE__ */ jsx("span", { className: "valueInputSuffix", children: suffix })
666
+ ]
667
+ }
668
+ );
669
+ }
76
670
  function clearDocumentSelection() {
77
671
  var _a;
78
672
  const sel = (_a = window.getSelection) == null ? void 0 : _a.call(window);
@@ -139,7 +733,6 @@ const THUMB_HEIGHT_PX = 16;
139
733
  const THUMB_HEIGHT_BEHIND_LABEL_PX = 10;
140
734
  const THUMB_INSET_FROM_FILL_END_PX = 10;
141
735
  const THUMB_OVERLAP_PAD_PX = 10;
142
- const VALUE_EDIT_WIDTH_CARET_PAD_PX = 6;
143
736
  function SliderRow({
144
737
  label,
145
738
  value,
@@ -147,23 +740,23 @@ function SliderRow({
147
740
  max,
148
741
  step,
149
742
  onChange,
150
- fmt
743
+ fmt,
744
+ disabled: disabledProp
151
745
  }) {
152
- const { theme } = useTheme();
746
+ useTheme();
747
+ const categoryDisabled = useCategoryDisabled();
748
+ const disabled = Boolean(disabledProp || categoryDisabled);
153
749
  const debugOverlap = useSliderOverlapDebugEnabled();
154
- const [editing, setEditing] = useState(false);
750
+ const [valueEditing, setValueEditing] = useState(false);
155
751
  const [hovered, setHovered] = useState(false);
156
752
  const [dragging, setDragging] = useState(false);
157
- const [draft, setDraft] = useState("");
158
753
  const [mounted, setMounted] = useState(false);
159
754
  const inputRef = useRef(null);
160
755
  const cardRef = useRef(null);
161
756
  const wrapRef = useRef(null);
162
757
  const labelTextRef = useRef(null);
163
758
  const valueTextRef = useRef(null);
164
- const valueEditMeasureRef = useRef(null);
165
759
  const valueRef = useRef(null);
166
- const [valueEditWidthPx, setValueEditWidthPx] = useState(null);
167
760
  const [overlapLayout, setOverlapLayout] = useState(null);
168
761
  const [idleThumbOpacity, setIdleThumbOpacity] = useState(0);
169
762
  const lastPointerXRef = useRef(0);
@@ -180,15 +773,15 @@ function SliderRow({
180
773
  const baseW = useMotionValue(0);
181
774
  const cardWidth = useTransform(
182
775
  [stretchL, stretchR, pullLSpring, pullRSpring, baseW],
183
- ([l, r, pl, pr, b]) => {
184
- const base = Number(b);
185
- let extra = Number(l) + Number(r) + Number(pl) + Number(pr);
776
+ ([l2, r, pl, pr, b2]) => {
777
+ const base = Number(b2);
778
+ let extra = Number(l2) + Number(r) + Number(pl) + Number(pr);
186
779
  if (Math.abs(extra) < STRETCH_SNAP_EPS_PX) extra = 0;
187
780
  return Math.max(1, Math.round(base + extra));
188
781
  }
189
782
  );
190
- const cardMarginLeft = useTransform([stretchL, pullLSpring], ([l, pl]) => {
191
- let leftExtra = Number(l) + Number(pl);
783
+ const cardMarginLeft = useTransform([stretchL, pullLSpring], ([l2, pl]) => {
784
+ let leftExtra = Number(l2) + Number(pl);
192
785
  if (Math.abs(leftExtra) < STRETCH_SNAP_EPS_PX) leftExtra = 0;
193
786
  return Math.round(-leftExtra);
194
787
  });
@@ -213,19 +806,13 @@ function SliderRow({
213
806
  setMounted(true);
214
807
  }, []);
215
808
  useLayoutEffect(() => {
216
- if (theme !== "light") {
217
- setIdleThumbOpacity(0);
218
- return;
219
- }
220
- const raw = getComputedStyle(document.documentElement).getPropertyValue("--slider-thumb-idle-opacity").trim();
221
- const n = parseFloat(raw);
222
- setIdleThumbOpacity(Number.isFinite(n) ? n : 0.4);
223
- }, [theme]);
809
+ setIdleThumbOpacity(0);
810
+ }, []);
224
811
  const display = fmt ? fmt(value) : value;
225
812
  useLayoutEffect(() => {
226
813
  const card = cardRef.current;
227
814
  const labelEl = labelTextRef.current;
228
- const valueEl = editing ? inputRef.current : valueTextRef.current;
815
+ const valueEl = valueEditing ? inputRef.current : valueTextRef.current;
229
816
  if (!mounted || !card || !labelEl || !valueEl) {
230
817
  setOverlapLayout(null);
231
818
  return;
@@ -233,7 +820,7 @@ function SliderRow({
233
820
  const measure = () => {
234
821
  const c = cardRef.current;
235
822
  const le = labelTextRef.current;
236
- const ve = editing ? inputRef.current : valueTextRef.current;
823
+ const ve = valueEditing ? inputRef.current : valueTextRef.current;
237
824
  if (!c || !le || !ve) {
238
825
  setOverlapLayout(null);
239
826
  return;
@@ -241,11 +828,11 @@ function SliderRow({
241
828
  const cardRect = c.getBoundingClientRect();
242
829
  const lr = le.getBoundingClientRect();
243
830
  const vr = ve.getBoundingClientRect();
244
- const w = cardRect.width;
831
+ const w2 = cardRect.width;
245
832
  let labelEnd = lr.right - cardRect.left + THUMB_OVERLAP_PAD_PX;
246
833
  let valueStart = vr.left - cardRect.left - THUMB_OVERLAP_PAD_PX;
247
- labelEnd = Math.min(Math.max(0, labelEnd), w);
248
- valueStart = Math.min(Math.max(0, valueStart), w);
834
+ labelEnd = Math.min(Math.max(0, labelEnd), w2);
835
+ valueStart = Math.min(Math.max(0, valueStart), w2);
249
836
  if (valueStart < labelEnd) valueStart = labelEnd;
250
837
  setOverlapLayout((prev) => {
251
838
  if (prev && prev.labelEnd === labelEnd && prev.valueStart === valueStart) {
@@ -258,33 +845,7 @@ function SliderRow({
258
845
  const ro = new ResizeObserver(measure);
259
846
  ro.observe(card);
260
847
  return () => ro.disconnect();
261
- }, [mounted, label, display, editing, draft, value]);
262
- useLayoutEffect(() => {
263
- if (!editing) {
264
- setValueEditWidthPx(null);
265
- return;
266
- }
267
- const measure = () => {
268
- const m = valueEditMeasureRef.current;
269
- const zone = valueRef.current;
270
- if (!m || !zone) return;
271
- const w = m.getBoundingClientRect().width;
272
- const cs = getComputedStyle(zone);
273
- const padL = parseFloat(cs.paddingLeft) || 0;
274
- const padR = parseFloat(cs.paddingRight) || 0;
275
- const maxInner = Math.max(0, zone.clientWidth - padL - padR);
276
- const next = Math.min(
277
- Math.max(Math.ceil(w + VALUE_EDIT_WIDTH_CARET_PAD_PX), 22),
278
- Math.max(maxInner, 22)
279
- );
280
- setValueEditWidthPx((prev) => prev === next ? prev : next);
281
- };
282
- measure();
283
- const ro = new ResizeObserver(measure);
284
- const z = valueRef.current;
285
- if (z) ro.observe(z);
286
- return () => ro.disconnect();
287
- }, [editing, draft]);
848
+ }, [mounted, label, display, valueEditing, value]);
288
849
  const span = max - min;
289
850
  const u = span > 0 ? (value - min) / span : 0;
290
851
  useEffect(() => {
@@ -306,10 +867,10 @@ function SliderRow({
306
867
  useEffect(() => {
307
868
  if (!dragging) return;
308
869
  const onMove = (e) => {
309
- const { value: v, min: lo, max: hi } = dragStateRef.current;
870
+ const { value: v2, min: lo, max: hi } = dragStateRef.current;
310
871
  const sp = hi - lo;
311
872
  if (sp <= 0) return;
312
- const uNow = (v - lo) / sp;
873
+ const uNow = (v2 - lo) / sp;
313
874
  const dx = e.clientX - lastPointerXRef.current;
314
875
  lastPointerXRef.current = e.clientX;
315
876
  if (uNow >= 1 - 1e-6 && dx > 0) {
@@ -357,10 +918,10 @@ function SliderRow({
357
918
  }
358
919
  const height = behindLabel ? THUMB_HEIGHT_BEHIND_LABEL_PX : THUMB_HEIGHT_PX;
359
920
  let debugZones2 = null;
360
- if (debugOverlap && labelTextRef.current && (editing ? inputRef.current : valueTextRef.current)) {
921
+ if (debugOverlap && labelTextRef.current && (valueEditing ? inputRef.current : valueTextRef.current)) {
361
922
  const cr = cardRef.current.getBoundingClientRect();
362
923
  const lr = labelTextRef.current.getBoundingClientRect();
363
- const ve = editing ? inputRef.current : valueTextRef.current;
924
+ const ve = valueEditing ? inputRef.current : valueTextRef.current;
364
925
  const vr = ve.getBoundingClientRect();
365
926
  debugZones2 = {
366
927
  overlapLabelEnd: labelEnd,
@@ -382,35 +943,10 @@ function SliderRow({
382
943
  return { opacity, height, debugZones: debugZones2 };
383
944
  };
384
945
  const { opacity: thumbOpacity, height: thumbHeight, debugZones } = getThumbLayout();
385
- const startEdit = (e) => {
386
- e.stopPropagation();
387
- setDraft(String(value));
388
- setEditing(true);
389
- setTimeout(() => {
390
- var _a;
391
- return (_a = inputRef.current) == null ? void 0 : _a.select();
392
- }, 0);
393
- };
394
- const commitEdit = () => {
395
- const parsed = parseFloat(draft);
396
- if (!Number.isNaN(parsed)) {
397
- onChange(Math.max(min, Math.min(max, parsed)));
398
- }
399
- setEditing(false);
400
- };
401
- const handleKeyDown = (e) => {
402
- if (e.key === "Enter") {
403
- e.preventDefault();
404
- commitEdit();
405
- }
406
- if (e.key === "Escape") {
407
- setEditing(false);
408
- }
409
- e.stopPropagation();
410
- };
411
946
  const isActive = hovered || dragging;
412
- const idleThumbWidth = theme === "light" ? THUMB_WIDTH_PX : 0;
947
+ const idleThumbWidth = 0;
413
948
  const handleRangePointerDown = (e) => {
949
+ if (disabled) return;
414
950
  clearDocumentSelection();
415
951
  lastPointerXRef.current = e.clientX;
416
952
  syncBaseWidthFromParent();
@@ -441,7 +977,8 @@ function SliderRow({
441
977
  {
442
978
  className: "sliderCardWrap",
443
979
  ref: wrapRef,
444
- onPointerEnter: () => setHovered(true),
980
+ "data-disabled": disabled ? "true" : "false",
981
+ onPointerEnter: () => !disabled && setHovered(true),
445
982
  onPointerLeave: () => setHovered(false),
446
983
  children: /* @__PURE__ */ jsxs(
447
984
  motion.div,
@@ -527,7 +1064,7 @@ function SliderRow({
527
1064
  className: "sliderTicks",
528
1065
  animate: { opacity: isActive ? 1 : 0 },
529
1066
  transition: { duration: 0.15, ease: "easeOut" },
530
- children: [...Array(10)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "sliderTick" }, i))
1067
+ children: [...Array(10)].map((_2, i) => /* @__PURE__ */ jsx("div", { className: "sliderTick" }, i))
531
1068
  }
532
1069
  ),
533
1070
  /* @__PURE__ */ jsx(
@@ -539,6 +1076,7 @@ function SliderRow({
539
1076
  max,
540
1077
  step,
541
1078
  value,
1079
+ disabled,
542
1080
  draggable: false,
543
1081
  onChange: (e) => onChange(parseFloat(e.target.value)),
544
1082
  onPointerDown: handleRangePointerDown,
@@ -570,43 +1108,40 @@ function SliderRow({
570
1108
  ref: valueRef,
571
1109
  className: "sliderValueZone",
572
1110
  onClick: (e) => e.stopPropagation(),
573
- children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
574
- /* @__PURE__ */ jsx(
575
- "span",
576
- {
577
- ref: valueEditMeasureRef,
578
- className: "sliderValue sliderValueText sliderValueMeasure",
579
- "aria-hidden": true,
580
- children: draft.length > 0 ? draft : "0"
581
- }
582
- ),
583
- /* @__PURE__ */ jsx(
584
- "input",
585
- {
586
- ref: inputRef,
587
- className: "sliderValueInput",
588
- type: "text",
589
- inputMode: min < 0 || stepAllowsDecimal(step) ? "decimal" : "numeric",
590
- value: draft,
591
- autoComplete: "off",
592
- spellCheck: false,
593
- "aria-valuemin": min,
594
- "aria-valuemax": max,
595
- style: valueEditWidthPx != null ? { width: valueEditWidthPx } : { minWidth: 22 },
596
- onChange: (e) => setDraft(sanitizeValueDraft(e.target.value, min, step)),
597
- onBlur: commitEdit,
598
- onKeyDown: handleKeyDown,
599
- onClick: (e) => e.stopPropagation()
600
- }
601
- )
602
- ] }) : /* @__PURE__ */ jsx(
603
- "span",
1111
+ children: /* @__PURE__ */ jsx(
1112
+ ValueInput,
604
1113
  {
1114
+ value: String(display),
1115
+ disabled,
1116
+ onCommit: (val) => {
1117
+ const parsed = parseFloat(val);
1118
+ if (!Number.isNaN(parsed)) {
1119
+ onChange(Math.max(min, Math.min(max, parsed)));
1120
+ }
1121
+ },
1122
+ sanitize: (val) => sanitizeValueDraft(val, min, step),
1123
+ validate: (val) => !Number.isNaN(parseFloat(val)),
1124
+ inputMode: min < 0 || stepAllowsDecimal(step) ? "decimal" : "numeric",
1125
+ align: "right",
1126
+ isActive,
605
1127
  className: "sliderValue",
606
- draggable: false,
607
- "data-active": isActive ? "true" : "false",
608
- onClick: startEdit,
609
- children: /* @__PURE__ */ jsx("span", { ref: valueTextRef, className: "sliderValueText", children: display })
1128
+ autoWidth: true,
1129
+ arrowStep: step,
1130
+ arrowMin: min,
1131
+ arrowMax: max,
1132
+ valueTextRef,
1133
+ inputRef,
1134
+ onEditingChange: setValueEditing,
1135
+ renderValue: (val) => dragging ? val : /* @__PURE__ */ jsx(
1136
+ h,
1137
+ {
1138
+ className: "sliderValueMorph",
1139
+ as: "span",
1140
+ duration: 260,
1141
+ ease: { stiffness: 380, damping: 34, mass: 0.42 },
1142
+ children: val
1143
+ }
1144
+ )
610
1145
  }
611
1146
  )
612
1147
  }
@@ -617,177 +1152,915 @@ function SliderRow({
617
1152
  }
618
1153
  );
619
1154
  }
620
- function SwatchPopover({ title, renderTrigger, children }) {
621
- return /* @__PURE__ */ jsxs(Popover.Root, { modal: true, children: [
622
- /* @__PURE__ */ jsx(Popover.Trigger, { render: renderTrigger }),
623
- /* @__PURE__ */ jsx(Popover.Portal, { className: "swatchPopoverPortal", children: /* @__PURE__ */ jsx(
624
- Popover.Positioner,
625
- {
626
- className: "swatchPopoverPositioner",
627
- positionMethod: "fixed",
628
- side: "bottom",
629
- align: "end",
630
- sideOffset: 6,
631
- collisionAvoidance: { side: "flip", align: "shift", fallbackAxisSide: "none" },
632
- children: /* @__PURE__ */ jsxs(
633
- Popover.Popup,
634
- {
635
- className: "swatchPopoverPopup",
636
- initialFocus: (openType) => openType === "keyboard",
637
- children: [
638
- title && /* @__PURE__ */ jsx(Popover.Title, { className: "swatchPopoverTitle", children: title }),
639
- /* @__PURE__ */ jsx(Popover.Close, { type: "button", className: "swatchPopoverCloseSrOnly", children: "Close" }),
640
- /* @__PURE__ */ jsx("div", { className: "swatchPopoverBody", children })
641
- ]
642
- }
643
- )
644
- }
645
- ) })
646
- ] });
647
- }
648
- function normalizeHex$1(raw) {
1155
+ function normalizeHex(raw) {
649
1156
  const s = raw.trim();
650
1157
  const m = s.match(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
651
1158
  if (!m) return "#808080";
652
- let h = m[1];
653
- if (h.length === 3) {
654
- h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
1159
+ let h2 = m[1];
1160
+ if (h2.length === 3) {
1161
+ h2 = h2[0] + h2[0] + h2[1] + h2[1] + h2[2] + h2[2];
655
1162
  }
656
- return `#${h.toUpperCase()}`;
1163
+ return `#${h2.toUpperCase()}`;
657
1164
  }
658
- function hexDigits$1(hex) {
659
- const n = normalizeHex$1(hex);
660
- return n.slice(1);
1165
+ function hslToHex(h2, s, l2) {
1166
+ const [r, g, b2] = hslToRgbBytes(h2, s, l2);
1167
+ const toHex = (v2) => v2.toString(16).padStart(2, "0");
1168
+ return `#${toHex(r)}${toHex(g)}${toHex(b2)}`.toUpperCase();
661
1169
  }
662
- function isValidSixHex$1(d) {
663
- return /^[0-9a-fA-F]{6}$/.test(d);
1170
+ function hslToRgbBytes(h2, s, l2) {
1171
+ const hue = (h2 % 360 + 360) % 360;
1172
+ const sat = Math.max(0, Math.min(1, s));
1173
+ const lit = Math.max(0, Math.min(1, l2));
1174
+ const c = (1 - Math.abs(2 * lit - 1)) * sat;
1175
+ const x2 = c * (1 - Math.abs(hue / 60 % 2 - 1));
1176
+ const m = lit - c / 2;
1177
+ let rp = 0;
1178
+ let gp = 0;
1179
+ let bp = 0;
1180
+ if (hue < 60) {
1181
+ rp = c;
1182
+ gp = x2;
1183
+ } else if (hue < 120) {
1184
+ rp = x2;
1185
+ gp = c;
1186
+ } else if (hue < 180) {
1187
+ gp = c;
1188
+ bp = x2;
1189
+ } else if (hue < 240) {
1190
+ gp = x2;
1191
+ bp = c;
1192
+ } else if (hue < 300) {
1193
+ rp = x2;
1194
+ bp = c;
1195
+ } else {
1196
+ rp = c;
1197
+ bp = x2;
1198
+ }
1199
+ return [Math.round((rp + m) * 255), Math.round((gp + m) * 255), Math.round((bp + m) * 255)];
664
1200
  }
665
- function ColorRow({ label = "Color", value, onChange }) {
666
- const [hovered, setHovered] = useState(false);
667
- const [editing, setEditing] = useState(false);
668
- const [draft, setDraft] = useState("");
669
- const inputRef = useRef(null);
670
- const hex = normalizeHex$1(value);
671
- const isActive = hovered || editing;
672
- useLayoutEffect(() => {
673
- var _a, _b;
674
- if (!editing) return;
675
- (_a = inputRef.current) == null ? void 0 : _a.focus();
676
- (_b = inputRef.current) == null ? void 0 : _b.select();
677
- }, [editing]);
678
- const startEdit = (e) => {
679
- e.stopPropagation();
680
- setDraft(hexDigits$1(value));
681
- setEditing(true);
682
- };
683
- const commit = () => {
684
- const d = draft.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
685
- if (isValidSixHex$1(d)) {
686
- onChange(`#${d.toUpperCase()}`);
1201
+ function hexToHsl(hex) {
1202
+ const n2 = normalizeHex(hex);
1203
+ const r = parseInt(n2.slice(1, 3), 16) / 255;
1204
+ const g = parseInt(n2.slice(3, 5), 16) / 255;
1205
+ const b2 = parseInt(n2.slice(5, 7), 16) / 255;
1206
+ const max = Math.max(r, g, b2);
1207
+ const min = Math.min(r, g, b2);
1208
+ const l2 = (max + min) / 2;
1209
+ let h2 = 0;
1210
+ let s = 0;
1211
+ const d2 = max - min;
1212
+ if (d2 !== 0) {
1213
+ s = l2 > 0.5 ? d2 / (2 - max - min) : d2 / (max + min);
1214
+ switch (max) {
1215
+ case r:
1216
+ h2 = ((g - b2) / d2 + (g < b2 ? 6 : 0)) * 60;
1217
+ break;
1218
+ case g:
1219
+ h2 = ((b2 - r) / d2 + 2) * 60;
1220
+ break;
1221
+ default:
1222
+ h2 = ((r - g) / d2 + 4) * 60;
1223
+ break;
687
1224
  }
688
- setEditing(false);
689
- };
690
- const onHexKeyDown = (e) => {
691
- if (e.key === "Enter") {
692
- e.preventDefault();
693
- commit();
1225
+ }
1226
+ return { h: h2, s, l: l2 };
1227
+ }
1228
+ function hsvToRgbBytes(h2, s, v2) {
1229
+ const hue = (h2 % 360 + 360) % 360;
1230
+ const sat = Math.max(0, Math.min(1, s));
1231
+ const val = Math.max(0, Math.min(1, v2));
1232
+ const c = val * sat;
1233
+ const x2 = c * (1 - Math.abs(hue / 60 % 2 - 1));
1234
+ const m = val - c;
1235
+ let rp = 0;
1236
+ let gp = 0;
1237
+ let bp = 0;
1238
+ if (hue < 60) {
1239
+ rp = c;
1240
+ gp = x2;
1241
+ } else if (hue < 120) {
1242
+ rp = x2;
1243
+ gp = c;
1244
+ } else if (hue < 180) {
1245
+ gp = c;
1246
+ bp = x2;
1247
+ } else if (hue < 240) {
1248
+ gp = x2;
1249
+ bp = c;
1250
+ } else if (hue < 300) {
1251
+ rp = x2;
1252
+ bp = c;
1253
+ } else {
1254
+ rp = c;
1255
+ bp = x2;
1256
+ }
1257
+ return [Math.round((rp + m) * 255), Math.round((gp + m) * 255), Math.round((bp + m) * 255)];
1258
+ }
1259
+ function hsvToHex(h2, s, v2) {
1260
+ const [r, g, b2] = hsvToRgbBytes(h2, s, v2);
1261
+ const toHex = (n2) => n2.toString(16).padStart(2, "0");
1262
+ return `#${toHex(r)}${toHex(g)}${toHex(b2)}`.toUpperCase();
1263
+ }
1264
+ function hexToHsv(hex) {
1265
+ const n2 = normalizeHex(hex);
1266
+ const r = parseInt(n2.slice(1, 3), 16) / 255;
1267
+ const g = parseInt(n2.slice(3, 5), 16) / 255;
1268
+ const b2 = parseInt(n2.slice(5, 7), 16) / 255;
1269
+ const max = Math.max(r, g, b2);
1270
+ const min = Math.min(r, g, b2);
1271
+ const d2 = max - min;
1272
+ let h2 = 0;
1273
+ if (d2 !== 0) {
1274
+ switch (max) {
1275
+ case r:
1276
+ h2 = ((g - b2) / d2 + (g < b2 ? 6 : 0)) * 60;
1277
+ break;
1278
+ case g:
1279
+ h2 = ((b2 - r) / d2 + 2) * 60;
1280
+ break;
1281
+ default:
1282
+ h2 = ((r - g) / d2 + 4) * 60;
1283
+ break;
694
1284
  }
695
- if (e.key === "Escape") {
696
- setEditing(false);
1285
+ }
1286
+ const v2 = max;
1287
+ const s = max === 0 ? 0 : d2 / max;
1288
+ return { h: h2, s, v: v2 };
1289
+ }
1290
+ function hexToRgb(hex) {
1291
+ const n2 = normalizeHex(hex);
1292
+ return {
1293
+ r: parseInt(n2.slice(1, 3), 16),
1294
+ g: parseInt(n2.slice(3, 5), 16),
1295
+ b: parseInt(n2.slice(5, 7), 16)
1296
+ };
1297
+ }
1298
+ function rgbToHex(r, g, b2) {
1299
+ const clampByte = (v2) => Math.max(0, Math.min(255, Math.round(v2)));
1300
+ const toHex = (v2) => clampByte(v2).toString(16).padStart(2, "0");
1301
+ return `#${toHex(r)}${toHex(g)}${toHex(b2)}`.toUpperCase();
1302
+ }
1303
+ function clampHueDeg$1(h2) {
1304
+ return (h2 % 360 + 360) % 360;
1305
+ }
1306
+ function clamp01$1(v2) {
1307
+ return Math.max(0, Math.min(1, v2));
1308
+ }
1309
+ function parseNumberish(s) {
1310
+ const n2 = Number.parseFloat(s.trim());
1311
+ return Number.isFinite(n2) ? n2 : null;
1312
+ }
1313
+ function splitCssArgs(raw) {
1314
+ const noAlpha = raw.split("/")[0] ?? raw;
1315
+ return noAlpha.trim().replace(/,/g, " ").split(/\s+/).filter(Boolean);
1316
+ }
1317
+ function parseRgbArgsToBytes(args) {
1318
+ if (args.length < 3) return null;
1319
+ const nums = args.slice(0, 3);
1320
+ const bytes = [];
1321
+ for (const token of nums) {
1322
+ if (token.endsWith("%")) {
1323
+ const n2 = parseNumberish(token.slice(0, -1));
1324
+ if (n2 === null) return null;
1325
+ bytes.push(Math.round(Math.max(0, Math.min(100, n2)) / 100 * 255));
1326
+ } else {
1327
+ const n2 = parseNumberish(token);
1328
+ if (n2 === null) return null;
1329
+ bytes.push(Math.round(Math.max(0, Math.min(255, n2))));
697
1330
  }
698
- e.stopPropagation();
1331
+ }
1332
+ return { r: bytes[0], g: bytes[1], b: bytes[2] };
1333
+ }
1334
+ function parseHslArgs(args) {
1335
+ if (args.length < 3) return null;
1336
+ const hRaw = parseNumberish(args[0]);
1337
+ if (hRaw === null) return null;
1338
+ const h2 = clampHueDeg$1(String(args[0]).endsWith("deg") ? parseNumberish(String(args[0]).replace(/deg$/i, "")) ?? hRaw : hRaw);
1339
+ const sTok = args[1];
1340
+ const lTok = args[2];
1341
+ if (!sTok.endsWith("%") || !lTok.endsWith("%")) return null;
1342
+ const sN = parseNumberish(sTok.slice(0, -1));
1343
+ const lN = parseNumberish(lTok.slice(0, -1));
1344
+ if (sN === null || lN === null) return null;
1345
+ return { h: h2, s: clamp01$1(sN / 100), l: clamp01$1(lN / 100) };
1346
+ }
1347
+ function parseHsvArgs(args) {
1348
+ if (args.length < 3) return null;
1349
+ const hRaw = parseNumberish(args[0]);
1350
+ if (hRaw === null) return null;
1351
+ const h2 = clampHueDeg$1(hRaw);
1352
+ const sTok = args[1];
1353
+ const vTok = args[2];
1354
+ if (!sTok.endsWith("%") || !vTok.endsWith("%")) return null;
1355
+ const sN = parseNumberish(sTok.slice(0, -1));
1356
+ const vN = parseNumberish(vTok.slice(0, -1));
1357
+ if (sN === null || vN === null) return null;
1358
+ return { h: h2, s: clamp01$1(sN / 100), v: clamp01$1(vN / 100) };
1359
+ }
1360
+ function parseCssColorToHex(raw) {
1361
+ const s = raw.trim();
1362
+ if (!s) return null;
1363
+ if (/^#?[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(s)) {
1364
+ return normalizeHex(s);
1365
+ }
1366
+ const fn = s.match(/^([a-zA-Z]+)\((.*)\)$/);
1367
+ if (!fn) return null;
1368
+ const name = fn[1].toLowerCase();
1369
+ const args = splitCssArgs(fn[2] ?? "");
1370
+ if (name === "rgb" || name === "rgba") {
1371
+ const rgb = parseRgbArgsToBytes(args);
1372
+ return rgb ? rgbToHex(rgb.r, rgb.g, rgb.b) : null;
1373
+ }
1374
+ if (name === "hsl" || name === "hsla") {
1375
+ const hsl = parseHslArgs(args);
1376
+ return hsl ? hslToHex(hsl.h, hsl.s, hsl.l) : null;
1377
+ }
1378
+ if (name === "hsv" || name === "hsb") {
1379
+ const hsv = parseHsvArgs(args);
1380
+ return hsv ? hsvToHex(hsv.h, hsv.s, hsv.v) : null;
1381
+ }
1382
+ return null;
1383
+ }
1384
+ const HUE_STOPS = [
1385
+ "#FF0000",
1386
+ "#FFFF00",
1387
+ "#00FF00",
1388
+ "#00FFFF",
1389
+ "#0000FF",
1390
+ "#FF00FF",
1391
+ "#FF0000"
1392
+ ];
1393
+ function clamp01(v2) {
1394
+ return Math.max(0, Math.min(1, v2));
1395
+ }
1396
+ function clampHueDeg(h2) {
1397
+ return (h2 % 360 + 360) % 360;
1398
+ }
1399
+ function clampInt(n2, min, max) {
1400
+ return Math.max(min, Math.min(max, Math.round(n2)));
1401
+ }
1402
+ function normalizeHexDigits(raw) {
1403
+ return raw.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
1404
+ }
1405
+ function isValidSixHex$2(d2) {
1406
+ return /^[0-9a-fA-F]{6}$/.test(d2);
1407
+ }
1408
+ function clampHsv01(base) {
1409
+ return { h: clampHueDeg(base.h), s: clamp01(base.s), v: clamp01(base.v) };
1410
+ }
1411
+ function clampHsl01(base) {
1412
+ return { h: clampHueDeg(base.h), s: clamp01(base.s), l: clamp01(base.l) };
1413
+ }
1414
+ function cycleMode(m) {
1415
+ if (m === "hex") return "hsl";
1416
+ if (m === "hsl") return "hsb";
1417
+ if (m === "hsb") return "rgb";
1418
+ return "hex";
1419
+ }
1420
+ function drawSvPlane(ctx, w2, h2, hue) {
1421
+ const img = ctx.createImageData(w2, h2);
1422
+ const d2 = img.data;
1423
+ let p = 0;
1424
+ for (let y2 = 0; y2 < h2; y2++) {
1425
+ const v2 = 1 - y2 / Math.max(1, h2 - 1);
1426
+ for (let x2 = 0; x2 < w2; x2++) {
1427
+ const s = x2 / Math.max(1, w2 - 1);
1428
+ const [r, g, b2] = hsvToRgbBytes(hue, s, v2);
1429
+ d2[p] = r;
1430
+ d2[p + 1] = g;
1431
+ d2[p + 2] = b2;
1432
+ d2[p + 3] = 255;
1433
+ p += 4;
1434
+ }
1435
+ }
1436
+ ctx.putImageData(img, 0, 0);
1437
+ }
1438
+ function readSvFromClient(clientX, clientY, base, svEl) {
1439
+ if (!svEl) return null;
1440
+ const r = svEl.getBoundingClientRect();
1441
+ if (r.width <= 0 || r.height <= 0) return null;
1442
+ const s = clamp01((clientX - r.left) / r.width);
1443
+ const v2 = clamp01(1 - (clientY - r.top) / r.height);
1444
+ return { ...base, s, v: v2 };
1445
+ }
1446
+ function readHueFromClient(clientY, base, hueEl) {
1447
+ if (!hueEl) return null;
1448
+ const r = hueEl.getBoundingClientRect();
1449
+ if (r.height <= 0) return null;
1450
+ const t2 = clamp01((clientY - r.top) / r.height);
1451
+ return { ...base, h: t2 * 360 };
1452
+ }
1453
+ function EyeDropperIcon() {
1454
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 18 18", "aria-hidden": "true", focusable: "false", children: [
1455
+ /* @__PURE__ */ jsx(
1456
+ "path",
1457
+ {
1458
+ d: "M15.651,2.348c-.998-.998-2.739-.998-3.737,0l-2.503,2.503-1.131-1.131c-.293-.293-.768-.293-1.061,0s-.293,.768,0,1.061l6,6c.146,.146,.338,.22,.53,.22s.384-.073,.53-.22c.293-.293,.293-.768,0-1.061l-1.131-1.131,2.502-2.502c1.03-1.03,1.03-2.708,0-3.738Z",
1459
+ fill: "currentColor"
1460
+ }
1461
+ ),
1462
+ /* @__PURE__ */ jsx(
1463
+ "path",
1464
+ {
1465
+ d: "M2.596,11.667c-.845,.845-.985,2.12-.444,3.121l-.932,.931c-.293,.292-.293,.768,0,1.061,.146,.146,.338,.22,.53,.22,.191,0,.384-.073,.53-.22l.931-.931c.382,.208,.807,.329,1.254,.329,.706,0,1.369-.275,1.868-.774l4.695-4.695-3.738-3.737L2.596,11.667Z",
1466
+ fill: "currentColor"
1467
+ }
1468
+ )
1469
+ ] });
1470
+ }
1471
+ function ColorPicker({ value, onChange }) {
1472
+ const canvasRef = useRef(null);
1473
+ const svWrapRef = useRef(null);
1474
+ const hueRef = useRef(null);
1475
+ const [dragging, setDragging] = useState(null);
1476
+ const [pointerDraft, setPointerDraft] = useState(null);
1477
+ const [memory, setMemory] = useState(() => {
1478
+ const base = hexToHsv(normalizeHex(value));
1479
+ return { h: base.h, s: base.s || 1 };
1480
+ });
1481
+ const [mode, setMode] = useState("hex");
1482
+ const hsvFromValueRaw = useMemo(() => hexToHsv(normalizeHex(value)), [value]);
1483
+ const hsvFromValue = useMemo(() => {
1484
+ const base = hsvFromValueRaw;
1485
+ if (base.v === 0) {
1486
+ return { ...base, h: memory.h, s: memory.s };
1487
+ }
1488
+ if (base.s === 0) {
1489
+ return { ...base, h: memory.h };
1490
+ }
1491
+ return base;
1492
+ }, [hsvFromValueRaw, memory.h, memory.s]);
1493
+ const draft = pointerDraft ?? hsvFromValue;
1494
+ const hex = useMemo(() => normalizeHex(value), [value]);
1495
+ const hslFromHex = useMemo(() => hexToHsl(hex), [hex]);
1496
+ const rgbFromHex = useMemo(() => hexToRgb(hex), [hex]);
1497
+ const redrawCanvas = useCallback(() => {
1498
+ const canvas = canvasRef.current;
1499
+ if (!canvas) return;
1500
+ const rect = canvas.getBoundingClientRect();
1501
+ const w2 = Math.max(1, Math.round(rect.width * (window.devicePixelRatio || 1)));
1502
+ const h2 = Math.max(1, Math.round(rect.height * (window.devicePixelRatio || 1)));
1503
+ if (canvas.width !== w2 || canvas.height !== h2) {
1504
+ canvas.width = w2;
1505
+ canvas.height = h2;
1506
+ }
1507
+ const ctx = canvas.getContext("2d");
1508
+ if (!ctx) return;
1509
+ drawSvPlane(ctx, w2, h2, draft.h);
1510
+ }, [draft.h]);
1511
+ useLayoutEffect(() => {
1512
+ redrawCanvas();
1513
+ }, [redrawCanvas]);
1514
+ useEffect(() => {
1515
+ const el = canvasRef.current;
1516
+ if (!el || typeof ResizeObserver === "undefined") return;
1517
+ const ro = new ResizeObserver(() => redrawCanvas());
1518
+ ro.observe(el);
1519
+ return () => ro.disconnect();
1520
+ }, [redrawCanvas]);
1521
+ const commit = useCallback(
1522
+ (next) => {
1523
+ setMemory({ h: next.h, s: next.s || memory.s });
1524
+ onChange(hsvToHex(next.h, next.s, next.v));
1525
+ },
1526
+ [onChange, memory.s]
1527
+ );
1528
+ const onToggleMode = () => {
1529
+ setMode((m) => cycleMode(m));
699
1530
  };
700
- const onHexChange = (e) => {
701
- const next = e.target.value.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
702
- setDraft(next);
1531
+ const handleColorPaste = (pasted) => {
1532
+ const parsed = parseCssColorToHex(pasted);
1533
+ if (parsed) {
1534
+ onChange(parsed);
1535
+ return "";
1536
+ }
1537
+ return null;
1538
+ };
1539
+ const onEyeDropper = async () => {
1540
+ const AnyWindow = window;
1541
+ if (!AnyWindow.EyeDropper) return;
1542
+ try {
1543
+ const dropper = new AnyWindow.EyeDropper();
1544
+ const result = await dropper.open();
1545
+ onChange(normalizeHex(result.sRGBHex));
1546
+ } catch {
1547
+ }
703
1548
  };
704
- const onHexPaste = (e) => {
1549
+ const onSlPointerDown = (e) => {
1550
+ if (e.button !== 0) return;
705
1551
  e.preventDefault();
706
- const pasted = e.clipboardData.getData("text");
707
- const cleaned = pasted.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
708
- setDraft(cleaned);
1552
+ const base = pointerDraft ?? hsvFromValue;
1553
+ const next = readSvFromClient(e.clientX, e.clientY, base, svWrapRef.current);
1554
+ if (!next) return;
1555
+ setMemory({ h: next.h, s: next.s || memory.s });
1556
+ setPointerDraft(next);
1557
+ setDragging("sl");
1558
+ e.currentTarget.setPointerCapture(e.pointerId);
1559
+ };
1560
+ const onSlPointerMove = (e) => {
1561
+ if (dragging !== "sl" || !e.currentTarget.hasPointerCapture(e.pointerId)) return;
1562
+ setPointerDraft((prev) => {
1563
+ const base = prev ?? hsvFromValue;
1564
+ const next = readSvFromClient(e.clientX, e.clientY, base, svWrapRef.current);
1565
+ if (next) setMemory({ h: next.h, s: next.s || memory.s });
1566
+ return next ?? prev;
1567
+ });
1568
+ };
1569
+ const onSlPointerUp = (e) => {
1570
+ if (dragging !== "sl" || !e.currentTarget.hasPointerCapture(e.pointerId)) return;
1571
+ e.currentTarget.releasePointerCapture(e.pointerId);
1572
+ const base = pointerDraft ?? hsvFromValue;
1573
+ const next = readSvFromClient(e.clientX, e.clientY, base, svWrapRef.current);
1574
+ if (next) commit(next);
1575
+ setPointerDraft(null);
1576
+ setDragging(null);
1577
+ };
1578
+ const onHuePointerDown = (e) => {
1579
+ if (e.button !== 0) return;
1580
+ e.preventDefault();
1581
+ const base = pointerDraft ?? hsvFromValue;
1582
+ const next = readHueFromClient(e.clientY, base, hueRef.current);
1583
+ if (!next) return;
1584
+ setMemory({ h: next.h, s: next.s || memory.s });
1585
+ setPointerDraft(next);
1586
+ setDragging("hue");
1587
+ e.currentTarget.setPointerCapture(e.pointerId);
1588
+ };
1589
+ const onHuePointerMove = (e) => {
1590
+ if (dragging !== "hue" || !e.currentTarget.hasPointerCapture(e.pointerId)) return;
1591
+ setPointerDraft((prev) => {
1592
+ const base = prev ?? hsvFromValue;
1593
+ const next = readHueFromClient(e.clientY, base, hueRef.current);
1594
+ if (next) setMemory({ h: next.h, s: next.s || memory.s });
1595
+ return next ?? prev;
1596
+ });
709
1597
  };
710
- const onPickerChange = (e) => {
711
- onChange(normalizeHex$1(e.target.value));
1598
+ const onHuePointerUp = (e) => {
1599
+ if (dragging !== "hue" || !e.currentTarget.hasPointerCapture(e.pointerId)) return;
1600
+ e.currentTarget.releasePointerCapture(e.pointerId);
1601
+ const base = pointerDraft ?? hsvFromValue;
1602
+ const next = readHueFromClient(e.clientY, base, hueRef.current);
1603
+ if (next) commit(next);
1604
+ setPointerDraft(null);
1605
+ setDragging(null);
1606
+ };
1607
+ const previewHex = hsvToHex(draft.h, draft.s, draft.v);
1608
+ const onSlKeyDown = (e) => {
1609
+ const step = e.shiftKey ? 0.05 : 0.01;
1610
+ let { s, v: v2 } = draft;
1611
+ let handled = true;
1612
+ switch (e.key) {
1613
+ case "ArrowRight":
1614
+ s = clamp01(s + step);
1615
+ break;
1616
+ case "ArrowLeft":
1617
+ s = clamp01(s - step);
1618
+ break;
1619
+ case "ArrowUp":
1620
+ v2 = clamp01(v2 + step);
1621
+ break;
1622
+ case "ArrowDown":
1623
+ v2 = clamp01(v2 - step);
1624
+ break;
1625
+ case "Home":
1626
+ s = 0;
1627
+ break;
1628
+ case "End":
1629
+ s = 1;
1630
+ break;
1631
+ case "PageUp":
1632
+ v2 = clamp01(v2 + 0.1);
1633
+ break;
1634
+ case "PageDown":
1635
+ v2 = clamp01(v2 - 0.1);
1636
+ break;
1637
+ default:
1638
+ handled = false;
1639
+ }
1640
+ if (!handled) return;
1641
+ e.preventDefault();
1642
+ e.stopPropagation();
1643
+ const next = { ...draft, s, v: v2 };
1644
+ commit(next);
1645
+ };
1646
+ const onHueKeyDown = (e) => {
1647
+ const stepSmall = e.shiftKey ? 10 : 1;
1648
+ let h2 = draft.h;
1649
+ let handled = true;
1650
+ switch (e.key) {
1651
+ case "ArrowRight":
1652
+ h2 = (h2 + stepSmall) % 360 + 360;
1653
+ h2 %= 360;
1654
+ break;
1655
+ case "ArrowLeft":
1656
+ h2 = (h2 - stepSmall) % 360 + 360;
1657
+ h2 %= 360;
1658
+ break;
1659
+ case "Home":
1660
+ h2 = 0;
1661
+ break;
1662
+ case "End":
1663
+ h2 = 359;
1664
+ break;
1665
+ case "PageUp":
1666
+ h2 = ((Math.round(h2) + 10) % 360 + 360) % 360;
1667
+ break;
1668
+ case "PageDown":
1669
+ h2 = ((Math.round(h2) - 10) % 360 + 360) % 360;
1670
+ break;
1671
+ default:
1672
+ handled = false;
1673
+ }
1674
+ if (!handled) return;
1675
+ e.preventDefault();
1676
+ e.stopPropagation();
1677
+ const next = { ...draft, h: h2 };
1678
+ commit(next);
1679
+ };
1680
+ const crossLeft = `${draft.s * 100}%`;
1681
+ const crossTop = `${(1 - draft.v) * 100}%`;
1682
+ const hueThumbTop = `${draft.h / 360 * 100}%`;
1683
+ const hueThumbTopClamped = `clamp(8px, ${hueThumbTop}, calc(100% - 8px))`;
1684
+ const hslUiH = clampInt(hslFromHex.h, 0, 360);
1685
+ const hslUiS = clampInt(hslFromHex.s * 100, 0, 100);
1686
+ const hslUiL = clampInt(hslFromHex.l * 100, 0, 100);
1687
+ const hsbUiH = clampInt(draft.h, 0, 360);
1688
+ const hsbUiS = clampInt(draft.s * 100, 0, 100);
1689
+ const hsbUiB = clampInt(draft.v * 100, 0, 100);
1690
+ return /* @__PURE__ */ jsxs("div", { className: "colorPicker", children: [
1691
+ /* @__PURE__ */ jsxs(
1692
+ "div",
1693
+ {
1694
+ className: "colorPickerSpace",
1695
+ "data-node-id": "565:1006",
1696
+ children: [
1697
+ /* @__PURE__ */ jsxs(
1698
+ "div",
1699
+ {
1700
+ ref: svWrapRef,
1701
+ className: "colorPickerSv",
1702
+ onPointerDown: onSlPointerDown,
1703
+ onPointerMove: onSlPointerMove,
1704
+ onPointerUp: onSlPointerUp,
1705
+ onPointerCancel: onSlPointerUp,
1706
+ role: "group",
1707
+ "aria-label": "Saturation and brightness",
1708
+ tabIndex: 0,
1709
+ onKeyDown: onSlKeyDown,
1710
+ "data-node-id": "565:1007",
1711
+ children: [
1712
+ /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "colorPickerSvCanvas", "aria-hidden": true }),
1713
+ /* @__PURE__ */ jsx("span", { className: "colorPickerIndicator", style: { left: crossLeft, top: crossTop }, "aria-hidden": true })
1714
+ ]
1715
+ }
1716
+ ),
1717
+ /* @__PURE__ */ jsx("div", { className: "colorPickerHueWrap", children: /* @__PURE__ */ jsx(
1718
+ "div",
1719
+ {
1720
+ ref: hueRef,
1721
+ className: "colorPickerHue",
1722
+ onPointerDown: onHuePointerDown,
1723
+ onPointerMove: onHuePointerMove,
1724
+ onPointerUp: onHuePointerUp,
1725
+ onPointerCancel: onHuePointerUp,
1726
+ role: "slider",
1727
+ "aria-label": "Hue",
1728
+ "aria-valuemin": 0,
1729
+ "aria-valuemax": 360,
1730
+ "aria-valuenow": Math.round(draft.h),
1731
+ "aria-orientation": "vertical",
1732
+ tabIndex: 0,
1733
+ onKeyDown: onHueKeyDown,
1734
+ style: {
1735
+ background: `linear-gradient(to bottom, ${HUE_STOPS.join(", ")})`
1736
+ },
1737
+ "data-node-id": "565:1008",
1738
+ children: /* @__PURE__ */ jsx("span", { className: "colorPickerIndicator", style: { left: "50%", top: hueThumbTopClamped }, "aria-hidden": true })
1739
+ }
1740
+ ) })
1741
+ ]
1742
+ }
1743
+ ),
1744
+ /* @__PURE__ */ jsxs("div", { className: "colorPickerFooter", "data-node-id": "565:1011", children: [
1745
+ /* @__PURE__ */ jsx("div", { className: "colorPickerValueZone", "data-node-id": "565:1012", children: mode === "hex" ? /* @__PURE__ */ jsx(
1746
+ ValueInput,
1747
+ {
1748
+ value: hex.slice(1),
1749
+ prefix: "#",
1750
+ onCommit: (val) => {
1751
+ const d2 = normalizeHexDigits(val);
1752
+ if (isValidSixHex$2(d2)) onChange(`#${d2.toUpperCase()}`);
1753
+ },
1754
+ sanitize: normalizeHexDigits,
1755
+ validate: isValidSixHex$2,
1756
+ maxLength: 6,
1757
+ inputMode: "text",
1758
+ ariaLabel: `Edit hex color, ${hex}`,
1759
+ className: "colorPickerHex",
1760
+ onPaste: (pasted) => {
1761
+ const parsed = parseCssColorToHex(pasted);
1762
+ if (parsed) {
1763
+ onChange(parsed);
1764
+ return "";
1765
+ }
1766
+ return normalizeHexDigits(pasted);
1767
+ }
1768
+ }
1769
+ ) : mode === "hsl" ? /* @__PURE__ */ jsxs("div", { className: "colorPickerTriplet", "aria-label": "HSL values", children: [
1770
+ /* @__PURE__ */ jsx(
1771
+ ValueInput,
1772
+ {
1773
+ value: String(hslUiH),
1774
+ onCommit: (val) => {
1775
+ const n2 = Number.parseInt(val, 10);
1776
+ if (!Number.isFinite(n2)) return;
1777
+ const next = clampHsl01({ ...hslFromHex, h: clampHueDeg(n2) });
1778
+ onChange(hslToHex(next.h, next.s, next.l));
1779
+ },
1780
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1781
+ maxLength: 3,
1782
+ inputMode: "numeric",
1783
+ ariaLabel: "Hue",
1784
+ className: "colorPickerTripletBtn",
1785
+ onPaste: handleColorPaste
1786
+ }
1787
+ ),
1788
+ /* @__PURE__ */ jsx(
1789
+ ValueInput,
1790
+ {
1791
+ value: String(hslUiS),
1792
+ onCommit: (val) => {
1793
+ const n2 = Number.parseInt(val, 10);
1794
+ if (!Number.isFinite(n2)) return;
1795
+ const next = clampHsl01({ ...hslFromHex, s: clamp01(n2 / 100) });
1796
+ onChange(hslToHex(next.h, next.s, next.l));
1797
+ },
1798
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1799
+ maxLength: 3,
1800
+ inputMode: "numeric",
1801
+ ariaLabel: "Saturation",
1802
+ className: "colorPickerTripletBtn",
1803
+ onPaste: handleColorPaste
1804
+ }
1805
+ ),
1806
+ /* @__PURE__ */ jsx(
1807
+ ValueInput,
1808
+ {
1809
+ value: String(hslUiL),
1810
+ onCommit: (val) => {
1811
+ const n2 = Number.parseInt(val, 10);
1812
+ if (!Number.isFinite(n2)) return;
1813
+ const next = clampHsl01({ ...hslFromHex, l: clamp01(n2 / 100) });
1814
+ onChange(hslToHex(next.h, next.s, next.l));
1815
+ },
1816
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1817
+ maxLength: 3,
1818
+ inputMode: "numeric",
1819
+ ariaLabel: "Lightness",
1820
+ className: "colorPickerTripletBtn",
1821
+ onPaste: handleColorPaste
1822
+ }
1823
+ )
1824
+ ] }) : mode === "hsb" ? /* @__PURE__ */ jsxs("div", { className: "colorPickerTriplet", "aria-label": "HSB values", children: [
1825
+ /* @__PURE__ */ jsx(
1826
+ ValueInput,
1827
+ {
1828
+ value: String(hsbUiH),
1829
+ onCommit: (val) => {
1830
+ const n2 = Number.parseInt(val, 10);
1831
+ if (!Number.isFinite(n2)) return;
1832
+ const next = clampHsv01({ ...draft, h: clampHueDeg(n2) });
1833
+ onChange(hsvToHex(next.h, next.s, next.v));
1834
+ },
1835
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1836
+ maxLength: 3,
1837
+ inputMode: "numeric",
1838
+ ariaLabel: "Hue",
1839
+ className: "colorPickerTripletBtn",
1840
+ onPaste: handleColorPaste
1841
+ }
1842
+ ),
1843
+ /* @__PURE__ */ jsx(
1844
+ ValueInput,
1845
+ {
1846
+ value: String(hsbUiS),
1847
+ onCommit: (val) => {
1848
+ const n2 = Number.parseInt(val, 10);
1849
+ if (!Number.isFinite(n2)) return;
1850
+ const next = clampHsv01({ ...draft, s: clamp01(n2 / 100) });
1851
+ onChange(hsvToHex(next.h, next.s, next.v));
1852
+ },
1853
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1854
+ maxLength: 3,
1855
+ inputMode: "numeric",
1856
+ ariaLabel: "Saturation",
1857
+ className: "colorPickerTripletBtn",
1858
+ onPaste: handleColorPaste
1859
+ }
1860
+ ),
1861
+ /* @__PURE__ */ jsx(
1862
+ ValueInput,
1863
+ {
1864
+ value: String(hsbUiB),
1865
+ onCommit: (val) => {
1866
+ const n2 = Number.parseInt(val, 10);
1867
+ if (!Number.isFinite(n2)) return;
1868
+ const next = clampHsv01({ ...draft, v: clamp01(n2 / 100) });
1869
+ onChange(hsvToHex(next.h, next.s, next.v));
1870
+ },
1871
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1872
+ maxLength: 3,
1873
+ inputMode: "numeric",
1874
+ ariaLabel: "Brightness",
1875
+ className: "colorPickerTripletBtn",
1876
+ onPaste: handleColorPaste
1877
+ }
1878
+ )
1879
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "colorPickerTriplet", "aria-label": "RGB values", children: [
1880
+ /* @__PURE__ */ jsx(
1881
+ ValueInput,
1882
+ {
1883
+ value: String(clampInt(rgbFromHex.r, 0, 255)),
1884
+ onCommit: (val) => {
1885
+ const n2 = Number.parseInt(val, 10);
1886
+ if (!Number.isFinite(n2)) return;
1887
+ onChange(rgbToHex(clampInt(n2, 0, 255), rgbFromHex.g, rgbFromHex.b));
1888
+ },
1889
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1890
+ maxLength: 3,
1891
+ inputMode: "numeric",
1892
+ ariaLabel: "Red",
1893
+ className: "colorPickerTripletBtn",
1894
+ onPaste: handleColorPaste
1895
+ }
1896
+ ),
1897
+ /* @__PURE__ */ jsx(
1898
+ ValueInput,
1899
+ {
1900
+ value: String(clampInt(rgbFromHex.g, 0, 255)),
1901
+ onCommit: (val) => {
1902
+ const n2 = Number.parseInt(val, 10);
1903
+ if (!Number.isFinite(n2)) return;
1904
+ onChange(rgbToHex(rgbFromHex.r, clampInt(n2, 0, 255), rgbFromHex.b));
1905
+ },
1906
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1907
+ maxLength: 3,
1908
+ inputMode: "numeric",
1909
+ ariaLabel: "Green",
1910
+ className: "colorPickerTripletBtn",
1911
+ onPaste: handleColorPaste
1912
+ }
1913
+ ),
1914
+ /* @__PURE__ */ jsx(
1915
+ ValueInput,
1916
+ {
1917
+ value: String(clampInt(rgbFromHex.b, 0, 255)),
1918
+ onCommit: (val) => {
1919
+ const n2 = Number.parseInt(val, 10);
1920
+ if (!Number.isFinite(n2)) return;
1921
+ onChange(rgbToHex(rgbFromHex.r, rgbFromHex.g, clampInt(n2, 0, 255)));
1922
+ },
1923
+ sanitize: (v2) => v2.replace(/[^\d]/g, "").slice(0, 3),
1924
+ maxLength: 3,
1925
+ inputMode: "numeric",
1926
+ ariaLabel: "Blue",
1927
+ className: "colorPickerTripletBtn",
1928
+ onPaste: handleColorPaste
1929
+ }
1930
+ )
1931
+ ] }) }),
1932
+ /* @__PURE__ */ jsxs("div", { className: "colorPickerActions", "data-node-id": "565:1014", children: [
1933
+ /* @__PURE__ */ jsx(
1934
+ "button",
1935
+ {
1936
+ type: "button",
1937
+ className: "colorPickerModeBtn",
1938
+ onClick: onToggleMode,
1939
+ "aria-label": "Switch color value mode",
1940
+ title: "Switch value mode",
1941
+ "data-node-id": "565:1015",
1942
+ children: mode.toUpperCase()
1943
+ }
1944
+ ),
1945
+ /* @__PURE__ */ jsx(
1946
+ "button",
1947
+ {
1948
+ type: "button",
1949
+ className: "colorPickerIconBtn",
1950
+ onClick: onEyeDropper,
1951
+ "aria-label": "Pick color from screen",
1952
+ title: "Eyedropper",
1953
+ "data-node-id": "565:1020",
1954
+ children: /* @__PURE__ */ jsx(EyeDropperIcon, {})
1955
+ }
1956
+ )
1957
+ ] })
1958
+ ] }),
1959
+ /* @__PURE__ */ jsx("div", { className: "colorPickerLiveSr", "aria-live": "polite", "aria-atomic": "true", children: dragging === null ? `Color ${previewHex}` : "" })
1960
+ ] });
1961
+ }
1962
+ function SwatchPopover({
1963
+ title,
1964
+ renderTrigger,
1965
+ children,
1966
+ modal = true,
1967
+ side = "bottom",
1968
+ align = "end",
1969
+ sideOffset = 6,
1970
+ zIndex
1971
+ }) {
1972
+ return /* @__PURE__ */ jsxs(Popover.Root, { modal, children: [
1973
+ /* @__PURE__ */ jsx(Popover.Trigger, { render: renderTrigger }),
1974
+ /* @__PURE__ */ jsx(Popover.Portal, { className: "swatchPopoverPortal", style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsx(
1975
+ Popover.Positioner,
1976
+ {
1977
+ className: "swatchPopoverPositioner",
1978
+ positionMethod: "fixed",
1979
+ side,
1980
+ align,
1981
+ sideOffset,
1982
+ collisionAvoidance: { side: "flip", align: "shift", fallbackAxisSide: "none" },
1983
+ children: /* @__PURE__ */ jsxs(
1984
+ Popover.Popup,
1985
+ {
1986
+ className: "swatchPopoverPopup",
1987
+ initialFocus: (openType) => openType === "keyboard",
1988
+ children: [
1989
+ title && /* @__PURE__ */ jsx(Popover.Title, { className: "swatchPopoverTitle", children: title }),
1990
+ /* @__PURE__ */ jsx(Popover.Close, { type: "button", className: "swatchPopoverCloseSrOnly", children: "Close" }),
1991
+ /* @__PURE__ */ jsx("div", { className: "swatchPopoverBody", children })
1992
+ ]
1993
+ }
1994
+ )
1995
+ }
1996
+ ) })
1997
+ ] });
1998
+ }
1999
+ function hexDigits$1(hex) {
2000
+ const n2 = normalizeHex(hex);
2001
+ return n2.slice(1);
2002
+ }
2003
+ function isValidSixHex$1(d2) {
2004
+ return /^[0-9a-fA-F]{6}$/.test(d2);
2005
+ }
2006
+ function sanitizeHex$1(input) {
2007
+ return input.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
2008
+ }
2009
+ function ColorRow({ label = "Color", value, onChange, disabled: disabledProp }) {
2010
+ const categoryDisabled = useCategoryDisabled();
2011
+ const disabled = Boolean(disabledProp || categoryDisabled);
2012
+ const [hovered, setHovered] = useState(false);
2013
+ const hex = normalizeHex(value);
2014
+ const handleCommit = (sanitized) => {
2015
+ if (isValidSixHex$1(sanitized)) {
2016
+ onChange(`#${sanitized.toUpperCase()}`);
2017
+ }
2018
+ };
2019
+ const handlePaste = (pasted) => {
2020
+ return pasted.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
712
2021
  };
713
2022
  return /* @__PURE__ */ jsx(
714
2023
  "div",
715
2024
  {
716
2025
  className: "colorRowWrap",
717
- onPointerEnter: () => setHovered(true),
2026
+ "data-disabled": disabled ? "true" : "false",
2027
+ onPointerEnter: () => !disabled && setHovered(true),
718
2028
  onPointerLeave: () => setHovered(false),
719
- children: /* @__PURE__ */ jsxs("div", { className: "colorCard", "data-active": isActive ? "true" : "false", children: [
2029
+ children: /* @__PURE__ */ jsxs("div", { className: "colorCard", "data-active": !disabled && hovered ? "true" : "false", children: [
720
2030
  /* @__PURE__ */ jsx("span", { className: "colorLabel", children: label }),
721
2031
  /* @__PURE__ */ jsxs("div", { className: "colorValueZone", children: [
722
- editing ? /* @__PURE__ */ jsxs("span", { className: "colorRowValue", "data-active": isActive ? "true" : "false", children: [
723
- /* @__PURE__ */ jsx("span", { className: "colorHexHash", children: "#" }),
724
- /* @__PURE__ */ jsx(
725
- "input",
726
- {
727
- ref: inputRef,
728
- className: "colorHexInput",
729
- value: draft,
730
- onChange: onHexChange,
731
- onPaste: onHexPaste,
732
- onBlur: commit,
733
- onKeyDown: onHexKeyDown,
734
- onClick: (e) => e.stopPropagation(),
735
- maxLength: 6,
736
- inputMode: "text",
737
- autoComplete: "off",
738
- spellCheck: false,
739
- "aria-label": "Hex color without hash"
740
- }
741
- )
742
- ] }) : /* @__PURE__ */ jsxs(
743
- "span",
2032
+ /* @__PURE__ */ jsx(
2033
+ ValueInput,
744
2034
  {
2035
+ value: hexDigits$1(hex),
2036
+ prefix: "#",
2037
+ onCommit: handleCommit,
2038
+ sanitize: sanitizeHex$1,
2039
+ validate: isValidSixHex$1,
2040
+ maxLength: 6,
2041
+ inputMode: "text",
2042
+ ariaLabel: `Edit hex color, ${hex}`,
2043
+ disabled,
2044
+ isActive: !disabled && hovered,
745
2045
  className: "colorRowValue",
746
- "data-active": isActive ? "true" : "false",
747
- role: "button",
748
- tabIndex: 0,
749
- "aria-label": `Edit hex color, ${hex}`,
750
- onClick: startEdit,
751
- onKeyDown: (e) => {
752
- if (e.key === "Enter" || e.key === " ") {
753
- e.preventDefault();
754
- setDraft(hexDigits$1(value));
755
- setEditing(true);
756
- }
757
- },
758
- children: [
759
- /* @__PURE__ */ jsx("span", { className: "colorHexHash", children: "#" }),
760
- /* @__PURE__ */ jsx("span", { className: "colorHexDigits", children: hexDigits$1(hex) })
761
- ]
2046
+ onPaste: handlePaste
762
2047
  }
763
2048
  ),
764
- /* @__PURE__ */ jsxs(
2049
+ /* @__PURE__ */ jsx(
765
2050
  SwatchPopover,
766
2051
  {
767
- title: label,
768
2052
  renderTrigger: (props) => /* @__PURE__ */ jsx(
769
2053
  "button",
770
2054
  {
771
2055
  type: "button",
772
2056
  ...props,
2057
+ disabled,
773
2058
  className: ["colorSwatchBtn", props.className].filter(Boolean).join(" "),
774
- style: { ...props.style, backgroundColor: hex },
2059
+ style: { ...props.style, ["--swatch-color"]: hex },
775
2060
  "aria-label": `Open ${label} picker`
776
2061
  }
777
2062
  ),
778
- children: [
779
- /* @__PURE__ */ jsx("p", { children: "Custom color controls will live here. For now, use the system picker below." }),
780
- /* @__PURE__ */ jsx(
781
- "input",
782
- {
783
- type: "color",
784
- className: "swatchPopoverNativeColor",
785
- value: hex,
786
- onChange: onPickerChange,
787
- "aria-label": "Native color picker"
788
- }
789
- )
790
- ]
2063
+ children: /* @__PURE__ */ jsx(ColorPicker, { value: hex, onChange })
791
2064
  }
792
2065
  )
793
2066
  ] })
@@ -795,21 +2068,28 @@ function ColorRow({ label = "Color", value, onChange }) {
795
2068
  }
796
2069
  );
797
2070
  }
798
- function ToggleRow({ label, checked, onChange }) {
799
- const toggle = () => onChange(!checked);
2071
+ function ToggleRow({ label, checked, onChange, disabled: disabledProp }) {
2072
+ const categoryDisabled = useCategoryDisabled();
2073
+ const disabled = Boolean(disabledProp || categoryDisabled);
2074
+ const toggle = () => {
2075
+ if (disabled) return;
2076
+ onChange(!checked);
2077
+ };
800
2078
  const onKeyDown = (e) => {
2079
+ if (disabled) return;
801
2080
  if (e.key === " " || e.key === "Enter") {
802
2081
  e.preventDefault();
803
2082
  toggle();
804
2083
  }
805
2084
  };
806
- return /* @__PURE__ */ jsx("div", { className: "toggleRowWrap", children: /* @__PURE__ */ jsxs(
2085
+ return /* @__PURE__ */ jsx("div", { className: "toggleRowWrap", "data-disabled": disabled ? "true" : "false", children: /* @__PURE__ */ jsxs(
807
2086
  "div",
808
2087
  {
809
2088
  role: "switch",
810
- tabIndex: 0,
2089
+ tabIndex: disabled ? -1 : 0,
811
2090
  "aria-checked": checked,
812
2091
  "aria-label": label,
2092
+ "aria-disabled": disabled,
813
2093
  className: "toggleCard",
814
2094
  "data-on": checked ? "true" : "false",
815
2095
  onClick: toggle,
@@ -821,66 +2101,20 @@ function ToggleRow({ label, checked, onChange }) {
821
2101
  }
822
2102
  ) });
823
2103
  }
824
- function normalizeHex(raw) {
825
- const s = raw.trim();
826
- const m = s.match(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
827
- if (!m) return "#808080";
828
- let h = m[1];
829
- if (h.length === 3) {
830
- h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
831
- }
832
- return `#${h.toUpperCase()}`;
833
- }
834
- function hslToHex(h, s, l) {
835
- const hue = (h % 360 + 360) % 360;
836
- const sat = Math.max(0, Math.min(1, s));
837
- const lit = Math.max(0, Math.min(1, l));
838
- const c = (1 - Math.abs(2 * lit - 1)) * sat;
839
- const x = c * (1 - Math.abs(hue / 60 % 2 - 1));
840
- const m = lit - c / 2;
841
- let r = 0, g = 0, b = 0;
842
- if (hue < 60) {
843
- r = c;
844
- g = x;
845
- b = 0;
846
- } else if (hue < 120) {
847
- r = x;
848
- g = c;
849
- b = 0;
850
- } else if (hue < 180) {
851
- r = 0;
852
- g = c;
853
- b = x;
854
- } else if (hue < 240) {
855
- r = 0;
856
- g = x;
857
- b = c;
858
- } else if (hue < 300) {
859
- r = x;
860
- g = 0;
861
- b = c;
862
- } else {
863
- r = c;
864
- g = 0;
865
- b = x;
866
- }
867
- const toHex = (v) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
868
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
869
- }
870
2104
  function generateAestheticGradient(stopCount) {
871
2105
  const baseHue = Math.random() * 360;
872
2106
  const hueShift = 25 + Math.random() * 35;
873
2107
  const hueDirection = Math.random() > 0.5 ? 1 : -1;
874
2108
  const stops = [];
875
2109
  for (let i = 0; i < stopCount; i++) {
876
- const t = i / (stopCount - 1);
877
- const hue = baseHue + hueDirection * hueShift * t;
2110
+ const t2 = i / (stopCount - 1);
2111
+ const hue = baseHue + hueDirection * hueShift * t2;
878
2112
  const satBase = 0.35 + Math.random() * 0.15;
879
2113
  const satPeak = 0.65 + Math.random() * 0.25;
880
- const saturation = satBase + (satPeak - satBase) * Math.sin(t * Math.PI * 0.8);
2114
+ const saturation = satBase + (satPeak - satBase) * Math.sin(t2 * Math.PI * 0.8);
881
2115
  const lightStart = 0.75 + Math.random() * 0.1;
882
2116
  const lightEnd = 0.08 + Math.random() * 0.07;
883
- const eased = t * t * (3 - 2 * t);
2117
+ const eased = t2 * t2 * (3 - 2 * t2);
884
2118
  const lightness = lightStart + (lightEnd - lightStart) * eased;
885
2119
  stops.push({ color: hslToHex(hue, saturation, lightness) });
886
2120
  }
@@ -889,8 +2123,8 @@ function generateAestheticGradient(stopCount) {
889
2123
  function hexDigits(hex) {
890
2124
  return normalizeHex(hex).slice(1);
891
2125
  }
892
- function isValidSixHex(d) {
893
- return /^[0-9a-fA-F]{6}$/.test(d);
2126
+ function isValidSixHex(d2) {
2127
+ return /^[0-9a-fA-F]{6}$/.test(d2);
894
2128
  }
895
2129
  function stopsToGradient(stops, angle = 90) {
896
2130
  if (stops.length === 0) return "linear-gradient(90deg, #808080, #808080)";
@@ -926,99 +2160,56 @@ const DEFAULT_GRADIENT_STOPS = [
926
2160
  { color: "#42C0B0" },
927
2161
  { color: "#BAC9C7" }
928
2162
  ];
2163
+ function sanitizeHex(input) {
2164
+ return input.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
2165
+ }
929
2166
  function StopRow({ index, stop, canDelete, onColorChange, onDelete }) {
930
- const [editing, setEditing] = useState(false);
931
- const [draft, setDraft] = useState("");
932
- const inputRef = useRef(null);
933
2167
  const hex = normalizeHex(stop.color);
934
- useLayoutEffect(() => {
935
- var _a, _b;
936
- if (!editing) return;
937
- (_a = inputRef.current) == null ? void 0 : _a.focus();
938
- (_b = inputRef.current) == null ? void 0 : _b.select();
939
- }, [editing]);
940
- const startEdit = (e) => {
941
- e.stopPropagation();
942
- setDraft(hexDigits(stop.color));
943
- setEditing(true);
944
- };
945
- const commit = () => {
946
- const d = draft.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
947
- if (isValidSixHex(d)) {
948
- onColorChange(`#${d.toUpperCase()}`);
949
- }
950
- setEditing(false);
951
- };
952
- const onHexKeyDown = (e) => {
953
- if (e.key === "Enter") {
954
- e.preventDefault();
955
- commit();
2168
+ const handleCommit = (val) => {
2169
+ const d2 = sanitizeHex(val);
2170
+ if (isValidSixHex(d2)) {
2171
+ onColorChange(`#${d2.toUpperCase()}`);
956
2172
  }
957
- if (e.key === "Escape") {
958
- setEditing(false);
959
- }
960
- e.stopPropagation();
961
2173
  };
962
- const onHexChange = (e) => {
963
- const next = e.target.value.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
964
- setDraft(next);
965
- };
966
- const onHexPaste = (e) => {
967
- e.preventDefault();
968
- const pasted = e.clipboardData.getData("text");
969
- const cleaned = pasted.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
970
- setDraft(cleaned);
2174
+ const handlePaste = (pasted) => {
2175
+ return sanitizeHex(pasted);
971
2176
  };
972
2177
  return /* @__PURE__ */ jsxs("div", { className: "gradientPickerStop", children: [
973
2178
  /* @__PURE__ */ jsxs("div", { className: "gradientPickerStopColorHex", children: [
974
2179
  /* @__PURE__ */ jsx(
975
- "button",
2180
+ SwatchPopover,
976
2181
  {
977
- type: "button",
978
- className: "gradientPickerStopSwatch",
979
- style: { backgroundColor: hex },
980
- onClick: startEdit,
981
- "aria-label": `Edit color for stop ${index + 1}`
2182
+ modal: false,
2183
+ side: "right",
2184
+ align: "start",
2185
+ sideOffset: 8,
2186
+ zIndex: 200001,
2187
+ renderTrigger: (props) => /* @__PURE__ */ jsx(
2188
+ "button",
2189
+ {
2190
+ type: "button",
2191
+ ...props,
2192
+ className: ["gradientPickerStopSwatch", props.className].filter(Boolean).join(" "),
2193
+ style: { ...props.style, backgroundColor: hex },
2194
+ "aria-label": `Edit color for stop ${index + 1}`
2195
+ }
2196
+ ),
2197
+ children: /* @__PURE__ */ jsx(ColorPicker, { value: hex, onChange: onColorChange })
982
2198
  }
983
2199
  ),
984
- editing ? /* @__PURE__ */ jsxs("span", { className: "gradientPickerStopHexWrap", children: [
985
- /* @__PURE__ */ jsx("span", { className: "gradientPickerStopHexHash", children: "#" }),
986
- /* @__PURE__ */ jsx(
987
- "input",
988
- {
989
- ref: inputRef,
990
- className: "gradientPickerStopHexInput",
991
- value: draft,
992
- onChange: onHexChange,
993
- onPaste: onHexPaste,
994
- onBlur: commit,
995
- onKeyDown: onHexKeyDown,
996
- onClick: (e) => e.stopPropagation(),
997
- maxLength: 6,
998
- inputMode: "text",
999
- autoComplete: "off",
1000
- spellCheck: false,
1001
- "aria-label": "Hex color without hash"
1002
- }
1003
- )
1004
- ] }) : /* @__PURE__ */ jsxs(
1005
- "span",
2200
+ /* @__PURE__ */ jsx(
2201
+ ValueInput,
1006
2202
  {
2203
+ value: hexDigits(hex),
2204
+ prefix: "#",
2205
+ onCommit: handleCommit,
2206
+ sanitize: sanitizeHex,
2207
+ validate: isValidSixHex,
2208
+ maxLength: 6,
2209
+ inputMode: "text",
2210
+ ariaLabel: "Hex color without hash",
1007
2211
  className: "gradientPickerStopHex",
1008
- role: "button",
1009
- tabIndex: 0,
1010
- onClick: startEdit,
1011
- onKeyDown: (e) => {
1012
- if (e.key === "Enter" || e.key === " ") {
1013
- e.preventDefault();
1014
- setDraft(hexDigits(stop.color));
1015
- setEditing(true);
1016
- }
1017
- },
1018
- children: [
1019
- /* @__PURE__ */ jsx("span", { className: "gradientPickerStopHexHash", children: "#" }),
1020
- /* @__PURE__ */ jsx("span", { className: "gradientPickerStopHexDigits", children: hexDigits(hex) })
1021
- ]
2212
+ onPaste: handlePaste
1022
2213
  }
1023
2214
  )
1024
2215
  ] }),
@@ -1026,16 +2217,16 @@ function StopRow({ index, stop, canDelete, onColorChange, onDelete }) {
1026
2217
  "button",
1027
2218
  {
1028
2219
  type: "button",
1029
- className: "gradientPickerStopDelete",
2220
+ className: "gradientPickerIconBtn gradientPickerStopDelete",
1030
2221
  onClick: onDelete,
1031
2222
  disabled: !canDelete,
1032
2223
  "aria-label": `Remove stop ${index + 1}`,
1033
- children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx("use", { href: "/icons.svg#minus" }) })
2224
+ children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 18 18", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M14.75,9.75H3.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75H14.75c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z", fill: "currentColor" }) })
1034
2225
  }
1035
2226
  )
1036
2227
  ] });
1037
2228
  }
1038
- function GradientPicker({ stops, onChange }) {
2229
+ function GradientPicker({ stops, onChange, angle = 90 }) {
1039
2230
  const handleInvert = () => {
1040
2231
  onChange([...stops].reverse());
1041
2232
  };
@@ -1054,13 +2245,21 @@ function GradientPicker({ stops, onChange }) {
1054
2245
  };
1055
2246
  const handleDeleteStop = (index) => {
1056
2247
  if (stops.length <= 2) return;
1057
- onChange(stops.filter((_, i) => i !== index));
2248
+ onChange(stops.filter((_2, i) => i !== index));
1058
2249
  };
1059
2250
  const handleColorChange = (index, color) => {
1060
2251
  const newStops = stops.map((s, i) => i === index ? { ...s, color } : s);
1061
2252
  onChange(newStops);
1062
2253
  };
1063
2254
  return /* @__PURE__ */ jsxs("div", { className: "gradientPicker", children: [
2255
+ /* @__PURE__ */ jsx("div", { className: "gradientPickerPreviewWrap", children: /* @__PURE__ */ jsx(
2256
+ "div",
2257
+ {
2258
+ className: "gradientPickerPreview",
2259
+ "aria-hidden": true,
2260
+ style: { background: stopsToGradient(stops, angle) }
2261
+ }
2262
+ ) }),
1064
2263
  /* @__PURE__ */ jsxs("div", { className: "gradientPickerHeader", children: [
1065
2264
  /* @__PURE__ */ jsxs("div", { className: "gradientPickerHeaderIconsLeft", children: [
1066
2265
  /* @__PURE__ */ jsx(
@@ -1071,7 +2270,24 @@ function GradientPicker({ stops, onChange }) {
1071
2270
  onClick: handleShuffle,
1072
2271
  "aria-label": "Randomize gradient",
1073
2272
  title: "Randomize gradient",
1074
- children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx("use", { href: "/icons.svg#shuffle-sparkle" }) })
2273
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 18 18", "aria-hidden": "true", focusable: "false", children: [
2274
+ /* @__PURE__ */ jsx(
2275
+ "path",
2276
+ {
2277
+ d: "M16.525 6.05302L13.245 4.75602L11.947 1.47502C11.72 0.903021 10.779 0.903021 10.552 1.47502L9.25399 4.75602L5.97399 6.05302C5.68799 6.16602 5.49899 6.44302 5.49899 6.75002C5.49899 7.05702 5.68699 7.33402 5.97399 7.44702L9.25399 8.74402L10.552 12.025C10.665 12.311 10.942 12.499 11.249 12.499C11.556 12.499 11.833 12.311 11.946 12.025L13.244 8.74402L16.524 7.44702C16.81 7.33402 16.999 7.05702 16.999 6.75002C16.999 6.44302 16.812 6.16602 16.525 6.05302Z",
2278
+ fill: "currentColor"
2279
+ }
2280
+ ),
2281
+ /* @__PURE__ */ jsx(
2282
+ "path",
2283
+ {
2284
+ fillRule: "evenodd",
2285
+ clipRule: "evenodd",
2286
+ d: "M4.75 9.5C5.09415 9.5 5.39414 9.73422 5.47761 10.0681L5.96847 12.0315L7.9319 12.5224C8.26578 12.6059 8.5 12.9058 8.5 13.25C8.5 13.5942 8.26578 13.8941 7.9319 13.9776L5.96847 14.4685L5.47761 16.4319C5.39414 16.7658 5.09415 17 4.75 17C4.40585 17 4.10586 16.7658 4.02239 16.4319L3.53153 14.4685L1.5681 13.9776C1.23422 13.8941 1 13.5942 1 13.25C1 12.9058 1.23422 12.6059 1.5681 12.5224L3.53153 12.0315L4.02239 10.0681C4.10586 9.73422 4.40585 9.5 4.75 9.5Z",
2287
+ fill: "currentColor"
2288
+ }
2289
+ )
2290
+ ] })
1075
2291
  }
1076
2292
  ),
1077
2293
  /* @__PURE__ */ jsx(
@@ -1082,7 +2298,22 @@ function GradientPicker({ stops, onChange }) {
1082
2298
  onClick: handleInvert,
1083
2299
  "aria-label": "Invert gradient",
1084
2300
  title: "Invert gradient",
1085
- children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx("use", { href: "/icons.svg#dark-mode" }) })
2301
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 18 18", "aria-hidden": "true", focusable: "false", children: [
2302
+ /* @__PURE__ */ jsx(
2303
+ "path",
2304
+ {
2305
+ d: "M4.061,7h6.189c.414,0,.75-.336,.75-.75s-.336-.75-.75-.75H4.061l1.97-1.97c.293-.293,.293-.768,0-1.061s-.768-.293-1.061,0L1.72,5.72c-.293,.293-.293,.768,0,1.061l3.25,3.25c.146,.146,.338,.22,.53,.22s.384-.073,.53-.22c.293-.293,.293-.768,0-1.061l-1.97-1.97Z",
2306
+ fill: "currentColor"
2307
+ }
2308
+ ),
2309
+ /* @__PURE__ */ jsx(
2310
+ "path",
2311
+ {
2312
+ d: "M13.03,7.97c-.293-.293-.768-.293-1.061,0s-.293,.768,0,1.061l1.97,1.97H7.75c-.414,0-.75,.336-.75,.75s.336,.75,.75,.75h6.189l-1.97,1.97c-.293,.293-.293,.768,0,1.061,.146,.146,.338,.22,.53,.22s.384-.073,.53-.22l3.25-3.25c.293-.293,.293-.768,0-1.061l-3.25-3.25Z",
2313
+ fill: "currentColor"
2314
+ }
2315
+ )
2316
+ ] })
1086
2317
  }
1087
2318
  )
1088
2319
  ] }),
@@ -1094,7 +2325,10 @@ function GradientPicker({ stops, onChange }) {
1094
2325
  onClick: handleAddStop,
1095
2326
  "aria-label": "Add color stop",
1096
2327
  title: "Add color stop",
1097
- children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx("use", { href: "/icons.svg#plus" }) })
2328
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 18 18", "aria-hidden": "true", children: [
2329
+ /* @__PURE__ */ jsx("path", { d: "M14.75,9.75H3.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75H14.75c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z", fill: "currentColor" }),
2330
+ /* @__PURE__ */ jsx("path", { d: "M9,15.5c-.414,0-.75-.336-.75-.75V3.25c0-.414,.336-.75,.75-.75s.75,.336,.75,.75V14.75c0,.414-.336,.75-.75,.75Z", fill: "currentColor" })
2331
+ ] })
1098
2332
  }
1099
2333
  )
1100
2334
  ] }),
@@ -1111,9 +2345,17 @@ function GradientPicker({ stops, onChange }) {
1111
2345
  )) })
1112
2346
  ] });
1113
2347
  }
1114
- function GradientRow({ label = "Gradient", initialStops, onChange, angle = 90 }) {
2348
+ function GradientRow({
2349
+ label = "Gradient",
2350
+ initialStops,
2351
+ onChange,
2352
+ angle = 90,
2353
+ disabled: disabledProp
2354
+ }) {
2355
+ const categoryDisabled = useCategoryDisabled();
2356
+ const disabled = Boolean(disabledProp || categoryDisabled);
1115
2357
  const [hovered, setHovered] = useState(false);
1116
- const isActive = hovered;
2358
+ const isActive = !disabled && hovered;
1117
2359
  const [stops, setStops] = useState(() => initialStops ?? DEFAULT_GRADIENT_STOPS);
1118
2360
  const handleStopsChange = (newStops) => {
1119
2361
  setStops(newStops);
@@ -1126,7 +2368,8 @@ function GradientRow({ label = "Gradient", initialStops, onChange, angle = 90 })
1126
2368
  "div",
1127
2369
  {
1128
2370
  className: "gradientRowWrap",
1129
- onPointerEnter: () => setHovered(true),
2371
+ "data-disabled": disabled ? "true" : "false",
2372
+ onPointerEnter: () => !disabled && setHovered(true),
1130
2373
  onPointerLeave: () => setHovered(false),
1131
2374
  children: /* @__PURE__ */ jsxs("div", { className: "gradientCard", "data-active": isActive ? "true" : "false", children: [
1132
2375
  /* @__PURE__ */ jsx("span", { className: "gradientLabel", children: label }),
@@ -1138,6 +2381,7 @@ function GradientRow({ label = "Gradient", initialStops, onChange, angle = 90 })
1138
2381
  {
1139
2382
  type: "button",
1140
2383
  ...props,
2384
+ disabled,
1141
2385
  className: ["gradientSwatchBtn", props.className].filter(Boolean).join(" "),
1142
2386
  style: { ...props.style, background: currentGradient },
1143
2387
  "aria-label": `Open ${label} editor`
@@ -1151,18 +2395,23 @@ function GradientRow({ label = "Gradient", initialStops, onChange, angle = 90 })
1151
2395
  );
1152
2396
  }
1153
2397
  export {
2398
+ Category,
2399
+ ColorPicker,
1154
2400
  ColorRow,
1155
2401
  DEFAULT_GRADIENT_STOPS,
1156
2402
  GradientPicker,
1157
2403
  GradientRow,
2404
+ Panel,
1158
2405
  SliderOverlapDebugProvider,
1159
2406
  SliderRow,
1160
2407
  SwatchPopover,
1161
2408
  ThemeProvider,
1162
2409
  ToggleRow,
2410
+ Panel as default,
1163
2411
  isTextInputTarget,
1164
2412
  parseGradient,
1165
2413
  stopsToGradient,
2414
+ useCategoryDisabled,
1166
2415
  useSliderOverlapDebugEnabled,
1167
2416
  useTheme
1168
2417
  };