@oddsquad/vite-plugin-lit 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +28 -0
  3. package/README.md +153 -0
  4. package/bin.mjs +154 -0
  5. package/client.d.ts +61 -0
  6. package/dist/client/assets/browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js +69 -0
  7. package/dist/client/assets/browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js.map +1 -0
  8. package/dist/client/assets/index-CFrVgNfS.js +9740 -0
  9. package/dist/client/assets/index-CFrVgNfS.js.map +1 -0
  10. package/{panel → dist/client}/index.html +1 -0
  11. package/index.d.ts.map +1 -1
  12. package/index.js +2 -2
  13. package/index.js.map +1 -1
  14. package/lib/color-scheme.d.ts.map +1 -1
  15. package/lib/color-scheme.js +17 -21
  16. package/lib/color-scheme.js.map +1 -1
  17. package/lib/devframe/definition.d.ts +45 -0
  18. package/lib/devframe/definition.d.ts.map +1 -0
  19. package/lib/devframe/definition.js +547 -0
  20. package/lib/devframe/definition.js.map +1 -0
  21. package/lib/devframe/icon.d.ts +8 -0
  22. package/lib/devframe/icon.d.ts.map +1 -0
  23. package/lib/devframe/icon.js +21 -0
  24. package/lib/devframe/icon.js.map +1 -0
  25. package/lib/devframe/paths.d.ts +12 -0
  26. package/lib/devframe/paths.d.ts.map +1 -0
  27. package/lib/devframe/paths.js +38 -0
  28. package/lib/devframe/paths.js.map +1 -0
  29. package/lib/devframe/protocol.d.ts +218 -0
  30. package/lib/devframe/protocol.d.ts.map +1 -0
  31. package/lib/devframe/protocol.js +76 -0
  32. package/lib/devframe/protocol.js.map +1 -0
  33. package/lib/devframe/source.d.ts +49 -0
  34. package/lib/devframe/source.d.ts.map +1 -0
  35. package/lib/devframe/source.js +23 -0
  36. package/lib/devframe/source.js.map +1 -0
  37. package/lib/devframe/vite.d.ts +116 -0
  38. package/lib/devframe/vite.d.ts.map +1 -0
  39. package/lib/devframe/vite.js +291 -0
  40. package/lib/devframe/vite.js.map +1 -0
  41. package/lib/http.d.ts +29 -0
  42. package/lib/http.d.ts.map +1 -0
  43. package/lib/http.js +35 -0
  44. package/lib/http.js.map +1 -0
  45. package/lib/icons.d.ts +2 -0
  46. package/lib/icons.d.ts.map +1 -1
  47. package/lib/icons.js +10 -4
  48. package/lib/icons.js.map +1 -1
  49. package/lib/plugin.d.ts +15 -4
  50. package/lib/plugin.d.ts.map +1 -1
  51. package/lib/plugin.js +640 -574
  52. package/lib/plugin.js.map +1 -1
  53. package/lib/runtime/css.d.ts +2 -16
  54. package/lib/runtime/css.d.ts.map +1 -1
  55. package/lib/runtime/css.js +43 -36
  56. package/lib/runtime/css.js.map +1 -1
  57. package/lib/runtime/edge-panel.d.ts.map +1 -1
  58. package/lib/runtime/edge-panel.js +82 -91
  59. package/lib/runtime/edge-panel.js.map +1 -1
  60. package/lib/runtime/fonts.d.ts.map +1 -1
  61. package/lib/runtime/fonts.js +1 -1
  62. package/lib/runtime/fonts.js.map +1 -1
  63. package/lib/runtime/indicator.d.ts.map +1 -1
  64. package/lib/runtime/indicator.js +71 -74
  65. package/lib/runtime/indicator.js.map +1 -1
  66. package/lib/runtime/inspector/collect.d.ts.map +1 -1
  67. package/lib/runtime/inspector/collect.js +83 -90
  68. package/lib/runtime/inspector/collect.js.map +1 -1
  69. package/lib/runtime/inspector/highlight.d.ts +16 -0
  70. package/lib/runtime/inspector/highlight.d.ts.map +1 -0
  71. package/lib/runtime/inspector/highlight.js +66 -0
  72. package/lib/runtime/inspector/highlight.js.map +1 -0
  73. package/lib/runtime/inspector/install.d.ts.map +1 -1
  74. package/lib/runtime/inspector/install.js +180 -205
  75. package/lib/runtime/inspector/install.js.map +1 -1
  76. package/lib/runtime/inspector/serialize.d.ts.map +1 -1
  77. package/lib/runtime/inspector/serialize.js +86 -107
  78. package/lib/runtime/inspector/serialize.js.map +1 -1
  79. package/lib/runtime/intern.d.ts.map +1 -1
  80. package/lib/runtime/intern.js +31 -29
  81. package/lib/runtime/intern.js.map +1 -1
  82. package/lib/runtime/overrides.d.ts.map +1 -1
  83. package/lib/runtime/overrides.js +14 -13
  84. package/lib/runtime/overrides.js.map +1 -1
  85. package/lib/runtime/patch.d.ts +8 -0
  86. package/lib/runtime/patch.d.ts.map +1 -1
  87. package/lib/runtime/patch.js +321 -282
  88. package/lib/runtime/patch.js.map +1 -1
  89. package/lib/runtime/source-meta.d.ts.map +1 -1
  90. package/lib/runtime/source-meta.js +1 -1
  91. package/lib/runtime/source-meta.js.map +1 -1
  92. package/lib/runtime/source-overlay/editors.d.ts.map +1 -1
  93. package/lib/runtime/source-overlay/editors.js +27 -27
  94. package/lib/runtime/source-overlay/editors.js.map +1 -1
  95. package/lib/runtime/source-overlay/mask-path.d.ts.map +1 -1
  96. package/lib/runtime/source-overlay/mask-path.js +15 -15
  97. package/lib/runtime/source-overlay/mask-path.js.map +1 -1
  98. package/lib/runtime/source-overlay/overlay-element.d.ts.map +1 -1
  99. package/lib/runtime/source-overlay/overlay-element.js +347 -356
  100. package/lib/runtime/source-overlay/overlay-element.js.map +1 -1
  101. package/lib/runtime/source-overlay/source-host.d.ts.map +1 -1
  102. package/lib/runtime/source-overlay/source-host.js +64 -71
  103. package/lib/runtime/source-overlay/source-host.js.map +1 -1
  104. package/lib/runtime/source-overlay/template.d.ts.map +1 -1
  105. package/lib/runtime/source-overlay/template.js +3 -3
  106. package/lib/runtime/source-overlay/template.js.map +1 -1
  107. package/lib/runtime/source-overlay.d.ts.map +1 -1
  108. package/lib/runtime/source-overlay.js +5 -2
  109. package/lib/runtime/source-overlay.js.map +1 -1
  110. package/lib/runtime/timeline/clock.d.ts.map +1 -1
  111. package/lib/runtime/timeline/clock.js +2 -2
  112. package/lib/runtime/timeline/clock.js.map +1 -1
  113. package/lib/runtime/timeline/flash.d.ts +39 -0
  114. package/lib/runtime/timeline/flash.d.ts.map +1 -0
  115. package/lib/runtime/timeline/flash.js +169 -0
  116. package/lib/runtime/timeline/flash.js.map +1 -0
  117. package/lib/runtime/timeline/identity.d.ts.map +1 -1
  118. package/lib/runtime/timeline/identity.js +27 -27
  119. package/lib/runtime/timeline/identity.js.map +1 -1
  120. package/lib/runtime/timeline/input.d.ts.map +1 -1
  121. package/lib/runtime/timeline/input.js +54 -67
  122. package/lib/runtime/timeline/input.js.map +1 -1
  123. package/lib/runtime/timeline/install.d.ts.map +1 -1
  124. package/lib/runtime/timeline/install.js +38 -29
  125. package/lib/runtime/timeline/install.js.map +1 -1
  126. package/lib/runtime/timeline/lifecycle.d.ts +8 -0
  127. package/lib/runtime/timeline/lifecycle.d.ts.map +1 -1
  128. package/lib/runtime/timeline/lifecycle.js +149 -133
  129. package/lib/runtime/timeline/lifecycle.js.map +1 -1
  130. package/lib/runtime/timeline/public-api.d.ts.map +1 -1
  131. package/lib/runtime/timeline/public-api.js +9 -9
  132. package/lib/runtime/timeline/public-api.js.map +1 -1
  133. package/lib/runtime/timeline/render.d.ts.map +1 -1
  134. package/lib/runtime/timeline/render.js +70 -75
  135. package/lib/runtime/timeline/render.js.map +1 -1
  136. package/lib/runtime/timeline/transport.d.ts.map +1 -1
  137. package/lib/runtime/timeline/transport.js +39 -43
  138. package/lib/runtime/timeline/transport.js.map +1 -1
  139. package/lib/segmented-tabs.d.ts +3 -1
  140. package/lib/segmented-tabs.d.ts.map +1 -1
  141. package/lib/segmented-tabs.js +136 -107
  142. package/lib/segmented-tabs.js.map +1 -1
  143. package/lib/snapshot.d.ts +28 -0
  144. package/lib/snapshot.d.ts.map +1 -0
  145. package/lib/snapshot.js +57 -0
  146. package/lib/snapshot.js.map +1 -0
  147. package/lib/source-meta.d.ts.map +1 -1
  148. package/lib/source-meta.js +176 -162
  149. package/lib/source-meta.js.map +1 -1
  150. package/lib/timeline/derive.d.ts +152 -0
  151. package/lib/timeline/derive.d.ts.map +1 -0
  152. package/lib/timeline/derive.js +242 -0
  153. package/lib/timeline/derive.js.map +1 -0
  154. package/lib/timeline-plugin.d.ts.map +1 -1
  155. package/lib/timeline-plugin.js +304 -321
  156. package/lib/timeline-plugin.js.map +1 -1
  157. package/lib/tokens.d.ts.map +1 -1
  158. package/lib/tokens.js +17 -18
  159. package/lib/tokens.js.map +1 -1
  160. package/lib/transform.d.ts.map +1 -1
  161. package/lib/transform.js +62 -57
  162. package/lib/transform.js.map +1 -1
  163. package/lib/types.d.ts.map +1 -1
  164. package/lib/types.js +1 -1
  165. package/lib/types.js.map +1 -1
  166. package/lib/wrap-table.d.ts.map +1 -1
  167. package/lib/wrap-table.js +19 -17
  168. package/lib/wrap-table.js.map +1 -1
  169. package/package.json +77 -13
  170. package/types/hmr-incompatibility.d.ts +59 -0
  171. package/types/hmr-incompatibility.d.ts.map +1 -0
  172. package/types/hmr-incompatibility.js +30 -0
  173. package/types/hmr-incompatibility.js.map +1 -0
  174. package/types/in-page.d.ts +49 -0
  175. package/types/in-page.d.ts.map +1 -0
  176. package/types/in-page.js +33 -0
  177. package/types/in-page.js.map +1 -0
  178. package/types/inspector.d.ts.map +1 -1
  179. package/types/inspector.js.map +1 -1
  180. package/types/snapshot.d.ts +44 -0
  181. package/types/snapshot.d.ts.map +1 -0
  182. package/types/snapshot.js +7 -0
  183. package/types/snapshot.js.map +1 -0
  184. package/types/timeline.d.ts +11 -0
  185. package/types/timeline.d.ts.map +1 -1
  186. package/types/timeline.js.map +1 -1
  187. package/panel/components-view.d.ts +0 -60
  188. package/panel/components-view.d.ts.map +0 -1
  189. package/panel/components-view.js +0 -587
  190. package/panel/components-view.js.map +0 -1
  191. package/panel/devtools-settings.d.ts +0 -41
  192. package/panel/devtools-settings.d.ts.map +0 -1
  193. package/panel/devtools-settings.js +0 -463
  194. package/panel/devtools-settings.js.map +0 -1
  195. package/panel/lit-devtools-panel.d.ts +0 -37
  196. package/panel/lit-devtools-panel.d.ts.map +0 -1
  197. package/panel/lit-devtools-panel.js +0 -185
  198. package/panel/lit-devtools-panel.js.map +0 -1
  199. package/panel/timeline-event-list.d.ts +0 -43
  200. package/panel/timeline-event-list.d.ts.map +0 -1
  201. package/panel/timeline-event-list.js +0 -416
  202. package/panel/timeline-event-list.js.map +0 -1
  203. package/panel/timeline-layers.d.ts +0 -25
  204. package/panel/timeline-layers.d.ts.map +0 -1
  205. package/panel/timeline-layers.js +0 -96
  206. package/panel/timeline-layers.js.map +0 -1
  207. package/panel/timeline-view.d.ts +0 -38
  208. package/panel/timeline-view.d.ts.map +0 -1
  209. package/panel/timeline-view.js +0 -234
  210. package/panel/timeline-view.js.map +0 -1
@@ -3,12 +3,19 @@
3
3
  * Copyright 2026 Google LLC
4
4
  * SPDX-License-Identifier: BSD-3-Clause
5
5
  */
6
- import { existsSync } from 'node:fs';
7
- import { readFile } from 'node:fs/promises';
8
- import { join } from 'node:path';
9
- import { fileURLToPath } from 'node:url';
10
- import { SETTINGS_OVERRIDE_CHANNEL, } from '../types/timeline.js';
11
- import { INSPECT_CMD_CHANNEL, INSPECT_DATA_CHANNEL, INSPECT_OVERLAY_TOGGLE_CHANNEL, INSPECT_PATH, INSPECT_SSE_EVENT, } from '../types/inspector.js';
6
+ import {existsSync} from 'node:fs';
7
+ import {readFile} from 'node:fs/promises';
8
+ import {join} from 'node:path';
9
+ import {fileURLToPath} from 'node:url';
10
+ import {isTrustedRequest} from './http.js';
11
+ import {SETTINGS_OVERRIDE_CHANNEL} from '../types/timeline.js';
12
+ import {
13
+ INSPECT_CMD_CHANNEL,
14
+ INSPECT_DATA_CHANNEL,
15
+ INSPECT_OVERLAY_TOGGLE_CHANNEL,
16
+ INSPECT_PATH,
17
+ INSPECT_SSE_EVENT,
18
+ } from '../types/inspector.js';
12
19
  // ---------------------------------------------------------------------------
