@chuvenger/devdock 0.5.0 → 0.7.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.
Files changed (40) hide show
  1. package/README.md +200 -12
  2. package/dist/{chunk-3MVFATRN.js → chunk-2IQRWZ7T.js} +3 -3
  3. package/dist/{chunk-3MVFATRN.js.map → chunk-2IQRWZ7T.js.map} +1 -1
  4. package/dist/{chunk-NXZNFF34.js → chunk-2YX4DI2D.js} +253 -145
  5. package/dist/chunk-2YX4DI2D.js.map +1 -0
  6. package/dist/{dock-d3HqT25k.d.cts → dock-C7wZ8pQb.d.cts} +1 -1
  7. package/dist/{dock-P6l6pKjS.d.ts → dock-DJZuGmih.d.ts} +1 -1
  8. package/dist/index.cjs +253 -143
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +3 -2
  11. package/dist/index.d.ts +3 -2
  12. package/dist/index.js +1 -1
  13. package/dist/{match-DwGIF1Xt.d.ts → match-C8MsaP6h.d.ts} +1 -1
  14. package/dist/{match-DDSJIRYW.d.cts → match-Dw1CSS0G.d.cts} +1 -1
  15. package/dist/react-router.cjs +348 -148
  16. package/dist/react-router.cjs.map +1 -1
  17. package/dist/react-router.d.cts +59 -11
  18. package/dist/react-router.d.ts +59 -11
  19. package/dist/react-router.js +101 -8
  20. package/dist/react-router.js.map +1 -1
  21. package/dist/react.cjs +250 -143
  22. package/dist/react.cjs.map +1 -1
  23. package/dist/react.d.cts +3 -2
  24. package/dist/react.d.ts +3 -2
  25. package/dist/react.js +2 -2
  26. package/dist/svelte.cjs +253 -143
  27. package/dist/svelte.cjs.map +1 -1
  28. package/dist/svelte.d.cts +4 -3
  29. package/dist/svelte.d.ts +4 -3
  30. package/dist/svelte.js +2 -2
  31. package/dist/svelte.js.map +1 -1
  32. package/dist/sveltekit.cjs +253 -143
  33. package/dist/sveltekit.cjs.map +1 -1
  34. package/dist/sveltekit.d.cts +5 -4
  35. package/dist/sveltekit.d.ts +5 -4
  36. package/dist/sveltekit.js +2 -2
  37. package/dist/{types-B_SpF32A.d.cts → types-B8WgSe7o.d.cts} +103 -3
  38. package/dist/{types-B_SpF32A.d.ts → types-B8WgSe7o.d.ts} +103 -3
  39. package/package.json +4 -1
  40. package/dist/chunk-NXZNFF34.js.map +0 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@chuvenger/devdock.svg)](https://www.npmjs.com/package/@chuvenger/devdock)
4
4
  [![license](https://img.shields.io/npm/l/@chuvenger/devdock.svg)](./LICENSE)
5
5
 
6
- A tiny, dependency-free dev-only floating button. It sits in a corner of your
6
+ A tiny dev-only floating button. It sits in a corner of your
7
7
  app, **only renders in development**, and opens a popup to jump between dev
8
8
  routes and run custom commands.
9
9
 
@@ -12,7 +12,7 @@ routes and run custom commands.
12
12
  - ⚡ **Custom commands** — register any action (reset DB, toggle a flag, copy a token…), with live labels and check marks for toggles.
13
13
  - 📊 **Custom views** — mount your own UI in the popup for anything a list can't say: a frame-rate meter, a sync status, a segmented control.
14
14
  - 🔎 Filter box + keyboard nav (↑/↓/Enter/Esc) and an optional global hotkey.
15
- - 🎨 Zero CSS to import — inline-styled, high z-index, no runtime dependencies.
15
+ - 🎨 Zero CSS to import — it brings its own sheet and a high z-index. Theme it with `--devdock-*` custom properties (they cross the shadow boundary, so isolation stays), or merge your own classes into any part with `classNames`.
16
16
  - 🛡️ **Shadow DOM isolated** by default — the host page's CSS can't touch it (and vice versa), or `shadow: "inherit"` to let your design system in.
17
17
  - 🧩 **Framework-agnostic core** with thin **React** and **Svelte** adapters.
18
18
 
@@ -133,6 +133,43 @@ arguments — to reach one, use a command that navigates to a known id:
133
133
  <ReactRouterDevDock staticRoutes match={(r) => r.path.length < 20} />
134
134
  ```
135
135
 
136
+ ### React Router framework mode (`app/routes.ts`)
137
+
138
+ Framework mode works, with one wrinkle: its routes are **lazy**. `handle` is an
139
+ export of the route *module*, so until that module loads it isn't on the route
140
+ object the adapter reads — a `handle.devLabel` on a page you haven't opened has
141
+ nothing to take effect on. Such routes still list by path convention (`/dev/*`),
142
+ labelled with their path.
143
+
144
+ `loadHandles` closes the gap by importing the lazy route modules to read their
145
+ `handle`:
146
+
147
+ ```tsx
148
+ <ReactRouterDevDock loadHandles />
149
+ ```
150
+
151
+ ```tsx
152
+ // app/routes/dev.style-guide.tsx
153
+ export const handle = { devLabel: "Style Guide", devGroup: "Internal" };
154
+ ```
155
+
156
+ It's opt-in because reading a handle up front means importing the module up
157
+ front — every lazy route's module, and any side effect its import carries. That
158
+ only ever happens in development (the dock doesn't render in production), but it
159
+ is real work on a dev page load. Leave it off if path labels are fine; turn it
160
+ on if you want `devLabel` / `devGroup`, or `handle.dev` to mark a route that
161
+ isn't under `/dev` (without it, such a route is never seen at all).
162
+
163
+ One thing it can't do: list a route the router hasn't discovered yet. Framework
164
+ mode fetches route branches as links render, so the dock watches the tree and
165
+ picks them up as they arrive — but a dev route nothing links to stays unknown
166
+ until you navigate there. Ship the whole tree up front with
167
+ `routeDiscovery: { mode: "initial" }` in `react-router.config.ts`.
168
+
169
+ `loadHandles` is not framework-only: a plain data router with
170
+ `lazy: () => import("./route")` hides `handle` exactly the same way, and the
171
+ flag reads it the same way.
172
+
136
173
  ## Svelte
137
174
 
138
175
  `@chuvenger/devdock/svelte` exports a Svelte action — no extra build step, works
@@ -403,11 +440,11 @@ tree-shakes it: `{import.meta.env.DEV && <DevDock … />}`.
403
440
 
404
441
  ## Styling & isolation
405
442
 
406
- The dock is styled entirely with inline styles (no stylesheet to import) and,
407
- by default, renders inside an **open shadow root** attached to a host element in
408
- `document.body`. That means the host page's CSS can't leak in and the dock's
409
- styles can't leak out. The host element carries a `data-devdock` attribute in
410
- every mode.
443
+ The dock ships its own stylesheet — one it injects itself, nothing to
444
+ import, themeable with [custom properties](#theming-it-custom-properties) — and, by default, renders inside an **open shadow root** attached to a
445
+ host element in `document.body`. The host page's CSS can't leak in and the
446
+ dock's styles can't leak out. The host element carries a `data-devdock`
447
+ attribute in every mode.
411
448
 
412
449
  | `shadow` | Where it renders | The page's CSS |
413
450
  | -------- | ---------------- | -------------- |
@@ -416,8 +453,152 @@ every mode.
416
453
  | `false` | light DOM | applies directly |
417
454
 
418
455
  Use `"inherit"` (or `false`) when a [custom view](#views-and-your-css) renders
419
- your app's own components and needs its stylesheet. Otherwise leave it alone —
420
- isolation is the reason the dock survives arbitrary host CSS.
456
+ your app's own components and needs its stylesheet, or when you're restyling
457
+ the dock with `classNames` below — passing `classNames` switches the default to
458
+ `"inherit"` for you, since a class of yours is defined by a stylesheet that has
459
+ to cross the boundary. Otherwise leave it alone: isolation is the reason the
460
+ dock survives arbitrary host CSS.
461
+
462
+ There are two ways to change how it looks, and they compose:
463
+
464
+ | | Reach | Costs you |
465
+ | --- | --- | --- |
466
+ | [Custom properties](#theming-it-custom-properties) | colours, radii, font, size | nothing — they inherit **through** the shadow boundary, so the dock stays isolated |
467
+ | [`classNames`](#restyling-it-classnames) | anything CSS can say | your stylesheet has to reach the dock, so `shadow` becomes `"inherit"` |
468
+
469
+ Reach for the properties first; drop to `classNames` when you need something
470
+ they don't cover.
471
+
472
+ ### Theming it: custom properties
473
+
474
+ Set any of these on `:root`, or on the `[data-devdock]` host to theme one dock,
475
+ and you're done — no `shadow` change, no classes, nothing to import:
476
+
477
+ ```css
478
+ :root {
479
+ --devdock-bg: #ffffff;
480
+ --devdock-fg: #0f172a;
481
+ --devdock-accent: #a855f7;
482
+ --devdock-border: #e2e8f0;
483
+ --devdock-radius: 16px;
484
+ --devdock-width: 320px;
485
+ }
486
+ ```
487
+
488
+ | Property | Default | What it paints |
489
+ | -------- | ------- | -------------- |
490
+ | `--devdock-font` | system UI stack | The dock's font family. |
491
+ | `--devdock-bg` | `#16181d` | Popup background. |
492
+ | `--devdock-fg` | `#e7e9ee` | Primary text — rows, the input. |
493
+ | `--devdock-muted` | `#8b909c` | The title, a view's label. |
494
+ | `--devdock-subtle` | `#6b7280` | Group headings, route paths, the empty state. |
495
+ | `--devdock-border` | `#2a2e37` | Every border. |
496
+ | `--devdock-accent` | `#2563eb` | Highlighted row, and the button while open. |
497
+ | `--devdock-accent-fg` | `#e7e9ee` | Text on the highlighted row. |
498
+ | `--devdock-button-bg` | `#111317` | The button, closed. |
499
+ | `--devdock-button-fg` | `#fff` | The button's text. |
500
+ | `--devdock-input-bg` | `#0f1115` | The filter input. |
501
+ | `--devdock-success` | `#22c55e` | The status dot and toggle check marks. |
502
+ | `--devdock-radius` | `12px` | Popup corners. |
503
+ | `--devdock-radius-sm` | `8px` | Rows and the input. |
504
+ | `--devdock-width` | `280px` | Popup width. |
505
+ | `--devdock-max-height` | `70vh` | Popup height before it scrolls. |
506
+ | `--devdock-shadow` | `0 12px 40px rgba(0,0,0,.45)` | The popup's shadow. |
507
+
508
+ They're read as `var(--devdock-x, default)` and never declared inside the
509
+ dock, which is what lets a value you set outside win — a declaration of ours
510
+ in the shadow tree would have beaten the one inheriting in.
511
+
512
+ ### Restyling it: `classNames`
513
+
514
+ Every part of the dock takes a class of yours, **merged onto** the one it
515
+ already carries rather than replacing it:
516
+
517
+ ```tsx
518
+ <DevDock
519
+ classNames={{
520
+ panel: "w-96 max-h-[80vh] bg-white text-slate-900 border-slate-200",
521
+ button: "bg-emerald-600 rounded-md",
522
+ input: "bg-slate-50 border-slate-300",
523
+ row: "rounded-none",
524
+ rowActive: "bg-emerald-100 text-emerald-900",
525
+ }}
526
+ />
527
+ ```
528
+
529
+ Each entry takes **anything [`cn`](https://www.npmjs.com/package/cn) takes** —
530
+ a string, an array, a conditional, an object — so a wrapper of your own can
531
+ forward its props straight through without a helper import:
532
+
533
+ ```tsx
534
+ export function AppDock({ className }: { className?: string }) {
535
+ return (
536
+ <DevDock
537
+ classNames={{
538
+ panel: ["bg-red-100", className],
539
+ row: ["px-3", compact && "py-1", { "opacity-50": muted }],
540
+ }}
541
+ />
542
+ );
543
+ }
544
+ ```
545
+
546
+ Values are merged with [`cn`](https://www.npmjs.com/package/cn), so conflicts
547
+ resolve the way you'd write them by hand: `rowActive: "text-emerald-900"` over
548
+ `row: "text-slate-700"` keeps emerald and **drops** slate, instead of putting
549
+ both in the attribute and letting Tailwind's own sheet order pick the winner.
550
+ The dock's `devdock-*` classes aren't utilities, so they pass through untouched.
551
+
552
+ No `!important`, no `[&_div]` reaching: a plain utility class wins on its own.
553
+ Two things in the dock's sheet make that true, and they're worth knowing about
554
+ because they also say when it *doesn't*:
555
+
556
+ - **It lives in a cascade layer** (`devdock`), declared *after* Tailwind's
557
+ `base` and *before* its `utilities`. Layered rules lose to unlayered ones
558
+ whatever their specificity, so this is what lets a Tailwind v4 utility
559
+ through while keeping a reset in `base` from stripping the dock's chrome.
560
+ - **Each rule is worth exactly one class** — one bare `.devdock-*`, with any
561
+ state selector tucked inside `:where()`. That's what settles ties when the
562
+ layer is off: the sheet goes in first, so a page-level reset
563
+ (`button { … }`, `*`) loses to it and your class beats it.
564
+
565
+ If the page's own CSS is unlayered — Tailwind v3, a hand-written reset —
566
+ turn the layer off with `cssLayer={false}`, or the page's reset will outrank
567
+ the dock itself. (A page with no `@layer` anywhere is unaffected either way.)
568
+ If you run your own layers, name the dock's with `cssLayer="chrome"`: it then
569
+ ships no order statement at all, and you place it in your own `@layer` line,
570
+ where you know the names around it.
571
+
572
+ Not themeable this way: the corner placement, the z-index, and the popup's
573
+ anchoring. Those are computed from `position`/`zIndex` and stay inline.
574
+
575
+ ### The parts
576
+
577
+ Each one carries its `devdock-*` class in every mode, so you can also just
578
+ write CSS against them and skip `classNames` entirely.
579
+
580
+ | `classNames` key | Class | What it is |
581
+ | ---------------- | ----- | ---------- |
582
+ | `root` | `.devdock-root` | The fixed wrapper holding the button and the popup. |
583
+ | `button` | `.devdock-button` | The floating button. `[aria-expanded="true"]` while open. |
584
+ | `dot` | `.devdock-dot` | The status dot inside it. |
585
+ | `buttonLabel` | `.devdock-button-label`| The button's text. |
586
+ | `panel` | `.devdock-panel` | The popup. |
587
+ | `title` | `.devdock-title` | Its heading. |
588
+ | `inputWrap` | `.devdock-input-wrap` | The padding around the filter input. |
589
+ | `input` | `.devdock-input` | The filter input. |
590
+ | `body` | `.devdock-body` | The scroller holding the views and the list. |
591
+ | `list` | `.devdock-list` | The routes/commands list. |
592
+ | `section` | `.devdock-section` | One group's wrapper, in the list or around a view. |
593
+ | `group` | `.devdock-group` | A group heading. |
594
+ | `row` | `.devdock-row` | Every row. `[data-active]` on the highlighted one. |
595
+ | `rowActive` | — | Added to the highlighted row, on top of `row`. |
596
+ | `rowLabel` | `.devdock-row-label` | A row's label. |
597
+ | `rowPath` | `.devdock-row-path` | The monospace path on a route row. |
598
+ | `check` | `.devdock-check` | A toggle command's check mark. |
599
+ | `empty` | `.devdock-empty` | The "Nothing here." placeholder. |
600
+ | `viewLabel` | `.devdock-view-label` | A view's own label under a shared heading. |
601
+ | `view` | `.devdock-view` | Each view's host element. |
421
602
 
422
603
  ## API
423
604
 
@@ -437,12 +618,18 @@ isolation is the reason the dock survives arbitrary host CSS.
437
618
  | `hotkey` | `string \| null` | `null` | Toggle shortcut, e.g. `"mod+."`. |
438
619
  | `zIndex` | `number` | `2147483000` | Base z-index. |
439
620
  | `container` | `HTMLElement` | `document.body` | Where to mount. |
440
- | `shadow` | `boolean \| "inherit"` | `true` | Render in a shadow root (CSS-isolated). `"inherit"` copies the page's styles in; `false` renders in the light DOM. Read once, at creation. |
621
+ | `shadow` | `boolean \| "inherit"` | `true`, or `"inherit"` with `classNames` | Render in a shadow root (CSS-isolated). `"inherit"` copies the page's styles in; `false` renders in the light DOM. Read once, at creation. |
622
+ | `classNames` | `DevDockClassNames` | `{}` | [Classes merged onto the dock's parts](#restyling-it-classnames) with `cn`, e.g. `{ panel: ["w-96", className] }`. |
623
+ | `cssLayer` | `string \| false` | `"devdock"` | The cascade layer the dock's stylesheet goes in. A name of your own ships no order statement — place it yourself. `false` for an unlayered page (Tailwind v3). Read once, at creation. |
441
624
 
442
625
  `DevRoute`: `{ path: string; label?: string; group?: string }`
443
626
 
444
627
  `DevCommand`: `{ label: string | (() => string); run: () => void | Promise<void>; checked?: () => boolean; id?: string; group?: string; keepOpen?: boolean }`
445
628
 
629
+ `DevDockClassNames`: one optional `ClassValue` per part — see [the parts](#the-parts).
630
+
631
+ `ClassValue`: whatever `cn` accepts — `string | number | bigint | boolean | null | undefined | ClassValue[] | Record<string, unknown>`. Falsy entries drop out, conflicting Tailwind classes collapse to the last one.
632
+
446
633
  `DevView`: `{ mount: (host: HTMLElement) => void | (() => void); id?: string; label?: string; group?: string; order?: "before" | "after" }` — on the React adapter, `render: () => ReactNode` replaces `mount`.
447
634
 
448
635
  ### `@chuvenger/devdock` (core)
@@ -457,8 +644,9 @@ isolation is the reason the dock survives arbitrary host CSS.
457
644
 
458
645
  ### `@chuvenger/devdock/react-router`
459
646
 
460
- - `ReactRouterDevDock(props)` — `DevDock` wired to `useNavigate()` + auto-detected routes. Extra props: `match?: RouteMatcher`, `staticRoutes?: boolean`.
461
- - `useDetectedRoutes(options?): DevRoute[]` — the detected routes (build your own UI). Takes `{ match?, staticRoutes? }`, or a bare matcher.
647
+ - `ReactRouterDevDock(props)` — `DevDock` wired to `useNavigate()` + auto-detected routes. Extra props: `match?: RouteMatcher`, `staticRoutes?: boolean`, `loadHandles?: boolean`.
648
+ - `useDetectedRoutes(options?): DevRoute[]` — the detected routes (build your own UI). Takes `{ match?, staticRoutes?, loadHandles? }`, or a bare matcher. Re-reads the tree as the router grows it.
649
+ - `loadHandles` — import lazy route modules to read their `handle`; needed for `handle` in [framework mode](#react-router-framework-mode-approutests).
462
650
  - `flattenRoutes(routes, parent?, options?): DevRoute[]` — pure helper that flattens a route tree.
463
651
  - `RouteMatcher` — `string | RegExp | ((route: DevRoute) => boolean)`.
464
652
 
@@ -1,4 +1,4 @@
1
- import { keyViews, createDevDock } from './chunk-NXZNFF34.js';
1
+ import { keyViews, createDevDock } from './chunk-2YX4DI2D.js';
2
2
  import { useRef, useState, useMemo, useEffect } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
@@ -56,5 +56,5 @@ function DevDock({ views = NO_VIEWS, ...options }) {
56
56
  }
57
57
 
58
58
  export { DevDock };
59
- //# sourceMappingURL=chunk-3MVFATRN.js.map
60
- //# sourceMappingURL=chunk-3MVFATRN.js.map
59
+ //# sourceMappingURL=chunk-2IQRWZ7T.js.map
60
+ //# sourceMappingURL=chunk-2IQRWZ7T.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react/DevDock.tsx"],"names":[],"mappings":";;;;;AA6BA,IAAM,WAA0B,EAAC;AAQ1B,SAAS,QAAQ,EAAE,KAAA,GAAQ,QAAA,EAAU,GAAG,SAAQ,EAAiB;AACtE,EAAA,MAAM,QAAA,GAAW,OAA+B,IAAI,CAAA;AAIpD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA;AAAA,IACxB,0BAAU,GAAA;AAAI,GAChB;AAGA,EAAA,MAAM,KAAA,GAAQ,OAAO,IAAI,CAAA;AAIzB,EAAA,MAAM,KAAA,GAAQ,QAAQ,MAAM,QAAA,CAAS,KAAK,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEpD,EAAA,MAAM,SAAA,GAAY,OAAA;AAAA,IAChB,MACE,KAAA,CAAM,GAAA,CAAI,CAAC,EAAE,GAAA,EAAK,MAAK,MAAO;AAAA,MAC5B,EAAA,EAAI,GAAA;AAAA,MACJ,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,KAAA,EAAO,CAAC,IAAA,KAAS;AACf,QAAA,QAAA,CAAS,CAAC,SAAS,IAAI,GAAA,CAAI,IAAI,CAAA,CAAE,GAAA,CAAI,GAAA,EAAK,IAAI,CAAC,CAAA;AAC/C,QAAA,OAAO,MAAM;AACX,UAAA,IAAI,CAAC,MAAM,OAAA,EAAS;AACpB,UAAA,QAAA,CAAS,CAAC,IAAA,KAAS;AACjB,YAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,IAAI,CAAA;AACzB,YAAA,IAAA,CAAK,OAAO,GAAG,CAAA;AACf,YAAA,OAAO,IAAA;AAAA,UACT,CAAC,CAAA;AAAA,QACH,CAAA;AAAA,MACF;AAAA,KACF,CAAE,CAAA;AAAA,IACJ,CAAC,KAAK;AAAA,GACR;AAKA,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,EAAE,OAAA,EAAS,WAAW,CAAA;AAC5C,EAAA,MAAA,CAAO,OAAA,GAAU,EAAE,OAAA,EAAS,SAAA,EAAU;AAEtC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,KAAA,CAAM,OAAA,GAAU,IAAA;AAChB,IAAA,MAAM,OAAO,aAAA,CAAc;AAAA,MACzB,GAAG,OAAO,OAAA,CAAQ,OAAA;AAAA,MAClB,KAAA,EAAO,OAAO,OAAA,CAAQ;AAAA,KACvB,CAAA;AACD,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,OAAO,MAAM;AACX,MAAA,KAAA,CAAM,OAAA,GAAU,KAAA;AAChB,MAAA,IAAA,CAAK,OAAA,EAAQ;AACb,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AAAA,IACrB,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,QAAA,CAAS,SAAS,MAAA,CAAO,EAAE,GAAG,OAAA,EAAS,KAAA,EAAO,WAAW,CAAA;AAAA,EAC3D,CAAC,CAAA;AAED,EAAA,uCAEK,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,EAAE,GAAA,EAAK,MAAK,KAAM;AAC5B,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAC1B,IAAA,OAAO,OAAO,YAAA,CAAa,IAAA,CAAK,QAAO,EAAG,IAAA,EAAM,GAAG,CAAA,GAAI,IAAA;AAAA,EACzD,CAAC,CAAA,EACH,CAAA;AAEJ","file":"chunk-3MVFATRN.js","sourcesContent":["import { useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { createDevDock, keyViews } from \"../core/dock\";\nimport type {\n DevDockInstance,\n DevDockOptions,\n DevView,\n} from \"../core/types\";\n\n/**\n * A custom panel rendered by React inside the dock's popup. Same contract as\n * the core's {@link DevView}, with `render` in place of `mount`: the component\n * is portaled into the dock, mounted when the popup opens and unmounted when\n * it closes.\n *\n * A view renders inside the dock's shadow root, which the document's\n * stylesheets don't cross — pass `shadow=\"inherit\"` (or `shadow={false}`) if\n * it uses your app's CSS.\n */\nexport interface DevDockView extends Omit<DevView, \"mount\"> {\n /** The view's content. Called on every render of the host `<DevDock>`. */\n render: () => ReactNode;\n}\n\nexport type DevDockProps = Omit<DevDockOptions, \"views\"> & {\n /** Custom panels rendered inside the popup. See {@link DevDockView}. */\n views?: DevDockView[];\n};\n\nconst NO_VIEWS: DevDockView[] = [];\n\n/**\n * React wrapper around the framework-agnostic core. The dock itself mounts\n * into `document.body` (or `container`) and is torn down on unmount; this\n * component renders only the portals for any `views` you pass, so with none it\n * renders nothing. Safe to leave mounted; it renders nothing in production.\n */\nexport function DevDock({ views = NO_VIEWS, ...options }: DevDockProps) {\n const instance = useRef<DevDockInstance | null>(null);\n // The host elements the core created for our views, keyed the same way it\n // keys them. React draws into these with a portal, which keeps the core free\n // of any React import.\n const [hosts, setHosts] = useState<ReadonlyMap<string, HTMLElement>>(\n () => new Map(),\n );\n // `destroy()` tears views down, which would otherwise ask an unmounting\n // component to set state.\n const alive = useRef(true);\n\n // Keyed exactly the way the core keys the host elements, so each portal\n // finds its own.\n const keyed = useMemo(() => keyViews(views), [views]);\n\n const coreViews = useMemo<DevView[]>(\n () =>\n keyed.map(({ key, view }) => ({\n id: key,\n label: view.label,\n group: view.group,\n order: view.order,\n mount: (host) => {\n setHosts((prev) => new Map(prev).set(key, host));\n return () => {\n if (!alive.current) return;\n setHosts((prev) => {\n const next = new Map(prev);\n next.delete(key);\n return next;\n });\n };\n },\n })),\n [keyed],\n );\n\n // The core reconciles views by id and no-ops when their shape is unchanged,\n // so handing it a fresh array on every render costs nothing and never\n // remounts a view.\n const latest = useRef({ options, coreViews });\n latest.current = { options, coreViews };\n\n useEffect(() => {\n alive.current = true;\n const inst = createDevDock({\n ...latest.current.options,\n views: latest.current.coreViews,\n });\n instance.current = inst;\n return () => {\n alive.current = false;\n inst.destroy();\n instance.current = null;\n };\n }, []);\n\n useEffect(() => {\n instance.current?.update({ ...options, views: coreViews });\n });\n\n return (\n <>\n {keyed.map(({ key, view }) => {\n const host = hosts.get(key);\n return host ? createPortal(view.render(), host, key) : null;\n })}\n </>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/react/DevDock.tsx"],"names":[],"mappings":";;;;;AA6BA,IAAM,WAA0B,EAAC;AAQ1B,SAAS,QAAQ,EAAE,KAAA,GAAQ,QAAA,EAAU,GAAG,SAAQ,EAAiB;AACtE,EAAA,MAAM,QAAA,GAAW,OAA+B,IAAI,CAAA;AAIpD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA;AAAA,IACxB,0BAAU,GAAA;AAAI,GAChB;AAGA,EAAA,MAAM,KAAA,GAAQ,OAAO,IAAI,CAAA;AAIzB,EAAA,MAAM,KAAA,GAAQ,QAAQ,MAAM,QAAA,CAAS,KAAK,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEpD,EAAA,MAAM,SAAA,GAAY,OAAA;AAAA,IAChB,MACE,KAAA,CAAM,GAAA,CAAI,CAAC,EAAE,GAAA,EAAK,MAAK,MAAO;AAAA,MAC5B,EAAA,EAAI,GAAA;AAAA,MACJ,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,KAAA,EAAO,CAAC,IAAA,KAAS;AACf,QAAA,QAAA,CAAS,CAAC,SAAS,IAAI,GAAA,CAAI,IAAI,CAAA,CAAE,GAAA,CAAI,GAAA,EAAK,IAAI,CAAC,CAAA;AAC/C,QAAA,OAAO,MAAM;AACX,UAAA,IAAI,CAAC,MAAM,OAAA,EAAS;AACpB,UAAA,QAAA,CAAS,CAAC,IAAA,KAAS;AACjB,YAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,IAAI,CAAA;AACzB,YAAA,IAAA,CAAK,OAAO,GAAG,CAAA;AACf,YAAA,OAAO,IAAA;AAAA,UACT,CAAC,CAAA;AAAA,QACH,CAAA;AAAA,MACF;AAAA,KACF,CAAE,CAAA;AAAA,IACJ,CAAC,KAAK;AAAA,GACR;AAKA,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,EAAE,OAAA,EAAS,WAAW,CAAA;AAC5C,EAAA,MAAA,CAAO,OAAA,GAAU,EAAE,OAAA,EAAS,SAAA,EAAU;AAEtC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,KAAA,CAAM,OAAA,GAAU,IAAA;AAChB,IAAA,MAAM,OAAO,aAAA,CAAc;AAAA,MACzB,GAAG,OAAO,OAAA,CAAQ,OAAA;AAAA,MAClB,KAAA,EAAO,OAAO,OAAA,CAAQ;AAAA,KACvB,CAAA;AACD,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,OAAO,MAAM;AACX,MAAA,KAAA,CAAM,OAAA,GAAU,KAAA;AAChB,MAAA,IAAA,CAAK,OAAA,EAAQ;AACb,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AAAA,IACrB,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,QAAA,CAAS,SAAS,MAAA,CAAO,EAAE,GAAG,OAAA,EAAS,KAAA,EAAO,WAAW,CAAA;AAAA,EAC3D,CAAC,CAAA;AAED,EAAA,uCAEK,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,EAAE,GAAA,EAAK,MAAK,KAAM;AAC5B,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAC1B,IAAA,OAAO,OAAO,YAAA,CAAa,IAAA,CAAK,QAAO,EAAG,IAAA,EAAM,GAAG,CAAA,GAAI,IAAA;AAAA,EACzD,CAAC,CAAA,EACH,CAAA;AAEJ","file":"chunk-2IQRWZ7T.js","sourcesContent":["import { useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { createDevDock, keyViews } from \"../core/dock\";\nimport type {\n DevDockInstance,\n DevDockOptions,\n DevView,\n} from \"../core/types\";\n\n/**\n * A custom panel rendered by React inside the dock's popup. Same contract as\n * the core's {@link DevView}, with `render` in place of `mount`: the component\n * is portaled into the dock, mounted when the popup opens and unmounted when\n * it closes.\n *\n * A view renders inside the dock's shadow root, which the document's\n * stylesheets don't cross — pass `shadow=\"inherit\"` (or `shadow={false}`) if\n * it uses your app's CSS.\n */\nexport interface DevDockView extends Omit<DevView, \"mount\"> {\n /** The view's content. Called on every render of the host `<DevDock>`. */\n render: () => ReactNode;\n}\n\nexport type DevDockProps = Omit<DevDockOptions, \"views\"> & {\n /** Custom panels rendered inside the popup. See {@link DevDockView}. */\n views?: DevDockView[];\n};\n\nconst NO_VIEWS: DevDockView[] = [];\n\n/**\n * React wrapper around the framework-agnostic core. The dock itself mounts\n * into `document.body` (or `container`) and is torn down on unmount; this\n * component renders only the portals for any `views` you pass, so with none it\n * renders nothing. Safe to leave mounted; it renders nothing in production.\n */\nexport function DevDock({ views = NO_VIEWS, ...options }: DevDockProps) {\n const instance = useRef<DevDockInstance | null>(null);\n // The host elements the core created for our views, keyed the same way it\n // keys them. React draws into these with a portal, which keeps the core free\n // of any React import.\n const [hosts, setHosts] = useState<ReadonlyMap<string, HTMLElement>>(\n () => new Map(),\n );\n // `destroy()` tears views down, which would otherwise ask an unmounting\n // component to set state.\n const alive = useRef(true);\n\n // Keyed exactly the way the core keys the host elements, so each portal\n // finds its own.\n const keyed = useMemo(() => keyViews(views), [views]);\n\n const coreViews = useMemo<DevView[]>(\n () =>\n keyed.map(({ key, view }) => ({\n id: key,\n label: view.label,\n group: view.group,\n order: view.order,\n mount: (host) => {\n setHosts((prev) => new Map(prev).set(key, host));\n return () => {\n if (!alive.current) return;\n setHosts((prev) => {\n const next = new Map(prev);\n next.delete(key);\n return next;\n });\n };\n },\n })),\n [keyed],\n );\n\n // The core reconciles views by id and no-ops when their shape is unchanged,\n // so handing it a fresh array on every render costs nothing and never\n // remounts a view.\n const latest = useRef({ options, coreViews });\n latest.current = { options, coreViews };\n\n useEffect(() => {\n alive.current = true;\n const inst = createDevDock({\n ...latest.current.options,\n views: latest.current.coreViews,\n });\n instance.current = inst;\n return () => {\n alive.current = false;\n inst.destroy();\n instance.current = null;\n };\n }, []);\n\n useEffect(() => {\n instance.current?.update({ ...options, views: coreViews });\n });\n\n return (\n <>\n {keyed.map(({ key, view }) => {\n const host = hosts.get(key);\n return host ? createPortal(view.render(), host, key) : null;\n })}\n </>\n );\n}\n"]}