tab-bridge 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -235,8 +235,340 @@ function useTabSyncActions() {
235
235
  [instance]
236
236
  );
237
237
  }
238
+ var FONT = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
239
+ function containerStyle(position) {
240
+ const base = {
241
+ position: "fixed",
242
+ zIndex: 99999,
243
+ fontFamily: FONT,
244
+ fontSize: 12,
245
+ color: "#e4e4e7",
246
+ lineHeight: 1.5
247
+ };
248
+ if (position.includes("bottom")) base.bottom = 8;
249
+ else base.top = 8;
250
+ if (position.includes("right")) base.right = 8;
251
+ else base.left = 8;
252
+ return base;
253
+ }
254
+ var PANEL = {
255
+ width: 380,
256
+ maxHeight: 420,
257
+ background: "#18181b",
258
+ border: "1px solid #3f3f46",
259
+ borderRadius: 8,
260
+ overflow: "hidden",
261
+ display: "flex",
262
+ flexDirection: "column",
263
+ boxShadow: "0 8px 32px rgba(0,0,0,.45)"
264
+ };
265
+ var TOGGLE_BTN = {
266
+ background: "#18181b",
267
+ color: "#a1a1aa",
268
+ border: "1px solid #3f3f46",
269
+ borderRadius: 6,
270
+ padding: "4px 12px",
271
+ cursor: "pointer",
272
+ fontFamily: FONT,
273
+ fontSize: 11,
274
+ marginBottom: 4,
275
+ display: "flex",
276
+ alignItems: "center",
277
+ gap: 6
278
+ };
279
+ var TAB_BAR = {
280
+ display: "flex",
281
+ borderBottom: "1px solid #3f3f46",
282
+ background: "#27272a"
283
+ };
284
+ function tabBtnStyle(active) {
285
+ return {
286
+ flex: 1,
287
+ padding: "6px 0",
288
+ background: active ? "#18181b" : "transparent",
289
+ color: active ? "#fafafa" : "#a1a1aa",
290
+ border: "none",
291
+ borderBottom: active ? "2px solid #6366f1" : "2px solid transparent",
292
+ cursor: "pointer",
293
+ fontFamily: FONT,
294
+ fontSize: 11,
295
+ fontWeight: active ? 600 : 400
296
+ };
297
+ }
298
+ var BODY = {
299
+ flex: 1,
300
+ overflow: "auto",
301
+ padding: 10
302
+ };
303
+ var BADGE = {
304
+ display: "inline-block",
305
+ padding: "1px 5px",
306
+ borderRadius: 4,
307
+ fontSize: 10,
308
+ fontWeight: 600,
309
+ marginLeft: 6
310
+ };
311
+ function TabSyncDevTools({
312
+ position = "bottom-right",
313
+ defaultOpen = false
314
+ }) {
315
+ const instance = react.useContext(TabSyncContext);
316
+ const [is_open, setIsOpen] = react.useState(defaultOpen);
317
+ const [active_tab, setActiveTab] = react.useState("state");
318
+ if (!instance) return null;
319
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: containerStyle(position), children: [
320
+ /* @__PURE__ */ jsxRuntime.jsxs(
321
+ "button",
322
+ {
323
+ type: "button",
324
+ style: TOGGLE_BTN,
325
+ onClick: () => setIsOpen((v) => !v),
326
+ children: [
327
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#6366f1", fontWeight: 700 }, children: "tab-bridge" }),
328
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: is_open ? "\u25BC" : "\u25B2" })
329
+ ]
330
+ }
331
+ ),
332
+ is_open && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: PANEL, children: [
333
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: TAB_BAR, children: ["state", "tabs", "log"].map((t) => /* @__PURE__ */ jsxRuntime.jsx(
334
+ "button",
335
+ {
336
+ type: "button",
337
+ style: tabBtnStyle(active_tab === t),
338
+ onClick: () => setActiveTab(t),
339
+ children: t === "state" ? "State" : t === "tabs" ? "Tabs" : "Log"
340
+ },
341
+ t
342
+ )) }),
343
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: BODY, children: [
344
+ active_tab === "state" && /* @__PURE__ */ jsxRuntime.jsx(StatePanel, { instance }),
345
+ active_tab === "tabs" && /* @__PURE__ */ jsxRuntime.jsx(TabsPanel, { instance }),
346
+ active_tab === "log" && /* @__PURE__ */ jsxRuntime.jsx(LogPanel, { instance })
347
+ ] })
348
+ ] })
349
+ ] });
350
+ }
351
+ function StatePanel({
352
+ instance
353
+ }) {
354
+ const [state, setState] = react.useState(() => instance.getAll());
355
+ const [editing, setEditing] = react.useState(false);
356
+ const [draft, setDraft] = react.useState("");
357
+ const [error, setError] = react.useState("");
358
+ react.useEffect(() => {
359
+ return instance.onChange((s) => setState(s));
360
+ }, [instance]);
361
+ const startEdit = react.useCallback(() => {
362
+ setDraft(JSON.stringify(state, null, 2));
363
+ setError("");
364
+ setEditing(true);
365
+ }, [state]);
366
+ const applyEdit = react.useCallback(() => {
367
+ try {
368
+ const parsed = JSON.parse(draft);
369
+ instance.patch(parsed);
370
+ setEditing(false);
371
+ setError("");
372
+ } catch (e) {
373
+ setError(String(e.message));
374
+ }
375
+ }, [draft, instance]);
376
+ if (editing) {
377
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
378
+ /* @__PURE__ */ jsxRuntime.jsx(
379
+ "textarea",
380
+ {
381
+ value: draft,
382
+ onChange: (e) => setDraft(e.target.value),
383
+ style: {
384
+ width: "100%",
385
+ minHeight: 180,
386
+ background: "#09090b",
387
+ color: "#e4e4e7",
388
+ border: "1px solid #3f3f46",
389
+ borderRadius: 4,
390
+ fontFamily: FONT,
391
+ fontSize: 11,
392
+ padding: 6,
393
+ resize: "vertical",
394
+ boxSizing: "border-box"
395
+ }
396
+ }
397
+ ),
398
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#ef4444", fontSize: 11, marginTop: 4 }, children: error }),
399
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginTop: 6 }, children: [
400
+ /* @__PURE__ */ jsxRuntime.jsx(
401
+ "button",
402
+ {
403
+ type: "button",
404
+ onClick: applyEdit,
405
+ style: {
406
+ ...TOGGLE_BTN,
407
+ background: "#6366f1",
408
+ color: "#fff",
409
+ border: "none",
410
+ margin: 0
411
+ },
412
+ children: "Apply"
413
+ }
414
+ ),
415
+ /* @__PURE__ */ jsxRuntime.jsx(
416
+ "button",
417
+ {
418
+ type: "button",
419
+ onClick: () => setEditing(false),
420
+ style: { ...TOGGLE_BTN, margin: 0 },
421
+ children: "Cancel"
422
+ }
423
+ )
424
+ ] })
425
+ ] });
426
+ }
427
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
428
+ /* @__PURE__ */ jsxRuntime.jsx(
429
+ "pre",
430
+ {
431
+ style: {
432
+ margin: 0,
433
+ whiteSpace: "pre-wrap",
434
+ wordBreak: "break-all",
435
+ fontSize: 11,
436
+ color: "#a5f3fc"
437
+ },
438
+ children: JSON.stringify(state, null, 2)
439
+ }
440
+ ),
441
+ /* @__PURE__ */ jsxRuntime.jsx(
442
+ "button",
443
+ {
444
+ type: "button",
445
+ onClick: startEdit,
446
+ style: { ...TOGGLE_BTN, marginTop: 8, margin: 0 },
447
+ children: "Edit State"
448
+ }
449
+ )
450
+ ] });
451
+ }
452
+ function TabsPanel({
453
+ instance
454
+ }) {
455
+ const [tabs, setTabs] = react.useState(() => instance.getTabs());
456
+ react.useEffect(() => {
457
+ return instance.onTabChange((t) => setTabs(t));
458
+ }, [instance]);
459
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
460
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#a1a1aa", marginBottom: 6, fontSize: 11 }, children: [
461
+ tabs.length,
462
+ " active tab",
463
+ tabs.length !== 1 ? "s" : ""
464
+ ] }),
465
+ tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsxs(
466
+ "div",
467
+ {
468
+ style: {
469
+ padding: "4px 6px",
470
+ marginBottom: 4,
471
+ background: tab.id === instance.id ? "#1e1b4b" : "#27272a",
472
+ borderRadius: 4,
473
+ display: "flex",
474
+ alignItems: "center",
475
+ flexWrap: "wrap",
476
+ gap: 4
477
+ },
478
+ children: [
479
+ /* @__PURE__ */ jsxRuntime.jsx(
480
+ "span",
481
+ {
482
+ style: {
483
+ fontFamily: FONT,
484
+ fontSize: 11,
485
+ color: "#e4e4e7",
486
+ wordBreak: "break-all"
487
+ },
488
+ children: tab.id.slice(0, 8)
489
+ }
490
+ ),
491
+ tab.id === instance.id && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...BADGE, background: "#6366f1", color: "#fff" }, children: "you" }),
492
+ tab.isLeader && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...BADGE, background: "#f59e0b", color: "#000" }, children: "leader" })
493
+ ]
494
+ },
495
+ tab.id
496
+ ))
497
+ ] });
498
+ }
499
+ var MAX_LOG = 200;
500
+ var next_log_id = 0;
501
+ function LogPanel({
502
+ instance
503
+ }) {
504
+ const [entries, setEntries] = react.useState([]);
505
+ const bottom_ref = react.useRef(null);
506
+ const push = react.useCallback((kind, detail) => {
507
+ setEntries((prev) => {
508
+ const entry = {
509
+ id: ++next_log_id,
510
+ time: Date.now(),
511
+ kind,
512
+ detail
513
+ };
514
+ const next = [...prev, entry];
515
+ return next.length > MAX_LOG ? next.slice(-MAX_LOG) : next;
516
+ });
517
+ }, []);
518
+ react.useEffect(() => {
519
+ const unsub_state = instance.onChange(
520
+ (_state, keys, meta) => {
521
+ const src = meta.isLocal ? "local" : `remote(${meta.sourceTabId.slice(0, 8)})`;
522
+ push("state", `${String(keys.join(", "))} [${src}]`);
523
+ }
524
+ );
525
+ const unsub_tabs = instance.onTabChange((tabs) => {
526
+ push("tab", `${tabs.length} tabs`);
527
+ });
528
+ return () => {
529
+ unsub_state();
530
+ unsub_tabs();
531
+ };
532
+ }, [instance, push]);
533
+ react.useEffect(() => {
534
+ bottom_ref.current?.scrollIntoView({ behavior: "smooth" });
535
+ }, [entries.length]);
536
+ const kind_color = {
537
+ state: "#a5f3fc",
538
+ tab: "#86efac",
539
+ leader: "#fde68a"
540
+ };
541
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { maxHeight: 300, overflow: "auto" }, children: [
542
+ entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#71717a", fontSize: 11 }, children: "Waiting for events..." }),
543
+ entries.map((e) => /* @__PURE__ */ jsxRuntime.jsxs(
544
+ "div",
545
+ {
546
+ style: { fontSize: 11, marginBottom: 2, display: "flex", gap: 6 },
547
+ children: [
548
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#71717a", flexShrink: 0 }, children: new Date(e.time).toLocaleTimeString() }),
549
+ /* @__PURE__ */ jsxRuntime.jsx(
550
+ "span",
551
+ {
552
+ style: {
553
+ color: kind_color[e.kind],
554
+ fontWeight: 600,
555
+ flexShrink: 0,
556
+ width: 38
557
+ },
558
+ children: e.kind
559
+ }
560
+ ),
561
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#d4d4d8", wordBreak: "break-all" }, children: e.detail })
562
+ ]
563
+ },
564
+ e.id
565
+ )),
566
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottom_ref })
567
+ ] });
568
+ }
238
569
 
