dev-inspector 1.0.5 → 1.1.1

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 (36) hide show
  1. package/README.md +22 -1
  2. package/lib/init.d.ts +5 -2
  3. package/lib/init.js +5 -2
  4. package/lib/ui/panel/behavior/setupPanelBehavior.d.ts +4 -1
  5. package/lib/ui/panel/behavior/setupPanelBehavior.js +48 -1
  6. package/lib/ui/panel/bindings/bindStorageToPanelView.d.ts +2 -0
  7. package/lib/ui/panel/bindings/bindStorageToPanelView.js +50 -6
  8. package/lib/ui/panel/bindings/matchesQuery.d.ts +2 -0
  9. package/lib/ui/panel/bindings/matchesQuery.js +54 -0
  10. package/lib/ui/panel/bindings/types.d.ts +2 -0
  11. package/lib/ui/panel/dom/buildPanelDOM.js +65 -6
  12. package/lib/ui/panel/dom/constants.d.ts +5 -1
  13. package/lib/ui/panel/dom/constants.js +35 -20
  14. package/lib/ui/panel/dom/types.d.ts +6 -0
  15. package/lib/ui/panel/index.d.ts +4 -0
  16. package/lib/ui/panel/index.js +8 -3
  17. package/lib/ui/panel/state/createPanelState.d.ts +2 -1
  18. package/lib/ui/panel/state/createPanelState.js +5 -1
  19. package/lib/ui/panel/state/types.d.ts +5 -0
  20. package/lib/ui/panel/theme/applyTheme.d.ts +2 -0
  21. package/lib/ui/panel/theme/applyTheme.js +6 -0
  22. package/lib/ui/panel/theme/index.d.ts +3 -0
  23. package/lib/ui/panel/theme/index.js +9 -0
  24. package/lib/ui/panel/theme/storage.d.ts +4 -0
  25. package/lib/ui/panel/theme/storage.js +45 -0
  26. package/lib/ui/panel/theme/types.d.ts +6 -0
  27. package/lib/ui/panel/theme/types.js +2 -0
  28. package/lib/ui/panelStyles.d.ts +1 -1
  29. package/lib/ui/panelStyles.js +514 -125
  30. package/package.json +3 -2
  31. package/lib/ui/logList/networkDetails.d.ts +0 -2
  32. package/lib/ui/logList/networkDetails.js +0 -227
  33. package/lib/ui/logList.d.ts +0 -7
  34. package/lib/ui/logList.js +0 -117
  35. package/lib/ui/panel.d.ts +0 -15
  36. package/lib/ui/panel.js +0 -375
package/README.md CHANGED
@@ -19,6 +19,10 @@ In-page devtools-style logger panel for web apps. Capture **console** and **netw
19
19
  - Network interception: `fetch` + `XMLHttpRequest`
20
20
  - In-memory storage with events (`EventTarget`)
21
21
  - UI panel with tabs (Console / Network), counters, severity/status colors, and resize
22
+ - Search across console messages and network requests (URL, method, status, body)
23
+ - Auto-scroll lock with a "Latest" jump button that surfaces unread entries
24
+ - Light / dark theme with `localStorage` persistence (defaults to light)
25
+ - Configurable panel title (used in the header and the floating toggle)
22
26
  - Vite demo playground
23
27
 
24
28
  ## Installation
@@ -35,10 +39,27 @@ import { initDevInspector } from "dev-inspector";
35
39
  initDevInspector({
36
40
  maxSize: 500,
37
41
  networkOptions: { includeBodies: false },
38
- panelOptions: { initiallyOpen: true, title: "Dev Inspector" },
42
+ panelOptions: {
43
+ initiallyOpen: true,
44
+ title: "My App Inspector",
45
+ theme: "light",
46
+ persistTheme: true,
47
+ },
39
48
  });
