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