239
570
  exports.TabSyncContext = TabSyncContext;
571
+ exports.TabSyncDevTools = TabSyncDevTools;
240
572
  exports.TabSyncProvider = TabSyncProvider;
241
573
  exports.useIsLeader = useIsLeader;
242
574
  exports.useLeaderInfo = useLeaderInfo;
@@ -1,7 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { T as TabSyncOptions, a as TabSyncInstance, R as RPCMap, b as TabInfo } from '../instance-5LIItazN.cjs';
4
+ import { T as TabSyncOptions } from '../options-DmHyGTL0.cjs';
5
+ import { T as TabSyncInstance, R as RPCMap, a as TabInfo } from '../instance-hvEUHx6i.cjs';
5
6
 
6
7
  interface TabSyncProviderProps<TState extends Record<string, unknown>> {
7
8
  options: TabSyncOptions<TState>;
@@ -103,4 +104,26 @@ interface TabSyncActions<TState extends Record<string, unknown>> {
103
104
  */
104
105
  declare function useTabSyncActions<TState extends Record<string, unknown> = Record<string, unknown>>(): TabSyncActions<TState>;
105
106
 
106
- export { type TabSyncActions, TabSyncContext, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };
107
+ interface TabSyncDevToolsProps {
108
+ /** Panel position on screen. @default 'bottom-right' */
109
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
110
+ /** Start expanded. @default false */
111
+ defaultOpen?: boolean;
112
+ }
113
+ /**
114
+ * Floating dev-tools panel that visualises tab-bridge state, active tabs,
115
+ * leader info, and an event log. Supports manual state editing.
116
+ *
117
+ * **Tree-shakeable** — if you never import `TabSyncDevTools`, it won't
118
+ * appear in your production bundle.
119
+ *
120
+ * Must be used inside a `<TabSyncProvider>`.
121
+ *
122
+ * @example
123
+ * ```tsx
124
+ * {process.env.NODE_ENV === 'development' && <TabSyncDevTools />}
125
+ * ```
126
+ */
127
+ declare function TabSyncDevTools({ position, defaultOpen, }: TabSyncDevToolsProps): react_jsx_runtime.JSX.Element | null;
128
+
129
+ export { type TabSyncActions, TabSyncContext, TabSyncDevTools, type TabSyncDevToolsProps, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };
@@ -1,7 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { T as TabSyncOptions, a as TabSyncInstance, R as RPCMap, b as TabInfo } from '../instance-5LIItazN.js';
4
+ import { T as TabSyncOptions } from '../options-iN7Rnvwj.js';
5
+ import { T as TabSyncInstance, R as RPCMap, a as TabInfo } from '../instance-hvEUHx6i.js';
5
6
 
6
7
  interface TabSyncProviderProps<TState extends Record<string, unknown>> {
7
8
  options: TabSyncOptions<TState>;
@@ -103,4 +104,26 @@ interface TabSyncActions<TState extends Record<string, unknown>> {
103
104
  */
104
105
  declare function useTabSyncActions<TState extends Record<string, unknown> = Record<string, unknown>>(): TabSyncActions<TState>;
105
106
 
106
- export { type TabSyncActions, TabSyncContext, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };
107
+ interface TabSyncDevToolsProps {
108
+ /** Panel position on screen. @default 'bottom-right' */
109
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
110
+ /** Start expanded. @default false */
111
+ defaultOpen?: boolean;
112
+ }
113
+ /**
114
+ * Floating dev-tools panel that visualises tab-bridge state, active tabs,
115
+ * leader info, and an event log. Supports manual state editing.
116
+ *
117
+ * **Tree-shakeable** — if you never import `TabSyncDevTools`, it won't
118
+ * appear in your production bundle.
119
+ *
120
+ * Must be used inside a `<TabSyncProvider>`.
121
+ *
122
+ * @example
123
+ * ```tsx
124
+ * {process.env.NODE_ENV === 'development' && <TabSyncDevTools />}
125
+ * ```
126
+ */
127
+ declare function TabSyncDevTools({ position, defaultOpen, }: TabSyncDevToolsProps): react_jsx_runtime.JSX.Element | null;
128
+
129
+ export { type TabSyncActions, TabSyncContext, TabSyncDevTools, type TabSyncDevToolsProps, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };