react-state-inspector-devtools 0.1.8 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -188,22 +188,536 @@ function useComponentLabel(label) {
188
188
  }
189
189
 
190
190
  // src/StateInspectorUI.tsx
191
- import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
191
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef2, useState as useState3 } from "react";
192
192
  import { createPortal } from "react-dom";
193
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
194
- var isMac = navigator.platform.toUpperCase().includes("MAC");
193
+
194
+ // src/ui/constants.ts
195
+ var LS_LAYOUT = "rsi:panel-layout";
196
+ var LS_KEY = "rsi:panel-pos";
197
+ var LS_SIZE = "rsi:panel-size";
198
+ var LS_LEFT_W = "rsi:left-width";
199
+ var SNAP = 100;
200
+ var DOCK_W = 420;
201
+ var DOCK_H = 320;
202
+ var MARGIN = 12;
203
+ var LEFT_MIN = 180;
204
+ var LEFT_MAX = 420;
205
+ var isMac = typeof navigator !== "undefined" ? navigator.platform.toUpperCase().includes("MAC") : false;
206
+
207
+ // src/ui/utils.ts
195
208
  function isOptionObject(opt) {
196
209
  return typeof opt === "object" && opt !== null && "value" in opt;
197
210
  }
211
+ function computePreview(x, y) {
212
+ const w = window.innerWidth;
213
+ const h = window.innerHeight;
214
+ const nearLeft = x <= SNAP;
215
+ const nearRight = x >= w - SNAP;
216
+ const nearTop = y <= SNAP;
217
+ const nearBottom = y >= h - SNAP;
218
+ if (nearLeft) return "dock-left";
219
+ if (nearRight) return "dock-right";
220
+ if (nearTop) return "dock-top";
221
+ if (nearBottom) return "dock-bottom";
222
+ return null;
223
+ }
224
+ function previewRectStyle(p) {
225
+ const base = {
226
+ position: "fixed",
227
+ border: "1px dashed #555",
228
+ background: "rgba(255,255,255,0.06)",
229
+ borderRadius: 12
230
+ };
231
+ if (p === "dock-left") {
232
+ return { ...base, left: MARGIN, top: MARGIN, bottom: MARGIN, width: DOCK_W };
233
+ }
234
+ if (p === "dock-right") {
235
+ return { ...base, right: MARGIN, top: MARGIN, bottom: MARGIN, width: DOCK_W };
236
+ }
237
+ if (p === "dock-top") {
238
+ return { ...base, left: MARGIN, right: MARGIN, top: MARGIN, height: DOCK_H };
239
+ }
240
+ return { ...base, left: MARGIN, right: MARGIN, bottom: MARGIN, height: DOCK_H };
241
+ }
242
+ function safeStringify(v) {
243
+ try {
244
+ return JSON.stringify(v, null, 2);
245
+ } catch {
246
+ return String(v);
247
+ }
248
+ }
249
+
250
+ // src/ui/components/FloatingButton.tsx
251
+ import { jsx as jsx2 } from "react/jsx-runtime";
252
+ function FloatingButton({ onClick }) {
253
+ return /* @__PURE__ */ jsx2(
254
+ "button",
255
+ {
256
+ title: "State Inspector (\u2318\u21E7I / Ctrl\u21E7I)",
257
+ onClick,
258
+ style: {
259
+ position: "fixed",
260
+ right: 16,
261
+ bottom: 16,
262
+ width: 44,
263
+ height: 44,
264
+ borderRadius: 22,
265
+ border: "none",
266
+ background: "#111",
267
+ color: "#fff",
268
+ cursor: "pointer",
269
+ zIndex: 999999
270
+ },
271
+ "aria-label": "Toggle State Inspector",
272
+ children: "\u25CE"
273
+ }
274
+ );
275
+ }
276
+
277
+ // src/ui/components/DockPreviewOverlay.tsx
278
+ import { jsx as jsx3 } from "react/jsx-runtime";
279
+ function DockPreviewOverlay({ preview }) {
280
+ if (!preview) return null;
281
+ return /* @__PURE__ */ jsx3(
282
+ "div",
283
+ {
284
+ style: {
285
+ position: "fixed",
286
+ inset: 0,
287
+ pointerEvents: "none",
288
+ zIndex: 999998
289
+ },
290
+ children: /* @__PURE__ */ jsx3("div", { style: previewRectStyle(preview) })
291
+ }
292
+ );
293
+ }
294
+
295
+ // src/ui/components/ResizeHandle.tsx
296
+ import { jsx as jsx4 } from "react/jsx-runtime";
297
+ function ResizeHandle({ onResize }) {
298
+ return /* @__PURE__ */ jsx4(
299
+ "div",
300
+ {
301
+ onPointerDown: (e) => {
302
+ const startX = e.clientX;
303
+ const startY = e.clientY;
304
+ e.currentTarget.setPointerCapture(e.pointerId);
305
+ const onMove = (ev) => {
306
+ onResize({
307
+ dx: ev.clientX - startX,
308
+ dy: ev.clientY - startY
309
+ });
310
+ };
311
+ const onUp = () => {
312
+ window.removeEventListener("pointermove", onMove);
313
+ window.removeEventListener("pointerup", onUp);
314
+ };
315
+ window.addEventListener("pointermove", onMove);
316
+ window.addEventListener("pointerup", onUp);
317
+ },
318
+ style: {
319
+ position: "absolute",
320
+ right: 6,
321
+ bottom: 6,
322
+ width: 14,
323
+ height: 14,
324
+ borderRadius: 6,
325
+ border: "1px solid #333",
326
+ background: "#838383",
327
+ cursor: "nwse-resize"
328
+ }
329
+ }
330
+ );
331
+ }
332
+
333
+ // src/ui/components/SidebarResizer.tsx
334
+ import { jsx as jsx5 } from "react/jsx-runtime";
335
+ function SidebarResizer({ leftWidth, onWidthChange, hidden }) {
336
+ if (hidden) return null;
337
+ return /* @__PURE__ */ jsx5(
338
+ "div",
339
+ {
340
+ onPointerDown: (e) => {
341
+ e.preventDefault();
342
+ const startX = e.clientX;
343
+ const startW = leftWidth;
344
+ e.currentTarget.setPointerCapture(e.pointerId);
345
+ const onMove = (ev) => {
346
+ const dx = ev.clientX - startX;
347
+ const next = Math.max(LEFT_MIN, Math.min(LEFT_MAX, startW + dx));
348
+ onWidthChange(next);
349
+ };
350
+ const onUp = () => {
351
+ window.removeEventListener("pointermove", onMove);
352
+ window.removeEventListener("pointerup", onUp);
353
+ };
354
+ window.addEventListener("pointermove", onMove);
355
+ window.addEventListener("pointerup", onUp);
356
+ },
357
+ style: {
358
+ width: 8,
359
+ cursor: "col-resize",
360
+ background: "transparent",
361
+ borderLeft: "1px solid #2a2a2a",
362
+ borderRight: "1px solid #2a2a2a"
363
+ },
364
+ title: "Resize sidebar"
365
+ }
366
+ );
367
+ }
368
+
369
+ // src/ui/components/DragHandle.tsx
370
+ import { jsx as jsx6 } from "react/jsx-runtime";
371
+ function DragHandle({ children, onDragStart, onDragEnd, style }) {
372
+ return /* @__PURE__ */ jsx6(
373
+ "div",
374
+ {
375
+ style: { cursor: "grab", userSelect: "none", ...style },
376
+ onPointerDown: onDragStart,
377
+ onPointerUp: onDragEnd,
378
+ children
379
+ }
380
+ );
381
+ }
382
+
383
+ // src/ui/components/ComponentList.tsx
384
+ import { jsx as jsx7, jsxs } from "react/jsx-runtime";
385
+ function ComponentList({
386
+ components,
387
+ selectedId,
388
+ onSelect,
389
+ onDragStart,
390
+ onDragEnd,
391
+ height,
392
+ hidden
393
+ }) {
394
+ if (hidden) return null;
395
+ return /* @__PURE__ */ jsxs("div", { style: { padding: 12, height, flexDirection: "column", boxSizing: "border-box", display: "flex" }, children: [
396
+ /* @__PURE__ */ jsxs(
397
+ DragHandle,
398
+ {
399
+ onDragStart,
400
+ onDragEnd,
401
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between" },
402
+ children: [
403
+ /* @__PURE__ */ jsx7("h4", { style: { margin: 0 }, children: "Components" }),
404
+ /* @__PURE__ */ jsx7("span", { style: { fontSize: 12, opacity: 0.7 }, children: components.length })
405
+ ]
406
+ }
407
+ ),
408
+ /* @__PURE__ */ jsx7("div", { style: { marginTop: 10, display: "flex", flexDirection: "column", gap: 8, overflow: "auto", flex: 1 }, children: components.map((c) => {
409
+ const active = c.id === selectedId;
410
+ return /* @__PURE__ */ jsxs(
411
+ "button",
412
+ {
413
+ id: `rsi-comp-${c.id}`,
414
+ onClick: () => onSelect(c.id),
415
+ style: {
416
+ textAlign: "left",
417
+ background: active ? "#2a2a2a" : "transparent",
418
+ border: "1px solid #333",
419
+ borderRadius: 10,
420
+ padding: 10,
421
+ color: "#fff",
422
+ cursor: "pointer",
423
+ flex: "none"
424
+ },
425
+ children: [
426
+ /* @__PURE__ */ jsx7("div", { style: { fontWeight: 700 }, children: c.label }),
427
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, opacity: 0.7 }, children: [
428
+ "states: ",
429
+ c.stateKeys.length || 0
430
+ ] })
431
+ ]
432
+ },
433
+ c.id
434
+ );
435
+ }) })
436
+ ] });
437
+ }
438
+
439
+ // src/ui/components/StatePanelHeader.tsx
440
+ import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
441
+ function StatePanelHeader({
442
+ selectedLabel,
443
+ leftCollapsed,
444
+ onToggleCollapse,
445
+ onClose,
446
+ onDragStart,
447
+ onDragEnd
448
+ }) {
449
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center" }, children: [
450
+ /* @__PURE__ */ jsx8(
451
+ "button",
452
+ {
453
+ onClick: (e) => {
454
+ e.preventDefault();
455
+ onToggleCollapse();
456
+ },
457
+ style: {
458
+ padding: 4,
459
+ marginRight: 12,
460
+ borderRadius: 6,
461
+ border: "1px solid #333",
462
+ background: "transparent",
463
+ color: "#fff",
464
+ cursor: "pointer",
465
+ fontSize: 12,
466
+ width: 24,
467
+ height: 24
468
+ },
469
+ children: leftCollapsed ? "\xBB" : "\xAB"
470
+ }
471
+ ),
472
+ /* @__PURE__ */ jsxs2(
473
+ DragHandle,
474
+ {
475
+ onDragStart,
476
+ onDragEnd,
477
+ style: { display: "flex", flexDirection: "column", flex: 1 },
478
+ children: [
479
+ /* @__PURE__ */ jsx8("h4", { style: { margin: 0 }, children: "State" }),
480
+ /* @__PURE__ */ jsx8("div", { style: { fontSize: 12, opacity: 0.7 }, children: selectedLabel ?? "No selection" })
481
+ ]
482
+ }
483
+ ),
484
+ /* @__PURE__ */ jsx8(
485
+ "button",
486
+ {
487
+ onClick: onClose,
488
+ style: {
489
+ background: "transparent",
490
+ color: "#fff",
491
+ border: "1px solid #333",
492
+ borderRadius: 10,
493
+ padding: "6px 10px",
494
+ cursor: "pointer",
495
+ fontSize: 12,
496
+ marginLeft: "auto"
497
+ },
498
+ children: "Close"
499
+ }
500
+ )
501
+ ] });
502
+ }
503
+
504
+ // src/ui/styles.ts
505
+ var inputStyle = {
506
+ padding: "8px 10px",
507
+ borderRadius: 10,
508
+ border: "1px solid #333",
509
+ background: "#111",
510
+ color: "#fff"
511
+ };
512
+ var cardStyle = {
513
+ border: "1px solid #333",
514
+ borderRadius: 12,
515
+ padding: 10
516
+ };
517
+
518
+ // src/ui/components/SearchInput.tsx
519
+ import { jsx as jsx9 } from "react/jsx-runtime";
520
+ function SearchInput({ value, onChange, inputRef }) {
521
+ return /* @__PURE__ */ jsx9(
522
+ "input",
523
+ {
524
+ value,
525
+ onChange: (e) => onChange(e.target.value),
526
+ placeholder: "Search components / state keys\u2026",
527
+ ref: inputRef,
528
+ style: {
529
+ ...inputStyle,
530
+ marginTop: 10,
531
+ outline: "none"
532
+ }
533
+ }
534
+ );
535
+ }
536
+
537
+ // src/ui/components/JsonEditor.tsx
538
+ import { useEffect as useEffect3, useState as useState2 } from "react";
539
+ import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
540
+ function JsonEditor({ initial, onValidJson }) {
541
+ const [raw, setRaw] = useState2(initial);
542
+ const [error, setError] = useState2(null);
543
+ useEffect3(() => {
544
+ setRaw(initial);
545
+ }, [initial]);
546
+ const handleChange = (e) => {
547
+ const next = e.target.value;
548
+ setRaw(next);
549
+ try {
550
+ const parsed = JSON.parse(next);
551
+ setError(null);
552
+ onValidJson(parsed);
553
+ } catch {
554
+ setError("Invalid JSON");
555
+ }
556
+ };
557
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "grid", gap: 6 }, children: [
558
+ /* @__PURE__ */ jsx10(
559
+ "textarea",
560
+ {
561
+ value: raw,
562
+ onChange: handleChange,
563
+ rows: 6,
564
+ style: {
565
+ ...inputStyle,
566
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
567
+ fontSize: 12,
568
+ resize: "vertical"
569
+ }
570
+ }
571
+ ),
572
+ error && /* @__PURE__ */ jsx10("div", { style: { fontSize: 12, color: "#ff6b6b" }, children: error })
573
+ ] });
574
+ }
575
+
576
+ // src/ui/components/StateEditor.tsx
577
+ import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
578
+ function StateEditor({ value, meta, onChange }) {
579
+ if (meta?.type === "boolean" || typeof value === "boolean") {
580
+ return /* @__PURE__ */ jsxs4("label", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
581
+ /* @__PURE__ */ jsx11(
582
+ "input",
583
+ {
584
+ type: "checkbox",
585
+ checked: Boolean(value),
586
+ onChange: (e) => onChange(e.target.checked)
587
+ }
588
+ ),
589
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 13, opacity: 0.8 }, children: String(Boolean(value)) })
590
+ ] });
591
+ }
592
+ if (meta?.type === "select" && Array.isArray(meta.options)) {
593
+ const current = typeof value === "string" ? value : String(value ?? "");
594
+ return /* @__PURE__ */ jsx11(
595
+ "select",
596
+ {
597
+ value: current,
598
+ onChange: (e) => onChange(e.target.value),
599
+ style: inputStyle,
600
+ children: meta.options.map((opt) => {
601
+ const o = isOptionObject(opt) ? opt : { label: String(opt), value: String(opt) };
602
+ return /* @__PURE__ */ jsx11("option", { value: o.value, children: o.label }, o.value);
603
+ })
604
+ }
605
+ );
606
+ }
607
+ if (meta?.type === "number") {
608
+ return /* @__PURE__ */ jsx11(
609
+ "input",
610
+ {
611
+ type: "number",
612
+ value: typeof value === "number" ? value : Number(value ?? 0),
613
+ min: meta?.min,
614
+ max: meta?.max,
615
+ step: meta?.step,
616
+ onChange: (e) => onChange(e.target.value === "" ? 0 : Number(e.target.value)),
617
+ style: inputStyle
618
+ }
619
+ );
620
+ }
621
+ if (meta?.type === "json" || value && typeof value === "object") {
622
+ return /* @__PURE__ */ jsx11(JsonEditor, { initial: safeStringify(value), onValidJson: onChange });
623
+ }
624
+ return /* @__PURE__ */ jsx11(
625
+ "input",
626
+ {
627
+ type: "text",
628
+ value: typeof value === "string" ? value : String(value ?? ""),
629
+ placeholder: meta?.type === "text" ? meta.placeholder : void 0,
630
+ onChange: (e) => onChange(e.target.value),
631
+ style: inputStyle
632
+ }
633
+ );
634
+ }
635
+
636
+ // src/ui/components/StateCard.tsx
637
+ import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
638
+ function StateCard({ state }) {
639
+ return /* @__PURE__ */ jsxs5(
640
+ "div",
641
+ {
642
+ style: {
643
+ ...cardStyle,
644
+ display: "flex",
645
+ flexDirection: "column",
646
+ gap: 8
647
+ },
648
+ children: [
649
+ /* @__PURE__ */ jsxs5("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [
650
+ /* @__PURE__ */ jsx12("div", { style: { fontWeight: 700 }, children: state.key }),
651
+ /* @__PURE__ */ jsx12("div", { style: { fontSize: 12, opacity: 0.6 }, children: state.meta?.type ?? "auto" })
652
+ ] }),
653
+ /* @__PURE__ */ jsx12(
654
+ StateEditor,
655
+ {
656
+ value: state.value,
657
+ meta: state.meta,
658
+ onChange: (next) => state.setValue(next)
659
+ }
660
+ )
661
+ ]
662
+ }
663
+ );
664
+ }
665
+
666
+ // src/ui/components/StateEditorPanel.tsx
667
+ import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
668
+ function StateEditorPanel({
669
+ selected,
670
+ query,
671
+ onQueryChange,
672
+ searchRef,
673
+ leftCollapsed,
674
+ onToggleCollapse,
675
+ onClose,
676
+ onDragStart,
677
+ onDragEnd
678
+ }) {
679
+ return /* @__PURE__ */ jsxs6("div", { style: { padding: 12, overflow: "auto", display: "flex", flexDirection: "column", boxSizing: "border-box" }, children: [
680
+ /* @__PURE__ */ jsx13(
681
+ StatePanelHeader,
682
+ {
683
+ selectedLabel: selected?.label ?? null,
684
+ leftCollapsed,
685
+ onToggleCollapse,
686
+ onClose,
687
+ onDragStart,
688
+ onDragEnd
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsx13(
692
+ SearchInput,
693
+ {
694
+ value: query,
695
+ onChange: onQueryChange,
696
+ inputRef: searchRef
697
+ }
698
+ ),
699
+ /* @__PURE__ */ jsxs6("div", { style: { marginTop: 12, display: "grid", gap: 10 }, children: [
700
+ selected && selected.states.size === 0 && /* @__PURE__ */ jsx13("div", { style: { fontSize: 13, opacity: 0.7 }, children: "This component has no inspectable state." }),
701
+ !selected && /* @__PURE__ */ jsx13("div", { style: { opacity: 0.7, fontSize: 13 }, children: "No component selected." }),
702
+ selected && Array.from(selected.states.values()).map((s) => /* @__PURE__ */ jsx13(StateCard, { state: s }, s.key))
703
+ ] })
704
+ ] });
705
+ }
706
+
707
+ // src/StateInspectorUI.tsx
708
+ import { Fragment, jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
198
709
  function StateInspectorUI() {
199
710
  const store = useInspectorStore();
200
- const [query, setQuery] = useState2("");
201
- const [open, setOpen] = useState2(false);
202
- const [selectedId, setSelectedId] = useState2(null);
203
- const [, force] = useState2(0);
711
+ const [query, setQuery] = useState3("");
712
+ const [leftCollapsed, setLeftCollapsed] = useState3(false);
713
+ const [layout, setLayout] = useState3("floating");
714
+ const [preview, setPreview] = useState3(null);
715
+ const [isTransitioning, setIsTransitioning] = useState3(false);
716
+ const [open, setOpen] = useState3(false);
717
+ const [selectedId, setSelectedId] = useState3(null);
718
+ const [, force] = useState3(0);
204
719
  const searchRef = useRef2(null);
205
- const LS_KEY = "rsi:panel-pos";
206
- const [pos, setPos] = useState2(() => {
720
+ const [pos, setPos] = useState3(() => {
207
721
  try {
208
722
  const raw = localStorage.getItem(LS_KEY);
209
723
  return raw ? JSON.parse(raw) : { right: 16, bottom: 72 };
@@ -211,28 +725,55 @@ function StateInspectorUI() {
211
725
  return { right: 16, bottom: 72 };
212
726
  }
213
727
  });
214
- useEffect3(() => {
728
+ const [leftWidth, setLeftWidth] = useState3(() => {
215
729
  try {
216
- localStorage.setItem(LS_KEY, JSON.stringify(pos));
730
+ const raw = localStorage.getItem(LS_LEFT_W);
731
+ return raw ? Number(raw) : 280;
217
732
  } catch {
733
+ return 280;
218
734
  }
219
- }, [pos]);
220
- const SIZE_KEY = "rsi:panel-size";
221
- const [size, setSize] = useState2(() => {
735
+ });
736
+ const [size, setSize] = useState3(() => {
222
737
  try {
223
- const raw = localStorage.getItem(SIZE_KEY);
738
+ const raw = localStorage.getItem(LS_SIZE);
224
739
  return raw ? JSON.parse(raw) : { width: 720, height: 420 };
225
740
  } catch {
226
741
  return { width: 720, height: 420 };
227
742
  }
228
743
  });
229
- useEffect3(() => {
744
+ useEffect4(() => {
230
745
  try {
231
- localStorage.setItem(SIZE_KEY, JSON.stringify(size));
232
- } catch {
746
+ localStorage.setItem(LS_LAYOUT, layout);
747
+ } catch (e) {
748
+ console.error(e);
749
+ }
750
+ }, [layout]);
751
+ useEffect4(() => {
752
+ try {
753
+ localStorage.setItem(LS_KEY, JSON.stringify(pos));
754
+ } catch (e) {
755
+ console.error(e);
756
+ }
757
+ }, [pos]);
758
+ useEffect4(() => {
759
+ try {
760
+ localStorage.setItem(LS_LEFT_W, String(leftWidth));
761
+ } catch (e) {
762
+ console.error(e);
763
+ }
764
+ }, [leftWidth]);
765
+ useEffect4(() => {
766
+ try {
767
+ localStorage.setItem(LS_SIZE, JSON.stringify(size));
768
+ } catch (e) {
769
+ console.error(e);
233
770
  }
234
771
  }, [size]);
235
- useEffect3(() => store.subscribe(() => force((x) => x + 1)), [store]);
772
+ useEffect4(() => {
773
+ const shouldCollapse = leftWidth <= 200 || (size.width ?? 720) - leftWidth < 320;
774
+ setLeftCollapsed(shouldCollapse);
775
+ }, [leftWidth, size.width]);
776
+ useEffect4(() => store.subscribe(() => force((x) => x + 1)), [store]);
236
777
  const snapshot = useMemo3(() => store.getSnapshot(), [store]);
237
778
  const components = useMemo3(
238
779
  () => snapshot.components.filter((c) => c.mounted),
@@ -242,17 +783,13 @@ function StateInspectorUI() {
242
783
  const filtered = useMemo3(
243
784
  () => q ? components.filter((c) => {
244
785
  const labelHit = c.label.toLowerCase().includes(q);
245
- const keyHit = c.stateKeys.some(
246
- (k) => k.toLowerCase().includes(q)
247
- );
786
+ const keyHit = c.stateKeys.some((k) => k.toLowerCase().includes(q));
248
787
  return labelHit || keyHit;
249
788
  }) : components,
250
789
  [q, components]
251
790
  );
252
- useEffect3(() => {
253
- if (!open) {
254
- return;
255
- }
791
+ useEffect4(() => {
792
+ if (!open) return;
256
793
  if (components.length === 0) {
257
794
  setSelectedId(null);
258
795
  return;
@@ -261,17 +798,7 @@ function StateInspectorUI() {
261
798
  setSelectedId(components[0].id);
262
799
  }
263
800
  }, [open, components.length]);
264
- useEffect3(() => {
265
- if (!open) {
266
- return;
267
- }
268
- function onKey(e) {
269
- if (e.key === "Escape") setOpen(false);
270
- }
271
- window.addEventListener("keydown", onKey);
272
- return () => window.removeEventListener("keydown", onKey);
273
- }, [open]);
274
- useEffect3(() => {
801
+ useEffect4(() => {
275
802
  function onKey(e) {
276
803
  const mod = isMac ? e.metaKey : e.ctrlKey;
277
804
  if (mod && e.shiftKey && e.key.toLowerCase() === "i") {
@@ -296,6 +823,9 @@ function StateInspectorUI() {
296
823
  const idx = list.findIndex((c) => c.id === selectedId);
297
824
  const next = e.key === "ArrowDown" ? list[(Math.max(-1, idx) + 1) % list.length].id : list[idx <= 0 ? list.length - 1 : idx - 1].id;
298
825
  setSelectedId(next);
826
+ setTimeout(() => {
827
+ document.getElementById(`rsi-comp-${next}`)?.scrollIntoView({ block: "nearest" });
828
+ }, 0);
299
829
  }
300
830
  }
301
831
  window.addEventListener("keydown", onKey);
@@ -305,220 +835,128 @@ function StateInspectorUI() {
305
835
  if (!selectedId) return null;
306
836
  return store.components.get(selectedId) ?? null;
307
837
  }, [store, selectedId]);
838
+ const handleDragEnd = () => {
839
+ if (preview) {
840
+ setLayout(preview);
841
+ setIsTransitioning(true);
842
+ const rects = previewRectStyle(preview);
843
+ setSize({ width: rects.width, height: rects.height });
844
+ setPos({
845
+ left: rects.left,
846
+ right: rects.right,
847
+ top: rects.top,
848
+ bottom: rects.bottom
849
+ });
850
+ setPreview(null);
851
+ setTimeout(() => setIsTransitioning(false), 300);
852
+ return;
853
+ }
854
+ setPreview(null);
855
+ };
856
+ const handleDragStart = (e) => {
857
+ const startX = e.clientX;
858
+ const startY = e.clientY;
859
+ const start = pos;
860
+ setLayout("floating");
861
+ setIsTransitioning(true);
862
+ setSize({ width: 720, height: 420 });
863
+ setTimeout(() => setIsTransitioning(false), 300);
864
+ e.currentTarget.setPointerCapture(e.pointerId);
865
+ const onMove = (ev) => {
866
+ const dx = ev.clientX - startX;
867
+ const dy = ev.clientY - startY;
868
+ const nextPreview = computePreview(ev.clientX, ev.clientY);
869
+ setPreview((p) => p === nextPreview ? p : nextPreview);
870
+ const newPos = {};
871
+ if (start.left !== void 0) {
872
+ newPos.left = Math.max(8, start.left + dx);
873
+ } else {
874
+ newPos.right = Math.max(8, (start.right ?? 16) - dx);
875
+ }
876
+ if (start.top !== void 0) {
877
+ newPos.top = Math.max(8, start.top + dy);
878
+ } else {
879
+ newPos.bottom = Math.max(8, (start.bottom ?? 72) - dy);
880
+ }
881
+ setPos(newPos);
882
+ };
883
+ const onUp = () => {
884
+ window.removeEventListener("pointermove", onMove);
885
+ window.removeEventListener("pointerup", onUp);
886
+ };
887
+ window.addEventListener("pointermove", onMove);
888
+ window.addEventListener("pointerup", onUp);
889
+ };
890
+ const handlePanelResize = (startSize) => (delta) => {
891
+ setSize({
892
+ width: Math.min(window.innerWidth - 16, Math.max(480, (startSize.width ?? 0) + delta.dx)),
893
+ height: Math.min(window.innerHeight - 120, Math.max(280, (startSize.height ?? 0) + delta.dy))
894
+ });
895
+ };
308
896
  if (!store.enabled) return null;
309
897
  return createPortal(
310
- /* @__PURE__ */ jsxs(Fragment, { children: [
311
- /* @__PURE__ */ jsx2(
312
- "button",
313
- {
314
- title: "State Inspector (\u2318\u21E7I / Ctrl\u21E7I)",
315
- onClick: () => setOpen((o) => !o),
316
- style: {
317
- position: "fixed",
318
- right: 16,
319
- bottom: 16,
320
- width: 44,
321
- height: 44,
322
- borderRadius: 22,
323
- border: "none",
324
- background: "#111",
325
- color: "#fff",
326
- cursor: "pointer",
327
- zIndex: 999999
328
- },
329
- "aria-label": "Toggle State Inspector",
330
- children: "\u25CE"
331
- }
332
- ),
333
- open && /* @__PURE__ */ jsxs(
898
+ /* @__PURE__ */ jsxs7(Fragment, { children: [
899
+ /* @__PURE__ */ jsx14(FloatingButton, { onClick: () => setOpen((o) => !o) }),
900
+ /* @__PURE__ */ jsx14(DockPreviewOverlay, { preview }),
901
+ open && /* @__PURE__ */ jsxs7(
334
902
  "div",
335
903
  {
336
904
  style: {
337
905
  position: "fixed",
906
+ left: pos.left,
338
907
  right: pos.right,
908
+ top: pos.top,
339
909
  bottom: pos.bottom,
340
- width: size.width,
341
- maxWidth: "calc(100vw - 32px)",
342
- height: size.height,
343
- maxHeight: "calc(100vh - 120px)",
910
+ width: size.width || "calc(100dvw - 24px)",
911
+ maxWidth: "calc(100vw - 24px)",
912
+ height: size.height || "calc(100dvh - 24px)",
913
+ maxHeight: "calc(100vh - 24px)",
344
914
  background: "#1c1c1c",
345
915
  color: "#fff",
346
916
  borderRadius: 12,
347
917
  border: "1px solid #333",
348
918
  zIndex: 999999,
349
919
  display: "grid",
350
- gridTemplateColumns: "280px 1fr",
351
- overflow: "hidden"
920
+ gridTemplateColumns: leftCollapsed ? "auto" : `${leftWidth}px 14px auto`,
921
+ overflow: "hidden",
922
+ transition: isTransitioning ? "all 0.3s ease" : "none"
352
923
  },
353
924
  children: [
354
- /* @__PURE__ */ jsx2(
355
- "div",
925
+ /* @__PURE__ */ jsx14(ResizeHandle, { onResize: handlePanelResize(size) }),
926
+ /* @__PURE__ */ jsx14(
927
+ ComponentList,
356
928
  {
357
- onPointerDown: (e) => {
358
- const startX = e.clientX;
359
- const startY = e.clientY;
360
- const start = size;
361
- e.currentTarget.setPointerCapture(e.pointerId);
362
- const onMove = (ev) => {
363
- const dx = ev.clientX - startX;
364
- const dy = ev.clientY - startY;
365
- setSize({
366
- width: Math.min(window.innerWidth - 16, Math.max(480, start.width + dx)),
367
- height: Math.min(window.innerHeight - 120, Math.max(280, start.height + dy))
368
- });
369
- };
370
- const onUp = () => {
371
- window.removeEventListener("pointermove", onMove);
372
- window.removeEventListener("pointerup", onUp);
373
- };
374
- window.addEventListener("pointermove", onMove);
375
- window.addEventListener("pointerup", onUp);
376
- },
377
- style: {
378
- position: "absolute",
379
- right: 6,
380
- bottom: 6,
381
- width: 14,
382
- height: 14,
383
- borderRadius: 6,
384
- border: "1px solid #333",
385
- background: "#838383",
386
- cursor: "nwse-resize"
387
- }
929
+ components: filtered,
930
+ selectedId,
931
+ onSelect: setSelectedId,
932
+ onDragStart: handleDragStart,
933
+ onDragEnd: handleDragEnd,
934
+ height: size.height,
935
+ hidden: leftCollapsed
388
936
  }
389
937
  ),
390
- /* @__PURE__ */ jsxs("div", { style: { borderRight: "1px solid #333", padding: 12, height: size.height, display: "flex", flexDirection: "column", boxSizing: "border-box" }, children: [
391
- /* @__PURE__ */ jsxs(
392
- "div",
393
- {
394
- style: { display: "flex", alignItems: "center", justifyContent: "space-between", cursor: "grab", userSelect: "none" },
395
- onPointerDown: (e) => {
396
- const startX = e.clientX;
397
- const startY = e.clientY;
398
- const start = pos;
399
- e.currentTarget.setPointerCapture(e.pointerId);
400
- const onMove = (ev) => {
401
- const dx = ev.clientX - startX;
402
- const dy = ev.clientY - startY;
403
- setPos({
404
- right: Math.max(8, start.right - dx),
405
- bottom: Math.max(8, start.bottom - dy)
406
- });
407
- };
408
- const onUp = () => {
409
- window.removeEventListener("pointermove", onMove);
410
- window.removeEventListener("pointerup", onUp);
411
- };
412
- window.addEventListener("pointermove", onMove);
413
- window.addEventListener("pointerup", onUp);
414
- },
415
- children: [
416
- /* @__PURE__ */ jsx2("h4", { style: { margin: 0 }, children: "Components" }),
417
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 12, opacity: 0.7 }, children: filtered.length })
418
- ]
419
- }
420
- ),
421
- /* @__PURE__ */ jsx2("div", { style: { marginTop: 10, display: "grid", gap: 8, overflow: "auto", flex: 1 }, children: filtered.map((c) => {
422
- const active = c.id === selectedId;
423
- return /* @__PURE__ */ jsxs(
424
- "button",
425
- {
426
- onClick: () => setSelectedId(c.id),
427
- style: {
428
- textAlign: "left",
429
- background: active ? "#2a2a2a" : "transparent",
430
- border: "1px solid #333",
431
- borderRadius: 10,
432
- padding: 10,
433
- color: "#fff",
434
- cursor: "pointer"
435
- },
436
- children: [
437
- /* @__PURE__ */ jsx2("div", { style: { fontWeight: 700 }, children: c.label }),
438
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, opacity: 0.7 }, children: [
439
- "states: ",
440
- c.stateKeys.length ? c.stateKeys.length : 0
441
- ] })
442
- ]
443
- },
444
- c.id
445
- );
446
- }) })
447
- ] }),
448
- /* @__PURE__ */ jsxs("div", { style: { padding: 12, overflow: "auto", display: "flex", flexDirection: "column", boxSizing: "border-box" }, children: [
449
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
450
- /* @__PURE__ */ jsxs("div", { children: [
451
- /* @__PURE__ */ jsx2("h4", { style: { margin: 0 }, children: "State" }),
452
- /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, opacity: 0.7 }, children: selected ? selected.label : "No selection" })
453
- ] }),
454
- /* @__PURE__ */ jsx2(
455
- "button",
456
- {
457
- onClick: () => setOpen(false),
458
- style: {
459
- background: "transparent",
460
- color: "#fff",
461
- border: "1px solid #333",
462
- borderRadius: 10,
463
- padding: "6px 10px",
464
- cursor: "pointer",
465
- fontSize: 12
466
- },
467
- children: "Close"
468
- }
469
- )
470
- ] }),
471
- /* @__PURE__ */ jsx2(
472
- "input",
473
- {
474
- value: query,
475
- onChange: (e) => setQuery(e.target.value),
476
- placeholder: "Search components / state keys\u2026",
477
- ref: searchRef,
478
- style: {
479
- marginTop: 10,
480
- padding: "8px 10px",
481
- borderRadius: 10,
482
- border: "1px solid #333",
483
- background: "#111",
484
- color: "#fff",
485
- outline: "none"
486
- }
487
- }
488
- ),
489
- /* @__PURE__ */ jsxs("div", { style: { marginTop: 12, display: "grid", gap: 10 }, children: [
490
- selected && selected.states.size === 0 && /* @__PURE__ */ jsx2("div", { style: { fontSize: 13, opacity: 0.7 }, children: "This component has no inspectable state." }),
491
- !selected && /* @__PURE__ */ jsx2("div", { style: { opacity: 0.7, fontSize: 13 }, children: "No component selected." }),
492
- selected && Array.from(selected.states.values()).map((s) => /* @__PURE__ */ jsxs(
493
- "div",
494
- {
495
- style: {
496
- border: "1px solid #333",
497
- borderRadius: 12,
498
- padding: 10,
499
- display: "flex",
500
- flexDirection: "column",
501
- gap: 8
502
- },
503
- children: [
504
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [
505
- /* @__PURE__ */ jsx2("div", { style: { fontWeight: 700 }, children: s.key }),
506
- /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, opacity: 0.6 }, children: s.meta?.type ?? "auto" })
507
- ] }),
508
- /* @__PURE__ */ jsx2(
509
- StateEditor,
510
- {
511
- value: s.value,
512
- meta: s.meta,
513
- onChange: (next) => s.setValue(next)
514
- }
515
- )
516
- ]
517
- },
518
- s.key
519
- ))
520
- ] })
521
- ] })
938
+ /* @__PURE__ */ jsx14(
939
+ SidebarResizer,
940
+ {
941
+ leftWidth,
942
+ onWidthChange: setLeftWidth,
943
+ hidden: leftCollapsed
944
+ }
945
+ ),
946
+ /* @__PURE__ */ jsx14(
947
+ StateEditorPanel,
948
+ {
949
+ selected,
950
+ query,
951
+ onQueryChange: setQuery,
952
+ searchRef,
953
+ leftCollapsed,
954
+ onToggleCollapse: () => setLeftCollapsed((v) => !v),
955
+ onClose: () => setOpen(false),
956
+ onDragStart: handleDragStart,
957
+ onDragEnd: handleDragEnd
958
+ }
959
+ )
522
960
  ]
523
961
  }
524
962
  )
@@ -526,133 +964,6 @@ function StateInspectorUI() {
526
964
  document.body
527
965
  );
528
966
  }
529
- function StateEditor({
530
- value,
531
- meta,
532
- onChange
533
- }) {
534
- if (meta?.type === "boolean" || typeof value === "boolean") {
535
- return /* @__PURE__ */ jsxs("label", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
536
- /* @__PURE__ */ jsx2(
537
- "input",
538
- {
539
- type: "checkbox",
540
- checked: Boolean(value),
541
- onChange: (e) => onChange(e.target.checked)
542
- }
543
- ),
544
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, opacity: 0.8 }, children: String(Boolean(value)) })
545
- ] });
546
- }
547
- if (meta?.type === "select" && Array.isArray(meta.options)) {
548
- const current = typeof value === "string" ? value : String(value ?? "");
549
- return /* @__PURE__ */ jsx2(
550
- "select",
551
- {
552
- value: current,
553
- onChange: (e) => onChange(e.target.value),
554
- style: {
555
- padding: "8px 10px",
556
- borderRadius: 10,
557
- border: "1px solid #333",
558
- background: "#111",
559
- color: "#fff"
560
- },
561
- children: meta.options.map((opt) => {
562
- const o = isOptionObject(opt) ? opt : { label: String(opt), value: String(opt) };
563
- return /* @__PURE__ */ jsx2("option", { value: o.value, children: o.label }, o.value);
564
- })
565
- }
566
- );
567
- }
568
- if (meta?.type === "number") {
569
- return /* @__PURE__ */ jsx2(
570
- "input",
571
- {
572
- type: "number",
573
- value: typeof value === "number" ? value : Number(value ?? 0),
574
- min: meta?.min,
575
- max: meta?.max,
576
- step: meta?.step,
577
- onChange: (e) => onChange(e.target.value === "" ? 0 : Number(e.target.value)),
578
- style: {
579
- padding: "8px 10px",
580
- borderRadius: 10,
581
- border: "1px solid #333",
582
- background: "#111",
583
- color: "#fff"
584
- }
585
- }
586
- );
587
- }
588
- if (meta?.type === "json" || value && typeof value === "object") {
589
- const text = safeStringify(value);
590
- return /* @__PURE__ */ jsx2(JsonEditor, { initial: text, onValidJson: (obj) => onChange(obj) });
591
- }
592
- return /* @__PURE__ */ jsx2(
593
- "input",
594
- {
595
- type: "text",
596
- value: typeof value === "string" ? value : String(value ?? ""),
597
- placeholder: meta?.type === "text" ? meta.placeholder : void 0,
598
- onChange: (e) => onChange(e.target.value),
599
- style: {
600
- padding: "8px 10px",
601
- borderRadius: 10,
602
- border: "1px solid #333",
603
- background: "#111",
604
- color: "#fff"
605
- }
606
- }
607
- );
608
- }
609
- function safeStringify(v) {
610
- try {
611
- return JSON.stringify(v, null, 2);
612
- } catch {
613
- return String(v);
614
- }
615
- }
616
- function JsonEditor({
617
- initial,
618
- onValidJson
619
- }) {
620
- const [raw, setRaw] = useState2(initial);
621
- const [error, setError] = useState2(null);
622
- useEffect3(() => {
623
- setRaw(initial);
624
- }, [initial]);
625
- return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 6 }, children: [
626
- /* @__PURE__ */ jsx2(
627
- "textarea",
628
- {
629
- value: raw,
630
- onChange: (e) => {
631
- const next = e.target.value;
632
- setRaw(next);
633
- try {
634
- const parsed = JSON.parse(next);
635
- setError(null);
636
- onValidJson(parsed);
637
- } catch {
638
- setError("Invalid JSON");
639
- }
640
- },
641
- rows: 6,
642
- style: {
643
- padding: "8px 10px",
644
- borderRadius: 10,
645
- border: "1px solid #333",
646
- background: "#111",
647
- color: "#fff",
648
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
649
- fontSize: 12
650
- }
651
- }
652
- ),
653
- error && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, color: "#ff6b6b" }, children: error })
654
- ] });
655
- }
656
967
  export {
657
968
  StateInspectorProvider,
658
969
  StateInspectorUI,