react-boxmodel-inspector 1.0.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,738 @@
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react/jsx-runtime"), require("react"), require("react-dom")) : typeof define === "function" && define.amd ? define(["exports", "react/jsx-runtime", "react", "react-dom"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ReactBoxModelInspector = {}, global.ReactJSXRuntime, global.React, global.ReactDOM));
3
+ })(this, function(exports2, jsxRuntime, react, reactDom) {
4
+ "use strict";
5
+ const C = {
6
+ margin: { fill: "rgba(232,168,56,0.09)", stroke: "rgba(232,168,56,0.75)", text: "#e8a838" },
7
+ padding: { fill: "rgba(93,168,93,0.09)", stroke: "rgba(93,168,93,0.75)", text: "#5da85d" },
8
+ content: { fill: "rgba(167,139,250,0.12)", stroke: "rgba(167,139,250,0.75)", text: "#a78bfa" }
9
+ };
10
+ function EL({ v, style, color }) {
11
+ if (!v) return null;
12
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
13
+ position: "absolute",
14
+ fontSize: 9,
15
+ fontFamily: "monospace",
16
+ fontWeight: 700,
17
+ color,
18
+ background: "rgba(0,0,0,0.65)",
19
+ border: `1px solid ${color}44`,
20
+ borderRadius: 3,
21
+ padding: "0 3px",
22
+ lineHeight: "15px",
23
+ pointerEvents: "none",
24
+ transform: "translate(-50%,-50%)",
25
+ whiteSpace: "nowrap",
26
+ zIndex: 100001,
27
+ ...style
28
+ }, children: [
29
+ v,
30
+ "px"
31
+ ] });
32
+ }
33
+ function BoxOverlay({ box, rect, tagName }) {
34
+ const { width: w, height: h, margin: m, padding: p, border: b } = box;
35
+ const f = (n) => n % 1 === 0 ? String(n) : n.toFixed(1);
36
+ const cW = Math.max(0, w - p.left - p.right - b.left - b.right);
37
+ const cH = Math.max(0, h - p.top - p.bottom - b.top - b.bottom);
38
+ const containerStyle = {
39
+ position: "fixed",
40
+ left: rect.left - m.left,
41
+ top: rect.top - m.top,
42
+ width: w + m.left + m.right,
43
+ height: h + m.top + m.bottom,
44
+ pointerEvents: "none",
45
+ zIndex: 1e5
46
+ };
47
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: containerStyle, children: [
48
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
49
+ position: "absolute",
50
+ top: -18,
51
+ left: 0,
52
+ fontSize: 10,
53
+ fontWeight: 700,
54
+ fontFamily: "monospace",
55
+ letterSpacing: "0.06em",
56
+ color: "#fff",
57
+ background: "#4a90d9",
58
+ borderRadius: "4px 4px 4px 0",
59
+ padding: "1px 7px",
60
+ lineHeight: "17px",
61
+ zIndex: 100003,
62
+ pointerEvents: "none"
63
+ }, children: tagName }),
64
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
65
+ position: "absolute",
66
+ top: 0,
67
+ left: 0,
68
+ width: "100%",
69
+ height: "100%",
70
+ background: C.margin.fill,
71
+ border: `1.5px dashed ${C.margin.stroke}`,
72
+ boxSizing: "border-box"
73
+ }, children: [
74
+ /* @__PURE__ */ jsxRuntime.jsx(EL, { v: f(m.top), color: C.margin.text, style: { top: m.top / 2, left: "50%" } }),
75
+ /* @__PURE__ */ jsxRuntime.jsx(EL, { v: f(m.bottom), color: C.margin.text, style: { top: m.top + h + m.bottom / 2, left: "50%" } }),
76
+ /* @__PURE__ */ jsxRuntime.jsx(EL, { v: f(m.left), color: C.margin.text, style: { top: "50%", left: m.left / 2 } }),
77
+ /* @__PURE__ */ jsxRuntime.jsx(EL, { v: f(m.right), color: C.margin.text, style: { top: "50%", left: m.left + w + m.right / 2 } })
78
+ ] }),
79
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
80
+ position: "absolute",
81
+ top: m.top,
82
+ left: m.left,
83
+ width: w,
84
+ height: h,
85
+ background: C.padding.fill,
86
+ border: `1.5px dashed ${C.padding.stroke}`,
87
+ boxSizing: "border-box"
88
+ }, children: [
89
+ /* @__PURE__ */ jsxRuntime.jsx(EL, { v: f(p.top), color: C.padding.text, style: { top: p.top / 2, left: "50%" } }),
90
+ /* @__PURE__ */ jsxRuntime.jsx(EL, { v: f(p.bottom), color: C.padding.text, style: { top: h - p.bottom / 2, left: "50%" } }),
91
+ /* @__PURE__ */ jsxRuntime.jsx(EL, { v: f(p.left), color: C.padding.text, style: { top: "50%", left: p.left / 2 } }),
92
+ /* @__PURE__ */ jsxRuntime.jsx(EL, { v: f(p.right), color: C.padding.text, style: { top: "50%", left: w - p.right / 2 } })
93
+ ] }),
94
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
95
+ position: "absolute",
96
+ top: m.top + p.top + b.top,
97
+ left: m.left + p.left + b.left,
98
+ width: cW,
99
+ height: cH,
100
+ background: C.content.fill,
101
+ border: `1.5px solid ${C.content.stroke}`,
102
+ boxSizing: "border-box"
103
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
104
+ position: "absolute",
105
+ bottom: 2,
106
+ right: 3,
107
+ fontSize: 9,
108
+ fontFamily: "monospace",
109
+ fontWeight: 700,
110
+ color: C.content.text
111
+ }, children: [
112
+ Math.round(cW),
113
+ "×",
114
+ Math.round(cH)
115
+ ] }) })
116
+ ] });
117
+ }
118
+ const EXTRA_GROUPS = [
119
+ {
120
+ key: "typography",
121
+ label: "Typography",
122
+ color: "#f472b6",
123
+ props: [
124
+ "fontFamily",
125
+ "fontSize",
126
+ "fontWeight",
127
+ "fontStyle",
128
+ "lineHeight",
129
+ "letterSpacing",
130
+ "textAlign",
131
+ "textDecoration",
132
+ "textTransform",
133
+ "whiteSpace",
134
+ "wordBreak"
135
+ ]
136
+ },
137
+ {
138
+ key: "color",
139
+ label: "Color & Background",
140
+ color: "#fb923c",
141
+ props: [
142
+ "color",
143
+ "backgroundColor",
144
+ "backgroundImage",
145
+ "opacity",
146
+ "visibility"
147
+ ]
148
+ },
149
+ {
150
+ key: "layout",
151
+ label: "Layout",
152
+ color: "#34d399",
153
+ props: [
154
+ "display",
155
+ "position",
156
+ "top",
157
+ "right",
158
+ "bottom",
159
+ "left",
160
+ "float",
161
+ "clear",
162
+ "zIndex",
163
+ "overflow",
164
+ "overflowX",
165
+ "overflowY"
166
+ ]
167
+ },
168
+ {
169
+ key: "flex",
170
+ label: "Flex / Grid",
171
+ color: "#60a5fa",
172
+ props: [
173
+ "flexDirection",
174
+ "flexWrap",
175
+ "justifyContent",
176
+ "alignItems",
177
+ "alignContent",
178
+ "flex",
179
+ "flexGrow",
180
+ "flexShrink",
181
+ "flexBasis",
182
+ "gap",
183
+ "gridTemplateColumns",
184
+ "gridTemplateRows",
185
+ "gridArea"
186
+ ]
187
+ },
188
+ {
189
+ key: "box",
190
+ label: "Box",
191
+ color: "#a78bfa",
192
+ props: [
193
+ "width",
194
+ "height",
195
+ "minWidth",
196
+ "maxWidth",
197
+ "minHeight",
198
+ "maxHeight",
199
+ "boxSizing",
200
+ "boxShadow",
201
+ "borderRadius",
202
+ "outline",
203
+ "cursor"
204
+ ]
205
+ },
206
+ {
207
+ key: "transform",
208
+ label: "Transform & Animation",
209
+ color: "#e879f9",
210
+ props: [
211
+ "transform",
212
+ "transition",
213
+ "animation",
214
+ "willChange",
215
+ "pointerEvents"
216
+ ]
217
+ }
218
+ ];
219
+ function camelToKebab(str) {
220
+ return str.replace(/([A-Z])/g, "-$1").toLowerCase();
221
+ }
222
+ function PropRow({ name, value }) {
223
+ if (!value || value === "none" || value === "normal" || value === "auto" || value === "0px") return null;
224
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
225
+ display: "flex",
226
+ gap: 6,
227
+ alignItems: "baseline",
228
+ padding: "2px 0",
229
+ borderBottom: "1px solid rgba(255,255,255,0.04)"
230
+ }, children: [
231
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
232
+ fontSize: 10,
233
+ color: "rgba(255,255,255,0.35)",
234
+ fontFamily: "monospace",
235
+ flexShrink: 0,
236
+ minWidth: 140
237
+ }, children: camelToKebab(name) }),
238
+ /* @__PURE__ */ jsxRuntime.jsx(
239
+ "span",
240
+ {
241
+ style: {
242
+ fontSize: 10,
243
+ color: "#e2e8f0",
244
+ fontFamily: "monospace",
245
+ wordBreak: "break-all",
246
+ maxWidth: 180,
247
+ overflow: "hidden",
248
+ textOverflow: "ellipsis",
249
+ whiteSpace: "nowrap"
250
+ },
251
+ title: value,
252
+ children: value
253
+ }
254
+ )
255
+ ] });
256
+ }
257
+ function ExtraSection({ computed }) {
258
+ const [openGroups, setOpenGroups] = react.useState({});
259
+ const toggle = (key) => setOpenGroups((prev) => ({ ...prev, [key]: !prev[key] }));
260
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 8, borderTop: "1px solid rgba(255,255,255,0.07)", paddingTop: 8 }, children: EXTRA_GROUPS.map(({ key, label, color, props }) => {
261
+ const entries = props.map((p) => ({ name: p, value: computed[p] })).filter(({ value }) => value && value !== "none" && value !== "normal" && value !== "auto" && value !== "0px");
262
+ if (entries.length === 0) return null;
263
+ const open = openGroups[key];
264
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 4 }, children: [
265
+ /* @__PURE__ */ jsxRuntime.jsxs(
266
+ "div",
267
+ {
268
+ onClick: () => toggle(key),
269
+ style: {
270
+ display: "flex",
271
+ alignItems: "center",
272
+ gap: 6,
273
+ cursor: "pointer",
274
+ padding: "3px 0"
275
+ },
276
+ children: [
277
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
278
+ width: 7,
279
+ height: 7,
280
+ borderRadius: "50%",
281
+ background: color,
282
+ flexShrink: 0,
283
+ boxShadow: `0 0 4px ${color}88`
284
+ } }),
285
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, color: "rgba(255,255,255,0.55)", flex: 1 }, children: label }),
286
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
287
+ fontSize: 10,
288
+ color: "rgba(255,255,255,0.25)",
289
+ marginRight: 2,
290
+ transition: "transform 0.15s",
291
+ display: "inline-block",
292
+ transform: open ? "rotate(180deg)" : "rotate(0deg)"
293
+ }, children: "▾" })
294
+ ]
295
+ }
296
+ ),
297
+ open && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { paddingLeft: 13, paddingTop: 3 }, children: entries.map(({ name, value }) => /* @__PURE__ */ jsxRuntime.jsx(PropRow, { name, value }, name)) })
298
+ ] }, key);
299
+ }) });
300
+ }
301
+ function Panel({ box, tagName, initialPos, onClose }) {
302
+ const [pos, setPos] = react.useState(initialPos);
303
+ const [expanded, setExp] = react.useState(false);
304
+ const dragging = react.useRef(false);
305
+ const offset = react.useRef({ x: 0, y: 0 });
306
+ const onMouseDown = (e) => {
307
+ if (e.target.closest("[data-nodrag]")) return;
308
+ dragging.current = true;
309
+ offset.current = { x: e.clientX - pos.x, y: e.clientY - pos.y };
310
+ e.preventDefault();
311
+ };
312
+ react.useEffect(() => {
313
+ const move = (e) => {
314
+ if (!dragging.current) return;
315
+ setPos({ x: e.clientX - offset.current.x, y: e.clientY - offset.current.y });
316
+ };
317
+ const up = () => {
318
+ dragging.current = false;
319
+ };
320
+ window.addEventListener("mousemove", move);
321
+ window.addEventListener("mouseup", up);
322
+ return () => {
323
+ window.removeEventListener("mousemove", move);
324
+ window.removeEventListener("mouseup", up);
325
+ };
326
+ }, []);
327
+ if (!box) return null;
328
+ const { margin: m, padding: p, border: b, width, height, computed } = box;
329
+ const f = (n) => n % 1 === 0 ? String(n) : n.toFixed(1);
330
+ const cW = Math.round(Math.max(0, width - p.left - p.right - b.left - b.right));
331
+ const cH = Math.round(Math.max(0, height - p.top - p.bottom - b.top - b.bottom));
332
+ const rows = [
333
+ { key: "margin", color: C.margin.text, label: "margin", vals: [m.top, m.right, m.bottom, m.left] },
334
+ { key: "border", color: "#4a90d9", label: "border", vals: [b.top, b.right, b.bottom, b.left] },
335
+ { key: "padding", color: C.padding.text, label: "padding", vals: [p.top, p.right, p.bottom, p.left] }
336
+ ];
337
+ return /* @__PURE__ */ jsxRuntime.jsxs(
338
+ "div",
339
+ {
340
+ onMouseDown,
341
+ style: {
342
+ position: "fixed",
343
+ left: pos.x,
344
+ top: pos.y,
345
+ zIndex: 999999,
346
+ cursor: "grab",
347
+ userSelect: "none",
348
+ background: "rgba(10,11,16,0.97)",
349
+ border: "1px solid rgba(255,255,255,0.12)",
350
+ borderRadius: 8,
351
+ padding: "10px 14px 10px",
352
+ minWidth: 260,
353
+ maxWidth: 340,
354
+ fontFamily: "'SF Mono','Fira Code','Cascadia Code',monospace",
355
+ boxShadow: "0 8px 32px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.3)",
356
+ whiteSpace: "nowrap",
357
+ maxHeight: "80vh",
358
+ overflowY: "auto",
359
+ scrollbarWidth: "thin",
360
+ scrollbarColor: "#333 transparent"
361
+ },
362
+ children: [
363
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
364
+ display: "flex",
365
+ justifyContent: "space-between",
366
+ alignItems: "center",
367
+ marginBottom: 8,
368
+ paddingBottom: 7,
369
+ borderBottom: "1px solid rgba(255,255,255,0.08)"
370
+ }, children: [
371
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
372
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
373
+ fontSize: 10,
374
+ fontWeight: 700,
375
+ color: "#4a90d9",
376
+ background: "rgba(74,144,217,0.15)",
377
+ border: "1px solid rgba(74,144,217,0.35)",
378
+ borderRadius: 4,
379
+ padding: "0 6px",
380
+ lineHeight: "18px"
381
+ }, children: tagName }),
382
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 9, letterSpacing: "0.14em", textTransform: "uppercase", color: "rgba(255,255,255,0.28)" }, children: "box model" })
383
+ ] }),
384
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
385
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.18)" }, children: "T · R · B · L" }),
386
+ /* @__PURE__ */ jsxRuntime.jsx(
387
+ "span",
388
+ {
389
+ "data-nodrag": "true",
390
+ onClick: onClose,
391
+ title: "Close (ESC)",
392
+ style: {
393
+ cursor: "pointer",
394
+ color: "rgba(255,255,255,0.35)",
395
+ fontSize: 14,
396
+ lineHeight: 1,
397
+ padding: "0 2px",
398
+ borderRadius: 3,
399
+ userSelect: "none"
400
+ },
401
+ onMouseEnter: (e) => e.currentTarget.style.color = "#ff6b6b",
402
+ onMouseLeave: (e) => e.currentTarget.style.color = "rgba(255,255,255,0.35)",
403
+ children: "✕"
404
+ }
405
+ )
406
+ ] })
407
+ ] }),
408
+ rows.map(({ key, color, label: rowLabel, vals }) => {
409
+ const allZero = vals.every((v) => v === 0);
410
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
411
+ display: "flex",
412
+ alignItems: "center",
413
+ gap: 8,
414
+ marginBottom: 5,
415
+ opacity: allZero ? 0.25 : 1
416
+ }, children: [
417
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
418
+ width: 7,
419
+ height: 7,
420
+ borderRadius: "50%",
421
+ background: color,
422
+ flexShrink: 0,
423
+ boxShadow: `0 0 5px ${color}88`
424
+ } }),
425
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, color: "rgba(255,255,255,0.35)", width: 46, flexShrink: 0 }, children: rowLabel }),
426
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 3 }, children: vals.map((v, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
427
+ fontSize: 11,
428
+ fontWeight: 600,
429
+ color: v === 0 ? "rgba(255,255,255,0.18)" : color,
430
+ background: v === 0 ? "transparent" : `${color}18`,
431
+ border: `1px solid ${v === 0 ? "rgba(255,255,255,0.05)" : `${color}44`}`,
432
+ borderRadius: 4,
433
+ padding: "1px 5px",
434
+ minWidth: 32,
435
+ textAlign: "center"
436
+ }, children: f(v) }, i)) })
437
+ ] }, key);
438
+ }),
439
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
440
+ display: "flex",
441
+ alignItems: "center",
442
+ gap: 8,
443
+ marginTop: 8,
444
+ paddingTop: 7,
445
+ borderTop: "1px solid rgba(255,255,255,0.07)"
446
+ }, children: [
447
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
448
+ width: 7,
449
+ height: 7,
450
+ borderRadius: 2,
451
+ background: C.content.text,
452
+ flexShrink: 0,
453
+ boxShadow: `0 0 5px ${C.content.text}88`
454
+ } }),
455
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, color: "rgba(255,255,255,0.35)", width: 46 }, children: "content" }),
456
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 12, fontWeight: 600, color: C.content.text }, children: [
457
+ cW,
458
+ " × ",
459
+ cH,
460
+ " px"
461
+ ] })
462
+ ] }),
463
+ /* @__PURE__ */ jsxRuntime.jsxs(
464
+ "div",
465
+ {
466
+ "data-nodrag": "true",
467
+ onClick: () => setExp((v) => !v),
468
+ style: {
469
+ display: "flex",
470
+ alignItems: "center",
471
+ justifyContent: "center",
472
+ gap: 5,
473
+ marginTop: 10,
474
+ paddingTop: 8,
475
+ borderTop: "1px solid rgba(255,255,255,0.07)",
476
+ cursor: "pointer",
477
+ color: "rgba(255,255,255,0.35)",
478
+ fontSize: 10,
479
+ letterSpacing: "0.05em"
480
+ },
481
+ children: [
482
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "All properties" }),
483
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
484
+ display: "inline-block",
485
+ transition: "transform 0.2s",
486
+ transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
487
+ fontSize: 11
488
+ }, children: "▾" })
489
+ ]
490
+ }
491
+ ),
492
+ expanded && computed && /* @__PURE__ */ jsxRuntime.jsx(ExtraSection, { computed }),
493
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
494
+ marginTop: 8,
495
+ paddingTop: 6,
496
+ borderTop: "1px solid rgba(255,255,255,0.05)",
497
+ fontSize: 9,
498
+ color: "rgba(255,255,255,0.12)",
499
+ textAlign: "center",
500
+ letterSpacing: "0.05em"
501
+ }, children: "⠿ drag to move · ESC to close" })
502
+ ]
503
+ }
504
+ );
505
+ }
506
+ function LongPressIndicator({ x, y, progress }) {
507
+ if (progress === 0) return null;
508
+ return reactDom.createPortal(
509
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
510
+ position: "fixed",
511
+ left: x,
512
+ top: y,
513
+ width: 0,
514
+ height: 0,
515
+ pointerEvents: "none",
516
+ zIndex: 99999
517
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
518
+ position: "absolute",
519
+ top: "50%",
520
+ left: "50%",
521
+ width: 60,
522
+ height: 60,
523
+ borderRadius: "50%",
524
+ background: `rgba(74,144,217,${0.1 + progress * 0.2})`,
525
+ border: `2px solid rgba(74,144,217,${0.3 + progress * 0.5})`,
526
+ transform: `translate(-50%, -50%) scale(${progress})`,
527
+ transition: "transform 0.05s linear"
528
+ } }) }),
529
+ document.body
530
+ );
531
+ }
532
+ function BoxModelInspector({
533
+ children,
534
+ longPressDuration = 500,
535
+ toggleKey = "`"
536
+ }) {
537
+ const [active, setActive] = react.useState(false);
538
+ const [target, setTarget] = react.useState(null);
539
+ const [box, setBox] = react.useState(null);
540
+ const [rect, setRect] = react.useState(null);
541
+ const [panelPos, setPanelPos] = react.useState(null);
542
+ const [longPressState, setLongPressState] = react.useState({ active: false, x: 0, y: 0, progress: 0 });
543
+ const timerRef = react.useRef(null);
544
+ const progressIntervalRef = react.useRef(null);
545
+ const readBox = (el) => {
546
+ if (!el) return;
547
+ const s = getComputedStyle(el);
548
+ const r = el.getBoundingClientRect();
549
+ const computed = {};
550
+ EXTRA_GROUPS.forEach(({ props }) => {
551
+ props.forEach((prop) => {
552
+ computed[prop] = s[prop];
553
+ });
554
+ });
555
+ setBox({
556
+ width: r.width,
557
+ height: r.height,
558
+ margin: {
559
+ top: parseFloat(s.marginTop),
560
+ right: parseFloat(s.marginRight),
561
+ bottom: parseFloat(s.marginBottom),
562
+ left: parseFloat(s.marginLeft)
563
+ },
564
+ padding: {
565
+ top: parseFloat(s.paddingTop),
566
+ right: parseFloat(s.paddingRight),
567
+ bottom: parseFloat(s.paddingBottom),
568
+ left: parseFloat(s.paddingLeft)
569
+ },
570
+ border: {
571
+ top: parseFloat(s.borderTopWidth),
572
+ right: parseFloat(s.borderRightWidth),
573
+ bottom: parseFloat(s.borderBottomWidth),
574
+ left: parseFloat(s.borderLeftWidth)
575
+ },
576
+ computed
577
+ });
578
+ setRect(r);
579
+ setPanelPos({
580
+ x: Math.min(r.right + 24, window.innerWidth - 320),
581
+ y: Math.max(20, r.top)
582
+ });
583
+ };
584
+ const handlePointerDown = (e) => {
585
+ if (!active) return;
586
+ if (e.target.closest("[data-boxmodel-ui]")) return;
587
+ const el = e.target;
588
+ setLongPressState({
589
+ active: true,
590
+ x: e.clientX,
591
+ y: e.clientY,
592
+ progress: 0
593
+ });
594
+ const startTime = Date.now();
595
+ progressIntervalRef.current = setInterval(() => {
596
+ const elapsed = Date.now() - startTime;
597
+ const progress = Math.min(elapsed / longPressDuration, 1);
598
+ setLongPressState((prev) => ({ ...prev, progress }));
599
+ }, 16);
600
+ timerRef.current = setTimeout(() => {
601
+ setLongPressState({ active: false, x: 0, y: 0, progress: 0 });
602
+ clearInterval(progressIntervalRef.current);
603
+ setTarget(el);
604
+ readBox(el);
605
+ }, longPressDuration);
606
+ };
607
+ const handlePointerUp = () => {
608
+ if (timerRef.current) {
609
+ clearTimeout(timerRef.current);
610
+ timerRef.current = null;
611
+ }
612
+ if (progressIntervalRef.current) {
613
+ clearInterval(progressIntervalRef.current);
614
+ progressIntervalRef.current = null;
615
+ }
616
+ setLongPressState({ active: false, x: 0, y: 0, progress: 0 });
617
+ };
618
+ const handlePointerMove = (e) => {
619
+ if (!longPressState.active) return;
620
+ const dx = e.clientX - longPressState.x;
621
+ const dy = e.clientY - longPressState.y;
622
+ if (Math.sqrt(dx * dx + dy * dy) > 10) {
623
+ handlePointerUp();
624
+ }
625
+ };
626
+ const close = () => {
627
+ setTarget(null);
628
+ setBox(null);
629
+ setRect(null);
630
+ setPanelPos(null);
631
+ };
632
+ react.useEffect(() => {
633
+ const handleKey = (e) => {
634
+ if (e.key === "Escape" && target) {
635
+ close();
636
+ } else if (e.key === toggleKey && !e.repeat) {
637
+ setActive((v) => {
638
+ const newVal = !v;
639
+ if (!newVal && target) close();
640
+ return newVal;
641
+ });
642
+ }
643
+ };
644
+ window.addEventListener("keydown", handleKey);
645
+ return () => window.removeEventListener("keydown", handleKey);
646
+ }, [target, toggleKey]);
647
+ react.useEffect(() => {
648
+ if (!target) return;
649
+ const update = () => readBox(target);
650
+ const ro = new ResizeObserver(update);
651
+ ro.observe(target);
652
+ const mo = new MutationObserver(update);
653
+ mo.observe(target, { attributes: true, attributeFilter: ["style", "class"] });
654
+ const handleScroll = () => update();
655
+ const handleResize = () => update();
656
+ window.addEventListener("scroll", handleScroll, { passive: true });
657
+ window.addEventListener("resize", handleResize);
658
+ return () => {
659
+ ro.disconnect();
660
+ mo.disconnect();
661
+ window.removeEventListener("scroll", handleScroll);
662
+ window.removeEventListener("resize", handleResize);
663
+ };
664
+ }, [target]);
665
+ react.useEffect(() => {
666
+ if (!active) {
667
+ handlePointerUp();
668
+ return;
669
+ }
670
+ window.addEventListener("pointerdown", handlePointerDown, true);
671
+ window.addEventListener("pointerup", handlePointerUp, true);
672
+ window.addEventListener("pointermove", handlePointerMove, true);
673
+ return () => {
674
+ window.removeEventListener("pointerdown", handlePointerDown, true);
675
+ window.removeEventListener("pointerup", handlePointerUp, true);
676
+ window.removeEventListener("pointermove", handlePointerMove, true);
677
+ };
678
+ });
679
+ const tagName = target ? `${target.tagName.toLowerCase()}${target.id ? `#${target.id}` : ""}${target.className ? `.${target.className.split(" ")[0]}` : ""}` : "";
680
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
681
+ children,
682
+ active && reactDom.createPortal(
683
+ /* @__PURE__ */ jsxRuntime.jsxs(
684
+ "div",
685
+ {
686
+ "data-boxmodel-ui": "true",
687
+ style: {
688
+ position: "fixed",
689
+ top: 12,
690
+ left: "50%",
691
+ transform: "translateX(-50%)",
692
+ zIndex: 999998,
693
+ background: "rgba(74,144,217,0.95)",
694
+ color: "#fff",
695
+ fontSize: 11,
696
+ fontFamily: "monospace",
697
+ padding: "6px 14px",
698
+ borderRadius: 6,
699
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
700
+ pointerEvents: "none",
701
+ letterSpacing: "0.03em"
702
+ },
703
+ children: [
704
+ "🔍 Inspector active · Long-press any element · Press ",
705
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { style: {
706
+ background: "rgba(255,255,255,0.2)",
707
+ padding: "1px 5px",
708
+ borderRadius: 3,
709
+ fontWeight: 700
710
+ }, children: toggleKey }),
711
+ " to toggle"
712
+ ]
713
+ }
714
+ ),
715
+ document.body
716
+ ),
717
+ longPressState.progress > 0 && /* @__PURE__ */ jsxRuntime.jsx(
718
+ LongPressIndicator,
719
+ {
720
+ x: longPressState.x,
721
+ y: longPressState.y,
722
+ progress: longPressState.progress
723
+ }
724
+ ),
725
+ target && box && rect && reactDom.createPortal(
726
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "data-boxmodel-ui": "true", children: /* @__PURE__ */ jsxRuntime.jsx(BoxOverlay, { box, rect, tagName }) }),
727
+ document.body
728
+ ),
729
+ target && box && panelPos && reactDom.createPortal(
730
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "data-boxmodel-ui": "true", children: /* @__PURE__ */ jsxRuntime.jsx(Panel, { box, tagName, initialPos: panelPos, onClose: close }) }),
731
+ document.body
732
+ )
733
+ ] });
734
+ }
735
+ exports2.BoxModelInspector = BoxModelInspector;
736
+ Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
737
+ });
738
+ //# sourceMappingURL=react-boxmodel-inspector.umd.js.map