@chuvenger/devdock 0.1.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.
@@ -0,0 +1,407 @@
1
+ 'use strict';
2
+
3
+ // src/core/hotkey.ts
4
+ function matchHotkey(spec, e) {
5
+ const parts = spec.toLowerCase().split("+").map((p) => p.trim()).filter(Boolean);
6
+ if (parts.length === 0) return false;
7
+ const key = parts[parts.length - 1];
8
+ const mods = new Set(parts.slice(0, -1));
9
+ const isMac = typeof navigator !== "undefined" && /mac/i.test(navigator.platform);
10
+ const wantMeta = mods.has("meta") || mods.has("cmd") || mods.has("mod") && isMac;
11
+ const wantCtrl = mods.has("ctrl") || mods.has("control") || mods.has("mod") && !isMac;
12
+ const wantShift = mods.has("shift");
13
+ const wantAlt = mods.has("alt") || mods.has("option");
14
+ if (e.metaKey !== wantMeta) return false;
15
+ if (e.ctrlKey !== wantCtrl) return false;
16
+ if (e.shiftKey !== wantShift) return false;
17
+ if (e.altKey !== wantAlt) return false;
18
+ return e.key.toLowerCase() === key;
19
+ }
20
+
21
+ // src/core/dock.ts
22
+ var isDev = () => {
23
+ try {
24
+ return process.env.NODE_ENV !== "production";
25
+ } catch {
26
+ return false;
27
+ }
28
+ };
29
+ function el(tag, styles, props) {
30
+ const node = document.createElement(tag);
31
+ if (styles) Object.assign(node.style, styles);
32
+ if (props) Object.assign(node, props);
33
+ return node;
34
+ }
35
+ function cornerStyle(position) {
36
+ const gap = "16px";
37
+ switch (position) {
38
+ case "bottom-right":
39
+ return { bottom: gap, right: gap };
40
+ case "top-left":
41
+ return { top: gap, left: gap };
42
+ case "top-right":
43
+ return { top: gap, right: gap };
44
+ default:
45
+ return { bottom: gap, left: gap };
46
+ }
47
+ }
48
+ function panelAnchor(position) {
49
+ const isTop = position.startsWith("top");
50
+ const isRight = position.endsWith("right");
51
+ return {
52
+ position: "absolute",
53
+ [isTop ? "top" : "bottom"]: "calc(100% + 8px)",
54
+ [isRight ? "right" : "left"]: "0"
55
+ };
56
+ }
57
+ var FONT = "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif";
58
+ function createDevDock(initial = {}) {
59
+ if (typeof document === "undefined") {
60
+ return { update() {
61
+ }, destroy() {
62
+ } };
63
+ }
64
+ let opts = { ...initial };
65
+ let open = false;
66
+ let query = "";
67
+ let active = 0;
68
+ let mounted = false;
69
+ const host = el("div");
70
+ host.setAttribute("data-devdock", "");
71
+ const useShadow = (initial.shadow ?? true) && typeof host.attachShadow === "function";
72
+ const shadowRoot = useShadow ? host.attachShadow({ mode: "open" }) : null;
73
+ const mountPoint = shadowRoot ?? host;
74
+ const root = el("div", { fontFamily: FONT });
75
+ mountPoint.appendChild(root);
76
+ const panel = el("div", {
77
+ width: "280px",
78
+ maxHeight: "min(70vh, 480px)",
79
+ display: "flex",
80
+ flexDirection: "column",
81
+ background: "#16181d",
82
+ color: "#e7e9ee",
83
+ border: "1px solid #2a2e37",
84
+ borderRadius: "12px",
85
+ boxShadow: "0 12px 40px rgba(0,0,0,0.45)",
86
+ overflow: "hidden"
87
+ });
88
+ panel.setAttribute("role", "dialog");
89
+ const heading = el("div", {
90
+ padding: "10px 12px",
91
+ borderBottom: "1px solid #2a2e37",
92
+ fontSize: "12px",
93
+ fontWeight: "600",
94
+ letterSpacing: "0.4px",
95
+ textTransform: "uppercase",
96
+ color: "#8b909c"
97
+ });
98
+ const inputWrap = el("div", { padding: "8px" });
99
+ const input = el("input", {
100
+ width: "100%",
101
+ boxSizing: "border-box",
102
+ padding: "8px 10px",
103
+ fontSize: "13px",
104
+ color: "#e7e9ee",
105
+ background: "#0f1115",
106
+ border: "1px solid #2a2e37",
107
+ borderRadius: "8px",
108
+ outline: "none"
109
+ });
110
+ input.placeholder = "Filter\u2026";
111
+ inputWrap.appendChild(input);
112
+ const list = el("div", { overflowY: "auto", padding: "0 6px 8px" });
113
+ panel.append(heading, inputWrap, list);
114
+ const button = el("button", {
115
+ display: "inline-flex",
116
+ alignItems: "center",
117
+ gap: "6px",
118
+ padding: "8px 12px",
119
+ fontSize: "12px",
120
+ fontWeight: "700",
121
+ letterSpacing: "0.6px",
122
+ color: "#fff",
123
+ background: "#111317",
124
+ border: "1px solid #2a2e37",
125
+ borderRadius: "999px",
126
+ boxShadow: "0 4px 14px rgba(0,0,0,0.35)",
127
+ cursor: "pointer"
128
+ });
129
+ button.type = "button";
130
+ button.setAttribute("aria-label", "Toggle dev menu");
131
+ const dot = el("span", {
132
+ width: "8px",
133
+ height: "8px",
134
+ borderRadius: "999px",
135
+ background: "#22c55e",
136
+ display: "inline-block"
137
+ });
138
+ const buttonLabel = el("span");
139
+ button.append(dot, buttonLabel);
140
+ root.appendChild(button);
141
+ const enabled = () => {
142
+ const e = opts.enabled;
143
+ if (typeof e === "function") return e();
144
+ return e ?? isDev();
145
+ };
146
+ const navigate = (path) => {
147
+ if (opts.onNavigate) opts.onNavigate(path);
148
+ else if (typeof window !== "undefined") window.location.assign(path);
149
+ };
150
+ const buildItems = () => {
151
+ const out = [];
152
+ (opts.routes ?? []).forEach((r, i) => {
153
+ out.push({
154
+ kind: "route",
155
+ key: `route:${r.path}:${i}`,
156
+ label: r.label ?? r.path,
157
+ group: r.group ?? "Routes",
158
+ route: r
159
+ });
160
+ });
161
+ (opts.commands ?? []).forEach((c, i) => {
162
+ out.push({
163
+ kind: "command",
164
+ key: `cmd:${c.id ?? c.label}:${i}`,
165
+ label: c.label,
166
+ group: c.group ?? "Commands",
167
+ command: c
168
+ });
169
+ });
170
+ return out;
171
+ };
172
+ const getFiltered = () => {
173
+ const q = query.trim().toLowerCase();
174
+ const items = buildItems();
175
+ if (!q) return items;
176
+ return items.filter(
177
+ (it) => it.label.toLowerCase().includes(q) || it.kind === "route" && it.route.path.toLowerCase().includes(q)
178
+ );
179
+ };
180
+ const runItem = async (it) => {
181
+ if (it.kind === "route") {
182
+ navigate(it.route.path);
183
+ setOpen(false);
184
+ return;
185
+ }
186
+ try {
187
+ await it.command.run();
188
+ } finally {
189
+ if (!it.command.keepOpen) setOpen(false);
190
+ }
191
+ };
192
+ const renderList = () => {
193
+ const filtered = getFiltered();
194
+ active = Math.min(active, Math.max(0, filtered.length - 1));
195
+ list.replaceChildren();
196
+ if (filtered.length === 0) {
197
+ list.appendChild(
198
+ el(
199
+ "div",
200
+ { padding: "12px", fontSize: "13px", color: "#6b7280" },
201
+ { textContent: "Nothing here." }
202
+ )
203
+ );
204
+ return;
205
+ }
206
+ const groups = /* @__PURE__ */ new Map();
207
+ for (const it of filtered) {
208
+ const arr = groups.get(it.group) ?? [];
209
+ arr.push(it);
210
+ groups.set(it.group, arr);
211
+ }
212
+ for (const [groupName, groupItems] of groups) {
213
+ const section = el("div", { marginTop: "6px" });
214
+ section.appendChild(
215
+ el(
216
+ "div",
217
+ {
218
+ padding: "6px 8px 2px",
219
+ fontSize: "10px",
220
+ fontWeight: "700",
221
+ letterSpacing: "0.6px",
222
+ textTransform: "uppercase",
223
+ color: "#6b7280"
224
+ },
225
+ { textContent: groupName }
226
+ )
227
+ );
228
+ for (const it of groupItems) {
229
+ const idx = filtered.indexOf(it);
230
+ const isActive = idx === active;
231
+ const row = el("button", {
232
+ display: "flex",
233
+ alignItems: "center",
234
+ justifyContent: "space-between",
235
+ gap: "8px",
236
+ width: "100%",
237
+ textAlign: "left",
238
+ padding: "8px 10px",
239
+ fontSize: "13px",
240
+ color: "#e7e9ee",
241
+ background: isActive ? "#2563eb" : "transparent",
242
+ border: "none",
243
+ borderRadius: "8px",
244
+ cursor: "pointer"
245
+ });
246
+ row.type = "button";
247
+ const labelSpan = el(
248
+ "span",
249
+ {
250
+ overflow: "hidden",
251
+ textOverflow: "ellipsis",
252
+ whiteSpace: "nowrap"
253
+ },
254
+ { textContent: it.label }
255
+ );
256
+ row.appendChild(labelSpan);
257
+ if (it.kind === "route" && it.route.path !== it.label) {
258
+ row.appendChild(
259
+ el(
260
+ "span",
261
+ {
262
+ fontSize: "11px",
263
+ color: isActive ? "#cfe0ff" : "#6b7280",
264
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
265
+ flexShrink: "0"
266
+ },
267
+ { textContent: it.route.path }
268
+ )
269
+ );
270
+ }
271
+ row.addEventListener("mouseenter", () => {
272
+ active = idx;
273
+ renderList();
274
+ });
275
+ row.addEventListener("click", () => void runItem(it));
276
+ section.appendChild(row);
277
+ }
278
+ list.appendChild(section);
279
+ }
280
+ };
281
+ const renderShell = () => {
282
+ const position = opts.position ?? "bottom-left";
283
+ const zIndex = opts.zIndex ?? 2147483e3;
284
+ Object.assign(root.style, {
285
+ position: "fixed",
286
+ zIndex: String(zIndex),
287
+ top: "",
288
+ bottom: "",
289
+ left: "",
290
+ right: ""
291
+ });
292
+ Object.assign(root.style, cornerStyle(position));
293
+ heading.textContent = opts.title ?? "Dev Menu";
294
+ panel.setAttribute("aria-label", opts.title ?? "Dev Menu");
295
+ Object.assign(panel.style, panelAnchor(position));
296
+ buttonLabel.textContent = opts.label ?? "DEV";
297
+ button.style.background = open ? "#2563eb" : "#111317";
298
+ button.setAttribute("aria-expanded", String(open));
299
+ };
300
+ function setOpen(next) {
301
+ if (open === next) return;
302
+ open = next;
303
+ if (open) {
304
+ query = "";
305
+ input.value = "";
306
+ active = 0;
307
+ panel.isConnected || root.insertBefore(panel, button);
308
+ renderShell();
309
+ renderList();
310
+ requestAnimationFrame(() => input.focus());
311
+ } else if (panel.isConnected) {
312
+ panel.remove();
313
+ }
314
+ renderShell();
315
+ }
316
+ input.addEventListener("input", () => {
317
+ query = input.value;
318
+ active = 0;
319
+ renderList();
320
+ });
321
+ panel.addEventListener("keydown", (e) => {
322
+ const filtered = getFiltered();
323
+ if (e.key === "Escape") {
324
+ e.preventDefault();
325
+ setOpen(false);
326
+ } else if (e.key === "ArrowDown") {
327
+ e.preventDefault();
328
+ active = Math.min(active + 1, filtered.length - 1);
329
+ renderList();
330
+ } else if (e.key === "ArrowUp") {
331
+ e.preventDefault();
332
+ active = Math.max(active - 1, 0);
333
+ renderList();
334
+ } else if (e.key === "Enter") {
335
+ e.preventDefault();
336
+ const it = filtered[active];
337
+ if (it) void runItem(it);
338
+ }
339
+ });
340
+ button.addEventListener("click", () => setOpen(!open));
341
+ const onDocMouseDown = (e) => {
342
+ if (open && !e.composedPath().includes(root)) setOpen(false);
343
+ };
344
+ const onKeyDown = (e) => {
345
+ if (opts.hotkey && matchHotkey(opts.hotkey, e)) {
346
+ e.preventDefault();
347
+ setOpen(!open);
348
+ }
349
+ };
350
+ const addListeners = () => {
351
+ document.addEventListener("mousedown", onDocMouseDown);
352
+ window.addEventListener("keydown", onKeyDown);
353
+ };
354
+ const removeListeners = () => {
355
+ document.removeEventListener("mousedown", onDocMouseDown);
356
+ window.removeEventListener("keydown", onKeyDown);
357
+ };
358
+ const mount = () => {
359
+ if (mounted) return;
360
+ (opts.container ?? document.body).appendChild(host);
361
+ addListeners();
362
+ mounted = true;
363
+ };
364
+ const unmount = () => {
365
+ if (!mounted) return;
366
+ removeListeners();
367
+ host.remove();
368
+ mounted = false;
369
+ };
370
+ renderShell();
371
+ if (enabled()) mount();
372
+ return {
373
+ update(next) {
374
+ opts = { ...opts, ...next };
375
+ if (enabled()) {
376
+ mount();
377
+ renderShell();
378
+ if (open) renderList();
379
+ } else {
380
+ setOpen(false);
381
+ unmount();
382
+ }
383
+ },
384
+ destroy() {
385
+ setOpen(false);
386
+ unmount();
387
+ }
388
+ };
389
+ }
390
+
391
+ // src/svelte/index.ts
392
+ function devdock(_node, options = {}) {
393
+ const instance = createDevDock(options);
394
+ return {
395
+ update(next = {}) {
396
+ instance.update(next);
397
+ },
398
+ destroy() {
399
+ instance.destroy();
400
+ }
401
+ };
402
+ }
403
+
404
+ exports.createDevDock = createDevDock;
405
+ exports.devdock = devdock;
406
+ //# sourceMappingURL=svelte.cjs.map
407
+ //# sourceMappingURL=svelte.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/hotkey.ts","../src/core/dock.ts","../src/svelte/index.ts"],"names":[],"mappings":";;;AACO,SAAS,WAAA,CAAY,MAAc,CAAA,EAA2B;AACnE,EAAA,MAAM,KAAA,GAAQ,IAAA,CACX,WAAA,EAAY,CACZ,MAAM,GAAG,CAAA,CACT,GAAA,CAAI,CAAC,MAAM,CAAA,CAAE,IAAA,EAAM,CAAA,CACnB,OAAO,OAAO,CAAA;AACjB,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA;AAE/B,EAAA,MAAM,GAAA,GAAM,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AAClC,EAAA,MAAM,OAAO,IAAI,GAAA,CAAI,MAAM,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA;AAEvC,EAAA,MAAM,QACJ,OAAO,SAAA,KAAc,eAAe,MAAA,CAAO,IAAA,CAAK,UAAU,QAAQ,CAAA;AAEpE,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,IAAK,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,IAAM,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,IAAK,KAAA;AAC5E,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,IAAK,IAAA,CAAK,GAAA,CAAI,SAAS,CAAA,IAAM,IAAA,CAAK,GAAA,CAAI,KAAK,KAAK,CAAC,KAAA;AACjF,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,OAAO,CAAA;AAClC,EAAA,MAAM,UAAU,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,IAAK,IAAA,CAAK,IAAI,QAAQ,CAAA;AAEpD,EAAA,IAAI,CAAA,CAAE,OAAA,KAAY,QAAA,EAAU,OAAO,KAAA;AACnC,EAAA,IAAI,CAAA,CAAE,OAAA,KAAY,QAAA,EAAU,OAAO,KAAA;AACnC,EAAA,IAAI,CAAA,CAAE,QAAA,KAAa,SAAA,EAAW,OAAO,KAAA;AACrC,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,OAAA,EAAS,OAAO,KAAA;AAEjC,EAAA,OAAO,CAAA,CAAE,GAAA,CAAI,WAAA,EAAY,KAAM,GAAA;AACjC;;;ACjBA,IAAM,QAAQ,MAAe;AAK3B,EAAA,IAAI;AACF,IAAA,OAAO,OAAA,CAAQ,IAAI,QAAA,KAAa,YAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF,CAAA;AAcA,SAAS,EAAA,CACP,GAAA,EACA,MAAA,EACA,KAAA,EAC0B;AAC1B,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACvC,EAAA,IAAI,MAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,OAAO,MAAM,CAAA;AAC5C,EAAA,IAAI,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,KAAK,CAAA;AACpC,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,YAAY,QAAA,EAAgC;AACnD,EAAA,MAAM,GAAA,GAAM,MAAA;AACZ,EAAA,QAAQ,QAAA;AAAU,IAChB,KAAK,cAAA;AACH,MAAA,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,KAAA,EAAO,GAAA,EAAI;AAAA,IACnC,KAAK,UAAA;AACH,MAAA,OAAO,EAAE,GAAA,EAAK,GAAA,EAAK,IAAA,EAAM,GAAA,EAAI;AAAA,IAC/B,KAAK,WAAA;AACH,MAAA,OAAO,EAAE,GAAA,EAAK,GAAA,EAAK,KAAA,EAAO,GAAA,EAAI;AAAA,IAChC;AACE,MAAA,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,IAAA,EAAM,GAAA,EAAI;AAAA;AAEtC;AAEA,SAAS,YAAY,QAAA,EAAgC;AACnD,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,UAAA,CAAW,KAAK,CAAA;AACvC,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,QAAA,CAAS,OAAO,CAAA;AACzC,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,UAAA;AAAA,IACV,CAAC,KAAA,GAAQ,KAAA,GAAQ,QAAQ,GAAG,kBAAA;AAAA,IAC5B,CAAC,OAAA,GAAU,OAAA,GAAU,MAAM,GAAG;AAAA,GAChC;AACF;AAEA,IAAM,IAAA,GACJ,uEAAA;AAOK,SAAS,aAAA,CAAc,OAAA,GAA0B,EAAC,EAAoB;AAC3E,EAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACnC,IAAA,OAAO,EAAE,MAAA,GAAS;AAAA,IAAC,GAAG,OAAA,GAAU;AAAA,IAAC,CAAA,EAAE;AAAA,EACrC;AAEA,EAAA,IAAI,IAAA,GAAuB,EAAE,GAAG,OAAA,EAAQ;AACxC,EAAA,IAAI,IAAA,GAAO,KAAA;AACX,EAAA,IAAI,KAAA,GAAQ,EAAA;AACZ,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,IAAI,OAAA,GAAU,KAAA;AAKd,EAAA,MAAM,IAAA,GAAO,GAAG,KAAK,CAAA;AACrB,EAAA,IAAA,CAAK,YAAA,CAAa,gBAAgB,EAAE,CAAA;AACpC,EAAA,MAAM,aACH,OAAA,CAAQ,MAAA,IAAU,IAAA,KAAS,OAAO,KAAK,YAAA,KAAiB,UAAA;AAC3D,EAAA,MAAM,UAAA,GAAa,YAAY,IAAA,CAAK,YAAA,CAAa,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA,GAAI,IAAA;AACrE,EAAA,MAAM,aAAmB,UAAA,IAAc,IAAA;AAEvC,EAAA,MAAM,OAAO,EAAA,CAAG,KAAA,EAAO,EAAE,UAAA,EAAY,MAAM,CAAA;AAC3C,EAAA,UAAA,CAAW,YAAY,IAAI,CAAA;AAE3B,EAAA,MAAM,KAAA,GAAQ,GAAG,KAAA,EAAO;AAAA,IACtB,KAAA,EAAO,OAAA;AAAA,IACP,SAAA,EAAW,kBAAA;AAAA,IACX,OAAA,EAAS,MAAA;AAAA,IACT,aAAA,EAAe,QAAA;AAAA,IACf,UAAA,EAAY,SAAA;AAAA,IACZ,KAAA,EAAO,SAAA;AAAA,IACP,MAAA,EAAQ,mBAAA;AAAA,IACR,YAAA,EAAc,MAAA;AAAA,IACd,SAAA,EAAW,8BAAA;AAAA,IACX,QAAA,EAAU;AAAA,GACX,CAAA;AACD,EAAA,KAAA,CAAM,YAAA,CAAa,QAAQ,QAAQ,CAAA;AAEnC,EAAA,MAAM,OAAA,GAAU,GAAG,KAAA,EAAO;AAAA,IACxB,OAAA,EAAS,WAAA;AAAA,IACT,YAAA,EAAc,mBAAA;AAAA,IACd,QAAA,EAAU,MAAA;AAAA,IACV,UAAA,EAAY,KAAA;AAAA,IACZ,aAAA,EAAe,OAAA;AAAA,IACf,aAAA,EAAe,WAAA;AAAA,IACf,KAAA,EAAO;AAAA,GACR,CAAA;AAED,EAAA,MAAM,YAAY,EAAA,CAAG,KAAA,EAAO,EAAE,OAAA,EAAS,OAAO,CAAA;AAC9C,EAAA,MAAM,KAAA,GAAQ,GAAG,OAAA,EAAS;AAAA,IACxB,KAAA,EAAO,MAAA;AAAA,IACP,SAAA,EAAW,YAAA;AAAA,IACX,OAAA,EAAS,UAAA;AAAA,IACT,QAAA,EAAU,MAAA;AAAA,IACV,KAAA,EAAO,SAAA;AAAA,IACP,UAAA,EAAY,SAAA;AAAA,IACZ,MAAA,EAAQ,mBAAA;AAAA,IACR,YAAA,EAAc,KAAA;AAAA,IACd,OAAA,EAAS;AAAA,GACV,CAAA;AACD,EAAA,KAAA,CAAM,WAAA,GAAc,cAAA;AACpB,EAAA,SAAA,CAAU,YAAY,KAAK,CAAA;AAE3B,EAAA,MAAM,IAAA,GAAO,GAAG,KAAA,EAAO,EAAE,WAAW,MAAA,EAAQ,OAAA,EAAS,aAAa,CAAA;AAElE,EAAA,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,SAAA,EAAW,IAAI,CAAA;AAErC,EAAA,MAAM,MAAA,GAAS,GAAG,QAAA,EAAU;AAAA,IAC1B,OAAA,EAAS,aAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,KAAA;AAAA,IACL,OAAA,EAAS,UAAA;AAAA,IACT,QAAA,EAAU,MAAA;AAAA,IACV,UAAA,EAAY,KAAA;AAAA,IACZ,aAAA,EAAe,OAAA;AAAA,IACf,KAAA,EAAO,MAAA;AAAA,IACP,UAAA,EAAY,SAAA;AAAA,IACZ,MAAA,EAAQ,mBAAA;AAAA,IACR,YAAA,EAAc,OAAA;AAAA,IACd,SAAA,EAAW,6BAAA;AAAA,IACX,MAAA,EAAQ;AAAA,GACT,CAAA;AACD,EAAA,MAAA,CAAO,IAAA,GAAO,QAAA;AACd,EAAA,MAAA,CAAO,YAAA,CAAa,cAAc,iBAAiB,CAAA;AACnD,EAAA,MAAM,GAAA,GAAM,GAAG,MAAA,EAAQ;AAAA,IACrB,KAAA,EAAO,KAAA;AAAA,IACP,MAAA,EAAQ,KAAA;AAAA,IACR,YAAA,EAAc,OAAA;AAAA,IACd,UAAA,EAAY,SAAA;AAAA,IACZ,OAAA,EAAS;AAAA,GACV,CAAA;AACD,EAAA,MAAM,WAAA,GAAc,GAAG,MAAM,CAAA;AAC7B,EAAA,MAAA,CAAO,MAAA,CAAO,KAAK,WAAW,CAAA;AAE9B,EAAA,IAAA,CAAK,YAAY,MAAM,CAAA;AAIvB,EAAA,MAAM,UAAU,MAAe;AAC7B,IAAA,MAAM,IAAI,IAAA,CAAK,OAAA;AACf,IAAA,IAAI,OAAO,CAAA,KAAM,UAAA,EAAY,OAAO,CAAA,EAAE;AACtC,IAAA,OAAO,KAAK,KAAA,EAAM;AAAA,EACpB,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,IAAA,KAAiB;AACjC,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,UAAA,CAAW,IAAI,CAAA;AAAA,SAAA,IAChC,OAAO,MAAA,KAAW,WAAA,EAAa,MAAA,CAAO,QAAA,CAAS,OAAO,IAAI,CAAA;AAAA,EACrE,CAAA;AAEA,EAAA,MAAM,aAAa,MAAc;AAC/B,IAAA,MAAM,MAAc,EAAC;AACrB,IAAA,CAAC,KAAK,MAAA,IAAU,IAAI,OAAA,CAAQ,CAAC,GAAG,CAAA,KAAM;AACpC,MAAA,GAAA,CAAI,IAAA,CAAK;AAAA,QACP,IAAA,EAAM,OAAA;AAAA,QACN,GAAA,EAAK,CAAA,MAAA,EAAS,CAAA,CAAE,IAAI,IAAI,CAAC,CAAA,CAAA;AAAA,QACzB,KAAA,EAAO,CAAA,CAAE,KAAA,IAAS,CAAA,CAAE,IAAA;AAAA,QACpB,KAAA,EAAO,EAAE,KAAA,IAAS,QAAA;AAAA,QAClB,KAAA,EAAO;AAAA,OACR,CAAA;AAAA,IACH,CAAC,CAAA;AACD,IAAA,CAAC,KAAK,QAAA,IAAY,IAAI,OAAA,CAAQ,CAAC,GAAG,CAAA,KAAM;AACtC,MAAA,GAAA,CAAI,IAAA,CAAK;AAAA,QACP,IAAA,EAAM,SAAA;AAAA,QACN,KAAK,CAAA,IAAA,EAAO,CAAA,CAAE,MAAM,CAAA,CAAE,KAAK,IAAI,CAAC,CAAA,CAAA;AAAA,QAChC,OAAO,CAAA,CAAE,KAAA;AAAA,QACT,KAAA,EAAO,EAAE,KAAA,IAAS,UAAA;AAAA,QAClB,OAAA,EAAS;AAAA,OACV,CAAA;AAAA,IACH,CAAC,CAAA;AACD,IAAA,OAAO,GAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,cAAc,MAAc;AAChC,IAAA,MAAM,CAAA,GAAI,KAAA,CAAM,IAAA,EAAK,CAAE,WAAA,EAAY;AACnC,IAAA,MAAM,QAAQ,UAAA,EAAW;AACzB,IAAA,IAAI,CAAC,GAAG,OAAO,KAAA;AACf,IAAA,OAAO,KAAA,CAAM,MAAA;AAAA,MACX,CAAC,EAAA,KACC,EAAA,CAAG,MAAM,WAAA,EAAY,CAAE,SAAS,CAAC,CAAA,IAChC,EAAA,CAAG,IAAA,KAAS,WAAW,EAAA,CAAG,KAAA,CAAM,KAAK,WAAA,EAAY,CAAE,SAAS,CAAC;AAAA,KAClE;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,OAAO,EAAA,KAAa;AAClC,IAAA,IAAI,EAAA,CAAG,SAAS,OAAA,EAAS;AACvB,MAAA,QAAA,CAAS,EAAA,CAAG,MAAM,IAAI,CAAA;AACtB,MAAA,OAAA,CAAQ,KAAK,CAAA;AACb,MAAA;AAAA,IACF;AACA,IAAA,IAAI;AACF,MAAA,MAAM,EAAA,CAAG,QAAQ,GAAA,EAAI;AAAA,IACvB,CAAA,SAAE;AACA,MAAA,IAAI,CAAC,EAAA,CAAG,OAAA,CAAQ,QAAA,UAAkB,KAAK,CAAA;AAAA,IACzC;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,aAAa,MAAM;AACvB,IAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,IAAA,MAAA,GAAS,IAAA,CAAK,IAAI,MAAA,EAAQ,IAAA,CAAK,IAAI,CAAA,EAAG,QAAA,CAAS,MAAA,GAAS,CAAC,CAAC,CAAA;AAC1D,IAAA,IAAA,CAAK,eAAA,EAAgB;AAErB,IAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,MAAA,IAAA,CAAK,WAAA;AAAA,QACH,EAAA;AAAA,UACE,KAAA;AAAA,UACA,EAAE,OAAA,EAAS,MAAA,EAAQ,QAAA,EAAU,MAAA,EAAQ,OAAO,SAAA,EAAU;AAAA,UACtD,EAAE,aAAa,eAAA;AAAgB;AACjC,OACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,uBAAa,GAAA,EAAoB;AACvC,IAAA,KAAA,MAAW,MAAM,QAAA,EAAU;AACzB,MAAA,MAAM,MAAM,MAAA,CAAO,GAAA,CAAI,EAAA,CAAG,KAAK,KAAK,EAAC;AACrC,MAAA,GAAA,CAAI,KAAK,EAAE,CAAA;AACX,MAAA,MAAA,CAAO,GAAA,CAAI,EAAA,CAAG,KAAA,EAAO,GAAG,CAAA;AAAA,IAC1B;AAEA,IAAA,KAAA,MAAW,CAAC,SAAA,EAAW,UAAU,CAAA,IAAK,MAAA,EAAQ;AAC5C,MAAA,MAAM,UAAU,EAAA,CAAG,KAAA,EAAO,EAAE,SAAA,EAAW,OAAO,CAAA;AAC9C,MAAA,OAAA,CAAQ,WAAA;AAAA,QACN,EAAA;AAAA,UACE,KAAA;AAAA,UACA;AAAA,YACE,OAAA,EAAS,aAAA;AAAA,YACT,QAAA,EAAU,MAAA;AAAA,YACV,UAAA,EAAY,KAAA;AAAA,YACZ,aAAA,EAAe,OAAA;AAAA,YACf,aAAA,EAAe,WAAA;AAAA,YACf,KAAA,EAAO;AAAA,WACT;AAAA,UACA,EAAE,aAAa,SAAA;AAAU;AAC3B,OACF;AAEA,MAAA,KAAA,MAAW,MAAM,UAAA,EAAY;AAC3B,QAAA,MAAM,GAAA,GAAM,QAAA,CAAS,OAAA,CAAQ,EAAE,CAAA;AAC/B,QAAA,MAAM,WAAW,GAAA,KAAQ,MAAA;AACzB,QAAA,MAAM,GAAA,GAAM,GAAG,QAAA,EAAU;AAAA,UACvB,OAAA,EAAS,MAAA;AAAA,UACT,UAAA,EAAY,QAAA;AAAA,UACZ,cAAA,EAAgB,eAAA;AAAA,UAChB,GAAA,EAAK,KAAA;AAAA,UACL,KAAA,EAAO,MAAA;AAAA,UACP,SAAA,EAAW,MAAA;AAAA,UACX,OAAA,EAAS,UAAA;AAAA,UACT,QAAA,EAAU,MAAA;AAAA,UACV,KAAA,EAAO,SAAA;AAAA,UACP,UAAA,EAAY,WAAW,SAAA,GAAY,aAAA;AAAA,UACnC,MAAA,EAAQ,MAAA;AAAA,UACR,YAAA,EAAc,KAAA;AAAA,UACd,MAAA,EAAQ;AAAA,SACT,CAAA;AACD,QAAA,GAAA,CAAI,IAAA,GAAO,QAAA;AAEX,QAAA,MAAM,SAAA,GAAY,EAAA;AAAA,UAChB,MAAA;AAAA,UACA;AAAA,YACE,QAAA,EAAU,QAAA;AAAA,YACV,YAAA,EAAc,UAAA;AAAA,YACd,UAAA,EAAY;AAAA,WACd;AAAA,UACA,EAAE,WAAA,EAAa,EAAA,CAAG,KAAA;AAAM,SAC1B;AACA,QAAA,GAAA,CAAI,YAAY,SAAS,CAAA;AAEzB,QAAA,IAAI,GAAG,IAAA,KAAS,OAAA,IAAW,GAAG,KAAA,CAAM,IAAA,KAAS,GAAG,KAAA,EAAO;AACrD,UAAA,GAAA,CAAI,WAAA;AAAA,YACF,EAAA;AAAA,cACE,MAAA;AAAA,cACA;AAAA,gBACE,QAAA,EAAU,MAAA;AAAA,gBACV,KAAA,EAAO,WAAW,SAAA,GAAY,SAAA;AAAA,gBAC9B,UAAA,EAAY,yCAAA;AAAA,gBACZ,UAAA,EAAY;AAAA,eACd;AAAA,cACA,EAAE,WAAA,EAAa,EAAA,CAAG,KAAA,CAAM,IAAA;AAAK;AAC/B,WACF;AAAA,QACF;AAEA,QAAA,GAAA,CAAI,gBAAA,CAAiB,cAAc,MAAM;AACvC,UAAA,MAAA,GAAS,GAAA;AACT,UAAA,UAAA,EAAW;AAAA,QACb,CAAC,CAAA;AACD,QAAA,GAAA,CAAI,iBAAiB,OAAA,EAAS,MAAM,KAAK,OAAA,CAAQ,EAAE,CAAC,CAAA;AACpD,QAAA,OAAA,CAAQ,YAAY,GAAG,CAAA;AAAA,MACzB;AACA,MAAA,IAAA,CAAK,YAAY,OAAO,CAAA;AAAA,IAC1B;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,cAAc,MAAM;AACxB,IAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,aAAA;AAClC,IAAA,MAAM,MAAA,GAAS,KAAK,MAAA,IAAU,SAAA;AAE9B,IAAA,MAAA,CAAO,MAAA,CAAO,KAAK,KAAA,EAAO;AAAA,MACxB,QAAA,EAAU,OAAA;AAAA,MACV,MAAA,EAAQ,OAAO,MAAM,CAAA;AAAA,MACrB,GAAA,EAAK,EAAA;AAAA,MACL,MAAA,EAAQ,EAAA;AAAA,MACR,IAAA,EAAM,EAAA;AAAA,MACN,KAAA,EAAO;AAAA,KACR,CAAA;AACD,IAAA,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,KAAA,EAAO,WAAA,CAAY,QAAQ,CAAC,CAAA;AAE/C,IAAA,OAAA,CAAQ,WAAA,GAAc,KAAK,KAAA,IAAS,UAAA;AACpC,IAAA,KAAA,CAAM,YAAA,CAAa,YAAA,EAAc,IAAA,CAAK,KAAA,IAAS,UAAU,CAAA;AACzD,IAAA,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,KAAA,EAAO,WAAA,CAAY,QAAQ,CAAC,CAAA;AAEhD,IAAA,WAAA,CAAY,WAAA,GAAc,KAAK,KAAA,IAAS,KAAA;AACxC,IAAA,MAAA,CAAO,KAAA,CAAM,UAAA,GAAa,IAAA,GAAO,SAAA,GAAY,SAAA;AAC7C,IAAA,MAAA,CAAO,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,IAAI,CAAC,CAAA;AAAA,EACnD,CAAA;AAEA,EAAA,SAAS,QAAQ,IAAA,EAAe;AAC9B,IAAA,IAAI,SAAS,IAAA,EAAM;AACnB,IAAA,IAAA,GAAO,IAAA;AACP,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,KAAA,GAAQ,EAAA;AACR,MAAA,KAAA,CAAM,KAAA,GAAQ,EAAA;AACd,MAAA,MAAA,GAAS,CAAA;AACT,MAAA,KAAA,CAAM,WAAA,IAAe,IAAA,CAAK,YAAA,CAAa,KAAA,EAAO,MAAM,CAAA;AACpD,MAAA,WAAA,EAAY;AACZ,MAAA,UAAA,EAAW;AACX,MAAA,qBAAA,CAAsB,MAAM,KAAA,CAAM,KAAA,EAAO,CAAA;AAAA,IAC3C,CAAA,MAAA,IAAW,MAAM,WAAA,EAAa;AAC5B,MAAA,KAAA,CAAM,MAAA,EAAO;AAAA,IACf;AACA,IAAA,WAAA,EAAY;AAAA,EACd;AAIA,EAAA,KAAA,CAAM,gBAAA,CAAiB,SAAS,MAAM;AACpC,IAAA,KAAA,GAAQ,KAAA,CAAM,KAAA;AACd,IAAA,MAAA,GAAS,CAAA;AACT,IAAA,UAAA,EAAW;AAAA,EACb,CAAC,CAAA;AAED,EAAA,KAAA,CAAM,gBAAA,CAAiB,SAAA,EAAW,CAAC,CAAA,KAAM;AACvC,IAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,IAAA,IAAI,CAAA,CAAE,QAAQ,QAAA,EAAU;AACtB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,IACf,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,WAAA,EAAa;AAChC,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,MAAA,GAAS,KAAK,GAAA,CAAI,MAAA,GAAS,CAAA,EAAG,QAAA,CAAS,SAAS,CAAC,CAAA;AACjD,MAAA,UAAA,EAAW;AAAA,IACb,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,SAAA,EAAW;AAC9B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,MAAA,GAAS,CAAA,EAAG,CAAC,CAAA;AAC/B,MAAA,UAAA,EAAW;AAAA,IACb,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,OAAA,EAAS;AAC5B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,MAAM,EAAA,GAAK,SAAS,MAAM,CAAA;AAC1B,MAAA,IAAI,EAAA,EAAI,KAAK,OAAA,CAAQ,EAAE,CAAA;AAAA,IACzB;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,MAAM,OAAA,CAAQ,CAAC,IAAI,CAAC,CAAA;AAErD,EAAA,MAAM,cAAA,GAAiB,CAAC,CAAA,KAAkB;AAGxC,IAAA,IAAI,IAAA,IAAQ,CAAC,CAAA,CAAE,YAAA,GAAe,QAAA,CAAS,IAAI,CAAA,EAAG,OAAA,CAAQ,KAAK,CAAA;AAAA,EAC7D,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAAqB;AACtC,IAAA,IAAI,KAAK,MAAA,IAAU,WAAA,CAAY,IAAA,CAAK,MAAA,EAAQ,CAAC,CAAA,EAAG;AAC9C,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,OAAA,CAAQ,CAAC,IAAI,CAAA;AAAA,IACf;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,eAAe,MAAM;AACzB,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,cAAc,CAAA;AACrD,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAAA,EAC9C,CAAA;AACA,EAAA,MAAM,kBAAkB,MAAM;AAC5B,IAAA,QAAA,CAAS,mBAAA,CAAoB,aAAa,cAAc,CAAA;AACxD,IAAA,MAAA,CAAO,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAAA,EACjD,CAAA;AAEA,EAAA,MAAM,QAAQ,MAAM;AAClB,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,CAAC,IAAA,CAAK,SAAA,IAAa,QAAA,CAAS,IAAA,EAAM,YAAY,IAAI,CAAA;AAClD,IAAA,YAAA,EAAa;AACb,IAAA,OAAA,GAAU,IAAA;AAAA,EACZ,CAAA;AACA,EAAA,MAAM,UAAU,MAAM;AACpB,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,eAAA,EAAgB;AAChB,IAAA,IAAA,CAAK,MAAA,EAAO;AACZ,IAAA,OAAA,GAAU,KAAA;AAAA,EACZ,CAAA;AAIA,EAAA,WAAA,EAAY;AACZ,EAAA,IAAI,OAAA,IAAW,KAAA,EAAM;AAErB,EAAA,OAAO;AAAA,IACL,OAAO,IAAA,EAAsB;AAC3B,MAAA,IAAA,GAAO,EAAE,GAAG,IAAA,EAAM,GAAG,IAAA,EAAK;AAC1B,MAAA,IAAI,SAAQ,EAAG;AACb,QAAA,KAAA,EAAM;AACN,QAAA,WAAA,EAAY;AACZ,QAAA,IAAI,MAAM,UAAA,EAAW;AAAA,MACvB,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,KAAK,CAAA;AACb,QAAA,OAAA,EAAQ;AAAA,MACV;AAAA,IACF,CAAA;AAAA,IACA,OAAA,GAAU;AACR,MAAA,OAAA,CAAQ,KAAK,CAAA;AACb,MAAA,OAAA,EAAQ;AAAA,IACV;AAAA,GACF;AACF;;;AChbO,SAAS,OAAA,CACd,KAAA,EACA,OAAA,GAA0B,EAAC,EACZ;AACf,EAAA,MAAM,QAAA,GAAW,cAAc,OAAO,CAAA;AACtC,EAAA,OAAO;AAAA,IACL,MAAA,CAAO,IAAA,GAAuB,EAAC,EAAG;AAChC,MAAA,QAAA,CAAS,OAAO,IAAI,CAAA;AAAA,IACtB,CAAA;AAAA,IACA,OAAA,GAAU;AACR,MAAA,QAAA,CAAS,OAAA,EAAQ;AAAA,IACnB;AAAA,GACF;AACF","file":"svelte.cjs","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-right\":\n return { bottom: gap, right: 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, left: 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 const renderList = () => {\n const filtered = getFiltered();\n active = Math.min(active, Math.max(0, filtered.length - 1));\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 isActive = idx === active;\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: isActive ? \"#2563eb\" : \"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 if (it.kind === \"route\" && it.route.path !== it.label) {\n row.appendChild(\n el(\n \"span\",\n {\n fontSize: \"11px\",\n color: isActive ? \"#cfe0ff\" : \"#6b7280\",\n fontFamily: \"ui-monospace, SFMono-Regular, monospace\",\n flexShrink: \"0\",\n },\n { textContent: it.route.path },\n ),\n );\n }\n\n row.addEventListener(\"mouseenter\", () => {\n active = idx;\n renderList();\n });\n row.addEventListener(\"click\", () => void runItem(it));\n section.appendChild(row);\n }\n list.appendChild(section);\n }\n };\n\n const renderShell = () => {\n const position = opts.position ?? \"bottom-left\";\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 const filtered = getFiltered();\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n active = Math.min(active + 1, filtered.length - 1);\n renderList();\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n active = Math.max(active - 1, 0);\n renderList();\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const it = filtered[active];\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","import { createDevDock } from \"../core/dock\";\nimport type { DevDockOptions } from \"../core/types\";\n\n/** Return type compatible with Svelte's `Action` contract. */\nexport interface DevDockAction {\n update(options?: DevDockOptions): void;\n destroy(): void;\n}\n\n/**\n * Svelte action that mounts a dev dock. Use it on any element:\n *\n * ```svelte\n * <script>\n * import { devdock } from \"devdock/svelte\";\n * const routes = [{ path: \"/admin\", label: \"Admin\" }];\n * </script>\n *\n * <div use:devdock={{ routes, hotkey: \"mod+.\" }}></div>\n * ```\n *\n * The dock mounts into `document.body` (or `options.container`), so the host\n * element is just an anchor. Reactive `options` flow through automatically.\n */\nexport function devdock(\n _node: HTMLElement,\n options: DevDockOptions = {},\n): DevDockAction {\n const instance = createDevDock(options);\n return {\n update(next: DevDockOptions = {}) {\n instance.update(next);\n },\n destroy() {\n instance.destroy();\n },\n };\n}\n\nexport { createDevDock } from \"../core/dock\";\nexport type {\n DevDockOptions,\n DevDockInstance,\n DevRoute,\n DevCommand,\n DockPosition,\n} from \"../core/types\";\n"]}
@@ -0,0 +1,27 @@
1
+ import { b as DevDockOptions } from './types-DYQBDvfK.cjs';
2
+ export { D as DevCommand, a as DevDockInstance, c as DevRoute, d as DockPosition } from './types-DYQBDvfK.cjs';
3
+ export { c as createDevDock } from './dock-mQl2WU8n.cjs';
4
+
5
+ /** Return type compatible with Svelte's `Action` contract. */
6
+ interface DevDockAction {
7
+ update(options?: DevDockOptions): void;
8
+ destroy(): void;
9
+ }
10
+ /**
11
+ * Svelte action that mounts a dev dock. Use it on any element:
12
+ *
13
+ * ```svelte
14
+ * <script>
15
+ * import { devdock } from "devdock/svelte";
16
+ * const routes = [{ path: "/admin", label: "Admin" }];
17
+ * </script>
18
+ *
19
+ * <div use:devdock={{ routes, hotkey: "mod+." }}></div>
20
+ * ```
21
+ *
22
+ * The dock mounts into `document.body` (or `options.container`), so the host
23
+ * element is just an anchor. Reactive `options` flow through automatically.
24
+ */
25
+ declare function devdock(_node: HTMLElement, options?: DevDockOptions): DevDockAction;
26
+
27
+ export { type DevDockAction, DevDockOptions, devdock };
@@ -0,0 +1,27 @@
1
+ import { b as DevDockOptions } from './types-DYQBDvfK.js';
2
+ export { D as DevCommand, a as DevDockInstance, c as DevRoute, d as DockPosition } from './types-DYQBDvfK.js';
3
+ export { c as createDevDock } from './dock-elbtMGsE.js';
4
+
5
+ /** Return type compatible with Svelte's `Action` contract. */
6
+ interface DevDockAction {
7
+ update(options?: DevDockOptions): void;
8
+ destroy(): void;
9
+ }
10
+ /**
11
+ * Svelte action that mounts a dev dock. Use it on any element:
12
+ *
13
+ * ```svelte
14
+ * <script>
15
+ * import { devdock } from "devdock/svelte";
16
+ * const routes = [{ path: "/admin", label: "Admin" }];
17
+ * </script>
18
+ *
19
+ * <div use:devdock={{ routes, hotkey: "mod+." }}></div>
20
+ * ```
21
+ *
22
+ * The dock mounts into `document.body` (or `options.container`), so the host
23
+ * element is just an anchor. Reactive `options` flow through automatically.
24
+ */
25
+ declare function devdock(_node: HTMLElement, options?: DevDockOptions): DevDockAction;
26
+
27
+ export { type DevDockAction, DevDockOptions, devdock };
package/dist/svelte.js ADDED
@@ -0,0 +1,19 @@
1
+ import { createDevDock } from './chunk-NJ3FCNK6.js';
2
+ export { createDevDock } from './chunk-NJ3FCNK6.js';
3
+
4
+ // src/svelte/index.ts
5
+ function devdock(_node, options = {}) {
6
+ const instance = createDevDock(options);
7
+ return {
8
+ update(next = {}) {
9
+ instance.update(next);
10
+ },
11
+ destroy() {
12
+ instance.destroy();
13
+ }
14
+ };
15
+ }
16
+
17
+ export { devdock };
18
+ //# sourceMappingURL=svelte.js.map
19
+ //# sourceMappingURL=svelte.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/svelte/index.ts"],"names":[],"mappings":";;;;AAwBO,SAAS,OAAA,CACd,KAAA,EACA,OAAA,GAA0B,EAAC,EACZ;AACf,EAAA,MAAM,QAAA,GAAW,cAAc,OAAO,CAAA;AACtC,EAAA,OAAO;AAAA,IACL,MAAA,CAAO,IAAA,GAAuB,EAAC,EAAG;AAChC,MAAA,QAAA,CAAS,OAAO,IAAI,CAAA;AAAA,IACtB,CAAA;AAAA,IACA,OAAA,GAAU;AACR,MAAA,QAAA,CAAS,OAAA,EAAQ;AAAA,IACnB;AAAA,GACF;AACF","file":"svelte.js","sourcesContent":["import { createDevDock } from \"../core/dock\";\nimport type { DevDockOptions } from \"../core/types\";\n\n/** Return type compatible with Svelte's `Action` contract. */\nexport interface DevDockAction {\n update(options?: DevDockOptions): void;\n destroy(): void;\n}\n\n/**\n * Svelte action that mounts a dev dock. Use it on any element:\n *\n * ```svelte\n * <script>\n * import { devdock } from \"devdock/svelte\";\n * const routes = [{ path: \"/admin\", label: \"Admin\" }];\n * </script>\n *\n * <div use:devdock={{ routes, hotkey: \"mod+.\" }}></div>\n * ```\n *\n * The dock mounts into `document.body` (or `options.container`), so the host\n * element is just an anchor. Reactive `options` flow through automatically.\n */\nexport function devdock(\n _node: HTMLElement,\n options: DevDockOptions = {},\n): DevDockAction {\n const instance = createDevDock(options);\n return {\n update(next: DevDockOptions = {}) {\n instance.update(next);\n },\n destroy() {\n instance.destroy();\n },\n };\n}\n\nexport { createDevDock } from \"../core/dock\";\nexport type {\n DevDockOptions,\n DevDockInstance,\n DevRoute,\n DevCommand,\n DockPosition,\n} from \"../core/types\";\n"]}
@@ -0,0 +1,73 @@
1
+ type DockPosition = "bottom-left" | "bottom-right" | "top-left" | "top-right";
2
+ /** A navigable destination shown in the dock's "Routes" section. */
3
+ interface DevRoute {
4
+ /** Path to navigate to, e.g. "/admin/users". */
5
+ path: string;
6
+ /** Human label. Defaults to `path`. */
7
+ label?: string;
8
+ /** Optional group heading to organize the list. */
9
+ group?: string;
10
+ }
11
+ /** A custom action shown in the dock's "Commands" section. */
12
+ interface DevCommand {
13
+ /** Stable id (used as key). Defaults to `label`. */
14
+ id?: string;
15
+ /** Human label shown in the list. */
16
+ label: string;
17
+ /** Called when the command is selected. May be async. */
18
+ run: () => void | Promise<void>;
19
+ /** Optional group heading to organize the list. */
20
+ group?: string;
21
+ /** Keep the popup open after running (default: false — it closes). */
22
+ keepOpen?: boolean;
23
+ }
24
+ /** Framework-agnostic configuration for the dock. */
25
+ interface DevDockOptions {
26
+ /** Routes to list. */
27
+ routes?: DevRoute[];
28
+ /** Custom commands to list. */
29
+ commands?: DevCommand[];
30
+ /**
31
+ * Whether the dock renders at all. Accepts a boolean or a predicate
32
+ * (re-evaluated on every `update`). When omitted, defaults to
33
+ * `process.env.NODE_ENV !== "production"`.
34
+ *
35
+ * @example enabled: () => location.search.includes("debug")
36
+ */
37
+ enabled?: boolean | (() => boolean);
38
+ /**
39
+ * How to navigate when a route is selected.
40
+ * Defaults to `window.location.assign(path)`.
41
+ */
42
+ onNavigate?: (path: string) => void;
43
+ /** Corner to anchor the button. Default: "bottom-left". */
44
+ position?: DockPosition;
45
+ /** Text on the floating button. Default: "DEV". */
46
+ label?: string;
47
+ /** Heading shown at the top of the popup. Default: "Dev Menu". */
48
+ title?: string;
49
+ /**
50
+ * Optional keyboard shortcut to toggle the popup, e.g. "mod+." or
51
+ * "ctrl+k". "mod" maps to ⌘ on Mac and Ctrl elsewhere. Default: none.
52
+ */
53
+ hotkey?: string | null;
54
+ /** Base z-index for the floating UI. Default: 2147483000. */
55
+ zIndex?: number;
56
+ /** Where to mount the dock. Default: `document.body`. */
57
+ container?: HTMLElement;
58
+ /**
59
+ * Render inside a shadow root so the host page's CSS can't affect the dock
60
+ * (and vice versa). Default: `true` where supported. Set `false` to render
61
+ * in the light DOM (e.g. if you want to style it from the page).
62
+ */
63
+ shadow?: boolean;
64
+ }
65
+ /** A live dock instance returned by {@link createDevDock}. */
66
+ interface DevDockInstance {
67
+ /** Re-render with new options (merged over the current ones). */
68
+ update(options: DevDockOptions): void;
69
+ /** Remove the dock and all its listeners. */
70
+ destroy(): void;
71
+ }
72
+
73
+ export type { DevCommand as D, DevDockInstance as a, DevDockOptions as b, DevRoute as c, DockPosition as d };