@chuvenger/devdock 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.
- package/README.md +93 -8
- package/dist/{chunk-JZWITFNK.js → chunk-D5IQBV2K.js} +3 -3
- package/dist/{chunk-JZWITFNK.js.map → chunk-D5IQBV2K.js.map} +1 -1
- package/dist/{chunk-DUHEBT6Q.js → chunk-F2KWDPMA.js} +18 -4
- package/dist/chunk-F2KWDPMA.js.map +1 -0
- package/dist/chunk-NRBOZ6H5.js +26 -0
- package/dist/chunk-NRBOZ6H5.js.map +1 -0
- package/dist/{dock-BQEAjL0s.d.ts → dock-CvTtAelh.d.ts} +1 -1
- package/dist/{dock-BTK1OArl.d.cts → dock-Du6Im767.d.cts} +1 -1
- package/dist/index.cjs +16 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/match-B6LN1RRP.d.cts +10 -0
- package/dist/match-C-mY8W8T.d.ts +10 -0
- package/dist/react-router.cjs +31 -12
- package/dist/react-router.cjs.map +1 -1
- package/dist/react-router.d.cts +3 -8
- package/dist/react-router.d.ts +3 -8
- package/dist/react-router.js +4 -21
- package/dist/react-router.js.map +1 -1
- package/dist/react.cjs +16 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -2
- package/dist/svelte.cjs +16 -2
- package/dist/svelte.cjs.map +1 -1
- package/dist/svelte.d.cts +3 -3
- package/dist/svelte.d.ts +3 -3
- package/dist/svelte.js +2 -2
- package/dist/sveltekit.cjs +520 -0
- package/dist/sveltekit.cjs.map +1 -0
- package/dist/sveltekit.d.cts +86 -0
- package/dist/sveltekit.d.ts +86 -0
- package/dist/sveltekit.js +68 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/{types-DWX2w51R.d.cts → types-fhhRek-e.d.cts} +17 -2
- package/dist/{types-DWX2w51R.d.ts → types-fhhRek-e.d.ts} +17 -2
- package/package.json +7 -1
- package/dist/chunk-DUHEBT6Q.js.map +0 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { b as DevDockOptions, c as DevRoute } from './types-fhhRek-e.cjs';
|
|
2
|
+
export { D as DevCommand, a as DevDockInstance, d as DockPosition } from './types-fhhRek-e.cjs';
|
|
3
|
+
import { R as RouteMatcher } from './match-B6LN1RRP.cjs';
|
|
4
|
+
import { DevDockAction } from './svelte.cjs';
|
|
5
|
+
export { c as createDevDock } from './dock-Du6Im767.cjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What `import.meta.glob("/src/routes/**\/+page.svelte")` returns — only the
|
|
9
|
+
* keys matter, so an array of file paths works too.
|
|
10
|
+
*/
|
|
11
|
+
type RouteModules = Record<string, unknown> | string[];
|
|
12
|
+
interface DetectOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Only keep routes that match. A glob string (`*` = any characters), a
|
|
15
|
+
* `RegExp`, or a predicate.
|
|
16
|
+
*/
|
|
17
|
+
match?: RouteMatcher;
|
|
18
|
+
/**
|
|
19
|
+
* Also list the app's ordinary routes, not just the ones under `/dev`.
|
|
20
|
+
* Default: `false`.
|
|
21
|
+
*/
|
|
22
|
+
staticRoutes?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Your routes directory, as a name or a trailing path (`"routes"`,
|
|
25
|
+
* `"app/pages"`). Detected from the `src/routes` (or `routes`) segment of
|
|
26
|
+
* each key by default; set it only if your routes live somewhere else.
|
|
27
|
+
*/
|
|
28
|
+
routesDir?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Turn a SvelteKit page file path into the route it serves, or `null` if it
|
|
32
|
+
* isn't a navigable page.
|
|
33
|
+
*
|
|
34
|
+
* Layout groups (`(app)`) and optional params (`[[lang]]`) drop out of the
|
|
35
|
+
* path; required (`[id]`), matched (`[id=int]`) and rest (`[...rest]`) params
|
|
36
|
+
* make the route unnavigable without arguments, so it's skipped.
|
|
37
|
+
*/
|
|
38
|
+
declare function routeFromFile(file: string, options?: Pick<DetectOptions, "routesDir">): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Flatten a SvelteKit `import.meta.glob` of page files into navigable
|
|
41
|
+
* {@link DevRoute}s.
|
|
42
|
+
*
|
|
43
|
+
* Only dev destinations are returned — anything under `/dev`, by convention.
|
|
44
|
+
* Pass `{ staticRoutes: true }` to list every page instead, and `{ match }`
|
|
45
|
+
* to scope the results. A bare matcher is accepted as shorthand.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* const routes = detectRoutes(import.meta.glob("/src/routes/**\/+page.svelte"));
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function detectRoutes(modules: RouteModules, options?: DetectOptions | RouteMatcher): DevRoute[];
|
|
52
|
+
interface SvelteKitDevDockOptions extends DevDockOptions, DetectOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Your page files, from `import.meta.glob`. The glob has to be written in
|
|
55
|
+
* your app (Vite resolves it at build time), so pass the result in:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* { modules: import.meta.glob("/src/routes/**\/+page.svelte") }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* Detected routes come first; any `routes` you pass are appended.
|
|
62
|
+
*/
|
|
63
|
+
modules?: RouteModules;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Svelte action that mounts a dev dock with the SvelteKit routes detected
|
|
67
|
+
* from `modules`:
|
|
68
|
+
*
|
|
69
|
+
* ```svelte
|
|
70
|
+
* <script lang="ts">
|
|
71
|
+
* import { goto } from "$app/navigation";
|
|
72
|
+
* import { devdock } from "@chuvenger/devdock/sveltekit";
|
|
73
|
+
*
|
|
74
|
+
* const modules = import.meta.glob("/src/routes/**\/+page.svelte");
|
|
75
|
+
* </script>
|
|
76
|
+
*
|
|
77
|
+
* <div use:devdock={{ modules, onNavigate: goto, hotkey: "mod+." }}></div>
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* Same contract as the plain `devdock` action: the dock mounts into
|
|
81
|
+
* `document.body` (or `options.container`), so the host element is just an
|
|
82
|
+
* anchor, and reactive options flow through `update`.
|
|
83
|
+
*/
|
|
84
|
+
declare function devdock(_node: HTMLElement, options?: SvelteKitDevDockOptions): DevDockAction;
|
|
85
|
+
|
|
86
|
+
export { type DetectOptions, DevDockAction, DevDockOptions, DevRoute, RouteMatcher, type RouteModules, type SvelteKitDevDockOptions, detectRoutes, devdock, routeFromFile };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { b as DevDockOptions, c as DevRoute } from './types-fhhRek-e.js';
|
|
2
|
+
export { D as DevCommand, a as DevDockInstance, d as DockPosition } from './types-fhhRek-e.js';
|
|
3
|
+
import { R as RouteMatcher } from './match-C-mY8W8T.js';
|
|
4
|
+
import { DevDockAction } from './svelte.js';
|
|
5
|
+
export { c as createDevDock } from './dock-CvTtAelh.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What `import.meta.glob("/src/routes/**\/+page.svelte")` returns — only the
|
|
9
|
+
* keys matter, so an array of file paths works too.
|
|
10
|
+
*/
|
|
11
|
+
type RouteModules = Record<string, unknown> | string[];
|
|
12
|
+
interface DetectOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Only keep routes that match. A glob string (`*` = any characters), a
|
|
15
|
+
* `RegExp`, or a predicate.
|
|
16
|
+
*/
|
|
17
|
+
match?: RouteMatcher;
|
|
18
|
+
/**
|
|
19
|
+
* Also list the app's ordinary routes, not just the ones under `/dev`.
|
|
20
|
+
* Default: `false`.
|
|
21
|
+
*/
|
|
22
|
+
staticRoutes?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Your routes directory, as a name or a trailing path (`"routes"`,
|
|
25
|
+
* `"app/pages"`). Detected from the `src/routes` (or `routes`) segment of
|
|
26
|
+
* each key by default; set it only if your routes live somewhere else.
|
|
27
|
+
*/
|
|
28
|
+
routesDir?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Turn a SvelteKit page file path into the route it serves, or `null` if it
|
|
32
|
+
* isn't a navigable page.
|
|
33
|
+
*
|
|
34
|
+
* Layout groups (`(app)`) and optional params (`[[lang]]`) drop out of the
|
|
35
|
+
* path; required (`[id]`), matched (`[id=int]`) and rest (`[...rest]`) params
|
|
36
|
+
* make the route unnavigable without arguments, so it's skipped.
|
|
37
|
+
*/
|
|
38
|
+
declare function routeFromFile(file: string, options?: Pick<DetectOptions, "routesDir">): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Flatten a SvelteKit `import.meta.glob` of page files into navigable
|
|
41
|
+
* {@link DevRoute}s.
|
|
42
|
+
*
|
|
43
|
+
* Only dev destinations are returned — anything under `/dev`, by convention.
|
|
44
|
+
* Pass `{ staticRoutes: true }` to list every page instead, and `{ match }`
|
|
45
|
+
* to scope the results. A bare matcher is accepted as shorthand.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* const routes = detectRoutes(import.meta.glob("/src/routes/**\/+page.svelte"));
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function detectRoutes(modules: RouteModules, options?: DetectOptions | RouteMatcher): DevRoute[];
|
|
52
|
+
interface SvelteKitDevDockOptions extends DevDockOptions, DetectOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Your page files, from `import.meta.glob`. The glob has to be written in
|
|
55
|
+
* your app (Vite resolves it at build time), so pass the result in:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* { modules: import.meta.glob("/src/routes/**\/+page.svelte") }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* Detected routes come first; any `routes` you pass are appended.
|
|
62
|
+
*/
|
|
63
|
+
modules?: RouteModules;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Svelte action that mounts a dev dock with the SvelteKit routes detected
|
|
67
|
+
* from `modules`:
|
|
68
|
+
*
|
|
69
|
+
* ```svelte
|
|
70
|
+
* <script lang="ts">
|
|
71
|
+
* import { goto } from "$app/navigation";
|
|
72
|
+
* import { devdock } from "@chuvenger/devdock/sveltekit";
|
|
73
|
+
*
|
|
74
|
+
* const modules = import.meta.glob("/src/routes/**\/+page.svelte");
|
|
75
|
+
* </script>
|
|
76
|
+
*
|
|
77
|
+
* <div use:devdock={{ modules, onNavigate: goto, hotkey: "mod+." }}></div>
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* Same contract as the plain `devdock` action: the dock mounts into
|
|
81
|
+
* `document.body` (or `options.container`), so the host element is just an
|
|
82
|
+
* anchor, and reactive options flow through `update`.
|
|
83
|
+
*/
|
|
84
|
+
declare function devdock(_node: HTMLElement, options?: SvelteKitDevDockOptions): DevDockAction;
|
|
85
|
+
|
|
86
|
+
export { type DetectOptions, DevDockAction, DevDockOptions, DevRoute, RouteMatcher, type RouteModules, type SvelteKitDevDockOptions, detectRoutes, devdock, routeFromFile };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isMatcher, isDevPath, dedupeByPath, toPredicate } from './chunk-NRBOZ6H5.js';
|
|
2
|
+
import { createDevDock } from './chunk-F2KWDPMA.js';
|
|
3
|
+
export { createDevDock } from './chunk-F2KWDPMA.js';
|
|
4
|
+
|
|
5
|
+
// src/svelte/sveltekit.ts
|
|
6
|
+
var PAGE_FILE = /^\+page(@[^.]*)?\.[^.]+$/;
|
|
7
|
+
function stripRoutesDir(file, routesDir) {
|
|
8
|
+
const path = file.replace(/\\/g, "/");
|
|
9
|
+
const dirs = routesDir ? [routesDir] : ["src/routes", "routes"];
|
|
10
|
+
for (const dir of dirs) {
|
|
11
|
+
const needle = `/${dir.replace(/^\/|\/$/g, "")}/`;
|
|
12
|
+
const at = path.lastIndexOf(needle);
|
|
13
|
+
if (at !== -1) return path.slice(at + needle.length);
|
|
14
|
+
if (path.startsWith(needle.slice(1))) return path.slice(needle.length - 1);
|
|
15
|
+
}
|
|
16
|
+
return routesDir ? null : path.replace(/^\.?\//, "");
|
|
17
|
+
}
|
|
18
|
+
function routeFromFile(file, options = {}) {
|
|
19
|
+
const relative = stripRoutesDir(file, options.routesDir);
|
|
20
|
+
if (relative == null) return null;
|
|
21
|
+
const segments = relative.split("/");
|
|
22
|
+
const filename = segments.pop();
|
|
23
|
+
if (!filename || !PAGE_FILE.test(filename)) return null;
|
|
24
|
+
const kept = [];
|
|
25
|
+
for (const segment of segments) {
|
|
26
|
+
if (!segment || segment === ".") continue;
|
|
27
|
+
if (segment.startsWith("(") && segment.endsWith(")")) continue;
|
|
28
|
+
if (segment.startsWith("[[") && segment.endsWith("]]")) continue;
|
|
29
|
+
if (segment.includes("[")) return null;
|
|
30
|
+
kept.push(segment);
|
|
31
|
+
}
|
|
32
|
+
return `/${kept.join("/")}`;
|
|
33
|
+
}
|
|
34
|
+
function detectRoutes(modules, options = {}) {
|
|
35
|
+
const opts = isMatcher(options) ? { match: options } : options;
|
|
36
|
+
const files = Array.isArray(modules) ? modules : Object.keys(modules);
|
|
37
|
+
const paths = [];
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const path = routeFromFile(file, opts);
|
|
40
|
+
if (path != null && (opts.staticRoutes === true || isDevPath(path)))
|
|
41
|
+
paths.push(path);
|
|
42
|
+
}
|
|
43
|
+
const routes = dedupeByPath(
|
|
44
|
+
paths.sort().map((path) => ({ path, label: path }))
|
|
45
|
+
);
|
|
46
|
+
return opts.match ? routes.filter(toPredicate(opts.match)) : routes;
|
|
47
|
+
}
|
|
48
|
+
function resolveOptions(options) {
|
|
49
|
+
const { modules, match, staticRoutes, routesDir, ...rest } = options;
|
|
50
|
+
if (!modules) return rest;
|
|
51
|
+
const detected = detectRoutes(modules, { match, staticRoutes, routesDir });
|
|
52
|
+
return { ...rest, routes: [...detected, ...rest.routes ?? []] };
|
|
53
|
+
}
|
|
54
|
+
function devdock(_node, options = {}) {
|
|
55
|
+
const instance = createDevDock(resolveOptions(options));
|
|
56
|
+
return {
|
|
57
|
+
update(next = {}) {
|
|
58
|
+
instance.update(resolveOptions(next));
|
|
59
|
+
},
|
|
60
|
+
destroy() {
|
|
61
|
+
instance.destroy();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { detectRoutes, devdock, routeFromFile };
|
|
67
|
+
//# sourceMappingURL=sveltekit.js.map
|
|
68
|
+
//# sourceMappingURL=sveltekit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/svelte/sveltekit.ts"],"names":[],"mappings":"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","file":"sveltekit.js","sourcesContent":["import { createDevDock } from \"../core/dock\";\nimport type { DevDockOptions, DevRoute } from \"../core/types\";\nimport {\n dedupeByPath,\n isDevPath,\n isMatcher,\n toPredicate,\n type RouteMatcher,\n} from \"../core/match\";\nimport type { DevDockAction } from \"./index\";\n\nexport type { RouteMatcher };\n\n/**\n * What `import.meta.glob(\"/src/routes/**\\/+page.svelte\")` returns — only the\n * keys matter, so an array of file paths works too.\n */\nexport type RouteModules = Record<string, unknown> | string[];\n\nexport interface DetectOptions {\n /**\n * Only keep routes that match. A glob string (`*` = any characters), a\n * `RegExp`, or a predicate.\n */\n match?: RouteMatcher;\n /**\n * Also list the app's ordinary routes, not just the ones under `/dev`.\n * Default: `false`.\n */\n staticRoutes?: boolean;\n /**\n * Your routes directory, as a name or a trailing path (`\"routes\"`,\n * `\"app/pages\"`). Detected from the `src/routes` (or `routes`) segment of\n * each key by default; set it only if your routes live somewhere else.\n */\n routesDir?: string;\n}\n\n/** Page files SvelteKit renders: `+page.svelte`, `+page.ts`, `+page@(app).svelte`… */\nconst PAGE_FILE = /^\\+page(@[^.]*)?\\.[^.]+$/;\n\nfunction stripRoutesDir(file: string, routesDir?: string): string | null {\n const path = file.replace(/\\\\/g, \"/\");\n const dirs = routesDir ? [routesDir] : [\"src/routes\", \"routes\"];\n for (const dir of dirs) {\n const needle = `/${dir.replace(/^\\/|\\/$/g, \"\")}/`;\n const at = path.lastIndexOf(needle);\n if (at !== -1) return path.slice(at + needle.length);\n if (path.startsWith(needle.slice(1))) return path.slice(needle.length - 1);\n }\n // Relative glob from inside the routes directory, e.g. \"./dev/+page.svelte\".\n return routesDir ? null : path.replace(/^\\.?\\//, \"\");\n}\n\n/**\n * Turn a SvelteKit page file path into the route it serves, or `null` if it\n * isn't a navigable page.\n *\n * Layout groups (`(app)`) and optional params (`[[lang]]`) drop out of the\n * path; required (`[id]`), matched (`[id=int]`) and rest (`[...rest]`) params\n * make the route unnavigable without arguments, so it's skipped.\n */\nexport function routeFromFile(\n file: string,\n options: Pick<DetectOptions, \"routesDir\"> = {},\n): string | null {\n const relative = stripRoutesDir(file, options.routesDir);\n if (relative == null) return null;\n const segments = relative.split(\"/\");\n const filename = segments.pop();\n if (!filename || !PAGE_FILE.test(filename)) return null;\n const kept: string[] = [];\n for (const segment of segments) {\n if (!segment || segment === \".\") continue;\n // Layout group — organizes files, not URLs.\n if (segment.startsWith(\"(\") && segment.endsWith(\")\")) continue;\n // Optional param: the route is reachable without it.\n if (segment.startsWith(\"[[\") && segment.endsWith(\"]]\")) continue;\n if (segment.includes(\"[\")) return null;\n kept.push(segment);\n }\n return `/${kept.join(\"/\")}`;\n}\n\n/**\n * Flatten a SvelteKit `import.meta.glob` of page files into navigable\n * {@link DevRoute}s.\n *\n * Only dev destinations are returned — anything under `/dev`, by convention.\n * Pass `{ staticRoutes: true }` to list every page instead, and `{ match }`\n * to scope the results. A bare matcher is accepted as shorthand.\n *\n * ```ts\n * const routes = detectRoutes(import.meta.glob(\"/src/routes/**\\/+page.svelte\"));\n * ```\n */\nexport function detectRoutes(\n modules: RouteModules,\n options: DetectOptions | RouteMatcher = {},\n): DevRoute[] {\n const opts: DetectOptions = isMatcher(options) ? { match: options } : options;\n const files = Array.isArray(modules) ? modules : Object.keys(modules);\n const paths: string[] = [];\n for (const file of files) {\n const path = routeFromFile(file, opts);\n if (path != null && (opts.staticRoutes === true || isDevPath(path)))\n paths.push(path);\n }\n const routes = dedupeByPath(\n paths.sort().map((path) => ({ path, label: path })),\n );\n return opts.match ? routes.filter(toPredicate(opts.match)) : routes;\n}\n\nexport interface SvelteKitDevDockOptions extends DevDockOptions, DetectOptions {\n /**\n * Your page files, from `import.meta.glob`. The glob has to be written in\n * your app (Vite resolves it at build time), so pass the result in:\n *\n * ```ts\n * { modules: import.meta.glob(\"/src/routes/**\\/+page.svelte\") }\n * ```\n *\n * Detected routes come first; any `routes` you pass are appended.\n */\n modules?: RouteModules;\n}\n\nfunction resolveOptions(options: SvelteKitDevDockOptions): DevDockOptions {\n const { modules, match, staticRoutes, routesDir, ...rest } = options;\n if (!modules) return rest;\n const detected = detectRoutes(modules, { match, staticRoutes, routesDir });\n return { ...rest, routes: [...detected, ...(rest.routes ?? [])] };\n}\n\n/**\n * Svelte action that mounts a dev dock with the SvelteKit routes detected\n * from `modules`:\n *\n * ```svelte\n * <script lang=\"ts\">\n * import { goto } from \"$app/navigation\";\n * import { devdock } from \"@chuvenger/devdock/sveltekit\";\n *\n * const modules = import.meta.glob(\"/src/routes/**\\/+page.svelte\");\n * </script>\n *\n * <div use:devdock={{ modules, onNavigate: goto, hotkey: \"mod+.\" }}></div>\n * ```\n *\n * Same contract as the plain `devdock` action: the dock mounts into\n * `document.body` (or `options.container`), so the host element is just an\n * anchor, and reactive options flow through `update`.\n */\nexport function devdock(\n _node: HTMLElement,\n options: SvelteKitDevDockOptions = {},\n): DevDockAction {\n const instance = createDevDock(resolveOptions(options));\n return {\n update(next: SvelteKitDevDockOptions = {}) {\n instance.update(resolveOptions(next));\n },\n destroy() {\n instance.destroy();\n },\n };\n}\n\nexport { createDevDock } from \"../core/dock\";\nexport type { DevDockAction } from \"./index\";\nexport type {\n DevDockOptions,\n DevDockInstance,\n DevRoute,\n DevCommand,\n DockPosition,\n} from \"../core/types\";\n"]}
|
|
@@ -29,12 +29,27 @@ interface DevDockOptions {
|
|
|
29
29
|
commands?: DevCommand[];
|
|
30
30
|
/**
|
|
31
31
|
* Whether the dock renders at all. Accepts a boolean or a predicate
|
|
32
|
-
* (re-evaluated on every `update`).
|
|
33
|
-
* `
|
|
32
|
+
* (re-evaluated on every `update`). Takes over the gating completely — both
|
|
33
|
+
* the `NODE_ENV` and the dev-host check are skipped when it's set.
|
|
34
|
+
*
|
|
35
|
+
* When omitted, the dock renders only in development: a dev build
|
|
36
|
+
* (`process.env.NODE_ENV !== "production"`) served from a dev host (see
|
|
37
|
+
* {@link DevDockOptions.devHostOnly}).
|
|
34
38
|
*
|
|
35
39
|
* @example enabled: () => location.search.includes("debug")
|
|
36
40
|
*/
|
|
37
41
|
enabled?: boolean | (() => boolean);
|
|
42
|
+
/**
|
|
43
|
+
* Also require the page to be served from a developer's machine — loopback,
|
|
44
|
+
* an mDNS `.local` name, or a private LAN address. Default: `true`.
|
|
45
|
+
*
|
|
46
|
+
* This catches the case `NODE_ENV` can't: a development build deployed to a
|
|
47
|
+
* real URL (a preview deploy, a staging box) still reports
|
|
48
|
+
* `NODE_ENV !== "production"`. Set `false` to drop the hostname check and
|
|
49
|
+
* gate on the build alone — e.g. to keep the dock on a shared staging
|
|
50
|
+
* environment or behind a dev tunnel. Ignored when `enabled` is set.
|
|
51
|
+
*/
|
|
52
|
+
devHostOnly?: boolean;
|
|
38
53
|
/**
|
|
39
54
|
* How to navigate when a route is selected.
|
|
40
55
|
* Defaults to `window.location.assign(path)`.
|
|
@@ -29,12 +29,27 @@ interface DevDockOptions {
|
|
|
29
29
|
commands?: DevCommand[];
|
|
30
30
|
/**
|
|
31
31
|
* Whether the dock renders at all. Accepts a boolean or a predicate
|
|
32
|
-
* (re-evaluated on every `update`).
|
|
33
|
-
* `
|
|
32
|
+
* (re-evaluated on every `update`). Takes over the gating completely — both
|
|
33
|
+
* the `NODE_ENV` and the dev-host check are skipped when it's set.
|
|
34
|
+
*
|
|
35
|
+
* When omitted, the dock renders only in development: a dev build
|
|
36
|
+
* (`process.env.NODE_ENV !== "production"`) served from a dev host (see
|
|
37
|
+
* {@link DevDockOptions.devHostOnly}).
|
|
34
38
|
*
|
|
35
39
|
* @example enabled: () => location.search.includes("debug")
|
|
36
40
|
*/
|
|
37
41
|
enabled?: boolean | (() => boolean);
|
|
42
|
+
/**
|
|
43
|
+
* Also require the page to be served from a developer's machine — loopback,
|
|
44
|
+
* an mDNS `.local` name, or a private LAN address. Default: `true`.
|
|
45
|
+
*
|
|
46
|
+
* This catches the case `NODE_ENV` can't: a development build deployed to a
|
|
47
|
+
* real URL (a preview deploy, a staging box) still reports
|
|
48
|
+
* `NODE_ENV !== "production"`. Set `false` to drop the hostname check and
|
|
49
|
+
* gate on the build alone — e.g. to keep the dock on a shared staging
|
|
50
|
+
* environment or behind a dev tunnel. Ignored when `enabled` is set.
|
|
51
|
+
*/
|
|
52
|
+
devHostOnly?: boolean;
|
|
38
53
|
/**
|
|
39
54
|
* How to navigate when a route is selected.
|
|
40
55
|
* Defaults to `window.location.assign(path)`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chuvenger/devdock",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A tiny dev-only floating button that opens a popup to jump between dev routes and run custom commands.",
|
|
5
5
|
"author": "Ruven Chu",
|
|
6
6
|
"repository": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"react",
|
|
23
23
|
"svelte",
|
|
24
24
|
"react-router",
|
|
25
|
+
"sveltekit",
|
|
25
26
|
"navigation",
|
|
26
27
|
"command-palette"
|
|
27
28
|
],
|
|
@@ -51,6 +52,11 @@
|
|
|
51
52
|
"types": "./dist/svelte.d.ts",
|
|
52
53
|
"import": "./dist/svelte.js",
|
|
53
54
|
"require": "./dist/svelte.cjs"
|
|
55
|
+
},
|
|
56
|
+
"./sveltekit": {
|
|
57
|
+
"types": "./dist/sveltekit.d.ts",
|
|
58
|
+
"import": "./dist/sveltekit.js",
|
|
59
|
+
"require": "./dist/sveltekit.cjs"
|
|
54
60
|
}
|
|
55
61
|
},
|
|
56
62
|
"main": "./dist/index.cjs",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/hotkey.ts","../src/core/dock.ts"],"names":[],"mappings":"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","file":"chunk-DUHEBT6Q.js","sourcesContent":["/** Parse a hotkey string like \"mod+.\" into a matcher predicate. */\nexport function matchHotkey(spec: string, e: KeyboardEvent): boolean {\n const parts = spec\n .toLowerCase()\n .split(\"+\")\n .map((p) => p.trim())\n .filter(Boolean);\n if (parts.length === 0) return false;\n\n const key = parts[parts.length - 1];\n const mods = new Set(parts.slice(0, -1));\n\n const isMac =\n typeof navigator !== \"undefined\" && /mac/i.test(navigator.platform);\n\n const wantMeta = mods.has(\"meta\") || mods.has(\"cmd\") || (mods.has(\"mod\") && isMac);\n const wantCtrl = mods.has(\"ctrl\") || mods.has(\"control\") || (mods.has(\"mod\") && !isMac);\n const wantShift = mods.has(\"shift\");\n const wantAlt = mods.has(\"alt\") || mods.has(\"option\");\n\n if (e.metaKey !== wantMeta) return false;\n if (e.ctrlKey !== wantCtrl) return false;\n if (e.shiftKey !== wantShift) return false;\n if (e.altKey !== wantAlt) return false;\n\n return e.key.toLowerCase() === key;\n}\n","import { matchHotkey } from \"./hotkey\";\nimport type {\n DevCommand,\n DevDockInstance,\n DevDockOptions,\n DevRoute,\n DockPosition,\n} from \"./types\";\n\nconst isDev = (): boolean => {\n // Bundlers (Vite, webpack, Next, esbuild…) statically replace the full\n // `process.env.NODE_ENV` token, even in browser builds where `process`\n // itself is undefined. Reading it directly lets that replacement work;\n // the try/catch covers runtimes where it's left as a real (missing) global.\n try {\n return process.env.NODE_ENV !== \"production\";\n } catch {\n return false;\n }\n};\n\ntype Item =\n | { kind: \"route\"; key: string; label: string; group: string; route: DevRoute }\n | {\n kind: \"command\";\n key: string;\n label: string;\n group: string;\n command: DevCommand;\n };\n\ntype Styles = Partial<CSSStyleDeclaration>;\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n styles?: Styles,\n props?: Partial<HTMLElementTagNameMap[K]>,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (styles) Object.assign(node.style, styles);\n if (props) Object.assign(node, props);\n return node;\n}\n\nfunction cornerStyle(position: DockPosition): Styles {\n const gap = \"16px\";\n switch (position) {\n case \"bottom-left\":\n return { bottom: gap, left: gap };\n case \"top-left\":\n return { top: gap, left: gap };\n case \"top-right\":\n return { top: gap, right: gap };\n default:\n return { bottom: gap, right: gap };\n }\n}\n\nfunction panelAnchor(position: DockPosition): Styles {\n const isTop = position.startsWith(\"top\");\n const isRight = position.endsWith(\"right\");\n return {\n position: \"absolute\",\n [isTop ? \"top\" : \"bottom\"]: \"calc(100% + 8px)\",\n [isRight ? \"right\" : \"left\"]: \"0\",\n } as Styles;\n}\n\nconst FONT =\n \"ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif\";\n\n/**\n * Create a framework-agnostic dev dock: a floating button that opens a popup\n * to jump between routes and run commands. Returns a handle to update its\n * options or tear it down. No-ops on the server (when there is no `document`).\n */\nexport function createDevDock(initial: DevDockOptions = {}): DevDockInstance {\n if (typeof document === \"undefined\") {\n return { update() {}, destroy() {} };\n }\n\n let opts: DevDockOptions = { ...initial };\n let open = false;\n let query = \"\";\n let active = 0;\n let mounted = false;\n\n // Light-DOM host that gets appended to the page. When shadow DOM is used,\n // the actual UI lives inside its shadow root — fully isolated from the host\n // page's CSS. `data-devdock` stays on the host so it's discoverable.\n const host = el(\"div\");\n host.setAttribute(\"data-devdock\", \"\");\n const useShadow =\n (initial.shadow ?? true) && typeof host.attachShadow === \"function\";\n const shadowRoot = useShadow ? host.attachShadow({ mode: \"open\" }) : null;\n const mountPoint: Node = shadowRoot ?? host;\n\n const root = el(\"div\", { fontFamily: FONT });\n mountPoint.appendChild(root);\n\n const panel = el(\"div\", {\n width: \"280px\",\n maxHeight: \"min(70vh, 480px)\",\n display: \"flex\",\n flexDirection: \"column\",\n background: \"#16181d\",\n color: \"#e7e9ee\",\n border: \"1px solid #2a2e37\",\n borderRadius: \"12px\",\n boxShadow: \"0 12px 40px rgba(0,0,0,0.45)\",\n overflow: \"hidden\",\n });\n panel.setAttribute(\"role\", \"dialog\");\n\n const heading = el(\"div\", {\n padding: \"10px 12px\",\n borderBottom: \"1px solid #2a2e37\",\n fontSize: \"12px\",\n fontWeight: \"600\",\n letterSpacing: \"0.4px\",\n textTransform: \"uppercase\",\n color: \"#8b909c\",\n });\n\n const inputWrap = el(\"div\", { padding: \"8px\" });\n const input = el(\"input\", {\n width: \"100%\",\n boxSizing: \"border-box\",\n padding: \"8px 10px\",\n fontSize: \"13px\",\n color: \"#e7e9ee\",\n background: \"#0f1115\",\n border: \"1px solid #2a2e37\",\n borderRadius: \"8px\",\n outline: \"none\",\n });\n input.placeholder = \"Filter…\";\n inputWrap.appendChild(input);\n\n const list = el(\"div\", { overflowY: \"auto\", padding: \"0 6px 8px\" });\n\n panel.append(heading, inputWrap, list);\n\n const button = el(\"button\", {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"6px\",\n padding: \"8px 12px\",\n fontSize: \"12px\",\n fontWeight: \"700\",\n letterSpacing: \"0.6px\",\n color: \"#fff\",\n background: \"#111317\",\n border: \"1px solid #2a2e37\",\n borderRadius: \"999px\",\n boxShadow: \"0 4px 14px rgba(0,0,0,0.35)\",\n cursor: \"pointer\",\n });\n button.type = \"button\";\n button.setAttribute(\"aria-label\", \"Toggle dev menu\");\n const dot = el(\"span\", {\n width: \"8px\",\n height: \"8px\",\n borderRadius: \"999px\",\n background: \"#22c55e\",\n display: \"inline-block\",\n });\n const buttonLabel = el(\"span\");\n button.append(dot, buttonLabel);\n\n root.appendChild(button);\n\n // ---- behavior ---------------------------------------------------------\n\n const enabled = (): boolean => {\n const e = opts.enabled;\n if (typeof e === \"function\") return e();\n return e ?? isDev();\n };\n\n const navigate = (path: string) => {\n if (opts.onNavigate) opts.onNavigate(path);\n else if (typeof window !== \"undefined\") window.location.assign(path);\n };\n\n const buildItems = (): Item[] => {\n const out: Item[] = [];\n (opts.routes ?? []).forEach((r, i) => {\n out.push({\n kind: \"route\",\n key: `route:${r.path}:${i}`,\n label: r.label ?? r.path,\n group: r.group ?? \"Routes\",\n route: r,\n });\n });\n (opts.commands ?? []).forEach((c, i) => {\n out.push({\n kind: \"command\",\n key: `cmd:${c.id ?? c.label}:${i}`,\n label: c.label,\n group: c.group ?? \"Commands\",\n command: c,\n });\n });\n return out;\n };\n\n const getFiltered = (): Item[] => {\n const q = query.trim().toLowerCase();\n const items = buildItems();\n if (!q) return items;\n return items.filter(\n (it) =>\n it.label.toLowerCase().includes(q) ||\n (it.kind === \"route\" && it.route.path.toLowerCase().includes(q)),\n );\n };\n\n const runItem = async (it: Item) => {\n if (it.kind === \"route\") {\n navigate(it.route.path);\n setOpen(false);\n return;\n }\n try {\n await it.command.run();\n } finally {\n if (!it.command.keepOpen) setOpen(false);\n }\n };\n\n type Row = {\n item: Item;\n node: HTMLButtonElement;\n pathSpan: HTMLSpanElement | null;\n };\n\n // The rendered rows, parallel to the current filtered list. They are kept\n // around so hovering or arrowing only repaints the highlight: rebuilding the\n // rows would destroy the node under the pointer, and the browser then never\n // dispatches a `click` on it (mousedown/mouseup retarget to the parent).\n let rows: Row[] = [];\n // Signature of the item list the rows were built from; identical signature\n // means the existing nodes can be reused as-is.\n let rowsSig: string | null = null;\n\n const itemSig = (it: Item): string =>\n [it.key, it.group, it.label, it.kind === \"route\" ? it.route.path : \"\"].join(\n \"\\u0000\",\n );\n\n const paintActive = () => {\n rows.forEach((row, idx) => {\n const isActive = idx === active;\n row.node.style.background = isActive ? \"#2563eb\" : \"transparent\";\n if (row.pathSpan)\n row.pathSpan.style.color = isActive ? \"#cfe0ff\" : \"#6b7280\";\n });\n };\n\n const setActive = (next: number, scroll = false) => {\n if (next === active || next < 0 || next >= rows.length) return;\n active = next;\n paintActive();\n // `scrollIntoView` is absent in jsdom, hence the optional call.\n if (scroll) rows[active]?.node.scrollIntoView?.({ block: \"nearest\" });\n };\n\n const renderList = () => {\n const filtered = getFiltered();\n active = Math.min(active, Math.max(0, filtered.length - 1));\n\n const sig = filtered.map(itemSig).join(\"\\n\");\n if (sig === rowsSig) {\n // Same items in the same order: keep the nodes, refresh the highlight.\n // The items themselves are rebuilt on every call, so re-point each row\n // at its current one (its `run`/`route` may have been swapped by\n // `update()`).\n filtered.forEach((it, idx) => {\n const row = rows[idx];\n if (row) row.item = it;\n });\n paintActive();\n return;\n }\n rowsSig = sig;\n rows = [];\n list.replaceChildren();\n\n if (filtered.length === 0) {\n list.appendChild(\n el(\n \"div\",\n { padding: \"12px\", fontSize: \"13px\", color: \"#6b7280\" },\n { textContent: \"Nothing here.\" },\n ),\n );\n return;\n }\n\n const groups = new Map<string, Item[]>();\n for (const it of filtered) {\n const arr = groups.get(it.group) ?? [];\n arr.push(it);\n groups.set(it.group, arr);\n }\n\n for (const [groupName, groupItems] of groups) {\n const section = el(\"div\", { marginTop: \"6px\" });\n section.appendChild(\n el(\n \"div\",\n {\n padding: \"6px 8px 2px\",\n fontSize: \"10px\",\n fontWeight: \"700\",\n letterSpacing: \"0.6px\",\n textTransform: \"uppercase\",\n color: \"#6b7280\",\n },\n { textContent: groupName },\n ),\n );\n\n for (const it of groupItems) {\n const idx = filtered.indexOf(it);\n const row = el(\"button\", {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: \"8px\",\n width: \"100%\",\n textAlign: \"left\",\n padding: \"8px 10px\",\n fontSize: \"13px\",\n color: \"#e7e9ee\",\n background: \"transparent\",\n border: \"none\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n });\n row.type = \"button\";\n\n const labelSpan = el(\n \"span\",\n {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n },\n { textContent: it.label },\n );\n row.appendChild(labelSpan);\n\n let pathSpan: HTMLSpanElement | null = null;\n if (it.kind === \"route\" && it.route.path !== it.label) {\n pathSpan = el(\n \"span\",\n {\n fontSize: \"11px\",\n color: \"#6b7280\",\n fontFamily: \"ui-monospace, SFMono-Regular, monospace\",\n flexShrink: \"0\",\n },\n { textContent: it.route.path },\n );\n row.appendChild(pathSpan);\n }\n\n const rec: Row = { item: it, node: row, pathSpan };\n rows[idx] = rec;\n\n row.addEventListener(\"mouseenter\", () => setActive(idx));\n row.addEventListener(\"click\", () => void runItem(rec.item));\n section.appendChild(row);\n }\n list.appendChild(section);\n }\n\n paintActive();\n };\n\n const renderShell = () => {\n const position = opts.position ?? \"bottom-right\";\n const zIndex = opts.zIndex ?? 2147483000;\n\n Object.assign(root.style, {\n position: \"fixed\",\n zIndex: String(zIndex),\n top: \"\",\n bottom: \"\",\n left: \"\",\n right: \"\",\n });\n Object.assign(root.style, cornerStyle(position));\n\n heading.textContent = opts.title ?? \"Dev Menu\";\n panel.setAttribute(\"aria-label\", opts.title ?? \"Dev Menu\");\n Object.assign(panel.style, panelAnchor(position));\n\n buttonLabel.textContent = opts.label ?? \"DEV\";\n button.style.background = open ? \"#2563eb\" : \"#111317\";\n button.setAttribute(\"aria-expanded\", String(open));\n };\n\n function setOpen(next: boolean) {\n if (open === next) return;\n open = next;\n if (open) {\n query = \"\";\n input.value = \"\";\n active = 0;\n panel.isConnected || root.insertBefore(panel, button);\n renderShell();\n renderList();\n requestAnimationFrame(() => input.focus());\n } else if (panel.isConnected) {\n panel.remove();\n }\n renderShell();\n }\n\n // ---- listeners --------------------------------------------------------\n\n input.addEventListener(\"input\", () => {\n query = input.value;\n active = 0;\n renderList();\n });\n\n panel.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActive(Math.min(active + 1, rows.length - 1), true);\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActive(Math.max(active - 1, 0), true);\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const it = rows[active]?.item;\n if (it) void runItem(it);\n }\n });\n\n button.addEventListener(\"click\", () => setOpen(!open));\n\n const onDocMouseDown = (e: MouseEvent) => {\n // composedPath() sees through the shadow boundary; a plain\n // `contains(e.target)` would miss clicks inside the shadow root.\n if (open && !e.composedPath().includes(root)) setOpen(false);\n };\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (opts.hotkey && matchHotkey(opts.hotkey, e)) {\n e.preventDefault();\n setOpen(!open);\n }\n };\n\n const addListeners = () => {\n document.addEventListener(\"mousedown\", onDocMouseDown);\n window.addEventListener(\"keydown\", onKeyDown);\n };\n const removeListeners = () => {\n document.removeEventListener(\"mousedown\", onDocMouseDown);\n window.removeEventListener(\"keydown\", onKeyDown);\n };\n\n const mount = () => {\n if (mounted) return;\n (opts.container ?? document.body).appendChild(host);\n addListeners();\n mounted = true;\n };\n const unmount = () => {\n if (!mounted) return;\n removeListeners();\n host.remove();\n mounted = false;\n };\n\n // ---- init -------------------------------------------------------------\n\n renderShell();\n if (enabled()) mount();\n\n return {\n update(next: DevDockOptions) {\n opts = { ...opts, ...next };\n if (enabled()) {\n mount();\n renderShell();\n if (open) renderList();\n } else {\n setOpen(false);\n unmount();\n }\n },\n destroy() {\n setOpen(false);\n unmount();\n },\n };\n}\n"]}
|