13
20
  // SSE push channel — streams batches of TimelineEvents from the server to
14
21
  // the panel iframe. The panel subscribes to /__lit-devtools-events using the
@@ -26,11 +33,12 @@ const SETTINGS_PATH = '/__lit-devtools-settings';
26
33
  // it renders at the same box size as the built-in phosphor icons. Inlined as a
27
34
  // data: URI rather than `logos:lit-icon` so it also works offline (no Iconify
28
35
  // API fetch).
29
- const LIT_LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 0 320 320">' +
30
- '<path fill="#00e8ff" d="m64 192l25.926-44.727l38.233-19.114l63.974 63.974l10.833 61.754L192 320l-64-64l-38.074-25.615z"/>' +
31
- '<path fill="#283198" d="M128 256V128l64-64v128zM0 256l64 64l9.202-60.602L64 192l-37.542 23.71z"/>' +
32
- '<path fill="#324fff" d="M64 192V64l64-64v128zm128 128V192l64-64v128zM0 256V128l64 64z"/>' +
33
- '<path fill="#0ff" d="M64 320V192l64 64z"/></svg>';
36
+ const LIT_LOGO_SVG =
37
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 0 320 320">' +
38
+ '<path fill="#00e8ff" d="m64 192l25.926-44.727l38.233-19.114l63.974 63.974l10.833 61.754L192 320l-64-64l-38.074-25.615z"/>' +
39
+ '<path fill="#283198" d="M128 256V128l64-64v128zM0 256l64 64l9.202-60.602L64 192l-37.542 23.71z"/>' +
40
+ '<path fill="#324fff" d="M64 192V64l64-64v128zm128 128V192l64-64v128zM0 256V128l64 64z"/>' +
41
+ '<path fill="#0ff" d="M64 320V192l64 64z"/></svg>';
34
42
  const LIT_LOGO_ICON = `data:image/svg+xml,${encodeURIComponent(LIT_LOGO_SVG)}`;
