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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/provider.tsx
2
- import { createContext, useContext, useEffect, useId, useMemo, useRef } from "react";
2
+ import { createContext, useContext, useEffect, useId, useMemo } from "react";
3
3
 
4
4
  // src/store.ts
5
5
  function createInspectorStore() {
@@ -84,16 +84,22 @@ function createInspectorStore() {
84
84
  // src/provider.tsx
85
85
  import { jsx } from "react/jsx-runtime";
86
86
  var InspectorContext = createContext(null);
87
+ var storeRef = { current: null };
88
+ function getStore() {
89
+ if (!storeRef.current) {
90
+ storeRef.current = createInspectorStore();
91
+ }
92
+ return storeRef.current;
93
+ }
87
94
  function StateInspectorProvider({
88
95
  enabled = true,
89
96
  children
90
97
  }) {
91
- const storeRef = useRef(null);
92
- if (!storeRef.current) {
93
- storeRef.current = createInspectorStore();
94
- }
95
- storeRef.current.enabled = enabled;
96
- return /* @__PURE__ */ jsx(InspectorContext.Provider, { value: storeRef.current, children });
98
+ const store = useMemo(() => getStore(), []);
99
+ useEffect(() => {
100
+ storeRef.current.enabled = enabled;
101
+ }, [enabled]);
102
+ return /* @__PURE__ */ jsx(InspectorContext.Provider, { value: store, children });
97
103
  }
98
104
  function useInspectorStore() {
99
105
  const ctx = useContext(InspectorContext);
@@ -122,7 +128,7 @@ function useInspectorComponent(label) {
122
128
  }
123
129
 
124
130
  // src/hooks.ts
125
- import { useEffect as useEffect2, useId as useId2, useMemo as useMemo2, useRef as useRef2, useState } from "react";
131
+ import { useEffect as useEffect2, useId as useId2, useMemo as useMemo2, useRef, useState } from "react";
126
132
  function stableKey(input) {
127
133
  return input.trim();
128
134
  }
@@ -139,7 +145,7 @@ function useInspectableState(component, key, initial, meta) {
139
145
  const componentId = component.id;
140
146
  const stateKey = useMemo2(() => stableKey(key), [key]);
141
147
  const [value, setValue] = useState(initial);
142
- const setValueRef = useRef2(() => {
148
+ const setValueRef = useRef(() => {
143
149
  });
144
150
  setValueRef.current = (next) => {
145
151
  setValue(next);
@@ -182,189 +188,397 @@ function useComponentLabel(label) {
182
188
  }
183
189
 
184
190
  // src/StateInspectorUI.tsx
185
- import { useEffect as useEffect3, useMemo as useMemo3, useState as useState2 } from "react";
191
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef2, useState as useState3 } from "react";
186
192
  import { createPortal } from "react-dom";
187
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
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
188
208
  function isOptionObject(opt) {
189
209
  return typeof opt === "object" && opt !== null && "value" in opt;
190
210
  }
191
- function StateInspectorUI() {
192
- const store = useInspectorStore();
193
- const [open, setOpen] = useState2(false);
194
- const [selectedId, setSelectedId] = useState2(null);
195
- const [, force] = useState2(0);
196
- useEffect3(() => store.subscribe(() => force((x) => x + 1)), [store]);
197
- if (!store.enabled) return null;
198
- const snapshot = store.getSnapshot();
199
- const components = snapshot.components.filter((c) => c.mounted);
200
- useEffect3(() => {
201
- if (!open) return;
202
- if (components.length === 0) {
203
- setSelectedId(null);
204
- return;
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"
205
273
  }
206
- if (!selectedId || !components.some((c) => c.id === selectedId)) {
207
- setSelectedId(components[0].id);
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) })
208
291
  }
209
- }, [open, components.length]);
210
- useEffect3(() => {
211
- if (!open) return;
212
- function onKey(e) {
213
- if (e.key === "Escape") setOpen(false);
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
+ }
214
329
  }
215
- window.addEventListener("keydown", onKey);
216
- return () => window.removeEventListener("keydown", onKey);
217
- }, [open]);
218
- const selected = useMemo3(() => {
219
- if (!selectedId) return null;
220
- return store.components.get(selectedId) ?? null;
221
- }, [store, selectedId, snapshot.components.length]);
222
- return createPortal(
223
- /* @__PURE__ */ jsxs(Fragment, { children: [
224
- /* @__PURE__ */ jsx2(
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(
225
411
  "button",
226
412
  {
227
- onClick: () => setOpen((o) => !o),
413
+ id: `rsi-comp-${c.id}`,
414
+ onClick: () => onSelect(c.id),
228
415
  style: {
229
- position: "fixed",
230
- right: 16,
231
- bottom: 16,
232
- width: 44,
233
- height: 44,
234
- borderRadius: 22,
235
- border: "none",
236
- background: "#111",
416
+ textAlign: "left",
417
+ background: active ? "#2a2a2a" : "transparent",
418
+ border: "1px solid #333",
419
+ borderRadius: 10,
420
+ padding: 10,
237
421
  color: "#fff",
238
422
  cursor: "pointer",
239
- zIndex: 999999
240
- },
241
- "aria-label": "Toggle State Inspector",
242
- title: "State Inspector",
243
- children: "\u25CE"
244
- }
245
- ),
246
- open && /* @__PURE__ */ jsxs(
247
- "div",
248
- {
249
- style: {
250
- position: "fixed",
251
- right: 16,
252
- bottom: 72,
253
- width: 720,
254
- maxWidth: "calc(100vw - 32px)",
255
- height: 420,
256
- maxHeight: "calc(100vh - 120px)",
257
- background: "#1c1c1c",
258
- color: "#fff",
259
- borderRadius: 12,
260
- border: "1px solid #333",
261
- zIndex: 999999,
262
- display: "grid",
263
- gridTemplateColumns: "280px 1fr",
264
- overflow: "hidden"
423
+ flex: "none"
265
424
  },
266
425
  children: [
267
- /* @__PURE__ */ jsxs("div", { style: { borderRight: "1px solid #333", padding: 12, height: "420px", display: "flex", flexDirection: "column", boxSizing: "border-box" }, children: [
268
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
269
- /* @__PURE__ */ jsx2("h4", { style: { margin: 0 }, children: "Components" }),
270
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 12, opacity: 0.7 }, children: components.length })
271
- ] }),
272
- /* @__PURE__ */ jsx2("div", { style: { marginTop: 10, display: "grid", gap: 8, overflow: "auto", flex: 1 }, children: components.map((c) => {
273
- const active = c.id === selectedId;
274
- return /* @__PURE__ */ jsxs(
275
- "button",
276
- {
277
- onClick: () => setSelectedId(c.id),
278
- style: {
279
- textAlign: "left",
280
- background: active ? "#2a2a2a" : "transparent",
281
- border: "1px solid #333",
282
- borderRadius: 10,
283
- padding: 10,
284
- color: "#fff",
285
- cursor: "pointer"
286
- },
287
- children: [
288
- /* @__PURE__ */ jsx2("div", { style: { fontWeight: 700 }, children: c.label }),
289
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, opacity: 0.7 }, children: [
290
- "states: ",
291
- c.stateKeys.length ? c.stateKeys.length : 0
292
- ] })
293
- ]
294
- },
295
- c.id
296
- );
297
- }) })
298
- ] }),
299
- /* @__PURE__ */ jsxs("div", { style: { padding: 12, overflow: "auto" }, children: [
300
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
301
- /* @__PURE__ */ jsxs("div", { children: [
302
- /* @__PURE__ */ jsx2("h4", { style: { margin: 0 }, children: "State" }),
303
- /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, opacity: 0.7 }, children: selected ? selected.label : "No selection" })
304
- ] }),
305
- /* @__PURE__ */ jsx2(
306
- "button",
307
- {
308
- onClick: () => setOpen(false),
309
- style: {
310
- background: "transparent",
311
- color: "#fff",
312
- border: "1px solid #333",
313
- borderRadius: 10,
314
- padding: "6px 10px",
315
- cursor: "pointer"
316
- },
317
- children: "Close"
318
- }
319
- )
320
- ] }),
321
- /* @__PURE__ */ jsxs("div", { style: { marginTop: 12, display: "grid", gap: 10 }, children: [
322
- selected && selected.states.size === 0 && /* @__PURE__ */ jsx2("div", { style: { fontSize: 13, opacity: 0.7 }, children: "This component has no inspectable state." }),
323
- !selected && /* @__PURE__ */ jsx2("div", { style: { opacity: 0.7, fontSize: 13 }, children: "No component selected." }),
324
- selected && Array.from(selected.states.values()).map((s) => /* @__PURE__ */ jsxs(
325
- "div",
326
- {
327
- style: {
328
- border: "1px solid #333",
329
- borderRadius: 12,
330
- padding: 10,
331
- display: "grid",
332
- gap: 8
333
- },
334
- children: [
335
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [
336
- /* @__PURE__ */ jsx2("div", { style: { fontWeight: 700 }, children: s.key }),
337
- /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, opacity: 0.6 }, children: s.meta?.type ?? "auto" })
338
- ] }),
339
- /* @__PURE__ */ jsx2(
340
- StateEditor,
341
- {
342
- value: s.value,
343
- meta: s.meta,
344
- onChange: (next) => s.setValue(next)
345
- }
346
- )
347
- ]
348
- },
349
- s.key
350
- ))
351
- ] })
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
352
430
  ] })
