react-state-inspector-devtools 0.1.2 → 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
@@ -116,16 +116,22 @@ function createInspectorStore() {
116
116
  // src/provider.tsx
117
117
  var import_jsx_runtime = require("react/jsx-runtime");
118
118
  var InspectorContext = (0, import_react.createContext)(null);
119
+ var storeRef = { current: null };
120
+ function getStore() {
121
+ if (!storeRef.current) {
122
+ storeRef.current = createInspectorStore();
123
+ }
124
+ return storeRef.current;
125
+ }
119
126
  function StateInspectorProvider({
120
127
  enabled = true,
121
128
  children
122
129
  }) {
123
- const storeRef = (0, import_react.useRef)(null);
124
- if (!storeRef.current) {
125
- storeRef.current = createInspectorStore();
126
- }
127
- storeRef.current.enabled = enabled;
128
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InspectorContext.Provider, { value: storeRef.current, children });
130
+ const store = (0, import_react.useMemo)(() => getStore(), []);
131
+ (0, import_react.useEffect)(() => {
132
+ storeRef.current.enabled = enabled;
133
+ }, [enabled]);
134
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InspectorContext.Provider, { value: store, children });
129
135
  }
130
136
  function useInspectorStore() {
131
137
  const ctx = (0, import_react.useContext)(InspectorContext);
@@ -214,189 +220,397 @@ function useComponentLabel(label) {
214
220
  }
215
221
 
216
222
  // src/StateInspectorUI.tsx
217
- var import_react3 = require("react");
223
+ var import_react4 = require("react");
218
224
  var import_react_dom = require("react-dom");
219
- var import_jsx_runtime2 = require("react/jsx-runtime");
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
220
240
  function isOptionObject(opt) {
221
241
  return typeof opt === "object" && opt !== null && "value" in opt;
222
242
  }
223
- function StateInspectorUI() {
224
- const store = useInspectorStore();
225
- const [open, setOpen] = (0, import_react3.useState)(false);
226
- const [selectedId, setSelectedId] = (0, import_react3.useState)(null);
227
- const [, force] = (0, import_react3.useState)(0);
228
- (0, import_react3.useEffect)(() => store.subscribe(() => force((x) => x + 1)), [store]);
229
- if (!store.enabled) return null;
230
- const snapshot = store.getSnapshot();
231
- const components = snapshot.components.filter((c) => c.mounted);
232
- (0, import_react3.useEffect)(() => {
233
- if (!open) return;
234
- if (components.length === 0) {
235
- setSelectedId(null);
236
- return;
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"
237
305
  }
238
- if (!selectedId || !components.some((c) => c.id === selectedId)) {
239
- setSelectedId(components[0].id);
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) })
240
323
  }
241
- }, [open, components.length]);
242
- (0, import_react3.useEffect)(() => {
243
- if (!open) return;
244
- function onKey(e) {
245
- if (e.key === "Escape") setOpen(false);
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
+ }
246
361
  }
247
- window.addEventListener("keydown", onKey);
248
- return () => window.removeEventListener("keydown", onKey);
249
- }, [open]);
250
- const selected = (0, import_react3.useMemo)(() => {
251
- if (!selectedId) return null;
252
- return store.components.get(selectedId) ?? null;
253
- }, [store, selectedId, snapshot.components.length]);
254
- return (0, import_react_dom.createPortal)(
255
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
256
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
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)(
257
443
  "button",
258
444
  {
259
- onClick: () => setOpen((o) => !o),
445
+ id: `rsi-comp-${c.id}`,
446
+ onClick: () => onSelect(c.id),
260
447
  style: {
261
- position: "fixed",
262
- right: 16,
263
- bottom: 16,
264
- width: 44,
265
- height: 44,
266
- borderRadius: 22,
267
- border: "none",
268
- background: "#111",
448
+ textAlign: "left",
449
+ background: active ? "#2a2a2a" : "transparent",
450
+ border: "1px solid #333",
451
+ borderRadius: 10,
452
+ padding: 10,
269
453
  color: "#fff",
270
454
  cursor: "pointer",
271
- zIndex: 999999
272
- },
273
- "aria-label": "Toggle State Inspector",
274
- title: "State Inspector",
275
- children: "\u25CE"
276
- }
277
- ),
278
- open && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
279
- "div",
280
- {
281
- style: {
282
- position: "fixed",
283
- right: 16,
284
- bottom: 72,
285
- width: 720,
286
- maxWidth: "calc(100vw - 32px)",
287
- height: 420,
288
- maxHeight: "calc(100vh - 120px)",
289
- background: "#1c1c1c",
290
- color: "#fff",
291
- borderRadius: 12,
292
- border: "1px solid #333",
293
- zIndex: 999999,
294
- display: "grid",
295
- gridTemplateColumns: "280px 1fr",
296
- overflow: "hidden"
455
+ flex: "none"
297
456
  },
298
457
  children: [
299
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { borderRight: "1px solid #333", padding: 12, height: "420px", display: "flex", flexDirection: "column", boxSizing: "border-box" }, children: [
300
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
301
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { style: { margin: 0 }, children: "Components" }),
302
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 12, opacity: 0.7 }, children: components.length })
303
- ] }),
304
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: 10, display: "grid", gap: 8, overflow: "auto", flex: 1 }, children: components.map((c) => {
305
- const active = c.id === selectedId;
306
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
307
- "button",
308
- {
309
- onClick: () => setSelectedId(c.id),
310
- style: {
311
- textAlign: "left",
312
- background: active ? "#2a2a2a" : "transparent",
313
- border: "1px solid #333",
314
- borderRadius: 10,
315
- padding: 10,
316
- color: "#fff",
317
- cursor: "pointer"
318
- },
319
- children: [
320
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 700 }, children: c.label }),
321
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: 12, opacity: 0.7 }, children: [
322
- "states: ",
323
- c.stateKeys.length ? c.stateKeys.length : 0
324
- ] })
325
- ]
326
- },
327
- c.id
328
- );
329
- }) })
330
- ] }),
331
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: 12, overflow: "auto" }, children: [
332
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
333
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
334
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { style: { margin: 0 }, children: "State" }),
335
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, opacity: 0.7 }, children: selected ? selected.label : "No selection" })
336
- ] }),
337
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
338
- "button",
339
- {
340
- onClick: () => setOpen(false),
341
- style: {
342
- background: "transparent",
343
- color: "#fff",
344
- border: "1px solid #333",
345
- borderRadius: 10,
346
- padding: "6px 10px",
347
- cursor: "pointer"
348
- },
349
- children: "Close"
350
- }
351
- )
352
- ] }),
353
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: 12, display: "grid", gap: 10 }, children: [
354
- 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." }),
355
- !selected && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { opacity: 0.7, fontSize: 13 }, children: "No component selected." }),
356
- selected && Array.from(selected.states.values()).map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
357
- "div",
358
- {
359
- style: {
360
- border: "1px solid #333",
361
- borderRadius: 12,
362
- padding: 10,
363
- display: "grid",
364
- gap: 8
365
- },
366
- children: [
367
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [
368
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 700 }, children: s.key }),
369
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, opacity: 0.6 }, children: s.meta?.type ?? "auto" })
370
- ] }),
371
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
372
- StateEditor,
373
- {
374
- value: s.value,
375
- meta: s.meta,
376
- onChange: (next) => s.setValue(next)
377
- }
378
- )
379
- ]
380
- },
381
- s.key
382
- ))
383
- ] })
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
384
462
  ] })