35
43
  /**
36
44
  * Resolves the panel files, checking the compiled output first (`panel/`) then
@@ -38,18 +46,18 @@ const LIT_LOGO_ICON = `data:image/svg+xml,${encodeURIComponent(LIT_LOGO_SVG)}`;
38
46
  * served via Vite's /@fs/ handler so TypeScript is compiled on the fly.
39
47
  */
40
48
  const resolvePanel = () => {
41
- for (const [htmlRel, appRel] of [
42
- ['../panel/index.html', '../panel/lit-devtools-panel.js'],
43
- ['../src/panel/index.html', '../src/panel/lit-devtools-panel.ts'],
44
- ]) {
45
- const htmlUrl = new URL(htmlRel, import.meta.url);
46
- if (existsSync(htmlUrl)) {
47
- const htmlDir = fileURLToPath(new URL('.', htmlUrl));
48
- const appModule = fileURLToPath(new URL(appRel, import.meta.url));
49
- return { htmlDir, appModule };
50
- }
49
+ for (const [htmlRel, appRel] of [
50
+ ['../panel/index.html', '../panel/lit-devtools-panel.js'],
51
+ ['../src/panel/index.html', '../src/panel/lit-devtools-panel.ts'],
52
+ ]) {
53
+ const htmlUrl = new URL(htmlRel, import.meta.url);
54
+ if (existsSync(htmlUrl)) {
55
+ const htmlDir = fileURLToPath(new URL('.', htmlUrl));
56
+ const appModule = fileURLToPath(new URL(appRel, import.meta.url));
57
+ return {htmlDir, appModule};
51
58
  }
52
- throw new Error('[lit-plugin:timeline] panel directory not found');
59
+ }
60
+ throw new Error('[lit-plugin:timeline] panel directory not found');
53
61
  };
54
62
  /**
55
63
  * Reads and JSON-parses a request body, invoking `done` with the parsed object
@@ -57,320 +65,295 @@ const resolvePanel = () => {
57
65
  * endpoints.
58
66
  */
59
67
  const readJsonBody = (req, done) => {
60
- const chunks = [];
61
- req.on?.('data', (chunk) => chunks.push(chunk));
62
- req.on?.('end', () => {
63
- try {
64
- done(JSON.parse(Buffer.concat(chunks).toString()));
65
- }
66
- catch {
67
- done(null);
68
- }
69
- });
70
- };
71
- /** True when the request carries no `Origin` or one whose host matches the
72
- * dev-server `Host`. These endpoints drive local dev tooling and (for SSE)
73
- * carry source paths and inspected element data, so we reject cross-origin
74
- * callers — a page the developer happens to visit — rather than opening them
75
- * up with wildcard CORS. */
76
- const isSameOrigin = (headers) => {
77
- const { origin, host } = headers;
78
- if (origin === undefined || origin === 'null')
79
- return true;
68
+ const chunks = [];
69
+ req.on?.('data', (chunk) => chunks.push(chunk));
70
+ req.on?.('end', () => {
80
71
  try {
81
- return new URL(origin).host === host;
82
- }
83
- catch {
84
- return false;
72
+ done(JSON.parse(Buffer.concat(chunks).toString()));
73
+ } catch {
74
+ done(null);
85
75
  }
76
+ });
86
77
  };
87
78
  const installSseMiddleware = (server, clients, onConnect) => {
88
- server.middlewares.use(SSE_PATH, (req, res, next) => {
89
- if (req.method !== 'GET') {
90
- next();
91
- return;
92
- }
93
- if (!isSameOrigin(req.headers ?? {})) {
94
- res.statusCode = 403;
95
- res.end?.('forbidden');
96
- return;
97
- }
98
- res.statusCode = 200;
99
- res.setHeader('Content-Type', 'text/event-stream');
100
- res.setHeader('Cache-Control', 'no-cache');
101
- res.setHeader('Connection', 'keep-alive');
102
- res.flushHeaders?.();
103
- res.write(':\n\n'); // keep-alive comment
104
- clients.add(res);
105
- res.on('close', () => clients.delete(res));
106
- // Replay a just-emitted overlay pick to a panel that connects right after
107
- // it (the cold-start case: picking an element opens the dock, but the
108
- // panel iframe subscribes here only after the `pick` was already
109
- // broadcast to zero clients). See `pendingPick` in `litTimelinePlugin`.
110
- onConnect?.(res);
111
- });
79
+ server.middlewares.use(SSE_PATH, (req, res, next) => {
80
+ if (req.method !== 'GET') {
81
+ next();
82
+ return;
83
+ }
84
+ if (!isTrustedRequest(req.headers ?? {})) {
85
+ res.statusCode = 403;
86
+ res.end?.('forbidden');
87
+ return;
88
+ }
89
+ res.statusCode = 200;
90
+ res.setHeader('Content-Type', 'text/event-stream');
91
+ res.setHeader('Cache-Control', 'no-cache');
92
+ res.setHeader('Connection', 'keep-alive');
93
+ res.flushHeaders?.();
94
+ res.write(':\n\n'); // keep-alive comment
95
+ clients.add(res);
96
+ res.on('close', () => clients.delete(res));
97
+ // Replay a just-emitted overlay pick to a panel that connects right after
98
+ // it (the cold-start case: picking an element opens the dock, but the
99
+ // panel iframe subscribes here only after the `pick` was already
100
+ // broadcast to zero clients). See `pendingPick` in `litTimelinePlugin`.
101
+ onConnect?.(res);
102
+ });
112
103
  };
113
104
  /** How long a pending overlay pick stays replayable to a freshly-connected
114
105
  * panel. Long enough to cover the dock iframe mounting + subscribing, short
115
106
  * enough that an unrelated later reconnect won't resurrect a stale selection. */
116
107
  const PICK_REPLAY_WINDOW_MS = 3000;
117
108
  export const litTimelinePlugin = (getSettings) => {
118
- const sseClients = new Set();
119
- // Captured in configureServer so the (server-side) DevTools command handler
120
- // can broadcast to the app runtime; it only runs after the server is up.
121
- let devServer;
122
- // The most recent overlay `pick`, framed as an SSE payload, kept briefly so a
123
- // panel that mounts in response to the pick (cold start) still receives it.
124
- let pendingPick = null;
125
- /** Write a pre-framed SSE payload to every client, pruning dead sockets. */
126
- const broadcast = (payload) => {
127
- for (const client of sseClients) {
128
- try {
129
- client.write(payload);
130
- }
131
- catch {
132
- sseClients.delete(client);
109
+ const sseClients = new Set();
110
+ // Captured in configureServer so the (server-side) DevTools command handler
111
+ // can broadcast to the app runtime; it only runs after the server is up.
112
+ let devServer;
113
+ // The most recent overlay `pick`, framed as an SSE payload, kept briefly so a
114
+ // panel that mounts in response to the pick (cold start) still receives it.
115
+ let pendingPick = null;
116
+ /** Write a pre-framed SSE payload to every client, pruning dead sockets. */
117
+ const broadcast = (payload) => {
118
+ for (const client of sseClients) {
119
+ try {
120
+ client.write(payload);
121
+ } catch {
122
+ sseClients.delete(client);
123
+ }
124
+ }
125
+ };
126
+ /** Push a batch of events to all subscribed panel SSE clients. */
127
+ const pushToPanel = (events) => {
128
+ if (sseClients.size === 0 || events.length === 0) return;
129
+ broadcast(`data: ${JSON.stringify(events)}\n\n`);
130
+ };
131
+ /** Push a named SSE event (the panel listens via `addEventListener(name)`). */
132
+ const pushEvent = (name, data) => {
133
+ if (sseClients.size === 0) return;
134
+ broadcast(`event: ${name}\ndata: ${JSON.stringify(data)}\n\n`);
135
+ };
136
+ return {
137
+ name: 'lit-devtools',
138
+ apply: 'serve',
139
+ configureServer(server) {
140
+ devServer = server;
141
+ // SSE endpoint: panel iframe subscribes here for event push. A client that
142
+ // connects within the replay window gets the pending pick replayed.
143
+ installSseMiddleware(server, sseClients, (client) => {
144
+ if (pendingPick === null) return;
145
+ if (Date.now() >= pendingPick.expires) {
146
+ pendingPick = null;
147
+ return;
148
+ }
149
+ // Replay to every client connecting within the window, not just the
150
+ // first: the panel opens two SSE subscriptions (components-view and
151
+ // timeline-view) and only components-view acts on `pick`. A one-shot
152
+ // replay could land on timeline-view and be dropped. Replaying to all
153
+ // is safe — timeline-view ignores the event, and components-view's
154
+ // selection is idempotent. The pick clears on expiry, below.
155
+ try {
156
+ client.write(pendingPick.payload);
157
+ } catch {
158
+ // Socket already gone — nothing to replay to.
159
+ }
160
+ });
161
+ // Settings endpoint. GET returns the resolved feature settings (the
162
+ // baseline the panel renders). POST carries a SettingsOverride which we
163
+ // rebroadcast to the app runtime via HMR so it takes effect live.
164
+ server.middlewares.use(SETTINGS_PATH, (req, res, next) => {
165
+ if (!isTrustedRequest(req.headers ?? {})) {
166
+ res.statusCode = 403;
167
+ res.end('forbidden');
168
+ return;
169
+ }
170
+ if (req.method === 'GET') {
171
+ res.statusCode = 200;
172
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
173
+ res.setHeader('Cache-Control', 'no-cache');
174
+ res.end(JSON.stringify(getSettings?.() ?? null));
175
+ return;
176
+ }
177
+ if (req.method === 'POST') {
178
+ readJsonBody(req, (body) => {
179
+ if (body !== null) {
180
+ server.hot.send(SETTINGS_OVERRIDE_CHANNEL, body);
133
181
  }
182
+ res.statusCode = 204;
183
+ res.end();
184
+ });
185
+ return;
134
186
  }
135
- };
136
- /** Push a batch of events to all subscribed panel SSE clients. */
137
- const pushToPanel = (events) => {
138
- if (sseClients.size === 0 || events.length === 0)
187
+ next();
188
+ });
189
+ // Serve the panel HTML with the Lit SPA entry injected via /@fs/ so
190
+ // Vite can transform the TypeScript source on the fly.
191
+ let panel;
192
+ try {
193
+ panel = resolvePanel();
194
+ } catch (e) {
195
+ console.warn(String(e));
196
+ }
197
+ if (panel !== undefined) {
198
+ const {htmlDir, appModule} = panel;
199
+ server.middlewares.use(PANEL_PATH, async (req, res, next) => {
200
+ const url = req.url ?? '/';
201
+ if (url !== '/' && url !== '' && url !== '/index.html') {
202
+ next();
139
203
  return;
140
- broadcast(`data: ${JSON.stringify(events)}\n\n`);
141
- };
142
- /** Push a named SSE event (the panel listens via `addEventListener(name)`). */
143
- const pushEvent = (name, data) => {
144
- if (sseClients.size === 0)
204
+ }
205
+ let html;
206
+ try {
207
+ html = await readFile(join(htmlDir, 'index.html'), 'utf-8');
208
+ } catch {
209
+ next();
145
210
  return;
146
- broadcast(`event: ${name}\ndata: ${JSON.stringify(data)}\n\n`);
147
- };
148
- return {
149
- name: 'lit-devtools',
150
- apply: 'serve',
151
- configureServer(server) {
152
- devServer = server;
153
- // SSE endpoint: panel iframe subscribes here for event push. A client that
154
- // connects within the replay window gets the pending pick replayed.
155
- installSseMiddleware(server, sseClients, (client) => {
156
- if (pendingPick === null)
157
- return;
158
- if (Date.now() >= pendingPick.expires) {
159
- pendingPick = null;
160
- return;
161
- }
162
- // Replay to every client connecting within the window, not just the
163
- // first: the panel opens two SSE subscriptions (components-view and
164
- // timeline-view) and only components-view acts on `pick`. A one-shot
165
- // replay could land on timeline-view and be dropped. Replaying to all
166
- // is safe — timeline-view ignores the event, and components-view's
167
- // selection is idempotent. The pick clears on expiry, below.
168
- try {
169
- client.write(pendingPick.payload);
170
- }
171
- catch {
172
- // Socket already gone — nothing to replay to.
173
- }
174
- });
175
- // Settings endpoint. GET returns the resolved feature settings (the
176
- // baseline the panel renders). POST carries a SettingsOverride which we
177
- // rebroadcast to the app runtime via HMR so it takes effect live.
178
- server.middlewares.use(SETTINGS_PATH, (req, res, next) => {
179
- if (!isSameOrigin(req.headers ?? {})) {
180
- res.statusCode = 403;
181
- res.end('forbidden');
182
- return;
183
- }
184
- if (req.method === 'GET') {
185
- res.statusCode = 200;
186
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
187
- res.setHeader('Cache-Control', 'no-cache');
188
- res.end(JSON.stringify(getSettings?.() ?? null));
189
- return;
190
- }
191
- if (req.method === 'POST') {
192
- readJsonBody(req, (body) => {
193
- if (body !== null) {
194
- server.hot.send(SETTINGS_OVERRIDE_CHANNEL, body);
195
- }
196
- res.statusCode = 204;
197
- res.end();
198
- });
199
- return;
200
- }
201
- next();
202
- });
203
- // Serve the panel HTML with the Lit SPA entry injected via /@fs/ so
204
- // Vite can transform the TypeScript source on the fly.
205
- let panel;
206
- try {
207
- panel = resolvePanel();
211
+ }
212
+ // Inject the panel entry as a /@fs/ module so Vite's transform
213
+ // pipeline compiles TypeScript and resolves bare specifiers (lit, etc).
214
+ const scriptTag = ` <script type="module" src="/@fs${appModule}"></script>\n`;
215
+ html = html.replace('</body>', scriptTag + '</body>');
216
+ res.statusCode = 200;
217
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
218
+ res.end(html);
219
+ });
220
+ }
221
+ // Control endpoint: the panel POSTs recording/layer state changes here.
222
+ // The server rebroadcasts the state to all connected app tabs via HMR.
223
+ server.middlewares.use(CONTROL_PATH, (req, res, next) => {
224
+ const method = req.method;
225
+ if (method !== 'POST') {
226
+ next();
227
+ return;
228
+ }
229
+ const r403 = res;
230
+ if (!isTrustedRequest(req.headers ?? {})) {
231
+ r403.statusCode = 403;
232
+ r403.end('forbidden');
233
+ return;
234
+ }
235
+ readJsonBody(req, (body) => {
236
+ if (body !== null) {
237
+ if (typeof body.recording === 'boolean') {
238
+ server.hot.send('lit:timeline:recording-changed', {
239
+ recording: body.recording,
240
+ });
208
241
  }
209
- catch (e) {
210
- console.warn(String(e));
242
+ const layerKeys = [
243
+ 'litLifecycleEnabled',
244
+ 'litRenderEnabled',
245
+ 'mouseEventEnabled',
246
+ 'keyboardEventEnabled',
247
+ ];
248
+ const layersUpdate = {};
249
+ for (const key of layerKeys) {
250
+ if (typeof body[key] === 'boolean') {
251
+ layersUpdate[key] = body[key];
252
+ }
211
253
  }
212
- if (panel !== undefined) {
213
- const { htmlDir, appModule } = panel;
214
- server.middlewares.use(PANEL_PATH, async (req, res, next) => {
215
- const url = req.url ?? '/';
216
- if (url !== '/' && url !== '' && url !== '/index.html') {
217
- next();
218
- return;
219
- }
220
- let html;
221
- try {
222
- html = await readFile(join(htmlDir, 'index.html'), 'utf-8');
223
- }
224
- catch {
225
- next();
226
- return;
227
- }
228
- // Inject the panel entry as a /@fs/ module so Vite's transform
229
- // pipeline compiles TypeScript and resolves bare specifiers (lit, etc).
230
- const scriptTag = ` <script type="module" src="/@fs${appModule}"></script>\n`;
231
- html = html.replace('</body>', scriptTag + '</body>');
232
- res.statusCode = 200;
233
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
234
- res.end(html);
235
- });
254
+ if (Object.keys(layersUpdate).length > 0) {
255
+ server.hot.send('lit:timeline:layers-changed', layersUpdate);
236
256
  }
237
- // Control endpoint: the panel POSTs recording/layer state changes here.
238
- // The server rebroadcasts the state to all connected app tabs via HMR.
239
- server.middlewares.use(CONTROL_PATH, (req, res, next) => {
240
- const method = req.method;
241
- if (method !== 'POST') {
242
- next();
243
- return;
244
- }
245
- const r403 = res;
246
- if (!isSameOrigin(req.headers ?? {})) {
247
- r403.statusCode = 403;
248
- r403.end('forbidden');
249
- return;
250
- }
251
- readJsonBody(req, (body) => {
252
- if (body !== null) {
253
- if (typeof body.recording === 'boolean') {
254
- server.hot.send('lit:timeline:recording-changed', {
255
- recording: body.recording,
256
- });
257
- }
258
- const layerKeys = [
259
- 'litLifecycleEnabled',
260
- 'litRenderEnabled',
261
- 'mouseEventEnabled',
262
- 'keyboardEventEnabled',
263
- ];
264
- const layersUpdate = {};
265
- for (const key of layerKeys) {
266
- if (typeof body[key] === 'boolean') {
267
- layersUpdate[key] = body[key];
268
- }
269
- }
270
- if (Object.keys(layersUpdate).length > 0) {
271
- server.hot.send('lit:timeline:layers-changed', layersUpdate);
272
- }
273
- }
274
- const r = res;
275
- r.statusCode = 204;
276
- r.end();
277
- });
278
- });
279
- // Accept batches of events from the browser runtime via Vite HMR.
280
- server.hot.on('lit:timeline:push-event', (data) => {
281
- pushToPanel(data.events ?? []);
282
- });
283
- // Legacy: panel postMessage → devtools shell → server (keep for compat).
284
- server.hot.on('lit:timeline:set-recording', (data) => {
285
- server.hot.send('lit:timeline:recording-changed', {
286
- recording: data.recording,
287
- });
288
- });
289
- // Custom layer announced by app code via addTimelineLayer().
290
- // Forward to the panel as a named SSE event so it can add the layer.
291
- server.hot.on('lit:timeline:custom-layer', (data) => {
292
- if (!data.layer?.id)
293
- return;
294
- pushEvent('layer', data.layer);
295
- });
296
- // Components inspector. The panel POSTs an InspectorCommand here; we
297
- // forward it to the app runtime over HMR. The runtime answers over
298
- // INSPECT_DATA_CHANNEL, which we relay to the panel as a named SSE event.
299
- server.middlewares.use(INSPECT_PATH, (req, res, next) => {
300
- const method = req.method;
301
- if (method !== 'POST') {
302
- next();
303
- return;
304
- }
305
- const r403 = res;
306
- if (!isSameOrigin(req.headers ?? {})) {
307
- r403.statusCode = 403;
308
- r403.end('forbidden');
309
- return;
310
- }
311
- readJsonBody(req, (body) => {
312
- if (body !== null) {
313
- // "Pick" starts the overlay's inspect picker (a server-side toggle),
314
- // not a runtime query, so it's routed to the overlay channel.
315
- if (body.type === 'pick') {
316
- server.hot.send(INSPECT_OVERLAY_TOGGLE_CHANNEL);
317
- }
318
- else {
319
- server.hot.send(INSPECT_CMD_CHANNEL, body);
320
- }
321
- }
322
- const r = res;
323
- r.statusCode = 204;
324
- r.end();
325
- });
326
- });
327
- server.hot.on(INSPECT_DATA_CHANNEL, (data) => {
328
- // Remember overlay picks so a cold-started panel (opened by the pick
329
- // itself) can replay it on connect — see `pendingPick`.
330
- if (data.type === 'pick') {
331
- pendingPick = {
332
- payload: `event: ${INSPECT_SSE_EVENT}\ndata: ${JSON.stringify(data)}\n\n`,
333
- expires: Date.now() + PICK_REPLAY_WINDOW_MS,
334
- };
335
- }
336
- pushEvent(INSPECT_SSE_EVENT, data);
337
- });
338
- },
339
- devtools: {
340
- setup(ctx) {
341
- // Register the dock entry. HTML is served by the configureServer
342
- // middleware above (with /@fs/ injection); hostStatic is not used.
343
- ctx.docks.register({
344
- id: 'lit-devtools',
345
- type: 'iframe',
346
- // This dock represents the whole Lit plugin (HMR, source overlay,
347
- // timeline), so it's branded "Lit" rather than the timeline panel
348
- // it currently opens.
349
- title: 'Lit',
350
- // Square, inlined Lit logo — see LIT_LOGO_ICON above. DevTools renders
351
- // a `data:` icon directly as an <img>, no Iconify API fetch.
352
- icon: LIT_LOGO_ICON,
353
- url: PANEL_PATH + '/',
354
- category: 'framework',
355
- });
356
- // Register the overlay toggle as a DevTools command so it shows in the
357
- // command palette and as a managed shortcut. The handler runs
358
- // server-side; it broadcasts to the app runtime, which toggles the
359
- // overlay. Only meaningful when the overlay is enabled.
360
- const so = getSettings?.()?.sourceOverlay;
361
- if (so?.enabled && ctx.commands?.register) {
362
- ctx.commands.register({
363
- id: 'lit:overlay:toggle',
364
- title: 'Pick Lit Element',
365
- description: 'Click to inspect in the Components panel, hold Meta/Ctrl and click to open in your editor',
366
- icon: 'ph:crosshair-duotone',
367
- category: 'Lit',
368
- keybindings: [{ key: 'Meta+Shift+E' }],
369
- handler: () => devServer?.hot.send(INSPECT_OVERLAY_TOGGLE_CHANNEL),
370
- });
371
- }
372
- },
373
- },
374
- };
257
+ }
258
+ const r = res;
259
+ r.statusCode = 204;
260
+ r.end();
261
+ });
262
+ });
263
+ // Accept batches of events from the browser runtime via Vite HMR.
264
+ server.hot.on('lit:timeline:push-event', (data) => {
265
+ pushToPanel(data.events ?? []);
266
+ });
267
+ // Legacy: panel postMessage → devtools shell → server (keep for compat).
268
+ server.hot.on('lit:timeline:set-recording', (data) => {
269
+ server.hot.send('lit:timeline:recording-changed', {
270
+ recording: data.recording,
271
+ });
272
+ });
273
+ // Custom layer announced by app code via addTimelineLayer().
274
+ // Forward to the panel as a named SSE event so it can add the layer.
275
+ server.hot.on('lit:timeline:custom-layer', (data) => {
276
+ if (!data.layer?.id) return;
277
+ pushEvent('layer', data.layer);
278
+ });
279
+ // Components inspector. The panel POSTs an InspectorCommand here; we
280
+ // forward it to the app runtime over HMR. The runtime answers over
281
+ // INSPECT_DATA_CHANNEL, which we relay to the panel as a named SSE event.
282
+ server.middlewares.use(INSPECT_PATH, (req, res, next) => {
283
+ const method = req.method;
284
+ if (method !== 'POST') {
285
+ next();
286
+ return;
287
+ }
288
+ const r403 = res;
289
+ if (!isTrustedRequest(req.headers ?? {})) {
290
+ r403.statusCode = 403;
291
+ r403.end('forbidden');
292
+ return;
293
+ }
294
+ readJsonBody(req, (body) => {
295
+ if (body !== null) {
296
+ // "Pick" starts the overlay's inspect picker (a server-side toggle),
297
+ // not a runtime query, so it's routed to the overlay channel.
298
+ if (body.type === 'pick') {
299
+ server.hot.send(INSPECT_OVERLAY_TOGGLE_CHANNEL);
300
+ } else {
301
+ server.hot.send(INSPECT_CMD_CHANNEL, body);
302
+ }
303
+ }
304
+ const r = res;
305
+ r.statusCode = 204;
306
+ r.end();
307
+ });
308
+ });
309
+ server.hot.on(INSPECT_DATA_CHANNEL, (data) => {
310
+ // Remember overlay picks so a cold-started panel (opened by the pick
311
+ // itself) can replay it on connect — see `pendingPick`.
312
+ if (data.type === 'pick') {
313
+ pendingPick = {
314
+ payload: `event: ${INSPECT_SSE_EVENT}\ndata: ${JSON.stringify(data)}\n\n`,
315
+ expires: Date.now() + PICK_REPLAY_WINDOW_MS,
316
+ };
317
+ }
318
+ pushEvent(INSPECT_SSE_EVENT, data);
319
+ });
320
+ },
321
+ devtools: {
322
+ setup(ctx) {
323
+ // Register the dock entry. HTML is served by the configureServer
324
+ // middleware above (with /@fs/ injection); hostStatic is not used.
325
+ ctx.docks.register({
326
+ id: 'lit-devtools',
327
+ type: 'iframe',
328
+ // This dock represents the whole Lit plugin (HMR, source overlay,
329
+ // timeline), so it's branded "Lit" rather than the timeline panel
330
+ // it currently opens.
331
+ title: 'Lit',
332
+ // Square, inlined Lit logo — see LIT_LOGO_ICON above. DevTools renders
333
+ // a `data:` icon directly as an <img>, no Iconify API fetch.
334
+ icon: LIT_LOGO_ICON,
335
+ url: PANEL_PATH + '/',
336
+ category: 'framework',
337
+ });
338
+ // Register the overlay toggle as a DevTools command so it shows in the
339
+ // command palette and as a managed shortcut. The handler runs
340
+ // server-side; it broadcasts to the app runtime, which toggles the
341
+ // overlay. Only meaningful when the overlay is enabled.
342
+ const so = getSettings?.()?.sourceOverlay;
343
+ if (so?.enabled && ctx.commands?.register) {
344
+ ctx.commands.register({
345
+ id: 'lit:overlay:toggle',
346
+ title: 'Pick Lit Element',
347
+ description:
348
+ 'Click to inspect in the Components panel, hold Meta/Ctrl and click to open in your editor',
349
+ icon: 'ph:crosshair-duotone',
350
+ category: 'Lit',
351
+ keybindings: [{key: 'Meta+Shift+E'}],
352
+ handler: () => devServer?.hot.send(INSPECT_OVERLAY_TOGGLE_CHANNEL),
353
+ });
354
+ }
355
+ },
356
+ },
357
+ };
375
358
  };
376
- //# sourceMappingURL=timeline-plugin.js.map
359
+ //# sourceMappingURL=timeline-plugin.js.map