40
49
  ```
41
50
 
51
+ ### Panel options
52
+
53
+ | Option | Type | Default | Description |
54
+ | ----------------- | ------------------- | ----------------------- | ------------------------------------------------------------------------------------ |
55
+ | `title` | `string` | `"Dev Inspector"` | Label shown in the panel header **and** the floating toggle pill in the bottom-right |
56
+ | `initiallyOpen` | `boolean` | `true` | Open the panel on mount |
57
+ | `theme` | `"light" \| "dark"` | `"light"` | Initial theme. Overridden by a stored value when `persistTheme` is `true` |
58
+ | `persistTheme` | `boolean` | `true` | Persist the user's theme choice in `localStorage` |
59
+ | `themeStorageKey` | `string` | `"dev-inspector:theme"` | Key used by `localStorage` to remember the theme |
60
+
61
+ The theme can also be toggled at runtime via the sun/moon button in the panel header. When `persistTheme` is `true`, the choice is stored under `themeStorageKey` and restored on the next load.
62
+
42
63
  ## Important: Browser-only (SSR)
43
64
 
44
65
  Dev Inspector’s UI (`createPanel()` and the default `initDevInspector()` flow) requires a **browser environment** (it needs `document`).
package/lib/init.d.ts CHANGED
@@ -1,8 +1,12 @@
1
- type InitOptions = {
1
+ export type Theme = "light" | "dark";
2
+ export type InitOptions = {
2
3
  maxSize?: number;
3
4
  panelOptions?: {
4
5
  title?: string;
5
6
  initiallyOpen?: boolean;
7
+ theme?: Theme;
8
+ persistTheme?: boolean;
9
+ themeStorageKey?: string;
6
10
  };
7
11
  networkOptions?: {
8
12
  includeBodies?: boolean;
@@ -10,4 +14,3 @@ type InitOptions = {
10
14
  };
11
15
  };
12
16
  export declare function initDevInspector(options?: InitOptions): void;
13
- export {};
package/lib/init.js CHANGED
@@ -12,7 +12,7 @@ function getGlobalBag() {
12
12
  return globalThis;
13
13
  }
14
14
  function initDevInspector(options = {}) {
15
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
15
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
16
16
  const key = getGlobalInstanceKey();
17
17
  const bag = getGlobalBag();
18
18
  const prev = bag[key];
@@ -20,7 +20,7 @@ function initDevInspector(options = {}) {
20
20
  try {
21
21
  prev.destroy();
22
22
  }
23
- catch (_k) {
23
+ catch (_o) {
24
24
  void 0;
25
25
  }
26
26
  }
@@ -35,6 +35,9 @@ function initDevInspector(options = {}) {
35
35
  storage,
36
36
  initiallyOpen: (_g = (_f = options.panelOptions) === null || _f === void 0 ? void 0 : _f.initiallyOpen) !== null && _g !== void 0 ? _g : true,
37
37
  title: (_j = (_h = options.panelOptions) === null || _h === void 0 ? void 0 : _h.title) !== null && _j !== void 0 ? _j : "Dev Inspector",
38
+ theme: (_k = options.panelOptions) === null || _k === void 0 ? void 0 : _k.theme,
39
+ persistTheme: (_l = options.panelOptions) === null || _l === void 0 ? void 0 : _l.persistTheme,
40
+ themeStorageKey: (_m = options.panelOptions) === null || _m === void 0 ? void 0 : _m.themeStorageKey,
38
41
  });
39
42
  const destroy = () => {
40
43
  try {
@@ -2,4 +2,7 @@ import type { LogStorage } from "../../../storage/logStorage";
2
2
  import type { PanelHandle } from "..";
3
3
  import type { PanelDOM } from "../dom/types";
4
4
  import type { PanelState } from "../state/types";
5
- export declare function setupPanelBehavior(dom: PanelDOM, state: PanelState, storage: LogStorage): PanelHandle;
5
+ export declare function setupPanelBehavior(dom: PanelDOM, state: PanelState, storage: LogStorage, themeConfig: {
6
+ storageKey: string;
7
+ persist: boolean;
8
+ }): PanelHandle;
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setupPanelBehavior = setupPanelBehavior;
4
4
  const bindings_1 = require("../bindings");
5
5
  const resize_1 = require("../resize");
6
- function setupPanelBehavior(dom, state, storage) {
6
+ const theme_1 = require("../theme");
7
+ function setupPanelBehavior(dom, state, storage, themeConfig) {
7
8
  const applyVisibility = () => {
8
9
  if (state.open)
9
10
  dom.panel.classList.remove("di-hidden");
@@ -20,17 +21,36 @@ function setupPanelBehavior(dom, state, storage) {
20
21
  dom.consoleTab.classList.remove("di-tabActive");
21
22
  }
22
23
  };
24
+ const updateThemeButton = () => {
25
+ dom.themeBtn.setAttribute("aria-pressed", state.theme === "dark" ? "true" : "false");
26
+ dom.themeBtn.setAttribute("title", state.theme === "dark" ? "Switch to light theme" : "Switch to dark theme");
27
+ };
28
+ const setTheme = (next) => {
29
+ state.theme = next;
30
+ (0, theme_1.applyTheme)(dom.root, next);
31
+ updateThemeButton();
32
+ if (themeConfig.persist)
33
+ (0, theme_1.saveTheme)(themeConfig.storageKey, next);
34
+ };
35
+ (0, theme_1.applyTheme)(dom.root, state.theme);
36
+ updateThemeButton();
23
37
  const bindings = (0, bindings_1.bindStorageToPanelView)({
24
38
  storage,
25
39
  state,
26
40
  list: dom.list,
27
41
  body: dom.body,
42
+ jumpBtn: dom.jumpBtn,
43
+ jumpBtnLabel: dom.jumpBtnLabel,
28
44
  counters: dom.counters,
29
45
  updateTabStyles,
30
46
  });
31
47
  applyVisibility();
32
48
  updateTabStyles();
33
49
  const resizeControls = (0, resize_1.attachResizeHandling)(dom.panel, dom.resizeHandle);
50
+ const updateSearchClearVisibility = () => {
51
+ dom.searchClearBtn.classList.toggle("di-searchClear--visible", state.query.length > 0);
52
+ };
53
+ updateSearchClearVisibility();
34
54
  const open = () => {
35
55
  state.open = true;
36
56
  applyVisibility();
@@ -56,17 +76,44 @@ function setupPanelBehavior(dom, state, storage) {
56
76
  updateTabStyles();
57
77
  bindings.renderActiveTab();
58
78
  };
79
+ const onThemeClick = () => setTheme(state.theme === "dark" ? "light" : "dark");
80
+ const onSearchInput = () => {
81
+ state.query = dom.searchInput.value;
82
+ updateSearchClearVisibility();
83
+ bindings.renderActiveTab();
84
+ };
85
+ const onSearchClear = () => {
86
+ if (state.query.length === 0)
87
+ return;
88
+ state.query = "";
89
+ dom.searchInput.value = "";
90
+ updateSearchClearVisibility();
91
+ bindings.renderActiveTab();
92
+ dom.searchInput.focus();
93
+ };
94
+ const onBodyScroll = () => bindings.refreshScrollAffordances();
95
+ const onJumpClick = () => bindings.scrollToBottom();
59
96
  dom.toggleBtn.addEventListener("click", onToggleClick);
60
97
  dom.closeBtn.addEventListener("click", onCloseClick);
61
98
  dom.clearBtn.addEventListener("click", onClearClick);
99
+ dom.themeBtn.addEventListener("click", onThemeClick);
62
100
  dom.consoleTab.addEventListener("click", onConsoleTab);
63
101
  dom.networkTab.addEventListener("click", onNetworkTab);
102
+ dom.searchInput.addEventListener("input", onSearchInput);
103
+ dom.searchClearBtn.addEventListener("click", onSearchClear);
104
+ dom.body.addEventListener("scroll", onBodyScroll, { passive: true });
105
+ dom.jumpBtn.addEventListener("click", onJumpClick);
64
106
  const destroy = () => {
65
107
  dom.toggleBtn.removeEventListener("click", onToggleClick);
66
108
  dom.closeBtn.removeEventListener("click", onCloseClick);
67
109
  dom.clearBtn.removeEventListener("click", onClearClick);
110
+ dom.themeBtn.removeEventListener("click", onThemeClick);
68
111
  dom.consoleTab.removeEventListener("click", onConsoleTab);
69
112
  dom.networkTab.removeEventListener("click", onNetworkTab);
113
+ dom.searchInput.removeEventListener("input", onSearchInput);
114
+ dom.searchClearBtn.removeEventListener("click", onSearchClear);
115
+ dom.body.removeEventListener("scroll", onBodyScroll);
116
+ dom.jumpBtn.removeEventListener("click", onJumpClick);
70
117
  resizeControls.destroy();
71
118
  bindings.destroy();
72
119
  dom.root.remove();
@@ -11,6 +11,8 @@ export declare function bindStorageToPanelView(args: {
11
11
  append: (entry: LogEntry) => void;
12
12
  };
13
13
  body: HTMLElement;
14
+ jumpBtn: HTMLElement;
15
+ jumpBtnLabel: HTMLElement;
14
16
  counters: PanelCountersDOM;
15
17
  updateTabStyles: () => void;
16
18
  }): DataBindings;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.bindStorageToPanelView = bindStorageToPanelView;
4
+ const matchesQuery_1 = require("./matchesQuery");
5
+ const SCROLL_PIN_THRESHOLD_PX = 32;
4
6
  function bindStorageToPanelView(args) {
5
7
  const isConsoleError = (e) => e.source === "console" && e.level === "error";
6
8
  const isNetworkError = (e) => e.source === "network" && (typeof e.status !== "number" || e.status >= 400);
@@ -11,13 +13,47 @@ function bindStorageToPanelView(args) {
11
13
  args.counters.toggleNetworkCount.textContent = String(args.state.entries.network.length);
12
14
  args.counters.toggleConsoleErrorCount.textContent = String(args.state.errorCounts.console);
13
15
  args.counters.toggleNetworkErrorCount.textContent = String(args.state.errorCounts.network);
14
- args.counters.toggleConsoleErrorWrap.style.display = args.state.errorCounts.console > 0 ? "inline-flex" : "none";
15
- args.counters.toggleNetworkErrorWrap.style.display = args.state.errorCounts.network > 0 ? "inline-flex" : "none";
16
+ args.counters.toggleConsoleErrorWrap.style.display =
17
+ args.state.errorCounts.console > 0 ? "inline-flex" : "none";
18
+ args.counters.toggleNetworkErrorWrap.style.display =
19
+ args.state.errorCounts.network > 0 ? "inline-flex" : "none";
20
+ };
21
+ const updateJumpAffordance = () => {
22
+ const visible = !args.state.pinnedToBottom;
23
+ args.jumpBtn.classList.toggle("di-jumpBtn--visible", visible);
24
+ if (args.state.pendingNew > 0) {
25
+ args.jumpBtnLabel.textContent = `${args.state.pendingNew} new`;
26
+ args.jumpBtn.classList.add("di-jumpBtn--hasNew");
27
+ }
28
+ else {
29
+ args.jumpBtnLabel.textContent = "Latest";
30
+ args.jumpBtn.classList.remove("di-jumpBtn--hasNew");
31
+ }
32
+ };
33
+ const scrollToBottom = () => {
34
+ args.body.scrollTop = args.body.scrollHeight;
35
+ args.state.pinnedToBottom = true;
36
+ args.state.pendingNew = 0;
37
+ updateJumpAffordance();
38
+ };
39
+ const measurePinned = () => {
40
+ const distance = args.body.scrollHeight - args.body.scrollTop - args.body.clientHeight;
41
+ return distance <= SCROLL_PIN_THRESHOLD_PX;
42
+ };
43
+ const refreshScrollAffordances = () => {
44
+ args.state.pinnedToBottom = measurePinned();
45
+ if (args.state.pinnedToBottom)
46
+ args.state.pendingNew = 0;
47
+ updateJumpAffordance();
16
48
  };
17
49
  const renderActiveTab = () => {
18
50
  args.list.clear();
19
- args.state.entries[args.state.tab].forEach((e) => args.list.append(e));
51
+ const filtered = args.state.entries[args.state.tab].filter((e) => (0, matchesQuery_1.matchesQuery)(e, args.state.query));
52
+ filtered.forEach((e) => args.list.append(e));
53
+ args.state.pendingNew = 0;
54
+ args.state.pinnedToBottom = true;
20
55
  args.body.scrollTop = args.body.scrollHeight;
56
+ updateJumpAffordance();
21
57
  };
22
58
  const hydrate = () => {
23
59
  args.state.entries.console = [];
@@ -52,10 +88,18 @@ function bindStorageToPanelView(args) {
52
88
  args.state.errorCounts.console += 1;
53
89
  }
54
90
  updateCounts();
55
- if (entry.source === args.state.tab) {
56
- args.list.append(entry);
91
+ if (entry.source !== args.state.tab)
92
+ return;
93
+ if (!(0, matchesQuery_1.matchesQuery)(entry, args.state.query))
94
+ return;
95
+ args.list.append(entry);
96
+ if (args.state.pinnedToBottom) {
57
97
  args.body.scrollTop = args.body.scrollHeight;
58
98
  }
99
+ else {
100
+ args.state.pendingNew += 1;
101
+ updateJumpAffordance();
102
+ }
59
103
  };
60
104
  const unsub = args.storage.onNewLog(onNewLog);
61
105
  const onCleared = () => {
@@ -72,5 +116,5 @@ function bindStorageToPanelView(args) {
72
116
  args.storage.removeEventListener("cleared", onCleared);
73
117
  unsub();
74
118
  };
75
- return { destroy, renderActiveTab, hydrate };
119
+ return { destroy, renderActiveTab, hydrate, refreshScrollAffordances, scrollToBottom };
76
120
  }
@@ -0,0 +1,2 @@
1
+ import type { LogEntry } from "../../../utils/types";
2
+ export declare function matchesQuery(entry: LogEntry, query: string): boolean;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.matchesQuery = matchesQuery;
4
+ function safeStringify(value) {
5
+ var _a;
6
+ const seen = new WeakSet();
7
+ try {
8
+ return (_a = JSON.stringify(value, (_k, v) => {
9
+ if (typeof v === "bigint")
10
+ return v.toString();
11
+ if (typeof v === "function")
12
+ return `[Function:${v.name || "anonymous"}]`;
13
+ if (v instanceof Error)
14
+ return { name: v.name, message: v.message };
15
+ if (v && typeof v === "object") {
16
+ if (seen.has(v))
17
+ return "[Circular]";
18
+ seen.add(v);
19
+ }
20
+ return v;
21
+ })) !== null && _a !== void 0 ? _a : "";
22
+ }
23
+ catch (_b) {
24
+ return "";
25
+ }
26
+ }
27
+ function corpusForEntry(entry) {
28
+ const parts = [];
29
+ if (entry.message)
30
+ parts.push(entry.message);
31
+ if (entry.source === "console") {
32
+ parts.push(entry.level);
33
+ parts.push(safeStringify(entry.args));
34
+ }
35
+ else {
36
+ if (entry.method)
37
+ parts.push(entry.method);
38
+ if (entry.url)
39
+ parts.push(entry.url);
40
+ if (typeof entry.status === "number")
41
+ parts.push(String(entry.status));
42
+ if (typeof entry.requestBody !== "undefined")
43
+ parts.push(safeStringify(entry.requestBody));
44
+ if (typeof entry.responseBody !== "undefined")
45
+ parts.push(safeStringify(entry.responseBody));
46
+ }
47
+ return parts.join("\n").toLowerCase();
48
+ }
49
+ function matchesQuery(entry, query) {
50
+ const q = query.trim().toLowerCase();
51
+ if (q.length === 0)
52
+ return true;
53
+ return corpusForEntry(entry).includes(q);
54
+ }
@@ -2,4 +2,6 @@ export type DataBindings = {
2
2
  destroy: () => void;
3
3
  renderActiveTab: () => void;
4
4
  hydrate: () => void;
5
+ refreshScrollAffordances: () => void;
6
+ scrollToBottom: () => void;
5
7
  };
@@ -13,8 +13,8 @@ function buildPanelDOM(doc, options) {
13
13
  const toggleBtn = doc.createElement("button");
14
14
  toggleBtn.type = "button";
15
15
  toggleBtn.className = "di-toggle";
16
- toggleBtn.setAttribute("aria-label", "Dev Inspector");
17
- toggleBtn.innerHTML = constants_1.TOGGLE_BUTTON_HTML;
16
+ toggleBtn.setAttribute("aria-label", title);
17
+ toggleBtn.innerHTML = (0, constants_1.buildToggleButtonHTML)(title);
18
18
  const panel = doc.createElement("div");
19
19
  panel.className = "di-panel";
20
20
  const resizeHandle = doc.createElement("div");
@@ -30,6 +30,11 @@ function buildPanelDOM(doc, options) {
30
30
  titleEl.textContent = title;
31
31
  const actions = doc.createElement("div");
32
32
  actions.className = "di-actions";
33
+ const themeBtn = doc.createElement("button");
34
+ themeBtn.type = "button";
35
+ themeBtn.className = "di-iconBtn di-themeBtn";
36
+ themeBtn.setAttribute("aria-label", "Toggle theme");
37
+ themeBtn.innerHTML = `<span class="di-themeIcon di-themeIcon--sun">${constants_1.SUN_ICON_HTML}</span><span class="di-themeIcon di-themeIcon--moon">${constants_1.MOON_ICON_HTML}</span>`;
33
38
  const clearBtn = doc.createElement("button");
34
39
  clearBtn.type = "button";
35
40
  clearBtn.className = "di-btn";
@@ -38,7 +43,7 @@ function buildPanelDOM(doc, options) {
38
43
  closeBtn.type = "button";
39
44
  closeBtn.className = "di-btn";
40
45
  closeBtn.textContent = "Close";
41
- actions.append(clearBtn, closeBtn);
46
+ actions.append(themeBtn, clearBtn, closeBtn);
42
47
  headerRow.append(resizeHandle, titleEl, actions);
43
48
  const tabs = doc.createElement("div");
44
49
  tabs.className = "di-tabs";
@@ -51,12 +56,47 @@ function buildPanelDOM(doc, options) {
51
56
  networkTab.className = "di-tab";
52
57
  networkTab.innerHTML = constants_1.NETWORK_TAB_HTML;
53
58
  tabs.append(consoleTab, networkTab);
54
- header.append(headerRow, tabs);
59
+ const searchRow = doc.createElement("div");
60
+ searchRow.className = "di-searchRow";
61
+ const searchWrap = doc.createElement("div");
62
+ searchWrap.className = "di-searchWrap";
63
+ const searchIcon = doc.createElement("span");
64
+ searchIcon.className = "di-searchIconWrap";
65
+ searchIcon.innerHTML = constants_1.SEARCH_ICON_HTML;
66
+ const searchInput = doc.createElement("input");
67
+ searchInput.type = "search";
68
+ searchInput.className = "di-searchInput";
69
+ searchInput.placeholder = "Search logs (URL, method, status, message)";
70
+ searchInput.setAttribute("aria-label", "Search logs");
71
+ searchInput.autocomplete = "off";
72
+ searchInput.spellcheck = false;
73
+ const searchClearBtn = doc.createElement("button");
74
+ searchClearBtn.type = "button";
75
+ searchClearBtn.className = "di-searchClear";
76
+ searchClearBtn.setAttribute("aria-label", "Clear search");
77
+ searchClearBtn.textContent = "×";
78
+ searchWrap.append(searchIcon, searchInput, searchClearBtn);
79
+ searchRow.append(searchWrap);
80
+ header.append(headerRow, tabs, searchRow);
81
+ const bodyWrap = doc.createElement("div");
82
+ bodyWrap.className = "di-bodyWrap";
55
83
  const body = doc.createElement("div");
56
84
  body.className = "di-body";
57
85
  const list = (0, logList_1.createLogList)(doc);
58
86
  body.append(list.el);
59
- panel.append(header, body);
87
+ const jumpBtn = doc.createElement("button");
88
+ jumpBtn.type = "button";
89
+ jumpBtn.className = "di-jumpBtn";
90
+ jumpBtn.setAttribute("aria-label", "Jump to latest");
91
+ const jumpBtnLabel = doc.createElement("span");
92
+ jumpBtnLabel.className = "di-jumpBtnLabel";
93
+ jumpBtnLabel.textContent = "Latest";
94
+ const jumpBtnIcon = doc.createElement("span");
95
+ jumpBtnIcon.className = "di-jumpBtnIcon";
96
+ jumpBtnIcon.innerHTML = constants_1.JUMP_ICON_HTML;
97
+ jumpBtn.append(jumpBtnIcon, jumpBtnLabel);
98
+ bodyWrap.append(body, jumpBtn);
99
+ panel.append(header, bodyWrap);
60
100
  root.append(toggleBtn, panel);
61
101
  mount.append(root);
62
102
  const counters = {
@@ -69,5 +109,24 @@ function buildPanelDOM(doc, options) {
69
109
  toggleConsoleErrorWrap: (0, shared_1.queryOrThrow)(toggleBtn, '[data-di-toggle-error="console"]'),
70
110
  toggleNetworkErrorWrap: (0, shared_1.queryOrThrow)(toggleBtn, '[data-di-toggle-error="network"]'),
71
111
  };
72
- return { root, toggleBtn, panel, header, body, closeBtn, clearBtn, consoleTab, networkTab, resizeHandle, list, counters };
112
+ return {
113
+ root,
114
+ toggleBtn,
115
+ panel,
116
+ header,
117
+ body,
118
+ bodyWrap,
119
+ closeBtn,
120
+ clearBtn,
121
+ themeBtn,
122
+ consoleTab,
123
+ networkTab,
124
+ resizeHandle,
125
+ searchInput,
126
+ searchClearBtn,
127
+ jumpBtn,
128
+ jumpBtnLabel,
129
+ list,
130
+ counters,
131
+ };
73
132
  }
@@ -1,3 +1,7 @@
1
- export declare const TOGGLE_BUTTON_HTML: string;
1
+ export declare function buildToggleButtonHTML(title: string): string;
2
2
  export declare const CONSOLE_TAB_HTML: string;
3
3
  export declare const NETWORK_TAB_HTML: string;
4
+ export declare const SEARCH_ICON_HTML = "<svg class=\"di-searchIcon\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"6\" stroke=\"currentColor\" stroke-width=\"1.6\"/><path d=\"M20 20L16 16\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/></svg>";
5
+ export declare const SUN_ICON_HTML = "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"4\" stroke=\"currentColor\" stroke-width=\"1.6\"/><path d=\"M12 3V5M12 19V21M3 12H5M19 12H21M5.6 5.6L7 7M17 17L18.4 18.4M5.6 18.4L7 17M17 7L18.4 5.6\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/></svg>";
6
+ export declare const MOON_ICON_HTML = "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"M20 14.5C19 17.5 16 20 12.5 20C7.8 20 4 16.2 4 11.5C4 8 6.5 5 9.5 4C8.5 6.5 9 9.5 11 11.5C13 13.5 16 14 18.5 13C18.5 13.5 19 14 20 14.5Z\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linejoin=\"round\"/></svg>";
7
+ export declare const JUMP_ICON_HTML = "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"M12 4V18M12 18L6 12M12 18L18 12\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>";
@@ -1,28 +1,43 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NETWORK_TAB_HTML = exports.CONSOLE_TAB_HTML = exports.TOGGLE_BUTTON_HTML = void 0;
4
- exports.TOGGLE_BUTTON_HTML = `<span class="di-toggleTitle">Dev Inspector</span>` +
5
- `<span class="di-toggleMeta">` +
6
- `<span class="di-toggleBadge" data-di-toggle-count="console">` +
7
- `<svg class="di-toggleIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 5.5C4 4.67 4.67 4 5.5 4H18.5C19.33 4 20 4.67 20 5.5V15.5C20 16.33 19.33 17 18.5 17H13.5L12 18.5L10.5 17H5.5C4.67 17 4 16.33 4 15.5V5.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M7 8H17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M7 11H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>` +
8
- `<span data-di-toggle-count-value="console">0</span>` +
9
- `<span class="di-toggleErr" data-di-toggle-error="console" aria-label="Console errors">` +
10
- `<span class="di-toggleErrIcon">!</span>` +
11
- `<span data-di-toggle-error-value="console">0</span>` +
12
- `</span>` +
13
- `</span>` +
14
- `<span class="di-toggleBadge" data-di-toggle-count="network">` +
15
- `<svg class="di-toggleIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 12C4 7.58 7.58 4 12 4C16.42 4 20 7.58 20 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M6 12C6 8.69 8.69 6 12 6C15.31 6 18 8.69 18 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M8.5 12C8.5 10.07 10.07 8.5 12 8.5C13.93 8.5 15.5 10.07 15.5 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M12 12L12 20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="20" r="1.5" fill="currentColor"/></svg>` +
16
- `<span data-di-toggle-count-value="network">0</span>` +
17
- `<span class="di-toggleErr" data-di-toggle-error="network" aria-label="Network errors">` +
18
- `<span class="di-toggleErrIcon">!</span>` +
19
- `<span data-di-toggle-error-value="network">0</span>` +
20
- `</span>` +
21
- `</span>` +
22
- `</span>`;
3
+ exports.JUMP_ICON_HTML = exports.MOON_ICON_HTML = exports.SUN_ICON_HTML = exports.SEARCH_ICON_HTML = exports.NETWORK_TAB_HTML = exports.CONSOLE_TAB_HTML = void 0;
4
+ exports.buildToggleButtonHTML = buildToggleButtonHTML;
5
+ function escapeHTML(input) {
6
+ return input
7
+ .replace(/&/g, "&amp;")
8
+ .replace(/</g, "&lt;")
9
+ .replace(/>/g, "&gt;")
10
+ .replace(/"/g, "&quot;")
11
+ .replace(/'/g, "&#39;");
12
+ }
13
+ function buildToggleButtonHTML(title) {
14
+ return (`<span class="di-toggleTitle">${escapeHTML(title)}</span>` +
15
+ `<span class="di-toggleMeta">` +
16
+ `<span class="di-toggleBadge" data-di-toggle-count="console">` +
17
+ `<svg class="di-toggleIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 5.5C4 4.67 4.67 4 5.5 4H18.5C19.33 4 20 4.67 20 5.5V15.5C20 16.33 19.33 17 18.5 17H13.5L12 18.5L10.5 17H5.5C4.67 17 4 16.33 4 15.5V5.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M7 8H17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M7 11H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>` +
18
+ `<span data-di-toggle-count-value="console">0</span>` +
19
+ `<span class="di-toggleErr" data-di-toggle-error="console" aria-label="Console errors">` +
20
+ `<span class="di-toggleErrIcon">!</span>` +
21
+ `<span data-di-toggle-error-value="console">0</span>` +
22
+ `</span>` +
23
+ `</span>` +
24
+ `<span class="di-toggleBadge" data-di-toggle-count="network">` +
25
+ `<svg class="di-toggleIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 12C4 7.58 7.58 4 12 4C16.42 4 20 7.58 20 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M6 12C6 8.69 8.69 6 12 6C15.31 6 18 8.69 18 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M8.5 12C8.5 10.07 10.07 8.5 12 8.5C13.93 8.5 15.5 10.07 15.5 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M12 12L12 20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="20" r="1.5" fill="currentColor"/></svg>` +
26
+ `<span data-di-toggle-count-value="network">0</span>` +
27
+ `<span class="di-toggleErr" data-di-toggle-error="network" aria-label="Network errors">` +
28
+ `<span class="di-toggleErrIcon">!</span>` +
29
+ `<span data-di-toggle-error-value="network">0</span>` +
30
+ `</span>` +
31
+ `</span>` +
32
+ `</span>`);
33
+ }
23
34
  exports.CONSOLE_TAB_HTML = `<svg class="di-tabIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 5.5C4 4.67 4.67 4 5.5 4H18.5C19.33 4 20 4.67 20 5.5V15.5C20 16.33 19.33 17 18.5 17H13.5L12 18.5L10.5 17H5.5C4.67 17 4 16.33 4 15.5V5.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M7 8H17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M7 11H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>` +
24
35
  `<span>Console</span>` +
25
36
  `<span class="di-badge" data-di-count="console">0</span>`;
26
37
  exports.NETWORK_TAB_HTML = `<svg class="di-tabIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 12C4 7.58 7.58 4 12 4C16.42 4 20 7.58 20 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M6 12C6 8.69 8.69 6 12 6C15.31 6 18 8.69 18 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M8.5 12C8.5 10.07 10.07 8.5 12 8.5C13.93 8.5 15.5 10.07 15.5 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M12 12L12 20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="20" r="1.5" fill="currentColor"/></svg>` +
27
38
  `<span>Network</span>` +
28
39
  `<span class="di-badge" data-di-count="network">0</span>`;
40
+ exports.SEARCH_ICON_HTML = `<svg class="di-searchIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="11" cy="11" r="6" stroke="currentColor" stroke-width="1.6"/><path d="M20 20L16 16" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>`;
41
+ exports.SUN_ICON_HTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="12" cy="12" r="4" stroke="currentColor" stroke-width="1.6"/><path d="M12 3V5M12 19V21M3 12H5M19 12H21M5.6 5.6L7 7M17 17L18.4 18.4M5.6 18.4L7 17M17 7L18.4 5.6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>`;
42
+ exports.MOON_ICON_HTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M20 14.5C19 17.5 16 20 12.5 20C7.8 20 4 16.2 4 11.5C4 8 6.5 5 9.5 4C8.5 6.5 9 9.5 11 11.5C13 13.5 16 14 18.5 13C18.5 13.5 19 14 20 14.5Z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>`;
43
+ exports.JUMP_ICON_HTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M12 4V18M12 18L6 12M12 18L18 12" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
@@ -15,11 +15,17 @@ export type PanelDOM = {
15
15
  panel: HTMLDivElement;
16
16
  header: HTMLDivElement;
17
17
  body: HTMLDivElement;
18
+ bodyWrap: HTMLDivElement;
18
19
  closeBtn: HTMLButtonElement;
19
20
  clearBtn: HTMLButtonElement;
21
+ themeBtn: HTMLButtonElement;
20
22
  consoleTab: HTMLButtonElement;
21
23
  networkTab: HTMLButtonElement;
22
24
  resizeHandle: HTMLDivElement;
25
+ searchInput: HTMLInputElement;
26
+ searchClearBtn: HTMLButtonElement;
27
+ jumpBtn: HTMLButtonElement;
28
+ jumpBtnLabel: HTMLElement;
23
29
  list: ReturnType<typeof createLogList>;
24
30
  counters: PanelCountersDOM;
25
31
  };
@@ -1,9 +1,13 @@
1
1
  import type { LogStorage } from "../../storage/logStorage";
2
+ import { type Theme } from "./theme";
2
3
  export type PanelOptions = {
3
4
  storage: LogStorage;
4
5
  title?: string;
5
6
  initiallyOpen?: boolean;
6
7
  mount?: HTMLElement;
8
+ theme?: Theme;
9
+ persistTheme?: boolean;
10
+ themeStorageKey?: string;
7
11
  };
8
12
  export type PanelHandle = {
9
13
  open: () => void;
@@ -6,11 +6,16 @@ const ensureStyle_1 = require("./shared/ensureStyle");
6
6
  const dom_1 = require("./dom");
7
7
  const state_1 = require("./state");
8
8
  const behavior_1 = require("./behavior");
9
+ const theme_1 = require("./theme");
9
10
  function createPanel(options) {
10
- var _a;
11
+ var _a, _b, _c, _d;
11
12
  const doc = (0, ensureDocument_1.ensureDocument)();
12
13
  (0, ensureStyle_1.ensureStyle)(doc);
14
+ const persistTheme = (_a = options.persistTheme) !== null && _a !== void 0 ? _a : true;
15
+ const storageKey = (_b = options.themeStorageKey) !== null && _b !== void 0 ? _b : theme_1.DEFAULT_STORAGE_KEY;
16
+ const stored = persistTheme ? (0, theme_1.loadTheme)(storageKey) : null;
17
+ const initialTheme = (_c = stored !== null && stored !== void 0 ? stored : options.theme) !== null && _c !== void 0 ? _c : "light";
13
18
  const dom = (0, dom_1.buildPanelDOM)(doc, options);
14
- const state = (0, state_1.createPanelState)((_a = options.initiallyOpen) !== null && _a !== void 0 ? _a : false);
15
- return (0, behavior_1.setupPanelBehavior)(dom, state, options.storage);
19
+ const state = (0, state_1.createPanelState)((_d = options.initiallyOpen) !== null && _d !== void 0 ? _d : false, initialTheme);
20
+ return (0, behavior_1.setupPanelBehavior)(dom, state, options.storage, { storageKey, persist: persistTheme });
16
21
  }
@@ -1,2 +1,3 @@
1
+ import type { Theme } from "../theme/types";
1
2
  import type { PanelState } from "./types";
2
- export declare function createPanelState(initiallyOpen: boolean): PanelState;
3
+ export declare function createPanelState(initiallyOpen: boolean, theme: Theme): PanelState;
@@ -1,11 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createPanelState = createPanelState;
4
- function createPanelState(initiallyOpen) {
4
+ function createPanelState(initiallyOpen, theme) {
5
5
  return {
6
6
  open: initiallyOpen,
7
7
  tab: "console",
8
8
  entries: { console: [], network: [] },
9
9
  errorCounts: { console: 0, network: 0 },
10
+ query: "",
11
+ pinnedToBottom: true,
12
+ pendingNew: 0,
13
+ theme,
10
14
  };
11
15
  }
@@ -1,8 +1,13 @@
1
1
  import type { LogEntry } from "../../../utils/types";
2
2
  import type { TabKey } from "../shared/types";
3
+ import type { Theme } from "../theme/types";
3
4
  export type PanelState = {
4
5
  open: boolean;
5
6
  tab: TabKey;
6
7
  entries: Record<TabKey, LogEntry[]>;
7
8
  errorCounts: Record<TabKey, number>;
9
+ query: string;
10
+ pinnedToBottom: boolean;
11
+ pendingNew: number;
12
+ theme: Theme;
8
13
  };
@@ -0,0 +1,2 @@
1
+ import type { Theme } from "./types";
2
+ export declare function applyTheme(root: HTMLElement, theme: Theme): void;