353
431
  ]
354
- }
355
- )
356
- ] }),
357
- document.body
358
- );
432
+ },
433
+ c.id
434
+ );
435
+ }) })
436
+ ] });
359
437
  }
360
- function StateEditor({
361
- value,
362
- meta,
363
- onChange
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
364
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 }) {
365
579
  if (meta?.type === "boolean" || typeof value === "boolean") {
366
- return /* @__PURE__ */ jsxs("label", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
367
- /* @__PURE__ */ jsx2(
580
+ return /* @__PURE__ */ jsxs4("label", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
581
+ /* @__PURE__ */ jsx11(
368
582
  "input",
369
583
  {
370
584
  type: "checkbox",
@@ -372,33 +586,26 @@ function StateEditor({
372
586
  onChange: (e) => onChange(e.target.checked)
373
587
  }
374
588
  ),
375
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, opacity: 0.8 }, children: String(Boolean(value)) })
589
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 13, opacity: 0.8 }, children: String(Boolean(value)) })
376
590
  ] });
377
591
  }
378
592
  if (meta?.type === "select" && Array.isArray(meta.options)) {
379
593
  const current = typeof value === "string" ? value : String(value ?? "");
380
- return /* @__PURE__ */ jsx2(
594
+ return /* @__PURE__ */ jsx11(
381
595
  "select",
382
596
  {
383
597
  value: current,
384
598
  onChange: (e) => onChange(e.target.value),
385
- style: {
386
- width: "100%",
387
- padding: "8px 10px",
388
- borderRadius: 10,
389
- border: "1px solid #333",
390
- background: "#111",
391
- color: "#fff"
392
- },
599
+ style: inputStyle,
393
600
  children: meta.options.map((opt) => {
394
601
  const o = isOptionObject(opt) ? opt : { label: String(opt), value: String(opt) };
395
- return /* @__PURE__ */ jsx2("option", { value: o.value, children: o.label }, o.value);
602
+ return /* @__PURE__ */ jsx11("option", { value: o.value, children: o.label }, o.value);
396
603
  })
397
604
  }
398
605
  );
399
606
  }
400
- if (meta?.type === "number" || typeof value === "number") {
401
- return /* @__PURE__ */ jsx2(
607
+ if (meta?.type === "number") {
608
+ return /* @__PURE__ */ jsx11(
402
609
  "input",
403
610
  {
404
611
  type: "number",
@@ -407,87 +614,356 @@ function StateEditor({
407
614
  max: meta?.max,
408
615
  step: meta?.step,
409
616
  onChange: (e) => onChange(e.target.value === "" ? 0 : Number(e.target.value)),
410
- style: {
411
- width: "100%",
412
- padding: "8px 10px",
413
- borderRadius: 10,
414
- border: "1px solid #333",
415
- background: "#111",
416
- color: "#fff"
417
- }
617
+ style: inputStyle
418
618
  }
419
619
  );
420
620
  }
421
621
  if (meta?.type === "json" || value && typeof value === "object") {
422
- const text = safeStringify(value);
423
- return /* @__PURE__ */ jsx2(JsonEditor, { initial: text, onValidJson: (obj) => onChange(obj) });
622
+ return /* @__PURE__ */ jsx11(JsonEditor, { initial: safeStringify(value), onValidJson: onChange });
424
623
  }
425
- return /* @__PURE__ */ jsx2(
624
+ return /* @__PURE__ */ jsx11(
426
625
  "input",
427
626
  {
428
627
  type: "text",
429
628
  value: typeof value === "string" ? value : String(value ?? ""),
430
- placeholder: meta?.placeholder,
629
+ placeholder: meta?.type === "text" ? meta.placeholder : void 0,
431
630
  onChange: (e) => onChange(e.target.value),
432
- style: {
433
- width: "100%",
434
- padding: "8px 10px",
435
- borderRadius: 10,
436
- border: "1px solid #333",
437
- background: "#111",
438
- color: "#fff"
439
- }
631
+ style: inputStyle
440
632
  }
441
633
  );
442
634
  }
443
- function safeStringify(v) {
444
- try {
445
- return JSON.stringify(v, null, 2);
446
- } catch {
447
- return String(v);
448
- }
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
+ );
449
664
  }
450
- function JsonEditor({
451
- initial,
452
- onValidJson
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
453
678
  }) {
454
- const [raw, setRaw] = useState2(initial);
455
- const [error, setError] = useState2(null);
456
- useEffect3(() => {
457
- setRaw(initial);
458
- }, [initial]);
459
- return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 6 }, children: [
460
- /* @__PURE__ */ jsx2(
461
- "textarea",
679
+ return /* @__PURE__ */ jsxs6("div", { style: { padding: 12, overflow: "auto", display: "flex", flexDirection: "column", boxSizing: "border-box" }, children: [
680
+ /* @__PURE__ */ jsx13(
681
+ StatePanelHeader,
462
682
  {
463
- value: raw,
464
- onChange: (e) => {
465
- const next = e.target.value;
466
- setRaw(next);
467
- try {
468
- const parsed = JSON.parse(next);
469
- setError(null);
470
- onValidJson(parsed);
471
- } catch (err) {
472
- setError("Invalid JSON");
473
- }
474
- },
475
- rows: 6,
476
- style: {
477
- width: "100%",
478
- padding: "8px 10px",
479
- borderRadius: 10,
480
- border: "1px solid #333",
481
- background: "#111",
482
- color: "#fff",
483
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
484
- fontSize: 12
485
- }
683
+ selectedLabel: selected?.label ?? null,
684
+ leftCollapsed,
685
+ onToggleCollapse,
686
+ onClose,
687
+ onDragStart,
688
+ onDragEnd
486
689
  }
487
690
  ),
488
- error && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, color: "#ff6b6b" }, children: error })
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
+ ] })
489
704
  ] });
