@dwntgrnd/devlens 0.1.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.
@@ -0,0 +1,4569 @@
1
+ "use client";
2
+ import {
3
+ useDevLensConfig
4
+ } from "./chunk-LWQVUC3L.mjs";
5
+
6
+ // src/components/DevLensDrawer.tsx
7
+ import { useEffect as useEffect12, useMemo as useMemo9, useState as useState17 } from "react";
8
+ import {
9
+ Palette,
10
+ X,
11
+ PanelLeft,
12
+ PanelRight,
13
+ PanelBottom,
14
+ ExternalLink,
15
+ FileCode,
16
+ MousePointer as MousePointer2
17
+ } from "lucide-react";
18
+
19
+ // src/hooks/use-token-editor.ts
20
+ import { useCallback, useEffect, useRef, useState } from "react";
21
+
22
+ // src/config/token-groups.ts
23
+ var TOKEN_GROUPS = [
24
+ "Brand Colors",
25
+ "Semantic Colors",
26
+ "Surfaces",
27
+ "Text Colors",
28
+ "Borders",
29
+ "Navigation",
30
+ "Modular Type Scale",
31
+ "Typography Scale",
32
+ "Spacing & Layout",
33
+ "Shadows"
34
+ ];
35
+
36
+ // src/core/token-registry.ts
37
+ function buildRegistry(detected, overrides) {
38
+ const registry = {};
39
+ for (const [name, token] of detected) {
40
+ registry[name] = {
41
+ name,
42
+ label: token.label,
43
+ group: token.group,
44
+ type: token.type,
45
+ value: token.value
46
+ };
47
+ }
48
+ if (overrides) {
49
+ for (const [name, override] of Object.entries(overrides)) {
50
+ const existing = registry[name];
51
+ if (existing) {
52
+ if (override.label) existing.label = override.label;
53
+ if (override.group) existing.group = override.group;
54
+ if (override.type) existing.type = override.type;
55
+ if (override.hint) existing.hint = override.hint;
56
+ if (override.usedBy) existing.usedBy = override.usedBy;
57
+ if (override.hidden !== void 0) existing.hidden = override.hidden;
58
+ if (override.managedByScale !== void 0) existing.managedByScale = override.managedByScale;
59
+ } else {
60
+ registry[name] = {
61
+ name,
62
+ label: override.label ?? name.replace(/^--/, "").replace(/-/g, " "),
63
+ group: override.group ?? "Other",
64
+ type: override.type ?? "other",
65
+ value: "",
66
+ hint: override.hint,
67
+ usedBy: override.usedBy,
68
+ hidden: override.hidden,
69
+ managedByScale: override.managedByScale
70
+ };
71
+ }
72
+ }
73
+ }
74
+ return registry;
75
+ }
76
+ function getTokensByGroup(registry, extraGroups) {
77
+ const allGroups = [...TOKEN_GROUPS];
78
+ if (extraGroups) {
79
+ for (const g of extraGroups) {
80
+ if (!allGroups.includes(g)) allGroups.push(g);
81
+ }
82
+ }
83
+ const grouped = /* @__PURE__ */ new Map();
84
+ for (const token of Object.values(registry)) {
85
+ if (token.hidden) continue;
86
+ const list = grouped.get(token.group) ?? [];
87
+ list.push(token);
88
+ grouped.set(token.group, list);
89
+ }
90
+ const result = [];
91
+ for (const group of allGroups) {
92
+ const tokens = grouped.get(group);
93
+ if (tokens && tokens.length > 0) {
94
+ result.push({ group, tokens });
95
+ }
96
+ }
97
+ for (const [group, tokens] of grouped) {
98
+ if (!allGroups.includes(group) && tokens.length > 0) {
99
+ result.push({ group, tokens });
100
+ }
101
+ }
102
+ return result;
103
+ }
104
+
105
+ // src/core/auto-detect.ts
106
+ var EXCLUDED_PREFIXES = [
107
+ "--tw-",
108
+ "--next-",
109
+ "--webkit-",
110
+ "--moz-",
111
+ "--color-",
112
+ "--text-",
113
+ "--font-heading",
114
+ "--font-body",
115
+ "--font-mono",
116
+ "--spacing-",
117
+ "--container-"
118
+ ];
119
+ function getAllCustomProperties() {
120
+ const props = /* @__PURE__ */ new Map();
121
+ const computed = getComputedStyle(document.documentElement);
122
+ for (let i = 0; i < document.styleSheets.length; i++) {
123
+ try {
124
+ const sheet = document.styleSheets[i];
125
+ const rules = sheet.cssRules;
126
+ for (let j = 0; j < rules.length; j++) {
127
+ const rule = rules[j];
128
+ if (rule instanceof CSSStyleRule && rule.selectorText === ":root") {
129
+ for (let k = 0; k < rule.style.length; k++) {
130
+ const name = rule.style[k];
131
+ if (name.startsWith("--")) {
132
+ props.set(name, "");
133
+ }
134
+ }
135
+ }
136
+ }
137
+ } catch {
138
+ }
139
+ }
140
+ for (const name of props.keys()) {
141
+ const value = computed.getPropertyValue(name).trim();
142
+ if (value && value !== "initial") {
143
+ props.set(name, value);
144
+ } else {
145
+ props.delete(name);
146
+ }
147
+ }
148
+ for (const name of Array.from(props.keys())) {
149
+ if (EXCLUDED_PREFIXES.some((prefix) => name.startsWith(prefix))) {
150
+ props.delete(name);
151
+ }
152
+ }
153
+ return props;
154
+ }
155
+ var HSL_PATTERN = /^\d{1,3}(\.\d+)?\s+\d{1,3}(\.\d+)?%\s+\d{1,3}(\.\d+)?%$/;
156
+ var LENGTH_PATTERN = /^-?[\d.]+\s*(px|rem|em|vh|vw|%)$/;
157
+ function inferType(name, value) {
158
+ if (HSL_PATTERN.test(value)) return "hsl-color";
159
+ if ((name.includes("font") || name.includes("size")) && LENGTH_PATTERN.test(value)) return "font-size";
160
+ if (LENGTH_PATTERN.test(value)) return "length";
161
+ if (value.includes("rgb") && /\d+px/.test(value)) return "shadow";
162
+ return "other";
163
+ }
164
+ function inferGroup(name, type) {
165
+ if (name.includes("brand")) return "Brand Colors";
166
+ if (name.includes("sidebar") || name.includes("topbar")) return "Navigation";
167
+ if (name.includes("surface") || name.includes("emphasis-surface")) return "Surfaces";
168
+ if (name.includes("border")) return "Borders";
169
+ if (name.includes("foreground-secondary") || name.includes("foreground-tertiary")) return "Text Colors";
170
+ if (name.includes("font-size-") || name === "--font-base") return "Modular Type Scale";
171
+ if (name.includes("shadow")) return "Shadows";
172
+ if (name.includes("radius") || name.includes("height") || name.includes("width") || name.includes("spacing")) return "Spacing & Layout";
173
+ if (type === "hsl-color") return "Semantic Colors";
174
+ if (type === "font-size") return "Modular Type Scale";
175
+ if (type === "length") return "Spacing & Layout";
176
+ if (type === "shadow") return "Shadows";
177
+ return "Other";
178
+ }
179
+ function generateLabel(cssVar) {
180
+ return cssVar.replace(/^--/, "").split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
181
+ }
182
+ function autoDetectTokens() {
183
+ const properties = getAllCustomProperties();
184
+ const result = /* @__PURE__ */ new Map();
185
+ for (const [name, value] of properties) {
186
+ const type = inferType(name, value);
187
+ const group = inferGroup(name, type);
188
+ const label = generateLabel(name);
189
+ result.set(name, { label, group, type, value });
190
+ }
191
+ return result;
192
+ }
193
+
194
+ // src/hooks/use-token-editor.ts
195
+ function useTokenEditor() {
196
+ const { namespace, tokenOverrides } = useDevLensConfig();
197
+ const channelName = `${namespace}-token-editor`;
198
+ const [state, setState] = useState({
199
+ changes: /* @__PURE__ */ new Map(),
200
+ defaults: /* @__PURE__ */ new Map(),
201
+ registry: {}
202
+ });
203
+ const channelRef = useRef(null);
204
+ const scaleMetadataRef = useRef(null);
205
+ const registryRef = useRef({});
206
+ useEffect(() => {
207
+ const detected = autoDetectTokens();
208
+ const registry = buildRegistry(detected, tokenOverrides);
209
+ registryRef.current = registry;
210
+ const defaults = /* @__PURE__ */ new Map();
211
+ for (const token of Object.values(registry)) {
212
+ defaults.set(token.name, token.value);
213
+ }
214
+ setState((prev) => ({ ...prev, defaults, registry }));
215
+ }, [tokenOverrides]);
216
+ useEffect(() => {
217
+ const channel = new BroadcastChannel(channelName);
218
+ channelRef.current = channel;
219
+ channel.onmessage = (event) => {
220
+ const { type, cssVar, value } = event.data;
221
+ if (type === "update") {
222
+ document.documentElement.style.setProperty(cssVar, value);
223
+ setState((prev) => {
224
+ const changes = new Map(prev.changes);
225
+ changes.set(cssVar, value);
226
+ return { ...prev, changes };
227
+ });
228
+ } else if (type === "reset") {
229
+ document.documentElement.style.removeProperty(cssVar);
230
+ setState((prev) => {
231
+ const changes = new Map(prev.changes);
232
+ changes.delete(cssVar);
233
+ return { ...prev, changes };
234
+ });
235
+ } else if (type === "reset-all") {
236
+ for (const token of Object.values(registryRef.current)) {
237
+ document.documentElement.style.removeProperty(token.name);
238
+ }
239
+ setState((prev) => ({ ...prev, changes: /* @__PURE__ */ new Map() }));
240
+ }
241
+ };
242
+ return () => {
243
+ channel.close();
244
+ channelRef.current = null;
245
+ };
246
+ }, [channelName]);
247
+ const broadcast = useCallback(
248
+ (data) => {
249
+ channelRef.current?.postMessage(data);
250
+ },
251
+ []
252
+ );
253
+ const updateToken = useCallback(
254
+ (cssVar, value) => {
255
+ document.documentElement.style.setProperty(cssVar, value);
256
+ setState((prev) => {
257
+ const changes = new Map(prev.changes);
258
+ changes.set(cssVar, value);
259
+ return { ...prev, changes };
260
+ });
261
+ broadcast({ type: "update", cssVar, value });
262
+ },
263
+ [broadcast]
264
+ );
265
+ const resetToken = useCallback(
266
+ (cssVar) => {
267
+ document.documentElement.style.removeProperty(cssVar);
268
+ setState((prev) => {
269
+ const changes = new Map(prev.changes);
270
+ changes.delete(cssVar);
271
+ return { ...prev, changes };
272
+ });
273
+ broadcast({ type: "reset", cssVar, value: "" });
274
+ },
275
+ [broadcast]
276
+ );
277
+ const resetAll = useCallback(() => {
278
+ for (const token of Object.values(registryRef.current)) {
279
+ document.documentElement.style.removeProperty(token.name);
280
+ }
281
+ setState((prev) => ({ ...prev, changes: /* @__PURE__ */ new Map() }));
282
+ broadcast({ type: "reset-all", cssVar: "", value: "" });
283
+ }, [broadcast]);
284
+ const getCurrentValue = useCallback(
285
+ (cssVar) => {
286
+ return state.changes.get(cssVar) ?? state.defaults.get(cssVar) ?? "";
287
+ },
288
+ [state.changes, state.defaults]
289
+ );
290
+ const isModified = useCallback(
291
+ (cssVar) => {
292
+ return state.changes.has(cssVar);
293
+ },
294
+ [state.changes]
295
+ );
296
+ const getChangeCount = useCallback(() => {
297
+ return state.changes.size;
298
+ }, [state.changes]);
299
+ const getGroupChangeCount = useCallback(
300
+ (groupTokens) => {
301
+ return groupTokens.filter((t) => state.changes.has(t.name)).length;
302
+ },
303
+ [state.changes]
304
+ );
305
+ const setScaleMetadata = useCallback((meta) => {
306
+ scaleMetadataRef.current = meta;
307
+ }, []);
308
+ const generateDiff = useCallback(() => {
309
+ if (state.changes.size === 0) return "No changes.";
310
+ const lines = [];
311
+ lines.push("| Variable | Original | New |");
312
+ lines.push("|----------|----------|-----|");
313
+ for (const [cssVar, newVal] of state.changes) {
314
+ const original = state.defaults.get(cssVar) ?? "(unknown)";
315
+ lines.push(`| \`${cssVar}\` | \`${original}\` | \`${newVal}\` |`);
316
+ }
317
+ lines.push("");
318
+ lines.push("```css");
319
+ const hasFontChanges = Array.from(state.changes.keys()).some(
320
+ (k) => k === "--font-base" || k.startsWith("--font-size-")
321
+ );
322
+ if (hasFontChanges && scaleMetadataRef.current) {
323
+ const { base, ratio, ratioLabel } = scaleMetadataRef.current;
324
+ lines.push(`/* Modular scale: base ${base}px, ratio ${ratio} (${ratioLabel}) */`);
325
+ }
326
+ lines.push(":root {");
327
+ for (const [cssVar, newVal] of state.changes) {
328
+ lines.push(` ${cssVar}: ${newVal};`);
329
+ }
330
+ lines.push("}");
331
+ lines.push("```");
332
+ return lines.join("\n");
333
+ }, [state.changes, state.defaults]);
334
+ return {
335
+ updateToken,
336
+ resetToken,
337
+ resetAll,
338
+ getCurrentValue,
339
+ isModified,
340
+ getChangeCount,
341
+ getGroupChangeCount,
342
+ generateDiff,
343
+ setScaleMetadata,
344
+ changes: state.changes,
345
+ registry: state.registry
346
+ };
347
+ }
348
+
349
+ // src/hooks/use-dock-position.ts
350
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
351
+ function readStorage(key, fallback) {
352
+ if (typeof window === "undefined") return fallback;
353
+ try {
354
+ const stored = localStorage.getItem(key);
355
+ return stored ? JSON.parse(stored) : fallback;
356
+ } catch {
357
+ return fallback;
358
+ }
359
+ }
360
+ function useDockPosition() {
361
+ const { namespace } = useDevLensConfig();
362
+ const DOCK_KEY = `${namespace}-dock`;
363
+ const OPEN_KEY = `${namespace}-open`;
364
+ const [dock, setDockState] = useState2("right");
365
+ const [isOpen, setIsOpenState] = useState2(false);
366
+ useEffect2(() => {
367
+ setDockState(readStorage(DOCK_KEY, "right"));
368
+ setIsOpenState(readStorage(OPEN_KEY, false));
369
+ }, [DOCK_KEY, OPEN_KEY]);
370
+ const setDock = useCallback2((pos) => {
371
+ setDockState(pos);
372
+ localStorage.setItem(DOCK_KEY, JSON.stringify(pos));
373
+ }, [DOCK_KEY]);
374
+ const setIsOpen = useCallback2((open) => {
375
+ setIsOpenState(open);
376
+ localStorage.setItem(OPEN_KEY, JSON.stringify(open));
377
+ }, [OPEN_KEY]);
378
+ const toggle = useCallback2(() => {
379
+ setIsOpen(!isOpen);
380
+ }, [isOpen, setIsOpen]);
381
+ return { dock, setDock, isOpen, setIsOpen, toggle };
382
+ }
383
+
384
+ // src/hooks/use-detached-window.ts
385
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
386
+ function useDetachedWindow() {
387
+ const { namespace, detachedRoute } = useDevLensConfig();
388
+ const windowName = `${namespace}-detached`;
389
+ const windowRef = useRef2(null);
390
+ const [isDetached, setIsDetached] = useState3(false);
391
+ const pollRef = useRef2(null);
392
+ const detach = useCallback3(() => {
393
+ if (windowRef.current && !windowRef.current.closed) {
394
+ windowRef.current.focus();
395
+ return;
396
+ }
397
+ const popup = window.open(
398
+ detachedRoute,
399
+ windowName,
400
+ "width=380,height=700,menubar=no,toolbar=no,location=no,status=no"
401
+ );
402
+ if (popup) {
403
+ windowRef.current = popup;
404
+ setIsDetached(true);
405
+ }
406
+ }, [detachedRoute, windowName]);
407
+ const reattach = useCallback3(() => {
408
+ if (windowRef.current && !windowRef.current.closed) {
409
+ windowRef.current.close();
410
+ }
411
+ windowRef.current = null;
412
+ setIsDetached(false);
413
+ }, []);
414
+ useEffect3(() => {
415
+ if (isDetached) {
416
+ pollRef.current = setInterval(() => {
417
+ if (windowRef.current?.closed) {
418
+ windowRef.current = null;
419
+ setIsDetached(false);
420
+ }
421
+ }, 500);
422
+ }
423
+ return () => {
424
+ if (pollRef.current) {
425
+ clearInterval(pollRef.current);
426
+ pollRef.current = null;
427
+ }
428
+ };
429
+ }, [isDetached]);
430
+ useEffect3(() => {
431
+ return () => {
432
+ if (windowRef.current && !windowRef.current.closed) {
433
+ windowRef.current.close();
434
+ }
435
+ };
436
+ }, []);
437
+ return { isDetached, detach, reattach };
438
+ }
439
+
440
+ // src/components/Accordion.tsx
441
+ import {
442
+ createContext,
443
+ useContext,
444
+ useState as useState4,
445
+ useRef as useRef3,
446
+ useCallback as useCallback4,
447
+ useEffect as useEffect4
448
+ } from "react";
449
+ import { ChevronDown } from "lucide-react";
450
+ import { jsx, jsxs } from "react/jsx-runtime";
451
+ var AccordionContext = createContext({
452
+ openItem: null,
453
+ toggle: () => {
454
+ },
455
+ collapsible: true
456
+ });
457
+ function Accordion({
458
+ collapsible = false,
459
+ className,
460
+ children
461
+ }) {
462
+ const [openItem, setOpenItem] = useState4(null);
463
+ const toggle = useCallback4(
464
+ (value) => {
465
+ setOpenItem((prev) => {
466
+ if (prev === value) return collapsible ? null : prev;
467
+ return value;
468
+ });
469
+ },
470
+ [collapsible]
471
+ );
472
+ return /* @__PURE__ */ jsx(AccordionContext.Provider, { value: { openItem, toggle, collapsible }, children: /* @__PURE__ */ jsx("div", { className, "data-state": openItem ? "open" : "closed", children }) });
473
+ }
474
+ var ItemContext = createContext({
475
+ value: "",
476
+ isOpen: false
477
+ });
478
+ function AccordionItem({ value, className, children }) {
479
+ const { openItem } = useContext(AccordionContext);
480
+ const isOpen = openItem === value;
481
+ return /* @__PURE__ */ jsx(ItemContext.Provider, { value: { value, isOpen }, children: /* @__PURE__ */ jsx(
482
+ "div",
483
+ {
484
+ className,
485
+ "data-state": isOpen ? "open" : "closed",
486
+ children
487
+ }
488
+ ) });
489
+ }
490
+ function AccordionTrigger({ className, children }) {
491
+ const { toggle } = useContext(AccordionContext);
492
+ const { value, isOpen } = useContext(ItemContext);
493
+ return /* @__PURE__ */ jsxs(
494
+ "button",
495
+ {
496
+ type: "button",
497
+ "aria-expanded": isOpen,
498
+ className,
499
+ "data-state": isOpen ? "open" : "closed",
500
+ onClick: () => toggle(value),
501
+ style: {
502
+ display: "flex",
503
+ alignItems: "center",
504
+ justifyContent: "space-between",
505
+ width: "100%",
506
+ background: "none",
507
+ border: "none",
508
+ cursor: "pointer",
509
+ textAlign: "left"
510
+ },
511
+ children: [
512
+ children,
513
+ /* @__PURE__ */ jsx(
514
+ ChevronDown,
515
+ {
516
+ size: 14,
517
+ style: {
518
+ transition: "transform 200ms ease",
519
+ transform: isOpen ? "rotate(180deg)" : "rotate(0deg)",
520
+ flexShrink: 0
521
+ }
522
+ }
523
+ )
524
+ ]
525
+ }
526
+ );
527
+ }
528
+ function AccordionContent({ className, children }) {
529
+ const { isOpen } = useContext(ItemContext);
530
+ const ref = useRef3(null);
531
+ const [height, setHeight] = useState4(void 0);
532
+ const [mounted, setMounted] = useState4(false);
533
+ useEffect4(() => {
534
+ setMounted(true);
535
+ }, []);
536
+ useEffect4(() => {
537
+ const el = ref.current;
538
+ if (!el) return;
539
+ if (isOpen) {
540
+ setHeight(el.scrollHeight);
541
+ } else {
542
+ setHeight(el.scrollHeight);
543
+ requestAnimationFrame(() => {
544
+ setHeight(0);
545
+ });
546
+ }
547
+ }, [isOpen]);
548
+ const handleTransitionEnd = () => {
549
+ if (isOpen) {
550
+ setHeight(void 0);
551
+ }
552
+ };
553
+ return /* @__PURE__ */ jsx(
554
+ "div",
555
+ {
556
+ ref,
557
+ className,
558
+ "data-state": isOpen ? "open" : "closed",
559
+ style: {
560
+ overflow: "hidden",
561
+ height: !mounted ? isOpen ? "auto" : 0 : height === void 0 ? "auto" : height,
562
+ transition: mounted ? "height 200ms ease" : "none"
563
+ },
564
+ onTransitionEnd: handleTransitionEnd,
565
+ children
566
+ }
567
+ );
568
+ }
569
+
570
+ // src/components/HslColorControl.tsx
571
+ import { useCallback as useCallback5, useMemo } from "react";
572
+ import { RotateCcw, Copy } from "lucide-react";
573
+
574
+ // src/core/color-utils.ts
575
+ function parseHslString(raw) {
576
+ const cleaned = raw.trim();
577
+ const match = cleaned.match(
578
+ /^(?:hsl\()?\s*(\d+(?:\.\d+)?)\s*[,\s]\s*(\d+(?:\.\d+)?)%?\s*[,\s]\s*(\d+(?:\.\d+)?)%?\s*\)?$/
579
+ );
580
+ if (!match) return null;
581
+ return {
582
+ h: parseFloat(match[1]),
583
+ s: parseFloat(match[2]),
584
+ l: parseFloat(match[3])
585
+ };
586
+ }
587
+ function formatHslString(hsl) {
588
+ return `${Math.round(hsl.h)} ${Math.round(hsl.s)}% ${Math.round(hsl.l)}%`;
589
+ }
590
+ function hslToHex(h, s, l) {
591
+ const sNorm = s / 100;
592
+ const lNorm = l / 100;
593
+ const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
594
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
595
+ const m = lNorm - c / 2;
596
+ let r = 0, g = 0, b = 0;
597
+ if (h < 60) {
598
+ r = c;
599
+ g = x;
600
+ b = 0;
601
+ } else if (h < 120) {
602
+ r = x;
603
+ g = c;
604
+ b = 0;
605
+ } else if (h < 180) {
606
+ r = 0;
607
+ g = c;
608
+ b = x;
609
+ } else if (h < 240) {
610
+ r = 0;
611
+ g = x;
612
+ b = c;
613
+ } else if (h < 300) {
614
+ r = x;
615
+ g = 0;
616
+ b = c;
617
+ } else {
618
+ r = c;
619
+ g = 0;
620
+ b = x;
621
+ }
622
+ const toHex = (v) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
623
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
624
+ }
625
+ function hexToHsl(hex) {
626
+ const clean = hex.replace("#", "");
627
+ const r = parseInt(clean.substring(0, 2), 16) / 255;
628
+ const g = parseInt(clean.substring(2, 4), 16) / 255;
629
+ const b = parseInt(clean.substring(4, 6), 16) / 255;
630
+ const max = Math.max(r, g, b);
631
+ const min = Math.min(r, g, b);
632
+ const l = (max + min) / 2;
633
+ const d = max - min;
634
+ if (d === 0) {
635
+ return { h: 0, s: 0, l: Math.round(l * 100) };
636
+ }
637
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
638
+ let h = 0;
639
+ if (max === r) {
640
+ h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
641
+ } else if (max === g) {
642
+ h = ((b - r) / d + 2) * 60;
643
+ } else {
644
+ h = ((r - g) / d + 4) * 60;
645
+ }
646
+ return {
647
+ h: Math.round(h),
648
+ s: Math.round(s * 100),
649
+ l: Math.round(l * 100)
650
+ };
651
+ }
652
+
653
+ // src/components/HslColorControl.tsx
654
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
655
+ function HslColorControl({
656
+ label,
657
+ value,
658
+ isModified,
659
+ onChange,
660
+ onReset
661
+ }) {
662
+ const hsl = useMemo(() => parseHslString(value), [value]);
663
+ const hex = useMemo(() => {
664
+ if (!hsl) return "#888888";
665
+ return hslToHex(hsl.h, hsl.s, hsl.l);
666
+ }, [hsl]);
667
+ const handleSliderChange = useCallback5(
668
+ (channel, rawValue) => {
669
+ if (!hsl) return;
670
+ const updated = { ...hsl, [channel]: parseFloat(rawValue) };
671
+ onChange(formatHslString(updated));
672
+ },
673
+ [hsl, onChange]
674
+ );
675
+ const handleHexChange = useCallback5(
676
+ (hexInput) => {
677
+ if (/^#[0-9a-fA-F]{6}$/.test(hexInput)) {
678
+ const converted = hexToHsl(hexInput);
679
+ onChange(formatHslString(converted));
680
+ }
681
+ },
682
+ [onChange]
683
+ );
684
+ const copyHsl = useCallback5(() => {
685
+ navigator.clipboard.writeText(value);
686
+ }, [value]);
687
+ if (!hsl) {
688
+ return /* @__PURE__ */ jsxs2("div", { className: "te-control", children: [
689
+ /* @__PURE__ */ jsx2("span", { className: "te-label", children: label }),
690
+ /* @__PURE__ */ jsxs2("span", { className: "te-muted", children: [
691
+ "Invalid HSL: ",
692
+ value
693
+ ] })
694
+ ] });
695
+ }
696
+ return /* @__PURE__ */ jsxs2("div", { className: "te-control", children: [
697
+ /* @__PURE__ */ jsxs2("div", { className: "te-control-header", children: [
698
+ /* @__PURE__ */ jsxs2("span", { className: "te-label", children: [
699
+ label,
700
+ isModified && /* @__PURE__ */ jsx2("span", { className: "te-modified-dot" })
701
+ ] }),
702
+ /* @__PURE__ */ jsxs2("div", { className: "te-control-actions", children: [
703
+ /* @__PURE__ */ jsx2(
704
+ "button",
705
+ {
706
+ type: "button",
707
+ className: "te-icon-btn",
708
+ onClick: copyHsl,
709
+ title: "Copy HSL value",
710
+ children: /* @__PURE__ */ jsx2(Copy, { size: 12 })
711
+ }
712
+ ),
713
+ isModified && /* @__PURE__ */ jsx2(
714
+ "button",
715
+ {
716
+ type: "button",
717
+ className: "te-icon-btn te-reset-btn",
718
+ onClick: onReset,
719
+ title: "Reset to default",
720
+ children: /* @__PURE__ */ jsx2(RotateCcw, { size: 12 })
721
+ }
722
+ )
723
+ ] })
724
+ ] }),
725
+ /* @__PURE__ */ jsxs2("div", { className: "te-color-row", children: [
726
+ /* @__PURE__ */ jsx2(
727
+ "input",
728
+ {
729
+ type: "color",
730
+ value: hex,
731
+ onChange: (e) => handleHexChange(e.target.value),
732
+ className: "te-color-swatch"
733
+ }
734
+ ),
735
+ /* @__PURE__ */ jsx2(
736
+ "input",
737
+ {
738
+ type: "text",
739
+ value: hex,
740
+ onChange: (e) => handleHexChange(e.target.value),
741
+ className: "te-text-input te-hex-input",
742
+ spellCheck: false
743
+ }
744
+ )
745
+ ] }),
746
+ /* @__PURE__ */ jsxs2("div", { className: "te-slider-group", children: [
747
+ /* @__PURE__ */ jsxs2("div", { className: "te-slider-row", children: [
748
+ /* @__PURE__ */ jsx2("span", { className: "te-slider-label", children: "H" }),
749
+ /* @__PURE__ */ jsx2(
750
+ "input",
751
+ {
752
+ type: "range",
753
+ min: 0,
754
+ max: 360,
755
+ value: hsl.h,
756
+ onChange: (e) => handleSliderChange("h", e.target.value),
757
+ className: "te-slider"
758
+ }
759
+ ),
760
+ /* @__PURE__ */ jsx2("span", { className: "te-slider-value", children: Math.round(hsl.h) })
761
+ ] }),
762
+ /* @__PURE__ */ jsxs2("div", { className: "te-slider-row", children: [
763
+ /* @__PURE__ */ jsx2("span", { className: "te-slider-label", children: "S" }),
764
+ /* @__PURE__ */ jsx2(
765
+ "input",
766
+ {
767
+ type: "range",
768
+ min: 0,
769
+ max: 100,
770
+ value: hsl.s,
771
+ onChange: (e) => handleSliderChange("s", e.target.value),
772
+ className: "te-slider"
773
+ }
774
+ ),
775
+ /* @__PURE__ */ jsxs2("span", { className: "te-slider-value", children: [
776
+ Math.round(hsl.s),
777
+ "%"
778
+ ] })
779
+ ] }),
780
+ /* @__PURE__ */ jsxs2("div", { className: "te-slider-row", children: [
781
+ /* @__PURE__ */ jsx2("span", { className: "te-slider-label", children: "L" }),
782
+ /* @__PURE__ */ jsx2(
783
+ "input",
784
+ {
785
+ type: "range",
786
+ min: 0,
787
+ max: 100,
788
+ value: hsl.l,
789
+ onChange: (e) => handleSliderChange("l", e.target.value),
790
+ className: "te-slider"
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsxs2("span", { className: "te-slider-value", children: [
794
+ Math.round(hsl.l),
795
+ "%"
796
+ ] })
797
+ ] })
798
+ ] })
799
+ ] });
800
+ }
801
+
802
+ // src/components/LengthControl.tsx
803
+ import { useCallback as useCallback6, useMemo as useMemo2 } from "react";
804
+ import { RotateCcw as RotateCcw2 } from "lucide-react";
805
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
806
+ function parseLength(raw) {
807
+ const match = raw.trim().match(/^([\d.]+)\s*(px|rem|em|%)$/);
808
+ if (match) {
809
+ return { num: parseFloat(match[1]), unit: match[2] };
810
+ }
811
+ const num = parseFloat(raw);
812
+ if (!isNaN(num)) {
813
+ return { num, unit: "px" };
814
+ }
815
+ return { num: 16, unit: "px" };
816
+ }
817
+ function LengthControl({
818
+ label,
819
+ value,
820
+ isModified,
821
+ onChange,
822
+ onReset,
823
+ isFontSize
824
+ }) {
825
+ const parsed = useMemo2(() => parseLength(value), [value]);
826
+ const maxRange = useMemo2(() => {
827
+ if (isFontSize) return parsed.unit === "rem" ? 4 : 64;
828
+ if (parsed.unit === "rem") return 120;
829
+ return 2e3;
830
+ }, [isFontSize, parsed.unit]);
831
+ const step = parsed.unit === "rem" ? 0.125 : 1;
832
+ const handleNumChange = useCallback6(
833
+ (rawValue) => {
834
+ const num = parseFloat(rawValue);
835
+ if (!isNaN(num)) {
836
+ onChange(`${num}${parsed.unit}`);
837
+ }
838
+ },
839
+ [onChange, parsed.unit]
840
+ );
841
+ const handleUnitChange = useCallback6(
842
+ (newUnit) => {
843
+ let converted = parsed.num;
844
+ if (parsed.unit === "px" && newUnit === "rem") {
845
+ converted = parseFloat((parsed.num / 16).toFixed(3));
846
+ } else if (parsed.unit === "rem" && newUnit === "px") {
847
+ converted = Math.round(parsed.num * 16);
848
+ }
849
+ onChange(`${converted}${newUnit}`);
850
+ },
851
+ [onChange, parsed]
852
+ );
853
+ return /* @__PURE__ */ jsxs3("div", { className: "te-control", children: [
854
+ /* @__PURE__ */ jsxs3("div", { className: "te-control-header", children: [
855
+ /* @__PURE__ */ jsxs3("span", { className: "te-label", children: [
856
+ label,
857
+ isModified && /* @__PURE__ */ jsx3("span", { className: "te-modified-dot" })
858
+ ] }),
859
+ isModified && /* @__PURE__ */ jsx3(
860
+ "button",
861
+ {
862
+ type: "button",
863
+ className: "te-icon-btn te-reset-btn",
864
+ onClick: onReset,
865
+ title: "Reset to default",
866
+ children: /* @__PURE__ */ jsx3(RotateCcw2, { size: 12 })
867
+ }
868
+ )
869
+ ] }),
870
+ /* @__PURE__ */ jsxs3("div", { className: "te-length-row", children: [
871
+ /* @__PURE__ */ jsx3(
872
+ "input",
873
+ {
874
+ type: "number",
875
+ value: parsed.num,
876
+ onChange: (e) => handleNumChange(e.target.value),
877
+ step,
878
+ min: 0,
879
+ className: "te-text-input te-num-input"
880
+ }
881
+ ),
882
+ /* @__PURE__ */ jsxs3(
883
+ "select",
884
+ {
885
+ value: parsed.unit,
886
+ onChange: (e) => handleUnitChange(e.target.value),
887
+ className: "te-select",
888
+ children: [
889
+ /* @__PURE__ */ jsx3("option", { value: "px", children: "px" }),
890
+ /* @__PURE__ */ jsx3("option", { value: "rem", children: "rem" })
891
+ ]
892
+ }
893
+ )
894
+ ] }),
895
+ /* @__PURE__ */ jsx3(
896
+ "input",
897
+ {
898
+ type: "range",
899
+ min: 0,
900
+ max: maxRange,
901
+ step,
902
+ value: parsed.num,
903
+ onChange: (e) => handleNumChange(e.target.value),
904
+ className: "te-slider te-slider-full"
905
+ }
906
+ ),
907
+ isFontSize && /* @__PURE__ */ jsx3(
908
+ "div",
909
+ {
910
+ className: "te-font-preview",
911
+ style: { fontSize: value },
912
+ children: "Aa Bb Cc 123"
913
+ }
914
+ )
915
+ ] });
916
+ }
917
+
918
+ // src/components/ShadowControl.tsx
919
+ import { useCallback as useCallback7 } from "react";
920
+ import { RotateCcw as RotateCcw3 } from "lucide-react";
921
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
922
+ var SHADOW_PRESETS = {
923
+ none: "none",
924
+ sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
925
+ md: "0 4px 6px -1px rgb(0 0 0 / 0.07), 0 2px 4px -2px rgb(0 0 0 / 0.05)",
926
+ lg: "0 10px 15px -3px rgb(0 0 0 / 0.07), 0 4px 6px -4px rgb(0 0 0 / 0.05)",
927
+ xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)"
928
+ };
929
+ function detectPreset(value) {
930
+ const trimmed = value.trim();
931
+ for (const [key, preset] of Object.entries(SHADOW_PRESETS)) {
932
+ if (trimmed === preset) return key;
933
+ }
934
+ return "custom";
935
+ }
936
+ function ShadowControl({
937
+ label,
938
+ value,
939
+ isModified,
940
+ onChange,
941
+ onReset
942
+ }) {
943
+ const currentPreset = detectPreset(value);
944
+ const handleChange = useCallback7(
945
+ (preset) => {
946
+ const shadow = SHADOW_PRESETS[preset];
947
+ if (shadow) {
948
+ onChange(shadow);
949
+ }
950
+ },
951
+ [onChange]
952
+ );
953
+ return /* @__PURE__ */ jsxs4("div", { className: "te-control", children: [
954
+ /* @__PURE__ */ jsxs4("div", { className: "te-control-header", children: [
955
+ /* @__PURE__ */ jsxs4("span", { className: "te-label", children: [
956
+ label,
957
+ isModified && /* @__PURE__ */ jsx4("span", { className: "te-modified-dot" })
958
+ ] }),
959
+ isModified && /* @__PURE__ */ jsx4(
960
+ "button",
961
+ {
962
+ type: "button",
963
+ className: "te-icon-btn te-reset-btn",
964
+ onClick: onReset,
965
+ title: "Reset to default",
966
+ children: /* @__PURE__ */ jsx4(RotateCcw3, { size: 12 })
967
+ }
968
+ )
969
+ ] }),
970
+ /* @__PURE__ */ jsxs4(
971
+ "select",
972
+ {
973
+ value: currentPreset,
974
+ onChange: (e) => handleChange(e.target.value),
975
+ className: "te-select te-select-full",
976
+ children: [
977
+ /* @__PURE__ */ jsx4("option", { value: "none", children: "None" }),
978
+ /* @__PURE__ */ jsx4("option", { value: "sm", children: "Small" }),
979
+ /* @__PURE__ */ jsx4("option", { value: "md", children: "Medium" }),
980
+ /* @__PURE__ */ jsx4("option", { value: "lg", children: "Large" }),
981
+ /* @__PURE__ */ jsx4("option", { value: "xl", children: "Extra Large" }),
982
+ currentPreset === "custom" && /* @__PURE__ */ jsx4("option", { value: "custom", disabled: true, children: "Custom" })
983
+ ]
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsx4(
987
+ "div",
988
+ {
989
+ className: "te-shadow-preview",
990
+ style: { boxShadow: value === "none" ? "none" : value }
991
+ }
992
+ )
993
+ ] });
994
+ }
995
+
996
+ // src/components/ModularScaleControl.tsx
997
+ import { useCallback as useCallback8, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
998
+ import { RotateCcw as RotateCcw4 } from "lucide-react";
999
+
1000
+ // src/core/modular-scale.ts
1001
+ var FONT_STEPS = [
1002
+ { cssVar: "--font-size-overline", step: -2, label: "Overline" },
1003
+ { cssVar: "--font-size-caption", step: -1, label: "Caption" },
1004
+ { cssVar: "--font-size-subsection-sm", step: -0.5, label: "Subsection Sm" },
1005
+ { cssVar: "--font-size-body", step: 0, label: "Body" },
1006
+ { cssVar: "--font-size-subsection-heading", step: 0.5, label: "Subsection" },
1007
+ { cssVar: "--font-size-section-heading", step: 1.5, label: "Section Heading" },
1008
+ { cssVar: "--font-size-page-title", step: 3, label: "Page Title" }
1009
+ ];
1010
+ var SCALE_RATIOS = [
1011
+ { label: "1.067 \u2014 Minor Second", value: 1.067 },
1012
+ { label: "1.125 \u2014 Major Second", value: 1.125 },
1013
+ { label: "1.200 \u2014 Minor Third", value: 1.2 },
1014
+ { label: "1.250 \u2014 Major Third", value: 1.25 },
1015
+ { label: "1.333 \u2014 Perfect Fourth", value: 1.333 },
1016
+ { label: "1.414 \u2014 Augmented Fourth", value: 1.414 },
1017
+ { label: "1.500 \u2014 Perfect Fifth", value: 1.5 },
1018
+ { label: "1.618 \u2014 Golden Ratio", value: 1.618 }
1019
+ ];
1020
+ function calculateScale(base, ratio, step) {
1021
+ const size = base * Math.pow(ratio, step);
1022
+ return `${Math.round(size * 100) / 100}px`;
1023
+ }
1024
+ function parsePx(value) {
1025
+ return parseFloat(value.replace("px", ""));
1026
+ }
1027
+ function detectClosestRatio(base, pageTitlePx) {
1028
+ let closest = SCALE_RATIOS[2].value;
1029
+ let minDiff = Infinity;
1030
+ for (const { value: ratio } of SCALE_RATIOS) {
1031
+ const predicted = base * Math.pow(ratio, 3);
1032
+ const diff = Math.abs(predicted - pageTitlePx);
1033
+ if (diff < minDiff) {
1034
+ minDiff = diff;
1035
+ closest = ratio;
1036
+ }
1037
+ }
1038
+ return closest;
1039
+ }
1040
+
1041
+ // src/components/InspectorSwitch.tsx
1042
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1043
+ function InspectorSwitch({
1044
+ checked,
1045
+ onChange,
1046
+ labelOn = "Selecting",
1047
+ labelOff = "Select",
1048
+ compact = false
1049
+ }) {
1050
+ const trackSize = compact ? { width: 28, height: 16 } : { width: 36, height: 20 };
1051
+ const thumbSize = compact ? 12 : 16;
1052
+ const thumbOffset = compact ? 2 : 2;
1053
+ const translateX = checked ? trackSize.width - thumbSize - thumbOffset * 2 : 0;
1054
+ return /* @__PURE__ */ jsxs5(
1055
+ "button",
1056
+ {
1057
+ type: "button",
1058
+ className: `te-switch ${compact ? "te-switch-compact" : ""} ${checked ? "te-switch-active" : ""}`,
1059
+ onClick: onChange,
1060
+ role: "switch",
1061
+ "aria-checked": checked,
1062
+ children: [
1063
+ /* @__PURE__ */ jsx5(
1064
+ "span",
1065
+ {
1066
+ className: "te-switch-track",
1067
+ style: {
1068
+ width: trackSize.width,
1069
+ height: trackSize.height,
1070
+ background: checked ? "#89b4fa" : "#313244"
1071
+ },
1072
+ children: /* @__PURE__ */ jsx5(
1073
+ "span",
1074
+ {
1075
+ className: "te-switch-thumb",
1076
+ style: {
1077
+ width: thumbSize,
1078
+ height: thumbSize,
1079
+ transform: `translateX(${translateX}px)`
1080
+ }
1081
+ }
1082
+ )
1083
+ }
1084
+ ),
1085
+ /* @__PURE__ */ jsx5(
1086
+ "span",
1087
+ {
1088
+ className: "te-switch-label",
1089
+ style: { color: checked ? "#89b4fa" : "#a6adc8" },
1090
+ children: checked ? labelOn : labelOff
1091
+ }
1092
+ )
1093
+ ]
1094
+ }
1095
+ );
1096
+ }
1097
+
1098
+ // src/components/ModularScaleControl.tsx
1099
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1100
+ function ModularScaleControl({
1101
+ getCurrentValue,
1102
+ isModified,
1103
+ updateToken,
1104
+ resetToken,
1105
+ setScaleMetadata,
1106
+ scaleBaseline
1107
+ }) {
1108
+ const initializedRef = useRef4(false);
1109
+ const [base, setBase] = useState5(() => {
1110
+ const val = parsePx(getCurrentValue("--font-base"));
1111
+ return isNaN(val) ? 16 : val;
1112
+ });
1113
+ const [ratio, setRatio] = useState5(() => {
1114
+ const baseVal = parsePx(getCurrentValue("--font-base"));
1115
+ const b = isNaN(baseVal) ? 16 : baseVal;
1116
+ const ptVal = parsePx(getCurrentValue("--font-size-page-title"));
1117
+ const pt = isNaN(ptVal) ? 24 : ptVal;
1118
+ return detectClosestRatio(b, pt);
1119
+ });
1120
+ useEffect5(() => {
1121
+ if (initializedRef.current) return;
1122
+ const baseStr = getCurrentValue("--font-base");
1123
+ const ptStr = getCurrentValue("--font-size-page-title");
1124
+ if (!baseStr && !ptStr) return;
1125
+ initializedRef.current = true;
1126
+ const b = parsePx(baseStr) || 16;
1127
+ const pt = parsePx(ptStr) || 24;
1128
+ setBase(b);
1129
+ setRatio(detectClosestRatio(b, pt));
1130
+ }, [getCurrentValue]);
1131
+ const applyScale = useCallback8(
1132
+ (newBase, newRatio) => {
1133
+ updateToken("--font-base", `${newBase}px`);
1134
+ for (const step of FONT_STEPS) {
1135
+ updateToken(step.cssVar, calculateScale(newBase, newRatio, step.step));
1136
+ }
1137
+ const ratioEntry2 = SCALE_RATIOS.find((r) => r.value === newRatio);
1138
+ setScaleMetadata({
1139
+ base: newBase,
1140
+ ratio: newRatio,
1141
+ ratioLabel: ratioEntry2?.label ?? `${newRatio}`
1142
+ });
1143
+ },
1144
+ [updateToken, setScaleMetadata]
1145
+ );
1146
+ const handleBaseChange = useCallback8(
1147
+ (newBase) => {
1148
+ setBase(newBase);
1149
+ applyScale(newBase, ratio);
1150
+ },
1151
+ [ratio, applyScale]
1152
+ );
1153
+ const handleRatioChange = useCallback8(
1154
+ (newRatio) => {
1155
+ setRatio(newRatio);
1156
+ applyScale(base, newRatio);
1157
+ },
1158
+ [base, applyScale]
1159
+ );
1160
+ const handleResetAll = useCallback8(() => {
1161
+ resetToken("--font-base");
1162
+ for (const step of FONT_STEPS) {
1163
+ resetToken(step.cssVar);
1164
+ }
1165
+ }, [resetToken]);
1166
+ const hasModifications = isModified("--font-base") || FONT_STEPS.some((s) => isModified(s.cssVar));
1167
+ const ratioEntry = SCALE_RATIOS.find((r) => r.value === ratio);
1168
+ return /* @__PURE__ */ jsxs6("div", { className: "te-scale", children: [
1169
+ /* @__PURE__ */ jsxs6("div", { style: {
1170
+ display: "flex",
1171
+ alignItems: "center",
1172
+ justifyContent: "space-between",
1173
+ padding: "6px 8px",
1174
+ marginBottom: 8,
1175
+ background: "#181825",
1176
+ borderRadius: 6,
1177
+ border: "1px solid #313244"
1178
+ }, children: [
1179
+ /* @__PURE__ */ jsxs6("div", { children: [
1180
+ /* @__PURE__ */ jsx6("span", { className: "te-label", style: { textTransform: "none", letterSpacing: "normal", fontSize: 12 }, children: "Scale Baseline" }),
1181
+ /* @__PURE__ */ jsx6("span", { className: "te-muted", style: { display: "block", marginTop: 4, fontSize: 10, lineHeight: 1.3 }, children: "Applies modular scale to bare h1\u2013h6, p, small elements via CSS cascade layer. Tailwind classes override." })
1182
+ ] }),
1183
+ /* @__PURE__ */ jsx6(
1184
+ InspectorSwitch,
1185
+ {
1186
+ checked: scaleBaseline.isActive,
1187
+ onChange: scaleBaseline.toggle,
1188
+ labelOn: "On",
1189
+ labelOff: "Off",
1190
+ compact: true
1191
+ }
1192
+ )
1193
+ ] }),
1194
+ hasModifications && /* @__PURE__ */ jsx6("div", { className: "te-scale-reset-row", children: /* @__PURE__ */ jsxs6(
1195
+ "button",
1196
+ {
1197
+ type: "button",
1198
+ className: "te-icon-btn te-reset-btn",
1199
+ onClick: handleResetAll,
1200
+ title: "Reset all font sizes",
1201
+ children: [
1202
+ /* @__PURE__ */ jsx6(RotateCcw4, { size: 12 }),
1203
+ /* @__PURE__ */ jsx6("span", { style: { fontSize: 11, marginLeft: 2 }, children: "Reset" })
1204
+ ]
1205
+ }
1206
+ ) }),
1207
+ /* @__PURE__ */ jsxs6("div", { className: "te-scale-control-row", children: [
1208
+ /* @__PURE__ */ jsx6("label", { className: "te-label", style: { marginBottom: 4 }, children: "Base Size" }),
1209
+ /* @__PURE__ */ jsxs6("div", { className: "te-slider-row", children: [
1210
+ /* @__PURE__ */ jsx6(
1211
+ "input",
1212
+ {
1213
+ type: "range",
1214
+ className: "te-slider",
1215
+ min: 12,
1216
+ max: 22,
1217
+ step: 0.5,
1218
+ value: base,
1219
+ onChange: (e) => handleBaseChange(parseFloat(e.target.value))
1220
+ }
1221
+ ),
1222
+ /* @__PURE__ */ jsxs6("span", { className: "te-slider-value", style: { width: 44 }, children: [
1223
+ base,
1224
+ "px"
1225
+ ] })
1226
+ ] })
1227
+ ] }),
1228
+ /* @__PURE__ */ jsxs6("div", { className: "te-scale-control-row", style: { marginTop: 8 }, children: [
1229
+ /* @__PURE__ */ jsx6("label", { className: "te-label", style: { marginBottom: 4 }, children: "Scale Ratio" }),
1230
+ /* @__PURE__ */ jsx6(
1231
+ "select",
1232
+ {
1233
+ className: "te-select te-select-full",
1234
+ value: ratio,
1235
+ onChange: (e) => handleRatioChange(parseFloat(e.target.value)),
1236
+ children: SCALE_RATIOS.map((r) => /* @__PURE__ */ jsx6("option", { value: r.value, children: r.label }, r.value))
1237
+ }
1238
+ )
1239
+ ] }),
1240
+ /* @__PURE__ */ jsxs6("div", { className: "te-scale-preview", style: { marginTop: 12 }, children: [
1241
+ /* @__PURE__ */ jsxs6("div", { className: "te-scale-preview-header", children: [
1242
+ /* @__PURE__ */ jsx6("span", { className: "te-label", children: "Preview" }),
1243
+ /* @__PURE__ */ jsx6("span", { className: "te-muted", children: ratioEntry?.label ?? `${ratio}` })
1244
+ ] }),
1245
+ FONT_STEPS.slice().reverse().map((step) => {
1246
+ const computed = calculateScale(base, ratio, step.step);
1247
+ const px = parsePx(computed);
1248
+ return /* @__PURE__ */ jsxs6("div", { className: "te-scale-row", children: [
1249
+ /* @__PURE__ */ jsx6(
1250
+ "span",
1251
+ {
1252
+ className: "te-scale-sample",
1253
+ style: {
1254
+ fontSize: `${px}px`,
1255
+ fontFamily: "var(--devlens-preview-font, inherit)",
1256
+ lineHeight: 1.3
1257
+ },
1258
+ children: step.label
1259
+ }
1260
+ ),
1261
+ /* @__PURE__ */ jsxs6("span", { className: "te-scale-meta", children: [
1262
+ /* @__PURE__ */ jsxs6("span", { className: "te-scale-px", children: [
1263
+ Math.round(px * 10) / 10,
1264
+ "px"
1265
+ ] }),
1266
+ /* @__PURE__ */ jsxs6("span", { className: "te-scale-step", children: [
1267
+ step.step >= 0 ? "+" : "",
1268
+ step.step
1269
+ ] })
1270
+ ] })
1271
+ ] }, step.cssVar);
1272
+ })
1273
+ ] })
1274
+ ] });
1275
+ }
1276
+
1277
+ // src/components/TokenEditorControls.tsx
1278
+ import { Info } from "lucide-react";
1279
+ import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1280
+ function TokenLabel({ label, usedBy }) {
1281
+ if (!usedBy || usedBy.length === 0) {
1282
+ return /* @__PURE__ */ jsx7(Fragment, { children: label });
1283
+ }
1284
+ return /* @__PURE__ */ jsxs7("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
1285
+ label,
1286
+ /* @__PURE__ */ jsxs7("span", { className: "te-usage-hint", children: [
1287
+ /* @__PURE__ */ jsx7(Info, { size: 10 }),
1288
+ /* @__PURE__ */ jsx7("span", { className: "te-usage-hint-tooltip", children: usedBy.join(", ") })
1289
+ ] })
1290
+ ] });
1291
+ }
1292
+ function TokenEditorControls({
1293
+ registry,
1294
+ getCurrentValue,
1295
+ isModified,
1296
+ updateToken,
1297
+ resetToken,
1298
+ getGroupChangeCount,
1299
+ setScaleMetadata,
1300
+ scaleBaseline
1301
+ }) {
1302
+ const groups = getTokensByGroup(registry);
1303
+ return /* @__PURE__ */ jsx7(Accordion, { type: "single", collapsible: true, className: "te-accordion", children: groups.map(({ group: groupName, tokens }) => {
1304
+ const changeCount = getGroupChangeCount(tokens);
1305
+ if (groupName === "Modular Type Scale") {
1306
+ return /* @__PURE__ */ jsxs7(AccordionItem, { value: groupName, className: "te-accordion-item", children: [
1307
+ /* @__PURE__ */ jsx7(AccordionTrigger, { className: "te-accordion-trigger", children: /* @__PURE__ */ jsxs7("span", { children: [
1308
+ "Modular Type Scale",
1309
+ changeCount > 0 && /* @__PURE__ */ jsx7("span", { className: "te-change-badge", children: changeCount })
1310
+ ] }) }),
1311
+ /* @__PURE__ */ jsx7(AccordionContent, { className: "te-accordion-content", children: /* @__PURE__ */ jsx7(
1312
+ ModularScaleControl,
1313
+ {
1314
+ getCurrentValue,
1315
+ isModified,
1316
+ updateToken,
1317
+ resetToken,
1318
+ setScaleMetadata,
1319
+ scaleBaseline
1320
+ }
1321
+ ) })
1322
+ ] }, groupName);
1323
+ }
1324
+ return /* @__PURE__ */ jsxs7(AccordionItem, { value: groupName, className: "te-accordion-item", children: [
1325
+ /* @__PURE__ */ jsx7(AccordionTrigger, { className: "te-accordion-trigger", children: /* @__PURE__ */ jsxs7("span", { children: [
1326
+ groupName,
1327
+ changeCount > 0 && /* @__PURE__ */ jsx7("span", { className: "te-change-badge", children: changeCount })
1328
+ ] }) }),
1329
+ /* @__PURE__ */ jsx7(AccordionContent, { className: "te-accordion-content", children: /* @__PURE__ */ jsx7("div", { className: "te-token-list", children: tokens.map((token) => {
1330
+ if (token.managedByScale && groupName !== "Modular Type Scale") {
1331
+ const value2 = getCurrentValue(token.name);
1332
+ return /* @__PURE__ */ jsxs7("div", { className: "te-managed-row", children: [
1333
+ /* @__PURE__ */ jsx7("span", { className: "te-label", children: /* @__PURE__ */ jsx7(TokenLabel, { label: token.label, usedBy: token.usedBy }) }),
1334
+ /* @__PURE__ */ jsxs7("span", { className: "te-managed-meta", children: [
1335
+ /* @__PURE__ */ jsx7("span", { className: "te-managed-value", children: value2 || token.value }),
1336
+ /* @__PURE__ */ jsx7("span", { className: "te-managed-badge", children: "scale" })
1337
+ ] })
1338
+ ] }, token.name);
1339
+ }
1340
+ const value = getCurrentValue(token.name);
1341
+ const modified = isModified(token.name);
1342
+ const labelNode = /* @__PURE__ */ jsx7(TokenLabel, { label: token.label, usedBy: token.usedBy });
1343
+ switch (token.type) {
1344
+ case "hsl-color":
1345
+ return /* @__PURE__ */ jsx7(
1346
+ HslColorControl,
1347
+ {
1348
+ label: labelNode,
1349
+ value,
1350
+ isModified: modified,
1351
+ onChange: (v) => updateToken(token.name, v),
1352
+ onReset: () => resetToken(token.name)
1353
+ },
1354
+ token.name
1355
+ );
1356
+ case "length":
1357
+ return /* @__PURE__ */ jsx7(
1358
+ LengthControl,
1359
+ {
1360
+ label: labelNode,
1361
+ value,
1362
+ isModified: modified,
1363
+ onChange: (v) => updateToken(token.name, v),
1364
+ onReset: () => resetToken(token.name)
1365
+ },
1366
+ token.name
1367
+ );
1368
+ case "font-size":
1369
+ return /* @__PURE__ */ jsx7(
1370
+ LengthControl,
1371
+ {
1372
+ label: labelNode,
1373
+ value,
1374
+ isModified: modified,
1375
+ onChange: (v) => updateToken(token.name, v),
1376
+ onReset: () => resetToken(token.name),
1377
+ isFontSize: true
1378
+ },
1379
+ token.name
1380
+ );
1381
+ case "shadow":
1382
+ return /* @__PURE__ */ jsx7(
1383
+ ShadowControl,
1384
+ {
1385
+ label: labelNode,
1386
+ value,
1387
+ isModified: modified,
1388
+ onChange: (v) => updateToken(token.name, v),
1389
+ onReset: () => resetToken(token.name)
1390
+ },
1391
+ token.name
1392
+ );
1393
+ default:
1394
+ return null;
1395
+ }
1396
+ }) }) })
1397
+ ] }, groupName);
1398
+ }) });
1399
+ }
1400
+
1401
+ // src/components/TokenCreationZone.tsx
1402
+ import { useCallback as useCallback9, useMemo as useMemo3, useState as useState6 } from "react";
1403
+ import { Copy as Copy2, Check } from "lucide-react";
1404
+
1405
+ // src/core/cc-prompt-generator.ts
1406
+ function generateCCPrompt(config) {
1407
+ const lines = [];
1408
+ lines.push(`# CC Prompt: Create Token \`--${config.tokenName}\``);
1409
+ lines.push("");
1410
+ lines.push("## Context");
1411
+ lines.push("");
1412
+ if (config.context === "deviation") {
1413
+ lines.push("A typography deviation was identified via the DevLens Element Inspector.");
1414
+ if (config.elementTag) {
1415
+ lines.push(`Element: \`<${config.elementTag}>\``);
1416
+ }
1417
+ if (config.elementPath) {
1418
+ lines.push(`Path: \`${config.elementPath}\``);
1419
+ }
1420
+ } else {
1421
+ lines.push("Custom CSS was applied via the DevLens Raw CSS Input and needs formalization as a design token.");
1422
+ if (config.elementTag) {
1423
+ lines.push(`Element: \`<${config.elementTag}>\``);
1424
+ }
1425
+ }
1426
+ lines.push("");
1427
+ lines.push("## Token Definition");
1428
+ lines.push("");
1429
+ lines.push(`**Name:** \`--${config.tokenName}\``);
1430
+ lines.push("");
1431
+ lines.push("**Properties:**");
1432
+ lines.push("");
1433
+ for (const prop of config.properties) {
1434
+ lines.push(`- \`${prop.property}: ${prop.value}\`${prop.locked ? " (locked)" : ""}`);
1435
+ }
1436
+ if (config.scaleRelationship) {
1437
+ lines.push("");
1438
+ lines.push("**Scale Relationship:**");
1439
+ if (config.scaleRelationship.type === "independent") {
1440
+ lines.push("- Independent (not derived from modular scale)");
1441
+ } else {
1442
+ lines.push(`- Derived from \`${config.scaleRelationship.baseVar || "--font-base"}\``);
1443
+ if (config.scaleRelationship.multiplier !== void 0) {
1444
+ lines.push(`- Multiplier: ${config.scaleRelationship.multiplier}`);
1445
+ }
1446
+ }
1447
+ }
1448
+ lines.push("");
1449
+ lines.push("## Implementation Steps");
1450
+ lines.push("");
1451
+ lines.push("1. Add CSS variable to `src/app/globals.css` under `:root`:");
1452
+ lines.push("");
1453
+ lines.push("```css");
1454
+ if (config.scaleRelationship?.type === "derived" && config.scaleRelationship.multiplier) {
1455
+ const baseVar = config.scaleRelationship.baseVar || "--font-base";
1456
+ lines.push(`:root {`);
1457
+ for (const prop of config.properties) {
1458
+ lines.push(` --${config.tokenName}-${prop.property}: calc(var(${baseVar}) * ${config.scaleRelationship.multiplier});`);
1459
+ }
1460
+ lines.push(`}`);
1461
+ } else {
1462
+ lines.push(`:root {`);
1463
+ for (const prop of config.properties) {
1464
+ lines.push(` --${config.tokenName}-${prop.property}: ${prop.value};`);
1465
+ }
1466
+ lines.push(`}`);
1467
+ }
1468
+ lines.push("```");
1469
+ lines.push("");
1470
+ lines.push("2. Register in `@theme inline` block (if using Tailwind v4):");
1471
+ lines.push("");
1472
+ lines.push("```css");
1473
+ lines.push("@theme inline {");
1474
+ for (const prop of config.properties) {
1475
+ lines.push(` --${config.tokenName}-${prop.property}: var(--${config.tokenName}-${prop.property});`);
1476
+ }
1477
+ lines.push("}");
1478
+ lines.push("```");
1479
+ lines.push("");
1480
+ lines.push("3. Register in your DevLens config's `tokenOverrides`:");
1481
+ lines.push("");
1482
+ lines.push("```typescript");
1483
+ for (const prop of config.properties) {
1484
+ lines.push(`'--${config.tokenName}-${prop.property}': {`);
1485
+ lines.push(` label: '${kebabToTitle(config.tokenName)} ${kebabToTitle(prop.property)}',`);
1486
+ lines.push(` group: 'Typography Scale',`);
1487
+ lines.push(`},`);
1488
+ }
1489
+ lines.push("```");
1490
+ lines.push("");
1491
+ lines.push("4. Replace hardcoded values in components with the new token.");
1492
+ lines.push("");
1493
+ return lines.join("\n");
1494
+ }
1495
+ function kebabToTitle(s) {
1496
+ return s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1497
+ }
1498
+
1499
+ // src/components/TokenCreationZone.tsx
1500
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1501
+ var TYPE_OPTIONS = [
1502
+ { value: "hsl-color", label: "Color (HSL)", placeholder: "hsl(210 40% 50%)", defaultGroup: "Semantic Colors" },
1503
+ { value: "font-size", label: "Font Size", placeholder: "16px", defaultGroup: "Typography Scale" },
1504
+ { value: "length", label: "Length / Spacing", placeholder: "1rem", defaultGroup: "Spacing & Layout" },
1505
+ { value: "shadow", label: "Shadow", placeholder: "0 1px 3px 0 rgb(0 0 0 / 0.1)", defaultGroup: "Shadows" }
1506
+ ];
1507
+ function toKebab(str) {
1508
+ return str.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1509
+ }
1510
+ var DEFAULT_VALUES = {
1511
+ "hsl-color": "210 40% 50%",
1512
+ "font-size": "16px",
1513
+ "length": "1rem",
1514
+ "shadow": ""
1515
+ };
1516
+ var PROPERTY_FOR_TYPE = {
1517
+ "hsl-color": "color",
1518
+ "font-size": "font-size",
1519
+ "length": "padding",
1520
+ "shadow": "box-shadow"
1521
+ };
1522
+ function TokenCreationZone() {
1523
+ const [isOpen, setIsOpen] = useState6(false);
1524
+ const [tokenType, setTokenType] = useState6("hsl-color");
1525
+ const [tokenName, setTokenName] = useState6("");
1526
+ const [group, setGroup] = useState6(TYPE_OPTIONS[0].defaultGroup);
1527
+ const [value, setValue] = useState6(DEFAULT_VALUES["hsl-color"]);
1528
+ const [copied, setCopied] = useState6(false);
1529
+ const { tokenOverrides, tokenGroups: extraGroups } = useDevLensConfig();
1530
+ const registry = useMemo3(
1531
+ () => buildRegistry(autoDetectTokens(), tokenOverrides),
1532
+ [tokenOverrides]
1533
+ );
1534
+ const allGroups = useMemo3(() => {
1535
+ const groups = [...TOKEN_GROUPS];
1536
+ if (extraGroups) {
1537
+ for (const g of extraGroups) {
1538
+ if (!groups.includes(g)) groups.push(g);
1539
+ }
1540
+ }
1541
+ return groups;
1542
+ }, [extraGroups]);
1543
+ const kebabName = toKebab(tokenName);
1544
+ const currentTypeOption = TYPE_OPTIONS.find((t) => t.value === tokenType);
1545
+ const collision = useMemo3(() => {
1546
+ if (!kebabName) return false;
1547
+ return `--${kebabName}` in registry;
1548
+ }, [kebabName, registry]);
1549
+ const outputPreview = useMemo3(() => {
1550
+ if (!kebabName || !value) return "";
1551
+ const prop = PROPERTY_FOR_TYPE[tokenType];
1552
+ const lines = [];
1553
+ lines.push("/* :root */");
1554
+ lines.push(`--${kebabName}: ${value};`);
1555
+ lines.push("");
1556
+ lines.push("/* @theme inline */");
1557
+ if (tokenType === "font-size") {
1558
+ lines.push(`--font-size-${kebabName}: var(--${kebabName});`);
1559
+ } else {
1560
+ lines.push(`--${kebabName}: var(--${kebabName});`);
1561
+ }
1562
+ lines.push("");
1563
+ lines.push("/* token-registry.ts */");
1564
+ lines.push(`'--${kebabName}': { label: '...', group: '${group}', type: '${tokenType}' },`);
1565
+ return lines.join("\n");
1566
+ }, [kebabName, value, tokenType, group]);
1567
+ const handleTypeChange = useCallback9((newType) => {
1568
+ setTokenType(newType);
1569
+ const opt = TYPE_OPTIONS.find((t) => t.value === newType);
1570
+ setGroup(opt.defaultGroup);
1571
+ setValue(DEFAULT_VALUES[newType]);
1572
+ }, []);
1573
+ const handleNameChange = useCallback9((raw) => {
1574
+ setTokenName(toKebab(raw));
1575
+ }, []);
1576
+ const handleCopy = useCallback9(async () => {
1577
+ const prop = PROPERTY_FOR_TYPE[tokenType];
1578
+ const prompt = generateCCPrompt({
1579
+ tokenName: kebabName,
1580
+ properties: [{ property: prop, value }],
1581
+ context: "custom-css"
1582
+ });
1583
+ try {
1584
+ await navigator.clipboard.writeText(prompt);
1585
+ } catch {
1586
+ const textarea = document.createElement("textarea");
1587
+ textarea.value = prompt;
1588
+ textarea.style.position = "fixed";
1589
+ textarea.style.opacity = "0";
1590
+ document.body.appendChild(textarea);
1591
+ textarea.select();
1592
+ document.execCommand("copy");
1593
+ document.body.removeChild(textarea);
1594
+ }
1595
+ setCopied(true);
1596
+ setTimeout(() => setCopied(false), 2e3);
1597
+ }, [kebabName, tokenType, value]);
1598
+ const handleCancel = useCallback9(() => {
1599
+ setIsOpen(false);
1600
+ setTokenType("hsl-color");
1601
+ setTokenName("");
1602
+ setGroup(TYPE_OPTIONS[0].defaultGroup);
1603
+ setValue("");
1604
+ setCopied(false);
1605
+ }, []);
1606
+ const isValid = kebabName.length > 0 && !collision && value.length > 0;
1607
+ if (!isOpen) {
1608
+ return /* @__PURE__ */ jsx8("div", { className: "te-creation-zone", children: /* @__PURE__ */ jsx8(
1609
+ "button",
1610
+ {
1611
+ type: "button",
1612
+ className: "te-btn te-btn-secondary te-creation-trigger",
1613
+ onClick: () => setIsOpen(true),
1614
+ children: "+ New Token"
1615
+ }
1616
+ ) });
1617
+ }
1618
+ return /* @__PURE__ */ jsx8("div", { className: "te-creation-zone", children: /* @__PURE__ */ jsxs8("div", { className: "te-creation-form", children: [
1619
+ /* @__PURE__ */ jsxs8("div", { className: "te-creation-header", children: [
1620
+ /* @__PURE__ */ jsx8("span", { className: "te-creation-title", children: "Create Token" }),
1621
+ /* @__PURE__ */ jsx8(
1622
+ "button",
1623
+ {
1624
+ type: "button",
1625
+ className: "te-creation-cancel",
1626
+ onClick: handleCancel,
1627
+ children: "\u2190 Cancel"
1628
+ }
1629
+ )
1630
+ ] }),
1631
+ /* @__PURE__ */ jsxs8("div", { className: "te-creation-row", children: [
1632
+ /* @__PURE__ */ jsx8("label", { className: "te-creation-row-label", children: "Type" }),
1633
+ /* @__PURE__ */ jsx8(
1634
+ "select",
1635
+ {
1636
+ className: "te-select te-select-full",
1637
+ value: tokenType,
1638
+ onChange: (e) => handleTypeChange(e.target.value),
1639
+ children: TYPE_OPTIONS.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
1640
+ }
1641
+ )
1642
+ ] }),
1643
+ /* @__PURE__ */ jsxs8("div", { className: "te-creation-row", children: [
1644
+ /* @__PURE__ */ jsx8("label", { className: "te-creation-row-label", children: "Name" }),
1645
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", alignItems: "center", gap: 4, width: "100%" }, children: [
1646
+ /* @__PURE__ */ jsx8("span", { className: "te-muted", children: "--" }),
1647
+ /* @__PURE__ */ jsx8(
1648
+ "input",
1649
+ {
1650
+ type: "text",
1651
+ className: "te-text-input",
1652
+ value: tokenName,
1653
+ onChange: (e) => handleNameChange(e.target.value),
1654
+ placeholder: "my-custom-token",
1655
+ style: { flex: 1 }
1656
+ }
1657
+ )
1658
+ ] }),
1659
+ collision && /* @__PURE__ */ jsx8("div", { className: "te-token-form-error", children: "Token already exists in the registry" })
1660
+ ] }),
1661
+ /* @__PURE__ */ jsxs8("div", { className: "te-creation-row", children: [
1662
+ /* @__PURE__ */ jsx8("label", { className: "te-creation-row-label", children: "Group" }),
1663
+ /* @__PURE__ */ jsx8(
1664
+ "select",
1665
+ {
1666
+ className: "te-select te-select-full",
1667
+ value: group,
1668
+ onChange: (e) => setGroup(e.target.value),
1669
+ children: allGroups.map((g) => /* @__PURE__ */ jsx8("option", { value: g, children: g }, g))
1670
+ }
1671
+ )
1672
+ ] }),
1673
+ /* @__PURE__ */ jsxs8("div", { className: "te-creation-row", children: [
1674
+ /* @__PURE__ */ jsx8("label", { className: "te-creation-row-label", children: "Value" }),
1675
+ tokenType === "hsl-color" && /* @__PURE__ */ jsx8(
1676
+ HslColorControl,
1677
+ {
1678
+ label: /* @__PURE__ */ jsx8(Fragment2, {}),
1679
+ value,
1680
+ isModified: false,
1681
+ onChange: setValue,
1682
+ onReset: () => setValue(DEFAULT_VALUES["hsl-color"])
1683
+ }
1684
+ ),
1685
+ tokenType === "font-size" && /* @__PURE__ */ jsx8(
1686
+ LengthControl,
1687
+ {
1688
+ label: /* @__PURE__ */ jsx8(Fragment2, {}),
1689
+ value,
1690
+ isModified: false,
1691
+ onChange: setValue,
1692
+ onReset: () => setValue(DEFAULT_VALUES["font-size"]),
1693
+ isFontSize: true
1694
+ }
1695
+ ),
1696
+ tokenType === "length" && /* @__PURE__ */ jsx8(
1697
+ LengthControl,
1698
+ {
1699
+ label: /* @__PURE__ */ jsx8(Fragment2, {}),
1700
+ value,
1701
+ isModified: false,
1702
+ onChange: setValue,
1703
+ onReset: () => setValue(DEFAULT_VALUES["length"]),
1704
+ isFontSize: false
1705
+ }
1706
+ ),
1707
+ tokenType === "shadow" && /* @__PURE__ */ jsx8(
1708
+ "input",
1709
+ {
1710
+ type: "text",
1711
+ className: "te-text-input",
1712
+ value,
1713
+ onChange: (e) => setValue(e.target.value),
1714
+ placeholder: currentTypeOption.placeholder,
1715
+ style: { width: "100%" }
1716
+ }
1717
+ )
1718
+ ] }),
1719
+ kebabName && value && /* @__PURE__ */ jsxs8("div", { className: "te-creation-row", children: [
1720
+ /* @__PURE__ */ jsx8("label", { className: "te-creation-row-label", children: "Preview" }),
1721
+ /* @__PURE__ */ jsx8("pre", { className: "te-diff-content", style: { fontSize: 10 }, children: outputPreview })
1722
+ ] }),
1723
+ /* @__PURE__ */ jsx8("div", { className: "te-token-form-actions", children: /* @__PURE__ */ jsxs8(
1724
+ "button",
1725
+ {
1726
+ type: "button",
1727
+ className: "te-btn te-btn-primary",
1728
+ onClick: handleCopy,
1729
+ disabled: !isValid,
1730
+ style: !isValid ? { opacity: 0.5, cursor: "not-allowed" } : void 0,
1731
+ children: [
1732
+ copied ? /* @__PURE__ */ jsx8(Check, { size: 12 }) : /* @__PURE__ */ jsx8(Copy2, { size: 12 }),
1733
+ copied ? "Copied!" : "Copy as CC Prompt"
1734
+ ]
1735
+ }
1736
+ ) })
1737
+ ] }) });
1738
+ }
1739
+
1740
+ // src/components/TokenEditorDiffOutput.tsx
1741
+ import { useCallback as useCallback10, useState as useState7 } from "react";
1742
+ import { Copy as Copy3, Check as Check2, RotateCcw as RotateCcw5 } from "lucide-react";
1743
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1744
+ function TokenEditorDiffOutput({
1745
+ diff,
1746
+ changeCount,
1747
+ onResetAll,
1748
+ classDiff,
1749
+ classChangeCount = 0,
1750
+ onResetAllClasses,
1751
+ customStyleDiff,
1752
+ customStyleChangeCount = 0,
1753
+ onResetCustomStyles
1754
+ }) {
1755
+ const [copiedToken, setCopiedToken] = useState7(false);
1756
+ const [copiedClass, setCopiedClass] = useState7(false);
1757
+ const [copiedCustom, setCopiedCustom] = useState7(false);
1758
+ const [copiedAll, setCopiedAll] = useState7(false);
1759
+ const [outputFormat, setOutputFormat] = useState7("diff");
1760
+ const totalCount = changeCount + classChangeCount + customStyleChangeCount;
1761
+ const typeCount = (changeCount > 0 ? 1 : 0) + (classChangeCount > 0 ? 1 : 0) + (customStyleChangeCount > 0 ? 1 : 0);
1762
+ const hasBothTypes = typeCount >= 2;
1763
+ const copyToClipboard = useCallback10(async (text) => {
1764
+ try {
1765
+ await navigator.clipboard.writeText(text);
1766
+ } catch {
1767
+ const textarea = document.createElement("textarea");
1768
+ textarea.value = text;
1769
+ textarea.style.position = "fixed";
1770
+ textarea.style.opacity = "0";
1771
+ document.body.appendChild(textarea);
1772
+ textarea.select();
1773
+ document.execCommand("copy");
1774
+ document.body.removeChild(textarea);
1775
+ }
1776
+ }, []);
1777
+ const handleCopyTokens = useCallback10(async () => {
1778
+ await copyToClipboard(diff);
1779
+ setCopiedToken(true);
1780
+ setTimeout(() => setCopiedToken(false), 2e3);
1781
+ }, [diff, copyToClipboard]);
1782
+ const handleCopyClasses = useCallback10(async () => {
1783
+ await copyToClipboard(classDiff || "");
1784
+ setCopiedClass(true);
1785
+ setTimeout(() => setCopiedClass(false), 2e3);
1786
+ }, [classDiff, copyToClipboard]);
1787
+ const handleCopyCustom = useCallback10(async () => {
1788
+ await copyToClipboard(customStyleDiff || "");
1789
+ setCopiedCustom(true);
1790
+ setTimeout(() => setCopiedCustom(false), 2e3);
1791
+ }, [customStyleDiff, copyToClipboard]);
1792
+ const generateCCPromptFromDiffs = useCallback10(() => {
1793
+ const sections = [];
1794
+ let sectionNum = 1;
1795
+ sections.push("# CC Prompt: Design Token & Class Changes");
1796
+ sections.push("");
1797
+ sections.push("## Context");
1798
+ sections.push("Apply the following design token and class changes to `src/app/globals.css` and related component files.");
1799
+ sections.push("");
1800
+ if (diff && changeCount > 0) {
1801
+ sections.push(`## ${sectionNum}. Token Changes (${changeCount})`);
1802
+ sections.push("");
1803
+ sections.push("Update the following CSS custom properties in `globals.css`:");
1804
+ sections.push("");
1805
+ sections.push("```css");
1806
+ sections.push(diff);
1807
+ sections.push("```");
1808
+ sections.push("");
1809
+ sectionNum++;
1810
+ }
1811
+ if (classDiff && classChangeCount > 0) {
1812
+ sections.push(`## ${sectionNum}. Class Changes (${classChangeCount})`);
1813
+ sections.push("");
1814
+ sections.push("Apply the following Tailwind class modifications:");
1815
+ sections.push("");
1816
+ sections.push("```diff");
1817
+ sections.push(classDiff);
1818
+ sections.push("```");
1819
+ sections.push("");
1820
+ sectionNum++;
1821
+ }
1822
+ if (customStyleDiff && customStyleChangeCount > 0) {
1823
+ sections.push(`## ${sectionNum}. Custom Style Changes (${customStyleChangeCount})`);
1824
+ sections.push("");
1825
+ sections.push("Apply the following inline/custom style changes:");
1826
+ sections.push("");
1827
+ sections.push("```css");
1828
+ sections.push(customStyleDiff);
1829
+ sections.push("```");
1830
+ sections.push("");
1831
+ sectionNum++;
1832
+ }
1833
+ sections.push("## Validation Checklist");
1834
+ sections.push("");
1835
+ sections.push("- [ ] All token values updated in `globals.css`");
1836
+ if (classChangeCount > 0) sections.push("- [ ] Class changes applied to target components");
1837
+ if (customStyleChangeCount > 0) sections.push("- [ ] Custom style overrides applied");
1838
+ sections.push("- [ ] `npm run build` passes");
1839
+ sections.push("- [ ] Visual regression check in browser");
1840
+ return sections.join("\n");
1841
+ }, [diff, classDiff, customStyleDiff, changeCount, classChangeCount, customStyleChangeCount]);
1842
+ const handleCopyAll = useCallback10(async () => {
1843
+ const content = outputFormat === "cc-prompt" ? generateCCPromptFromDiffs() : [diff, classDiff, customStyleDiff].filter(Boolean).join("\n\n---\n\n");
1844
+ await copyToClipboard(content);
1845
+ setCopiedAll(true);
1846
+ setTimeout(() => setCopiedAll(false), 2e3);
1847
+ }, [diff, classDiff, customStyleDiff, copyToClipboard, outputFormat, generateCCPromptFromDiffs]);
1848
+ if (totalCount === 0) {
1849
+ return /* @__PURE__ */ jsx9("div", { className: "te-diff-empty", children: "No changes yet. Modify tokens or inspect elements to see a diff." });
1850
+ }
1851
+ if (!hasBothTypes) {
1852
+ const isTokens = changeCount > 0;
1853
+ const isCustom = !isTokens && customStyleChangeCount > 0;
1854
+ const content = isTokens ? diff : isCustom ? customStyleDiff || "" : classDiff || "";
1855
+ const label = isTokens ? "token" : isCustom ? "custom style" : "class";
1856
+ const count = isTokens ? changeCount : isCustom ? customStyleChangeCount : classChangeCount;
1857
+ return /* @__PURE__ */ jsxs9("div", { className: "te-diff", children: [
1858
+ /* @__PURE__ */ jsxs9("div", { className: "te-diff-header", children: [
1859
+ /* @__PURE__ */ jsxs9("span", { className: "te-diff-title", children: [
1860
+ count,
1861
+ " ",
1862
+ label,
1863
+ " change",
1864
+ count !== 1 ? "s" : ""
1865
+ ] }),
1866
+ /* @__PURE__ */ jsxs9("div", { className: "te-diff-actions", children: [
1867
+ isTokens && /* @__PURE__ */ jsxs9(
1868
+ "button",
1869
+ {
1870
+ type: "button",
1871
+ className: "te-btn te-btn-secondary",
1872
+ onClick: onResetAll,
1873
+ children: [
1874
+ /* @__PURE__ */ jsx9(RotateCcw5, { size: 12 }),
1875
+ "Reset All"
1876
+ ]
1877
+ }
1878
+ ),
1879
+ !isTokens && !isCustom && onResetAllClasses && /* @__PURE__ */ jsxs9(
1880
+ "button",
1881
+ {
1882
+ type: "button",
1883
+ className: "te-btn te-btn-secondary",
1884
+ onClick: onResetAllClasses,
1885
+ children: [
1886
+ /* @__PURE__ */ jsx9(RotateCcw5, { size: 12 }),
1887
+ "Reset All"
1888
+ ]
1889
+ }
1890
+ ),
1891
+ isCustom && onResetCustomStyles && /* @__PURE__ */ jsxs9(
1892
+ "button",
1893
+ {
1894
+ type: "button",
1895
+ className: "te-btn te-btn-secondary",
1896
+ onClick: onResetCustomStyles,
1897
+ children: [
1898
+ /* @__PURE__ */ jsx9(RotateCcw5, { size: 12 }),
1899
+ "Reset All"
1900
+ ]
1901
+ }
1902
+ ),
1903
+ /* @__PURE__ */ jsxs9(
1904
+ "button",
1905
+ {
1906
+ type: "button",
1907
+ className: "te-btn te-btn-primary",
1908
+ onClick: handleCopyAll,
1909
+ children: [
1910
+ copiedAll ? /* @__PURE__ */ jsx9(Check2, { size: 12 }) : /* @__PURE__ */ jsx9(Copy3, { size: 12 }),
1911
+ copiedAll ? "Copied!" : outputFormat === "cc-prompt" ? "Copy as CC Prompt" : "Copy Changes"
1912
+ ]
1913
+ }
1914
+ )
1915
+ ] })
1916
+ ] }),
1917
+ /* @__PURE__ */ jsxs9("div", { className: "te-format-toggle", children: [
1918
+ /* @__PURE__ */ jsx9(
1919
+ "button",
1920
+ {
1921
+ type: "button",
1922
+ className: `te-format-btn ${outputFormat === "diff" ? "te-format-active" : ""}`,
1923
+ onClick: () => setOutputFormat("diff"),
1924
+ children: "Diff"
1925
+ }
1926
+ ),
1927
+ /* @__PURE__ */ jsx9(
1928
+ "button",
1929
+ {
1930
+ type: "button",
1931
+ className: `te-format-btn ${outputFormat === "cc-prompt" ? "te-format-active" : ""}`,
1932
+ onClick: () => setOutputFormat("cc-prompt"),
1933
+ children: "CC Prompt"
1934
+ }
1935
+ )
1936
+ ] }),
1937
+ /* @__PURE__ */ jsx9("pre", { className: "te-diff-content", children: content })
1938
+ ] });
1939
+ }
1940
+ return /* @__PURE__ */ jsxs9("div", { className: "te-diff", children: [
1941
+ /* @__PURE__ */ jsx9("div", { className: "te-diff-header", children: /* @__PURE__ */ jsxs9("span", { className: "te-diff-title", children: [
1942
+ totalCount,
1943
+ " change",
1944
+ totalCount !== 1 ? "s" : ""
1945
+ ] }) }),
1946
+ /* @__PURE__ */ jsxs9("div", { className: "te-format-toggle", children: [
1947
+ /* @__PURE__ */ jsx9(
1948
+ "button",
1949
+ {
1950
+ type: "button",
1951
+ className: `te-format-btn ${outputFormat === "diff" ? "te-format-active" : ""}`,
1952
+ onClick: () => setOutputFormat("diff"),
1953
+ children: "Diff"
1954
+ }
1955
+ ),
1956
+ /* @__PURE__ */ jsx9(
1957
+ "button",
1958
+ {
1959
+ type: "button",
1960
+ className: `te-format-btn ${outputFormat === "cc-prompt" ? "te-format-active" : ""}`,
1961
+ onClick: () => setOutputFormat("cc-prompt"),
1962
+ children: "CC Prompt"
1963
+ }
1964
+ )
1965
+ ] }),
1966
+ /* @__PURE__ */ jsxs9("div", { className: "te-diff-section", children: [
1967
+ /* @__PURE__ */ jsxs9("div", { className: "te-diff-section-title", children: [
1968
+ "Token Changes (",
1969
+ changeCount,
1970
+ ")",
1971
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 4, marginLeft: 8 }, children: [
1972
+ /* @__PURE__ */ jsxs9(
1973
+ "button",
1974
+ {
1975
+ type: "button",
1976
+ className: "te-btn te-btn-secondary",
1977
+ onClick: onResetAll,
1978
+ style: { padding: "2px 8px", fontSize: 10 },
1979
+ children: [
1980
+ /* @__PURE__ */ jsx9(RotateCcw5, { size: 10 }),
1981
+ "Reset"
1982
+ ]
1983
+ }
1984
+ ),
1985
+ /* @__PURE__ */ jsxs9(
1986
+ "button",
1987
+ {
1988
+ type: "button",
1989
+ className: "te-btn te-btn-primary",
1990
+ onClick: handleCopyTokens,
1991
+ style: { padding: "2px 8px", fontSize: 10 },
1992
+ children: [
1993
+ copiedToken ? /* @__PURE__ */ jsx9(Check2, { size: 10 }) : /* @__PURE__ */ jsx9(Copy3, { size: 10 }),
1994
+ copiedToken ? "Copied!" : "Copy"
1995
+ ]
1996
+ }
1997
+ )
1998
+ ] })
1999
+ ] }),
2000
+ /* @__PURE__ */ jsx9("pre", { className: "te-diff-content", children: diff })
2001
+ ] }),
2002
+ /* @__PURE__ */ jsx9("div", { className: "te-diff-separator" }),
2003
+ /* @__PURE__ */ jsxs9("div", { className: "te-diff-section", children: [
2004
+ /* @__PURE__ */ jsxs9("div", { className: "te-diff-section-title", children: [
2005
+ "Class Changes (",
2006
+ classChangeCount,
2007
+ ")",
2008
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 4, marginLeft: 8 }, children: [
2009
+ onResetAllClasses && /* @__PURE__ */ jsxs9(
2010
+ "button",
2011
+ {
2012
+ type: "button",
2013
+ className: "te-btn te-btn-secondary",
2014
+ onClick: onResetAllClasses,
2015
+ style: { padding: "2px 8px", fontSize: 10 },
2016
+ children: [
2017
+ /* @__PURE__ */ jsx9(RotateCcw5, { size: 10 }),
2018
+ "Reset"
2019
+ ]
2020
+ }
2021
+ ),
2022
+ /* @__PURE__ */ jsxs9(
2023
+ "button",
2024
+ {
2025
+ type: "button",
2026
+ className: "te-btn te-btn-primary",
2027
+ onClick: handleCopyClasses,
2028
+ style: { padding: "2px 8px", fontSize: 10 },
2029
+ children: [
2030
+ copiedClass ? /* @__PURE__ */ jsx9(Check2, { size: 10 }) : /* @__PURE__ */ jsx9(Copy3, { size: 10 }),
2031
+ copiedClass ? "Copied!" : "Copy"
2032
+ ]
2033
+ }
2034
+ )
2035
+ ] })
2036
+ ] }),
2037
+ /* @__PURE__ */ jsx9("pre", { className: "te-diff-content", children: classDiff })
2038
+ ] }),
2039
+ customStyleChangeCount > 0 && /* @__PURE__ */ jsxs9(Fragment3, { children: [
2040
+ /* @__PURE__ */ jsx9("div", { className: "te-diff-separator" }),
2041
+ /* @__PURE__ */ jsxs9("div", { className: "te-diff-section", children: [
2042
+ /* @__PURE__ */ jsxs9("div", { className: "te-diff-section-title", children: [
2043
+ "Custom Style Changes (",
2044
+ customStyleChangeCount,
2045
+ ")",
2046
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 4, marginLeft: 8 }, children: [
2047
+ onResetCustomStyles && /* @__PURE__ */ jsxs9(
2048
+ "button",
2049
+ {
2050
+ type: "button",
2051
+ className: "te-btn te-btn-secondary",
2052
+ onClick: onResetCustomStyles,
2053
+ style: { padding: "2px 8px", fontSize: 10 },
2054
+ children: [
2055
+ /* @__PURE__ */ jsx9(RotateCcw5, { size: 10 }),
2056
+ "Reset"
2057
+ ]
2058
+ }
2059
+ ),
2060
+ /* @__PURE__ */ jsxs9(
2061
+ "button",
2062
+ {
2063
+ type: "button",
2064
+ className: "te-btn te-btn-primary",
2065
+ onClick: handleCopyCustom,
2066
+ style: { padding: "2px 8px", fontSize: 10 },
2067
+ children: [
2068
+ copiedCustom ? /* @__PURE__ */ jsx9(Check2, { size: 10 }) : /* @__PURE__ */ jsx9(Copy3, { size: 10 }),
2069
+ copiedCustom ? "Copied!" : "Copy"
2070
+ ]
2071
+ }
2072
+ )
2073
+ ] })
2074
+ ] }),
2075
+ /* @__PURE__ */ jsx9("pre", { className: "te-diff-content", children: customStyleDiff })
2076
+ ] })
2077
+ ] }),
2078
+ /* @__PURE__ */ jsxs9(
2079
+ "button",
2080
+ {
2081
+ type: "button",
2082
+ className: "te-btn te-btn-primary te-diff-copy-all",
2083
+ onClick: handleCopyAll,
2084
+ children: [
2085
+ copiedAll ? /* @__PURE__ */ jsx9(Check2, { size: 12 }) : /* @__PURE__ */ jsx9(Copy3, { size: 12 }),
2086
+ copiedAll ? "Copied All!" : outputFormat === "cc-prompt" ? "Copy as CC Prompt" : "Copy All Changes"
2087
+ ]
2088
+ }
2089
+ )
2090
+ ] });
2091
+ }
2092
+
2093
+ // src/hooks/use-element-inspector.ts
2094
+ import { useCallback as useCallback11, useMemo as useMemo4, useRef as useRef5, useState as useState8 } from "react";
2095
+ function useElementInspector() {
2096
+ const [state, setState] = useState8({
2097
+ isSelectorActive: false,
2098
+ selectedElement: null,
2099
+ originalClasses: [],
2100
+ currentClasses: [],
2101
+ classChanges: [],
2102
+ cachedIdentifier: null,
2103
+ customStyles: [],
2104
+ originalInlineStyles: {}
2105
+ });
2106
+ const elementRef = useRef5(null);
2107
+ const toggleSelector = useCallback11(() => {
2108
+ setState((prev) => ({
2109
+ ...prev,
2110
+ isSelectorActive: !prev.isSelectorActive
2111
+ }));
2112
+ }, []);
2113
+ const activateSelector = useCallback11(() => {
2114
+ setState((prev) => ({
2115
+ ...prev,
2116
+ isSelectorActive: true
2117
+ }));
2118
+ }, []);
2119
+ const deactivateSelector = useCallback11(() => {
2120
+ setState((prev) => ({
2121
+ ...prev,
2122
+ isSelectorActive: false
2123
+ }));
2124
+ }, []);
2125
+ const computeIdentifier = useCallback11((el) => {
2126
+ const tagName = el.tagName.toLowerCase();
2127
+ const rawText = el.textContent || "";
2128
+ const textPreview = rawText.length > 60 ? rawText.slice(0, 60).trim() + "\u2026" : rawText.trim();
2129
+ const breadcrumb = [];
2130
+ let current = el;
2131
+ for (let i = 0; i < 5 && current; i++) {
2132
+ const tag = current.tagName.toLowerCase();
2133
+ const firstClass = Array.from(current.classList).find((c) => !c.startsWith("te-"));
2134
+ const segment = firstClass ? `${tag}.${firstClass}` : tag;
2135
+ breadcrumb.unshift(segment);
2136
+ current = current.parentElement;
2137
+ }
2138
+ const testId = el.getAttribute("data-testid") || void 0;
2139
+ const id = el.id || void 0;
2140
+ return { tagName, textPreview, breadcrumb, testId, id };
2141
+ }, []);
2142
+ const selectElement = useCallback11((el) => {
2143
+ elementRef.current = el;
2144
+ const classes = Array.from(el.classList);
2145
+ const identifier = computeIdentifier(el);
2146
+ setState({
2147
+ isSelectorActive: false,
2148
+ selectedElement: el,
2149
+ originalClasses: [...classes],
2150
+ currentClasses: [...classes],
2151
+ classChanges: [],
2152
+ cachedIdentifier: identifier,
2153
+ customStyles: [],
2154
+ originalInlineStyles: {}
2155
+ });
2156
+ }, [computeIdentifier]);
2157
+ const clearSelection = useCallback11(() => {
2158
+ elementRef.current = null;
2159
+ setState({
2160
+ isSelectorActive: false,
2161
+ selectedElement: null,
2162
+ originalClasses: [],
2163
+ currentClasses: [],
2164
+ classChanges: [],
2165
+ cachedIdentifier: null,
2166
+ customStyles: [],
2167
+ originalInlineStyles: {}
2168
+ });
2169
+ }, []);
2170
+ const addClass = useCallback11((className) => {
2171
+ const el = elementRef.current;
2172
+ if (!el || !className.trim()) return;
2173
+ const cls = className.trim();
2174
+ el.classList.add(cls);
2175
+ setState((prev) => {
2176
+ if (prev.originalClasses.includes(cls)) {
2177
+ return {
2178
+ ...prev,
2179
+ currentClasses: Array.from(el.classList)
2180
+ };
2181
+ }
2182
+ const existing = prev.classChanges.find(
2183
+ (c) => c.type === "added" && c.current === cls
2184
+ );
2185
+ if (existing) {
2186
+ return { ...prev, currentClasses: Array.from(el.classList) };
2187
+ }
2188
+ const removedIdx = prev.classChanges.findIndex(
2189
+ (c) => c.type === "removed" && c.original === cls
2190
+ );
2191
+ if (removedIdx >= 0) {
2192
+ const newChanges = [...prev.classChanges];
2193
+ newChanges.splice(removedIdx, 1);
2194
+ return {
2195
+ ...prev,
2196
+ currentClasses: Array.from(el.classList),
2197
+ classChanges: newChanges
2198
+ };
2199
+ }
2200
+ return {
2201
+ ...prev,
2202
+ currentClasses: Array.from(el.classList),
2203
+ classChanges: [
2204
+ ...prev.classChanges,
2205
+ { type: "added", current: cls }
2206
+ ]
2207
+ };
2208
+ });
2209
+ }, []);
2210
+ const removeClass = useCallback11((className) => {
2211
+ const el = elementRef.current;
2212
+ if (!el) return;
2213
+ el.classList.remove(className);
2214
+ setState((prev) => {
2215
+ const addedIdx = prev.classChanges.findIndex(
2216
+ (c) => c.type === "added" && c.current === className
2217
+ );
2218
+ if (addedIdx >= 0) {
2219
+ const newChanges = [...prev.classChanges];
2220
+ newChanges.splice(addedIdx, 1);
2221
+ return {
2222
+ ...prev,
2223
+ currentClasses: Array.from(el.classList),
2224
+ classChanges: newChanges
2225
+ };
2226
+ }
2227
+ const modifiedIdx = prev.classChanges.findIndex(
2228
+ (c) => c.type === "modified" && c.current === className
2229
+ );
2230
+ if (modifiedIdx >= 0) {
2231
+ const newChanges = [...prev.classChanges];
2232
+ const original = newChanges[modifiedIdx].original;
2233
+ newChanges[modifiedIdx] = { type: "removed", original };
2234
+ return {
2235
+ ...prev,
2236
+ currentClasses: Array.from(el.classList),
2237
+ classChanges: newChanges
2238
+ };
2239
+ }
2240
+ if (prev.originalClasses.includes(className)) {
2241
+ return {
2242
+ ...prev,
2243
+ currentClasses: Array.from(el.classList),
2244
+ classChanges: [
2245
+ ...prev.classChanges,
2246
+ { type: "removed", original: className }
2247
+ ]
2248
+ };
2249
+ }
2250
+ return { ...prev, currentClasses: Array.from(el.classList) };
2251
+ });
2252
+ }, []);
2253
+ const updateClass = useCallback11((oldClass, newClass) => {
2254
+ const el = elementRef.current;
2255
+ if (!el) return;
2256
+ const trimmed = newClass.trim();
2257
+ if (!trimmed) {
2258
+ el.classList.remove(oldClass);
2259
+ setState((prev) => {
2260
+ const existingIdx = prev.classChanges.findIndex(
2261
+ (c) => c.type === "modified" && c.current === oldClass || c.type === "added" && c.current === oldClass
2262
+ );
2263
+ if (existingIdx >= 0) {
2264
+ const change = prev.classChanges[existingIdx];
2265
+ const newChanges = [...prev.classChanges];
2266
+ if (change.type === "added") {
2267
+ newChanges.splice(existingIdx, 1);
2268
+ } else {
2269
+ newChanges[existingIdx] = { type: "removed", original: change.original };
2270
+ }
2271
+ return {
2272
+ ...prev,
2273
+ currentClasses: Array.from(el.classList),
2274
+ classChanges: newChanges
2275
+ };
2276
+ }
2277
+ if (prev.originalClasses.includes(oldClass)) {
2278
+ return {
2279
+ ...prev,
2280
+ currentClasses: Array.from(el.classList),
2281
+ classChanges: [...prev.classChanges, { type: "removed", original: oldClass }]
2282
+ };
2283
+ }
2284
+ return { ...prev, currentClasses: Array.from(el.classList) };
2285
+ });
2286
+ return;
2287
+ }
2288
+ if (oldClass === trimmed) return;
2289
+ el.classList.remove(oldClass);
2290
+ el.classList.add(trimmed);
2291
+ setState((prev) => {
2292
+ const newChanges = [...prev.classChanges];
2293
+ const existingIdx = newChanges.findIndex(
2294
+ (c) => c.type === "modified" && c.current === oldClass || c.type === "added" && c.current === oldClass
2295
+ );
2296
+ if (existingIdx >= 0) {
2297
+ const change = newChanges[existingIdx];
2298
+ if (change.type === "added") {
2299
+ newChanges[existingIdx] = { type: "added", current: trimmed };
2300
+ } else {
2301
+ if (change.original === trimmed) {
2302
+ newChanges.splice(existingIdx, 1);
2303
+ } else {
2304
+ newChanges[existingIdx] = {
2305
+ type: "modified",
2306
+ original: change.original,
2307
+ current: trimmed
2308
+ };
2309
+ }
2310
+ }
2311
+ } else if (prev.originalClasses.includes(oldClass)) {
2312
+ newChanges.push({
2313
+ type: "modified",
2314
+ original: oldClass,
2315
+ current: trimmed
2316
+ });
2317
+ } else {
2318
+ newChanges.push({ type: "added", current: trimmed });
2319
+ }
2320
+ return {
2321
+ ...prev,
2322
+ currentClasses: Array.from(el.classList),
2323
+ classChanges: newChanges
2324
+ };
2325
+ });
2326
+ }, []);
2327
+ const addCustomStyle = useCallback11((property, value) => {
2328
+ const el = elementRef.current;
2329
+ if (!el) return;
2330
+ setState((prev) => {
2331
+ const originals = { ...prev.originalInlineStyles };
2332
+ if (!(property in originals)) {
2333
+ originals[property] = el.style.getPropertyValue(property);
2334
+ }
2335
+ el.style.setProperty(property, value);
2336
+ const existing = prev.customStyles.findIndex((s) => s.property === property);
2337
+ const newStyles = [...prev.customStyles];
2338
+ if (existing >= 0) {
2339
+ newStyles[existing] = { property, value };
2340
+ } else {
2341
+ newStyles.push({ property, value });
2342
+ }
2343
+ return { ...prev, customStyles: newStyles, originalInlineStyles: originals };
2344
+ });
2345
+ }, []);
2346
+ const removeCustomStyle = useCallback11((property) => {
2347
+ const el = elementRef.current;
2348
+ if (!el) return;
2349
+ setState((prev) => {
2350
+ const original = prev.originalInlineStyles[property];
2351
+ if (original) {
2352
+ el.style.setProperty(property, original);
2353
+ } else {
2354
+ el.style.removeProperty(property);
2355
+ }
2356
+ const newStyles = prev.customStyles.filter((s) => s.property !== property);
2357
+ const originals = { ...prev.originalInlineStyles };
2358
+ delete originals[property];
2359
+ return { ...prev, customStyles: newStyles, originalInlineStyles: originals };
2360
+ });
2361
+ }, []);
2362
+ const clearCustomStyles = useCallback11(() => {
2363
+ const el = elementRef.current;
2364
+ if (!el) return;
2365
+ setState((prev) => {
2366
+ for (const entry of prev.customStyles) {
2367
+ const original = prev.originalInlineStyles[entry.property];
2368
+ if (original) {
2369
+ el.style.setProperty(entry.property, original);
2370
+ } else {
2371
+ el.style.removeProperty(entry.property);
2372
+ }
2373
+ }
2374
+ return { ...prev, customStyles: [], originalInlineStyles: {} };
2375
+ });
2376
+ }, []);
2377
+ const getElementIdentifier = useCallback11(() => {
2378
+ if (state.cachedIdentifier) return state.cachedIdentifier;
2379
+ const el = elementRef.current;
2380
+ if (!el) return null;
2381
+ return computeIdentifier(el);
2382
+ }, [state.cachedIdentifier, computeIdentifier]);
2383
+ const classDiffText = useMemo4(() => {
2384
+ if (state.classChanges.length === 0) return "";
2385
+ const identifier = state.cachedIdentifier;
2386
+ if (!identifier) return "";
2387
+ const lines = ["## Class Changes", ""];
2388
+ lines.push(
2389
+ `**Element:** <${identifier.tagName}> "${identifier.textPreview}"${identifier.testId ? ` (data-testid="${identifier.testId}")` : ""}${identifier.id ? ` (id="${identifier.id}")` : ""}`
2390
+ );
2391
+ lines.push(`**Path:** ${identifier.breadcrumb.join(" \u203A ")}`);
2392
+ lines.push("");
2393
+ lines.push("| Action | Class |");
2394
+ lines.push("|--------|-------|");
2395
+ for (const change of state.classChanges) {
2396
+ switch (change.type) {
2397
+ case "added":
2398
+ lines.push(`| + Added | \`${change.current}\` |`);
2399
+ break;
2400
+ case "removed":
2401
+ lines.push(`| - Removed | \`${change.original}\` |`);
2402
+ break;
2403
+ case "modified":
2404
+ lines.push(
2405
+ `| ~ Modified | \`${change.original}\` \u2192 \`${change.current}\` |`
2406
+ );
2407
+ break;
2408
+ }
2409
+ }
2410
+ return lines.join("\n");
2411
+ }, [state.classChanges, state.cachedIdentifier]);
2412
+ const customStyleDiffText = useMemo4(() => {
2413
+ if (state.customStyles.length === 0) return "";
2414
+ const identifier = state.cachedIdentifier;
2415
+ if (!identifier) return "";
2416
+ const lines = ["## Custom Style Changes", ""];
2417
+ lines.push(
2418
+ `**Element:** <${identifier.tagName}> "${identifier.textPreview}"${identifier.testId ? ` (data-testid="${identifier.testId}")` : ""}${identifier.id ? ` (id="${identifier.id}")` : ""}`
2419
+ );
2420
+ lines.push(`**Path:** ${identifier.breadcrumb.join(" \u203A ")}`);
2421
+ lines.push("");
2422
+ lines.push("| Property | Value |");
2423
+ lines.push("|----------|-------|");
2424
+ for (const entry of state.customStyles) {
2425
+ lines.push(`| \`${entry.property}\` | \`${entry.value}\` |`);
2426
+ }
2427
+ return lines.join("\n");
2428
+ }, [state.customStyles, state.cachedIdentifier]);
2429
+ const customStyleChangeCount = state.customStyles.length;
2430
+ const changeCount = state.classChanges.length;
2431
+ return {
2432
+ // State
2433
+ isSelectorActive: state.isSelectorActive,
2434
+ selectedElement: state.selectedElement,
2435
+ originalClasses: state.originalClasses,
2436
+ currentClasses: state.currentClasses,
2437
+ classChanges: state.classChanges,
2438
+ customStyles: state.customStyles,
2439
+ changeCount,
2440
+ customStyleChangeCount,
2441
+ // Actions
2442
+ toggleSelector,
2443
+ activateSelector,
2444
+ deactivateSelector,
2445
+ selectElement,
2446
+ clearSelection,
2447
+ addClass,
2448
+ removeClass,
2449
+ updateClass,
2450
+ addCustomStyle,
2451
+ removeCustomStyle,
2452
+ clearCustomStyles,
2453
+ // Derived
2454
+ getElementIdentifier,
2455
+ classDiffText,
2456
+ customStyleDiffText
2457
+ };
2458
+ }
2459
+
2460
+ // src/hooks/use-scale-baseline.ts
2461
+ import { useCallback as useCallback12, useEffect as useEffect6, useRef as useRef6, useState as useState9 } from "react";
2462
+
2463
+ // src/core/scale-baseline.ts
2464
+ function buildBaselineCSS(config) {
2465
+ if (!config.mapping || config.mapping.length === 0) return "";
2466
+ const rules = [];
2467
+ for (const m of config.mapping) {
2468
+ const declarations = [
2469
+ `font-size: var(${m.fontSizeVar});`,
2470
+ `line-height: var(${m.lineHeightVar});`
2471
+ ];
2472
+ if (m.fontWeight !== void 0) {
2473
+ declarations.push(`font-weight: ${m.fontWeight};`);
2474
+ }
2475
+ rules.push(`${m.selector} {
2476
+ ${declarations.join("\n ")}
2477
+ }`);
2478
+ }
2479
+ return rules.join("\n\n");
2480
+ }
2481
+
2482
+ // src/hooks/use-scale-baseline.ts
2483
+ function useScaleBaseline() {
2484
+ const { scaleBaseline, namespace } = useDevLensConfig();
2485
+ const [isActive, setIsActive] = useState9(false);
2486
+ const styleRef = useRef6(null);
2487
+ const hasBaseline = !!scaleBaseline && !!scaleBaseline.mapping && scaleBaseline.mapping.length > 0;
2488
+ const toggle = useCallback12(() => {
2489
+ setIsActive((prev) => !prev);
2490
+ }, []);
2491
+ useEffect6(() => {
2492
+ if (isActive && hasBaseline) {
2493
+ const css = buildBaselineCSS(scaleBaseline);
2494
+ if (!css) return;
2495
+ const style = document.createElement("style");
2496
+ style.setAttribute("data-devlens", `${namespace}-scale-baseline`);
2497
+ style.textContent = css;
2498
+ document.head.appendChild(style);
2499
+ styleRef.current = style;
2500
+ return () => {
2501
+ if (styleRef.current) {
2502
+ styleRef.current.remove();
2503
+ styleRef.current = null;
2504
+ }
2505
+ };
2506
+ } else if (styleRef.current) {
2507
+ styleRef.current.remove();
2508
+ styleRef.current = null;
2509
+ }
2510
+ }, [isActive, hasBaseline, scaleBaseline, namespace]);
2511
+ return { isActive, toggle, hasBaseline };
2512
+ }
2513
+
2514
+ // src/components/ElementInspectorTab.tsx
2515
+ import { useCallback as useCallback17, useEffect as useEffect11, useMemo as useMemo8, useRef as useRef11, useState as useState16 } from "react";
2516
+ import { MousePointer } from "lucide-react";
2517
+
2518
+ // src/core/class-categories.ts
2519
+ var TEXT_SIZE_KEYWORDS = /* @__PURE__ */ new Set([
2520
+ "text-xs",
2521
+ "text-sm",
2522
+ "text-base",
2523
+ "text-lg",
2524
+ "text-xl",
2525
+ "text-2xl",
2526
+ "text-3xl",
2527
+ "text-4xl",
2528
+ "text-5xl",
2529
+ "text-6xl",
2530
+ "text-7xl",
2531
+ "text-8xl",
2532
+ "text-9xl"
2533
+ ]);
2534
+ var TEXT_ALIGN_KEYWORDS = /* @__PURE__ */ new Set([
2535
+ "text-left",
2536
+ "text-center",
2537
+ "text-right",
2538
+ "text-justify",
2539
+ "text-start",
2540
+ "text-end"
2541
+ ]);
2542
+ var TEXT_DECORATION_KEYWORDS = /* @__PURE__ */ new Set([
2543
+ "underline",
2544
+ "overline",
2545
+ "line-through",
2546
+ "no-underline"
2547
+ ]);
2548
+ var TEXT_TRANSFORM_KEYWORDS = /* @__PURE__ */ new Set([
2549
+ "uppercase",
2550
+ "lowercase",
2551
+ "capitalize",
2552
+ "normal-case"
2553
+ ]);
2554
+ var TEXT_OVERFLOW_KEYWORDS = /* @__PURE__ */ new Set([
2555
+ "truncate",
2556
+ "text-ellipsis",
2557
+ "text-clip"
2558
+ ]);
2559
+ var TEXT_WRAP_KEYWORDS = /* @__PURE__ */ new Set([
2560
+ "text-wrap",
2561
+ "text-nowrap",
2562
+ "text-balance",
2563
+ "text-pretty"
2564
+ ]);
2565
+ function isTextSize(cls, projectTextSizeClasses) {
2566
+ if (TEXT_SIZE_KEYWORDS.has(cls)) return true;
2567
+ if (projectTextSizeClasses?.has(cls)) return true;
2568
+ return false;
2569
+ }
2570
+ function categorizeClass(cls, projectTextSizeClasses) {
2571
+ const colonIdx = cls.lastIndexOf(":");
2572
+ const base = colonIdx > 0 ? cls.slice(colonIdx + 1) : cls;
2573
+ if (base === "flex" || base === "grid" || base === "block" || base === "inline" || base === "inline-flex" || base === "inline-grid" || base === "inline-block" || base === "hidden" || base === "contents" || base === "absolute" || base === "relative" || base === "fixed" || base === "sticky" || base === "static" || base.startsWith("flex-") || base.startsWith("grid-") || base.startsWith("col-") || base.startsWith("row-") || base.startsWith("justify-") || base.startsWith("items-") || base.startsWith("self-") || base.startsWith("place-") || base.startsWith("order-") || base.startsWith("float-") || base === "clear-both" || base.startsWith("clear-") || base.startsWith("top-") || base.startsWith("right-") || base.startsWith("bottom-") || base.startsWith("left-") || base.startsWith("inset-") || base.startsWith("z-") || base.startsWith("overflow-")) {
2574
+ return "Layout";
2575
+ }
2576
+ if (base.startsWith("w-") || base.startsWith("h-") || base.startsWith("min-w-") || base.startsWith("min-h-") || base.startsWith("max-w-") || base.startsWith("max-h-") || base.startsWith("size-") || base.startsWith("aspect-")) {
2577
+ return "Sizing";
2578
+ }
2579
+ if (base.startsWith("p-") || base.startsWith("px-") || base.startsWith("py-") || base.startsWith("pt-") || base.startsWith("pr-") || base.startsWith("pb-") || base.startsWith("pl-") || base.startsWith("ps-") || base.startsWith("pe-") || base.startsWith("m-") || base.startsWith("mx-") || base.startsWith("my-") || base.startsWith("mt-") || base.startsWith("mr-") || base.startsWith("mb-") || base.startsWith("ml-") || base.startsWith("ms-") || base.startsWith("me-") || base.startsWith("gap-") || base.startsWith("space-")) {
2580
+ return "Spacing";
2581
+ }
2582
+ if (isTextSize(base, projectTextSizeClasses) || base.startsWith("font-") || base.startsWith("leading-") || base.startsWith("tracking-") || TEXT_ALIGN_KEYWORDS.has(base) || TEXT_DECORATION_KEYWORDS.has(base) || TEXT_TRANSFORM_KEYWORDS.has(base) || TEXT_OVERFLOW_KEYWORDS.has(base) || TEXT_WRAP_KEYWORDS.has(base) || base.startsWith("indent-") || base.startsWith("align-") || base.startsWith("whitespace-") || base.startsWith("break-") || base === "antialiased" || base === "subpixel-antialiased" || base.startsWith("decoration-") || base.startsWith("underline-offset-") || base.startsWith("list-")) {
2583
+ return "Typography";
2584
+ }
2585
+ if (base.startsWith("text-") && !isTextSize(base, projectTextSizeClasses) && !TEXT_ALIGN_KEYWORDS.has(base) && !TEXT_OVERFLOW_KEYWORDS.has(base) && !TEXT_WRAP_KEYWORDS.has(base) || base.startsWith("bg-") && !base.startsWith("bg-gradient-") || base.startsWith("from-") || base.startsWith("via-") || base.startsWith("to-") || base.startsWith("bg-gradient-") || base.startsWith("accent-") || base.startsWith("caret-") || base.startsWith("fill-") || base.startsWith("stroke-")) {
2586
+ return "Colors";
2587
+ }
2588
+ if (base.startsWith("border") || base.startsWith("rounded") || base.startsWith("divide-") || base.startsWith("ring-") || base === "ring" || base.startsWith("outline-") || base === "outline" || base === "outline-none") {
2589
+ return "Borders";
2590
+ }
2591
+ if (base.startsWith("shadow") || base.startsWith("opacity-") || base.startsWith("blur-") || base === "blur" || base.startsWith("brightness-") || base.startsWith("contrast-") || base.startsWith("grayscale") || base.startsWith("hue-rotate-") || base.startsWith("invert") || base.startsWith("saturate-") || base.startsWith("sepia") || base.startsWith("drop-shadow-") || base.startsWith("backdrop-") || base.startsWith("mix-blend-") || base.startsWith("bg-blend-") || base.startsWith("transition") || base.startsWith("duration-") || base.startsWith("ease-") || base.startsWith("delay-") || base.startsWith("animate-") || base.startsWith("scale-") || base.startsWith("rotate-") || base.startsWith("translate-") || base.startsWith("skew-") || base === "transform" || base === "transform-gpu" || base === "transform-none") {
2592
+ return "Effects";
2593
+ }
2594
+ return "Other";
2595
+ }
2596
+ function findFontSizeClasses(classes, projectTextSizeClasses) {
2597
+ return classes.filter((cls) => isTextSize(cls, projectTextSizeClasses));
2598
+ }
2599
+ function categorizeClasses(classes, projectTextSizeClasses) {
2600
+ const CATEGORY_ORDER = ["Layout", "Sizing", "Spacing", "Typography", "Colors", "Borders", "Effects", "Other"];
2601
+ const map = /* @__PURE__ */ new Map();
2602
+ for (const cls of classes) {
2603
+ const category = categorizeClass(cls, projectTextSizeClasses);
2604
+ const existing = map.get(category);
2605
+ if (existing) {
2606
+ existing.push(cls);
2607
+ } else {
2608
+ map.set(category, [cls]);
2609
+ }
2610
+ }
2611
+ const groups = [];
2612
+ for (const cat of CATEGORY_ORDER) {
2613
+ const items = map.get(cat);
2614
+ if (items && items.length > 0) {
2615
+ groups.push({ category: cat, classes: items });
2616
+ }
2617
+ }
2618
+ return groups;
2619
+ }
2620
+
2621
+ // src/components/ClassChip.tsx
2622
+ import { useCallback as useCallback14, useEffect as useEffect8, useRef as useRef8, useState as useState11 } from "react";
2623
+
2624
+ // src/components/ClassSuggestionDropdown.tsx
2625
+ import { useCallback as useCallback13, useEffect as useEffect7, useRef as useRef7, useState as useState10 } from "react";
2626
+ import { createPortal } from "react-dom";
2627
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2628
+ function ClassSuggestionDropdown({
2629
+ suggestions,
2630
+ inputValue,
2631
+ onSelect,
2632
+ onDismiss,
2633
+ visible,
2634
+ inputRef
2635
+ }) {
2636
+ const [activeIndex, setActiveIndex] = useState10(0);
2637
+ const listRef = useRef7(null);
2638
+ const [position, setPosition] = useState10(null);
2639
+ useEffect7(() => {
2640
+ setActiveIndex(0);
2641
+ }, [suggestions]);
2642
+ useEffect7(() => {
2643
+ if (!visible || !inputRef?.current) {
2644
+ setPosition(null);
2645
+ return;
2646
+ }
2647
+ const rect = inputRef.current.getBoundingClientRect();
2648
+ setPosition({
2649
+ top: rect.bottom + 2,
2650
+ left: rect.left,
2651
+ width: rect.width
2652
+ });
2653
+ }, [visible, inputRef, suggestions]);
2654
+ useEffect7(() => {
2655
+ if (!listRef.current) return;
2656
+ const activeEl = listRef.current.children[activeIndex];
2657
+ if (activeEl) {
2658
+ activeEl.scrollIntoView({ block: "nearest" });
2659
+ }
2660
+ }, [activeIndex]);
2661
+ const handleKeyDown = useCallback13(
2662
+ (e) => {
2663
+ if (!visible || suggestions.length === 0) return;
2664
+ if (e.key === "ArrowDown") {
2665
+ e.preventDefault();
2666
+ setActiveIndex((prev) => Math.min(prev + 1, suggestions.length - 1));
2667
+ } else if (e.key === "ArrowUp") {
2668
+ e.preventDefault();
2669
+ setActiveIndex((prev) => Math.max(prev - 1, 0));
2670
+ } else if (e.key === "Enter") {
2671
+ e.preventDefault();
2672
+ if (suggestions[activeIndex]) {
2673
+ onSelect(suggestions[activeIndex]);
2674
+ }
2675
+ } else if (e.key === "Escape") {
2676
+ e.preventDefault();
2677
+ onDismiss();
2678
+ }
2679
+ },
2680
+ [visible, suggestions, activeIndex, onSelect, onDismiss]
2681
+ );
2682
+ useEffect7(() => {
2683
+ if (!visible || suggestions.length === 0) return;
2684
+ window.addEventListener("keydown", handleKeyDown, true);
2685
+ return () => window.removeEventListener("keydown", handleKeyDown, true);
2686
+ }, [visible, suggestions, handleKeyDown]);
2687
+ if (!visible || suggestions.length === 0 || !position) return null;
2688
+ const query = inputValue.toLowerCase();
2689
+ const style = {
2690
+ position: "fixed",
2691
+ top: position.top,
2692
+ left: position.left,
2693
+ width: position.width,
2694
+ maxHeight: 240,
2695
+ overflowY: "auto",
2696
+ background: "#1e1e2e",
2697
+ border: "1px solid #45475a",
2698
+ borderRadius: 4,
2699
+ zIndex: 10001,
2700
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)"
2701
+ };
2702
+ return createPortal(
2703
+ /* @__PURE__ */ jsx10("div", { className: "te-suggest-dropdown", ref: listRef, style, children: suggestions.slice(0, 15).map((cls, i) => {
2704
+ const lower = cls.toLowerCase();
2705
+ const matchIdx = lower.indexOf(query);
2706
+ let content = cls;
2707
+ if (matchIdx >= 0 && query.length > 0) {
2708
+ const before = cls.slice(0, matchIdx);
2709
+ const match = cls.slice(matchIdx, matchIdx + query.length);
2710
+ const after = cls.slice(matchIdx + query.length);
2711
+ content = /* @__PURE__ */ jsxs10(Fragment4, { children: [
2712
+ before,
2713
+ /* @__PURE__ */ jsx10("span", { className: "te-suggest-match", children: match }),
2714
+ after
2715
+ ] });
2716
+ }
2717
+ return /* @__PURE__ */ jsx10(
2718
+ "div",
2719
+ {
2720
+ className: `te-suggest-item ${i === activeIndex ? "te-suggest-item-active" : ""}`,
2721
+ onMouseDown: (e) => {
2722
+ e.preventDefault();
2723
+ onSelect(cls);
2724
+ },
2725
+ onMouseEnter: () => setActiveIndex(i),
2726
+ children: content
2727
+ },
2728
+ cls
2729
+ );
2730
+ }) }),
2731
+ document.body
2732
+ );
2733
+ }
2734
+
2735
+ // src/core/class-dictionary.ts
2736
+ var cachedDictionary = null;
2737
+ var EXCLUDED_PREFIXES2 = ["te-", "__next", "__N_"];
2738
+ function isExcluded(cls) {
2739
+ if (cls.length <= 1) return true;
2740
+ if (cls.includes("[")) return true;
2741
+ for (const prefix of EXCLUDED_PREFIXES2) {
2742
+ if (cls.startsWith(prefix)) return true;
2743
+ }
2744
+ return false;
2745
+ }
2746
+ function cleanSelector(selector) {
2747
+ let cleaned = selector.replace(/:[\w-]+(\(.*?\))?/g, "");
2748
+ cleaned = cleaned.replace(/\\\//g, "/").replace(/\\:/g, ":").replace(/\\./g, ".");
2749
+ return cleaned;
2750
+ }
2751
+ function extractClassesFromRule(selectorText) {
2752
+ const classes = [];
2753
+ const classRegex = /\.([a-zA-Z0-9_-][\w\-/:.]*)/g;
2754
+ let match;
2755
+ const cleaned = cleanSelector(selectorText);
2756
+ while ((match = classRegex.exec(cleaned)) !== null) {
2757
+ const cls = match[1];
2758
+ if (!isExcluded(cls)) {
2759
+ classes.push(cls);
2760
+ }
2761
+ }
2762
+ return classes;
2763
+ }
2764
+ function buildClassDictionary() {
2765
+ const classSet = /* @__PURE__ */ new Set();
2766
+ if (typeof document === "undefined") {
2767
+ return { allClasses: [], prefixIndex: /* @__PURE__ */ new Map() };
2768
+ }
2769
+ for (let i = 0; i < document.styleSheets.length; i++) {
2770
+ const sheet = document.styleSheets[i];
2771
+ try {
2772
+ const rules = sheet.cssRules || sheet.rules;
2773
+ if (!rules) continue;
2774
+ for (let j = 0; j < rules.length; j++) {
2775
+ const rule = rules[j];
2776
+ if (rule instanceof CSSStyleRule) {
2777
+ const extracted = extractClassesFromRule(rule.selectorText);
2778
+ for (const cls of extracted) {
2779
+ classSet.add(cls);
2780
+ }
2781
+ } else if (rule instanceof CSSMediaRule) {
2782
+ for (let k = 0; k < rule.cssRules.length; k++) {
2783
+ const innerRule = rule.cssRules[k];
2784
+ if (innerRule instanceof CSSStyleRule) {
2785
+ const extracted = extractClassesFromRule(innerRule.selectorText);
2786
+ for (const cls of extracted) {
2787
+ classSet.add(cls);
2788
+ }
2789
+ }
2790
+ }
2791
+ }
2792
+ }
2793
+ } catch {
2794
+ continue;
2795
+ }
2796
+ }
2797
+ const allClasses = Array.from(classSet).sort();
2798
+ const prefixIndex = /* @__PURE__ */ new Map();
2799
+ for (const cls of allClasses) {
2800
+ if (cls.length < 2) continue;
2801
+ const prefix = cls.slice(0, 2).toLowerCase();
2802
+ const existing = prefixIndex.get(prefix);
2803
+ if (existing) {
2804
+ existing.push(cls);
2805
+ } else {
2806
+ prefixIndex.set(prefix, [cls]);
2807
+ }
2808
+ }
2809
+ return { allClasses, prefixIndex };
2810
+ }
2811
+ function getClassDictionary() {
2812
+ if (!cachedDictionary) {
2813
+ cachedDictionary = buildClassDictionary();
2814
+ }
2815
+ return cachedDictionary;
2816
+ }
2817
+ function filterSuggestions(dict, input, max = 30) {
2818
+ const query = input.trim().toLowerCase();
2819
+ if (query.length < 2) return [];
2820
+ const prefix = query.slice(0, 2);
2821
+ const candidates = dict.prefixIndex.get(prefix) || [];
2822
+ const prefixMatches = [];
2823
+ const containsMatches = [];
2824
+ for (const cls of candidates) {
2825
+ if (cls.toLowerCase().startsWith(query)) {
2826
+ prefixMatches.push(cls);
2827
+ }
2828
+ }
2829
+ if (prefixMatches.length < max) {
2830
+ const remaining = max - prefixMatches.length;
2831
+ const prefixSet = new Set(prefixMatches);
2832
+ for (const cls of dict.allClasses) {
2833
+ if (prefixSet.has(cls)) continue;
2834
+ if (cls.toLowerCase().includes(query)) {
2835
+ containsMatches.push(cls);
2836
+ if (containsMatches.length >= remaining) break;
2837
+ }
2838
+ }
2839
+ }
2840
+ return [...prefixMatches.slice(0, max), ...containsMatches].slice(0, max);
2841
+ }
2842
+
2843
+ // src/components/ClassChip.tsx
2844
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2845
+ function ClassChip({
2846
+ className,
2847
+ isModified,
2848
+ onUpdate,
2849
+ onRemove,
2850
+ conflict
2851
+ }) {
2852
+ const [isEditing, setIsEditing] = useState11(false);
2853
+ const [editValue, setEditValue] = useState11(className);
2854
+ const [suggestions, setSuggestions] = useState11([]);
2855
+ const [showSuggestions, setShowSuggestions] = useState11(false);
2856
+ const inputRef = useRef8(null);
2857
+ useEffect8(() => {
2858
+ if (isEditing && inputRef.current) {
2859
+ inputRef.current.focus();
2860
+ inputRef.current.select();
2861
+ }
2862
+ }, [isEditing]);
2863
+ const handleDoubleClick = useCallback14(() => {
2864
+ setEditValue(className);
2865
+ setIsEditing(true);
2866
+ setSuggestions([]);
2867
+ setShowSuggestions(false);
2868
+ }, [className]);
2869
+ const commitEdit = useCallback14(() => {
2870
+ setIsEditing(false);
2871
+ setShowSuggestions(false);
2872
+ setSuggestions([]);
2873
+ const trimmed = editValue.trim();
2874
+ if (trimmed === className) return;
2875
+ if (!trimmed) {
2876
+ onRemove(className);
2877
+ } else {
2878
+ onUpdate(className, trimmed);
2879
+ }
2880
+ }, [editValue, className, onUpdate, onRemove]);
2881
+ const handleKeyDown = useCallback14(
2882
+ (e) => {
2883
+ if (showSuggestions && suggestions.length > 0) return;
2884
+ if (e.key === "Enter") {
2885
+ e.preventDefault();
2886
+ commitEdit();
2887
+ } else if (e.key === "Escape") {
2888
+ e.preventDefault();
2889
+ setIsEditing(false);
2890
+ setEditValue(className);
2891
+ setShowSuggestions(false);
2892
+ setSuggestions([]);
2893
+ }
2894
+ },
2895
+ [commitEdit, className, showSuggestions, suggestions]
2896
+ );
2897
+ const handleInputChange = useCallback14((e) => {
2898
+ const val = e.target.value;
2899
+ setEditValue(val);
2900
+ const dict = getClassDictionary();
2901
+ const results = filterSuggestions(dict, val, 30);
2902
+ setSuggestions(results);
2903
+ setShowSuggestions(results.length > 0);
2904
+ }, []);
2905
+ const handleSuggestionSelect = useCallback14(
2906
+ (cls) => {
2907
+ setShowSuggestions(false);
2908
+ setSuggestions([]);
2909
+ setIsEditing(false);
2910
+ if (cls !== className) {
2911
+ onUpdate(className, cls);
2912
+ }
2913
+ },
2914
+ [className, onUpdate]
2915
+ );
2916
+ if (isEditing) {
2917
+ return /* @__PURE__ */ jsxs11("div", { style: { display: "inline-block" }, children: [
2918
+ /* @__PURE__ */ jsx11(
2919
+ "input",
2920
+ {
2921
+ ref: inputRef,
2922
+ type: "text",
2923
+ className: "te-chip-edit",
2924
+ value: editValue,
2925
+ onChange: handleInputChange,
2926
+ onBlur: () => {
2927
+ setTimeout(() => {
2928
+ if (showSuggestions) return;
2929
+ commitEdit();
2930
+ }, 150);
2931
+ },
2932
+ onKeyDown: handleKeyDown,
2933
+ style: {
2934
+ width: Math.max(60, Math.min(200, editValue.length * 7.5 + 16))
2935
+ }
2936
+ }
2937
+ ),
2938
+ /* @__PURE__ */ jsx11(
2939
+ ClassSuggestionDropdown,
2940
+ {
2941
+ suggestions,
2942
+ inputValue: editValue,
2943
+ onSelect: handleSuggestionSelect,
2944
+ onDismiss: () => setShowSuggestions(false),
2945
+ visible: showSuggestions,
2946
+ inputRef
2947
+ }
2948
+ )
2949
+ ] });
2950
+ }
2951
+ const chipClass = [
2952
+ "te-chip",
2953
+ conflict ? "te-chip-conflict" : "",
2954
+ isModified && !conflict ? "te-chip-modified" : ""
2955
+ ].filter(Boolean).join(" ");
2956
+ return /* @__PURE__ */ jsxs11(
2957
+ "span",
2958
+ {
2959
+ className: chipClass,
2960
+ onDoubleClick: handleDoubleClick,
2961
+ children: [
2962
+ /* @__PURE__ */ jsx11("span", { children: className }),
2963
+ conflict && /* @__PURE__ */ jsxs11("span", { className: "te-chip-tooltip", children: [
2964
+ /* @__PURE__ */ jsxs11("span", { className: "te-chip-tooltip-text", children: [
2965
+ "Conflicts with ",
2966
+ conflict.conflictsWith.join(", "),
2967
+ " on ",
2968
+ conflict.property
2969
+ ] }),
2970
+ "\u26A0"
2971
+ ] }),
2972
+ /* @__PURE__ */ jsx11(
2973
+ "button",
2974
+ {
2975
+ type: "button",
2976
+ className: "te-chip-remove",
2977
+ onClick: (e) => {
2978
+ e.stopPropagation();
2979
+ onRemove(className);
2980
+ },
2981
+ title: "Remove class",
2982
+ children: "\xD7"
2983
+ }
2984
+ )
2985
+ ]
2986
+ }
2987
+ );
2988
+ }
2989
+
2990
+ // src/core/class-conflict-detection.ts
2991
+ var PROPERTY_MAP = [
2992
+ // Text color vs text size disambiguation
2993
+ ["text-", "font-size", (cls) => TEXT_SIZE_KEYWORDS.has(cls)],
2994
+ ["text-", "color", (cls) => !TEXT_SIZE_KEYWORDS.has(cls) && !isTextAlignment(cls)],
2995
+ ["text-", "text-align", (cls) => isTextAlignment(cls)],
2996
+ // Background
2997
+ ["bg-", "background"],
2998
+ // Sizing
2999
+ ["w-", "width"],
3000
+ ["h-", "height"],
3001
+ ["min-w-", "min-width"],
3002
+ ["min-h-", "min-height"],
3003
+ ["max-w-", "max-width"],
3004
+ ["max-h-", "max-height"],
3005
+ // Spacing — each direction is its own group
3006
+ ["p-", "padding"],
3007
+ ["px-", "padding-x"],
3008
+ ["py-", "padding-y"],
3009
+ ["pt-", "padding-top"],
3010
+ ["pr-", "padding-right"],
3011
+ ["pb-", "padding-bottom"],
3012
+ ["pl-", "padding-left"],
3013
+ ["m-", "margin"],
3014
+ ["mx-", "margin-x"],
3015
+ ["my-", "margin-y"],
3016
+ ["mt-", "margin-top"],
3017
+ ["mr-", "margin-right"],
3018
+ ["mb-", "margin-bottom"],
3019
+ ["ml-", "margin-left"],
3020
+ // Typography
3021
+ ["font-", "font"],
3022
+ ["leading-", "line-height"],
3023
+ ["tracking-", "letter-spacing"],
3024
+ // Layout
3025
+ ["justify-", "justify-content"],
3026
+ ["items-", "align-items"],
3027
+ ["self-", "align-self"],
3028
+ ["gap-", "gap"],
3029
+ // Borders
3030
+ ["rounded-", "border-radius"],
3031
+ ["border-", "border"],
3032
+ // Effects
3033
+ ["shadow-", "box-shadow"],
3034
+ ["opacity-", "opacity"],
3035
+ // Display
3036
+ ["flex", "display"],
3037
+ ["grid", "display"],
3038
+ ["block", "display"],
3039
+ ["inline", "display"],
3040
+ ["hidden", "display"],
3041
+ // Position
3042
+ ["absolute", "position"],
3043
+ ["relative", "position"],
3044
+ ["fixed", "position"],
3045
+ ["sticky", "position"],
3046
+ // Overflow
3047
+ ["overflow-", "overflow"],
3048
+ // Z-index
3049
+ ["z-", "z-index"]
3050
+ ];
3051
+ var TEXT_ALIGN_KEYWORDS2 = /* @__PURE__ */ new Set([
3052
+ "text-left",
3053
+ "text-center",
3054
+ "text-right",
3055
+ "text-justify",
3056
+ "text-start",
3057
+ "text-end"
3058
+ ]);
3059
+ function isTextAlignment(cls) {
3060
+ return TEXT_ALIGN_KEYWORDS2.has(cls);
3061
+ }
3062
+ function getVariantPrefix(cls) {
3063
+ const lastColon = cls.lastIndexOf(":");
3064
+ if (lastColon === -1) return "";
3065
+ const prefix = cls.slice(0, lastColon + 1);
3066
+ if (/^(hover|focus|active|disabled|group-|peer-|first|last|odd|even|focus-within|focus-visible|aria-|data-|sm|md|lg|xl|2xl|dark|print):/.test(prefix)) {
3067
+ return prefix;
3068
+ }
3069
+ return "";
3070
+ }
3071
+ function getBaseClass(cls) {
3072
+ const prefix = getVariantPrefix(cls);
3073
+ return prefix ? cls.slice(prefix.length) : cls;
3074
+ }
3075
+ function getPropertyGroup(cls) {
3076
+ const base = getBaseClass(cls);
3077
+ for (const [prefix, property, filter] of PROPERTY_MAP) {
3078
+ if (base === prefix || base.startsWith(prefix)) {
3079
+ if (filter && !filter(base)) continue;
3080
+ return property;
3081
+ }
3082
+ }
3083
+ return null;
3084
+ }
3085
+ function detectConflicts(classes) {
3086
+ const groups = /* @__PURE__ */ new Map();
3087
+ for (const cls of classes) {
3088
+ const property = getPropertyGroup(cls);
3089
+ if (!property) continue;
3090
+ const variant = getVariantPrefix(cls);
3091
+ const key = `${variant}|${property}`;
3092
+ const existing = groups.get(key);
3093
+ if (existing) {
3094
+ existing.push(cls);
3095
+ } else {
3096
+ groups.set(key, [cls]);
3097
+ }
3098
+ }
3099
+ const conflicts = [];
3100
+ for (const [key, group] of groups) {
3101
+ if (group.length >= 2) {
3102
+ const property = key.split("|")[1];
3103
+ conflicts.push({ classes: group, property });
3104
+ }
3105
+ }
3106
+ return conflicts;
3107
+ }
3108
+ function getChipConflicts(cls, conflicts) {
3109
+ for (const conflict of conflicts) {
3110
+ if (conflict.classes.includes(cls)) {
3111
+ const others = conflict.classes.filter((c) => c !== cls);
3112
+ return { property: conflict.property, conflictsWith: others };
3113
+ }
3114
+ }
3115
+ return void 0;
3116
+ }
3117
+
3118
+ // src/components/ClassCategoryGroup.tsx
3119
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3120
+ function ClassCategoryGroup({
3121
+ category,
3122
+ classes,
3123
+ originalClasses,
3124
+ onUpdate,
3125
+ onRemove,
3126
+ conflicts = []
3127
+ }) {
3128
+ const hasConflict = conflicts.length > 0 && classes.some(
3129
+ (cls) => getChipConflicts(cls, conflicts) !== void 0
3130
+ );
3131
+ return /* @__PURE__ */ jsxs12("div", { className: "te-class-group", children: [
3132
+ /* @__PURE__ */ jsx12("div", { className: "te-class-group-header", children: /* @__PURE__ */ jsxs12("span", { children: [
3133
+ category,
3134
+ hasConflict && /* @__PURE__ */ jsxs12("span", { className: "te-conflict-warning", title: "Conflicting classes in this group", children: [
3135
+ " ",
3136
+ "\u26A0"
3137
+ ] })
3138
+ ] }) }),
3139
+ /* @__PURE__ */ jsx12("div", { className: "te-class-chips", children: classes.map((cls) => /* @__PURE__ */ jsx12(
3140
+ ClassChip,
3141
+ {
3142
+ className: cls,
3143
+ isModified: !originalClasses.includes(cls),
3144
+ onUpdate,
3145
+ onRemove,
3146
+ conflict: getChipConflicts(cls, conflicts)
3147
+ },
3148
+ cls
3149
+ )) })
3150
+ ] });
3151
+ }
3152
+
3153
+ // src/components/ElementSelectorOverlay.tsx
3154
+ import { useEffect as useEffect9 } from "react";
3155
+ function ElementSelectorOverlay({
3156
+ isActive,
3157
+ onSelect,
3158
+ onDeactivate
3159
+ }) {
3160
+ useEffect9(() => {
3161
+ if (!isActive) return;
3162
+ const overlay = document.createElement("div");
3163
+ overlay.style.cssText = `
3164
+ position: fixed;
3165
+ pointer-events: none;
3166
+ z-index: 10000;
3167
+ outline: 2px solid #89b4fa;
3168
+ background: rgba(137, 180, 250, 0.08);
3169
+ transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s;
3170
+ display: none;
3171
+ `;
3172
+ document.body.appendChild(overlay);
3173
+ const handleMouseMove = (e) => {
3174
+ const target = e.target;
3175
+ if (!target || target === overlay) return;
3176
+ if (target.closest(".te-drawer")) {
3177
+ overlay.style.display = "none";
3178
+ return;
3179
+ }
3180
+ const rect = target.getBoundingClientRect();
3181
+ overlay.style.top = `${rect.top}px`;
3182
+ overlay.style.left = `${rect.left}px`;
3183
+ overlay.style.width = `${rect.width}px`;
3184
+ overlay.style.height = `${rect.height}px`;
3185
+ overlay.style.display = "block";
3186
+ };
3187
+ const handleClick = (e) => {
3188
+ const target = e.target;
3189
+ if (!target) return;
3190
+ if (target.closest(".te-drawer")) return;
3191
+ e.preventDefault();
3192
+ e.stopPropagation();
3193
+ onSelect(target);
3194
+ onDeactivate();
3195
+ };
3196
+ const handleKeyDown = (e) => {
3197
+ if (e.key === "Escape") {
3198
+ onDeactivate();
3199
+ }
3200
+ };
3201
+ document.addEventListener("mousemove", handleMouseMove, true);
3202
+ document.addEventListener("click", handleClick, true);
3203
+ document.addEventListener("keydown", handleKeyDown, true);
3204
+ return () => {
3205
+ document.removeEventListener("mousemove", handleMouseMove, true);
3206
+ document.removeEventListener("click", handleClick, true);
3207
+ document.removeEventListener("keydown", handleKeyDown, true);
3208
+ if (overlay.parentNode) {
3209
+ overlay.parentNode.removeChild(overlay);
3210
+ }
3211
+ };
3212
+ }, [isActive, onSelect, onDeactivate]);
3213
+ return null;
3214
+ }
3215
+
3216
+ // src/components/TokenMigrationSuggestions.tsx
3217
+ import { useMemo as useMemo5, useState as useState12 } from "react";
3218
+
3219
+ // src/core/token-migration-map.ts
3220
+ function getMigrationSuggestions(classes, migrationMap) {
3221
+ const suggestions = [];
3222
+ for (const cls of classes) {
3223
+ const entry = migrationMap[cls];
3224
+ if (entry) {
3225
+ suggestions.push({
3226
+ from: cls,
3227
+ to: entry.replacement,
3228
+ reason: entry.reason ?? "",
3229
+ confidence: entry.confidence ?? "medium"
3230
+ });
3231
+ }
3232
+ }
3233
+ return suggestions;
3234
+ }
3235
+
3236
+ // src/components/TokenMigrationSuggestions.tsx
3237
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
3238
+ function TokenMigrationSuggestions({
3239
+ classes,
3240
+ onApply
3241
+ }) {
3242
+ const { migrationMap } = useDevLensConfig();
3243
+ const [dismissed, setDismissed] = useState12(/* @__PURE__ */ new Set());
3244
+ const suggestions = useMemo5(
3245
+ () => getMigrationSuggestions(classes, migrationMap),
3246
+ [classes, migrationMap]
3247
+ );
3248
+ const visible = suggestions.filter((s) => !dismissed.has(s.from));
3249
+ if (visible.length === 0) return null;
3250
+ return /* @__PURE__ */ jsxs13("div", { className: "te-migration-section", children: [
3251
+ /* @__PURE__ */ jsxs13("div", { className: "te-migration-header", children: [
3252
+ "Token Migrations",
3253
+ /* @__PURE__ */ jsx13("span", { className: "te-badge te-badge-sm", children: visible.length })
3254
+ ] }),
3255
+ visible.map((suggestion) => /* @__PURE__ */ jsxs13(
3256
+ "div",
3257
+ {
3258
+ className: "te-migration-row",
3259
+ onClick: () => onApply(suggestion.from, suggestion.to),
3260
+ title: `Replace ${suggestion.from} with ${suggestion.to}`,
3261
+ children: [
3262
+ /* @__PURE__ */ jsx13("span", { className: "te-migration-from", children: suggestion.from }),
3263
+ /* @__PURE__ */ jsx13("span", { className: "te-migration-arrow", children: "\u2192" }),
3264
+ /* @__PURE__ */ jsx13("span", { className: "te-migration-to", children: suggestion.to }),
3265
+ /* @__PURE__ */ jsx13(
3266
+ "span",
3267
+ {
3268
+ className: "te-migration-confidence",
3269
+ "data-confidence": suggestion.confidence,
3270
+ children: suggestion.confidence
3271
+ }
3272
+ ),
3273
+ /* @__PURE__ */ jsx13(
3274
+ "button",
3275
+ {
3276
+ type: "button",
3277
+ className: "te-migration-dismiss",
3278
+ onClick: (e) => {
3279
+ e.stopPropagation();
3280
+ setDismissed((prev) => new Set(prev).add(suggestion.from));
3281
+ },
3282
+ title: "Dismiss suggestion",
3283
+ children: "\xD7"
3284
+ }
3285
+ )
3286
+ ]
3287
+ },
3288
+ suggestion.from
3289
+ ))
3290
+ ] });
3291
+ }
3292
+
3293
+ // src/components/TypographyAudit.tsx
3294
+ import { useEffect as useEffect10, useMemo as useMemo6, useRef as useRef9, useState as useState13 } from "react";
3295
+
3296
+ // src/core/scale-step-lookup.ts
3297
+ function getExpectedScaleStep(tagName, baseline) {
3298
+ if (!baseline || !baseline.mapping || baseline.mapping.length === 0) return null;
3299
+ const tag = tagName.toLowerCase();
3300
+ const mapping = baseline.mapping.find((m) => m.selector === tag);
3301
+ if (!mapping) return null;
3302
+ const root = document.documentElement;
3303
+ const style = getComputedStyle(root);
3304
+ const fontSizeRaw = style.getPropertyValue(mapping.fontSizeVar).trim();
3305
+ const lineHeightRaw = style.getPropertyValue(mapping.lineHeightVar).trim();
3306
+ let expectedPx = parseFloat(fontSizeRaw);
3307
+ if (isNaN(expectedPx)) {
3308
+ const temp = document.createElement("div");
3309
+ temp.style.fontSize = `var(${mapping.fontSizeVar})`;
3310
+ temp.style.position = "absolute";
3311
+ temp.style.visibility = "hidden";
3312
+ document.body.appendChild(temp);
3313
+ expectedPx = parseFloat(getComputedStyle(temp).fontSize);
3314
+ document.body.removeChild(temp);
3315
+ }
3316
+ const expectedLineHeight = parseFloat(lineHeightRaw) || 1.5;
3317
+ return { mapping, expectedPx, expectedLineHeight };
3318
+ }
3319
+ function findNearestScaleStep(computedPx, baseline) {
3320
+ if (!baseline || !baseline.mapping || baseline.mapping.length === 0) return null;
3321
+ const root = document.documentElement;
3322
+ const style = getComputedStyle(root);
3323
+ let nearest = null;
3324
+ const seen = /* @__PURE__ */ new Set();
3325
+ for (const mapping of baseline.mapping) {
3326
+ if (seen.has(mapping.fontSizeVar)) continue;
3327
+ seen.add(mapping.fontSizeVar);
3328
+ const raw = style.getPropertyValue(mapping.fontSizeVar).trim();
3329
+ const px = parseFloat(raw);
3330
+ if (isNaN(px)) continue;
3331
+ const delta = Math.abs(computedPx - px);
3332
+ if (!nearest || delta < Math.abs(nearest.deltaPx)) {
3333
+ nearest = { mapping, expectedPx: px, deltaPx: computedPx - px };
3334
+ }
3335
+ }
3336
+ return nearest;
3337
+ }
3338
+
3339
+ // src/components/TypographyAudit.tsx
3340
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
3341
+ function revertPreview(state) {
3342
+ for (const cls of state.removedClasses) {
3343
+ state.element.classList.add(cls);
3344
+ }
3345
+ if (state.addedInlineStyles) {
3346
+ state.element.style.fontSize = state.originalInlineFontSize;
3347
+ state.element.style.lineHeight = state.originalInlineLineHeight;
3348
+ }
3349
+ }
3350
+ function formatDelta(delta) {
3351
+ const rounded = Math.round(delta * 100) / 100;
3352
+ if (rounded >= 0) return `+${rounded}px`;
3353
+ return `\u2212${Math.abs(rounded)}px`;
3354
+ }
3355
+ function TypographyAudit({
3356
+ element,
3357
+ currentClasses,
3358
+ onRemoveClass,
3359
+ isBaselineActive,
3360
+ onCreateToken
3361
+ }) {
3362
+ const { scaleBaseline, projectTextSizeClasses } = useDevLensConfig();
3363
+ const projectTextSizeSet = useMemo6(
3364
+ () => new Set(projectTextSizeClasses),
3365
+ [projectTextSizeClasses]
3366
+ );
3367
+ const [showOnScale, setShowOnScale] = useState13(false);
3368
+ const previewStateRef = useRef9(null);
3369
+ useEffect10(() => {
3370
+ return () => {
3371
+ if (previewStateRef.current) {
3372
+ revertPreview(previewStateRef.current);
3373
+ previewStateRef.current = null;
3374
+ }
3375
+ };
3376
+ }, [element]);
3377
+ useEffect10(() => {
3378
+ setShowOnScale(false);
3379
+ }, [element]);
3380
+ const tagName = element.tagName.toLowerCase();
3381
+ const expected = getExpectedScaleStep(tagName, scaleBaseline);
3382
+ const computed = getComputedStyle(element);
3383
+ const computedFontSize = parseFloat(computed.fontSize);
3384
+ const rawLineHeight = computed.lineHeight;
3385
+ const computedLineHeight = rawLineHeight === "normal" ? 1.5 : parseFloat(rawLineHeight) / computedFontSize;
3386
+ const fontSizeClasses = findFontSizeClasses(currentClasses, projectTextSizeSet);
3387
+ const viaClass = fontSizeClasses.length > 0 ? fontSizeClasses[0] : null;
3388
+ const handleTogglePreview = (checked) => {
3389
+ if (!expected) return;
3390
+ if (checked) {
3391
+ const state = {
3392
+ element,
3393
+ removedClasses: [...fontSizeClasses],
3394
+ originalInlineFontSize: element.style.fontSize,
3395
+ originalInlineLineHeight: element.style.lineHeight,
3396
+ addedInlineStyles: false,
3397
+ fontSizeVar: expected.mapping.fontSizeVar,
3398
+ lineHeightVar: expected.mapping.lineHeightVar
3399
+ };
3400
+ for (const cls of fontSizeClasses) {
3401
+ element.classList.remove(cls);
3402
+ }
3403
+ if (!isBaselineActive) {
3404
+ element.style.fontSize = `var(${expected.mapping.fontSizeVar})`;
3405
+ element.style.lineHeight = `var(${expected.mapping.lineHeightVar})`;
3406
+ state.addedInlineStyles = true;
3407
+ }
3408
+ previewStateRef.current = state;
3409
+ setShowOnScale(true);
3410
+ } else {
3411
+ if (previewStateRef.current) {
3412
+ revertPreview(previewStateRef.current);
3413
+ previewStateRef.current = null;
3414
+ }
3415
+ setShowOnScale(false);
3416
+ }
3417
+ };
3418
+ const handleStripOverride = () => {
3419
+ if (previewStateRef.current) {
3420
+ revertPreview(previewStateRef.current);
3421
+ previewStateRef.current = null;
3422
+ setShowOnScale(false);
3423
+ }
3424
+ for (const cls of fontSizeClasses) {
3425
+ onRemoveClass(cls);
3426
+ }
3427
+ };
3428
+ if (!expected) {
3429
+ const nearest = findNearestScaleStep(computedFontSize, scaleBaseline);
3430
+ return /* @__PURE__ */ jsxs14("div", { className: "te-typo-audit", children: [
3431
+ /* @__PURE__ */ jsx14("div", { className: "te-typo-audit-header", children: "Typography Audit" }),
3432
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-status", style: { color: "#6c7086" }, children: [
3433
+ "No scale mapping for <",
3434
+ tagName,
3435
+ ">"
3436
+ ] }),
3437
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail", children: [
3438
+ "Computed: ",
3439
+ Math.round(computedFontSize * 100) / 100,
3440
+ "px \xB7 Line-height: ",
3441
+ Math.round(computedLineHeight * 100) / 100
3442
+ ] }),
3443
+ nearest && /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail-muted", children: [
3444
+ "Nearest: ",
3445
+ nearest.mapping.scaleLabel,
3446
+ " (",
3447
+ Math.round(nearest.expectedPx * 100) / 100,
3448
+ "px)",
3449
+ Math.abs(nearest.deltaPx) <= 0.5 ? " \u2713" : ` (${formatDelta(nearest.deltaPx)})`
3450
+ ] })
3451
+ ] });
3452
+ }
3453
+ const deltaPx = computedFontSize - expected.expectedPx;
3454
+ const absDelta = Math.abs(deltaPx);
3455
+ const lineHeightDiff = Math.abs(computedLineHeight - expected.expectedLineHeight);
3456
+ if (absDelta <= 0.5) {
3457
+ return /* @__PURE__ */ jsxs14("div", { className: "te-typo-audit", children: [
3458
+ /* @__PURE__ */ jsx14("div", { className: "te-typo-audit-header", children: "Typography Audit" }),
3459
+ /* @__PURE__ */ jsx14("div", { className: "te-typo-status", style: { color: "#a6e3a1" }, children: "\u2713 On scale" }),
3460
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail", children: [
3461
+ "<",
3462
+ tagName,
3463
+ "> \u2192 ",
3464
+ expected.mapping.scaleLabel,
3465
+ " (",
3466
+ Math.round(expected.expectedPx * 100) / 100,
3467
+ "px)"
3468
+ ] }),
3469
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail-muted", children: [
3470
+ "Computed: ",
3471
+ Math.round(computedFontSize * 100) / 100,
3472
+ "px \xB7 Line-height: ",
3473
+ Math.round(computedLineHeight * 100) / 100
3474
+ ] })
3475
+ ] });
3476
+ }
3477
+ if (absDelta <= 3) {
3478
+ return /* @__PURE__ */ jsxs14("div", { className: "te-typo-audit", children: [
3479
+ /* @__PURE__ */ jsx14("div", { className: "te-typo-audit-header", children: "Typography Audit" }),
3480
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-status", style: { color: "#f9e2af" }, children: [
3481
+ "\u2248 Near scale (",
3482
+ formatDelta(deltaPx),
3483
+ ")"
3484
+ ] }),
3485
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail", children: [
3486
+ "<",
3487
+ tagName,
3488
+ "> \u2192 ",
3489
+ expected.mapping.scaleLabel,
3490
+ " (expected ",
3491
+ Math.round(expected.expectedPx * 100) / 100,
3492
+ "px)"
3493
+ ] }),
3494
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail", children: [
3495
+ "Computed: ",
3496
+ Math.round(computedFontSize * 100) / 100,
3497
+ "px",
3498
+ viaClass ? ` via ${viaClass}` : " via inherited"
3499
+ ] }),
3500
+ lineHeightDiff > 0.02 && /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail-muted", children: [
3501
+ "Line-height: ",
3502
+ Math.round(computedLineHeight * 100) / 100,
3503
+ " (expected ",
3504
+ expected.expectedLineHeight,
3505
+ ")"
3506
+ ] }),
3507
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-actions", children: [
3508
+ /* @__PURE__ */ jsx14(
3509
+ InspectorSwitch,
3510
+ {
3511
+ checked: showOnScale,
3512
+ onChange: () => handleTogglePreview(!showOnScale),
3513
+ labelOn: "Previewing",
3514
+ labelOff: "Show on-scale",
3515
+ compact: true
3516
+ }
3517
+ ),
3518
+ /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: 4, marginLeft: "auto" }, children: [
3519
+ onCreateToken && /* @__PURE__ */ jsx14(
3520
+ "button",
3521
+ {
3522
+ type: "button",
3523
+ className: "te-btn te-btn-secondary",
3524
+ onClick: () => {
3525
+ const props = [
3526
+ { property: "font-size", value: `${Math.round(computedFontSize * 100) / 100}px` }
3527
+ ];
3528
+ if (lineHeightDiff > 0.02) {
3529
+ props.push({ property: "line-height", value: `${Math.round(computedLineHeight * 100) / 100}` });
3530
+ }
3531
+ onCreateToken({
3532
+ source: "deviation",
3533
+ properties: props,
3534
+ elementTag: tagName,
3535
+ computedFontSize,
3536
+ expectedPx: expected.expectedPx,
3537
+ scaleLabel: expected.mapping.scaleLabel
3538
+ });
3539
+ },
3540
+ children: "Create token \u2192"
3541
+ }
3542
+ ),
3543
+ fontSizeClasses.length > 0 && /* @__PURE__ */ jsx14(
3544
+ "button",
3545
+ {
3546
+ type: "button",
3547
+ className: "te-btn te-btn-secondary",
3548
+ onClick: handleStripOverride,
3549
+ children: "Strip override"
3550
+ }
3551
+ )
3552
+ ] })
3553
+ ] })
3554
+ ] });
3555
+ }
3556
+ return /* @__PURE__ */ jsxs14("div", { className: "te-typo-audit", children: [
3557
+ /* @__PURE__ */ jsx14("div", { className: "te-typo-audit-header", children: "Typography Audit" }),
3558
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-status", style: { color: "#fab387" }, children: [
3559
+ "\u26A1 Off-scale (",
3560
+ formatDelta(deltaPx),
3561
+ ")"
3562
+ ] }),
3563
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail", children: [
3564
+ "<",
3565
+ tagName,
3566
+ "> \u2192 ",
3567
+ expected.mapping.scaleLabel,
3568
+ " (expected ",
3569
+ Math.round(expected.expectedPx * 100) / 100,
3570
+ "px)"
3571
+ ] }),
3572
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail", children: [
3573
+ "Computed: ",
3574
+ Math.round(computedFontSize * 100) / 100,
3575
+ "px",
3576
+ viaClass ? ` via ${viaClass}` : " via inherited"
3577
+ ] }),
3578
+ lineHeightDiff > 0.02 && /* @__PURE__ */ jsxs14("div", { className: "te-typo-detail-muted", children: [
3579
+ "Line-height: ",
3580
+ Math.round(computedLineHeight * 100) / 100,
3581
+ " (expected ",
3582
+ expected.expectedLineHeight,
3583
+ ")"
3584
+ ] }),
3585
+ /* @__PURE__ */ jsxs14("div", { className: "te-typo-actions", children: [
3586
+ /* @__PURE__ */ jsx14(
3587
+ InspectorSwitch,
3588
+ {
3589
+ checked: showOnScale,
3590
+ onChange: () => handleTogglePreview(!showOnScale),
3591
+ labelOn: "Previewing",
3592
+ labelOff: "Show on-scale",
3593
+ compact: true
3594
+ }
3595
+ ),
3596
+ onCreateToken ? /* @__PURE__ */ jsx14(
3597
+ "button",
3598
+ {
3599
+ type: "button",
3600
+ className: "te-btn te-btn-secondary",
3601
+ style: { marginLeft: "auto" },
3602
+ onClick: () => {
3603
+ const props = [
3604
+ { property: "font-size", value: `${Math.round(computedFontSize * 100) / 100}px` }
3605
+ ];
3606
+ if (lineHeightDiff > 0.02) {
3607
+ props.push({ property: "line-height", value: `${Math.round(computedLineHeight * 100) / 100}` });
3608
+ }
3609
+ const computed2 = getComputedStyle(element);
3610
+ const fontWeight = computed2.fontWeight;
3611
+ if (fontWeight && fontWeight !== "400") {
3612
+ props.push({ property: "font-weight", value: fontWeight });
3613
+ }
3614
+ const letterSpacing = computed2.letterSpacing;
3615
+ if (letterSpacing && letterSpacing !== "normal" && letterSpacing !== "0px") {
3616
+ props.push({ property: "letter-spacing", value: letterSpacing });
3617
+ }
3618
+ onCreateToken({
3619
+ source: "deviation",
3620
+ properties: props,
3621
+ elementTag: tagName,
3622
+ computedFontSize,
3623
+ expectedPx: expected.expectedPx,
3624
+ scaleLabel: expected.mapping.scaleLabel
3625
+ });
3626
+ },
3627
+ children: "Create token \u2192"
3628
+ }
3629
+ ) : /* @__PURE__ */ jsx14(
3630
+ "button",
3631
+ {
3632
+ type: "button",
3633
+ className: "te-btn te-btn-secondary",
3634
+ style: { marginLeft: "auto", opacity: 0.5, cursor: "not-allowed" },
3635
+ disabled: true,
3636
+ title: "Available in a future update",
3637
+ children: "Create token \u2192"
3638
+ }
3639
+ )
3640
+ ] })
3641
+ ] });
3642
+ }
3643
+
3644
+ // src/components/RawCssInput.tsx
3645
+ import { useCallback as useCallback15, useRef as useRef10, useState as useState14 } from "react";
3646
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
3647
+ function parseCssInput(input) {
3648
+ const results = [];
3649
+ const declarations = input.split(";").filter((s) => s.trim());
3650
+ for (const decl of declarations) {
3651
+ const colonIdx = decl.indexOf(":");
3652
+ if (colonIdx === -1) continue;
3653
+ const property = decl.slice(0, colonIdx).trim();
3654
+ const value = decl.slice(colonIdx + 1).trim();
3655
+ if (property && value) {
3656
+ results.push({ property, value });
3657
+ }
3658
+ }
3659
+ return results;
3660
+ }
3661
+ function RawCssInput({
3662
+ customStyles,
3663
+ onAddStyle,
3664
+ onRemoveStyle,
3665
+ onClearAll,
3666
+ onCreateToken,
3667
+ elementTag
3668
+ }) {
3669
+ const [inputValue, setInputValue] = useState14("");
3670
+ const [editingProp, setEditingProp] = useState14(null);
3671
+ const [editValue, setEditValue] = useState14("");
3672
+ const inputRef = useRef10(null);
3673
+ const editRef = useRef10(null);
3674
+ const handleSubmit = useCallback15(() => {
3675
+ const parsed = parseCssInput(inputValue);
3676
+ for (const { property, value } of parsed) {
3677
+ onAddStyle(property, value);
3678
+ }
3679
+ if (parsed.length > 0) {
3680
+ setInputValue("");
3681
+ }
3682
+ }, [inputValue, onAddStyle]);
3683
+ const handleKeyDown = useCallback15(
3684
+ (e) => {
3685
+ if (e.key === "Enter") {
3686
+ e.preventDefault();
3687
+ handleSubmit();
3688
+ } else if (e.key === "Escape") {
3689
+ setInputValue("");
3690
+ inputRef.current?.blur();
3691
+ }
3692
+ },
3693
+ [handleSubmit]
3694
+ );
3695
+ const handleStartEdit = useCallback15(
3696
+ (property, value) => {
3697
+ setEditingProp(property);
3698
+ setEditValue(value);
3699
+ requestAnimationFrame(() => editRef.current?.focus());
3700
+ },
3701
+ []
3702
+ );
3703
+ const handleEditCommit = useCallback15(() => {
3704
+ if (editingProp && editValue.trim()) {
3705
+ onAddStyle(editingProp, editValue.trim());
3706
+ }
3707
+ setEditingProp(null);
3708
+ setEditValue("");
3709
+ }, [editingProp, editValue, onAddStyle]);
3710
+ const handleEditKeyDown = useCallback15(
3711
+ (e) => {
3712
+ if (e.key === "Enter") {
3713
+ e.preventDefault();
3714
+ handleEditCommit();
3715
+ } else if (e.key === "Escape") {
3716
+ setEditingProp(null);
3717
+ setEditValue("");
3718
+ }
3719
+ },
3720
+ [handleEditCommit]
3721
+ );
3722
+ const handleCreateToken = useCallback15(() => {
3723
+ if (!onCreateToken || customStyles.length === 0) return;
3724
+ onCreateToken({
3725
+ source: "custom-css",
3726
+ properties: customStyles.map((s) => ({
3727
+ property: s.property,
3728
+ value: s.value
3729
+ })),
3730
+ elementTag
3731
+ });
3732
+ }, [onCreateToken, customStyles, elementTag]);
3733
+ return /* @__PURE__ */ jsxs15("div", { className: "te-raw-css-section", children: [
3734
+ /* @__PURE__ */ jsxs15("div", { className: "te-class-group-header", style: { marginBottom: 4 }, children: [
3735
+ /* @__PURE__ */ jsx15("span", { children: "Custom Styles" }),
3736
+ customStyles.length > 0 && /* @__PURE__ */ jsx15(
3737
+ "button",
3738
+ {
3739
+ type: "button",
3740
+ className: "te-raw-css-clear",
3741
+ onClick: onClearAll,
3742
+ children: "Clear all"
3743
+ }
3744
+ )
3745
+ ] }),
3746
+ customStyles.length > 0 && /* @__PURE__ */ jsx15("div", { className: "te-class-chips", style: { marginBottom: 6 }, children: customStyles.map((entry) => /* @__PURE__ */ jsxs15("span", { className: "te-chip te-chip-custom", children: [
3747
+ /* @__PURE__ */ jsxs15("span", { className: "te-chip-prop", children: [
3748
+ entry.property,
3749
+ ":"
3750
+ ] }),
3751
+ editingProp === entry.property ? /* @__PURE__ */ jsx15(
3752
+ "input",
3753
+ {
3754
+ ref: editRef,
3755
+ type: "text",
3756
+ className: "te-chip-edit",
3757
+ value: editValue,
3758
+ onChange: (e) => setEditValue(e.target.value),
3759
+ onKeyDown: handleEditKeyDown,
3760
+ onBlur: handleEditCommit,
3761
+ style: { width: Math.max(40, editValue.length * 7) }
3762
+ }
3763
+ ) : /* @__PURE__ */ jsx15(
3764
+ "span",
3765
+ {
3766
+ className: "te-chip-value",
3767
+ onDoubleClick: () => handleStartEdit(entry.property, entry.value),
3768
+ title: "Double-click to edit value",
3769
+ children: entry.value
3770
+ }
3771
+ ),
3772
+ /* @__PURE__ */ jsx15(
3773
+ "button",
3774
+ {
3775
+ type: "button",
3776
+ className: "te-chip-remove",
3777
+ onClick: () => onRemoveStyle(entry.property),
3778
+ children: "\xD7"
3779
+ }
3780
+ )
3781
+ ] }, entry.property)) }),
3782
+ /* @__PURE__ */ jsx15(
3783
+ "input",
3784
+ {
3785
+ ref: inputRef,
3786
+ type: "text",
3787
+ className: "te-add-class-input",
3788
+ value: inputValue,
3789
+ onChange: (e) => setInputValue(e.target.value),
3790
+ onKeyDown: handleKeyDown,
3791
+ placeholder: "font-size: 18px; color: red",
3792
+ style: { width: "100%" }
3793
+ }
3794
+ ),
3795
+ customStyles.length > 0 && onCreateToken && /* @__PURE__ */ jsx15(
3796
+ "button",
3797
+ {
3798
+ type: "button",
3799
+ className: "te-btn te-btn-secondary",
3800
+ onClick: handleCreateToken,
3801
+ style: { marginTop: 6, width: "100%", justifyContent: "center" },
3802
+ children: "Create token \u2192"
3803
+ }
3804
+ )
3805
+ ] });
3806
+ }
3807
+
3808
+ // src/components/TokenCreationForm.tsx
3809
+ import { useCallback as useCallback16, useMemo as useMemo7, useState as useState15 } from "react";
3810
+ import { Copy as Copy4, Check as Check3 } from "lucide-react";
3811
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
3812
+ function toKebab2(str) {
3813
+ return str.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3814
+ }
3815
+ function TokenCreationForm({
3816
+ context,
3817
+ onBack,
3818
+ onApplyLocally
3819
+ }) {
3820
+ const [tokenName, setTokenName] = useState15(() => {
3821
+ if (context.source === "deviation" && context.scaleLabel) {
3822
+ return toKebab2(context.scaleLabel) + "-override";
3823
+ }
3824
+ return "";
3825
+ });
3826
+ const [selectedProps, setSelectedProps] = useState15(() => {
3827
+ const set = /* @__PURE__ */ new Set();
3828
+ for (const p of context.properties) {
3829
+ set.add(p.property);
3830
+ }
3831
+ return set;
3832
+ });
3833
+ const [scaleType, setScaleType] = useState15("independent");
3834
+ const [copied, setCopied] = useState15(false);
3835
+ const { tokenOverrides } = useDevLensConfig();
3836
+ const registry = useMemo7(
3837
+ () => buildRegistry(autoDetectTokens(), tokenOverrides),
3838
+ [tokenOverrides]
3839
+ );
3840
+ const kebabName = toKebab2(tokenName);
3841
+ const collision = useMemo7(() => {
3842
+ if (!kebabName) return false;
3843
+ return `--${kebabName}` in registry;
3844
+ }, [kebabName, registry]);
3845
+ const multiplier = useMemo7(() => {
3846
+ if (context.source !== "deviation" || !context.computedFontSize) return void 0;
3847
+ const fontBase = 16;
3848
+ return Math.round(context.computedFontSize / fontBase * 1e3) / 1e3;
3849
+ }, [context]);
3850
+ const toggleProp = useCallback16((prop) => {
3851
+ setSelectedProps((prev) => {
3852
+ const next = new Set(prev);
3853
+ if (next.has(prop)) {
3854
+ if (context.source === "deviation" && prop === "font-size") return prev;
3855
+ next.delete(prop);
3856
+ } else {
3857
+ next.add(prop);
3858
+ }
3859
+ return next;
3860
+ });
3861
+ }, [context.source]);
3862
+ const activeProperties = useMemo7(
3863
+ () => context.properties.filter((p) => selectedProps.has(p.property)),
3864
+ [context.properties, selectedProps]
3865
+ );
3866
+ const outputPreview = useMemo7(() => {
3867
+ if (!kebabName || activeProperties.length === 0) return "";
3868
+ const lines = [];
3869
+ lines.push("/* :root */");
3870
+ for (const p of activeProperties) {
3871
+ if (scaleType === "derived" && multiplier && p.property === "font-size") {
3872
+ lines.push(`--${kebabName}-${p.property}: calc(var(--font-base) * ${multiplier});`);
3873
+ } else {
3874
+ lines.push(`--${kebabName}-${p.property}: ${p.value};`);
3875
+ }
3876
+ }
3877
+ lines.push("");
3878
+ lines.push("/* @theme inline */");
3879
+ for (const p of activeProperties) {
3880
+ lines.push(`--${kebabName}-${p.property}: var(--${kebabName}-${p.property});`);
3881
+ }
3882
+ lines.push("");
3883
+ lines.push("/* token-registry.ts */");
3884
+ for (const p of activeProperties) {
3885
+ lines.push(`'--${kebabName}-${p.property}': { group: 'Typography Scale' },`);
3886
+ }
3887
+ return lines.join("\n");
3888
+ }, [kebabName, activeProperties, scaleType, multiplier]);
3889
+ const handleCopy = useCallback16(async () => {
3890
+ const prompt = generateCCPrompt({
3891
+ tokenName: kebabName,
3892
+ properties: activeProperties.map((p) => ({
3893
+ property: p.property,
3894
+ value: p.value,
3895
+ locked: context.source === "deviation" && p.property === "font-size"
3896
+ })),
3897
+ context: context.source,
3898
+ elementTag: context.elementTag,
3899
+ elementPath: context.elementPath,
3900
+ scaleRelationship: context.source === "deviation" ? {
3901
+ type: scaleType,
3902
+ baseVar: "--font-base",
3903
+ multiplier: scaleType === "derived" ? multiplier : void 0
3904
+ } : void 0
3905
+ });
3906
+ try {
3907
+ await navigator.clipboard.writeText(prompt);
3908
+ } catch {
3909
+ const textarea = document.createElement("textarea");
3910
+ textarea.value = prompt;
3911
+ textarea.style.position = "fixed";
3912
+ textarea.style.opacity = "0";
3913
+ document.body.appendChild(textarea);
3914
+ textarea.select();
3915
+ document.execCommand("copy");
3916
+ document.body.removeChild(textarea);
3917
+ }
3918
+ setCopied(true);
3919
+ setTimeout(() => setCopied(false), 2e3);
3920
+ }, [kebabName, activeProperties, context, scaleType, multiplier]);
3921
+ const handleApplyLocally = useCallback16(() => {
3922
+ if (onApplyLocally && kebabName) {
3923
+ onApplyLocally(kebabName, activeProperties);
3924
+ }
3925
+ }, [onApplyLocally, kebabName, activeProperties]);
3926
+ const isValid = kebabName.length > 0 && !collision && activeProperties.length > 0;
3927
+ return /* @__PURE__ */ jsxs16("div", { className: "te-token-form", children: [
3928
+ /* @__PURE__ */ jsx16(
3929
+ "button",
3930
+ {
3931
+ type: "button",
3932
+ className: "te-token-form-back",
3933
+ onClick: onBack,
3934
+ children: "\u2190 Back"
3935
+ }
3936
+ ),
3937
+ /* @__PURE__ */ jsx16("div", { className: "te-token-form-title", children: "Create Token" }),
3938
+ /* @__PURE__ */ jsx16("div", { className: "te-token-form-context", children: context.source === "deviation" ? /* @__PURE__ */ jsxs16("span", { children: [
3939
+ "From typography deviation on <",
3940
+ context.elementTag,
3941
+ ">"
3942
+ ] }) : /* @__PURE__ */ jsxs16("span", { children: [
3943
+ "From custom CSS on <",
3944
+ context.elementTag,
3945
+ ">"
3946
+ ] }) }),
3947
+ /* @__PURE__ */ jsxs16("div", { className: "te-token-form-field", children: [
3948
+ /* @__PURE__ */ jsx16("label", { className: "te-label", children: "Token Name" }),
3949
+ /* @__PURE__ */ jsxs16("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
3950
+ /* @__PURE__ */ jsx16("span", { className: "te-muted", children: "--" }),
3951
+ /* @__PURE__ */ jsx16(
3952
+ "input",
3953
+ {
3954
+ type: "text",
3955
+ className: "te-text-input",
3956
+ value: tokenName,
3957
+ onChange: (e) => setTokenName(e.target.value),
3958
+ placeholder: "my-custom-size",
3959
+ style: { flex: 1 }
3960
+ }
3961
+ )
3962
+ ] }),
3963
+ collision && /* @__PURE__ */ jsx16("div", { className: "te-token-form-error", children: "Token name already exists in the registry" })
3964
+ ] }),
3965
+ /* @__PURE__ */ jsxs16("div", { className: "te-token-form-field", children: [
3966
+ /* @__PURE__ */ jsx16("label", { className: "te-label", children: "Properties" }),
3967
+ context.properties.map((p) => {
3968
+ const isLocked = context.source === "deviation" && p.property === "font-size";
3969
+ return /* @__PURE__ */ jsxs16("label", { className: "te-token-form-checkbox", children: [
3970
+ /* @__PURE__ */ jsx16(
3971
+ "input",
3972
+ {
3973
+ type: "checkbox",
3974
+ checked: selectedProps.has(p.property),
3975
+ onChange: () => toggleProp(p.property),
3976
+ disabled: isLocked
3977
+ }
3978
+ ),
3979
+ /* @__PURE__ */ jsxs16("span", { className: "te-token-form-prop-name", children: [
3980
+ p.property,
3981
+ ":"
3982
+ ] }),
3983
+ /* @__PURE__ */ jsx16("span", { className: "te-token-form-prop-value", children: p.value }),
3984
+ isLocked && /* @__PURE__ */ jsx16("span", { className: "te-muted", children: "(locked)" })
3985
+ ] }, p.property);
3986
+ })
3987
+ ] }),
3988
+ context.source === "deviation" && /* @__PURE__ */ jsxs16("div", { className: "te-token-form-field", children: [
3989
+ /* @__PURE__ */ jsx16("label", { className: "te-label", children: "Scale Relationship" }),
3990
+ /* @__PURE__ */ jsxs16("div", { className: "te-token-form-radio-group", children: [
3991
+ /* @__PURE__ */ jsxs16("label", { className: "te-token-form-radio", children: [
3992
+ /* @__PURE__ */ jsx16(
3993
+ "input",
3994
+ {
3995
+ type: "radio",
3996
+ name: "scaleType",
3997
+ value: "independent",
3998
+ checked: scaleType === "independent",
3999
+ onChange: () => setScaleType("independent")
4000
+ }
4001
+ ),
4002
+ "Independent"
4003
+ ] }),
4004
+ /* @__PURE__ */ jsxs16("label", { className: "te-token-form-radio", children: [
4005
+ /* @__PURE__ */ jsx16(
4006
+ "input",
4007
+ {
4008
+ type: "radio",
4009
+ name: "scaleType",
4010
+ value: "derived",
4011
+ checked: scaleType === "derived",
4012
+ onChange: () => setScaleType("derived")
4013
+ }
4014
+ ),
4015
+ "Derived from --font-base",
4016
+ scaleType === "derived" && multiplier && /* @__PURE__ */ jsxs16("span", { className: "te-muted", style: { marginLeft: 4 }, children: [
4017
+ "(",
4018
+ multiplier,
4019
+ "\xD7)"
4020
+ ] })
4021
+ ] })
4022
+ ] })
4023
+ ] }),
4024
+ kebabName && activeProperties.length > 0 && /* @__PURE__ */ jsxs16("div", { className: "te-token-form-field", children: [
4025
+ /* @__PURE__ */ jsx16("label", { className: "te-label", children: "Output Preview" }),
4026
+ /* @__PURE__ */ jsx16("pre", { className: "te-diff-content", style: { fontSize: 10 }, children: outputPreview })
4027
+ ] }),
4028
+ /* @__PURE__ */ jsxs16("div", { className: "te-token-form-actions", children: [
4029
+ /* @__PURE__ */ jsxs16(
4030
+ "button",
4031
+ {
4032
+ type: "button",
4033
+ className: "te-btn te-btn-primary",
4034
+ onClick: handleCopy,
4035
+ disabled: !isValid,
4036
+ style: !isValid ? { opacity: 0.5, cursor: "not-allowed" } : void 0,
4037
+ children: [
4038
+ copied ? /* @__PURE__ */ jsx16(Check3, { size: 12 }) : /* @__PURE__ */ jsx16(Copy4, { size: 12 }),
4039
+ copied ? "Copied!" : "Copy as CC Prompt"
4040
+ ]
4041
+ }
4042
+ ),
4043
+ onApplyLocally && /* @__PURE__ */ jsx16(
4044
+ "button",
4045
+ {
4046
+ type: "button",
4047
+ className: "te-btn te-btn-secondary",
4048
+ onClick: handleApplyLocally,
4049
+ disabled: !isValid,
4050
+ style: !isValid ? { opacity: 0.5, cursor: "not-allowed" } : void 0,
4051
+ children: "Apply Locally"
4052
+ }
4053
+ )
4054
+ ] })
4055
+ ] });
4056
+ }
4057
+
4058
+ // src/components/ElementInspectorTab.tsx
4059
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
4060
+ function ElementInspectorTab({
4061
+ inspector,
4062
+ isDetached,
4063
+ scaleBaseline
4064
+ }) {
4065
+ const { projectTextSizeClasses } = useDevLensConfig();
4066
+ const projectTextSizeSet = useMemo8(
4067
+ () => new Set(projectTextSizeClasses),
4068
+ [projectTextSizeClasses]
4069
+ );
4070
+ const [addValue, setAddValue] = useState16("");
4071
+ const [suggestions, setSuggestions] = useState16([]);
4072
+ const [showSuggestions, setShowSuggestions] = useState16(false);
4073
+ const addInputRef = useRef11(null);
4074
+ const [showTokenForm, setShowTokenForm] = useState16(false);
4075
+ const [tokenFormContext, setTokenFormContext] = useState16(null);
4076
+ const prevElementRef = useRef11(null);
4077
+ useEffect11(() => {
4078
+ const currentEl = inspector.selectedElement;
4079
+ if (prevElementRef.current && prevElementRef.current !== currentEl) {
4080
+ if (inspector.customStyles.length > 0) {
4081
+ inspector.clearCustomStyles();
4082
+ }
4083
+ setShowTokenForm(false);
4084
+ setTokenFormContext(null);
4085
+ }
4086
+ prevElementRef.current = currentEl;
4087
+ }, [inspector.selectedElement]);
4088
+ useEffect11(() => {
4089
+ if (!isDetached && !inspector.selectedElement && !inspector.isSelectorActive) {
4090
+ inspector.activateSelector();
4091
+ }
4092
+ return () => {
4093
+ inspector.deactivateSelector();
4094
+ };
4095
+ }, []);
4096
+ const identifier = inspector.selectedElement ? inspector.getElementIdentifier() : null;
4097
+ const categoryGroups = categorizeClasses(inspector.currentClasses, projectTextSizeSet);
4098
+ const conflicts = useMemo8(
4099
+ () => detectConflicts(inspector.currentClasses),
4100
+ [inspector.currentClasses]
4101
+ );
4102
+ const handleGeneralAdd = useCallback17(() => {
4103
+ const trimmed = addValue.trim();
4104
+ if (trimmed) {
4105
+ inspector.addClass(trimmed);
4106
+ setAddValue("");
4107
+ setSuggestions([]);
4108
+ setShowSuggestions(false);
4109
+ const input = addInputRef.current;
4110
+ if (input) {
4111
+ input.classList.add("te-add-success");
4112
+ setTimeout(() => input.classList.remove("te-add-success"), 300);
4113
+ requestAnimationFrame(() => input.focus());
4114
+ }
4115
+ }
4116
+ }, [addValue, inspector]);
4117
+ const handleGeneralAddKeyDown = useCallback17(
4118
+ (e) => {
4119
+ if (showSuggestions && suggestions.length > 0) {
4120
+ if (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Escape") return;
4121
+ }
4122
+ if (e.key === "Enter") {
4123
+ e.preventDefault();
4124
+ handleGeneralAdd();
4125
+ } else if (e.key === "Escape") {
4126
+ e.preventDefault();
4127
+ setAddValue("");
4128
+ setShowSuggestions(false);
4129
+ setSuggestions([]);
4130
+ addInputRef.current?.blur();
4131
+ }
4132
+ },
4133
+ [handleGeneralAdd, showSuggestions, suggestions]
4134
+ );
4135
+ const handleGeneralAddBlur = useCallback17(() => {
4136
+ setTimeout(() => {
4137
+ const trimmed = addValue.trim();
4138
+ if (trimmed) {
4139
+ inspector.addClass(trimmed);
4140
+ setAddValue("");
4141
+ setSuggestions([]);
4142
+ setShowSuggestions(false);
4143
+ }
4144
+ }, 200);
4145
+ }, [addValue, inspector]);
4146
+ const handleAddChange = useCallback17((e) => {
4147
+ const val = e.target.value;
4148
+ setAddValue(val);
4149
+ const dict = getClassDictionary();
4150
+ const results = filterSuggestions(dict, val, 30);
4151
+ setSuggestions(results);
4152
+ setShowSuggestions(results.length > 0);
4153
+ }, []);
4154
+ const handleSuggestionSelect = useCallback17(
4155
+ (cls) => {
4156
+ inspector.addClass(cls);
4157
+ setAddValue("");
4158
+ setSuggestions([]);
4159
+ setShowSuggestions(false);
4160
+ requestAnimationFrame(() => addInputRef.current?.focus());
4161
+ },
4162
+ [inspector]
4163
+ );
4164
+ const handleCreateToken = useCallback17((context) => {
4165
+ setTokenFormContext(context);
4166
+ setShowTokenForm(true);
4167
+ }, []);
4168
+ const handleTokenFormBack = useCallback17(() => {
4169
+ setShowTokenForm(false);
4170
+ setTokenFormContext(null);
4171
+ }, []);
4172
+ if (isDetached) {
4173
+ return /* @__PURE__ */ jsxs17("div", { className: "te-inspector-empty", children: [
4174
+ /* @__PURE__ */ jsx17("p", { style: { marginBottom: 4 }, children: "Inspector requires docked mode." }),
4175
+ /* @__PURE__ */ jsx17("p", { className: "te-muted", children: "Detached mode coming in a future update." })
4176
+ ] });
4177
+ }
4178
+ if (!inspector.selectedElement || !identifier) {
4179
+ return /* @__PURE__ */ jsxs17("div", { className: "te-inspector-empty", children: [
4180
+ inspector.isSelectorActive ? /* @__PURE__ */ jsx17("p", { children: "Click any element on the page to inspect it" }) : /* @__PURE__ */ jsxs17(Fragment5, { children: [
4181
+ /* @__PURE__ */ jsx17("p", { style: { marginBottom: 12 }, children: "Select an element to inspect its classes" }),
4182
+ /* @__PURE__ */ jsxs17(
4183
+ "button",
4184
+ {
4185
+ type: "button",
4186
+ className: "te-btn te-btn-secondary",
4187
+ onClick: () => inspector.activateSelector(),
4188
+ children: [
4189
+ /* @__PURE__ */ jsx17(MousePointer, { size: 12 }),
4190
+ "Select Element"
4191
+ ]
4192
+ }
4193
+ )
4194
+ ] }),
4195
+ /* @__PURE__ */ jsx17(
4196
+ ElementSelectorOverlay,
4197
+ {
4198
+ isActive: inspector.isSelectorActive,
4199
+ onSelect: inspector.selectElement,
4200
+ onDeactivate: () => {
4201
+ if (inspector.isSelectorActive) inspector.toggleSelector();
4202
+ }
4203
+ }
4204
+ )
4205
+ ] });
4206
+ }
4207
+ if (showTokenForm && tokenFormContext) {
4208
+ return /* @__PURE__ */ jsx17("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: /* @__PURE__ */ jsx17("div", { style: { flex: 1, overflowY: "auto" }, children: /* @__PURE__ */ jsx17(
4209
+ TokenCreationForm,
4210
+ {
4211
+ context: tokenFormContext,
4212
+ onBack: handleTokenFormBack,
4213
+ onApplyLocally: (tokenName, properties) => {
4214
+ for (const p of properties) {
4215
+ inspector.addCustomStyle(p.property, p.value);
4216
+ }
4217
+ handleTokenFormBack();
4218
+ }
4219
+ }
4220
+ ) }) });
4221
+ }
4222
+ return /* @__PURE__ */ jsxs17("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
4223
+ /* @__PURE__ */ jsxs17("div", { className: "te-element-info", children: [
4224
+ /* @__PURE__ */ jsxs17("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
4225
+ /* @__PURE__ */ jsxs17("span", { className: "te-element-tag", children: [
4226
+ "<",
4227
+ identifier.tagName,
4228
+ ">"
4229
+ ] }),
4230
+ /* @__PURE__ */ jsx17(
4231
+ InspectorSwitch,
4232
+ {
4233
+ checked: inspector.isSelectorActive,
4234
+ onChange: inspector.toggleSelector,
4235
+ labelOn: "Selecting\u2026",
4236
+ labelOff: "Re-select",
4237
+ compact: true
4238
+ }
4239
+ )
4240
+ ] }),
4241
+ identifier.textPreview && /* @__PURE__ */ jsxs17("div", { className: "te-element-text", children: [
4242
+ '"',
4243
+ identifier.textPreview,
4244
+ '"'
4245
+ ] }),
4246
+ /* @__PURE__ */ jsx17("div", { className: "te-breadcrumb", children: identifier.breadcrumb.map((segment, i) => /* @__PURE__ */ jsxs17("span", { children: [
4247
+ i > 0 && /* @__PURE__ */ jsx17("span", { style: { color: "#45475a", margin: "0 2px" }, children: "\u203A" }),
4248
+ segment
4249
+ ] }, i)) }),
4250
+ identifier.testId && /* @__PURE__ */ jsxs17("div", { className: "te-muted", style: { marginTop: 2 }, children: [
4251
+ 'data-testid="',
4252
+ identifier.testId,
4253
+ '"'
4254
+ ] }),
4255
+ identifier.id && /* @__PURE__ */ jsxs17("div", { className: "te-muted", style: { marginTop: 2 }, children: [
4256
+ 'id="',
4257
+ identifier.id,
4258
+ '"'
4259
+ ] })
4260
+ ] }),
4261
+ /* @__PURE__ */ jsxs17("div", { style: { flex: 1, overflowY: "auto" }, children: [
4262
+ categoryGroups.length === 0 ? /* @__PURE__ */ jsx17("div", { className: "te-muted", style: { textAlign: "center", padding: 16 }, children: "No classes on this element." }) : categoryGroups.map((group) => /* @__PURE__ */ jsx17(
4263
+ ClassCategoryGroup,
4264
+ {
4265
+ category: group.category,
4266
+ classes: group.classes,
4267
+ originalClasses: inspector.originalClasses,
4268
+ onUpdate: inspector.updateClass,
4269
+ onRemove: inspector.removeClass,
4270
+ conflicts
4271
+ },
4272
+ group.category
4273
+ )),
4274
+ inspector.selectedElement && /* @__PURE__ */ jsx17(
4275
+ TypographyAudit,
4276
+ {
4277
+ element: inspector.selectedElement,
4278
+ currentClasses: inspector.currentClasses,
4279
+ onRemoveClass: inspector.removeClass,
4280
+ isBaselineActive: scaleBaseline.isActive,
4281
+ onCreateToken: handleCreateToken
4282
+ }
4283
+ ),
4284
+ /* @__PURE__ */ jsx17(
4285
+ TokenMigrationSuggestions,
4286
+ {
4287
+ classes: inspector.currentClasses,
4288
+ onApply: inspector.updateClass
4289
+ }
4290
+ ),
4291
+ /* @__PURE__ */ jsx17("div", { style: { padding: "8px 0", borderTop: "1px solid #313244", marginTop: 4 }, children: /* @__PURE__ */ jsx17(
4292
+ RawCssInput,
4293
+ {
4294
+ customStyles: inspector.customStyles,
4295
+ onAddStyle: inspector.addCustomStyle,
4296
+ onRemoveStyle: inspector.removeCustomStyle,
4297
+ onClearAll: inspector.clearCustomStyles,
4298
+ onCreateToken: handleCreateToken,
4299
+ elementTag: identifier.tagName
4300
+ }
4301
+ ) }),
4302
+ /* @__PURE__ */ jsxs17("div", { style: { padding: "8px 0", borderTop: "1px solid #313244", marginTop: 4 }, children: [
4303
+ /* @__PURE__ */ jsx17("div", { className: "te-class-group-header", style: { marginBottom: 4 }, children: /* @__PURE__ */ jsx17("span", { children: "Add Class" }) }),
4304
+ /* @__PURE__ */ jsxs17("div", { children: [
4305
+ /* @__PURE__ */ jsx17(
4306
+ "input",
4307
+ {
4308
+ ref: addInputRef,
4309
+ type: "text",
4310
+ className: "te-add-class-input",
4311
+ value: addValue,
4312
+ onChange: handleAddChange,
4313
+ onKeyDown: handleGeneralAddKeyDown,
4314
+ onBlur: handleGeneralAddBlur,
4315
+ placeholder: "Type class name\u2026",
4316
+ style: { width: "100%" }
4317
+ }
4318
+ ),
4319
+ /* @__PURE__ */ jsx17(
4320
+ ClassSuggestionDropdown,
4321
+ {
4322
+ suggestions,
4323
+ inputValue: addValue,
4324
+ onSelect: handleSuggestionSelect,
4325
+ onDismiss: () => setShowSuggestions(false),
4326
+ visible: showSuggestions,
4327
+ inputRef: addInputRef
4328
+ }
4329
+ )
4330
+ ] })
4331
+ ] }),
4332
+ /* @__PURE__ */ jsx17("div", { style: { padding: "8px 0", borderTop: "1px solid #313244", marginTop: 4, textAlign: "center" }, children: /* @__PURE__ */ jsx17(
4333
+ "button",
4334
+ {
4335
+ type: "button",
4336
+ className: "te-btn te-btn-secondary",
4337
+ onClick: () => {
4338
+ inspector.clearSelection();
4339
+ inspector.activateSelector();
4340
+ },
4341
+ children: "Cancel"
4342
+ }
4343
+ ) })
4344
+ ] }),
4345
+ /* @__PURE__ */ jsx17(
4346
+ ElementSelectorOverlay,
4347
+ {
4348
+ isActive: inspector.isSelectorActive,
4349
+ onSelect: inspector.selectElement,
4350
+ onDeactivate: () => {
4351
+ if (inspector.isSelectorActive) inspector.toggleSelector();
4352
+ }
4353
+ }
4354
+ )
4355
+ ] });
4356
+ }
4357
+
4358
+ // src/components/DevLensDrawer.tsx
4359
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
4360
+ var DRAWER_SIZE = 360;
4361
+ var dockIcons = [
4362
+ { pos: "left", icon: PanelLeft, label: "Dock left" },
4363
+ { pos: "right", icon: PanelRight, label: "Dock right" },
4364
+ { pos: "bottom", icon: PanelBottom, label: "Dock bottom" },
4365
+ { pos: "detached", icon: ExternalLink, label: "Detach window" }
4366
+ ];
4367
+ function DevLensDrawer() {
4368
+ const { dock, setDock, isOpen, setIsOpen } = useDockPosition();
4369
+ const { isDetached, detach, reattach } = useDetachedWindow();
4370
+ const { previewFontFamily } = useDevLensConfig();
4371
+ const editor = useTokenEditor();
4372
+ const inspector = useElementInspector();
4373
+ const scaleBaseline = useScaleBaseline();
4374
+ const [activeTab, setActiveTab] = useState17("tokens");
4375
+ const changeCount = editor.getChangeCount();
4376
+ const combinedChangeCount = changeCount + inspector.changeCount + inspector.customStyleChangeCount;
4377
+ const handleDockChange = (pos) => {
4378
+ if (pos === "detached") {
4379
+ detach();
4380
+ setIsOpen(false);
4381
+ } else {
4382
+ if (isDetached) reattach();
4383
+ setDock(pos);
4384
+ setIsOpen(true);
4385
+ }
4386
+ };
4387
+ useEffect12(() => {
4388
+ if (!isOpen || isDetached) {
4389
+ document.body.style.marginLeft = "";
4390
+ document.body.style.marginRight = "";
4391
+ document.body.style.marginBottom = "";
4392
+ return;
4393
+ }
4394
+ const px = `${DRAWER_SIZE}px`;
4395
+ if (dock === "left") {
4396
+ document.body.style.marginLeft = px;
4397
+ document.body.style.marginRight = "";
4398
+ document.body.style.marginBottom = "";
4399
+ } else if (dock === "right") {
4400
+ document.body.style.marginRight = px;
4401
+ document.body.style.marginLeft = "";
4402
+ document.body.style.marginBottom = "";
4403
+ } else if (dock === "bottom") {
4404
+ document.body.style.marginBottom = px;
4405
+ document.body.style.marginLeft = "";
4406
+ document.body.style.marginRight = "";
4407
+ }
4408
+ return () => {
4409
+ document.body.style.marginLeft = "";
4410
+ document.body.style.marginRight = "";
4411
+ document.body.style.marginBottom = "";
4412
+ };
4413
+ }, [isOpen, dock, isDetached]);
4414
+ const drawerStyle = useMemo9(() => {
4415
+ const base = {
4416
+ position: "fixed",
4417
+ zIndex: 9999,
4418
+ display: isOpen && !isDetached ? "flex" : "none",
4419
+ flexDirection: "column",
4420
+ background: "#1e1e2e",
4421
+ color: "#cdd6f4",
4422
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
4423
+ fontSize: "13px",
4424
+ boxShadow: "0 0 20px rgba(0,0,0,0.3)",
4425
+ "--devlens-preview-font": previewFontFamily
4426
+ };
4427
+ switch (dock) {
4428
+ case "left":
4429
+ return { ...base, top: 0, left: 0, bottom: 0, width: DRAWER_SIZE };
4430
+ case "right":
4431
+ return { ...base, top: 0, right: 0, bottom: 0, width: DRAWER_SIZE };
4432
+ case "bottom":
4433
+ return { ...base, left: 0, right: 0, bottom: 0, height: DRAWER_SIZE };
4434
+ default:
4435
+ return base;
4436
+ }
4437
+ }, [dock, isOpen, isDetached, previewFontFamily]);
4438
+ return /* @__PURE__ */ jsxs18(Fragment6, { children: [
4439
+ /* @__PURE__ */ jsxs18("div", { style: drawerStyle, className: "te-drawer", children: [
4440
+ /* @__PURE__ */ jsxs18("div", { className: "te-header", children: [
4441
+ /* @__PURE__ */ jsxs18("div", { className: "te-header-title", children: [
4442
+ /* @__PURE__ */ jsx18(Palette, { size: 16 }),
4443
+ /* @__PURE__ */ jsx18("span", { children: "DevLens" }),
4444
+ combinedChangeCount > 0 && /* @__PURE__ */ jsx18("span", { className: "te-badge", children: combinedChangeCount })
4445
+ ] }),
4446
+ /* @__PURE__ */ jsxs18("div", { className: "te-header-actions", children: [
4447
+ /* @__PURE__ */ jsx18("div", { className: "te-dock-strip", children: dockIcons.map(({ pos, icon: Icon, label }) => /* @__PURE__ */ jsx18(
4448
+ "button",
4449
+ {
4450
+ type: "button",
4451
+ className: `te-dock-btn ${dock === pos ? "te-dock-active" : ""}`,
4452
+ onClick: () => handleDockChange(pos),
4453
+ title: label,
4454
+ children: /* @__PURE__ */ jsx18(Icon, { size: 14 })
4455
+ },
4456
+ pos
4457
+ )) }),
4458
+ /* @__PURE__ */ jsx18(
4459
+ "button",
4460
+ {
4461
+ type: "button",
4462
+ className: "te-icon-btn",
4463
+ onClick: () => setIsOpen(false),
4464
+ title: "Close",
4465
+ children: /* @__PURE__ */ jsx18(X, { size: 16 })
4466
+ }
4467
+ )
4468
+ ] })
4469
+ ] }),
4470
+ /* @__PURE__ */ jsxs18("div", { className: "te-tabs", children: [
4471
+ /* @__PURE__ */ jsxs18(
4472
+ "button",
4473
+ {
4474
+ type: "button",
4475
+ className: `te-tab ${activeTab === "tokens" ? "te-tab-active" : ""}`,
4476
+ onClick: () => setActiveTab("tokens"),
4477
+ children: [
4478
+ /* @__PURE__ */ jsx18(Palette, { size: 12 }),
4479
+ "Tokens"
4480
+ ]
4481
+ }
4482
+ ),
4483
+ /* @__PURE__ */ jsxs18(
4484
+ "button",
4485
+ {
4486
+ type: "button",
4487
+ className: `te-tab ${activeTab === "inspector" ? "te-tab-active" : ""}`,
4488
+ onClick: () => setActiveTab("inspector"),
4489
+ children: [
4490
+ /* @__PURE__ */ jsx18(MousePointer2, { size: 12 }),
4491
+ "Inspector",
4492
+ inspector.changeCount > 0 && /* @__PURE__ */ jsx18("span", { className: "te-badge te-badge-sm", children: inspector.changeCount })
4493
+ ]
4494
+ }
4495
+ ),
4496
+ /* @__PURE__ */ jsxs18(
4497
+ "button",
4498
+ {
4499
+ type: "button",
4500
+ className: `te-tab ${activeTab === "diff" ? "te-tab-active" : ""}`,
4501
+ onClick: () => setActiveTab("diff"),
4502
+ children: [
4503
+ /* @__PURE__ */ jsx18(FileCode, { size: 12 }),
4504
+ "Changes",
4505
+ combinedChangeCount > 0 && /* @__PURE__ */ jsx18("span", { className: "te-badge te-badge-sm", children: combinedChangeCount })
4506
+ ]
4507
+ }
4508
+ )
4509
+ ] }),
4510
+ /* @__PURE__ */ jsxs18("div", { className: "te-content", children: [
4511
+ activeTab === "tokens" && /* @__PURE__ */ jsxs18(Fragment6, { children: [
4512
+ /* @__PURE__ */ jsx18(
4513
+ TokenEditorControls,
4514
+ {
4515
+ registry: editor.registry,
4516
+ getCurrentValue: editor.getCurrentValue,
4517
+ isModified: editor.isModified,
4518
+ updateToken: editor.updateToken,
4519
+ resetToken: editor.resetToken,
4520
+ getGroupChangeCount: editor.getGroupChangeCount,
4521
+ setScaleMetadata: editor.setScaleMetadata,
4522
+ scaleBaseline
4523
+ }
4524
+ ),
4525
+ /* @__PURE__ */ jsx18(TokenCreationZone, {})
4526
+ ] }),
4527
+ activeTab === "inspector" && /* @__PURE__ */ jsx18(
4528
+ ElementInspectorTab,
4529
+ {
4530
+ inspector,
4531
+ isDetached,
4532
+ scaleBaseline
4533
+ }
4534
+ ),
4535
+ activeTab === "diff" && /* @__PURE__ */ jsx18(
4536
+ TokenEditorDiffOutput,
4537
+ {
4538
+ diff: editor.generateDiff(),
4539
+ changeCount,
4540
+ onResetAll: editor.resetAll,
4541
+ classDiff: inspector.classDiffText,
4542
+ classChangeCount: inspector.changeCount,
4543
+ onResetAllClasses: inspector.clearSelection,
4544
+ customStyleDiff: inspector.customStyleDiffText,
4545
+ customStyleChangeCount: inspector.customStyleChangeCount,
4546
+ onResetCustomStyles: inspector.clearCustomStyles
4547
+ }
4548
+ )
4549
+ ] })
4550
+ ] }),
4551
+ !isOpen && !isDetached && /* @__PURE__ */ jsxs18(
4552
+ "button",
4553
+ {
4554
+ type: "button",
4555
+ onClick: () => setIsOpen(true),
4556
+ className: "te-trigger",
4557
+ title: "Open DevLens",
4558
+ children: [
4559
+ /* @__PURE__ */ jsx18(Palette, { size: 22 }),
4560
+ scaleBaseline.isActive && /* @__PURE__ */ jsx18("span", { className: "te-trigger-dot-baseline" }),
4561
+ combinedChangeCount > 0 && /* @__PURE__ */ jsx18("span", { className: "te-trigger-badge", children: combinedChangeCount > 9 ? "9+" : combinedChangeCount })
4562
+ ]
4563
+ }
4564
+ )
4565
+ ] });
4566
+ }
4567
+ export {
4568
+ DevLensDrawer
4569
+ };