@oddsquad/vite-plugin-lit 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/README.md +26 -11
- package/bin.mjs +16 -12
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js → browser-agent-rpc-BXhoSh1z-CRHFLjFo.js} +2 -2
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js.map → browser-agent-rpc-BXhoSh1z-CRHFLjFo.js.map} +1 -1
- package/dist/client/assets/flow-DK8vmxvN.js +745 -0
- package/dist/client/assets/flow-DK8vmxvN.js.map +1 -0
- package/dist/client/assets/{index-CFrVgNfS.js → index-Gn4Hk_9s.js} +2749 -793
- package/dist/client/assets/index-Gn4Hk_9s.js.map +1 -0
- package/dist/client/index.html +11 -1
- package/lib/devframe/definition.d.ts +7 -0
- package/lib/devframe/definition.d.ts.map +1 -1
- package/lib/devframe/definition.js +56 -11
- package/lib/devframe/definition.js.map +1 -1
- package/lib/devframe/launch-editor.d.ts +20 -0
- package/lib/devframe/launch-editor.d.ts.map +1 -0
- package/lib/devframe/launch-editor.js +40 -0
- package/lib/devframe/launch-editor.js.map +1 -0
- package/lib/devframe/protocol.d.ts +31 -4
- package/lib/devframe/protocol.d.ts.map +1 -1
- package/lib/devframe/protocol.js +22 -3
- package/lib/devframe/protocol.js.map +1 -1
- package/lib/devframe/rpc-source.d.ts +44 -0
- package/lib/devframe/rpc-source.d.ts.map +1 -0
- package/lib/devframe/rpc-source.js +150 -0
- package/lib/devframe/rpc-source.js.map +1 -0
- package/lib/devframe/source.d.ts +12 -0
- package/lib/devframe/source.d.ts.map +1 -1
- package/lib/devframe/source.js +12 -0
- package/lib/devframe/source.js.map +1 -1
- package/lib/devframe/vite.d.ts +2 -0
- package/lib/devframe/vite.d.ts.map +1 -1
- package/lib/devframe/vite.js +3 -8
- package/lib/devframe/vite.js.map +1 -1
- package/lib/options.d.ts +136 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +175 -0
- package/lib/options.js.map +1 -0
- package/lib/override-baselines.d.ts +17 -0
- package/lib/override-baselines.d.ts.map +1 -0
- package/lib/override-baselines.js +25 -0
- package/lib/override-baselines.js.map +1 -0
- package/lib/plugin.d.ts +6 -148
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +22 -477
- package/lib/plugin.js.map +1 -1
- package/lib/plugins/css-literals.d.ts +15 -0
- package/lib/plugins/css-literals.d.ts.map +1 -0
- package/lib/plugins/css-literals.js +154 -0
- package/lib/plugins/css-literals.js.map +1 -0
- package/lib/plugins/css-queries.d.ts +20 -0
- package/lib/plugins/css-queries.d.ts.map +1 -0
- package/lib/plugins/css-queries.js +139 -0
- package/lib/plugins/css-queries.js.map +1 -0
- package/lib/plugins/open-in-editor.d.ts +16 -0
- package/lib/plugins/open-in-editor.d.ts.map +1 -0
- package/lib/plugins/open-in-editor.js +96 -0
- package/lib/plugins/open-in-editor.js.map +1 -0
- package/lib/plugins/shared.d.ts +13 -0
- package/lib/plugins/shared.d.ts.map +1 -0
- package/lib/plugins/shared.js +23 -0
- package/lib/plugins/shared.js.map +1 -0
- package/lib/plugins/timeline-virtual.d.ts +19 -0
- package/lib/plugins/timeline-virtual.d.ts.map +1 -0
- package/lib/plugins/timeline-virtual.js +53 -0
- package/lib/plugins/timeline-virtual.js.map +1 -0
- package/lib/runtime/css.d.ts.map +1 -1
- package/lib/runtime/css.js +9 -2
- package/lib/runtime/css.js.map +1 -1
- package/lib/runtime/indicator.js.map +1 -1
- package/lib/runtime/inspector/install.js +65 -13
- package/lib/runtime/inspector/install.js.map +1 -1
- package/lib/runtime/inspector/serialize.d.ts.map +1 -1
- package/lib/runtime/inspector/serialize.js +20 -0
- package/lib/runtime/inspector/serialize.js.map +1 -1
- package/lib/runtime/overrides.d.ts +4 -10
- package/lib/runtime/overrides.d.ts.map +1 -1
- package/lib/runtime/overrides.js +5 -3
- package/lib/runtime/overrides.js.map +1 -1
- package/lib/runtime/page-channel.d.ts +52 -0
- package/lib/runtime/page-channel.d.ts.map +1 -0
- package/lib/runtime/page-channel.js +67 -0
- package/lib/runtime/page-channel.js.map +1 -0
- package/lib/runtime/patch.d.ts.map +1 -1
- package/lib/runtime/patch.js +2 -1
- package/lib/runtime/patch.js.map +1 -1
- package/lib/runtime/rpc-transport.d.ts +46 -0
- package/lib/runtime/rpc-transport.d.ts.map +1 -0
- package/lib/runtime/rpc-transport.js +82 -0
- package/lib/runtime/rpc-transport.js.map +1 -0
- package/lib/runtime/source-overlay/overlay-element.d.ts.map +1 -1
- package/lib/runtime/source-overlay/overlay-element.js +30 -7
- package/lib/runtime/source-overlay/overlay-element.js.map +1 -1
- package/lib/runtime/timeline/install.js +25 -9
- package/lib/runtime/timeline/install.js.map +1 -1
- package/lib/runtime/timeline/public-api.d.ts +2 -2
- package/lib/runtime/timeline/public-api.d.ts.map +1 -1
- package/lib/runtime/timeline/public-api.js +13 -5
- package/lib/runtime/timeline/public-api.js.map +1 -1
- package/lib/runtime/timeline/render.d.ts +15 -6
- package/lib/runtime/timeline/render.d.ts.map +1 -1
- package/lib/runtime/timeline/render.js +150 -11
- package/lib/runtime/timeline/render.js.map +1 -1
- package/lib/runtime/timeline/transport.d.ts +6 -9
- package/lib/runtime/timeline/transport.d.ts.map +1 -1
- package/lib/runtime/timeline/transport.js.map +1 -1
- package/lib/timeline/filter.d.ts +38 -0
- package/lib/timeline/filter.d.ts.map +1 -0
- package/lib/timeline/filter.js +44 -0
- package/lib/timeline/filter.js.map +1 -0
- package/lib/timeline/tracks.d.ts +113 -0
- package/lib/timeline/tracks.d.ts.map +1 -0
- package/lib/timeline/tracks.js +168 -0
- package/lib/timeline/tracks.js.map +1 -0
- package/package.json +9 -1
- package/types/timeline.d.ts +50 -1
- package/types/timeline.d.ts.map +1 -1
- package/types/timeline.js +4 -0
- package/types/timeline.js.map +1 -1
- package/dist/client/assets/index-CFrVgNfS.js.map +0 -1
- package/lib/timeline-plugin.d.ts +0 -72
- package/lib/timeline-plugin.d.ts.map +0 -1
- package/lib/timeline-plugin.js +0 -359
- package/lib/timeline-plugin.js.map +0 -1
|
@@ -20,15 +20,52 @@ const hostMeta = (host) => {
|
|
|
20
20
|
source: sourceOf(host),
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
|
+
/** Reads the render `host` off a `Part` for the one kind (`set part`) whose
|
|
24
|
+
* detail carries the part instead of the render `options` directly. */
|
|
25
|
+
const partHost = (part) => part?.options?.host;
|
|
23
26
|
let removeListener = null;
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Reduces an arbitrary lit-html binding value to a small JSON-serializable
|
|
29
|
+
* summary for the verbose layer's `data` field. Never puts the value itself
|
|
30
|
+
* in `data` — it may be a DOM Node, a TemplateResult, a function, or hold a
|
|
31
|
+
* reference back to the host element, none of which survive (or belong in) a
|
|
32
|
+
* structured-clone over the HMR channel.
|
|
33
|
+
*/
|
|
34
|
+
const describeValue = (value) => {
|
|
35
|
+
if (value === null) return 'null';
|
|
36
|
+
if (value === undefined) return 'undefined';
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
return value.length > 40
|
|
39
|
+
? `string:"${value.slice(0, 40)}…"`
|
|
40
|
+
: `string:"${value}"`;
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === 'number') return `number:${value}`;
|
|
43
|
+
if (typeof value === 'boolean') return `boolean:${value}`;
|
|
44
|
+
if (typeof value === 'function') {
|
|
45
|
+
return `function:${value.name || 'anonymous'}`;
|
|
46
|
+
}
|
|
47
|
+
// lit's sentinels (`nothing`, `noChange`) are symbols.
|
|
48
|
+
if (typeof value === 'symbol') return `symbol:${value.description ?? ''}`;
|
|
49
|
+
if (Array.isArray(value)) return `array(${value.length})`;
|
|
50
|
+
if (typeof value === 'object' && '_$litType$' in value) return 'template';
|
|
51
|
+
if (typeof Node !== 'undefined' && value instanceof Node) {
|
|
52
|
+
const tag = value.tagName?.toLowerCase();
|
|
53
|
+
return tag ? `node:<${tag}>` : `node:#${value.nodeType}`;
|
|
54
|
+
}
|
|
55
|
+
const ctor = value?.constructor?.name;
|
|
56
|
+
return `object:${ctor ?? 'Object'}`;
|
|
57
|
+
};
|
|
58
|
+
/** Summarizes each entry of a template-instance's `values` array. */
|
|
59
|
+
const describeValues = (values) => values?.map(describeValue);
|
|
60
|
+
const onLitDebug = (e, emit, recording, renderEnabled, verboseEnabled) => {
|
|
61
|
+
if (!recording() || (!renderEnabled() && !verboseEnabled())) return;
|
|
26
62
|
const detail = e.detail;
|
|
27
63
|
if (!detail?.kind) return;
|
|
28
64
|
const time = now();
|
|
29
65
|
const {kind, id} = detail;
|
|
30
66
|
switch (kind) {
|
|
31
67
|
case 'begin render': {
|
|
68
|
+
if (!renderEnabled()) break;
|
|
32
69
|
const meta = hostMeta(detail.options?.host);
|
|
33
70
|
emit({
|
|
34
71
|
layerId: 'lit-render',
|
|
@@ -42,6 +79,7 @@ const onLitDebug = (e, emit, recording, layerEnabled) => {
|
|
|
42
79
|
break;
|
|
43
80
|
}
|
|
44
81
|
case 'end render': {
|
|
82
|
+
if (!renderEnabled()) break;
|
|
45
83
|
const meta = hostMeta(detail.options?.host);
|
|
46
84
|
emit({
|
|
47
85
|
layerId: 'lit-render',
|
|
@@ -57,6 +95,7 @@ const onLitDebug = (e, emit, recording, layerEnabled) => {
|
|
|
57
95
|
// `template prep` fires once per *unique* template, the first time it's
|
|
58
96
|
// compiled — low volume, useful as a "new template" marker.
|
|
59
97
|
case 'template prep':
|
|
98
|
+
if (!renderEnabled()) break;
|
|
60
99
|
emit({
|
|
61
100
|
layerId: 'lit-render',
|
|
62
101
|
time,
|
|
@@ -64,26 +103,126 @@ const onLitDebug = (e, emit, recording, layerEnabled) => {
|
|
|
64
103
|
data: {kind, id},
|
|
65
104
|
});
|
|
66
105
|
break;
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
106
|
+
// The events below fire once per template-bound part on *every* render —
|
|
107
|
+
// extremely high volume (a ticking clock or animation floods the layer).
|
|
108
|
+
// The begin/end render pair above already captures each render as a
|
|
109
|
+
// grouped duration, so these live on the separate opt-in
|
|
110
|
+
// `lit-render-verbose` layer instead of adding noise to `lit-render`.
|
|
111
|
+
case 'template updating':
|
|
112
|
+
case 'template instantiated':
|
|
113
|
+
case 'template instantiated and updated': {
|
|
114
|
+
if (!verboseEnabled()) break;
|
|
115
|
+
const meta = hostMeta(detail.options?.host);
|
|
116
|
+
emit({
|
|
117
|
+
layerId: 'lit-render-verbose',
|
|
118
|
+
time,
|
|
119
|
+
title: kind,
|
|
120
|
+
subtitle: meta?.tagName,
|
|
121
|
+
data: {kind, values: describeValues(detail.values)},
|
|
122
|
+
meta,
|
|
123
|
+
});
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'set part': {
|
|
127
|
+
if (!verboseEnabled()) break;
|
|
128
|
+
const meta = hostMeta(partHost(detail.part));
|
|
129
|
+
emit({
|
|
130
|
+
layerId: 'lit-render-verbose',
|
|
131
|
+
time,
|
|
132
|
+
title: kind,
|
|
133
|
+
subtitle: meta?.tagName,
|
|
134
|
+
data: {
|
|
135
|
+
kind,
|
|
136
|
+
valueIndex: detail.valueIndex,
|
|
137
|
+
value: describeValue(detail.value),
|
|
138
|
+
},
|
|
139
|
+
meta,
|
|
140
|
+
});
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'commit nothing to child': {
|
|
144
|
+
if (!verboseEnabled()) break;
|
|
145
|
+
const meta = hostMeta(detail.options?.host);
|
|
146
|
+
emit({
|
|
147
|
+
layerId: 'lit-render-verbose',
|
|
148
|
+
time,
|
|
149
|
+
title: kind,
|
|
150
|
+
subtitle: meta?.tagName,
|
|
151
|
+
data: {kind},
|
|
152
|
+
meta,
|
|
153
|
+
});
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'commit text':
|
|
157
|
+
case 'commit node':
|
|
158
|
+
case 'commit to element binding': {
|
|
159
|
+
if (!verboseEnabled()) break;
|
|
160
|
+
const meta = hostMeta(detail.options?.host);
|
|
161
|
+
emit({
|
|
162
|
+
layerId: 'lit-render-verbose',
|
|
163
|
+
time,
|
|
164
|
+
title: kind,
|
|
165
|
+
subtitle: meta?.tagName,
|
|
166
|
+
data: {kind, value: describeValue(detail.value)},
|
|
167
|
+
meta,
|
|
168
|
+
});
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'commit attribute':
|
|
172
|
+
case 'commit property':
|
|
173
|
+
case 'commit boolean attribute': {
|
|
174
|
+
if (!verboseEnabled()) break;
|
|
175
|
+
const meta = hostMeta(detail.options?.host);
|
|
176
|
+
emit({
|
|
177
|
+
layerId: 'lit-render-verbose',
|
|
178
|
+
time,
|
|
179
|
+
title: kind,
|
|
180
|
+
subtitle: meta?.tagName,
|
|
181
|
+
data: {kind, name: detail.name, value: describeValue(detail.value)},
|
|
182
|
+
meta,
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case 'commit event listener': {
|
|
187
|
+
if (!verboseEnabled()) break;
|
|
188
|
+
const meta = hostMeta(detail.options?.host);
|
|
189
|
+
emit({
|
|
190
|
+
layerId: 'lit-render-verbose',
|
|
191
|
+
time,
|
|
192
|
+
title: kind,
|
|
193
|
+
subtitle: meta?.tagName,
|
|
194
|
+
data: {
|
|
195
|
+
kind,
|
|
196
|
+
name: detail.name,
|
|
197
|
+
addListener: detail.addListener,
|
|
198
|
+
removeListener: detail.removeListener,
|
|
199
|
+
},
|
|
200
|
+
meta,
|
|
201
|
+
});
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
// Unknown or future `*Unstable` kind — ignore rather than guess a shape.
|
|
73
205
|
default:
|
|
74
206
|
break;
|
|
75
207
|
}
|
|
76
208
|
};
|
|
77
209
|
/**
|
|
78
|
-
* Install the lit-debug render
|
|
210
|
+
* Install the lit-debug render layers (`lit-render` and the opt-in
|
|
211
|
+
* `lit-render-verbose`).
|
|
79
212
|
* Idempotent — calling again when already installed is a no-op.
|
|
80
213
|
*/
|
|
81
|
-
export const installRenderLayer = (
|
|
214
|
+
export const installRenderLayer = (
|
|
215
|
+
emit,
|
|
216
|
+
recording,
|
|
217
|
+
renderEnabled,
|
|
218
|
+
verboseEnabled
|
|
219
|
+
) => {
|
|
82
220
|
if (removeListener !== null) return;
|
|
83
221
|
// The listener is cheap and always attached; the per-render cost lives in the
|
|
84
222
|
// `emitLitDebugLogEvents` flag, which the caller toggles via
|
|
85
223
|
// `setRenderDebugEnabled` only while actively capturing.
|
|
86
|
-
const handler = (e) =>
|
|
224
|
+
const handler = (e) =>
|
|
225
|
+
onLitDebug(e, emit, recording, renderEnabled, verboseEnabled);
|
|
87
226
|
window.addEventListener('lit-debug', handler);
|
|
88
227
|
removeListener = () => window.removeEventListener('lit-debug', handler);
|
|
89
228
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * lit-render timeline layer — listens to the built-in lit-debug CustomEvents\n * instead of patching prototypes, so it's zero-cost when the Lit debug flag\n * is off and perfectly accurate for the render layer.\n *\n * `globalThis.emitLitDebugLogEvents = true` gates the events, and lit-html\n * dispatches a CustomEvent per render whenever it's set — a real per-render\n * cost. So the caller drives the flag via {@link setRenderDebugEnabled} from\n * `recording × layer-enabled` rather than leaving it on for the whole session.\n * The events are tagged `*Unstable` in the Lit source; we tolerate missing\n * `kind` values gracefully.\n *\n * begin render / end render share a numeric `id` → we use it as groupId so\n * the panel can show a duration bar for each render call.\n */\n\nimport type {TimelineEvent} from '../../../types/timeline.js';\nimport {idOf, sourceOf} from './identity.js';\nimport {now} from './clock.js';\n\ntype EmitFn = (event: TimelineEvent) => void;\ntype RecordingFn = () => boolean;\ntype LayerEnabledFn = () => boolean;\n\n/** Minimal shape of the lit-debug event detail we care about. */\ninterface LitDebugDetail {\n kind: string;\n id?: number;\n template?: unknown;\n instance?: unknown;\n /** Render options passed to lit-html's `render()`; `host` is the element. */\n options?: {host?: unknown};\n}\n\n/**\n * Builds the element-identity `meta` for a render event from the render\n * `host` (the LitElement whose `render()` produced these debug events).\n * Uses the same id/source maps as the lifecycle layer so an element keeps one\n * stable id across both layers. Returns undefined for host-less renders\n * (e.g. a bare lit-html `render()` call not driven by a LitElement).\n */\nconst hostMeta = (\n host: unknown\n): NonNullable<TimelineEvent['meta']> | undefined => {\n if (host === null || typeof host !== 'object') return undefined;\n return {\n elementId: idOf(host),\n tagName: (host as Element).localName ?? 'unknown',\n source: sourceOf(host),\n };\n};\n\ntype LitDebugEvent = CustomEvent<LitDebugDetail>;\n\nlet removeListener: (() => void) | null = null;\n\nconst onLitDebug = (\n e: Event,\n emit: EmitFn,\n recording: RecordingFn,\n layerEnabled: LayerEnabledFn\n): void => {\n if (!recording() || !layerEnabled()) return;\n\n const detail = (e as LitDebugEvent).detail;\n if (!detail?.kind) return;\n\n const time = now();\n const {kind, id} = detail;\n\n switch (kind) {\n case 'begin render': {\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render',\n time,\n groupId: id,\n title: 'render:start',\n subtitle: meta?.tagName,\n data: {kind, id},\n meta,\n });\n break;\n }\n\n case 'end render': {\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render',\n time,\n groupId: id,\n title: 'render:end',\n subtitle: meta?.tagName,\n data: {kind, id},\n meta,\n });\n break;\n }\n\n // `template prep` fires once per *unique* template, the first time it's\n // compiled — low volume, useful as a \"new template\" marker.\n case 'template prep':\n emit({\n layerId: 'lit-render',\n time,\n title: kind,\n data: {kind, id},\n });\n break;\n\n // `template updating` / `template instantiated` / `…and updated` fire once\n // per template-bound ChildPart on *every* render — extremely high volume\n // (a ticking clock or animation floods the layer). The begin/end render\n // pair above already captures each render as a grouped duration, so these\n // add noise without signal. Skip them; re-expose behind a \"verbose\" toggle\n // if per-part detail is ever needed. Same rationale for commit * / set part.\n default:\n break;\n }\n};\n\n/**\n * Install the lit-debug render layer.\n * Idempotent — calling again when already installed is a no-op.\n */\nexport const installRenderLayer = (\n emit: EmitFn,\n recording: RecordingFn,\n layerEnabled: LayerEnabledFn\n): void => {\n if (removeListener !== null) return;\n\n // The listener is cheap and always attached; the per-render cost lives in the\n // `emitLitDebugLogEvents` flag, which the caller toggles via\n // `setRenderDebugEnabled` only while actively capturing.\n const handler = (e: Event) => onLitDebug(e, emit, recording, layerEnabled);\n window.addEventListener('lit-debug', handler);\n removeListener = () => window.removeEventListener('lit-debug', handler);\n};\n\n/**\n * Toggle Lit's debug event system. Enabling makes lit-html dispatch a\n * CustomEvent on every render (dev-only; no-op in prod builds), so the caller\n * keeps it off unless the render layer is actively recording.\n */\nexport const setRenderDebugEnabled = (enabled: boolean): void => {\n (globalThis as {emitLitDebugLogEvents?: boolean}).emitLitDebugLogEvents =\n enabled;\n};\n\n/** Remove the listener and clear the Lit debug flag. */\nexport const uninstallRenderLayer = (): void => {\n removeListener?.();\n removeListener = null;\n setRenderDebugEnabled(false);\n};\n"]}
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * lit-render timeline layers — listen to the built-in lit-debug CustomEvents\n * instead of patching prototypes, so they're zero-cost when the Lit debug\n * flag is off and perfectly accurate for the render layers.\n *\n * `globalThis.emitLitDebugLogEvents = true` gates the events, and lit-html\n * dispatches a CustomEvent per render whenever it's set — a real per-render\n * cost. So the caller drives the flag via {@link setRenderDebugEnabled} from\n * `recording × (lit-render OR lit-render-verbose enabled)` rather than\n * leaving it on for the whole session.\n * The events are tagged `*Unstable` in the Lit source; we tolerate missing\n * `kind` values gracefully.\n *\n * begin render / end render share a numeric `id` → we use it as groupId so\n * the panel can show a duration bar for each render call.\n *\n * `template updating` / `template instantiated[ and updated]` / `set part` /\n * `commit *` fire once per template-bound part on *every* render — extremely\n * high volume (a ticking clock or animation floods the layer). They're\n * emitted on the separate opt-in `lit-render-verbose` layer rather than\n * `lit-render`, so the common case (the begin/end render duration bar) stays\n * quiet by default.\n */\n\nimport type {TimelineEvent} from '../../../types/timeline.js';\nimport {idOf, sourceOf} from './identity.js';\nimport {now} from './clock.js';\n\ntype EmitFn = (event: TimelineEvent) => void;\ntype RecordingFn = () => boolean;\ntype LayerEnabledFn = () => boolean;\n\n/**\n * Minimal shape of the lit-debug event detail we care about. Broader than any\n * single `kind` needs — see lit-html's `LitUnstable.DebugLog.Entry` union in\n * `packages/lit-html/src/lit-html.ts` for the exact per-kind shapes; fields\n * below are the ones this layer reads across all of them.\n */\ninterface LitDebugDetail {\n kind: string;\n id?: number;\n template?: unknown;\n instance?: unknown;\n /** Render options passed to lit-html's `render()`; `host` is the element. */\n options?: {host?: unknown};\n /** `template updating` / `template instantiated[ and updated]`. */\n values?: unknown[];\n /** `set part` / most `commit *` kinds. */\n value?: unknown;\n /**\n * `set part` only. Unlike every other kind, `set part` carries no\n * top-level `options` — the host lives on the `Part` instance itself\n * (`ChildPart` / `AttributePart` / `EventPart` / `ElementPart` all expose a\n * public `options: RenderOptions | undefined`), so the host is derived via\n * {@link partHost} instead of `options?.host` directly.\n */\n part?: {options?: {host?: unknown}};\n /** `set part`. */\n valueIndex?: number;\n /** `commit attribute` / `commit property` / `commit boolean attribute` /\n * `commit event listener`. */\n name?: string;\n /** `commit event listener`. */\n addListener?: boolean;\n removeListener?: boolean;\n}\n\n/**\n * Builds the element-identity `meta` for a render event from the render\n * `host` (the LitElement whose `render()` produced these debug events).\n * Uses the same id/source maps as the lifecycle layer so an element keeps one\n * stable id across both layers. Returns undefined for host-less renders\n * (e.g. a bare lit-html `render()` call not driven by a LitElement).\n */\nconst hostMeta = (\n host: unknown\n): NonNullable<TimelineEvent['meta']> | undefined => {\n if (host === null || typeof host !== 'object') return undefined;\n return {\n elementId: idOf(host),\n tagName: (host as Element).localName ?? 'unknown',\n source: sourceOf(host),\n };\n};\n\n/** Reads the render `host` off a `Part` for the one kind (`set part`) whose\n * detail carries the part instead of the render `options` directly. */\nconst partHost = (part: LitDebugDetail['part']): unknown => part?.options?.host;\n\ntype LitDebugEvent = CustomEvent<LitDebugDetail>;\n\nlet removeListener: (() => void) | null = null;\n\n/**\n * Reduces an arbitrary lit-html binding value to a small JSON-serializable\n * summary for the verbose layer's `data` field. Never puts the value itself\n * in `data` — it may be a DOM Node, a TemplateResult, a function, or hold a\n * reference back to the host element, none of which survive (or belong in) a\n * structured-clone over the HMR channel.\n */\nconst describeValue = (value: unknown): string => {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value === 'string') {\n return value.length > 40\n ? `string:\"${value.slice(0, 40)}…\"`\n : `string:\"${value}\"`;\n }\n if (typeof value === 'number') return `number:${value}`;\n if (typeof value === 'boolean') return `boolean:${value}`;\n if (typeof value === 'function') {\n return `function:${value.name || 'anonymous'}`;\n }\n // lit's sentinels (`nothing`, `noChange`) are symbols.\n if (typeof value === 'symbol') return `symbol:${value.description ?? ''}`;\n if (Array.isArray(value)) return `array(${value.length})`;\n if (typeof value === 'object' && '_$litType$' in value) return 'template';\n if (typeof Node !== 'undefined' && value instanceof Node) {\n const tag = (value as Partial<Element>).tagName?.toLowerCase();\n return tag ? `node:<${tag}>` : `node:#${value.nodeType}`;\n }\n const ctor = (value as {constructor?: {name?: string}})?.constructor?.name;\n return `object:${ctor ?? 'Object'}`;\n};\n\n/** Summarizes each entry of a template-instance's `values` array. */\nconst describeValues = (values: unknown[] | undefined): string[] | undefined =>\n values?.map(describeValue);\n\nconst onLitDebug = (\n e: Event,\n emit: EmitFn,\n recording: RecordingFn,\n renderEnabled: LayerEnabledFn,\n verboseEnabled: LayerEnabledFn\n): void => {\n if (!recording() || (!renderEnabled() && !verboseEnabled())) return;\n\n const detail = (e as LitDebugEvent).detail;\n if (!detail?.kind) return;\n\n const time = now();\n const {kind, id} = detail;\n\n switch (kind) {\n case 'begin render': {\n if (!renderEnabled()) break;\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render',\n time,\n groupId: id,\n title: 'render:start',\n subtitle: meta?.tagName,\n data: {kind, id},\n meta,\n });\n break;\n }\n\n case 'end render': {\n if (!renderEnabled()) break;\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render',\n time,\n groupId: id,\n title: 'render:end',\n subtitle: meta?.tagName,\n data: {kind, id},\n meta,\n });\n break;\n }\n\n // `template prep` fires once per *unique* template, the first time it's\n // compiled — low volume, useful as a \"new template\" marker.\n case 'template prep':\n if (!renderEnabled()) break;\n emit({\n layerId: 'lit-render',\n time,\n title: kind,\n data: {kind, id},\n });\n break;\n\n // The events below fire once per template-bound part on *every* render —\n // extremely high volume (a ticking clock or animation floods the layer).\n // The begin/end render pair above already captures each render as a\n // grouped duration, so these live on the separate opt-in\n // `lit-render-verbose` layer instead of adding noise to `lit-render`.\n case 'template updating':\n case 'template instantiated':\n case 'template instantiated and updated': {\n if (!verboseEnabled()) break;\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render-verbose',\n time,\n title: kind,\n subtitle: meta?.tagName,\n data: {kind, values: describeValues(detail.values)},\n meta,\n });\n break;\n }\n\n case 'set part': {\n if (!verboseEnabled()) break;\n const meta = hostMeta(partHost(detail.part));\n emit({\n layerId: 'lit-render-verbose',\n time,\n title: kind,\n subtitle: meta?.tagName,\n data: {\n kind,\n valueIndex: detail.valueIndex,\n value: describeValue(detail.value),\n },\n meta,\n });\n break;\n }\n\n case 'commit nothing to child': {\n if (!verboseEnabled()) break;\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render-verbose',\n time,\n title: kind,\n subtitle: meta?.tagName,\n data: {kind},\n meta,\n });\n break;\n }\n\n case 'commit text':\n case 'commit node':\n case 'commit to element binding': {\n if (!verboseEnabled()) break;\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render-verbose',\n time,\n title: kind,\n subtitle: meta?.tagName,\n data: {kind, value: describeValue(detail.value)},\n meta,\n });\n break;\n }\n\n case 'commit attribute':\n case 'commit property':\n case 'commit boolean attribute': {\n if (!verboseEnabled()) break;\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render-verbose',\n time,\n title: kind,\n subtitle: meta?.tagName,\n data: {kind, name: detail.name, value: describeValue(detail.value)},\n meta,\n });\n break;\n }\n\n case 'commit event listener': {\n if (!verboseEnabled()) break;\n const meta = hostMeta(detail.options?.host);\n emit({\n layerId: 'lit-render-verbose',\n time,\n title: kind,\n subtitle: meta?.tagName,\n data: {\n kind,\n name: detail.name,\n addListener: detail.addListener,\n removeListener: detail.removeListener,\n },\n meta,\n });\n break;\n }\n\n // Unknown or future `*Unstable` kind — ignore rather than guess a shape.\n default:\n break;\n }\n};\n\n/**\n * Install the lit-debug render layers (`lit-render` and the opt-in\n * `lit-render-verbose`).\n * Idempotent — calling again when already installed is a no-op.\n */\nexport const installRenderLayer = (\n emit: EmitFn,\n recording: RecordingFn,\n renderEnabled: LayerEnabledFn,\n verboseEnabled: LayerEnabledFn\n): void => {\n if (removeListener !== null) return;\n\n // The listener is cheap and always attached; the per-render cost lives in the\n // `emitLitDebugLogEvents` flag, which the caller toggles via\n // `setRenderDebugEnabled` only while actively capturing.\n const handler = (e: Event) =>\n onLitDebug(e, emit, recording, renderEnabled, verboseEnabled);\n window.addEventListener('lit-debug', handler);\n removeListener = () => window.removeEventListener('lit-debug', handler);\n};\n\n/**\n * Toggle Lit's debug event system. Enabling makes lit-html dispatch a\n * CustomEvent on every render (dev-only; no-op in prod builds), so the caller\n * keeps it off unless the render layer is actively recording.\n */\nexport const setRenderDebugEnabled = (enabled: boolean): void => {\n (globalThis as {emitLitDebugLogEvents?: boolean}).emitLitDebugLogEvents =\n enabled;\n};\n\n/** Remove the listener and clear the Lit debug flag. */\nexport const uninstallRenderLayer = (): void => {\n removeListener?.();\n removeListener = null;\n setRenderDebugEnabled(false);\n};\n"]}
|
|
@@ -6,21 +6,18 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Buffered transport for timeline events.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Phase 2 migration: replace `sendViaHmr` with `sendViaRpc` when the
|
|
14
|
-
* devframe RPC client is available (injected by the panel's clientScript).
|
|
9
|
+
* Forwards events over the page channel (`../page-channel.ts`), which is
|
|
10
|
+
* Vite's HMR WebSocket by default and can be pointed at a standalone
|
|
11
|
+
* devframe server instead. Which carrier is in use is not this module's
|
|
12
|
+
* concern.
|
|
15
13
|
*
|
|
16
14
|
* Batching: we coalesce events with `queueMicrotask` so a burst of
|
|
17
15
|
* commit/set-part events from a single render doesn't hammer the channel
|
|
18
16
|
* with one message per event.
|
|
19
17
|
*/
|
|
20
18
|
import type { TimelineEvent } from '../../../types/timeline.js';
|
|
21
|
-
type
|
|
22
|
-
|
|
23
|
-
};
|
|
19
|
+
import type { PageTransport } from '../page-channel.js';
|
|
20
|
+
type HotClient = Pick<PageTransport, 'send'>;
|
|
24
21
|
/**
|
|
25
22
|
* Register the HMR hot client.
|
|
26
23
|
* Called by install.ts after import.meta.hot check.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/transport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/transport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEtD,KAAK,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAmB7C;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAAS,SAAS,KAAG,IAe7C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,OAAQ,MAAM,IAAI,KAAG,IAMrD,CAAC;AAaF;;;;GAIG;AACH,eAAO,MAAM,IAAI,UAAW,aAAa,KAAG,IAc3C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/transport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/transport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoBH,MAAM,KAAK,GAAoB,EAAE,CAAC;AAClC,IAAI,cAAc,GAAG,KAAK,CAAC;AAC3B,IAAI,SAAS,GAAqB,IAAI,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB,kFAAkF;AAClF,oEAAoE;AACpE,MAAM,gBAAgB,GAAsB,EAAE,CAAC;AAE/C;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAc,EAAQ,EAAE;IACnD,SAAS,GAAG,GAAG,CAAC;IAChB,6DAA6D;IAC7D,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;QACxC,cAAc,GAAG,IAAI,CAAC;QACtB,cAAc,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IACD,iEAAiE;IACjE,KAAK,MAAM,EAAE,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,EAAE,EAAE,CAAC;QACP,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,EAAc,EAAQ,EAAE;IAC3D,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,cAAc,CAAC,EAAE,CAAC,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,GAAS,EAAE;IACvB,cAAc,GAAG,KAAK,CAAC;IACvB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAC,MAAM,EAAE,KAAK,EAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,kEAAkE;IACpE,CAAC;AACH,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,KAAoB,EAAQ,EAAE;IACjD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,uEAAuE;QACvE,wEAAwE;QACxE,IAAI,KAAK,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;YAC/B,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO;IACT,CAAC;IACD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,IAAI,CAAC;QACtB,cAAc,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;AACH,CAAC,CAAC","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Buffered transport for timeline events.\n *\n * Forwards events over the page channel (`../page-channel.ts`), which is\n * Vite's HMR WebSocket by default and can be pointed at a standalone\n * devframe server instead. Which carrier is in use is not this module's\n * concern.\n *\n * Batching: we coalesce events with `queueMicrotask` so a burst of\n * commit/set-part events from a single render doesn't hammer the channel\n * with one message per event.\n */\n\nimport type {TimelineEvent} from '../../../types/timeline.js';\nimport type {PageTransport} from '../page-channel.js';\n\ntype HotClient = Pick<PageTransport, 'send'>;\n\nconst queue: TimelineEvent[] = [];\nlet flushScheduled = false;\nlet hotClient: HotClient | null = null;\n\n/**\n * Cap on events retained while no hot client is connected. Without a client\n * `flush()` cannot drain the queue, so an uncapped buffer would grow for the\n * whole session (in production, or before `install.ts` wires the client).\n * We keep only the most recent events; the retained tail flushes if a client\n * later connects.\n */\nconst MAX_PENDING = 1000;\n\n// Callbacks registered before the hot client was available (e.g. addTimelineLayer\n// called at module init time before install.ts has set the client).\nconst pendingCallbacks: Array<() => void> = [];\n\n/**\n * Register the HMR hot client.\n * Called by install.ts after import.meta.hot check.\n * Flushes any queued events and fires pending one-shot callbacks.\n */\nexport const setHotClient = (hot: HotClient): void => {\n hotClient = hot;\n // Drain any events that arrived before the client was wired.\n if (queue.length > 0 && !flushScheduled) {\n flushScheduled = true;\n queueMicrotask(flush);\n }\n // Fire one-shot callbacks (e.g. addTimelineLayer announcements).\n for (const cb of pendingCallbacks.splice(0)) {\n try {\n cb();\n } catch {\n // ignore\n }\n }\n};\n\n/**\n * Register a callback to be invoked once the HMR client is available.\n * If the client is already set the callback is fired on the next microtask.\n */\nexport const setHotClientCallback = (cb: () => void): void => {\n if (hotClient !== null) {\n queueMicrotask(cb);\n } else {\n pendingCallbacks.push(cb);\n }\n};\n\nconst flush = (): void => {\n flushScheduled = false;\n if (queue.length === 0 || hotClient === null) return;\n const batch = queue.splice(0);\n try {\n hotClient.send('lit:timeline:push-event', {events: batch});\n } catch {\n // HMR channel may be temporarily unavailable; events are dropped.\n }\n};\n\n/**\n * Enqueue a timeline event for forwarding to the Vite server.\n * Batches via microtask so a single synchronous render burst\n * doesn't send dozens of individual messages.\n */\nexport const emit = (event: TimelineEvent): void => {\n queue.push(event);\n if (hotClient === null) {\n // No consumer yet: bound the buffer instead of scheduling a flush that\n // would only no-op. `setHotClient` drains the retained tail on connect.\n if (queue.length > MAX_PENDING) {\n queue.splice(0, queue.length - MAX_PENDING);\n }\n return;\n }\n if (!flushScheduled) {\n flushScheduled = true;\n queueMicrotask(flush);\n }\n};\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The Timeline's element and regex filters, as pure functions.
|
|
8
|
+
*
|
|
9
|
+
* Both presentations (the list and the tracks) answer "show me only this
|
|
10
|
+
* element" or "only things matching /update/". The filter state therefore
|
|
11
|
+
* lives in `timeline-view` and each presentation applies it here, so the two
|
|
12
|
+
* cannot drift apart on what a match means.
|
|
13
|
+
*/
|
|
14
|
+
import type { TimelineEvent } from '../../types/timeline.js';
|
|
15
|
+
import type { TimelineSpan } from './derive.js';
|
|
16
|
+
/**
|
|
17
|
+
* Compiles the regex box. An empty box matches everything. A pattern that
|
|
18
|
+
* does not compile (the user is mid-typing `foo(`) disables the filter rather
|
|
19
|
+
* than hiding every row, and is reported as `invalid` so the input can say so.
|
|
20
|
+
*/
|
|
21
|
+
export declare const compileRegex: (source: string) => {
|
|
22
|
+
re: RegExp | null;
|
|
23
|
+
invalid: boolean;
|
|
24
|
+
};
|
|
25
|
+
/** Text the regex filter searches: element tag, name, subtitle and the
|
|
26
|
+
* changed property keys (the reason is worth searching for by name). */
|
|
27
|
+
export declare const spanHaystack: (span: TimelineSpan) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Applies the element and regex filters. `re` comes from {@link compileRegex}
|
|
30
|
+
* so a caller filtering on every keystroke compiles once, not per span.
|
|
31
|
+
*/
|
|
32
|
+
export declare const filterSpans: (spans: readonly TimelineSpan[], elementId: number | null, re: RegExp | null) => TimelineSpan[];
|
|
33
|
+
/** Distinct elements (by stable id) seen across the recorded events. */
|
|
34
|
+
export declare const listElements: (events: readonly TimelineEvent[]) => Array<{
|
|
35
|
+
id: number;
|
|
36
|
+
tag: string;
|
|
37
|
+
}>;
|
|
38
|
+
//# sourceMappingURL=filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/lib/timeline/filter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,YAAY,WACf,MAAM,KACb;IAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAOtC,CAAC;AAEF;yEACyE;AACzE,eAAO,MAAM,YAAY,SAAU,YAAY,KAAG,MAGnC,CAAC;AAEhB;;;GAGG;AACH,eAAO,MAAM,WAAW,UACf,SAAS,YAAY,EAAE,aACnB,MAAM,GAAG,IAAI,MACpB,MAAM,GAAG,IAAI,KAChB,YAAY,EAKZ,CAAC;AAEJ,wEAAwE;AACxE,eAAO,MAAM,YAAY,WACf,SAAS,aAAa,EAAE,KAC/B,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,CASjC,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Compiles the regex box. An empty box matches everything. A pattern that
|
|
8
|
+
* does not compile (the user is mid-typing `foo(`) disables the filter rather
|
|
9
|
+
* than hiding every row, and is reported as `invalid` so the input can say so.
|
|
10
|
+
*/
|
|
11
|
+
export const compileRegex = (source) => {
|
|
12
|
+
if (source === '') return {re: null, invalid: false};
|
|
13
|
+
try {
|
|
14
|
+
return {re: new RegExp(source, 'i'), invalid: false};
|
|
15
|
+
} catch {
|
|
16
|
+
return {re: null, invalid: true};
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** Text the regex filter searches: element tag, name, subtitle and the
|
|
20
|
+
* changed property keys (the reason is worth searching for by name). */
|
|
21
|
+
export const spanHaystack = (span) =>
|
|
22
|
+
`${span.meta?.tagName ?? ''} ${span.name} ${span.subtitle ?? ''} ${(span.changed ?? []).join(' ')}`;
|
|
23
|
+
/**
|
|
24
|
+
* Applies the element and regex filters. `re` comes from {@link compileRegex}
|
|
25
|
+
* so a caller filtering on every keystroke compiles once, not per span.
|
|
26
|
+
*/
|
|
27
|
+
export const filterSpans = (spans, elementId, re) =>
|
|
28
|
+
spans.filter(
|
|
29
|
+
(span) =>
|
|
30
|
+
(elementId === null || span.meta?.elementId === elementId) &&
|
|
31
|
+
(re === null || re.test(spanHaystack(span)))
|
|
32
|
+
);
|
|
33
|
+
/** Distinct elements (by stable id) seen across the recorded events. */
|
|
34
|
+
export const listElements = (events) => {
|
|
35
|
+
const seen = new Map();
|
|
36
|
+
for (const ev of events) {
|
|
37
|
+
const id = ev.meta?.elementId;
|
|
38
|
+
if (id != null && !seen.has(id)) {
|
|
39
|
+
seen.set(id, ev.meta?.tagName ?? 'unknown');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return [...seen].map(([id, tag]) => ({id, tag}));
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/lib/timeline/filter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,MAAc,EACyB,EAAE;IACzC,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,EAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAC,CAAC;IACrD,IAAI,CAAC;QACH,OAAO,EAAC,EAAE,EAAE,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAC,CAAC;IACnC,CAAC;AACH,CAAC,CAAC;AAEF;yEACyE;AACzE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAkB,EAAU,EAAE,CACzD,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,IAAI,CACjE,IAAI,CAAC,OAAO,IAAI,EAAE,CACnB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AAEhB;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,KAA8B,EAC9B,SAAwB,EACxB,EAAiB,EACD,EAAE,CAClB,KAAK,CAAC,MAAM,CACV,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,SAAS,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,KAAK,SAAS,CAAC;IAC1D,CAAC,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAC/C,CAAC;AAEJ,wEAAwE;AACxE,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,MAAgC,EACE,EAAE;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC;QAC9B,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,IAAI,SAAS,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,EAAE,EAAE,GAAG,EAAC,CAAC,CAAC,CAAC;AACnD,CAAC,CAAC","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * The Timeline's element and regex filters, as pure functions.\n *\n * Both presentations (the list and the tracks) answer \"show me only this\n * element\" or \"only things matching /update/\". The filter state therefore\n * lives in `timeline-view` and each presentation applies it here, so the two\n * cannot drift apart on what a match means.\n */\n\nimport type {TimelineEvent} from '../../types/timeline.js';\nimport type {TimelineSpan} from './derive.js';\n\n/**\n * Compiles the regex box. An empty box matches everything. A pattern that\n * does not compile (the user is mid-typing `foo(`) disables the filter rather\n * than hiding every row, and is reported as `invalid` so the input can say so.\n */\nexport const compileRegex = (\n source: string\n): {re: RegExp | null; invalid: boolean} => {\n if (source === '') return {re: null, invalid: false};\n try {\n return {re: new RegExp(source, 'i'), invalid: false};\n } catch {\n return {re: null, invalid: true};\n }\n};\n\n/** Text the regex filter searches: element tag, name, subtitle and the\n * changed property keys (the reason is worth searching for by name). */\nexport const spanHaystack = (span: TimelineSpan): string =>\n `${span.meta?.tagName ?? ''} ${span.name} ${span.subtitle ?? ''} ${(\n span.changed ?? []\n ).join(' ')}`;\n\n/**\n * Applies the element and regex filters. `re` comes from {@link compileRegex}\n * so a caller filtering on every keystroke compiles once, not per span.\n */\nexport const filterSpans = (\n spans: readonly TimelineSpan[],\n elementId: number | null,\n re: RegExp | null\n): TimelineSpan[] =>\n spans.filter(\n (span) =>\n (elementId === null || span.meta?.elementId === elementId) &&\n (re === null || re.test(spanHaystack(span)))\n );\n\n/** Distinct elements (by stable id) seen across the recorded events. */\nexport const listElements = (\n events: readonly TimelineEvent[]\n): Array<{id: number; tag: string}> => {\n const seen = new Map<number, string>();\n for (const ev of events) {\n const id = ev.meta?.elementId;\n if (id != null && !seen.has(id)) {\n seen.set(id, ev.meta?.tagName ?? 'unknown');\n }\n }\n return [...seen].map(([id, tag]) => ({id, tag}));\n};\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Layout for the Timeline's track view: one horizontal lane per layer on a
|
|
8
|
+
* shared time axis.
|
|
9
|
+
*
|
|
10
|
+
* The list answers "what happened, in what order, to whom". It cannot show
|
|
11
|
+
* concurrency or rhythm — which layers fire together, how a click lines up
|
|
12
|
+
* with the update tick it caused, how long a tick is next to its neighbours.
|
|
13
|
+
* This module turns the same {@link TimelineSpan}s into lanes and rows, and
|
|
14
|
+
* owns the arithmetic between milliseconds and pixels, so the element that
|
|
15
|
+
* draws them (`panel/timeline-tracks.ts`) is only DOM.
|
|
16
|
+
*
|
|
17
|
+
* Pure and dependency-free, like `derive.ts`, so it is unit-tested without a
|
|
18
|
+
* browser.
|
|
19
|
+
*/
|
|
20
|
+
import type { TimelineSpan } from './derive.js';
|
|
21
|
+
/**
|
|
22
|
+
* Spans in one row of a lane, sorted by start and never overlapping, so
|
|
23
|
+
* {@link visibleRange} can binary-search them.
|
|
24
|
+
*/
|
|
25
|
+
export type TrackRow = TimelineSpan[];
|
|
26
|
+
/** One lane: every span of one layer, packed into non-overlapping rows. */
|
|
27
|
+
export interface Track {
|
|
28
|
+
layerId: string;
|
|
29
|
+
rows: TrackRow[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Where a span stops occupying its row. A point event occupies only its own
|
|
33
|
+
* instant; a span whose end has not arrived (still running, or its end fell
|
|
34
|
+
* out of the buffer) occupies the rest of the recording, because that is
|
|
35
|
+
* where it is drawn — up to the live edge.
|
|
36
|
+
*/
|
|
37
|
+
export declare const occupiedEnd: (span: TimelineSpan) => number;
|
|
38
|
+
/**
|
|
39
|
+
* Groups spans into one track per layer, in `layers` order, then any layer
|
|
40
|
+
* the spans mention that `layers` does not (a custom layer announced after the
|
|
41
|
+
* last `get-meta`), in first-seen order. A layer with no spans still gets an
|
|
42
|
+
* empty track, so a lane does not appear and vanish as events arrive.
|
|
43
|
+
*
|
|
44
|
+
* Within a track, spans are packed greedily: each goes into the first row
|
|
45
|
+
* whose last span has ended by the time it starts. Lit's lifecycle phases
|
|
46
|
+
* nest, so one update tick lands as `performUpdate` on row 0 and
|
|
47
|
+
* `willUpdate` / `update` / `updated` side by side on row 1 — the flame shape
|
|
48
|
+
* falls out of the packing without modelling it.
|
|
49
|
+
*/
|
|
50
|
+
export declare const buildTracks: (spans: readonly TimelineSpan[], layers: readonly {
|
|
51
|
+
id: string;
|
|
52
|
+
}[]) => Track[];
|
|
53
|
+
/**
|
|
54
|
+
* The recording's time bounds over `spans`: the earliest start, and the
|
|
55
|
+
* latest instant anything is known to have happened (an end, or the start of
|
|
56
|
+
* a span with none). The origin is the earliest start rather than zero
|
|
57
|
+
* because the panel's buffer is capped and rolls its oldest events off; a
|
|
58
|
+
* long recording would otherwise open on an empty stretch.
|
|
59
|
+
*/
|
|
60
|
+
export declare const timeBounds: (spans: readonly TimelineSpan[]) => {
|
|
61
|
+
origin: number;
|
|
62
|
+
extent: number;
|
|
63
|
+
};
|
|
64
|
+
/** Deepest zoom, in multiples of "fit": about a microsecond per pixel on a
|
|
65
|
+
* second-long recording, past which there is nothing left to separate. */
|
|
66
|
+
export declare const MAX_ZOOM = 100000;
|
|
67
|
+
/** Mapping between recording time and pixels for the current zoom and pan. */
|
|
68
|
+
export interface TimeScale {
|
|
69
|
+
pxPerMs: number;
|
|
70
|
+
/** First and last millisecond inside the plot. */
|
|
71
|
+
start: number;
|
|
72
|
+
end: number;
|
|
73
|
+
toX(ms: number): number;
|
|
74
|
+
toMs(x: number): number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Builds the scale for a plot `width` pixels wide showing a recording that
|
|
78
|
+
* begins at `originMs` and runs for `extentMs`.
|
|
79
|
+
*
|
|
80
|
+
* `zoom` is a multiple of "fit" (1 shows the whole recording); `pan` is how
|
|
81
|
+
* many milliseconds past the origin the left edge sits. Both are clamped, so
|
|
82
|
+
* callers can apply a raw wheel or drag delta and read back where it landed.
|
|
83
|
+
*/
|
|
84
|
+
export declare const timeScale: (originMs: number, extentMs: number, width: number, zoom?: number, pan?: number) => TimeScale;
|
|
85
|
+
export declare const clampZoom: (zoom: number) => number;
|
|
86
|
+
/** Keeps the visible window inside the recording. */
|
|
87
|
+
export declare const clampPan: (pan: number, extentMs: number, zoom: number) => number;
|
|
88
|
+
/** The pan that puts the live edge (the end of the recording) at the right
|
|
89
|
+
* of the plot, for following a recording in progress. */
|
|
90
|
+
export declare const livePan: (extentMs: number, zoom: number) => number;
|
|
91
|
+
/**
|
|
92
|
+
* Zooms by `factor` while keeping the millisecond under pixel `x` where it
|
|
93
|
+
* is, which is what a wheel over a timeline is expected to do.
|
|
94
|
+
*/
|
|
95
|
+
export declare const zoomAt: (originMs: number, extentMs: number, width: number, zoom: number, pan: number, x: number, factor: number) => {
|
|
96
|
+
zoom: number;
|
|
97
|
+
pan: number;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Index range `[from, to)` of the spans in `row` that intersect the window
|
|
101
|
+
* `[start, end]`, found by binary search so a zoomed-in lane renders a
|
|
102
|
+
* handful of marks rather than walking thousands.
|
|
103
|
+
*
|
|
104
|
+
* Only the span just before the first one starting inside the window can
|
|
105
|
+
* reach into it, because a row's spans never overlap.
|
|
106
|
+
*/
|
|
107
|
+
export declare const visibleRange: (row: TrackRow, start: number, end: number) => [number, number];
|
|
108
|
+
/**
|
|
109
|
+
* A round tick interval (1, 2 or 5 times a power of ten, in milliseconds) at
|
|
110
|
+
* least `minMs` long, for axis labels that land on readable numbers.
|
|
111
|
+
*/
|
|
112
|
+
export declare const niceStep: (minMs: number) => number;
|
|
113
|
+
//# sourceMappingURL=tracks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracks.d.ts","sourceRoot":"","sources":["../../src/lib/timeline/tracks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;AAEtC,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,SAAU,YAAY,KAAG,MAGhD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,UACf,SAAS,YAAY,EAAE,UACtB,SAAS;IAAC,EAAE,EAAE,MAAM,CAAA;CAAC,EAAE,KAC9B,KAAK,EAeP,CAAC;AAwBF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,UACd,SAAS,YAAY,EAAE,KAC7B;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CASjC,CAAC;AAEF;2EAC2E;AAC3E,eAAO,MAAM,QAAQ,SAAU,CAAC;AAKhC,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,aACV,MAAM,YACN,MAAM,SACT,MAAM,kCAGZ,SAcF,CAAC;AAEF,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,MACuB,CAAC;AAEjE,qDAAqD;AACrD,eAAO,MAAM,QAAQ,QACd,MAAM,YACD,MAAM,QACV,MAAM,KACX,MAIF,CAAC;AAEF;0DAC0D;AAC1D,eAAO,MAAM,OAAO,aAAc,MAAM,QAAQ,MAAM,KAAG,MACrB,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,MAAM,aACP,MAAM,YACN,MAAM,SACT,MAAM,QACP,MAAM,OACP,MAAM,KACR,MAAM,UACD,MAAM,KACb;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAO5B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,QAClB,QAAQ,SACN,MAAM,OACR,MAAM,KACV,CAAC,MAAM,EAAE,MAAM,CAgBjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,UAAW,MAAM,KAAG,MAOxC,CAAC"}
|