490
705
  }
706
+
707
+ // src/StateInspectorUI.tsx
708
+ import { Fragment, jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
709
+ function StateInspectorUI() {
710
+ const store = useInspectorStore();
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);
719
+ const searchRef = useRef2(null);
720
+ const [pos, setPos] = useState3(() => {
721
+ try {
722
+ const raw = localStorage.getItem(LS_KEY);
723
+ return raw ? JSON.parse(raw) : { right: 16, bottom: 72 };
724
+ } catch {
725
+ return { right: 16, bottom: 72 };
726
+ }
727
+ });
728
+ const [leftWidth, setLeftWidth] = useState3(() => {
729
+ try {
730
+ const raw = localStorage.getItem(LS_LEFT_W);
731
+ return raw ? Number(raw) : 280;
732
+ } catch {
733
+ return 280;
734
+ }
735
+ });
736
+ const [size, setSize] = useState3(() => {
737
+ try {
738
+ const raw = localStorage.getItem(LS_SIZE);
739
+ return raw ? JSON.parse(raw) : { width: 720, height: 420 };
740
+ } catch {
741
+ return { width: 720, height: 420 };
742
+ }
743
+ });
744
+ useEffect4(() => {
745
+ try {
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);
770
+ }
771
+ }, [size]);
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]);
777
+ const snapshot = useMemo3(() => store.getSnapshot(), [store]);
778
+ const components = useMemo3(
779
+ () => snapshot.components.filter((c) => c.mounted),
780
+ [snapshot.components]
781
+ );
782
+ const q = query.trim().toLowerCase();
783
+ const filtered = useMemo3(
784
+ () => q ? components.filter((c) => {
785
+ const labelHit = c.label.toLowerCase().includes(q);
786
+ const keyHit = c.stateKeys.some((k) => k.toLowerCase().includes(q));
787
+ return labelHit || keyHit;
788
+ }) : components,
789
+ [q, components]
790
+ );
791
+ useEffect4(() => {
792
+ if (!open) return;
793
+ if (components.length === 0) {
794
+ setSelectedId(null);
795
+ return;
796
+ }
797
+ if (!selectedId || !components.some((c) => c.id === selectedId)) {
798
+ setSelectedId(components[0].id);
799
+ }
800
+ }, [open, components.length]);
801
+ useEffect4(() => {
802
+ function onKey(e) {
803
+ const mod = isMac ? e.metaKey : e.ctrlKey;
804
+ if (mod && e.shiftKey && e.key.toLowerCase() === "i") {
805
+ e.preventDefault();
806
+ setOpen((o) => !o);
807
+ return;
808
+ }
809
+ if (!open) return;
810
+ if (e.key === "Escape") {
811
+ setOpen(false);
812
+ return;
813
+ }
814
+ if (e.key === "/") {
815
+ e.preventDefault();
816
+ searchRef.current?.focus();
817
+ return;
818
+ }
819
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
820
+ e.preventDefault();
821
+ const list = filtered;
822
+ if (!list.length) return;
823
+ const idx = list.findIndex((c) => c.id === selectedId);
824
+ const next = e.key === "ArrowDown" ? list[(Math.max(-1, idx) + 1) % list.length].id : list[idx <= 0 ? list.length - 1 : idx - 1].id;
825
+ setSelectedId(next);
826
+ setTimeout(() => {
827
+ document.getElementById(`rsi-comp-${next}`)?.scrollIntoView({ block: "nearest" });
828
+ }, 0);
829
+ }
830
+ }
831
+ window.addEventListener("keydown", onKey);
832
+ return () => window.removeEventListener("keydown", onKey);
833
+ }, [open, filtered, selectedId]);
834
+ const selected = useMemo3(() => {
835
+ if (!selectedId) return null;
836
+ return store.components.get(selectedId) ?? null;
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
+ };
896
+ if (!store.enabled) return null;
897
+ return createPortal(
898
+ /* @__PURE__ */ jsxs7(Fragment, { children: [
899
+ /* @__PURE__ */ jsx14(FloatingButton, { onClick: () => setOpen((o) => !o) }),
900
+ /* @__PURE__ */ jsx14(DockPreviewOverlay, { preview }),
901
+ open && /* @__PURE__ */ jsxs7(
902
+ "div",
903
+ {
904
+ style: {
905
+ position: "fixed",
906
+ left: pos.left,
907
+ right: pos.right,
908
+ top: pos.top,
909
+ bottom: pos.bottom,
910
+ width: size.width || "calc(100dvw - 24px)",
911
+ maxWidth: "calc(100vw - 24px)",
912
+ height: size.height || "calc(100dvh - 24px)",
913
+ maxHeight: "calc(100vh - 24px)",
914
+ background: "#1c1c1c",
915
+ color: "#fff",
916
+ borderRadius: 12,
917
+ border: "1px solid #333",
918
+ zIndex: 999999,
919
+ display: "grid",
920
+ gridTemplateColumns: leftCollapsed ? "auto" : `${leftWidth}px 14px auto`,
921
+ overflow: "hidden",
922
+ transition: isTransitioning ? "all 0.3s ease" : "none"
923
+ },
924
+ children: [
925
+ /* @__PURE__ */ jsx14(ResizeHandle, { onResize: handlePanelResize(size) }),
926
+ /* @__PURE__ */ jsx14(
927
+ ComponentList,
928
+ {
929
+ components: filtered,
930
+ selectedId,
931
+ onSelect: setSelectedId,
932
+ onDragStart: handleDragStart,
933
+ onDragEnd: handleDragEnd,
934
+ height: size.height,
935
+ hidden: leftCollapsed
936
+ }
937
+ ),
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
+ )
960
+ ]
961
+ }
962
+ )
963
+ ] }),
964
+ document.body
965
+ );
966
+ }
491
967
  export {
492
968
  StateInspectorProvider,
493
969
  StateInspectorUI,