@chuvenger/devdock 0.6.0 → 0.8.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 (39) hide show
  1. package/README.md +165 -15
  2. package/dist/{chunk-NXZNFF34.js → chunk-7Z3YWV5K.js} +343 -152
  3. package/dist/chunk-7Z3YWV5K.js.map +1 -0
  4. package/dist/{chunk-3MVFATRN.js → chunk-MKYQ666Z.js} +3 -3
  5. package/dist/{chunk-3MVFATRN.js.map → chunk-MKYQ666Z.js.map} +1 -1
  6. package/dist/{dock-P6l6pKjS.d.ts → dock-C8tR26RP.d.ts} +1 -1
  7. package/dist/{dock-d3HqT25k.d.cts → dock-Cxh5FIAv.d.cts} +1 -1
  8. package/dist/index.cjs +344 -149
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +20 -3
  11. package/dist/index.d.ts +20 -3
  12. package/dist/index.js +1 -1
  13. package/dist/{match-DDSJIRYW.d.cts → match-CFB-9KGi.d.cts} +1 -1
  14. package/dist/{match-DwGIF1Xt.d.ts → match-injUeExB.d.ts} +1 -1
  15. package/dist/react-router.cjs +339 -149
  16. package/dist/react-router.cjs.map +1 -1
  17. package/dist/react-router.d.cts +3 -2
  18. package/dist/react-router.d.ts +3 -2
  19. package/dist/react-router.js +2 -2
  20. package/dist/react.cjs +339 -149
  21. package/dist/react.cjs.map +1 -1
  22. package/dist/react.d.cts +3 -2
  23. package/dist/react.d.ts +3 -2
  24. package/dist/react.js +2 -2
  25. package/dist/svelte.cjs +342 -149
  26. package/dist/svelte.cjs.map +1 -1
  27. package/dist/svelte.d.cts +4 -3
  28. package/dist/svelte.d.ts +4 -3
  29. package/dist/svelte.js +2 -2
  30. package/dist/svelte.js.map +1 -1
  31. package/dist/sveltekit.cjs +342 -149
  32. package/dist/sveltekit.cjs.map +1 -1
  33. package/dist/sveltekit.d.cts +5 -4
  34. package/dist/sveltekit.d.ts +5 -4
  35. package/dist/sveltekit.js +2 -2
  36. package/dist/{types-B_SpF32A.d.cts → types-BuYSEeDa.d.cts} +113 -5
  37. package/dist/{types-B_SpF32A.d.ts → types-BuYSEeDa.d.ts} +113 -5
  38. package/package.json +4 -1
  39. 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
 
@@ -11,8 +11,8 @@ routes and run custom commands.
11
11
  - 🧭 **Route jumper** — list routes manually, or auto-detect your `/dev` routes from React Router or SvelteKit.
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
- - 🔎 Filter box + keyboard nav (↑/↓/Enter/Esc) and an optional global hotkey.
15
- - 🎨 Zero CSS to import — inline-styled, high z-index, no runtime dependencies.
14
+ - ⌨️ **Hotkey out of the box** — ⌥⇧D (Alt+Shift+D elsewhere) toggles the popup, a chord no browser, OS or app tends to want, and the popup's heading says so. Rebind with `hotkey`, or `hotkey={null}` to claim no key. Plus a filter box and keyboard nav (↑/↓/Enter/Esc).
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
 
@@ -61,7 +61,6 @@ export function App() {
61
61
  { label: "Reset onboarding", run: () => localStorage.clear() },
62
62
  { label: "Log current user", run: async () => console.log(await getUser()) },
63
63
  ]}
64
- hotkey="mod+."
65
64
  />
66
65
  </>
67
66
  );
@@ -182,7 +181,6 @@ in any Svelte 4/5 app:
182
181
  const options = {
183
182
  routes: [{ path: "/admin", label: "Admin" }],
184
183
  commands: [{ label: "Reset", run: () => localStorage.clear() }],
185
- hotkey: "mod+.",
186
184
  onNavigate: (path) => goto(path), // e.g. SvelteKit's goto
187
185
  };
188
186
  </script>