385
463
  ]
386
- }
387
- )
388
- ] }),
389
- document.body
390
- );
464
+ },
465
+ c.id
466
+ );
467
+ }) })
468
+ ] });
391
469
  }
392
- function StateEditor({
393
- value,
394
- meta,
395
- onChange
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
396
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 }) {
397
611
  if (meta?.type === "boolean" || typeof value === "boolean") {
398
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
399
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
612
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
613
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
400
614
  "input",
401
615
  {
402
616
  type: "checkbox",
@@ -404,33 +618,26 @@ function StateEditor({
404
618
  onChange: (e) => onChange(e.target.checked)
405
619
  }
406
620
  ),
407
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 13, opacity: 0.8 }, children: String(Boolean(value)) })
621
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontSize: 13, opacity: 0.8 }, children: String(Boolean(value)) })
408
622
  ] });
409
623
  }
410
624
  if (meta?.type === "select" && Array.isArray(meta.options)) {
411
625
  const current = typeof value === "string" ? value : String(value ?? "");
412
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
626
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
413
627
  "select",
414
628
  {
415
629
  value: current,
416
630
  onChange: (e) => onChange(e.target.value),
417
- style: {
418
- width: "100%",
419
- padding: "8px 10px",
420
- borderRadius: 10,
421
- border: "1px solid #333",
422
- background: "#111",
423
- color: "#fff"
424
- },
631
+ style: inputStyle,
425
632
  children: meta.options.map((opt) => {
426
633
  const o = isOptionObject(opt) ? opt : { label: String(opt), value: String(opt) };
427
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: o.value, children: o.label }, o.value);
634
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: o.value, children: o.label }, o.value);
428
635
  })
