@oddsquad/vite-plugin-lit 0.4.0 → 0.6.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 +85 -0
- package/README.md +16 -5
- package/bin.mjs +16 -12
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-CdtPhBL-.js → browser-agent-rpc-BXhoSh1z-DXAVFObe.js} +2 -2
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-CdtPhBL-.js.map → browser-agent-rpc-BXhoSh1z-DXAVFObe.js.map} +1 -1
- package/dist/client/assets/{index-DVe6M4HV.js → index-DVr5EnyB.js} +1859 -751
- package/dist/client/assets/index-DVr5EnyB.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 +21 -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 -9
- package/lib/devframe/vite.js.map +1 -1
- package/lib/options.d.ts +6 -1
- package/lib/options.d.ts.map +1 -1
- package/lib/options.js +65 -18
- package/lib/options.js.map +1 -1
- 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.map +1 -1
- package/lib/plugin.js +8 -0
- package/lib/plugin.js.map +1 -1
- package/lib/plugins/open-in-editor.d.ts.map +1 -1
- package/lib/plugins/open-in-editor.js +6 -1
- package/lib/plugins/open-in-editor.js.map +1 -1
- package/lib/runtime/indicator.js.map +1 -1
- package/lib/runtime/inspector/install.js +4 -2
- 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 +13 -3
- package/lib/runtime/source-overlay/overlay-element.js.map +1 -1
- package/lib/runtime/timeline/chrome-tracks.d.ts +22 -0
- package/lib/runtime/timeline/chrome-tracks.d.ts.map +1 -0
- package/lib/runtime/timeline/chrome-tracks.js +74 -0
- package/lib/runtime/timeline/chrome-tracks.js.map +1 -0
- package/lib/runtime/timeline/clock.d.ts +6 -0
- package/lib/runtime/timeline/clock.d.ts.map +1 -1
- package/lib/runtime/timeline/clock.js +6 -0
- package/lib/runtime/timeline/clock.js.map +1 -1
- package/lib/runtime/timeline/install.js +42 -13
- package/lib/runtime/timeline/install.js.map +1 -1
- package/lib/runtime/timeline/public-api.d.ts.map +1 -1
- package/lib/runtime/timeline/public-api.js +11 -3
- package/lib/runtime/timeline/public-api.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 +6 -1
- package/types/timeline.d.ts +44 -0
- package/types/timeline.d.ts.map +1 -1
- package/types/timeline.js +2 -0
- package/types/timeline.js.map +1 -1
- package/dist/client/assets/index-DVe6M4HV.js.map +0 -1
|
@@ -1044,103 +1044,12 @@ var setColorSchemePreference = (pref) => {
|
|
|
1044
1044
|
localStorage.setItem(COLOR_SCHEME_LS_KEY, pref);
|
|
1045
1045
|
} catch {}
|
|
1046
1046
|
applyColorScheme(pref);
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1054
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1055
|
-
}
|
|
1056
|
-
//#endregion
|
|
1057
|
-
//#region src/panel/timeline-layers.ts
|
|
1058
|
-
/**
|
|
1059
|
-
* @license
|
|
1060
|
-
* Copyright 2026 Google LLC
|
|
1061
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
1062
|
-
*/
|
|
1063
|
-
var TimelineLayers = class TimelineLayers extends i$2 {
|
|
1064
|
-
constructor(..._args) {
|
|
1065
|
-
super(..._args);
|
|
1066
|
-
this.layers = [];
|
|
1067
|
-
}
|
|
1068
|
-
static {
|
|
1069
|
-
this.styles = [tokens, i$5`
|
|
1070
|
-
:host {
|
|
1071
|
-
display: flex;
|
|
1072
|
-
gap: var(--lit-devtools-space-2);
|
|
1073
|
-
padding: var(--lit-devtools-space-3) var(--lit-devtools-space-5);
|
|
1074
|
-
border-bottom: 1px solid var(--lit-devtools-border);
|
|
1075
|
-
background: var(--lit-devtools-surface-low);
|
|
1076
|
-
flex-shrink: 0;
|
|
1077
|
-
flex-wrap: wrap;
|
|
1078
|
-
}
|
|
1079
|
-
button {
|
|
1080
|
-
display: flex;
|
|
1081
|
-
align-items: center;
|
|
1082
|
-
gap: var(--lit-devtools-space-2);
|
|
1083
|
-
padding: var(--lit-devtools-space-2) var(--lit-devtools-space-4);
|
|
1084
|
-
border-radius: var(--lit-devtools-radius-pill);
|
|
1085
|
-
background: var(--lit-devtools-surface-elevated);
|
|
1086
|
-
border: 1px solid transparent;
|
|
1087
|
-
color: var(--lit-devtools-text-secondary);
|
|
1088
|
-
font-size: var(--lit-devtools-text-2xs);
|
|
1089
|
-
cursor: pointer;
|
|
1090
|
-
user-select: none;
|
|
1091
|
-
transition: opacity var(--lit-devtools-dur-fast)
|
|
1092
|
-
var(--lit-devtools-ease-standard);
|
|
1093
|
-
}
|
|
1094
|
-
button.on {
|
|
1095
|
-
color: var(--lit-devtools-text);
|
|
1096
|
-
}
|
|
1097
|
-
button:hover {
|
|
1098
|
-
background: var(--lit-devtools-surface-hover);
|
|
1099
|
-
}
|
|
1100
|
-
.dot {
|
|
1101
|
-
width: 8px;
|
|
1102
|
-
height: 8px;
|
|
1103
|
-
border-radius: 50%;
|
|
1104
|
-
flex-shrink: 0;
|
|
1105
|
-
opacity: 0.4;
|
|
1106
|
-
}
|
|
1107
|
-
button.on .dot {
|
|
1108
|
-
opacity: 1;
|
|
1109
|
-
}
|
|
1110
|
-
`];
|
|
1111
|
-
}
|
|
1112
|
-
_toggle(id) {
|
|
1113
|
-
this.dispatchEvent(new CustomEvent("layer-toggle", {
|
|
1114
|
-
detail: { id },
|
|
1115
|
-
bubbles: true,
|
|
1116
|
-
composed: true
|
|
1117
|
-
}));
|
|
1118
|
-
}
|
|
1119
|
-
render() {
|
|
1120
|
-
return b`${this.layers.map((l) => b`
|
|
1121
|
-
<button
|
|
1122
|
-
class=${l.enabled ? "on" : ""}
|
|
1123
|
-
title=${l.enabled ? `Hide ${l.label}` : `Show ${l.label}`}
|
|
1124
|
-
@click=${() => this._toggle(l.id)}
|
|
1125
|
-
>
|
|
1126
|
-
<span
|
|
1127
|
-
class="dot"
|
|
1128
|
-
style=${"background:#" + l.color.toString(16).padStart(6, "0")}
|
|
1129
|
-
></span>
|
|
1130
|
-
${l.label}
|
|
1131
|
-
</button>
|
|
1132
|
-
`)}`;
|
|
1133
|
-
}
|
|
1134
|
-
};
|
|
1135
|
-
__decorate([n$3({ type: Array })], TimelineLayers.prototype, "layers", void 0);
|
|
1136
|
-
TimelineLayers = __decorate([t$2("timeline-layers")], TimelineLayers);
|
|
1137
|
-
//#endregion
|
|
1138
|
-
//#region node_modules/.pnpm/lit-html@3.3.3/node_modules/lit-html/directive-helpers.js
|
|
1139
|
-
/**
|
|
1140
|
-
* @license
|
|
1141
|
-
* Copyright 2020 Google LLC
|
|
1142
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
1143
|
-
*/ var { I: t$1 } = j$1, i$1 = (o) => o, r$1 = (o) => void 0 === o.strings, s$1 = () => document.createComment(""), v = (o, n, e) => {
|
|
1047
|
+
}, { I: t$1 } = j$1, i$1 = (o) => o, r$1 = (o) => void 0 === o.strings, s$1 = () => document.createComment(""), v = (o, n, e) => {
|
|
1048
|
+
/**
|
|
1049
|
+
* @license
|
|
1050
|
+
* Copyright 2020 Google LLC
|
|
1051
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
1052
|
+
*/
|
|
1144
1053
|
const l = o._$AA.parentNode, d = void 0 === n ? o._$AB : n._$AA;
|
|
1145
1054
|
if (void 0 === e) e = new t$1(l.insertBefore(s$1(), d), l.insertBefore(s$1(), d), o, o.options);
|
|
1146
1055
|
else {
|
|
@@ -1281,13 +1190,488 @@ var n = e$1(class extends f {
|
|
|
1281
1190
|
get lt() {
|
|
1282
1191
|
return "function" == typeof this.G ? o.get(this.ht ?? globalThis)?.get(this.G) : this.G?.value;
|
|
1283
1192
|
}
|
|
1284
|
-
disconnected() {
|
|
1285
|
-
this.lt === this.ct && this.rt(void 0);
|
|
1193
|
+
disconnected() {
|
|
1194
|
+
this.lt === this.ct && this.rt(void 0);
|
|
1195
|
+
}
|
|
1196
|
+
reconnected() {
|
|
1197
|
+
this.rt(this.ct);
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
//#endregion
|
|
1201
|
+
//#region src/lib/timeline/derive.ts
|
|
1202
|
+
/**
|
|
1203
|
+
* @license
|
|
1204
|
+
* Copyright 2026 Google LLC
|
|
1205
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
1206
|
+
*/
|
|
1207
|
+
/** Layer whose events describe Lit update ticks. */
|
|
1208
|
+
var LIFECYCLE_LAYER_ID = "lit-lifecycle";
|
|
1209
|
+
/** Layers whose events are developer input, for {@link attributeInput}. */
|
|
1210
|
+
var INPUT_LAYER_IDS = ["mouse", "keyboard"];
|
|
1211
|
+
/** The phase that brackets an entire update tick. */
|
|
1212
|
+
var ROOT_PHASE = "performUpdate";
|
|
1213
|
+
var START_SUFFIX = ":start";
|
|
1214
|
+
var END_SUFFIX = ":end";
|
|
1215
|
+
/** Reads `data.changed` defensively — `data` is `unknown` on the wire. */
|
|
1216
|
+
var changedOf = (event) => {
|
|
1217
|
+
const data = event.data;
|
|
1218
|
+
if (data === null || typeof data !== "object") return void 0;
|
|
1219
|
+
const changed = data.changed;
|
|
1220
|
+
if (!Array.isArray(changed)) return void 0;
|
|
1221
|
+
const keys = changed.filter((key) => typeof key === "string");
|
|
1222
|
+
return keys.length > 0 ? keys : void 0;
|
|
1223
|
+
};
|
|
1224
|
+
var pointSpan = (event, index) => ({
|
|
1225
|
+
layerId: event.layerId,
|
|
1226
|
+
key: `${event.layerId}:point:${index}`,
|
|
1227
|
+
name: event.title ?? event.layerId,
|
|
1228
|
+
start: event.time,
|
|
1229
|
+
changed: changedOf(event),
|
|
1230
|
+
subtitle: event.subtitle,
|
|
1231
|
+
logType: event.logType,
|
|
1232
|
+
meta: event.meta,
|
|
1233
|
+
events: [event]
|
|
1234
|
+
});
|
|
1235
|
+
/**
|
|
1236
|
+
* Collapses `:start` / `:end` event pairs into spans, passing anything else
|
|
1237
|
+
* through as a point span.
|
|
1238
|
+
*
|
|
1239
|
+
* Pairing is keyed on **layer + groupId + phase name**, never on `groupId`
|
|
1240
|
+
* alone: a whole update tick shares one groupId (`runtime/timeline/
|
|
1241
|
+
* lifecycle.ts` builds it as `${elementId}:${tick}`), so matching on the id
|
|
1242
|
+
* would marry `willUpdate:start` to `performUpdate:end`. Each phase occurs at
|
|
1243
|
+
* most once per tick, which makes the triple unique.
|
|
1244
|
+
*
|
|
1245
|
+
* Unmatched halves survive rather than disappear — the buffer is a bounded
|
|
1246
|
+
* ring, so its oldest end and its newest start routinely have no partner, and
|
|
1247
|
+
* an update that is still running has a genuinely open span.
|
|
1248
|
+
*/
|
|
1249
|
+
var toSpans = (events) => {
|
|
1250
|
+
const spans = [];
|
|
1251
|
+
const open = /* @__PURE__ */ new Map();
|
|
1252
|
+
events.forEach((event, index) => {
|
|
1253
|
+
const title = event.title ?? event.layerId;
|
|
1254
|
+
const isStart = title.endsWith(START_SUFFIX);
|
|
1255
|
+
const isEnd = !isStart && title.endsWith(END_SUFFIX);
|
|
1256
|
+
if (event.groupId === void 0 || !isStart && !isEnd) {
|
|
1257
|
+
spans.push(pointSpan(event, index));
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
const name = title.slice(0, -(isStart ? 6 : 4));
|
|
1261
|
+
const key = `${event.layerId}:${event.groupId}:${name}`;
|
|
1262
|
+
if (isStart) {
|
|
1263
|
+
const span = {
|
|
1264
|
+
layerId: event.layerId,
|
|
1265
|
+
key,
|
|
1266
|
+
name,
|
|
1267
|
+
groupId: event.groupId,
|
|
1268
|
+
start: event.time,
|
|
1269
|
+
changed: changedOf(event),
|
|
1270
|
+
subtitle: event.subtitle,
|
|
1271
|
+
logType: event.logType,
|
|
1272
|
+
meta: event.meta,
|
|
1273
|
+
events: [event]
|
|
1274
|
+
};
|
|
1275
|
+
open.set(key, span);
|
|
1276
|
+
spans.push(span);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const span = open.get(key);
|
|
1280
|
+
if (span === void 0) {
|
|
1281
|
+
spans.push(pointSpan(event, index));
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
open.delete(key);
|
|
1285
|
+
span.events.push(event);
|
|
1286
|
+
if (event.time >= span.start) {
|
|
1287
|
+
span.end = event.time;
|
|
1288
|
+
span.duration = event.time - span.start;
|
|
1289
|
+
}
|
|
1290
|
+
span.changed ??= changedOf(event);
|
|
1291
|
+
});
|
|
1292
|
+
return spans.sort((a, b) => a.start - b.start);
|
|
1293
|
+
};
|
|
1294
|
+
/**
|
|
1295
|
+
* Groups lifecycle spans into update ticks.
|
|
1296
|
+
*
|
|
1297
|
+
* `duration` is taken from the `performUpdate` span alone, never from a sum of
|
|
1298
|
+
* the phases: `willUpdate` / `update` / `updated` all run *inside*
|
|
1299
|
+
* `performUpdate`, so summing counts the same milliseconds several times over.
|
|
1300
|
+
*
|
|
1301
|
+
* `changed` is the union across the tick's phases because only the phases that
|
|
1302
|
+
* receive a `PropertyValues` argument carry it — `performUpdate` takes none,
|
|
1303
|
+
* so the bracketing span is always silent about the reason.
|
|
1304
|
+
*/
|
|
1305
|
+
var toUpdateCycles = (spans) => {
|
|
1306
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
1307
|
+
for (const span of spans) {
|
|
1308
|
+
const elementId = span.meta?.elementId;
|
|
1309
|
+
if (span.layerId !== LIFECYCLE_LAYER_ID || span.groupId === void 0 || elementId === void 0) continue;
|
|
1310
|
+
const key = String(span.groupId);
|
|
1311
|
+
let cycle = byGroup.get(key);
|
|
1312
|
+
if (cycle === void 0) {
|
|
1313
|
+
cycle = {
|
|
1314
|
+
key,
|
|
1315
|
+
elementId,
|
|
1316
|
+
tagName: span.meta?.tagName ?? "unknown",
|
|
1317
|
+
source: span.meta?.source,
|
|
1318
|
+
start: span.start,
|
|
1319
|
+
changed: [],
|
|
1320
|
+
phases: []
|
|
1321
|
+
};
|
|
1322
|
+
byGroup.set(key, cycle);
|
|
1323
|
+
}
|
|
1324
|
+
cycle.phases.push(span);
|
|
1325
|
+
if (span.start < cycle.start) cycle.start = span.start;
|
|
1326
|
+
if (span.name === ROOT_PHASE) cycle.duration = span.duration;
|
|
1327
|
+
for (const changedKey of span.changed ?? []) if (!cycle.changed.includes(changedKey)) cycle.changed.push(changedKey);
|
|
1328
|
+
}
|
|
1329
|
+
return [...byGroup.values()].sort((a, b) => a.start - b.start);
|
|
1330
|
+
};
|
|
1331
|
+
/**
|
|
1332
|
+
* Totals update cycles per component tag.
|
|
1333
|
+
*
|
|
1334
|
+
* Grouped by tag rather than by instance because the question this answers is
|
|
1335
|
+
* "which *component* re-renders too much" — a list of 200 rows each updating
|
|
1336
|
+
* twice is one component's problem, not 200. The instance ids are kept so the
|
|
1337
|
+
* caller can still drill in.
|
|
1338
|
+
*/
|
|
1339
|
+
var rollup = (cycles) => {
|
|
1340
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
1341
|
+
for (const cycle of cycles) {
|
|
1342
|
+
let record = byTag.get(cycle.tagName);
|
|
1343
|
+
if (record === void 0) {
|
|
1344
|
+
record = {
|
|
1345
|
+
entry: {
|
|
1346
|
+
tagName: cycle.tagName,
|
|
1347
|
+
elementIds: [],
|
|
1348
|
+
updates: 0,
|
|
1349
|
+
totalMs: 0,
|
|
1350
|
+
maxMs: 0,
|
|
1351
|
+
reasons: [],
|
|
1352
|
+
source: cycle.source
|
|
1353
|
+
},
|
|
1354
|
+
reasons: /* @__PURE__ */ new Map()
|
|
1355
|
+
};
|
|
1356
|
+
byTag.set(cycle.tagName, record);
|
|
1357
|
+
}
|
|
1358
|
+
const { entry, reasons } = record;
|
|
1359
|
+
entry.updates++;
|
|
1360
|
+
if (!entry.elementIds.includes(cycle.elementId)) entry.elementIds.push(cycle.elementId);
|
|
1361
|
+
entry.source ??= cycle.source;
|
|
1362
|
+
if (cycle.duration !== void 0) {
|
|
1363
|
+
entry.totalMs += cycle.duration;
|
|
1364
|
+
if (cycle.duration > entry.maxMs) entry.maxMs = cycle.duration;
|
|
1365
|
+
}
|
|
1366
|
+
for (const key of cycle.changed) reasons.set(key, (reasons.get(key) ?? 0) + 1);
|
|
1367
|
+
}
|
|
1368
|
+
const entries = [];
|
|
1369
|
+
for (const { entry, reasons } of byTag.values()) {
|
|
1370
|
+
entry.reasons = [...reasons].map(([key, count]) => ({
|
|
1371
|
+
key,
|
|
1372
|
+
count
|
|
1373
|
+
})).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
1374
|
+
entries.push(entry);
|
|
1375
|
+
}
|
|
1376
|
+
return entries.sort((a, b) => b.totalMs - a.totalMs || b.updates - a.updates);
|
|
1377
|
+
};
|
|
1378
|
+
/**
|
|
1379
|
+
* Attributes each update cycle to the input event that preceded it, within
|
|
1380
|
+
* `windowMs`.
|
|
1381
|
+
*
|
|
1382
|
+
* This is what the mouse and keyboard layers are for. On their own they
|
|
1383
|
+
* duplicate what the browser's own event log already shows; joined to the
|
|
1384
|
+
* updates they precede they answer the question the raw layers only imply —
|
|
1385
|
+
* an update with a cause is an interaction, and an update without one is a
|
|
1386
|
+
* timer, a signal, or a stray `requestUpdate()`.
|
|
1387
|
+
*
|
|
1388
|
+
* Returns a new array; cycles with no cause are returned as-is.
|
|
1389
|
+
*/
|
|
1390
|
+
var attributeInput = (cycles, events, windowMs = 200) => {
|
|
1391
|
+
const inputs = events.filter((event) => INPUT_LAYER_IDS.includes(event.layerId)).sort((a, b) => a.time - b.time);
|
|
1392
|
+
if (inputs.length === 0) return [...cycles];
|
|
1393
|
+
let cursor = 0;
|
|
1394
|
+
return cycles.map((cycle) => {
|
|
1395
|
+
while (cursor + 1 < inputs.length && inputs[cursor + 1].time <= cycle.start) cursor++;
|
|
1396
|
+
const candidate = inputs[cursor];
|
|
1397
|
+
const delta = cycle.start - candidate.time;
|
|
1398
|
+
if (delta < 0 || delta > windowMs) return cycle;
|
|
1399
|
+
return {
|
|
1400
|
+
...cycle,
|
|
1401
|
+
cause: {
|
|
1402
|
+
layerId: candidate.layerId,
|
|
1403
|
+
type: candidate.title ?? candidate.layerId,
|
|
1404
|
+
detail: candidate.subtitle,
|
|
1405
|
+
time: candidate.time
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
});
|
|
1409
|
+
};
|
|
1410
|
+
//#endregion
|
|
1411
|
+
//#region src/lib/timeline/filter.ts
|
|
1412
|
+
/**
|
|
1413
|
+
* @license
|
|
1414
|
+
* Copyright 2026 Google LLC
|
|
1415
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
1416
|
+
*/
|
|
1417
|
+
/**
|
|
1418
|
+
* Compiles the regex box. An empty box matches everything. A pattern that
|
|
1419
|
+
* does not compile (the user is mid-typing `foo(`) disables the filter rather
|
|
1420
|
+
* than hiding every row, and is reported as `invalid` so the input can say so.
|
|
1421
|
+
*/
|
|
1422
|
+
var compileRegex = (source) => {
|
|
1423
|
+
if (source === "") return {
|
|
1424
|
+
re: null,
|
|
1425
|
+
invalid: false
|
|
1426
|
+
};
|
|
1427
|
+
try {
|
|
1428
|
+
return {
|
|
1429
|
+
re: new RegExp(source, "i"),
|
|
1430
|
+
invalid: false
|
|
1431
|
+
};
|
|
1432
|
+
} catch {
|
|
1433
|
+
return {
|
|
1434
|
+
re: null,
|
|
1435
|
+
invalid: true
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
/** Text the regex filter searches: element tag, name, subtitle and the
|
|
1440
|
+
* changed property keys (the reason is worth searching for by name). */
|
|
1441
|
+
var spanHaystack = (span) => `${span.meta?.tagName ?? ""} ${span.name} ${span.subtitle ?? ""} ${(span.changed ?? []).join(" ")}`;
|
|
1442
|
+
/**
|
|
1443
|
+
* Applies the element and regex filters. `re` comes from {@link compileRegex}
|
|
1444
|
+
* so a caller filtering on every keystroke compiles once, not per span.
|
|
1445
|
+
*/
|
|
1446
|
+
var filterSpans = (spans, elementId, re) => spans.filter((span) => (elementId === null || span.meta?.elementId === elementId) && (re === null || re.test(spanHaystack(span))));
|
|
1447
|
+
/** Distinct elements (by stable id) seen across the recorded events. */
|
|
1448
|
+
var listElements = (events) => {
|
|
1449
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1450
|
+
for (const ev of events) {
|
|
1451
|
+
const id = ev.meta?.elementId;
|
|
1452
|
+
if (id != null && !seen.has(id)) seen.set(id, ev.meta?.tagName ?? "unknown");
|
|
1453
|
+
}
|
|
1454
|
+
return [...seen].map(([id, tag]) => ({
|
|
1455
|
+
id,
|
|
1456
|
+
tag
|
|
1457
|
+
}));
|
|
1458
|
+
};
|
|
1459
|
+
//#endregion
|
|
1460
|
+
//#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorate.js
|
|
1461
|
+
function __decorate(decorators, target, key, desc) {
|
|
1462
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1463
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1464
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1465
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1466
|
+
}
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/lib/segmented-tabs.ts
|
|
1469
|
+
/**
|
|
1470
|
+
* @license
|
|
1471
|
+
* Copyright 2026 Google LLC
|
|
1472
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
1473
|
+
*/
|
|
1474
|
+
var SegmentedTabs = class SegmentedTabs extends i$2 {
|
|
1475
|
+
static {
|
|
1476
|
+
this.styles = [tokens, i$5`
|
|
1477
|
+
:host {
|
|
1478
|
+
display: flex;
|
|
1479
|
+
gap: var(--lit-devtools-space-1);
|
|
1480
|
+
align-self: stretch;
|
|
1481
|
+
}
|
|
1482
|
+
button {
|
|
1483
|
+
appearance: none;
|
|
1484
|
+
display: flex;
|
|
1485
|
+
align-items: center;
|
|
1486
|
+
gap: var(--lit-devtools-space-2);
|
|
1487
|
+
border: 0;
|
|
1488
|
+
background: none;
|
|
1489
|
+
font: inherit;
|
|
1490
|
+
color: var(--lit-devtools-text-muted);
|
|
1491
|
+
padding: 0 var(--lit-devtools-space-5);
|
|
1492
|
+
cursor: pointer;
|
|
1493
|
+
border-bottom: 2px solid transparent;
|
|
1494
|
+
white-space: nowrap;
|
|
1495
|
+
transition:
|
|
1496
|
+
color var(--lit-devtools-dur-fast) var(--lit-devtools-ease-standard),
|
|
1497
|
+
border-color var(--lit-devtools-dur-fast)
|
|
1498
|
+
var(--lit-devtools-ease-standard),
|
|
1499
|
+
background var(--lit-devtools-dur-fast)
|
|
1500
|
+
var(--lit-devtools-ease-standard);
|
|
1501
|
+
}
|
|
1502
|
+
button:hover {
|
|
1503
|
+
color: var(--lit-devtools-text);
|
|
1504
|
+
background: var(--lit-devtools-surface-hover);
|
|
1505
|
+
}
|
|
1506
|
+
button.active {
|
|
1507
|
+
color: var(--lit-devtools-accent);
|
|
1508
|
+
border-bottom-color: var(--lit-devtools-accent);
|
|
1509
|
+
}
|
|
1510
|
+
button:focus-visible {
|
|
1511
|
+
outline: 2px solid var(--lit-devtools-accent-ring);
|
|
1512
|
+
outline-offset: -2px;
|
|
1513
|
+
}
|
|
1514
|
+
button svg {
|
|
1515
|
+
width: 18px;
|
|
1516
|
+
height: 18px;
|
|
1517
|
+
flex-shrink: 0;
|
|
1518
|
+
}
|
|
1519
|
+
.badge {
|
|
1520
|
+
flex-shrink: 0;
|
|
1521
|
+
min-width: 16px;
|
|
1522
|
+
padding: 0 var(--lit-devtools-space-1);
|
|
1523
|
+
border-radius: var(--lit-devtools-radius-pill);
|
|
1524
|
+
background: var(--lit-devtools-error-soft);
|
|
1525
|
+
color: var(--lit-devtools-error);
|
|
1526
|
+
font-size: var(--lit-devtools-text-xs);
|
|
1527
|
+
line-height: 16px;
|
|
1528
|
+
text-align: center;
|
|
1529
|
+
}
|
|
1530
|
+
:host([size='sm']) button {
|
|
1531
|
+
font-size: var(--lit-devtools-text-xs);
|
|
1532
|
+
padding: 0 var(--lit-devtools-space-4);
|
|
1533
|
+
}
|
|
1534
|
+
:host([size='md']) button,
|
|
1535
|
+
:host(:not([size])) button {
|
|
1536
|
+
font-size: var(--lit-devtools-text-sm);
|
|
1537
|
+
padding: 0 var(--lit-devtools-space-5);
|
|
1538
|
+
}
|
|
1539
|
+
`];
|
|
1540
|
+
}
|
|
1541
|
+
constructor() {
|
|
1542
|
+
super();
|
|
1543
|
+
this.items = [];
|
|
1544
|
+
this.value = "";
|
|
1545
|
+
this.size = "md";
|
|
1546
|
+
}
|
|
1547
|
+
_select(id) {
|
|
1548
|
+
this.value = id;
|
|
1549
|
+
this.dispatchEvent(new CustomEvent("change", {
|
|
1550
|
+
detail: { value: id },
|
|
1551
|
+
bubbles: true,
|
|
1552
|
+
composed: true
|
|
1553
|
+
}));
|
|
1554
|
+
}
|
|
1555
|
+
render() {
|
|
1556
|
+
return b`
|
|
1557
|
+
${this.items.map((item) => b`
|
|
1558
|
+
<button
|
|
1559
|
+
role="tab"
|
|
1560
|
+
aria-selected=${item.id === this.value}
|
|
1561
|
+
class=${item.id === this.value ? "active" : ""}
|
|
1562
|
+
@click=${() => this._select(item.id)}
|
|
1563
|
+
>
|
|
1564
|
+
${item.icon ? b`<svg
|
|
1565
|
+
viewBox="0 0 256 256"
|
|
1566
|
+
fill="currentColor"
|
|
1567
|
+
aria-hidden="true"
|
|
1568
|
+
>
|
|
1569
|
+
<path d=${item.icon} />
|
|
1570
|
+
</svg>` : A}
|
|
1571
|
+
${item.label}
|
|
1572
|
+
${item.badge ? b`<span class="badge">${item.badge}</span>` : A}
|
|
1573
|
+
</button>
|
|
1574
|
+
`)}
|
|
1575
|
+
`;
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
__decorate([n$3({ type: Array })], SegmentedTabs.prototype, "items", void 0);
|
|
1579
|
+
__decorate([n$3()], SegmentedTabs.prototype, "value", void 0);
|
|
1580
|
+
__decorate([n$3()], SegmentedTabs.prototype, "size", void 0);
|
|
1581
|
+
SegmentedTabs = __decorate([t$2("segmented-tabs")], SegmentedTabs);
|
|
1582
|
+
//#endregion
|
|
1583
|
+
//#region src/panel/timeline-layers.ts
|
|
1584
|
+
/**
|
|
1585
|
+
* @license
|
|
1586
|
+
* Copyright 2026 Google LLC
|
|
1587
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
1588
|
+
*/
|
|
1589
|
+
/** A layer's `0xRRGGBB` colour as a CSS hex string. */
|
|
1590
|
+
var hexColor = (color) => "#" + color.toString(16).padStart(6, "0");
|
|
1591
|
+
/** CSS colour of layer `id`, grey for a layer `layers` does not know. */
|
|
1592
|
+
var layerColor = (layers, id) => {
|
|
1593
|
+
const layer = layers.find((l) => l.id === id);
|
|
1594
|
+
return layer ? hexColor(layer.color) : "#888";
|
|
1595
|
+
};
|
|
1596
|
+
var TimelineLayers = class TimelineLayers extends i$2 {
|
|
1597
|
+
constructor(..._args) {
|
|
1598
|
+
super(..._args);
|
|
1599
|
+
this.layers = [];
|
|
1600
|
+
this.caption = "";
|
|
1601
|
+
}
|
|
1602
|
+
static {
|
|
1603
|
+
this.styles = [tokens, i$5`
|
|
1604
|
+
:host {
|
|
1605
|
+
display: flex;
|
|
1606
|
+
gap: var(--lit-devtools-space-2);
|
|
1607
|
+
padding: var(--lit-devtools-space-3) var(--lit-devtools-space-5);
|
|
1608
|
+
border-bottom: 1px solid var(--lit-devtools-border);
|
|
1609
|
+
background: var(--lit-devtools-surface-low);
|
|
1610
|
+
flex-shrink: 0;
|
|
1611
|
+
flex-wrap: wrap;
|
|
1612
|
+
}
|
|
1613
|
+
button {
|
|
1614
|
+
display: flex;
|
|
1615
|
+
align-items: center;
|
|
1616
|
+
gap: var(--lit-devtools-space-2);
|
|
1617
|
+
padding: var(--lit-devtools-space-2) var(--lit-devtools-space-4);
|
|
1618
|
+
border-radius: var(--lit-devtools-radius-pill);
|
|
1619
|
+
background: var(--lit-devtools-surface-elevated);
|
|
1620
|
+
border: 1px solid transparent;
|
|
1621
|
+
color: var(--lit-devtools-text-secondary);
|
|
1622
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
1623
|
+
cursor: pointer;
|
|
1624
|
+
user-select: none;
|
|
1625
|
+
transition: opacity var(--lit-devtools-dur-fast)
|
|
1626
|
+
var(--lit-devtools-ease-standard);
|
|
1627
|
+
}
|
|
1628
|
+
button.on {
|
|
1629
|
+
color: var(--lit-devtools-text);
|
|
1630
|
+
}
|
|
1631
|
+
button:hover {
|
|
1632
|
+
background: var(--lit-devtools-surface-hover);
|
|
1633
|
+
}
|
|
1634
|
+
.caption {
|
|
1635
|
+
align-self: center;
|
|
1636
|
+
color: var(--lit-devtools-text-muted);
|
|
1637
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
1638
|
+
margin-right: var(--lit-devtools-space-2);
|
|
1639
|
+
}
|
|
1640
|
+
.dot {
|
|
1641
|
+
width: 8px;
|
|
1642
|
+
height: 8px;
|
|
1643
|
+
border-radius: 50%;
|
|
1644
|
+
flex-shrink: 0;
|
|
1645
|
+
opacity: 0.4;
|
|
1646
|
+
}
|
|
1647
|
+
button.on .dot {
|
|
1648
|
+
opacity: 1;
|
|
1649
|
+
}
|
|
1650
|
+
`];
|
|
1651
|
+
}
|
|
1652
|
+
_toggle(id) {
|
|
1653
|
+
this.dispatchEvent(new CustomEvent("layer-toggle", {
|
|
1654
|
+
detail: { id },
|
|
1655
|
+
bubbles: true,
|
|
1656
|
+
composed: true
|
|
1657
|
+
}));
|
|
1286
1658
|
}
|
|
1287
|
-
|
|
1288
|
-
this.
|
|
1659
|
+
render() {
|
|
1660
|
+
return b`${this.caption ? b`<span class="caption">${this.caption}</span>` : A}${this.layers.map((l) => b`
|
|
1661
|
+
<button
|
|
1662
|
+
class=${l.enabled ? "on" : ""}
|
|
1663
|
+
title=${l.enabled ? `Hide ${l.label}` : `Show ${l.label}`}
|
|
1664
|
+
@click=${() => this._toggle(l.id)}
|
|
1665
|
+
>
|
|
1666
|
+
<span class="dot" style=${"background:" + hexColor(l.color)}></span>
|
|
1667
|
+
${l.label}
|
|
1668
|
+
</button>
|
|
1669
|
+
`)}`;
|
|
1289
1670
|
}
|
|
1290
|
-
}
|
|
1671
|
+
};
|
|
1672
|
+
__decorate([n$3({ type: Array })], TimelineLayers.prototype, "layers", void 0);
|
|
1673
|
+
__decorate([n$3()], TimelineLayers.prototype, "caption", void 0);
|
|
1674
|
+
TimelineLayers = __decorate([t$2("timeline-layers")], TimelineLayers);
|
|
1291
1675
|
//#endregion
|
|
1292
1676
|
//#region node_modules/.pnpm/lit-html@3.3.3/node_modules/lit-html/directives/repeat.js
|
|
1293
1677
|
/**
|
|
@@ -2223,264 +2607,54 @@ var VirtualizeDirective = class extends f {
|
|
|
2223
2607
|
}
|
|
2224
2608
|
update(part, [config]) {
|
|
2225
2609
|
this._setFunctions(config);
|
|
2226
|
-
const itemsChanged = this._items !== config.items;
|
|
2227
|
-
this._items = config.items || [];
|
|
2228
|
-
if (this._virtualizer) this._updateVirtualizerConfig(part, config);
|
|
2229
|
-
else this._initialize(part, config);
|
|
2230
|
-
return itemsChanged ? E : this.render();
|
|
2231
|
-
}
|
|
2232
|
-
async _updateVirtualizerConfig(part, config) {
|
|
2233
|
-
if (!await this._virtualizer.updateLayoutConfig(config.layout || {})) {
|
|
2234
|
-
const hostElement = part.parentNode;
|
|
2235
|
-
this._makeVirtualizer(hostElement, config);
|
|
2236
|
-
}
|
|
2237
|
-
this._virtualizer.items = this._items;
|
|
2238
|
-
}
|
|
2239
|
-
_setFunctions(config) {
|
|
2240
|
-
const { renderItem, keyFunction } = config;
|
|
2241
|
-
if (renderItem) this._renderItem = (item, idx) => renderItem(item, idx + this._first);
|
|
2242
|
-
if (keyFunction) this._keyFunction = (item, idx) => keyFunction(item, idx + this._first);
|
|
2243
|
-
}
|
|
2244
|
-
_makeVirtualizer(hostElement, config) {
|
|
2245
|
-
if (this._virtualizer) this._virtualizer.disconnected();
|
|
2246
|
-
const { layout, scroller, items } = config;
|
|
2247
|
-
this._virtualizer = new Virtualizer({
|
|
2248
|
-
hostElement,
|
|
2249
|
-
layout,
|
|
2250
|
-
scroller
|
|
2251
|
-
});
|
|
2252
|
-
this._virtualizer.items = items;
|
|
2253
|
-
this._virtualizer.connected();
|
|
2254
|
-
}
|
|
2255
|
-
_initialize(part, config) {
|
|
2256
|
-
const hostElement = part.parentNode;
|
|
2257
|
-
if (hostElement && hostElement.nodeType === 1) {
|
|
2258
|
-
hostElement.addEventListener("rangeChanged", (e) => {
|
|
2259
|
-
this._first = e.first;
|
|
2260
|
-
this._last = e.last;
|
|
2261
|
-
this.setValue(this.render());
|
|
2262
|
-
});
|
|
2263
|
-
this._makeVirtualizer(hostElement, config);
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
disconnected() {
|
|
2267
|
-
this._virtualizer?.disconnected();
|
|
2268
|
-
}
|
|
2269
|
-
reconnected() {
|
|
2270
|
-
this._virtualizer?.connected();
|
|
2271
|
-
}
|
|
2272
|
-
};
|
|
2273
|
-
var virtualize = e$1(VirtualizeDirective);
|
|
2274
|
-
//#endregion
|
|
2275
|
-
//#region src/lib/timeline/derive.ts
|
|
2276
|
-
/**
|
|
2277
|
-
* @license
|
|
2278
|
-
* Copyright 2026 Google LLC
|
|
2279
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
2280
|
-
*/
|
|
2281
|
-
/** Layer whose events describe Lit update ticks. */
|
|
2282
|
-
var LIFECYCLE_LAYER_ID = "lit-lifecycle";
|
|
2283
|
-
/** Layers whose events are developer input, for {@link attributeInput}. */
|
|
2284
|
-
var INPUT_LAYER_IDS = ["mouse", "keyboard"];
|
|
2285
|
-
/** The phase that brackets an entire update tick. */
|
|
2286
|
-
var ROOT_PHASE = "performUpdate";
|
|
2287
|
-
var START_SUFFIX = ":start";
|
|
2288
|
-
var END_SUFFIX = ":end";
|
|
2289
|
-
/** Reads `data.changed` defensively — `data` is `unknown` on the wire. */
|
|
2290
|
-
var changedOf = (event) => {
|
|
2291
|
-
const data = event.data;
|
|
2292
|
-
if (data === null || typeof data !== "object") return void 0;
|
|
2293
|
-
const changed = data.changed;
|
|
2294
|
-
if (!Array.isArray(changed)) return void 0;
|
|
2295
|
-
const keys = changed.filter((key) => typeof key === "string");
|
|
2296
|
-
return keys.length > 0 ? keys : void 0;
|
|
2297
|
-
};
|
|
2298
|
-
var pointSpan = (event, index) => ({
|
|
2299
|
-
layerId: event.layerId,
|
|
2300
|
-
key: `${event.layerId}:point:${index}`,
|
|
2301
|
-
name: event.title ?? event.layerId,
|
|
2302
|
-
start: event.time,
|
|
2303
|
-
changed: changedOf(event),
|
|
2304
|
-
subtitle: event.subtitle,
|
|
2305
|
-
logType: event.logType,
|
|
2306
|
-
meta: event.meta,
|
|
2307
|
-
events: [event]
|
|
2308
|
-
});
|
|
2309
|
-
/**
|
|
2310
|
-
* Collapses `:start` / `:end` event pairs into spans, passing anything else
|
|
2311
|
-
* through as a point span.
|
|
2312
|
-
*
|
|
2313
|
-
* Pairing is keyed on **layer + groupId + phase name**, never on `groupId`
|
|
2314
|
-
* alone: a whole update tick shares one groupId (`runtime/timeline/
|
|
2315
|
-
* lifecycle.ts` builds it as `${elementId}:${tick}`), so matching on the id
|
|
2316
|
-
* would marry `willUpdate:start` to `performUpdate:end`. Each phase occurs at
|
|
2317
|
-
* most once per tick, which makes the triple unique.
|
|
2318
|
-
*
|
|
2319
|
-
* Unmatched halves survive rather than disappear — the buffer is a bounded
|
|
2320
|
-
* ring, so its oldest end and its newest start routinely have no partner, and
|
|
2321
|
-
* an update that is still running has a genuinely open span.
|
|
2322
|
-
*/
|
|
2323
|
-
var toSpans = (events) => {
|
|
2324
|
-
const spans = [];
|
|
2325
|
-
const open = /* @__PURE__ */ new Map();
|
|
2326
|
-
events.forEach((event, index) => {
|
|
2327
|
-
const title = event.title ?? event.layerId;
|
|
2328
|
-
const isStart = title.endsWith(START_SUFFIX);
|
|
2329
|
-
const isEnd = !isStart && title.endsWith(END_SUFFIX);
|
|
2330
|
-
if (event.groupId === void 0 || !isStart && !isEnd) {
|
|
2331
|
-
spans.push(pointSpan(event, index));
|
|
2332
|
-
return;
|
|
2333
|
-
}
|
|
2334
|
-
const name = title.slice(0, -(isStart ? 6 : 4));
|
|
2335
|
-
const key = `${event.layerId}:${event.groupId}:${name}`;
|
|
2336
|
-
if (isStart) {
|
|
2337
|
-
const span = {
|
|
2338
|
-
layerId: event.layerId,
|
|
2339
|
-
key,
|
|
2340
|
-
name,
|
|
2341
|
-
groupId: event.groupId,
|
|
2342
|
-
start: event.time,
|
|
2343
|
-
changed: changedOf(event),
|
|
2344
|
-
subtitle: event.subtitle,
|
|
2345
|
-
logType: event.logType,
|
|
2346
|
-
meta: event.meta,
|
|
2347
|
-
events: [event]
|
|
2348
|
-
};
|
|
2349
|
-
open.set(key, span);
|
|
2350
|
-
spans.push(span);
|
|
2351
|
-
return;
|
|
2352
|
-
}
|
|
2353
|
-
const span = open.get(key);
|
|
2354
|
-
if (span === void 0) {
|
|
2355
|
-
spans.push(pointSpan(event, index));
|
|
2356
|
-
return;
|
|
2357
|
-
}
|
|
2358
|
-
open.delete(key);
|
|
2359
|
-
span.events.push(event);
|
|
2360
|
-
if (event.time >= span.start) {
|
|
2361
|
-
span.end = event.time;
|
|
2362
|
-
span.duration = event.time - span.start;
|
|
2363
|
-
}
|
|
2364
|
-
span.changed ??= changedOf(event);
|
|
2365
|
-
});
|
|
2366
|
-
return spans.sort((a, b) => a.start - b.start);
|
|
2367
|
-
};
|
|
2368
|
-
/**
|
|
2369
|
-
* Groups lifecycle spans into update ticks.
|
|
2370
|
-
*
|
|
2371
|
-
* `duration` is taken from the `performUpdate` span alone, never from a sum of
|
|
2372
|
-
* the phases: `willUpdate` / `update` / `updated` all run *inside*
|
|
2373
|
-
* `performUpdate`, so summing counts the same milliseconds several times over.
|
|
2374
|
-
*
|
|
2375
|
-
* `changed` is the union across the tick's phases because only the phases that
|
|
2376
|
-
* receive a `PropertyValues` argument carry it — `performUpdate` takes none,
|
|
2377
|
-
* so the bracketing span is always silent about the reason.
|
|
2378
|
-
*/
|
|
2379
|
-
var toUpdateCycles = (spans) => {
|
|
2380
|
-
const byGroup = /* @__PURE__ */ new Map();
|
|
2381
|
-
for (const span of spans) {
|
|
2382
|
-
const elementId = span.meta?.elementId;
|
|
2383
|
-
if (span.layerId !== LIFECYCLE_LAYER_ID || span.groupId === void 0 || elementId === void 0) continue;
|
|
2384
|
-
const key = String(span.groupId);
|
|
2385
|
-
let cycle = byGroup.get(key);
|
|
2386
|
-
if (cycle === void 0) {
|
|
2387
|
-
cycle = {
|
|
2388
|
-
key,
|
|
2389
|
-
elementId,
|
|
2390
|
-
tagName: span.meta?.tagName ?? "unknown",
|
|
2391
|
-
source: span.meta?.source,
|
|
2392
|
-
start: span.start,
|
|
2393
|
-
changed: [],
|
|
2394
|
-
phases: []
|
|
2395
|
-
};
|
|
2396
|
-
byGroup.set(key, cycle);
|
|
2397
|
-
}
|
|
2398
|
-
cycle.phases.push(span);
|
|
2399
|
-
if (span.start < cycle.start) cycle.start = span.start;
|
|
2400
|
-
if (span.name === ROOT_PHASE) cycle.duration = span.duration;
|
|
2401
|
-
for (const changedKey of span.changed ?? []) if (!cycle.changed.includes(changedKey)) cycle.changed.push(changedKey);
|
|
2402
|
-
}
|
|
2403
|
-
return [...byGroup.values()].sort((a, b) => a.start - b.start);
|
|
2404
|
-
};
|
|
2405
|
-
/**
|
|
2406
|
-
* Totals update cycles per component tag.
|
|
2407
|
-
*
|
|
2408
|
-
* Grouped by tag rather than by instance because the question this answers is
|
|
2409
|
-
* "which *component* re-renders too much" — a list of 200 rows each updating
|
|
2410
|
-
* twice is one component's problem, not 200. The instance ids are kept so the
|
|
2411
|
-
* caller can still drill in.
|
|
2412
|
-
*/
|
|
2413
|
-
var rollup = (cycles) => {
|
|
2414
|
-
const byTag = /* @__PURE__ */ new Map();
|
|
2415
|
-
for (const cycle of cycles) {
|
|
2416
|
-
let record = byTag.get(cycle.tagName);
|
|
2417
|
-
if (record === void 0) {
|
|
2418
|
-
record = {
|
|
2419
|
-
entry: {
|
|
2420
|
-
tagName: cycle.tagName,
|
|
2421
|
-
elementIds: [],
|
|
2422
|
-
updates: 0,
|
|
2423
|
-
totalMs: 0,
|
|
2424
|
-
maxMs: 0,
|
|
2425
|
-
reasons: [],
|
|
2426
|
-
source: cycle.source
|
|
2427
|
-
},
|
|
2428
|
-
reasons: /* @__PURE__ */ new Map()
|
|
2429
|
-
};
|
|
2430
|
-
byTag.set(cycle.tagName, record);
|
|
2610
|
+
const itemsChanged = this._items !== config.items;
|
|
2611
|
+
this._items = config.items || [];
|
|
2612
|
+
if (this._virtualizer) this._updateVirtualizerConfig(part, config);
|
|
2613
|
+
else this._initialize(part, config);
|
|
2614
|
+
return itemsChanged ? E : this.render();
|
|
2615
|
+
}
|
|
2616
|
+
async _updateVirtualizerConfig(part, config) {
|
|
2617
|
+
if (!await this._virtualizer.updateLayoutConfig(config.layout || {})) {
|
|
2618
|
+
const hostElement = part.parentNode;
|
|
2619
|
+
this._makeVirtualizer(hostElement, config);
|
|
2431
2620
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
if (
|
|
2437
|
-
|
|
2438
|
-
|
|
2621
|
+
this._virtualizer.items = this._items;
|
|
2622
|
+
}
|
|
2623
|
+
_setFunctions(config) {
|
|
2624
|
+
const { renderItem, keyFunction } = config;
|
|
2625
|
+
if (renderItem) this._renderItem = (item, idx) => renderItem(item, idx + this._first);
|
|
2626
|
+
if (keyFunction) this._keyFunction = (item, idx) => keyFunction(item, idx + this._first);
|
|
2627
|
+
}
|
|
2628
|
+
_makeVirtualizer(hostElement, config) {
|
|
2629
|
+
if (this._virtualizer) this._virtualizer.disconnected();
|
|
2630
|
+
const { layout, scroller, items } = config;
|
|
2631
|
+
this._virtualizer = new Virtualizer({
|
|
2632
|
+
hostElement,
|
|
2633
|
+
layout,
|
|
2634
|
+
scroller
|
|
2635
|
+
});
|
|
2636
|
+
this._virtualizer.items = items;
|
|
2637
|
+
this._virtualizer.connected();
|
|
2638
|
+
}
|
|
2639
|
+
_initialize(part, config) {
|
|
2640
|
+
const hostElement = part.parentNode;
|
|
2641
|
+
if (hostElement && hostElement.nodeType === 1) {
|
|
2642
|
+
hostElement.addEventListener("rangeChanged", (e) => {
|
|
2643
|
+
this._first = e.first;
|
|
2644
|
+
this._last = e.last;
|
|
2645
|
+
this.setValue(this.render());
|
|
2646
|
+
});
|
|
2647
|
+
this._makeVirtualizer(hostElement, config);
|
|
2439
2648
|
}
|
|
2440
|
-
for (const key of cycle.changed) reasons.set(key, (reasons.get(key) ?? 0) + 1);
|
|
2441
2649
|
}
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
})).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
2448
|
-
entries.push(entry);
|
|
2650
|
+
disconnected() {
|
|
2651
|
+
this._virtualizer?.disconnected();
|
|
2652
|
+
}
|
|
2653
|
+
reconnected() {
|
|
2654
|
+
this._virtualizer?.connected();
|
|
2449
2655
|
}
|
|
2450
|
-
return entries.sort((a, b) => b.totalMs - a.totalMs || b.updates - a.updates);
|
|
2451
|
-
};
|
|
2452
|
-
/**
|
|
2453
|
-
* Attributes each update cycle to the input event that preceded it, within
|
|
2454
|
-
* `windowMs`.
|
|
2455
|
-
*
|
|
2456
|
-
* This is what the mouse and keyboard layers are for. On their own they
|
|
2457
|
-
* duplicate what the browser's own event log already shows; joined to the
|
|
2458
|
-
* updates they precede they answer the question the raw layers only imply —
|
|
2459
|
-
* an update with a cause is an interaction, and an update without one is a
|
|
2460
|
-
* timer, a signal, or a stray `requestUpdate()`.
|
|
2461
|
-
*
|
|
2462
|
-
* Returns a new array; cycles with no cause are returned as-is.
|
|
2463
|
-
*/
|
|
2464
|
-
var attributeInput = (cycles, events, windowMs = 200) => {
|
|
2465
|
-
const inputs = events.filter((event) => INPUT_LAYER_IDS.includes(event.layerId)).sort((a, b) => a.time - b.time);
|
|
2466
|
-
if (inputs.length === 0) return [...cycles];
|
|
2467
|
-
let cursor = 0;
|
|
2468
|
-
return cycles.map((cycle) => {
|
|
2469
|
-
while (cursor + 1 < inputs.length && inputs[cursor + 1].time <= cycle.start) cursor++;
|
|
2470
|
-
const candidate = inputs[cursor];
|
|
2471
|
-
const delta = cycle.start - candidate.time;
|
|
2472
|
-
if (delta < 0 || delta > windowMs) return cycle;
|
|
2473
|
-
return {
|
|
2474
|
-
...cycle,
|
|
2475
|
-
cause: {
|
|
2476
|
-
layerId: candidate.layerId,
|
|
2477
|
-
type: candidate.title ?? candidate.layerId,
|
|
2478
|
-
detail: candidate.subtitle,
|
|
2479
|
-
time: candidate.time
|
|
2480
|
-
}
|
|
2481
|
-
};
|
|
2482
|
-
});
|
|
2483
2656
|
};
|
|
2657
|
+
var virtualize = e$1(VirtualizeDirective);
|
|
2484
2658
|
//#endregion
|
|
2485
2659
|
//#region node_modules/.pnpm/devframe@1.0.0_@devframes+agentic@1.0.0_cac@7.0.0_srvx@1.0.5/node_modules/devframe/dist/utils/colors.mjs
|
|
2486
2660
|
function makeColor(open, close) {
|
|
@@ -6835,7 +7009,7 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
6835
7009
|
bootstrapAuthSettled = true;
|
|
6836
7010
|
});
|
|
6837
7011
|
if (connectionMeta.mcp) __vitePreload(async () => {
|
|
6838
|
-
const { setupBrowserAgentRpcBridge } = await import("./browser-agent-rpc-BXhoSh1z-
|
|
7012
|
+
const { setupBrowserAgentRpcBridge } = await import("./browser-agent-rpc-BXhoSh1z-DXAVFObe.js");
|
|
6839
7013
|
return { setupBrowserAgentRpcBridge };
|
|
6840
7014
|
}, [], import.meta.url).then(({ setupBrowserAgentRpcBridge }) => {
|
|
6841
7015
|
if (!closed) disposeBrowserAgentBridge = setupBrowserAgentRpcBridge(rpc);
|
|
@@ -6881,6 +7055,8 @@ var SOURCE_OVERLAY_EDITORS = [
|
|
|
6881
7055
|
];
|
|
6882
7056
|
/** localStorage key holding the {@link SettingsOverride}. */
|
|
6883
7057
|
var SETTINGS_OVERRIDE_LS_KEY = "lit-devtools-overrides";
|
|
7058
|
+
/** localStorage key holding the {@link OverrideBaselines}. */
|
|
7059
|
+
var OVERRIDE_BASELINES_LS_KEY = "lit-devtools-override-baselines";
|
|
6884
7060
|
//#endregion
|
|
6885
7061
|
//#region src/lib/devframe/protocol.ts
|
|
6886
7062
|
/**
|
|
@@ -7023,12 +7199,142 @@ var openInEditor = async (file, line) => {
|
|
|
7023
7199
|
}
|
|
7024
7200
|
};
|
|
7025
7201
|
//#endregion
|
|
7202
|
+
//#region src/panel/timeline-span-detail.ts
|
|
7203
|
+
/**
|
|
7204
|
+
* @license
|
|
7205
|
+
* Copyright 2026 Google LLC
|
|
7206
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
7207
|
+
*/
|
|
7208
|
+
var TimelineSpanDetail = class TimelineSpanDetail extends i$2 {
|
|
7209
|
+
constructor(..._args) {
|
|
7210
|
+
super(..._args);
|
|
7211
|
+
this.filterable = false;
|
|
7212
|
+
}
|
|
7213
|
+
static {
|
|
7214
|
+
this.styles = [tokens, i$5`
|
|
7215
|
+
:host {
|
|
7216
|
+
display: block;
|
|
7217
|
+
border-top: 1px solid var(--lit-devtools-border);
|
|
7218
|
+
background: var(--lit-devtools-surface-low);
|
|
7219
|
+
padding: var(--lit-devtools-space-5) var(--lit-devtools-space-5);
|
|
7220
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
7221
|
+
font-family: var(--lit-devtools-font-mono);
|
|
7222
|
+
color: var(--lit-devtools-text-secondary);
|
|
7223
|
+
flex-shrink: 0;
|
|
7224
|
+
max-height: 130px;
|
|
7225
|
+
overflow-y: auto;
|
|
7226
|
+
}
|
|
7227
|
+
table {
|
|
7228
|
+
border-collapse: collapse;
|
|
7229
|
+
width: 100%;
|
|
7230
|
+
}
|
|
7231
|
+
td {
|
|
7232
|
+
padding: var(--lit-devtools-space-1) var(--lit-devtools-space-4)
|
|
7233
|
+
var(--lit-devtools-space-1) 0;
|
|
7234
|
+
vertical-align: top;
|
|
7235
|
+
}
|
|
7236
|
+
.key {
|
|
7237
|
+
color: var(--lit-devtools-text-muted);
|
|
7238
|
+
white-space: nowrap;
|
|
7239
|
+
}
|
|
7240
|
+
.val {
|
|
7241
|
+
color: var(--lit-devtools-text);
|
|
7242
|
+
word-break: break-all;
|
|
7243
|
+
}
|
|
7244
|
+
a {
|
|
7245
|
+
color: var(--lit-devtools-accent);
|
|
7246
|
+
text-decoration: none;
|
|
7247
|
+
cursor: pointer;
|
|
7248
|
+
}
|
|
7249
|
+
a:hover {
|
|
7250
|
+
text-decoration: underline;
|
|
7251
|
+
}
|
|
7252
|
+
.filter-link {
|
|
7253
|
+
margin-left: var(--lit-devtools-space-4);
|
|
7254
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
7255
|
+
}
|
|
7256
|
+
`];
|
|
7257
|
+
}
|
|
7258
|
+
_emit(type, id) {
|
|
7259
|
+
this.dispatchEvent(new CustomEvent(type, {
|
|
7260
|
+
detail: { id },
|
|
7261
|
+
bubbles: true,
|
|
7262
|
+
composed: true
|
|
7263
|
+
}));
|
|
7264
|
+
}
|
|
7265
|
+
render() {
|
|
7266
|
+
const row = this.span;
|
|
7267
|
+
if (!row) return A;
|
|
7268
|
+
const { meta } = row;
|
|
7269
|
+
const src = meta?.source;
|
|
7270
|
+
const data = row.events[0]?.data;
|
|
7271
|
+
const id = meta?.elementId;
|
|
7272
|
+
return b`
|
|
7273
|
+
<table>
|
|
7274
|
+
<tr>
|
|
7275
|
+
<td class="key">layer</td>
|
|
7276
|
+
<td class="val">${row.layerId}</td>
|
|
7277
|
+
</tr>
|
|
7278
|
+
<tr>
|
|
7279
|
+
<td class="key">time</td>
|
|
7280
|
+
<td class="val">${row.start.toFixed(3)} ms</td>
|
|
7281
|
+
</tr>
|
|
7282
|
+
${row.duration !== void 0 ? b`<tr>
|
|
7283
|
+
<td class="key">duration</td>
|
|
7284
|
+
<td class="val">${row.duration.toFixed(3)} ms</td>
|
|
7285
|
+
</tr>` : A}
|
|
7286
|
+
${row.changed?.length ? b`<tr>
|
|
7287
|
+
<td class="key">changed</td>
|
|
7288
|
+
<td class="val">${row.changed.join(", ")}</td>
|
|
7289
|
+
</tr>` : A}
|
|
7290
|
+
${meta?.tagName ? b`<tr>
|
|
7291
|
+
<td class="key">element</td>
|
|
7292
|
+
<td class="val">
|
|
7293
|
+
<${meta.tagName}> #${id}
|
|
7294
|
+
${id != null && this.filterable ? b`<a
|
|
7295
|
+
class="filter-link"
|
|
7296
|
+
@click=${() => this._emit("element-filter", id)}
|
|
7297
|
+
>filter</a
|
|
7298
|
+
>` : A}${id != null ? b`<a
|
|
7299
|
+
class="filter-link"
|
|
7300
|
+
title="Open this element in the Components tab"
|
|
7301
|
+
@click=${() => this._emit("inspect-element", id)}
|
|
7302
|
+
>inspect</a
|
|
7303
|
+
>` : A}
|
|
7304
|
+
</td>
|
|
7305
|
+
</tr>` : A}
|
|
7306
|
+
${src ? b`<tr>
|
|
7307
|
+
<td class="key">source</td>
|
|
7308
|
+
<td class="val">
|
|
7309
|
+
<a
|
|
7310
|
+
class="src-link"
|
|
7311
|
+
title="Open this file in your editor"
|
|
7312
|
+
@click=${() => openInEditor(src.file, src.line)}
|
|
7313
|
+
>${src.file}:${src.line}</a
|
|
7314
|
+
>
|
|
7315
|
+
</td>
|
|
7316
|
+
</tr>` : A}
|
|
7317
|
+
${data != null ? b`<tr>
|
|
7318
|
+
<td class="key">data</td>
|
|
7319
|
+
<td class="val">${JSON.stringify(data)}</td>
|
|
7320
|
+
</tr>` : A}
|
|
7321
|
+
</table>
|
|
7322
|
+
`;
|
|
7323
|
+
}
|
|
7324
|
+
};
|
|
7325
|
+
__decorate([n$3({ attribute: false })], TimelineSpanDetail.prototype, "span", void 0);
|
|
7326
|
+
__decorate([n$3({ type: Boolean })], TimelineSpanDetail.prototype, "filterable", void 0);
|
|
7327
|
+
TimelineSpanDetail = __decorate([t$2("timeline-span-detail")], TimelineSpanDetail);
|
|
7328
|
+
//#endregion
|
|
7026
7329
|
//#region src/panel/timeline-event-list.ts
|
|
7027
7330
|
/**
|
|
7028
7331
|
* @license
|
|
7029
7332
|
* Copyright 2026 Google LLC
|
|
7030
7333
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
7031
7334
|
*/
|
|
7335
|
+
var RAW_KEY_PREFIX = "raw:";
|
|
7336
|
+
/** Whether `key` names a Raw-mode row rather than a collapsed span. */
|
|
7337
|
+
var isRawKey = (key) => key.startsWith(RAW_KEY_PREFIX);
|
|
7032
7338
|
/**
|
|
7033
7339
|
* Adapts one raw event to the row shape for the Raw toggle. Deliberately
|
|
7034
7340
|
* drops `groupId`: pairing is what the collapsed mode is for, and without it
|
|
@@ -7036,7 +7342,7 @@ var openInEditor = async (file, line) => {
|
|
|
7036
7342
|
*/
|
|
7037
7343
|
var rawRow = (event, index) => ({
|
|
7038
7344
|
layerId: event.layerId,
|
|
7039
|
-
key:
|
|
7345
|
+
key: `${RAW_KEY_PREFIX}${index}`,
|
|
7040
7346
|
name: event.title ?? event.layerId,
|
|
7041
7347
|
start: event.time,
|
|
7042
7348
|
subtitle: event.subtitle,
|
|
@@ -7054,16 +7360,15 @@ var TimelineEventList = class TimelineEventList extends i$2 {
|
|
|
7054
7360
|
constructor(..._args) {
|
|
7055
7361
|
super(..._args);
|
|
7056
7362
|
this.events = [];
|
|
7363
|
+
this.spans = [];
|
|
7057
7364
|
this.layers = [];
|
|
7058
|
-
this.
|
|
7059
|
-
this.
|
|
7060
|
-
this.
|
|
7365
|
+
this.selectedKey = null;
|
|
7366
|
+
this.elementFilter = null;
|
|
7367
|
+
this.regex = "";
|
|
7061
7368
|
this._raw = false;
|
|
7062
7369
|
this._scrollRef = e();
|
|
7063
|
-
this._elementsCache = [];
|
|
7064
7370
|
this._rowsCache = [];
|
|
7065
7371
|
this._visibleCache = [];
|
|
7066
|
-
this._regexInvalid = false;
|
|
7067
7372
|
}
|
|
7068
7373
|
static {
|
|
7069
7374
|
this.styles = [tokens, i$5`
|
|
@@ -7083,31 +7388,6 @@ var TimelineEventList = class TimelineEventList extends i$2 {
|
|
|
7083
7388
|
color: var(--lit-devtools-text-muted);
|
|
7084
7389
|
flex-shrink: 0;
|
|
7085
7390
|
}
|
|
7086
|
-
.filterbar select {
|
|
7087
|
-
background: var(--lit-devtools-surface-elevated);
|
|
7088
|
-
color: var(--lit-devtools-text);
|
|
7089
|
-
border: 1px solid var(--lit-devtools-border-strong);
|
|
7090
|
-
border-radius: var(--lit-devtools-radius-sm);
|
|
7091
|
-
padding: var(--lit-devtools-space-1) var(--lit-devtools-space-3);
|
|
7092
|
-
font-size: var(--lit-devtools-text-2xs);
|
|
7093
|
-
font-family: var(--lit-devtools-font-mono);
|
|
7094
|
-
}
|
|
7095
|
-
.filterbar input.regex {
|
|
7096
|
-
background: var(--lit-devtools-surface-elevated);
|
|
7097
|
-
color: var(--lit-devtools-text);
|
|
7098
|
-
border: 1px solid var(--lit-devtools-border-strong);
|
|
7099
|
-
border-radius: var(--lit-devtools-radius-sm);
|
|
7100
|
-
padding: var(--lit-devtools-space-1) var(--lit-devtools-space-3);
|
|
7101
|
-
font-size: var(--lit-devtools-text-2xs);
|
|
7102
|
-
font-family: var(--lit-devtools-font-mono);
|
|
7103
|
-
min-width: 140px;
|
|
7104
|
-
}
|
|
7105
|
-
.filterbar input.regex::placeholder {
|
|
7106
|
-
color: var(--lit-devtools-text-muted);
|
|
7107
|
-
}
|
|
7108
|
-
.filterbar input.regex.invalid {
|
|
7109
|
-
border-color: var(--lit-devtools-error);
|
|
7110
|
-
}
|
|
7111
7391
|
.filterbar button {
|
|
7112
7392
|
background: var(--lit-devtools-surface-elevated);
|
|
7113
7393
|
color: var(--lit-devtools-text-muted);
|
|
@@ -7209,70 +7489,11 @@ var TimelineEventList = class TimelineEventList extends i$2 {
|
|
|
7209
7489
|
.dur.open {
|
|
7210
7490
|
color: var(--lit-devtools-text-muted);
|
|
7211
7491
|
}
|
|
7212
|
-
.detail {
|
|
7213
|
-
border-top: 1px solid var(--lit-devtools-border);
|
|
7214
|
-
background: var(--lit-devtools-surface-low);
|
|
7215
|
-
padding: var(--lit-devtools-space-5) var(--lit-devtools-space-5);
|
|
7216
|
-
font-size: var(--lit-devtools-text-2xs);
|
|
7217
|
-
font-family: var(--lit-devtools-font-mono);
|
|
7218
|
-
color: var(--lit-devtools-text-secondary);
|
|
7219
|
-
flex-shrink: 0;
|
|
7220
|
-
max-height: 130px;
|
|
7221
|
-
overflow-y: auto;
|
|
7222
|
-
}
|
|
7223
|
-
table {
|
|
7224
|
-
border-collapse: collapse;
|
|
7225
|
-
width: 100%;
|
|
7226
|
-
}
|
|
7227
|
-
td {
|
|
7228
|
-
padding: var(--lit-devtools-space-1) var(--lit-devtools-space-4)
|
|
7229
|
-
var(--lit-devtools-space-1) 0;
|
|
7230
|
-
vertical-align: top;
|
|
7231
|
-
}
|
|
7232
|
-
.key {
|
|
7233
|
-
color: var(--lit-devtools-text-muted);
|
|
7234
|
-
white-space: nowrap;
|
|
7235
|
-
}
|
|
7236
|
-
.val {
|
|
7237
|
-
color: var(--lit-devtools-text);
|
|
7238
|
-
word-break: break-all;
|
|
7239
|
-
}
|
|
7240
|
-
a {
|
|
7241
|
-
color: var(--lit-devtools-accent);
|
|
7242
|
-
text-decoration: none;
|
|
7243
|
-
}
|
|
7244
|
-
a:hover {
|
|
7245
|
-
text-decoration: underline;
|
|
7246
|
-
}
|
|
7247
|
-
.src-link {
|
|
7248
|
-
cursor: pointer;
|
|
7249
|
-
}
|
|
7250
|
-
.filter-link {
|
|
7251
|
-
cursor: pointer;
|
|
7252
|
-
margin-left: var(--lit-devtools-space-4);
|
|
7253
|
-
font-size: var(--lit-devtools-text-2xs);
|
|
7254
|
-
}
|
|
7255
7492
|
`];
|
|
7256
7493
|
}
|
|
7257
7494
|
willUpdate(changed) {
|
|
7258
|
-
if (changed.has("events") || changed.has("_raw"))
|
|
7259
|
-
|
|
7260
|
-
if (this._selectedKey !== null && !this._rowsCache.some((row) => row.key === this._selectedKey)) this._selectedKey = null;
|
|
7261
|
-
}
|
|
7262
|
-
if (changed.has("events")) {
|
|
7263
|
-
this._elementsCache = this._computeElements();
|
|
7264
|
-
if (this._elementFilter !== null && !this.events.some((ev) => ev.meta?.elementId === this._elementFilter)) this._elementFilter = null;
|
|
7265
|
-
}
|
|
7266
|
-
if (changed.has("events") || changed.has("_raw") || changed.has("layers") || changed.has("_elementFilter") || changed.has("_regex")) {
|
|
7267
|
-
let re = null;
|
|
7268
|
-
this._regexInvalid = false;
|
|
7269
|
-
if (this._regex !== "") try {
|
|
7270
|
-
re = new RegExp(this._regex, "i");
|
|
7271
|
-
} catch {
|
|
7272
|
-
this._regexInvalid = true;
|
|
7273
|
-
}
|
|
7274
|
-
this._visibleCache = this._rowsCache.filter((row) => this._isVisible(row) && (re === null || re.test(this._haystack(row))));
|
|
7275
|
-
}
|
|
7495
|
+
if (changed.has("events") || changed.has("spans") || changed.has("_raw")) this._rowsCache = this._raw ? this.events.map(rawRow) : this.spans;
|
|
7496
|
+
if (changed.has("events") || changed.has("spans") || changed.has("_raw") || changed.has("layers") || changed.has("elementFilter") || changed.has("regex")) this._visibleCache = filterSpans(this._rowsCache.filter((row) => this._layerOn(row)), this.elementFilter, compileRegex(this.regex).re);
|
|
7276
7497
|
}
|
|
7277
7498
|
updated(changed) {
|
|
7278
7499
|
if (changed.has("events")) {
|
|
@@ -7280,89 +7501,52 @@ var TimelineEventList = class TimelineEventList extends i$2 {
|
|
|
7280
7501
|
if (el) el.scrollTop = el.scrollHeight;
|
|
7281
7502
|
}
|
|
7282
7503
|
}
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
return "#" + l.color.toString(16).padStart(6, "0");
|
|
7287
|
-
}
|
|
7288
|
-
_isVisible(row) {
|
|
7289
|
-
const l = this.layers.find((l) => l.id === row.layerId);
|
|
7290
|
-
if (l && !l.enabled) return false;
|
|
7291
|
-
if (this._elementFilter !== null && row.meta?.elementId !== this._elementFilter) return false;
|
|
7292
|
-
return true;
|
|
7293
|
-
}
|
|
7294
|
-
/** Distinct elements (by stable id) seen across the recorded events. */
|
|
7295
|
-
_computeElements() {
|
|
7296
|
-
const seen = /* @__PURE__ */ new Map();
|
|
7297
|
-
for (const ev of this.events) {
|
|
7298
|
-
const id = ev.meta?.elementId;
|
|
7299
|
-
if (id != null && !seen.has(id)) seen.set(id, ev.meta?.tagName ?? "unknown");
|
|
7300
|
-
}
|
|
7301
|
-
return [...seen].map(([id, tag]) => ({
|
|
7302
|
-
id,
|
|
7303
|
-
tag
|
|
7304
|
-
}));
|
|
7305
|
-
}
|
|
7306
|
-
_onFilterChange(e) {
|
|
7307
|
-
const v = e.target.value;
|
|
7308
|
-
this._elementFilter = v === "" ? null : Number(v);
|
|
7309
|
-
}
|
|
7310
|
-
/** Ask the panel shell to open the Components tab on this element. */
|
|
7311
|
-
_inspect(id) {
|
|
7312
|
-
this.dispatchEvent(new CustomEvent("inspect-element", {
|
|
7313
|
-
detail: { id },
|
|
7504
|
+
_select(key) {
|
|
7505
|
+
this.dispatchEvent(new CustomEvent("span-select", {
|
|
7506
|
+
detail: { key },
|
|
7314
7507
|
bubbles: true,
|
|
7315
7508
|
composed: true
|
|
7316
7509
|
}));
|
|
7317
7510
|
}
|
|
7318
|
-
|
|
7319
|
-
|
|
7511
|
+
/**
|
|
7512
|
+
* Scrolls the selected row into view, or to the newest row when nothing is
|
|
7513
|
+
* selected. `timeline-view` calls this when switching back from Tracks: the
|
|
7514
|
+
* list was hidden while the selection moved, so its own auto-scroll could
|
|
7515
|
+
* not follow.
|
|
7516
|
+
*/
|
|
7517
|
+
reveal() {
|
|
7518
|
+
const el = this._scrollRef.value;
|
|
7519
|
+
if (!el) return;
|
|
7520
|
+
const index = this.selectedKey === null ? -1 : this._visibleCache.findIndex((row) => row.key === this.selectedKey);
|
|
7521
|
+
if (index === -1) {
|
|
7522
|
+
el.scrollTop = el.scrollHeight;
|
|
7523
|
+
return;
|
|
7524
|
+
}
|
|
7525
|
+
const attempt = (left) => {
|
|
7526
|
+
try {
|
|
7527
|
+
el[virtualizerRef]?.element(index)?.scrollIntoView({ block: "center" });
|
|
7528
|
+
} catch {
|
|
7529
|
+
if (left > 0) requestAnimationFrame(() => attempt(left - 1));
|
|
7530
|
+
}
|
|
7531
|
+
};
|
|
7532
|
+
attempt(30);
|
|
7320
7533
|
}
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
return `${row.meta?.tagName ?? ""} ${row.name} ${row.subtitle ?? ""} ${(row.changed ?? []).join(" ")}`;
|
|
7534
|
+
_layerOn(row) {
|
|
7535
|
+
const l = this.layers.find((l) => l.id === row.layerId);
|
|
7536
|
+
return !l || l.enabled;
|
|
7325
7537
|
}
|
|
7326
7538
|
render() {
|
|
7327
|
-
const elements = this._elementsCache;
|
|
7328
7539
|
const visible = this._visibleCache;
|
|
7329
|
-
const selected = this.
|
|
7540
|
+
const selected = this.selectedKey === null ? void 0 : this._rowsCache.find((row) => row.key === this.selectedKey);
|
|
7330
7541
|
return b`
|
|
7331
7542
|
${this.events.length > 0 ? b`
|
|
7332
7543
|
<div class="filterbar">
|
|
7333
|
-
${elements.length > 0 ? b`
|
|
7334
|
-
<span>Element:</span>
|
|
7335
|
-
<select @change=${this._onFilterChange}>
|
|
7336
|
-
<option
|
|
7337
|
-
value=""
|
|
7338
|
-
?selected=${this._elementFilter === null}
|
|
7339
|
-
>
|
|
7340
|
-
All elements
|
|
7341
|
-
</option>
|
|
7342
|
-
${elements.map((el) => b`
|
|
7343
|
-
<option
|
|
7344
|
-
value=${el.id}
|
|
7345
|
-
?selected=${this._elementFilter === el.id}
|
|
7346
|
-
>
|
|
7347
|
-
<${el.tag}> #${el.id}
|
|
7348
|
-
</option>
|
|
7349
|
-
`)}
|
|
7350
|
-
</select>
|
|
7351
|
-
` : A}
|
|
7352
|
-
<input
|
|
7353
|
-
class="regex ${this._regexInvalid ? "invalid" : ""}"
|
|
7354
|
-
type="text"
|
|
7355
|
-
spellcheck="false"
|
|
7356
|
-
placeholder="filter regex…"
|
|
7357
|
-
title="Case-insensitive regex matched against element tag, title and subtitle"
|
|
7358
|
-
.value=${this._regex}
|
|
7359
|
-
@input=${this._onRegexInput}
|
|
7360
|
-
/>
|
|
7361
7544
|
<button
|
|
7362
7545
|
class=${this._raw ? "on" : ""}
|
|
7363
7546
|
title="Show one row per recorded event instead of collapsing start/end pairs"
|
|
7364
7547
|
@click=${() => {
|
|
7365
7548
|
this._raw = !this._raw;
|
|
7549
|
+
this._select(null);
|
|
7366
7550
|
}}
|
|
7367
7551
|
>
|
|
7368
7552
|
Raw
|
|
@@ -7391,22 +7575,23 @@ var TimelineEventList = class TimelineEventList extends i$2 {
|
|
|
7391
7575
|
})}
|
|
7392
7576
|
</div>
|
|
7393
7577
|
`}
|
|
7394
|
-
${selected ?
|
|
7578
|
+
${selected ? b`<timeline-span-detail
|
|
7579
|
+
filterable
|
|
7580
|
+
.span=${selected}
|
|
7581
|
+
></timeline-span-detail>` : A}
|
|
7395
7582
|
`;
|
|
7396
7583
|
}
|
|
7397
7584
|
/** One row of the virtualized list (`virtualize`'s `renderItem`). */
|
|
7398
7585
|
_renderRow(row) {
|
|
7399
7586
|
return b`
|
|
7400
7587
|
<div
|
|
7401
|
-
class="row ${this.
|
|
7402
|
-
@click=${() =>
|
|
7403
|
-
this._selectedKey = row.key;
|
|
7404
|
-
}}
|
|
7588
|
+
class="row ${this.selectedKey === row.key ? "selected" : ""}"
|
|
7589
|
+
@click=${() => this._select(row.key)}
|
|
7405
7590
|
>
|
|
7406
7591
|
<span class="time">${row.start.toFixed(1)}ms</span>
|
|
7407
7592
|
<span
|
|
7408
7593
|
class="dot"
|
|
7409
|
-
style=${"background:" + this.
|
|
7594
|
+
style=${"background:" + layerColor(this.layers, row.layerId)}
|
|
7410
7595
|
></span>
|
|
7411
7596
|
<span class="title">${row.name}</span>
|
|
7412
7597
|
${row.changed?.length ? b`<span class="changed">${row.changed.join(", ")}</span>` : A}
|
|
@@ -7417,75 +7602,571 @@ var TimelineEventList = class TimelineEventList extends i$2 {
|
|
|
7417
7602
|
</div>
|
|
7418
7603
|
`;
|
|
7419
7604
|
}
|
|
7420
|
-
_renderDetail(row) {
|
|
7421
|
-
const { meta } = row;
|
|
7422
|
-
const src = meta?.source;
|
|
7423
|
-
const data = row.events[0]?.data;
|
|
7424
|
-
return b`
|
|
7425
|
-
<div class="detail">
|
|
7426
|
-
<table>
|
|
7427
|
-
<tr>
|
|
7428
|
-
<td class="key">layer</td>
|
|
7429
|
-
<td class="val">${row.layerId}</td>
|
|
7430
|
-
</tr>
|
|
7431
|
-
<tr>
|
|
7432
|
-
<td class="key">time</td>
|
|
7433
|
-
<td class="val">${row.start.toFixed(3)} ms</td>
|
|
7434
|
-
</tr>
|
|
7435
|
-
${row.duration !== void 0 ? b`<tr>
|
|
7436
|
-
<td class="key">duration</td>
|
|
7437
|
-
<td class="val">${row.duration.toFixed(3)} ms</td>
|
|
7438
|
-
</tr>` : A}
|
|
7439
|
-
${row.changed?.length ? b`<tr>
|
|
7440
|
-
<td class="key">changed</td>
|
|
7441
|
-
<td class="val">${row.changed.join(", ")}</td>
|
|
7442
|
-
</tr>` : A}
|
|
7443
|
-
${meta?.tagName ? b`<tr>
|
|
7444
|
-
<td class="key">element</td>
|
|
7445
|
-
<td class="val">
|
|
7446
|
-
<${meta.tagName}> #${meta.elementId}
|
|
7447
|
-
${meta.elementId != null ? b`<a
|
|
7448
|
-
class="filter-link"
|
|
7449
|
-
@click=${() => {
|
|
7450
|
-
this._elementFilter = meta.elementId;
|
|
7451
|
-
}}
|
|
7452
|
-
>filter</a
|
|
7453
|
-
><a
|
|
7454
|
-
class="filter-link"
|
|
7455
|
-
title="Open this element in the Components tab"
|
|
7456
|
-
@click=${() => this._inspect(meta.elementId)}
|
|
7457
|
-
>inspect</a
|
|
7458
|
-
>` : A}
|
|
7459
|
-
</td>
|
|
7460
|
-
</tr>` : A}
|
|
7461
|
-
${src ? b`<tr>
|
|
7462
|
-
<td class="key">source</td>
|
|
7463
|
-
<td class="val">
|
|
7464
|
-
<a
|
|
7465
|
-
class="src-link"
|
|
7466
|
-
title="Open this file in your editor"
|
|
7467
|
-
@click=${() => openInEditor(src.file, src.line)}
|
|
7468
|
-
>${src.file}:${src.line}</a
|
|
7469
|
-
>
|
|
7470
|
-
</td>
|
|
7471
|
-
</tr>` : A}
|
|
7472
|
-
${data != null ? b`<tr>
|
|
7473
|
-
<td class="key">data</td>
|
|
7474
|
-
<td class="val">${JSON.stringify(data)}</td>
|
|
7475
|
-
</tr>` : A}
|
|
7476
|
-
</table>
|
|
7477
|
-
</div>
|
|
7478
|
-
`;
|
|
7479
|
-
}
|
|
7480
7605
|
};
|
|
7481
7606
|
__decorate([n$3({ type: Array })], TimelineEventList.prototype, "events", void 0);
|
|
7607
|
+
__decorate([n$3({ type: Array })], TimelineEventList.prototype, "spans", void 0);
|
|
7482
7608
|
__decorate([n$3({ type: Array })], TimelineEventList.prototype, "layers", void 0);
|
|
7483
|
-
__decorate([
|
|
7484
|
-
__decorate([
|
|
7485
|
-
__decorate([
|
|
7609
|
+
__decorate([n$3({ attribute: false })], TimelineEventList.prototype, "selectedKey", void 0);
|
|
7610
|
+
__decorate([n$3({ attribute: false })], TimelineEventList.prototype, "elementFilter", void 0);
|
|
7611
|
+
__decorate([n$3({ attribute: false })], TimelineEventList.prototype, "regex", void 0);
|
|
7486
7612
|
__decorate([r$2()], TimelineEventList.prototype, "_raw", void 0);
|
|
7487
7613
|
TimelineEventList = __decorate([t$2("timeline-event-list")], TimelineEventList);
|
|
7488
7614
|
//#endregion
|
|
7615
|
+
//#region src/lib/timeline/tracks.ts
|
|
7616
|
+
/**
|
|
7617
|
+
* @license
|
|
7618
|
+
* Copyright 2026 Google LLC
|
|
7619
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
7620
|
+
*/
|
|
7621
|
+
/**
|
|
7622
|
+
* Where a span stops occupying its row. A point event occupies only its own
|
|
7623
|
+
* instant; a span whose end has not arrived (still running, or its end fell
|
|
7624
|
+
* out of the buffer) occupies the rest of the recording, because that is
|
|
7625
|
+
* where it is drawn — up to the live edge.
|
|
7626
|
+
*/
|
|
7627
|
+
var occupiedEnd = (span) => {
|
|
7628
|
+
if (span.end !== void 0) return span.end;
|
|
7629
|
+
return span.groupId === void 0 ? span.start : Infinity;
|
|
7630
|
+
};
|
|
7631
|
+
/**
|
|
7632
|
+
* Groups spans into one track per layer, in `layers` order, then any layer
|
|
7633
|
+
* the spans mention that `layers` does not (a custom layer announced after the
|
|
7634
|
+
* last `get-meta`), in first-seen order. A layer with no spans still gets an
|
|
7635
|
+
* empty track, so a lane does not appear and vanish as events arrive.
|
|
7636
|
+
*
|
|
7637
|
+
* Within a track, spans are packed greedily: each goes into the first row
|
|
7638
|
+
* whose last span has ended by the time it starts. Lit's lifecycle phases
|
|
7639
|
+
* nest, so one update tick lands as `performUpdate` on row 0 and
|
|
7640
|
+
* `willUpdate` / `update` / `updated` side by side on row 1 — the flame shape
|
|
7641
|
+
* falls out of the packing without modelling it.
|
|
7642
|
+
*/
|
|
7643
|
+
var buildTracks = (spans, layers) => {
|
|
7644
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
7645
|
+
for (const layer of layers) byLayer.set(layer.id, []);
|
|
7646
|
+
for (const span of spans) {
|
|
7647
|
+
let list = byLayer.get(span.layerId);
|
|
7648
|
+
if (list === void 0) {
|
|
7649
|
+
list = [];
|
|
7650
|
+
byLayer.set(span.layerId, list);
|
|
7651
|
+
}
|
|
7652
|
+
list.push(span);
|
|
7653
|
+
}
|
|
7654
|
+
return [...byLayer].map(([layerId, list]) => ({
|
|
7655
|
+
layerId,
|
|
7656
|
+
rows: packRows(list)
|
|
7657
|
+
}));
|
|
7658
|
+
};
|
|
7659
|
+
var packRows = (spans) => {
|
|
7660
|
+
const sorted = spans.every((s, i) => i === 0 || spans[i - 1].start <= s.start) ? spans : [...spans].sort((a, b) => a.start - b.start);
|
|
7661
|
+
const rows = [];
|
|
7662
|
+
const rowEnds = [];
|
|
7663
|
+
for (const span of sorted) {
|
|
7664
|
+
let row = rowEnds.findIndex((end) => end <= span.start);
|
|
7665
|
+
if (row === -1) {
|
|
7666
|
+
row = rows.length;
|
|
7667
|
+
rows.push([]);
|
|
7668
|
+
rowEnds.push(-Infinity);
|
|
7669
|
+
}
|
|
7670
|
+
rows[row].push(span);
|
|
7671
|
+
rowEnds[row] = occupiedEnd(span);
|
|
7672
|
+
}
|
|
7673
|
+
return rows;
|
|
7674
|
+
};
|
|
7675
|
+
/**
|
|
7676
|
+
* The recording's time bounds over `spans`: the earliest start, and the
|
|
7677
|
+
* latest instant anything is known to have happened (an end, or the start of
|
|
7678
|
+
* a span with none). The origin is the earliest start rather than zero
|
|
7679
|
+
* because the panel's buffer is capped and rolls its oldest events off; a
|
|
7680
|
+
* long recording would otherwise open on an empty stretch.
|
|
7681
|
+
*/
|
|
7682
|
+
var timeBounds = (spans) => {
|
|
7683
|
+
if (spans.length === 0) return {
|
|
7684
|
+
origin: 0,
|
|
7685
|
+
extent: 0
|
|
7686
|
+
};
|
|
7687
|
+
let origin = Infinity;
|
|
7688
|
+
let last = -Infinity;
|
|
7689
|
+
for (const span of spans) {
|
|
7690
|
+
origin = Math.min(origin, span.start);
|
|
7691
|
+
last = Math.max(last, span.end ?? span.start);
|
|
7692
|
+
}
|
|
7693
|
+
return {
|
|
7694
|
+
origin,
|
|
7695
|
+
extent: last - origin
|
|
7696
|
+
};
|
|
7697
|
+
};
|
|
7698
|
+
/** Deepest zoom, in multiples of "fit": about a microsecond per pixel on a
|
|
7699
|
+
* second-long recording, past which there is nothing left to separate. */
|
|
7700
|
+
var MAX_ZOOM = 1e5;
|
|
7701
|
+
/** A recording with a single instant in it still needs a nonzero axis. */
|
|
7702
|
+
var MIN_EXTENT_MS = 1;
|
|
7703
|
+
/**
|
|
7704
|
+
* Builds the scale for a plot `width` pixels wide showing a recording that
|
|
7705
|
+
* begins at `originMs` and runs for `extentMs`.
|
|
7706
|
+
*
|
|
7707
|
+
* `zoom` is a multiple of "fit" (1 shows the whole recording); `pan` is how
|
|
7708
|
+
* many milliseconds past the origin the left edge sits. Both are clamped, so
|
|
7709
|
+
* callers can apply a raw wheel or drag delta and read back where it landed.
|
|
7710
|
+
*/
|
|
7711
|
+
var timeScale = (originMs, extentMs, width, zoom = 1, pan = 0) => {
|
|
7712
|
+
const extent = Math.max(extentMs, MIN_EXTENT_MS);
|
|
7713
|
+
const z = clampZoom(zoom);
|
|
7714
|
+
const visibleMs = extent / z;
|
|
7715
|
+
const p = clampPan(pan, extent, z);
|
|
7716
|
+
const pxPerMs = Math.max(width, 1) / visibleMs;
|
|
7717
|
+
const start = originMs + p;
|
|
7718
|
+
return {
|
|
7719
|
+
pxPerMs,
|
|
7720
|
+
start,
|
|
7721
|
+
end: start + visibleMs,
|
|
7722
|
+
toX: (ms) => (ms - start) * pxPerMs,
|
|
7723
|
+
toMs: (x) => start + x / pxPerMs
|
|
7724
|
+
};
|
|
7725
|
+
};
|
|
7726
|
+
var clampZoom = (zoom) => Math.min(Math.max(Number.isNaN(zoom) ? 1 : zoom, 1), MAX_ZOOM);
|
|
7727
|
+
/** Keeps the visible window inside the recording. */
|
|
7728
|
+
var clampPan = (pan, extentMs, zoom) => {
|
|
7729
|
+
const extent = Math.max(extentMs, MIN_EXTENT_MS);
|
|
7730
|
+
const max = extent - extent / clampZoom(zoom);
|
|
7731
|
+
return Math.min(Math.max(Number.isNaN(pan) ? 0 : pan, 0), max);
|
|
7732
|
+
};
|
|
7733
|
+
/** The pan that puts the live edge (the end of the recording) at the right
|
|
7734
|
+
* of the plot, for following a recording in progress. */
|
|
7735
|
+
var livePan = (extentMs, zoom) => clampPan(Infinity, extentMs, zoom);
|
|
7736
|
+
/**
|
|
7737
|
+
* Zooms by `factor` while keeping the millisecond under pixel `x` where it
|
|
7738
|
+
* is, which is what a wheel over a timeline is expected to do.
|
|
7739
|
+
*/
|
|
7740
|
+
var zoomAt = (originMs, extentMs, width, zoom, pan, x, factor) => {
|
|
7741
|
+
const anchor = timeScale(originMs, extentMs, width, zoom, pan).toMs(x);
|
|
7742
|
+
const nextZoom = clampZoom(clampZoom(zoom) * factor);
|
|
7743
|
+
const after = timeScale(originMs, extentMs, width, nextZoom, 0);
|
|
7744
|
+
return {
|
|
7745
|
+
zoom: nextZoom,
|
|
7746
|
+
pan: clampPan(anchor - originMs - x / after.pxPerMs, extentMs, nextZoom)
|
|
7747
|
+
};
|
|
7748
|
+
};
|
|
7749
|
+
/**
|
|
7750
|
+
* Index range `[from, to)` of the spans in `row` that intersect the window
|
|
7751
|
+
* `[start, end]`, found by binary search so a zoomed-in lane renders a
|
|
7752
|
+
* handful of marks rather than walking thousands.
|
|
7753
|
+
*
|
|
7754
|
+
* Only the span just before the first one starting inside the window can
|
|
7755
|
+
* reach into it, because a row's spans never overlap.
|
|
7756
|
+
*/
|
|
7757
|
+
var visibleRange = (row, start, end) => {
|
|
7758
|
+
const firstAtOrAfter = (t, inclusive) => {
|
|
7759
|
+
let lo = 0;
|
|
7760
|
+
let hi = row.length;
|
|
7761
|
+
while (lo < hi) {
|
|
7762
|
+
const mid = lo + hi >> 1;
|
|
7763
|
+
const s = row[mid].start;
|
|
7764
|
+
if (inclusive ? s < t : s <= t) lo = mid + 1;
|
|
7765
|
+
else hi = mid;
|
|
7766
|
+
}
|
|
7767
|
+
return lo;
|
|
7768
|
+
};
|
|
7769
|
+
let from = firstAtOrAfter(start, true);
|
|
7770
|
+
if (from > 0 && occupiedEnd(row[from - 1]) >= start) from--;
|
|
7771
|
+
const to = firstAtOrAfter(end, false);
|
|
7772
|
+
return [from, Math.max(from, to)];
|
|
7773
|
+
};
|
|
7774
|
+
/**
|
|
7775
|
+
* A round tick interval (1, 2 or 5 times a power of ten, in milliseconds) at
|
|
7776
|
+
* least `minMs` long, for axis labels that land on readable numbers.
|
|
7777
|
+
*/
|
|
7778
|
+
var niceStep = (minMs) => {
|
|
7779
|
+
if (!(minMs > 0) || !Number.isFinite(minMs)) return 1;
|
|
7780
|
+
const pow = 10 ** Math.floor(Math.log10(minMs));
|
|
7781
|
+
for (const m of [
|
|
7782
|
+
1,
|
|
7783
|
+
2,
|
|
7784
|
+
5,
|
|
7785
|
+
10
|
|
7786
|
+
]) if (m * pow >= minMs) return m * pow;
|
|
7787
|
+
return 10 * pow;
|
|
7788
|
+
};
|
|
7789
|
+
//#endregion
|
|
7790
|
+
//#region src/panel/timeline-tracks.ts
|
|
7791
|
+
/**
|
|
7792
|
+
* @license
|
|
7793
|
+
* Copyright 2026 Google LLC
|
|
7794
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
7795
|
+
*/
|
|
7796
|
+
/** Height of one packed row inside a lane. */
|
|
7797
|
+
var ROW_PX = 14;
|
|
7798
|
+
/** Marks narrower than this draw as a fixed-width tick, so sub-millisecond
|
|
7799
|
+
* Lit phases stay visible at any zoom. */
|
|
7800
|
+
var MIN_MARK_PX = 2;
|
|
7801
|
+
/** Rough spacing between axis labels. */
|
|
7802
|
+
var TICK_PX = 90;
|
|
7803
|
+
/** A pointer that moves less than this between down and up was a click. */
|
|
7804
|
+
var DRAG_SLOP_PX = 3;
|
|
7805
|
+
var formatAxis = (ms, step) => step >= 1e3 ? `${(ms / 1e3).toFixed(step >= 1e4 ? 0 : 1)}s` : `${ms.toFixed(step >= 1 ? 0 : step >= .1 ? 1 : 2)}ms`;
|
|
7806
|
+
var describe = (span) => {
|
|
7807
|
+
const parts = [span.name];
|
|
7808
|
+
if (span.subtitle) parts.push(span.subtitle);
|
|
7809
|
+
parts.push(`@ ${span.start.toFixed(2)}ms`);
|
|
7810
|
+
if (span.duration !== void 0) parts.push(`${span.duration.toFixed(3)}ms`);
|
|
7811
|
+
else if (span.groupId !== void 0) parts.push("still open");
|
|
7812
|
+
return parts.join(" · ");
|
|
7813
|
+
};
|
|
7814
|
+
var TimelineTracks = class TimelineTracks extends i$2 {
|
|
7815
|
+
constructor(..._args) {
|
|
7816
|
+
super(..._args);
|
|
7817
|
+
this.spans = [];
|
|
7818
|
+
this.layers = [];
|
|
7819
|
+
this.selectedKey = null;
|
|
7820
|
+
this.visibleTracks = [];
|
|
7821
|
+
this.recording = false;
|
|
7822
|
+
this._zoom = 1;
|
|
7823
|
+
this._pan = 0;
|
|
7824
|
+
this._follow = true;
|
|
7825
|
+
this._width = 0;
|
|
7826
|
+
this._ticksRef = e();
|
|
7827
|
+
this._resize = null;
|
|
7828
|
+
this._observed = null;
|
|
7829
|
+
this._tracks = [];
|
|
7830
|
+
this._visibleSpans = [];
|
|
7831
|
+
this._bounds = {
|
|
7832
|
+
origin: 0,
|
|
7833
|
+
extent: 0
|
|
7834
|
+
};
|
|
7835
|
+
this._drag = null;
|
|
7836
|
+
this._suppressClick = false;
|
|
7837
|
+
}
|
|
7838
|
+
static {
|
|
7839
|
+
this.styles = [tokens, i$5`
|
|
7840
|
+
:host {
|
|
7841
|
+
display: flex;
|
|
7842
|
+
flex-direction: column;
|
|
7843
|
+
flex: 1;
|
|
7844
|
+
overflow: hidden;
|
|
7845
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
7846
|
+
font-family: var(--lit-devtools-font-mono);
|
|
7847
|
+
--gutter: 120px;
|
|
7848
|
+
}
|
|
7849
|
+
.axis,
|
|
7850
|
+
.lane {
|
|
7851
|
+
display: flex;
|
|
7852
|
+
}
|
|
7853
|
+
.axis {
|
|
7854
|
+
flex-shrink: 0;
|
|
7855
|
+
height: 20px;
|
|
7856
|
+
border-bottom: 1px solid var(--lit-devtools-border);
|
|
7857
|
+
color: var(--lit-devtools-text-muted);
|
|
7858
|
+
}
|
|
7859
|
+
.gutter {
|
|
7860
|
+
flex: 0 0 var(--gutter);
|
|
7861
|
+
box-sizing: border-box;
|
|
7862
|
+
padding: 0 var(--lit-devtools-space-4);
|
|
7863
|
+
display: flex;
|
|
7864
|
+
align-items: center;
|
|
7865
|
+
gap: var(--lit-devtools-space-2);
|
|
7866
|
+
overflow: hidden;
|
|
7867
|
+
white-space: nowrap;
|
|
7868
|
+
border-right: 1px solid var(--lit-devtools-border);
|
|
7869
|
+
}
|
|
7870
|
+
.ticks {
|
|
7871
|
+
position: relative;
|
|
7872
|
+
flex: 1;
|
|
7873
|
+
overflow: hidden;
|
|
7874
|
+
}
|
|
7875
|
+
.tick-label {
|
|
7876
|
+
position: absolute;
|
|
7877
|
+
top: 3px;
|
|
7878
|
+
padding-left: var(--lit-devtools-space-2);
|
|
7879
|
+
border-left: 1px solid var(--lit-devtools-border-strong);
|
|
7880
|
+
white-space: nowrap;
|
|
7881
|
+
}
|
|
7882
|
+
.lanes {
|
|
7883
|
+
flex: 1;
|
|
7884
|
+
overflow-y: auto;
|
|
7885
|
+
cursor: grab;
|
|
7886
|
+
user-select: none;
|
|
7887
|
+
touch-action: none;
|
|
7888
|
+
}
|
|
7889
|
+
.lanes.dragging {
|
|
7890
|
+
cursor: grabbing;
|
|
7891
|
+
}
|
|
7892
|
+
.lane {
|
|
7893
|
+
border-bottom: 1px solid var(--lit-devtools-border);
|
|
7894
|
+
}
|
|
7895
|
+
.lane .gutter {
|
|
7896
|
+
color: var(--lit-devtools-text-secondary);
|
|
7897
|
+
cursor: default;
|
|
7898
|
+
}
|
|
7899
|
+
.dot {
|
|
7900
|
+
width: 8px;
|
|
7901
|
+
height: 8px;
|
|
7902
|
+
border-radius: 50%;
|
|
7903
|
+
flex-shrink: 0;
|
|
7904
|
+
}
|
|
7905
|
+
.plot {
|
|
7906
|
+
flex: 1;
|
|
7907
|
+
min-width: 0;
|
|
7908
|
+
max-height: calc(8 * ${ROW_PX}px);
|
|
7909
|
+
overflow-x: hidden;
|
|
7910
|
+
overflow-y: auto;
|
|
7911
|
+
}
|
|
7912
|
+
.rows {
|
|
7913
|
+
position: relative;
|
|
7914
|
+
}
|
|
7915
|
+
.mark {
|
|
7916
|
+
position: absolute;
|
|
7917
|
+
box-sizing: border-box;
|
|
7918
|
+
height: ${11}px;
|
|
7919
|
+
margin-top: 1px;
|
|
7920
|
+
border-radius: 2px;
|
|
7921
|
+
opacity: 0.85;
|
|
7922
|
+
overflow: hidden;
|
|
7923
|
+
white-space: nowrap;
|
|
7924
|
+
color: var(--lit-devtools-surface-low);
|
|
7925
|
+
font-size: 9px;
|
|
7926
|
+
line-height: ${11}px;
|
|
7927
|
+
padding: 0 2px;
|
|
7928
|
+
cursor: pointer;
|
|
7929
|
+
}
|
|
7930
|
+
.mark.tick {
|
|
7931
|
+
padding: 0;
|
|
7932
|
+
}
|
|
7933
|
+
.mark.open {
|
|
7934
|
+
opacity: 0.45;
|
|
7935
|
+
}
|
|
7936
|
+
.mark:hover {
|
|
7937
|
+
opacity: 1;
|
|
7938
|
+
}
|
|
7939
|
+
.mark.selected {
|
|
7940
|
+
opacity: 1;
|
|
7941
|
+
outline: 2px solid var(--lit-devtools-text);
|
|
7942
|
+
outline-offset: 0;
|
|
7943
|
+
z-index: 1;
|
|
7944
|
+
}
|
|
7945
|
+
.mark:focus-visible {
|
|
7946
|
+
outline: 2px solid var(--lit-devtools-accent-ring);
|
|
7947
|
+
z-index: 1;
|
|
7948
|
+
}
|
|
7949
|
+
.empty {
|
|
7950
|
+
display: flex;
|
|
7951
|
+
flex-direction: column;
|
|
7952
|
+
align-items: center;
|
|
7953
|
+
justify-content: center;
|
|
7954
|
+
flex: 1;
|
|
7955
|
+
gap: var(--lit-devtools-space-4);
|
|
7956
|
+
color: var(--lit-devtools-text-muted);
|
|
7957
|
+
font-family: var(--lit-devtools-font-sans);
|
|
7958
|
+
font-size: var(--lit-devtools-text-xs);
|
|
7959
|
+
}
|
|
7960
|
+
`];
|
|
7961
|
+
}
|
|
7962
|
+
disconnectedCallback() {
|
|
7963
|
+
super.disconnectedCallback();
|
|
7964
|
+
this._resize?.disconnect();
|
|
7965
|
+
this._resize = null;
|
|
7966
|
+
this._observed = null;
|
|
7967
|
+
}
|
|
7968
|
+
updated() {
|
|
7969
|
+
const el = this._ticksRef.value ?? null;
|
|
7970
|
+
if (el === this._observed) return;
|
|
7971
|
+
this._resize?.disconnect();
|
|
7972
|
+
this._observed = el;
|
|
7973
|
+
if (!el) return;
|
|
7974
|
+
this._resize ??= new ResizeObserver(() => {
|
|
7975
|
+
this._width = this._observed?.clientWidth ?? 0;
|
|
7976
|
+
});
|
|
7977
|
+
this._resize.observe(el);
|
|
7978
|
+
}
|
|
7979
|
+
willUpdate(changed) {
|
|
7980
|
+
if (changed.has("spans") || changed.has("layers") || changed.has("visibleTracks")) {
|
|
7981
|
+
const shown = new Set(this.visibleTracks);
|
|
7982
|
+
this._visibleSpans = this.spans.filter((s) => shown.has(s.layerId));
|
|
7983
|
+
this._tracks = buildTracks(this._visibleSpans, this.layers).filter((t) => shown.has(t.layerId));
|
|
7984
|
+
this._bounds = timeBounds(this._visibleSpans);
|
|
7985
|
+
}
|
|
7986
|
+
}
|
|
7987
|
+
_scale() {
|
|
7988
|
+
const { origin, extent } = this._bounds;
|
|
7989
|
+
const pan = this._follow && this.recording ? livePan(extent, this._zoom) : this._pan;
|
|
7990
|
+
return timeScale(origin, extent, this._width, this._zoom, pan);
|
|
7991
|
+
}
|
|
7992
|
+
/** Commit a zoom/pan, and keep following only if it still ends at the live
|
|
7993
|
+
* edge. */
|
|
7994
|
+
_setView(zoom, pan) {
|
|
7995
|
+
const { extent } = this._bounds;
|
|
7996
|
+
this._zoom = clampZoom(zoom);
|
|
7997
|
+
this._pan = clampPan(pan, extent, this._zoom);
|
|
7998
|
+
this._follow = this._pan >= livePan(extent, this._zoom) - 1e-9;
|
|
7999
|
+
}
|
|
8000
|
+
_currentPan() {
|
|
8001
|
+
return this._scale().start - this._bounds.origin;
|
|
8002
|
+
}
|
|
8003
|
+
_onWheel(e) {
|
|
8004
|
+
if (this._width === 0 || this._visibleSpans.length === 0) return;
|
|
8005
|
+
const plot = this._ticksRef.value;
|
|
8006
|
+
if (!plot) return;
|
|
8007
|
+
const x = e.clientX - plot.getBoundingClientRect().left;
|
|
8008
|
+
if (x < 0) return;
|
|
8009
|
+
e.preventDefault();
|
|
8010
|
+
const { origin, extent } = this._bounds;
|
|
8011
|
+
const pan = this._currentPan();
|
|
8012
|
+
if (Math.abs(e.deltaX) > Math.abs(e.deltaY) || e.shiftKey) {
|
|
8013
|
+
const delta = e.shiftKey ? e.deltaY : e.deltaX;
|
|
8014
|
+
const scale = this._scale();
|
|
8015
|
+
this._setView(this._zoom, pan + delta / scale.pxPerMs);
|
|
8016
|
+
return;
|
|
8017
|
+
}
|
|
8018
|
+
const factor = Math.exp(-e.deltaY * .002);
|
|
8019
|
+
const next = zoomAt(origin, extent, this._width, this._zoom, pan, x, factor);
|
|
8020
|
+
this._setView(next.zoom, next.pan);
|
|
8021
|
+
}
|
|
8022
|
+
_onPointerDown(e) {
|
|
8023
|
+
if (e.button !== 0) return;
|
|
8024
|
+
this._drag = {
|
|
8025
|
+
x: e.clientX,
|
|
8026
|
+
pan: this._currentPan(),
|
|
8027
|
+
moved: false
|
|
8028
|
+
};
|
|
8029
|
+
}
|
|
8030
|
+
_onPointerMove(e) {
|
|
8031
|
+
const drag = this._drag;
|
|
8032
|
+
if (!drag) return;
|
|
8033
|
+
const dx = e.clientX - drag.x;
|
|
8034
|
+
if (!drag.moved && Math.abs(dx) < DRAG_SLOP_PX) return;
|
|
8035
|
+
if (!drag.moved) {
|
|
8036
|
+
drag.moved = true;
|
|
8037
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
8038
|
+
this.requestUpdate();
|
|
8039
|
+
}
|
|
8040
|
+
this._setView(this._zoom, drag.pan - dx / this._scale().pxPerMs);
|
|
8041
|
+
}
|
|
8042
|
+
_onPointerUp() {
|
|
8043
|
+
if (this._drag?.moved) {
|
|
8044
|
+
this._suppressClick = true;
|
|
8045
|
+
setTimeout(() => this._suppressClick = false);
|
|
8046
|
+
this.requestUpdate();
|
|
8047
|
+
}
|
|
8048
|
+
this._drag = null;
|
|
8049
|
+
}
|
|
8050
|
+
_refit() {
|
|
8051
|
+
this._zoom = 1;
|
|
8052
|
+
this._pan = 0;
|
|
8053
|
+
this._follow = true;
|
|
8054
|
+
}
|
|
8055
|
+
_select(key) {
|
|
8056
|
+
if (this._suppressClick) return;
|
|
8057
|
+
this.dispatchEvent(new CustomEvent("span-select", {
|
|
8058
|
+
detail: { key },
|
|
8059
|
+
bubbles: true,
|
|
8060
|
+
composed: true
|
|
8061
|
+
}));
|
|
8062
|
+
}
|
|
8063
|
+
render() {
|
|
8064
|
+
const selected = this.selectedSpan;
|
|
8065
|
+
const detail = selected ? b`<timeline-span-detail
|
|
8066
|
+
filterable
|
|
8067
|
+
.span=${selected}
|
|
8068
|
+
></timeline-span-detail>` : A;
|
|
8069
|
+
if (this._tracks.length === 0) return b`<div class="empty">
|
|
8070
|
+
${this.visibleTracks.length === 0 ? "No tracks shown. Pick some above." : "No events recorded."}
|
|
8071
|
+
</div>
|
|
8072
|
+
${detail}`;
|
|
8073
|
+
const scale = this._scale();
|
|
8074
|
+
return b`
|
|
8075
|
+
<div class="axis">
|
|
8076
|
+
<div class="gutter"></div>
|
|
8077
|
+
<div class="ticks" ${n(this._ticksRef)}>
|
|
8078
|
+
${this._width > 0 ? this._renderTicks(scale) : A}
|
|
8079
|
+
</div>
|
|
8080
|
+
</div>
|
|
8081
|
+
<div
|
|
8082
|
+
class="lanes ${this._drag?.moved ? "dragging" : ""}"
|
|
8083
|
+
title="Wheel to zoom, drag to pan, double-click to fit"
|
|
8084
|
+
@wheel=${this._onWheel}
|
|
8085
|
+
@pointerdown=${this._onPointerDown}
|
|
8086
|
+
@pointermove=${this._onPointerMove}
|
|
8087
|
+
@pointerup=${this._onPointerUp}
|
|
8088
|
+
@pointercancel=${this._onPointerUp}
|
|
8089
|
+
@dblclick=${this._refit}
|
|
8090
|
+
>
|
|
8091
|
+
${this._tracks.map((track) => this._renderLane(track, scale))}
|
|
8092
|
+
</div>
|
|
8093
|
+
${detail}
|
|
8094
|
+
`;
|
|
8095
|
+
}
|
|
8096
|
+
_renderTicks(scale) {
|
|
8097
|
+
const step = niceStep(TICK_PX / scale.pxPerMs);
|
|
8098
|
+
const labels = [];
|
|
8099
|
+
for (let t = Math.ceil(scale.start / step) * step; t <= scale.end && labels.length < 200; t += step) labels.push(b`<span class="tick-label" style="left:${scale.toX(t)}px"
|
|
8100
|
+
>${formatAxis(t, step)}</span
|
|
8101
|
+
>`);
|
|
8102
|
+
return labels;
|
|
8103
|
+
}
|
|
8104
|
+
_renderLane(track, scale) {
|
|
8105
|
+
const layer = this.layers.find((l) => l.id === track.layerId);
|
|
8106
|
+
const color = layerColor(this.layers, track.layerId);
|
|
8107
|
+
const rows = Math.max(track.rows.length, 1);
|
|
8108
|
+
return b`
|
|
8109
|
+
<div class="lane">
|
|
8110
|
+
<div class="gutter" title=${layer?.label ?? track.layerId}>
|
|
8111
|
+
<span class="dot" style="background:${color}"></span>
|
|
8112
|
+
${layer?.label ?? track.layerId}
|
|
8113
|
+
</div>
|
|
8114
|
+
<div class="plot">
|
|
8115
|
+
<div class="rows" style="height:${rows * ROW_PX}px">
|
|
8116
|
+
${this._width > 0 ? track.rows.map((row, i) => this._renderRow(row, i, scale, color)) : A}
|
|
8117
|
+
</div>
|
|
8118
|
+
</div>
|
|
8119
|
+
</div>
|
|
8120
|
+
`;
|
|
8121
|
+
}
|
|
8122
|
+
_renderRow(row, rowIndex, scale, color) {
|
|
8123
|
+
const [from, to] = visibleRange(row, scale.start, scale.end);
|
|
8124
|
+
const liveEdge = this._bounds.origin + this._bounds.extent;
|
|
8125
|
+
const marks = [];
|
|
8126
|
+
let lastTickPx = -Infinity;
|
|
8127
|
+
for (let i = from; i < to; i++) {
|
|
8128
|
+
const span = row[i];
|
|
8129
|
+
const open = span.end === void 0 && span.groupId !== void 0;
|
|
8130
|
+
const x0 = scale.toX(span.start);
|
|
8131
|
+
const x1 = scale.toX(span.end ?? (open ? liveEdge : span.start));
|
|
8132
|
+
const isTick = x1 - x0 < MIN_MARK_PX;
|
|
8133
|
+
const selected = span.key === this.selectedKey;
|
|
8134
|
+
if (isTick && !selected && Math.round(x0) === lastTickPx) continue;
|
|
8135
|
+
if (isTick) lastTickPx = Math.round(x0);
|
|
8136
|
+
const left = Math.max(x0, -1);
|
|
8137
|
+
const width = isTick ? MIN_MARK_PX : Math.min(x1, this._width + 1) - left;
|
|
8138
|
+
marks.push(b`<div
|
|
8139
|
+
class="mark ${isTick ? "tick" : ""} ${open ? "open" : ""} ${selected ? "selected" : ""}"
|
|
8140
|
+
role="button"
|
|
8141
|
+
tabindex="0"
|
|
8142
|
+
title=${describe(span)}
|
|
8143
|
+
style="left:${left}px;width:${width}px;top:${rowIndex * ROW_PX}px;background:${color}"
|
|
8144
|
+
@click=${() => this._select(span.key)}
|
|
8145
|
+
@keydown=${(e) => {
|
|
8146
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
8147
|
+
e.preventDefault();
|
|
8148
|
+
this._select(span.key);
|
|
8149
|
+
}
|
|
8150
|
+
}}
|
|
8151
|
+
>
|
|
8152
|
+
${isTick || width < 40 ? A : span.name}
|
|
8153
|
+
</div>`);
|
|
8154
|
+
}
|
|
8155
|
+
return marks;
|
|
8156
|
+
}
|
|
8157
|
+
};
|
|
8158
|
+
__decorate([n$3({ type: Array })], TimelineTracks.prototype, "spans", void 0);
|
|
8159
|
+
__decorate([n$3({ type: Array })], TimelineTracks.prototype, "layers", void 0);
|
|
8160
|
+
__decorate([n$3({ attribute: false })], TimelineTracks.prototype, "selectedKey", void 0);
|
|
8161
|
+
__decorate([n$3({ attribute: false })], TimelineTracks.prototype, "selectedSpan", void 0);
|
|
8162
|
+
__decorate([n$3({ type: Array })], TimelineTracks.prototype, "visibleTracks", void 0);
|
|
8163
|
+
__decorate([n$3({ type: Boolean })], TimelineTracks.prototype, "recording", void 0);
|
|
8164
|
+
__decorate([r$2()], TimelineTracks.prototype, "_zoom", void 0);
|
|
8165
|
+
__decorate([r$2()], TimelineTracks.prototype, "_pan", void 0);
|
|
8166
|
+
__decorate([r$2()], TimelineTracks.prototype, "_follow", void 0);
|
|
8167
|
+
__decorate([r$2()], TimelineTracks.prototype, "_width", void 0);
|
|
8168
|
+
TimelineTracks = __decorate([t$2("timeline-tracks")], TimelineTracks);
|
|
8169
|
+
//#endregion
|
|
7489
8170
|
//#region src/panel/timeline-store.ts
|
|
7490
8171
|
/**
|
|
7491
8172
|
* @license
|
|
@@ -7514,6 +8195,47 @@ TimelineEventList = __decorate([t$2("timeline-event-list")], TimelineEventList);
|
|
|
7514
8195
|
* recent events; older ones scroll off.
|
|
7515
8196
|
*/
|
|
7516
8197
|
var MAX_EVENTS = 5e3;
|
|
8198
|
+
/**
|
|
8199
|
+
* sessionStorage key for the id of the newest event Clear (or a fresh
|
|
8200
|
+
* recording) threw away. A reloaded panel is refilled from the node's
|
|
8201
|
+
* `timeline-history`, which Clear does not touch (agents still read the same
|
|
8202
|
+
* buffer through `recent-events`), so without this a reload would resurrect
|
|
8203
|
+
* everything the developer cleared. sessionStorage, not a module variable:
|
|
8204
|
+
* the point is to survive the reload. Ids are `${epoch}-${seq}` with a seq
|
|
8205
|
+
* that never resets across Clear (see `devframe/definition.ts`).
|
|
8206
|
+
*/
|
|
8207
|
+
var CLEARED_THROUGH_KEY = "lit-devtools:timeline-cleared-through";
|
|
8208
|
+
var parseEventId = (id) => {
|
|
8209
|
+
if (id === void 0) return void 0;
|
|
8210
|
+
const cut = id.lastIndexOf("-");
|
|
8211
|
+
const seq = Number(id.slice(cut + 1));
|
|
8212
|
+
return cut > 0 && Number.isInteger(seq) ? {
|
|
8213
|
+
epoch: id.slice(0, cut),
|
|
8214
|
+
seq
|
|
8215
|
+
} : void 0;
|
|
8216
|
+
};
|
|
8217
|
+
var readClearedThrough = () => {
|
|
8218
|
+
try {
|
|
8219
|
+
return parseEventId(sessionStorage.getItem(CLEARED_THROUGH_KEY) ?? void 0);
|
|
8220
|
+
} catch {
|
|
8221
|
+
return;
|
|
8222
|
+
}
|
|
8223
|
+
};
|
|
8224
|
+
var rememberClearedThrough = (id) => {
|
|
8225
|
+
try {
|
|
8226
|
+
sessionStorage.setItem(CLEARED_THROUGH_KEY, id);
|
|
8227
|
+
} catch {}
|
|
8228
|
+
};
|
|
8229
|
+
/** Drops events at or before the cleared-through mark. A mark from another
|
|
8230
|
+
* epoch belongs to an earlier dev server and says nothing about these ids. */
|
|
8231
|
+
var withoutCleared = (batch) => {
|
|
8232
|
+
const mark = readClearedThrough();
|
|
8233
|
+
if (mark === void 0) return batch;
|
|
8234
|
+
return batch.filter((event) => {
|
|
8235
|
+
const id = parseEventId(event.id);
|
|
8236
|
+
return id === void 0 || id.epoch !== mark.epoch || id.seq > mark.seq;
|
|
8237
|
+
});
|
|
8238
|
+
};
|
|
7517
8239
|
var events = [];
|
|
7518
8240
|
var error = null;
|
|
7519
8241
|
var started = false;
|
|
@@ -7528,6 +8250,8 @@ var getTimelineEvents = () => events;
|
|
|
7528
8250
|
var getTimelineError = () => error;
|
|
7529
8251
|
var clearTimelineEvents = () => {
|
|
7530
8252
|
if (events.length === 0) return;
|
|
8253
|
+
const newest = events[events.length - 1].id;
|
|
8254
|
+
if (newest !== void 0) rememberClearedThrough(newest);
|
|
7531
8255
|
events = [];
|
|
7532
8256
|
notify$1();
|
|
7533
8257
|
};
|
|
@@ -7553,8 +8277,16 @@ var start = async () => {
|
|
|
7553
8277
|
return;
|
|
7554
8278
|
}
|
|
7555
8279
|
const reader = rpc.rpc.streaming.subscribe(meta.stream.channel, meta.stream.id, { highWaterMark: 4096 });
|
|
8280
|
+
const seeded = withoutCleared(await rpc.rpc.call("timeline-history").catch(() => [])).slice(-5e3);
|
|
8281
|
+
const seededIds = new Set(seeded.map((event) => event.id));
|
|
8282
|
+
if (seeded.length > 0) {
|
|
8283
|
+
events = seeded;
|
|
8284
|
+
notify$1();
|
|
8285
|
+
}
|
|
7556
8286
|
for await (const batch of reader) {
|
|
7557
|
-
const
|
|
8287
|
+
const fresh = withoutCleared(batch).filter((event) => event.id === void 0 || !seededIds.has(event.id));
|
|
8288
|
+
if (fresh.length === 0) continue;
|
|
8289
|
+
const next = [...events, ...fresh];
|
|
7558
8290
|
events = next.length > MAX_EVENTS ? next.slice(-5e3) : next;
|
|
7559
8291
|
notify$1();
|
|
7560
8292
|
}
|
|
@@ -7570,15 +8302,63 @@ var start = async () => {
|
|
|
7570
8302
|
* Copyright 2026 Google LLC
|
|
7571
8303
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
7572
8304
|
*/
|
|
8305
|
+
/** localStorage key remembering List vs Tracks. */
|
|
8306
|
+
var MODE_LS_KEY = "lit-devtools-timeline-mode";
|
|
8307
|
+
/** localStorage key remembering which tracks the user hid. Stored as the
|
|
8308
|
+
* hidden set, not the shown one, so a layer seen for the first time (a
|
|
8309
|
+
* custom layer, a new built-in) gets a track by default. */
|
|
8310
|
+
var HIDDEN_TRACKS_LS_KEY = "lit-devtools-timeline-hidden-tracks";
|
|
8311
|
+
var MODE_TABS = [{
|
|
8312
|
+
id: "list",
|
|
8313
|
+
label: "List"
|
|
8314
|
+
}, {
|
|
8315
|
+
id: "tracks",
|
|
8316
|
+
label: "Tracks"
|
|
8317
|
+
}];
|
|
8318
|
+
var readMode = () => {
|
|
8319
|
+
try {
|
|
8320
|
+
return localStorage.getItem(MODE_LS_KEY) === "tracks" ? "tracks" : "list";
|
|
8321
|
+
} catch {
|
|
8322
|
+
return "list";
|
|
8323
|
+
}
|
|
8324
|
+
};
|
|
8325
|
+
var readHiddenTracks = () => {
|
|
8326
|
+
try {
|
|
8327
|
+
const parsed = JSON.parse(localStorage.getItem(HIDDEN_TRACKS_LS_KEY) ?? "[]");
|
|
8328
|
+
return new Set(Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : []);
|
|
8329
|
+
} catch {
|
|
8330
|
+
return /* @__PURE__ */ new Set();
|
|
8331
|
+
}
|
|
8332
|
+
};
|
|
8333
|
+
var store = (key, value) => {
|
|
8334
|
+
try {
|
|
8335
|
+
localStorage.setItem(key, value);
|
|
8336
|
+
} catch {}
|
|
8337
|
+
};
|
|
7573
8338
|
var TimelineView = class TimelineView extends i$2 {
|
|
7574
8339
|
constructor(..._args) {
|
|
7575
8340
|
super(..._args);
|
|
7576
8341
|
this._recording = false;
|
|
8342
|
+
this._sessionSeen = false;
|
|
7577
8343
|
this._exporting = false;
|
|
7578
8344
|
this._exportNote = null;
|
|
7579
8345
|
this._events = [];
|
|
7580
8346
|
this._layers = [];
|
|
7581
8347
|
this._error = null;
|
|
8348
|
+
this._mode = readMode();
|
|
8349
|
+
this._hiddenTracks = readHiddenTracks();
|
|
8350
|
+
this._selectedKey = null;
|
|
8351
|
+
this._elementFilter = null;
|
|
8352
|
+
this._regex = "";
|
|
8353
|
+
this._spans = [];
|
|
8354
|
+
this._spansOf = null;
|
|
8355
|
+
this._elements = [];
|
|
8356
|
+
this._filteredSpans = [];
|
|
8357
|
+
this._pendingEventId = null;
|
|
8358
|
+
this._revealSelection = false;
|
|
8359
|
+
this._pendingSettled = false;
|
|
8360
|
+
this._listRef = e();
|
|
8361
|
+
this._visibleTracks = [];
|
|
7582
8362
|
this._baseLayers = [];
|
|
7583
8363
|
this._rpc = null;
|
|
7584
8364
|
this._sessionOff = null;
|
|
@@ -7633,10 +8413,47 @@ var TimelineView = class TimelineView extends i$2 {
|
|
|
7633
8413
|
background: var(--lit-devtools-error-soft);
|
|
7634
8414
|
color: var(--lit-devtools-error);
|
|
7635
8415
|
}
|
|
7636
|
-
|
|
8416
|
+
.toolbar segmented-tabs {
|
|
8417
|
+
align-self: stretch;
|
|
8418
|
+
margin: calc(-1 * var(--lit-devtools-space-3)) 0;
|
|
8419
|
+
}
|
|
8420
|
+
.filterbar {
|
|
8421
|
+
display: flex;
|
|
8422
|
+
align-items: center;
|
|
8423
|
+
gap: var(--lit-devtools-space-3);
|
|
8424
|
+
padding: var(--lit-devtools-space-2) var(--lit-devtools-space-5);
|
|
8425
|
+
border-bottom: 1px solid var(--lit-devtools-border);
|
|
8426
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
8427
|
+
color: var(--lit-devtools-text-muted);
|
|
8428
|
+
flex-shrink: 0;
|
|
8429
|
+
}
|
|
8430
|
+
.filterbar select,
|
|
8431
|
+
.filterbar input.regex {
|
|
8432
|
+
background: var(--lit-devtools-surface-elevated);
|
|
8433
|
+
color: var(--lit-devtools-text);
|
|
8434
|
+
border: 1px solid var(--lit-devtools-border-strong);
|
|
8435
|
+
border-radius: var(--lit-devtools-radius-sm);
|
|
8436
|
+
padding: var(--lit-devtools-space-1) var(--lit-devtools-space-3);
|
|
8437
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
8438
|
+
font-family: var(--lit-devtools-font-mono);
|
|
8439
|
+
}
|
|
8440
|
+
.filterbar input.regex {
|
|
8441
|
+
min-width: 140px;
|
|
8442
|
+
}
|
|
8443
|
+
.filterbar input.regex::placeholder {
|
|
8444
|
+
color: var(--lit-devtools-text-muted);
|
|
8445
|
+
}
|
|
8446
|
+
.filterbar input.regex.invalid {
|
|
8447
|
+
border-color: var(--lit-devtools-error);
|
|
8448
|
+
}
|
|
8449
|
+
timeline-event-list,
|
|
8450
|
+
timeline-tracks {
|
|
7637
8451
|
flex: 1;
|
|
7638
8452
|
overflow: hidden;
|
|
7639
8453
|
}
|
|
8454
|
+
[hidden] {
|
|
8455
|
+
display: none !important;
|
|
8456
|
+
}
|
|
7640
8457
|
.error {
|
|
7641
8458
|
padding: var(--lit-devtools-space-5);
|
|
7642
8459
|
color: var(--lit-devtools-text-secondary);
|
|
@@ -7659,6 +8476,60 @@ var TimelineView = class TimelineView extends i$2 {
|
|
|
7659
8476
|
this._storeOff?.();
|
|
7660
8477
|
this._storeOff = null;
|
|
7661
8478
|
}
|
|
8479
|
+
willUpdate(changed) {
|
|
8480
|
+
if (changed.has("_layers") || changed.has("_hiddenTracks")) this._visibleTracks = this._layers.filter((l) => l.enabled && !this._hiddenTracks.has(l.id)).map((l) => l.id);
|
|
8481
|
+
if (this._events !== this._spansOf) {
|
|
8482
|
+
this._spansOf = this._events;
|
|
8483
|
+
this._spans = toSpans(this._events);
|
|
8484
|
+
this._elements = listElements(this._events);
|
|
8485
|
+
if (this._elementFilter !== null && !this._elements.some((el) => el.id === this._elementFilter)) this._elementFilter = null;
|
|
8486
|
+
const key = this._selectedKey;
|
|
8487
|
+
if (key !== null && !isRawKey(key) && !this._spans.some((s) => s.key === key)) this._selectedKey = null;
|
|
8488
|
+
this._applyPendingEvent();
|
|
8489
|
+
}
|
|
8490
|
+
if (changed.has("_events") || changed.has("_elementFilter") || changed.has("_regex")) this._filteredSpans = filterSpans(this._spans, this._elementFilter, compileRegex(this._regex).re);
|
|
8491
|
+
}
|
|
8492
|
+
/**
|
|
8493
|
+
* Select the span a deep link named. Its start event is the natural target
|
|
8494
|
+
* (`span.events[0]`), but any of the span's events matches, so a link to
|
|
8495
|
+
* either half of a pair lands on the same row.
|
|
8496
|
+
*
|
|
8497
|
+
* An id the buffer does not hold — evicted, or from another session — ends
|
|
8498
|
+
* with no selection and no error, once there are events to have looked in.
|
|
8499
|
+
*/
|
|
8500
|
+
selectEvent(id) {
|
|
8501
|
+
this._pendingEventId = id;
|
|
8502
|
+
this._applyPendingEvent();
|
|
8503
|
+
this.requestUpdate();
|
|
8504
|
+
}
|
|
8505
|
+
_applyPendingEvent() {
|
|
8506
|
+
const id = this._pendingEventId;
|
|
8507
|
+
if (id === null || this._spans.length === 0) return;
|
|
8508
|
+
const span = this._spans.find((s) => s.events.some((e) => e.id === id));
|
|
8509
|
+
this._pendingEventId = null;
|
|
8510
|
+
this._pendingSettled = true;
|
|
8511
|
+
this._selectedKey = span?.key ?? null;
|
|
8512
|
+
if (span !== void 0) this._revealSelection = true;
|
|
8513
|
+
}
|
|
8514
|
+
/** Id of the selected span's start event, for the shell's URL sync. While a
|
|
8515
|
+
* linked id is still waiting on the store it is reported as-is, so the
|
|
8516
|
+
* address bar does not drop it before the events land. */
|
|
8517
|
+
get selectedEventId() {
|
|
8518
|
+
if (this._pendingEventId !== null) return this._pendingEventId;
|
|
8519
|
+
const key = this._selectedKey;
|
|
8520
|
+
if (key === null) return null;
|
|
8521
|
+
return this._spans.find((s) => s.key === key)?.events[0]?.id ?? null;
|
|
8522
|
+
}
|
|
8523
|
+
updated(changed) {
|
|
8524
|
+
if (this._revealSelection) {
|
|
8525
|
+
this._revealSelection = false;
|
|
8526
|
+
this.updateComplete.then(() => this._listRef.value?.updateComplete).then(() => this._listRef.value?.reveal());
|
|
8527
|
+
}
|
|
8528
|
+
if (changed.has("_selectedKey") || this._pendingSettled) {
|
|
8529
|
+
this._pendingSettled = false;
|
|
8530
|
+
this.dispatchEvent(new CustomEvent("selection-change"));
|
|
8531
|
+
}
|
|
8532
|
+
}
|
|
7662
8533
|
_readStore() {
|
|
7663
8534
|
if (!this._active) return;
|
|
7664
8535
|
this._events = getTimelineEvents();
|
|
@@ -7690,7 +8561,8 @@ var TimelineView = class TimelineView extends i$2 {
|
|
|
7690
8561
|
* to the recording flag and merged layer list. */
|
|
7691
8562
|
_applySession(state) {
|
|
7692
8563
|
const { recordingState } = state.layers;
|
|
7693
|
-
if (recordingState && !this._recording) clearTimelineEvents();
|
|
8564
|
+
if (this._sessionSeen && recordingState && !this._recording) clearTimelineEvents();
|
|
8565
|
+
this._sessionSeen = true;
|
|
7694
8566
|
this._recording = recordingState;
|
|
7695
8567
|
this._layers = this._mergeLayers(state.layers, state.customLayers);
|
|
7696
8568
|
}
|
|
@@ -7745,10 +8617,46 @@ var TimelineView = class TimelineView extends i$2 {
|
|
|
7745
8617
|
console.warn("[lit-devtools] toggle-layer failed", err);
|
|
7746
8618
|
});
|
|
7747
8619
|
}
|
|
8620
|
+
_onModeChange(e) {
|
|
8621
|
+
e.stopPropagation();
|
|
8622
|
+
this._mode = e.detail.value === "tracks" ? "tracks" : "list";
|
|
8623
|
+
store(MODE_LS_KEY, this._mode);
|
|
8624
|
+
if (this._mode === "list") this.updateComplete.then(() => this._listRef.value?.reveal());
|
|
8625
|
+
}
|
|
8626
|
+
_onTrackToggle(e) {
|
|
8627
|
+
e.stopPropagation();
|
|
8628
|
+
const hidden = new Set(this._hiddenTracks);
|
|
8629
|
+
if (!hidden.delete(e.detail.id)) hidden.add(e.detail.id);
|
|
8630
|
+
this._hiddenTracks = hidden;
|
|
8631
|
+
store(HIDDEN_TRACKS_LS_KEY, JSON.stringify([...hidden]));
|
|
8632
|
+
}
|
|
8633
|
+
_onElementSelect(e) {
|
|
8634
|
+
const v = e.target.value;
|
|
8635
|
+
this._elementFilter = v === "" ? null : Number(v);
|
|
8636
|
+
}
|
|
8637
|
+
_onRegexInput(e) {
|
|
8638
|
+
this._regex = e.target.value;
|
|
8639
|
+
}
|
|
8640
|
+
/** The detail pane's **filter** link. */
|
|
8641
|
+
_onElementFilter(e) {
|
|
8642
|
+
this._elementFilter = e.detail.id;
|
|
8643
|
+
}
|
|
8644
|
+
_onSpanSelect(e) {
|
|
8645
|
+
this._pendingEventId = null;
|
|
8646
|
+
this._selectedKey = e.detail.key;
|
|
8647
|
+
}
|
|
7748
8648
|
render() {
|
|
7749
8649
|
if (this._error !== null) return b`<div class="error">${this._error}</div>`;
|
|
8650
|
+
const tracks = this._mode === "tracks";
|
|
8651
|
+
const captured = this._layers.filter((l) => l.enabled);
|
|
7750
8652
|
return b`
|
|
7751
8653
|
<div class="toolbar">
|
|
8654
|
+
<segmented-tabs
|
|
8655
|
+
size="sm"
|
|
8656
|
+
.items=${MODE_TABS}
|
|
8657
|
+
.value=${this._mode}
|
|
8658
|
+
@change=${this._onModeChange}
|
|
8659
|
+
></segmented-tabs>
|
|
7752
8660
|
${this._exportNote === null ? A : b`<span class="export-note">${this._exportNote}</span>`}
|
|
7753
8661
|
<span class="spacer"></span>
|
|
7754
8662
|
<button
|
|
@@ -7770,10 +8678,67 @@ var TimelineView = class TimelineView extends i$2 {
|
|
|
7770
8678
|
.layers=${this._layers}
|
|
7771
8679
|
@layer-toggle=${this._onLayerToggle}
|
|
7772
8680
|
></timeline-layers>
|
|
8681
|
+
${tracks ? b`<timeline-layers
|
|
8682
|
+
caption="Tracks"
|
|
8683
|
+
.layers=${captured.map((l) => ({
|
|
8684
|
+
...l,
|
|
8685
|
+
enabled: !this._hiddenTracks.has(l.id)
|
|
8686
|
+
}))}
|
|
8687
|
+
@layer-toggle=${this._onTrackToggle}
|
|
8688
|
+
></timeline-layers>` : A}
|
|
8689
|
+
${this._events.length > 0 ? b`<div class="filterbar">
|
|
8690
|
+
${this._elements.length > 0 ? b`
|
|
8691
|
+
<span>Element:</span>
|
|
8692
|
+
<select @change=${this._onElementSelect}>
|
|
8693
|
+
<option
|
|
8694
|
+
value=""
|
|
8695
|
+
?selected=${this._elementFilter === null}
|
|
8696
|
+
>
|
|
8697
|
+
All elements
|
|
8698
|
+
</option>
|
|
8699
|
+
${this._elements.map((el) => b`
|
|
8700
|
+
<option
|
|
8701
|
+
value=${el.id}
|
|
8702
|
+
?selected=${this._elementFilter === el.id}
|
|
8703
|
+
>
|
|
8704
|
+
<${el.tag}> #${el.id}
|
|
8705
|
+
</option>
|
|
8706
|
+
`)}
|
|
8707
|
+
</select>
|
|
8708
|
+
` : A}
|
|
8709
|
+
<input
|
|
8710
|
+
class="regex ${compileRegex(this._regex).invalid ? "invalid" : ""}"
|
|
8711
|
+
type="text"
|
|
8712
|
+
spellcheck="false"
|
|
8713
|
+
placeholder="filter regex…"
|
|
8714
|
+
title="Case-insensitive regex matched against element tag, title and subtitle"
|
|
8715
|
+
.value=${this._regex}
|
|
8716
|
+
@input=${this._onRegexInput}
|
|
8717
|
+
/>
|
|
8718
|
+
</div>` : A}
|
|
7773
8719
|
<timeline-event-list
|
|
8720
|
+
${n(this._listRef)}
|
|
8721
|
+
?hidden=${tracks}
|
|
7774
8722
|
.events=${this._events}
|
|
8723
|
+
.spans=${this._spans}
|
|
7775
8724
|
.layers=${this._layers}
|
|
8725
|
+
.selectedKey=${this._selectedKey}
|
|
8726
|
+
.elementFilter=${this._elementFilter}
|
|
8727
|
+
.regex=${this._regex}
|
|
8728
|
+
@span-select=${this._onSpanSelect}
|
|
8729
|
+
@element-filter=${this._onElementFilter}
|
|
7776
8730
|
></timeline-event-list>
|
|
8731
|
+
<timeline-tracks
|
|
8732
|
+
?hidden=${!tracks}
|
|
8733
|
+
.spans=${this._filteredSpans}
|
|
8734
|
+
.layers=${this._layers}
|
|
8735
|
+
.visibleTracks=${this._visibleTracks}
|
|
8736
|
+
.selectedKey=${this._selectedKey}
|
|
8737
|
+
.selectedSpan=${this._selectedKey === null ? void 0 : this._spans.find((s) => s.key === this._selectedKey)}
|
|
8738
|
+
?recording=${this._recording}
|
|
8739
|
+
@span-select=${this._onSpanSelect}
|
|
8740
|
+
@element-filter=${this._onElementFilter}
|
|
8741
|
+
></timeline-tracks>
|
|
7777
8742
|
`;
|
|
7778
8743
|
}
|
|
7779
8744
|
};
|
|
@@ -7783,6 +8748,11 @@ __decorate([r$2()], TimelineView.prototype, "_exportNote", void 0);
|
|
|
7783
8748
|
__decorate([r$2()], TimelineView.prototype, "_events", void 0);
|
|
7784
8749
|
__decorate([r$2()], TimelineView.prototype, "_layers", void 0);
|
|
7785
8750
|
__decorate([r$2()], TimelineView.prototype, "_error", void 0);
|
|
8751
|
+
__decorate([r$2()], TimelineView.prototype, "_mode", void 0);
|
|
8752
|
+
__decorate([r$2()], TimelineView.prototype, "_hiddenTracks", void 0);
|
|
8753
|
+
__decorate([r$2()], TimelineView.prototype, "_selectedKey", void 0);
|
|
8754
|
+
__decorate([r$2()], TimelineView.prototype, "_elementFilter", void 0);
|
|
8755
|
+
__decorate([r$2()], TimelineView.prototype, "_regex", void 0);
|
|
7786
8756
|
TimelineView = __decorate([t$2("timeline-view")], TimelineView);
|
|
7787
8757
|
//#endregion
|
|
7788
8758
|
//#region src/types/hmr-incompatibility.ts
|
|
@@ -8597,6 +9567,29 @@ var writeLocal = (override) => {
|
|
|
8597
9567
|
else localStorage.setItem(SETTINGS_OVERRIDE_LS_KEY, JSON.stringify(override));
|
|
8598
9568
|
} catch {}
|
|
8599
9569
|
};
|
|
9570
|
+
/**
|
|
9571
|
+
* The config values the current overrides were made against; `{}` when unset
|
|
9572
|
+
* or unparsable. Deliberately separate from the override itself, so the
|
|
9573
|
+
* runtime's apply and the live push never carry it.
|
|
9574
|
+
*/
|
|
9575
|
+
var readBaselines = () => {
|
|
9576
|
+
try {
|
|
9577
|
+
const raw = localStorage.getItem(OVERRIDE_BASELINES_LS_KEY);
|
|
9578
|
+
if (raw !== null) return JSON.parse(raw);
|
|
9579
|
+
} catch {}
|
|
9580
|
+
return {};
|
|
9581
|
+
};
|
|
9582
|
+
var writeLocalBaselines = (baselines) => {
|
|
9583
|
+
try {
|
|
9584
|
+
if (baselines === null || Object.keys(baselines).length === 0) localStorage.removeItem(OVERRIDE_BASELINES_LS_KEY);
|
|
9585
|
+
else localStorage.setItem(OVERRIDE_BASELINES_LS_KEY, JSON.stringify(baselines));
|
|
9586
|
+
} catch {}
|
|
9587
|
+
};
|
|
9588
|
+
/** Write the durable baselines (or delete them when empty), best-effort. */
|
|
9589
|
+
var persistBaselines = (baselines) => {
|
|
9590
|
+
const empty = baselines === void 0 || Object.keys(baselines).length === 0;
|
|
9591
|
+
litRpc().then((rpc) => empty ? rpc.settings.global.delete("overrideBaselines") : rpc.settings.global.set("overrideBaselines", baselines)).catch(() => {});
|
|
9592
|
+
};
|
|
8600
9593
|
/** Send an override to the app runtime over RPC, best-effort. */
|
|
8601
9594
|
var pushLive = (override) => {
|
|
8602
9595
|
litRpc().then((rpc) => rpc.rpc.call("set-settings-override", override)).catch(() => {});
|
|
@@ -8616,10 +9609,18 @@ var onOverrideChange = (cb) => {
|
|
|
8616
9609
|
listeners.delete(cb);
|
|
8617
9610
|
};
|
|
8618
9611
|
};
|
|
8619
|
-
/**
|
|
8620
|
-
|
|
9612
|
+
/**
|
|
9613
|
+
* A change made in this panel: all three copies, then listeners. `baselines`,
|
|
9614
|
+
* when given, replaces the recorded baselines (local and durable; never sent
|
|
9615
|
+
* to the page).
|
|
9616
|
+
*/
|
|
9617
|
+
var commitOverride = (next, baselines) => {
|
|
8621
9618
|
writeLocal(next);
|
|
8622
9619
|
persist(next);
|
|
9620
|
+
if (baselines !== void 0) {
|
|
9621
|
+
writeLocalBaselines(baselines);
|
|
9622
|
+
persistBaselines(baselines);
|
|
9623
|
+
}
|
|
8623
9624
|
pushLive(next);
|
|
8624
9625
|
notify(next);
|
|
8625
9626
|
};
|
|
@@ -8634,8 +9635,9 @@ var patchOverride = (patch) => {
|
|
|
8634
9635
|
* A value that arrived *from* the durable store: mirror it locally and to the
|
|
8635
9636
|
* page, but don't write it back — that would be a pointless round trip.
|
|
8636
9637
|
*/
|
|
8637
|
-
var adoptOverride = (next) => {
|
|
9638
|
+
var adoptOverride = (next, baselines) => {
|
|
8638
9639
|
writeLocal(next);
|
|
9640
|
+
if (baselines !== void 0) writeLocalBaselines(baselines);
|
|
8639
9641
|
pushLive(next);
|
|
8640
9642
|
notify(next);
|
|
8641
9643
|
};
|
|
@@ -8647,9 +9649,46 @@ var adoptOverride = (next) => {
|
|
|
8647
9649
|
var resetOverride = (envValues) => {
|
|
8648
9650
|
writeLocal(null);
|
|
8649
9651
|
persist(void 0);
|
|
9652
|
+
writeLocalBaselines(null);
|
|
9653
|
+
persistBaselines(void 0);
|
|
8650
9654
|
if (envValues !== void 0) pushLive(envValues);
|
|
8651
9655
|
notify({});
|
|
8652
9656
|
};
|
|
9657
|
+
/**
|
|
9658
|
+
* Drop one key from the override, leaving the rest in place. `baseline` is
|
|
9659
|
+
* that key's resolved config value; it goes to the live runtime alongside the
|
|
9660
|
+
* remaining override (a missing field would leave the current live value in
|
|
9661
|
+
* place) but isn't stored, so the key reads as un-overridden again.
|
|
9662
|
+
*/
|
|
9663
|
+
var dropOverrideKey = (key, baseline) => {
|
|
9664
|
+
const next = { ...readOverride() };
|
|
9665
|
+
delete next[key];
|
|
9666
|
+
const empty = Object.keys(next).length === 0;
|
|
9667
|
+
writeLocal(empty ? null : next);
|
|
9668
|
+
persist(empty ? void 0 : next);
|
|
9669
|
+
const baselines = readBaselines();
|
|
9670
|
+
delete baselines[key];
|
|
9671
|
+
writeLocalBaselines(baselines);
|
|
9672
|
+
persistBaselines(baselines);
|
|
9673
|
+
pushLive({
|
|
9674
|
+
...next,
|
|
9675
|
+
[key]: baseline
|
|
9676
|
+
});
|
|
9677
|
+
notify(next);
|
|
9678
|
+
};
|
|
9679
|
+
/**
|
|
9680
|
+
* Re-stamp one key's baseline to the config value it has now ("Keep"), so the
|
|
9681
|
+
* changed-config hint goes away while the override stays.
|
|
9682
|
+
*/
|
|
9683
|
+
var keepBaseline = (key, current) => {
|
|
9684
|
+
const next = {
|
|
9685
|
+
...readBaselines(),
|
|
9686
|
+
[key]: current
|
|
9687
|
+
};
|
|
9688
|
+
writeLocalBaselines(next);
|
|
9689
|
+
persistBaselines(next);
|
|
9690
|
+
notify(readOverride());
|
|
9691
|
+
};
|
|
8653
9692
|
//#endregion
|
|
8654
9693
|
//#region src/panel/components-view.ts
|
|
8655
9694
|
/**
|
|
@@ -9707,6 +10746,8 @@ var DEEP_LINK_TABS = [
|
|
|
9707
10746
|
"settings"
|
|
9708
10747
|
];
|
|
9709
10748
|
var isTab = (value) => typeof value === "string" && DEEP_LINK_TABS.includes(value);
|
|
10749
|
+
/** True when a link asks for anything at all. */
|
|
10750
|
+
var hasLink = (link) => link.tab !== void 0 || link.componentId !== void 0 || link.eventId !== void 0;
|
|
9710
10751
|
/** Parse a `DeepLink` out of hash params, ignoring anything unrecognised. */
|
|
9711
10752
|
var fromParams = (params) => {
|
|
9712
10753
|
const link = {};
|
|
@@ -9714,6 +10755,8 @@ var fromParams = (params) => {
|
|
|
9714
10755
|
if (isTab(tab)) link.tab = tab;
|
|
9715
10756
|
const id = Number(params.get("component"));
|
|
9716
10757
|
if (params.get("component") !== null && Number.isInteger(id)) link.componentId = id;
|
|
10758
|
+
const eventId = params.get("event");
|
|
10759
|
+
if (eventId !== null && eventId !== "") link.eventId = eventId;
|
|
9717
10760
|
return link;
|
|
9718
10761
|
};
|
|
9719
10762
|
/** Read the link encoded in the current URL hash, if any. */
|
|
@@ -9733,6 +10776,8 @@ var writeHashLink = (link) => {
|
|
|
9733
10776
|
else params.set("tab", link.tab);
|
|
9734
10777
|
if (link.componentId === void 0) params.delete("component");
|
|
9735
10778
|
else params.set("component", String(link.componentId));
|
|
10779
|
+
if (link.eventId === void 0) params.delete("event");
|
|
10780
|
+
else params.set("event", link.eventId);
|
|
9736
10781
|
const next = params.toString();
|
|
9737
10782
|
if (next === location.hash.replace(/^#/, "")) return;
|
|
9738
10783
|
history.replaceState(history.state, "", next === "" ? location.pathname : `#${next}`);
|
|
@@ -9747,7 +10792,7 @@ var writeHashLink = (link) => {
|
|
|
9747
10792
|
*/
|
|
9748
10793
|
var onDeepLink = (apply) => {
|
|
9749
10794
|
const hashLink = readHashLink();
|
|
9750
|
-
if (hashLink
|
|
10795
|
+
if (hasLink(hashLink)) apply(hashLink);
|
|
9751
10796
|
window.addEventListener("hashchange", () => apply(readHashLink()));
|
|
9752
10797
|
(async () => {
|
|
9753
10798
|
try {
|
|
@@ -9762,7 +10807,9 @@ var onDeepLink = (apply) => {
|
|
|
9762
10807
|
if (isTab(tab)) link.tab = tab;
|
|
9763
10808
|
const id = params["componentId"];
|
|
9764
10809
|
if (typeof id === "number" && Number.isInteger(id)) link.componentId = id;
|
|
9765
|
-
|
|
10810
|
+
const eventId = params["eventId"];
|
|
10811
|
+
if (typeof eventId === "string" && eventId !== "") link.eventId = eventId;
|
|
10812
|
+
if (hasLink(link)) apply(link);
|
|
9766
10813
|
};
|
|
9767
10814
|
handle(state.value());
|
|
9768
10815
|
state.on("updated", handle);
|
|
@@ -9770,6 +10817,27 @@ var onDeepLink = (apply) => {
|
|
|
9770
10817
|
})();
|
|
9771
10818
|
};
|
|
9772
10819
|
//#endregion
|
|
10820
|
+
//#region src/lib/override-baselines.ts
|
|
10821
|
+
/**
|
|
10822
|
+
* @license
|
|
10823
|
+
* Copyright 2026 Google LLC
|
|
10824
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
10825
|
+
*/
|
|
10826
|
+
/**
|
|
10827
|
+
* Whether the config value an override was made against has since changed.
|
|
10828
|
+
*
|
|
10829
|
+
* `recorded` is `undefined` for a legacy override that predates baseline
|
|
10830
|
+
* tracking, which never counts as a change (no false positives). Values are
|
|
10831
|
+
* compared structurally. The source-overlay editor reports `'custom'` for an
|
|
10832
|
+
* `EditorConfig` object, which can't be compared meaningfully, so any side
|
|
10833
|
+
* being `'custom'` skips the check.
|
|
10834
|
+
*/
|
|
10835
|
+
var baselineChanged = (key, recorded, current) => {
|
|
10836
|
+
if (recorded === void 0 || current === void 0) return false;
|
|
10837
|
+
if (key === "sourceOverlayEditor" && (recorded === "custom" || current === "custom")) return false;
|
|
10838
|
+
return JSON.stringify(recorded) !== JSON.stringify(current);
|
|
10839
|
+
};
|
|
10840
|
+
//#endregion
|
|
9773
10841
|
//#region src/panel/devtools-settings.ts
|
|
9774
10842
|
/**
|
|
9775
10843
|
* @license
|
|
@@ -9781,6 +10849,7 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
9781
10849
|
super(..._args);
|
|
9782
10850
|
this._settings = null;
|
|
9783
10851
|
this._override = {};
|
|
10852
|
+
this._recorded = {};
|
|
9784
10853
|
this._loaded = false;
|
|
9785
10854
|
this._colorScheme = "auto";
|
|
9786
10855
|
this._unsubscribeOverride = null;
|
|
@@ -9880,6 +10949,52 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
9880
10949
|
margin-left: var(--lit-devtools-space-3);
|
|
9881
10950
|
vertical-align: middle;
|
|
9882
10951
|
}
|
|
10952
|
+
.src {
|
|
10953
|
+
margin-left: var(--lit-devtools-space-3);
|
|
10954
|
+
vertical-align: middle;
|
|
10955
|
+
}
|
|
10956
|
+
.row-reset {
|
|
10957
|
+
appearance: none;
|
|
10958
|
+
border: 1px solid var(--lit-devtools-border-strong);
|
|
10959
|
+
background: var(--lit-devtools-surface-elevated);
|
|
10960
|
+
color: var(--lit-devtools-text-muted);
|
|
10961
|
+
border-radius: var(--lit-devtools-radius-sm);
|
|
10962
|
+
font: inherit;
|
|
10963
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
10964
|
+
line-height: 1;
|
|
10965
|
+
padding: 1px var(--lit-devtools-space-2);
|
|
10966
|
+
margin-left: var(--lit-devtools-space-3);
|
|
10967
|
+
vertical-align: middle;
|
|
10968
|
+
cursor: pointer;
|
|
10969
|
+
}
|
|
10970
|
+
.row-reset:hover {
|
|
10971
|
+
background: var(--lit-devtools-surface-hover);
|
|
10972
|
+
color: var(--lit-devtools-text);
|
|
10973
|
+
}
|
|
10974
|
+
.nudge {
|
|
10975
|
+
display: flex;
|
|
10976
|
+
align-items: center;
|
|
10977
|
+
flex-wrap: wrap;
|
|
10978
|
+
gap: var(--lit-devtools-space-3);
|
|
10979
|
+
margin-top: var(--lit-devtools-space-2);
|
|
10980
|
+
color: var(--lit-devtools-warning);
|
|
10981
|
+
font-family: inherit;
|
|
10982
|
+
font-size: var(--lit-devtools-text-2xs);
|
|
10983
|
+
}
|
|
10984
|
+
.nudge button {
|
|
10985
|
+
appearance: none;
|
|
10986
|
+
border: 1px solid var(--lit-devtools-border-strong);
|
|
10987
|
+
background: var(--lit-devtools-surface-elevated);
|
|
10988
|
+
color: var(--lit-devtools-text);
|
|
10989
|
+
border-radius: var(--lit-devtools-radius-sm);
|
|
10990
|
+
font: inherit;
|
|
10991
|
+
line-height: 1;
|
|
10992
|
+
padding: 1px var(--lit-devtools-space-3);
|
|
10993
|
+
cursor: pointer;
|
|
10994
|
+
}
|
|
10995
|
+
.nudge button:hover {
|
|
10996
|
+
background: var(--lit-devtools-surface-hover);
|
|
10997
|
+
}
|
|
9883
10998
|
label.toggle {
|
|
9884
10999
|
display: inline-flex;
|
|
9885
11000
|
align-items: center;
|
|
@@ -9920,8 +11035,10 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
9920
11035
|
connectedCallback() {
|
|
9921
11036
|
super.connectedCallback();
|
|
9922
11037
|
this._override = readOverride();
|
|
11038
|
+
this._recorded = readBaselines();
|
|
9923
11039
|
this._unsubscribeOverride = onOverrideChange((o) => {
|
|
9924
11040
|
this._override = o;
|
|
11041
|
+
this._recorded = readBaselines();
|
|
9925
11042
|
});
|
|
9926
11043
|
this._colorScheme = readColorSchemePreference();
|
|
9927
11044
|
this._fetch();
|
|
@@ -9961,16 +11078,16 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
9961
11078
|
}
|
|
9962
11079
|
/**
|
|
9963
11080
|
* Apply a settings snapshot. Both branches no-op when the value already
|
|
9964
|
-
* matches, which is what stops `_adopt` -> `
|
|
11081
|
+
* matches, which is what stops `_adopt` -> `commitOverride` -> store write ->
|
|
9965
11082
|
* `onChange` -> `_adopt` from looping.
|
|
9966
11083
|
*/
|
|
9967
11084
|
_adopt(all) {
|
|
9968
|
-
const { appearance, override } = all;
|
|
11085
|
+
const { appearance, override, overrideBaselines } = all;
|
|
9969
11086
|
if (appearance !== void 0 && appearance !== this._colorScheme) {
|
|
9970
11087
|
this._colorScheme = appearance;
|
|
9971
11088
|
setColorSchemePreference(appearance);
|
|
9972
11089
|
}
|
|
9973
|
-
if (override !== void 0 && JSON.stringify(override) !== JSON.stringify(this._override)) adoptOverride(override);
|
|
11090
|
+
if (override !== void 0 && (JSON.stringify(override) !== JSON.stringify(this._override) || JSON.stringify(overrideBaselines ?? {}) !== JSON.stringify(readBaselines()))) adoptOverride(override, overrideBaselines ?? {});
|
|
9974
11091
|
}
|
|
9975
11092
|
/** Write one preference through to the durable store, best-effort. */
|
|
9976
11093
|
_persist(key, value) {
|
|
@@ -9991,43 +11108,92 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
9991
11108
|
this._loaded = true;
|
|
9992
11109
|
}
|
|
9993
11110
|
}
|
|
9994
|
-
/** Persist the merged override and push it to the app runtime. */
|
|
9995
|
-
_push(override) {
|
|
9996
|
-
commitOverride(override);
|
|
9997
|
-
}
|
|
9998
11111
|
_set(key, value) {
|
|
9999
|
-
this.
|
|
11112
|
+
const s = this._settings;
|
|
11113
|
+
const baselines = readBaselines();
|
|
11114
|
+
if (s !== null && key in this._baselines(s)) baselines[key] = this._baselines(s)[key];
|
|
11115
|
+
commitOverride({
|
|
10000
11116
|
...this._override,
|
|
10001
11117
|
[key]: value
|
|
10002
|
-
});
|
|
11118
|
+
}, baselines);
|
|
10003
11119
|
}
|
|
10004
|
-
/**
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
resetOverride(s === null ? void 0 : {
|
|
11120
|
+
/** The resolved config value of every overridable setting. */
|
|
11121
|
+
_baselines(s) {
|
|
11122
|
+
return {
|
|
10008
11123
|
hmrReconnect: s.hmr.reconnect,
|
|
10009
11124
|
hmrOnIncompatible: s.hmr.onIncompatible,
|
|
10010
11125
|
hmrIndicatorVisible: s.hmr.indicatorEnabled,
|
|
10011
11126
|
hmrIndicatorCount: s.hmr.indicatorCount,
|
|
10012
|
-
sourceOverlayEditor: s.sourceOverlay.editor
|
|
11127
|
+
sourceOverlayEditor: s.sourceOverlay.editor
|
|
11128
|
+
};
|
|
11129
|
+
}
|
|
11130
|
+
/** Clear overrides and revert the runtime to the resolved env values. */
|
|
11131
|
+
_reset() {
|
|
11132
|
+
const s = this._settings;
|
|
11133
|
+
resetOverride(s === null ? void 0 : {
|
|
11134
|
+
...this._baselines(s),
|
|
10013
11135
|
flashUpdates: false,
|
|
10014
|
-
flashUpdatesRamp: false
|
|
11136
|
+
flashUpdatesRamp: false,
|
|
11137
|
+
chromeTracks: false
|
|
10015
11138
|
});
|
|
10016
11139
|
}
|
|
11140
|
+
/** Drop one overridden setting, reverting it (live) to its baseline. */
|
|
11141
|
+
_resetKey(key) {
|
|
11142
|
+
const s = this._settings;
|
|
11143
|
+
if (s === null) return;
|
|
11144
|
+
dropOverrideKey(key, this._baselines(s)[key]);
|
|
11145
|
+
}
|
|
10017
11146
|
_pill(on) {
|
|
10018
11147
|
return b`<span class="pill ${on ? "on" : ""}"
|
|
10019
11148
|
>${on ? "enabled" : "disabled"}</span
|
|
10020
11149
|
>`;
|
|
10021
11150
|
}
|
|
10022
|
-
|
|
10023
|
-
|
|
11151
|
+
/**
|
|
11152
|
+
* Origin badge behind a value, from the server-side provenance:
|
|
11153
|
+
* "(env)", "(option)" or "(default)". Nothing when the server sent none.
|
|
11154
|
+
*/
|
|
11155
|
+
_source(key) {
|
|
11156
|
+
const src = this._settings?.sources?.[key];
|
|
11157
|
+
return src ? b`<span class="env src">(${src})</span>` : A;
|
|
11158
|
+
}
|
|
11159
|
+
/**
|
|
11160
|
+
* Badge for an overridable row. Without an override it's the plain origin
|
|
11161
|
+
* badge; with one it's "(overridden)", the baseline it replaced
|
|
11162
|
+
* ("env: Zed") and a reset for just this row. `baseline` is the config
|
|
11163
|
+
* value as the row displays it, `fmt` renders a recorded raw value the same
|
|
11164
|
+
* way. When the config value moved since the override was made, a second
|
|
11165
|
+
* line says so and offers Reset or Keep.
|
|
11166
|
+
*/
|
|
11167
|
+
_ovrSource(key, baseline, fmt = String) {
|
|
11168
|
+
if (this._override[key] === void 0) return this._source(key);
|
|
11169
|
+
const src = this._settings?.sources?.[key] ?? "config";
|
|
11170
|
+
const s = this._settings;
|
|
11171
|
+
const current = s === null ? void 0 : this._baselines(s)[key];
|
|
11172
|
+
return b`<span class="ovr">(overridden)</span>
|
|
11173
|
+
<span class="env">${src}: ${baseline}</span>
|
|
11174
|
+
<button
|
|
11175
|
+
class="row-reset"
|
|
11176
|
+
title="Reset to the ${src} value"
|
|
11177
|
+
aria-label="Reset to the ${src} value"
|
|
11178
|
+
@click=${() => this._resetKey(key)}
|
|
11179
|
+
>
|
|
11180
|
+
×
|
|
11181
|
+
</button>
|
|
11182
|
+
${baselineChanged(key, this._recorded[key], current) ? b`<div class="nudge" data-nudge=${key}>
|
|
11183
|
+
<span
|
|
11184
|
+
>Config changed since you overrode this: was
|
|
11185
|
+
${fmt(this._recorded[key])}, now ${baseline}</span
|
|
11186
|
+
>
|
|
11187
|
+
<button @click=${() => this._resetKey(key)}>Reset</button>
|
|
11188
|
+
<button @click=${() => keepBaseline(key, current)}>Keep</button>
|
|
11189
|
+
</div>` : A}`;
|
|
10024
11190
|
}
|
|
10025
|
-
_readonlyRow(key, value, env) {
|
|
11191
|
+
_readonlyRow(key, value, env, source) {
|
|
10026
11192
|
return b`
|
|
10027
11193
|
<tr>
|
|
10028
11194
|
<td class="key">${key}</td>
|
|
10029
11195
|
<td class="val">
|
|
10030
|
-
${String(value)}
|
|
11196
|
+
${String(value)} ${source ? this._source(source) : A}
|
|
10031
11197
|
${env ? b`<span class="env">${env}</span>` : A}
|
|
10032
11198
|
</td>
|
|
10033
11199
|
</tr>
|
|
@@ -10083,7 +11249,7 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
10083
11249
|
/>
|
|
10084
11250
|
${reconnect ? "on" : "off"}
|
|
10085
11251
|
</label>
|
|
10086
|
-
${this.
|
|
11252
|
+
${this._ovrSource("hmrReconnect", s.hmr.reconnect ? "on" : "off", (v) => v ? "on" : "off")}
|
|
10087
11253
|
</td>
|
|
10088
11254
|
</tr>
|
|
10089
11255
|
<tr>
|
|
@@ -10099,7 +11265,7 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
10099
11265
|
warn
|
|
10100
11266
|
</option>
|
|
10101
11267
|
</select>
|
|
10102
|
-
${this.
|
|
11268
|
+
${this._ovrSource("hmrOnIncompatible", s.hmr.onIncompatible)}
|
|
10103
11269
|
</td>
|
|
10104
11270
|
</tr>
|
|
10105
11271
|
<tr class=${s.hmr.indicatorEnabled ? "" : "row-disabled"}>
|
|
@@ -10114,7 +11280,7 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
10114
11280
|
/>
|
|
10115
11281
|
${s.hmr.indicatorEnabled ? indicatorVisible ? "shown" : "hidden" : "off (config)"}
|
|
10116
11282
|
</label>
|
|
10117
|
-
${this.
|
|
11283
|
+
${this._ovrSource("hmrIndicatorVisible", s.hmr.indicatorEnabled ? "shown" : "off (config)", (v) => v ? "shown" : "off (config)")}
|
|
10118
11284
|
</td>
|
|
10119
11285
|
</tr>
|
|
10120
11286
|
<tr class=${s.hmr.indicatorEnabled ? "" : "row-disabled"}>
|
|
@@ -10129,10 +11295,41 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
10129
11295
|
/>
|
|
10130
11296
|
${indicatorCount ? "shown" : "hidden"}
|
|
10131
11297
|
</label>
|
|
10132
|
-
${this.
|
|
11298
|
+
${this._ovrSource("hmrIndicatorCount", s.hmr.indicatorCount ? "shown" : "hidden", (v) => v ? "shown" : "hidden")}
|
|
10133
11299
|
</td>
|
|
10134
11300
|
</tr>
|
|
10135
11301
|
</table>
|
|
11302
|
+
`;
|
|
11303
|
+
}
|
|
11304
|
+
/**
|
|
11305
|
+
* The editor select. A custom `EditorConfig` object (reported as
|
|
11306
|
+
* `'custom'`) isn't one of the built-in choices, so the select is disabled
|
|
11307
|
+
* and just shows "Custom".
|
|
11308
|
+
*/
|
|
11309
|
+
_renderEditorRow(s) {
|
|
11310
|
+
const baseline = s.sourceOverlay.editor;
|
|
11311
|
+
const custom = baseline === "custom";
|
|
11312
|
+
const current = this._override.sourceOverlayEditor ?? baseline;
|
|
11313
|
+
const editorLabel = (value) => SOURCE_OVERLAY_EDITORS.find((ed) => ed.value === value)?.label ?? (value === "custom" ? "Custom" : value);
|
|
11314
|
+
const label = editorLabel(baseline);
|
|
11315
|
+
return b`
|
|
11316
|
+
<tr>
|
|
11317
|
+
<td class="key">editor</td>
|
|
11318
|
+
<td class="val">
|
|
11319
|
+
<select
|
|
11320
|
+
?disabled=${custom}
|
|
11321
|
+
@change=${(e) => this._set("sourceOverlayEditor", e.target.value)}
|
|
11322
|
+
>
|
|
11323
|
+
${custom ? b`<option value="custom" selected>Custom</option>` : SOURCE_OVERLAY_EDITORS.map((ed) => b`<option
|
|
11324
|
+
value=${ed.value}
|
|
11325
|
+
?selected=${ed.value === current}
|
|
11326
|
+
>
|
|
11327
|
+
${ed.label}
|
|
11328
|
+
</option>`)}
|
|
11329
|
+
</select>
|
|
11330
|
+
${this._ovrSource("sourceOverlayEditor", label, (v) => editorLabel(String(v)))}
|
|
11331
|
+
</td>
|
|
11332
|
+
</tr>
|
|
10136
11333
|
`;
|
|
10137
11334
|
}
|
|
10138
11335
|
/**
|
|
@@ -10174,6 +11371,30 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
10174
11371
|
</td>
|
|
10175
11372
|
</tr>
|
|
10176
11373
|
</table>
|
|
11374
|
+
`;
|
|
11375
|
+
}
|
|
11376
|
+
/**
|
|
11377
|
+
* Timeline preferences. Pure preference, no config-time baseline, so an
|
|
11378
|
+
* unset value means off.
|
|
11379
|
+
*/
|
|
11380
|
+
_renderTimelinePrefs() {
|
|
11381
|
+
const chrome = this._override.chromeTracks ?? false;
|
|
11382
|
+
return b`
|
|
11383
|
+
<table>
|
|
11384
|
+
<tr>
|
|
11385
|
+
<td class="key">chrome performance tracks</td>
|
|
11386
|
+
<td class="val">
|
|
11387
|
+
<label class="toggle">
|
|
11388
|
+
<input
|
|
11389
|
+
type="checkbox"
|
|
11390
|
+
.checked=${chrome}
|
|
11391
|
+
@change=${(e) => this._set("chromeTracks", e.target.checked)}
|
|
11392
|
+
/>
|
|
11393
|
+
${chrome ? "on" : "off"}
|
|
11394
|
+
</label>
|
|
11395
|
+
</td>
|
|
11396
|
+
</tr>
|
|
11397
|
+
</table>
|
|
10177
11398
|
`;
|
|
10178
11399
|
}
|
|
10179
11400
|
render() {
|
|
@@ -10203,24 +11424,9 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
10203
11424
|
<section>
|
|
10204
11425
|
<h3>Source Overlay ${this._pill(s.sourceOverlay.enabled)}</h3>
|
|
10205
11426
|
${s.sourceOverlay.enabled ? b`<table>
|
|
10206
|
-
${this._readonlyRow("hotkey", `Ctrl+Shift+${s.sourceOverlay.key.toUpperCase()}`, "LIT_PLUGIN_SOURCE_OVERLAY_KEY")}
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
<td class="val">
|
|
10210
|
-
<select
|
|
10211
|
-
@change=${(e) => this._set("sourceOverlayEditor", e.target.value)}
|
|
10212
|
-
>
|
|
10213
|
-
${SOURCE_OVERLAY_EDITORS.map((ed) => b`<option
|
|
10214
|
-
value=${ed.value}
|
|
10215
|
-
?selected=${ed.value === (this._override.sourceOverlayEditor ?? s.sourceOverlay.editor)}
|
|
10216
|
-
>
|
|
10217
|
-
${ed.label}
|
|
10218
|
-
</option>`)}
|
|
10219
|
-
</select>
|
|
10220
|
-
${this._ovr(this._override.sourceOverlayEditor !== void 0)}
|
|
10221
|
-
</td>
|
|
10222
|
-
</tr>
|
|
10223
|
-
${this._readonlyRow("throttle (ms)", s.sourceOverlay.throttleMs, "LIT_PLUGIN_SOURCE_OVERLAY_THROTTLE_MS")}
|
|
11427
|
+
${this._readonlyRow("hotkey", `Ctrl+Shift+${s.sourceOverlay.key.toUpperCase()}`, "LIT_PLUGIN_SOURCE_OVERLAY_KEY", "sourceOverlayKey")}
|
|
11428
|
+
${this._renderEditorRow(s)}
|
|
11429
|
+
${this._readonlyRow("throttle (ms)", s.sourceOverlay.throttleMs, "LIT_PLUGIN_SOURCE_OVERLAY_THROTTLE_MS", "sourceOverlayThrottleMs")}
|
|
10224
11430
|
</table>` : b`<p class="empty">
|
|
10225
11431
|
Enable with <code>sourceOverlay: true</code> or
|
|
10226
11432
|
<code>LIT_PLUGIN_SOURCE_OVERLAY=true</code>.
|
|
@@ -10241,131 +11447,18 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
|
|
|
10241
11447
|
<p class="empty">
|
|
10242
11448
|
Layers and recording are controlled in the Timeline tab.
|
|
10243
11449
|
</p>
|
|
11450
|
+
${s.timeline ? this._renderTimelinePrefs() : A}
|
|
10244
11451
|
</section>
|
|
10245
11452
|
`;
|
|
10246
11453
|
}
|
|
10247
11454
|
};
|
|
10248
11455
|
__decorate([r$2()], DevtoolsSettings.prototype, "_settings", void 0);
|
|
10249
11456
|
__decorate([r$2()], DevtoolsSettings.prototype, "_override", void 0);
|
|
11457
|
+
__decorate([r$2()], DevtoolsSettings.prototype, "_recorded", void 0);
|
|
10250
11458
|
__decorate([r$2()], DevtoolsSettings.prototype, "_loaded", void 0);
|
|
10251
11459
|
__decorate([r$2()], DevtoolsSettings.prototype, "_colorScheme", void 0);
|
|
10252
11460
|
DevtoolsSettings = __decorate([t$2("devtools-settings")], DevtoolsSettings);
|
|
10253
11461
|
//#endregion
|
|
10254
|
-
//#region src/lib/segmented-tabs.ts
|
|
10255
|
-
/**
|
|
10256
|
-
* @license
|
|
10257
|
-
* Copyright 2026 Google LLC
|
|
10258
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
10259
|
-
*/
|
|
10260
|
-
var SegmentedTabs = class SegmentedTabs extends i$2 {
|
|
10261
|
-
static {
|
|
10262
|
-
this.styles = [tokens, i$5`
|
|
10263
|
-
:host {
|
|
10264
|
-
display: flex;
|
|
10265
|
-
gap: var(--lit-devtools-space-1);
|
|
10266
|
-
align-self: stretch;
|
|
10267
|
-
}
|
|
10268
|
-
button {
|
|
10269
|
-
appearance: none;
|
|
10270
|
-
display: flex;
|
|
10271
|
-
align-items: center;
|
|
10272
|
-
gap: var(--lit-devtools-space-2);
|
|
10273
|
-
border: 0;
|
|
10274
|
-
background: none;
|
|
10275
|
-
font: inherit;
|
|
10276
|
-
color: var(--lit-devtools-text-muted);
|
|
10277
|
-
padding: 0 var(--lit-devtools-space-5);
|
|
10278
|
-
cursor: pointer;
|
|
10279
|
-
border-bottom: 2px solid transparent;
|
|
10280
|
-
white-space: nowrap;
|
|
10281
|
-
transition:
|
|
10282
|
-
color var(--lit-devtools-dur-fast) var(--lit-devtools-ease-standard),
|
|
10283
|
-
border-color var(--lit-devtools-dur-fast)
|
|
10284
|
-
var(--lit-devtools-ease-standard),
|
|
10285
|
-
background var(--lit-devtools-dur-fast)
|
|
10286
|
-
var(--lit-devtools-ease-standard);
|
|
10287
|
-
}
|
|
10288
|
-
button:hover {
|
|
10289
|
-
color: var(--lit-devtools-text);
|
|
10290
|
-
background: var(--lit-devtools-surface-hover);
|
|
10291
|
-
}
|
|
10292
|
-
button.active {
|
|
10293
|
-
color: var(--lit-devtools-accent);
|
|
10294
|
-
border-bottom-color: var(--lit-devtools-accent);
|
|
10295
|
-
}
|
|
10296
|
-
button:focus-visible {
|
|
10297
|
-
outline: 2px solid var(--lit-devtools-accent-ring);
|
|
10298
|
-
outline-offset: -2px;
|
|
10299
|
-
}
|
|
10300
|
-
button svg {
|
|
10301
|
-
width: 18px;
|
|
10302
|
-
height: 18px;
|
|
10303
|
-
flex-shrink: 0;
|
|
10304
|
-
}
|
|
10305
|
-
.badge {
|
|
10306
|
-
flex-shrink: 0;
|
|
10307
|
-
min-width: 16px;
|
|
10308
|
-
padding: 0 var(--lit-devtools-space-1);
|
|
10309
|
-
border-radius: var(--lit-devtools-radius-pill);
|
|
10310
|
-
background: var(--lit-devtools-error-soft);
|
|
10311
|
-
color: var(--lit-devtools-error);
|
|
10312
|
-
font-size: var(--lit-devtools-text-xs);
|
|
10313
|
-
line-height: 16px;
|
|
10314
|
-
text-align: center;
|
|
10315
|
-
}
|
|
10316
|
-
:host([size='sm']) button {
|
|
10317
|
-
font-size: var(--lit-devtools-text-xs);
|
|
10318
|
-
padding: 0 var(--lit-devtools-space-4);
|
|
10319
|
-
}
|
|
10320
|
-
:host([size='md']) button,
|
|
10321
|
-
:host(:not([size])) button {
|
|
10322
|
-
font-size: var(--lit-devtools-text-sm);
|
|
10323
|
-
padding: 0 var(--lit-devtools-space-5);
|
|
10324
|
-
}
|
|
10325
|
-
`];
|
|
10326
|
-
}
|
|
10327
|
-
constructor() {
|
|
10328
|
-
super();
|
|
10329
|
-
this.items = [];
|
|
10330
|
-
this.value = "";
|
|
10331
|
-
this.size = "md";
|
|
10332
|
-
}
|
|
10333
|
-
_select(id) {
|
|
10334
|
-
this.value = id;
|
|
10335
|
-
this.dispatchEvent(new CustomEvent("change", {
|
|
10336
|
-
detail: { value: id },
|
|
10337
|
-
bubbles: true,
|
|
10338
|
-
composed: true
|
|
10339
|
-
}));
|
|
10340
|
-
}
|
|
10341
|
-
render() {
|
|
10342
|
-
return b`
|
|
10343
|
-
${this.items.map((item) => b`
|
|
10344
|
-
<button
|
|
10345
|
-
role="tab"
|
|
10346
|
-
aria-selected=${item.id === this.value}
|
|
10347
|
-
class=${item.id === this.value ? "active" : ""}
|
|
10348
|
-
@click=${() => this._select(item.id)}
|
|
10349
|
-
>
|
|
10350
|
-
${item.icon ? b`<svg
|
|
10351
|
-
viewBox="0 0 256 256"
|
|
10352
|
-
fill="currentColor"
|
|
10353
|
-
aria-hidden="true"
|
|
10354
|
-
>
|
|
10355
|
-
<path d=${item.icon} />
|
|
10356
|
-
</svg>` : A}
|
|
10357
|
-
${item.label}
|
|
10358
|
-
${item.badge ? b`<span class="badge">${item.badge}</span>` : A}
|
|
10359
|
-
</button>
|
|
10360
|
-
`)}
|
|
10361
|
-
`;
|
|
10362
|
-
}
|
|
10363
|
-
};
|
|
10364
|
-
__decorate([n$3({ type: Array })], SegmentedTabs.prototype, "items", void 0);
|
|
10365
|
-
__decorate([n$3()], SegmentedTabs.prototype, "value", void 0);
|
|
10366
|
-
__decorate([n$3()], SegmentedTabs.prototype, "size", void 0);
|
|
10367
|
-
SegmentedTabs = __decorate([t$2("segmented-tabs")], SegmentedTabs);
|
|
10368
|
-
//#endregion
|
|
10369
11462
|
//#region src/lib/icons.ts
|
|
10370
11463
|
/**
|
|
10371
11464
|
* @license
|
|
@@ -10510,6 +11603,15 @@ var LitDevtoolsPanel = class LitDevtoolsPanel extends i$2 {
|
|
|
10510
11603
|
firstUpdated() {
|
|
10511
11604
|
onDeepLink((link) => {
|
|
10512
11605
|
if (link.tab !== void 0) this._tab = link.tab;
|
|
11606
|
+
if (link.componentId === void 0 && link.eventId !== void 0) {
|
|
11607
|
+
this._tab = "timeline";
|
|
11608
|
+
const eventId = link.eventId;
|
|
11609
|
+
this.updateComplete.then(() => {
|
|
11610
|
+
this._timelineView?.selectEvent(eventId);
|
|
11611
|
+
this._syncHash();
|
|
11612
|
+
});
|
|
11613
|
+
return;
|
|
11614
|
+
}
|
|
10513
11615
|
if (link.componentId === void 0) {
|
|
10514
11616
|
this._syncHash();
|
|
10515
11617
|
return;
|
|
@@ -10532,9 +11634,11 @@ var LitDevtoolsPanel = class LitDevtoolsPanel extends i$2 {
|
|
|
10532
11634
|
_syncHash() {
|
|
10533
11635
|
if (window.top !== window.self) return;
|
|
10534
11636
|
const selectedId = this._tab === "updates" ? this._updatesView?.selectedId : this._componentsView?.selectedId;
|
|
11637
|
+
const eventId = this._tab === "timeline" ? this._timelineView?.selectedEventId : null;
|
|
10535
11638
|
writeHashLink({
|
|
10536
11639
|
tab: this._tab,
|
|
10537
|
-
...selectedId === null || selectedId === void 0 ? {} : { componentId: selectedId }
|
|
11640
|
+
...selectedId === null || selectedId === void 0 ? {} : { componentId: selectedId },
|
|
11641
|
+
...eventId === null || eventId === void 0 ? {} : { eventId }
|
|
10538
11642
|
});
|
|
10539
11643
|
}
|
|
10540
11644
|
/**
|
|
@@ -10576,7 +11680,10 @@ var LitDevtoolsPanel = class LitDevtoolsPanel extends i$2 {
|
|
|
10576
11680
|
></segmented-tabs>
|
|
10577
11681
|
</header>
|
|
10578
11682
|
<div class="view" @inspect-element=${this._onInspectElement}>
|
|
10579
|
-
<timeline-view
|
|
11683
|
+
<timeline-view
|
|
11684
|
+
?hidden=${this._tab !== "timeline"}
|
|
11685
|
+
@selection-change=${this._syncHash}
|
|
11686
|
+
></timeline-view>
|
|
10580
11687
|
<!-- Kept mounted (like the timeline) so an overlay inspect-pick can
|
|
10581
11688
|
arrive and switch us here even while another tab is in front. -->
|
|
10582
11689
|
<components-view
|
|
@@ -10600,6 +11707,7 @@ __decorate([r$2()], LitDevtoolsPanel.prototype, "_tab", void 0);
|
|
|
10600
11707
|
__decorate([r$2()], LitDevtoolsPanel.prototype, "_hmrCount", void 0);
|
|
10601
11708
|
__decorate([e$3("components-view")], LitDevtoolsPanel.prototype, "_componentsView", void 0);
|
|
10602
11709
|
__decorate([e$3("updates-view")], LitDevtoolsPanel.prototype, "_updatesView", void 0);
|
|
11710
|
+
__decorate([e$3("timeline-view")], LitDevtoolsPanel.prototype, "_timelineView", void 0);
|
|
10603
11711
|
LitDevtoolsPanel = __decorate([t$2("lit-devtools-panel")], LitDevtoolsPanel);
|
|
10604
11712
|
//#endregion
|
|
10605
11713
|
//#region src/panel/main.ts
|
|
@@ -10611,4 +11719,4 @@ LitDevtoolsPanel = __decorate([t$2("lit-devtools-panel")], LitDevtoolsPanel);
|
|
|
10611
11719
|
//#endregion
|
|
10612
11720
|
export { onBrowserAgentToolsChanged as n, nanoid as r, listBrowserAgentTools as t };
|
|
10613
11721
|
|
|
10614
|
-
//# sourceMappingURL=index-
|
|
11722
|
+
//# sourceMappingURL=index-DVr5EnyB.js.map
|