@@ -211,7 +209,6 @@ in your app — Vite resolves it at build time — so pass the result in as
211
209
  const options = {
212
210
  modules,
213
211
  onNavigate: goto, // client-side navigation instead of a full load
214
- hotkey: "mod+.",
215
212
  };
216
213
  </script>
217
214
 
@@ -440,11 +437,11 @@ tree-shakes it: `{import.meta.env.DEV && <DevDock … />}`.
440
437
 
441
438
  ## Styling & isolation
442
439
 
443
- The dock is styled entirely with inline styles (no stylesheet to import) and,
444
- by default, renders inside an **open shadow root** attached to a host element in
445
- `document.body`. That means the host page's CSS can't leak in and the dock's
446
- styles can't leak out. The host element carries a `data-devdock` attribute in
447
- every mode.
440
+ The dock ships its own stylesheet — one it injects itself, nothing to
441
+ import, themeable with [custom properties](#theming-it-custom-properties) — and, by default, renders inside an **open shadow root** attached to a
442
+ host element in `document.body`. The host page's CSS can't leak in and the
443
+ dock's styles can't leak out. The host element carries a `data-devdock`
444
+ attribute in every mode.
448
445
 
449
446
  | `shadow` | Where it renders | The page's CSS |
450
447
  | -------- | ---------------- | -------------- |
@@ -453,8 +450,153 @@ every mode.
453
450
  | `false` | light DOM | applies directly |
454
451
 
455
452
  Use `"inherit"` (or `false`) when a [custom view](#views-and-your-css) renders
456
- your app's own components and needs its stylesheet. Otherwise leave it alone —
457
- isolation is the reason the dock survives arbitrary host CSS.
453
+ your app's own components and needs its stylesheet, or when you're restyling
454
+ the dock with `classNames` below — passing `classNames` switches the default to
455
+ `"inherit"` for you, since a class of yours is defined by a stylesheet that has
456
+ to cross the boundary. Otherwise leave it alone: isolation is the reason the
457
+ dock survives arbitrary host CSS.
458
+
459
+ There are two ways to change how it looks, and they compose:
460
+
461
+ | | Reach | Costs you |
462
+ | --- | --- | --- |
463
+ | [Custom properties](#theming-it-custom-properties) | colours, radii, font, size | nothing — they inherit **through** the shadow boundary, so the dock stays isolated |
464
+ | [`classNames`](#restyling-it-classnames) | anything CSS can say | your stylesheet has to reach the dock, so `shadow` becomes `"inherit"` |
465
+
466
+ Reach for the properties first; drop to `classNames` when you need something
467
+ they don't cover.
468
+
469
+ ### Theming it: custom properties
470
+
471
+ Set any of these on `:root`, or on the `[data-devdock]` host to theme one dock,
472
+ and you're done — no `shadow` change, no classes, nothing to import:
473
+
474
+ ```css
475
+ :root {
476
+ --devdock-bg: #ffffff;
477
+ --devdock-fg: #0f172a;
478
+ --devdock-accent: #a855f7;
479
+ --devdock-border: #e2e8f0;
480
+ --devdock-radius: 16px;
481
+ --devdock-width: 320px;
482
+ }
483
+ ```
484
+
485
+ | Property | Default | What it paints |
486
+ | -------- | ------- | -------------- |
487
+ | `--devdock-font` | system UI stack | The dock's font family. |
488
+ | `--devdock-bg` | `#16181d` | Popup background. |
489
+ | `--devdock-fg` | `#e7e9ee` | Primary text — rows, the input. |
490
+ | `--devdock-muted` | `#8b909c` | The title, a view's label. |
491
+ | `--devdock-subtle` | `#6b7280` | Group headings, route paths, the empty state. |
492
+ | `--devdock-border` | `#2a2e37` | Every border. |
493
+ | `--devdock-accent` | `#2563eb` | Highlighted row, and the button while open. |
494
+ | `--devdock-accent-fg` | `#e7e9ee` | Text on the highlighted row. |
495
+ | `--devdock-button-bg` | `#111317` | The button, closed. |
496
+ | `--devdock-button-fg` | `#fff` | The button's text. |
497
+ | `--devdock-input-bg` | `#0f1115` | The filter input. |
498
+ | `--devdock-success` | `#22c55e` | The status dot and toggle check marks. |
499
+ | `--devdock-radius` | `12px` | Popup corners. |
500
+ | `--devdock-radius-sm` | `8px` | Rows and the input. |
501
+ | `--devdock-width` | `280px` | Popup width. |
502
+ | `--devdock-max-height` | `70vh` | Popup height before it scrolls. |
503
+ | `--devdock-shadow` | `0 12px 40px rgba(0,0,0,.45)` | The popup's shadow. |
504
+
505
+ They're read as `var(--devdock-x, default)` and never declared inside the
506
+ dock, which is what lets a value you set outside win — a declaration of ours
507
+ in the shadow tree would have beaten the one inheriting in.
508
+
509
+ ### Restyling it: `classNames`
510
+
511
+ Every part of the dock takes a class of yours, **merged onto** the one it
512
+ already carries rather than replacing it:
513
+
514
+ ```tsx
515
+ <DevDock
516
+ classNames={{
517
+ panel: "w-96 max-h-[80vh] bg-white text-slate-900 border-slate-200",
518
+ button: "bg-emerald-600 rounded-md",
519
+ input: "bg-slate-50 border-slate-300",
520
+ row: "rounded-none",
521
+ rowActive: "bg-emerald-100 text-emerald-900",
522
+ }}
523
+ />
524
+ ```
525
+
526
+ Each entry takes **anything [`cn`](https://www.npmjs.com/package/cn) takes** —
527
+ a string, an array, a conditional, an object — so a wrapper of your own can
528
+ forward its props straight through without a helper import:
529
+
530
+ ```tsx
531
+ export function AppDock({ className }: { className?: string }) {
532
+ return (
533
+ <DevDock
534
+ classNames={{
535
+ panel: ["bg-red-100", className],
536
+ row: ["px-3", compact && "py-1", { "opacity-50": muted }],
537
+ }}
538
+ />
539
+ );
540
+ }
541
+ ```
542
+
543
+ Values are merged with [`cn`](https://www.npmjs.com/package/cn), so conflicts
544
+ resolve the way you'd write them by hand: `rowActive: "text-emerald-900"` over
545
+ `row: "text-slate-700"` keeps emerald and **drops** slate, instead of putting
546
+ both in the attribute and letting Tailwind's own sheet order pick the winner.
547
+ The dock's `devdock-*` classes aren't utilities, so they pass through untouched.
548
+
549
+ No `!important`, no `[&_div]` reaching: a plain utility class wins on its own.
550
+ Two things in the dock's sheet make that true, and they're worth knowing about
551
+ because they also say when it *doesn't*:
552
+
553
+ - **It lives in a cascade layer** (`devdock`), declared *after* Tailwind's
554
+ `base` and *before* its `utilities`. Layered rules lose to unlayered ones
555
+ whatever their specificity, so this is what lets a Tailwind v4 utility
556
+ through while keeping a reset in `base` from stripping the dock's chrome.
557
+ - **Each rule is worth exactly one class** — one bare `.devdock-*`, with any
558
+ state selector tucked inside `:where()`. That's what settles ties when the
559
+ layer is off: the sheet goes in first, so a page-level reset
560
+ (`button { … }`, `*`) loses to it and your class beats it.
561
+
562
+ If the page's own CSS is unlayered — Tailwind v3, a hand-written reset —
563
+ turn the layer off with `cssLayer={false}`, or the page's reset will outrank
564
+ the dock itself. (A page with no `@layer` anywhere is unaffected either way.)
565
+ If you run your own layers, name the dock's with `cssLayer="chrome"`: it then
566
+ ships no order statement at all, and you place it in your own `@layer` line,
567
+ where you know the names around it.
568
+
569
+ Not themeable this way: the corner placement, the z-index, and the popup's
570
+ anchoring. Those are computed from `position`/`zIndex` and stay inline.
571
+
572
+ ### The parts
573
+
574
+ Each one carries its `devdock-*` class in every mode, so you can also just
575
+ write CSS against them and skip `classNames` entirely.
576
+
577
+ | `classNames` key | Class | What it is |
578
+ | ---------------- | ----- | ---------- |
579
+ | `root` | `.devdock-root` | The fixed wrapper holding the button and the popup. |
580
+ | `button` | `.devdock-button` | The floating button. `[aria-expanded="true"]` while open. |
581
+ | `dot` | `.devdock-dot` | The status dot inside it. |
582
+ | `buttonLabel` | `.devdock-button-label`| The button's text. |
583
+ | `panel` | `.devdock-panel` | The popup. |
584
+ | `title` | `.devdock-title` | Its heading — a row holding the title and the hotkey hint. |
585
+ | `hotkeyHint` | `.devdock-hotkey-hint` | The `<kbd>` in the heading naming the `hotkey`. Hidden with `hotkey: null`. |
586
+ | `inputWrap` | `.devdock-input-wrap` | The padding around the filter input. |
587
+ | `input` | `.devdock-input` | The filter input. |
588
+ | `body` | `.devdock-body` | The scroller holding the views and the list. |
589
+ | `list` | `.devdock-list` | The routes/commands list. |
590
+ | `section` | `.devdock-section` | One group's wrapper, in the list or around a view. |
591
+ | `group` | `.devdock-group` | A group heading. |
592
+ | `row` | `.devdock-row` | Every row. `[data-active]` on the highlighted one. |
593
+ | `rowActive` | — | Added to the highlighted row, on top of `row`. |
594
+ | `rowLabel` | `.devdock-row-label` | A row's label. |
595
+ | `rowPath` | `.devdock-row-path` | The monospace path on a route row. |
596
+ | `check` | `.devdock-check` | A toggle command's check mark. |
597
+ | `empty` | `.devdock-empty` | The "Nothing here." placeholder. |
598
+ | `viewLabel` | `.devdock-view-label` | A view's own label under a shared heading. |
599
+ | `view` | `.devdock-view` | Each view's host element. |
458
600
 
459
601
  ## API
460
602
 
@@ -471,21 +613,29 @@ isolation is the reason the dock survives arbitrary host CSS.
471
613
  | `position` | `"bottom-left" \| "bottom-right" \| "top-left" \| "top-right"` | `"bottom-right"` | Corner. |
472
614
  | `label` | `string` | `"DEV"` | Button text. |
473
615
  | `title` | `string` | `"Dev Menu"` | Popup heading. |
474
- | `hotkey` | `string \| null` | `null` | Toggle shortcut, e.g. `"mod+."`. |
616
+ | `hotkey` | `string \| null` | `"alt+shift+d"` | Toggle shortcut (⌥⇧D / Alt+Shift+D). `"mod"` in a spec is ⌘ on Mac, Ctrl elsewhere. `null` claims no key. |
475
617
  | `zIndex` | `number` | `2147483000` | Base z-index. |
476
618
  | `container` | `HTMLElement` | `document.body` | Where to mount. |
477
- | `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. |
619
+ | `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. |
620
+ | `classNames` | `DevDockClassNames` | `{}` | [Classes merged onto the dock's parts](#restyling-it-classnames) with `cn`, e.g. `{ panel: ["w-96", className] }`. |
621
+ | `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. |
478
622
 
479
623
  `DevRoute`: `{ path: string; label?: string; group?: string }`
480
624
 
481
625
  `DevCommand`: `{ label: string | (() => string); run: () => void | Promise<void>; checked?: () => boolean; id?: string; group?: string; keepOpen?: boolean }`
482
626
 
627
+ `DevDockClassNames`: one optional `ClassValue` per part — see [the parts](#the-parts).
628
+
629
+ `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.
630
+
483
631
  `DevView`: `{ mount: (host: HTMLElement) => void | (() => void); id?: string; label?: string; group?: string; order?: "before" | "after" }` — on the React adapter, `render: () => ReactNode` replaces `mount`.
484
632
 
485
633
  ### `@chuvenger/devdock` (core)
486
634
 
487
635
  - `createDevDock(options?): { update(options), destroy() }` — mounts the dock; no-ops during SSR (no `document`).
488
636
  - `matchHotkey(spec, event)` — the hotkey matcher (exported for reuse).
637
+ - `formatHotkey(spec)` — a spec as a human reads it: `"⌥⇧D"` on Mac, `"Alt+Shift+D"` elsewhere.
638
+ - `DEFAULT_HOTKEY` — the default spec, `"alt+shift+d"`.
489
639
 
490
640
  ### `@chuvenger/devdock/react`
491
641