429
636
  }
430
637
  );
431
638
  }
432
- if (meta?.type === "number" || typeof value === "number") {
433
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
639
+ if (meta?.type === "number") {
640
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
434
641
  "input",
435
642
  {
436
643
  type: "number",
@@ -439,87 +646,356 @@ function StateEditor({
439
646
  max: meta?.max,
440
647
  step: meta?.step,
441
648
  onChange: (e) => onChange(e.target.value === "" ? 0 : Number(e.target.value)),
442
- style: {
443
- width: "100%",
444
- padding: "8px 10px",
445
- borderRadius: 10,
446
- border: "1px solid #333",
447
- background: "#111",
448
- color: "#fff"
449
- }
649
+ style: inputStyle
450
650
  }
451
651
  );
452
652
  }
453
653
  if (meta?.type === "json" || value && typeof value === "object") {
454
- const text = safeStringify(value);
455
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JsonEditor, { initial: text, onValidJson: (obj) => onChange(obj) });
654
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(JsonEditor, { initial: safeStringify(value), onValidJson: onChange });
456
655
  }
457
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
656
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
458
657
  "input",
459
658
  {
460
659
  type: "text",
461
660
  value: typeof value === "string" ? value : String(value ?? ""),
462
- placeholder: meta?.placeholder,
661
+ placeholder: meta?.type === "text" ? meta.placeholder : void 0,
463
662
  onChange: (e) => onChange(e.target.value),
464
- style: {
465
- width: "100%",
466
- padding: "8px 10px",
467
- borderRadius: 10,
468
- border: "1px solid #333",
469
- background: "#111",
470
- color: "#fff"
471
- }
663
+ style: inputStyle
472
664
  }
473
665
  );
474
666
  }
475
- function safeStringify(v) {
476
- try {
477
- return JSON.stringify(v, null, 2);
478
- } catch {
479
- return String(v);
480
- }
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
+ );
481
696
  }
482
- function JsonEditor({
483
- initial,
484
- onValidJson
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
485
710
  }) {
486
- const [raw, setRaw] = (0, import_react3.useState)(initial);
487
- const [error, setError] = (0, import_react3.useState)(null);
488
- (0, import_react3.useEffect)(() => {
489
- setRaw(initial);
490
- }, [initial]);
491
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "grid", gap: 6 }, children: [
492
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
493
- "textarea",
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,
494
714
  {
495
- value: raw,
496
- onChange: (e) => {
497
- const next = e.target.value;
498
- setRaw(next);
499
- try {
500
- const parsed = JSON.parse(next);
501
- setError(null);
502
- onValidJson(parsed);
503
- } catch (err) {
504
- setError("Invalid JSON");
505
- }
506
- },
507
- rows: 6,
508
- style: {
509
- width: "100%",
510
- padding: "8px 10px",
511
- borderRadius: 10,
512
- border: "1px solid #333",
513
- background: "#111",
514
- color: "#fff",
515
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
516
- fontSize: 12
517
- }
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
518
729
  }
519
730
  ),
520
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#ff6b6b" }, children: error })
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
+ ] })
521
736
  ] });
522
737
  }
738
+
739
+ // src/StateInspectorUI.tsx
740
+ var import_jsx_runtime14 = require("react/jsx-runtime");
741
+ function StateInspectorUI() {
742
+ const store = useInspectorStore();
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)(() => {
753
+ try {
754
+ const raw = localStorage.getItem(LS_KEY);
755
+ return raw ? JSON.parse(raw) : { right: 16, bottom: 72 };
756
+ } catch {
757
+ return { right: 16, bottom: 72 };
758
+ }
759
+ });
760
+ const [leftWidth, setLeftWidth] = (0, import_react4.useState)(() => {
761
+ try {
762
+ const raw = localStorage.getItem(LS_LEFT_W);
763
+ return raw ? Number(raw) : 280;
764
+ } catch {
765
+ return 280;
766
+ }
767
+ });
768
+ const [size, setSize] = (0, import_react4.useState)(() => {
769
+ try {
770
+ const raw = localStorage.getItem(LS_SIZE);
771
+ return raw ? JSON.parse(raw) : { width: 720, height: 420 };
772
+ } catch {
773
+ return { width: 720, height: 420 };
774
+ }
775
+ });
776
+ (0, import_react4.useEffect)(() => {
777
+ try {
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);
802
+ }
803
+ }, [size]);
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)(
811
+ () => snapshot.components.filter((c) => c.mounted),
812
+ [snapshot.components]
813
+ );
814
+ const q = query.trim().toLowerCase();
815
+ const filtered = (0, import_react4.useMemo)(
816
+ () => q ? components.filter((c) => {
817
+ const labelHit = c.label.toLowerCase().includes(q);
818
+ const keyHit = c.stateKeys.some((k) => k.toLowerCase().includes(q));
819
+ return labelHit || keyHit;
820
+ }) : components,
821
+ [q, components]
822
+ );
823
+ (0, import_react4.useEffect)(() => {
824
+ if (!open) return;
825
+ if (components.length === 0) {
826
+ setSelectedId(null);
827
+ return;
828
+ }
829
+ if (!selectedId || !components.some((c) => c.id === selectedId)) {
830
+ setSelectedId(components[0].id);
831
+ }
832
+ }, [open, components.length]);
833
+ (0, import_react4.useEffect)(() => {
834
+ function onKey(e) {
835
+ const mod = isMac ? e.metaKey : e.ctrlKey;
836
+ if (mod && e.shiftKey && e.key.toLowerCase() === "i") {
837
+ e.preventDefault();
838
+ setOpen((o) => !o);
839
+ return;
840
+ }
841
+ if (!open) return;
842
+ if (e.key === "Escape") {
843
+ setOpen(false);
844
+ return;
845
+ }
846
+ if (e.key === "/") {
847
+ e.preventDefault();
848
+ searchRef.current?.focus();
849
+ return;
850
+ }
851
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
852
+ e.preventDefault();
853
+ const list = filtered;
854
+ if (!list.length) return;
855
+ const idx = list.findIndex((c) => c.id === selectedId);
856
+ const next = e.key === "ArrowDown" ? list[(Math.max(-1, idx) + 1) % list.length].id : list[idx <= 0 ? list.length - 1 : idx - 1].id;
857
+ setSelectedId(next);
858
+ setTimeout(() => {
859
+ document.getElementById(`rsi-comp-${next}`)?.scrollIntoView({ block: "nearest" });
860
+ }, 0);
861
+ }
862
+ }
863
+ window.addEventListener("keydown", onKey);
864
+ return () => window.removeEventListener("keydown", onKey);
865
+ }, [open, filtered, selectedId]);
866
+ const selected = (0, import_react4.useMemo)(() => {
867
+ if (!selectedId) return null;
868
+ return store.components.get(selectedId) ?? null;
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
+ };
928
+ if (!store.enabled) return null;
929
+ return (0, import_react_dom.createPortal)(
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)(
934
+ "div",
935
+ {
936
+ style: {
937
+ position: "fixed",
938
+ left: pos.left,
939
+ right: pos.right,
940
+ top: pos.top,
941
+ bottom: pos.bottom,
942
+ width: size.width || "calc(100dvw - 24px)",
943
+ maxWidth: "calc(100vw - 24px)",
944
+ height: size.height || "calc(100dvh - 24px)",
945
+ maxHeight: "calc(100vh - 24px)",
946
+ background: "#1c1c1c",
947
+ color: "#fff",
948
+ borderRadius: 12,
949
+ border: "1px solid #333",
950
+ zIndex: 999999,
951
+ display: "grid",
952
+ gridTemplateColumns: leftCollapsed ? "auto" : `${leftWidth}px 14px auto`,
953
+ overflow: "hidden",
954
+ transition: isTransitioning ? "all 0.3s ease" : "none"
955
+ },
956
+ children: [
957
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ResizeHandle, { onResize: handlePanelResize(size) }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
959
+ ComponentList,
960
+ {
961
+ components: filtered,
962
+ selectedId,
963
+ onSelect: setSelectedId,
964
+ onDragStart: handleDragStart,
965
+ onDragEnd: handleDragEnd,
966
+ height: size.height,
967
+ hidden: leftCollapsed
968
+ }
969
+ ),
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
+ )
992
+ ]
993
+ }
994
+ )
995
+ ] }),
996
+ document.body
997
+ );
998
+ }
523
999
  // Annotate the CommonJS export names for ESM import in node:
524
1000
  0 && (module.exports = {
525
1001
  StateInspectorProvider,