uicore-ts
Version:
UICore is a library to build native-like user interfaces using pure Typescript. No HTML is needed at all. Components are described as TS classes and all user interactions are handled explicitly. This library is strongly inspired by the UIKit framework tha
1,245 lines • 106 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var UILayoutDebugger_exports = {};
__export(UILayoutDebugger_exports, {
UILayoutDebugger: () => UILayoutDebugger
});
module.exports = __toCommonJS(UILayoutDebugger_exports);
const _UILayoutDebugger = class {
static get isEnabled() {
return _UILayoutDebugger._isEnabled;
}
static get breakpointsEnabled() {
return _UILayoutDebugger._breakpointsEnabled;
}
static enable() {
_UILayoutDebugger._isEnabled = true;
_UILayoutDebugger._ensureOverlay();
_UILayoutDebugger._renderOverlay();
console.log(
"%c[UILayoutDebugger] ENABLED \u2014 recording layout traces and showing overlay.",
"color: #4CAF50; font-weight: bold"
);
}
static disable() {
_UILayoutDebugger._isEnabled = false;
_UILayoutDebugger._breakpointsEnabled = false;
_UILayoutDebugger._removeOverlay();
console.log(
"%c[UILayoutDebugger] DISABLED.",
"color: #9E9E9E; font-weight: bold"
);
}
static enableBreakpoints() {
if (!_UILayoutDebugger._isEnabled) {
_UILayoutDebugger.enable();
}
_UILayoutDebugger._breakpointsEnabled = true;
console.log(
"%c[UILayoutDebugger] Breakpoint mode ON. Search for 'breakpointOnThisLine' in Sources to set your breakpoint.",
"color: #FF9800; font-weight: bold"
);
}
static disableBreakpoints() {
_UILayoutDebugger._breakpointsEnabled = false;
console.log(
"%c[UILayoutDebugger] Breakpoint mode OFF.",
"color: #9E9E9E"
);
}
static stepForward() {
if (!_UILayoutDebugger._isEnabled) {
return;
}
const trace = _UILayoutDebugger._currentReplayTrace;
if (!trace) {
return;
}
const next = _UILayoutDebugger._replayStepIndex + 1;
_UILayoutDebugger.goToStep(Math.min(next, trace.steps.length - 1));
}
static stepBack() {
if (!_UILayoutDebugger._isEnabled) {
return;
}
_UILayoutDebugger.goToStep(Math.max(_UILayoutDebugger._replayStepIndex - 1, -1));
}
static goToStep(stepIndex) {
if (!_UILayoutDebugger._isEnabled) {
return;
}
_UILayoutDebugger._replayStepIndex = stepIndex;
_UILayoutDebugger._renderOverlay();
}
static goToCompareStep(stepIndex) {
if (!_UILayoutDebugger._isEnabled) {
return;
}
_UILayoutDebugger._compareStepIndex = stepIndex;
_UILayoutDebugger._renderOverlay();
}
static showTrace(traceIndex) {
if (!_UILayoutDebugger._isEnabled) {
return;
}
const clamped = Math.max(0, Math.min(traceIndex, _UILayoutDebugger._traces.length - 1));
_UILayoutDebugger._replayTraceIndex = clamped;
_UILayoutDebugger._replayStepIndex = -1;
_UILayoutDebugger._singleExpandState = /* @__PURE__ */ new Map();
_UILayoutDebugger._renderOverlay();
}
static showCompareTrace(traceIndex) {
if (!_UILayoutDebugger._isEnabled) {
return;
}
const clamped = Math.max(0, Math.min(traceIndex, _UILayoutDebugger._traces.length - 1));
_UILayoutDebugger._compareTraceIndex = clamped;
_UILayoutDebugger._compareStepIndex = -1;
_UILayoutDebugger._renderOverlay();
}
static get _currentReplayTrace() {
var _a;
return (_a = _UILayoutDebugger._traces[_UILayoutDebugger._replayTraceIndex]) != null ? _a : null;
}
static get _currentCompareTrace() {
var _a;
return (_a = _UILayoutDebugger._traces[_UILayoutDebugger._compareTraceIndex]) != null ? _a : null;
}
static willBeginLayoutPass(viewsToLayout) {
if (!_UILayoutDebugger._isEnabled) {
return;
}
_UILayoutDebugger._currentTrace = {
passIndex: _UILayoutDebugger._passIndex++,
steps: [],
roots: [],
cacheChanges: [],
totalIterations: 0
};
_UILayoutDebugger._currentIteration = 0;
_UILayoutDebugger._layoutCountsThisPass = /* @__PURE__ */ new Map();
_UILayoutDebugger._liveViewRegistry = /* @__PURE__ */ new Map();
_UILayoutDebugger._pendingStep = null;
_UILayoutDebugger._pendingSubviewsBefore = /* @__PURE__ */ new Map();
}
static willBeginIteration(iteration) {
if (!_UILayoutDebugger._isEnabled) {
return;
}
_UILayoutDebugger._currentIteration = iteration;
}
static viewDidCallSetNeedsLayout(view) {
var _a, _b;
if (!_UILayoutDebugger._isEnabled) {
return;
}
const viewIdx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
if (viewIdx < 0) {
return;
}
if (_UILayoutDebugger._triggerMap.has(viewIdx)) {
return;
}
const rawStack = (_b = new Error().stack) != null ? _b : "";
const cleanStack = _UILayoutDebugger._cleanStack(rawStack);
const callerFunction = _UILayoutDebugger._extractCallerFunctionName(cleanStack);
_UILayoutDebugger._triggerMap.set(viewIdx, { callerFunction, cleanStack });
}
static didSetCachedIntrinsicSize(view, cacheKey, value) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
if (!_UILayoutDebugger._isEnabled) {
return;
}
const trace = _UILayoutDebugger._currentTrace;
if (!trace) {
return;
}
const viewIdx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
if (viewIdx < 0) {
return;
}
const rawStack = (_b = new Error().stack) != null ? _b : "";
const cleanStack = _UILayoutDebugger._cleanStack(rawStack);
const callerFunction = _UILayoutDebugger._extractCallerFunctionName(cleanStack);
const event = {
eventIndex: trace.cacheChanges.length,
stepIndex: (_d = (_c = _UILayoutDebugger._pendingStep) == null ? void 0 : _c.stepIndex) != null ? _d : -1,
iteration: _UILayoutDebugger._currentIteration,
viewIndex: viewIdx,
className: (_f = (_e = view == null ? void 0 : view.constructor) == null ? void 0 : _e.name) != null ? _f : "UnknownView",
elementID: (_g = view == null ? void 0 : view.elementID) != null ? _g : String(viewIdx),
cacheKey,
newValue: { width: (_h = value == null ? void 0 : value.width) != null ? _h : 0, height: (_i = value == null ? void 0 : value.height) != null ? _i : 0 },
callerFunction,
cleanStack
};
trace.cacheChanges.push(event);
}
static willLayoutView(view) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
if (!_UILayoutDebugger._isEnabled) {
return;
}
const stepIndex = (_b = (_a = _UILayoutDebugger._currentTrace) == null ? void 0 : _a.steps.length) != null ? _b : 0;
const viewIdx = (_c = view == null ? void 0 : view._UIViewIndex) != null ? _c : -1;
if (viewIdx >= 0) {
_UILayoutDebugger._liveViewRegistry.set(viewIdx, view);
}
_UILayoutDebugger._pendingStep = {
stepIndex,
iteration: _UILayoutDebugger._currentIteration,
viewIndex: viewIdx,
className: (_e = (_d = view == null ? void 0 : view.constructor) == null ? void 0 : _d.name) != null ? _e : "UnknownView",
elementID: (_f = view == null ? void 0 : view.elementID) != null ? _f : String(viewIdx),
frameBefore: _UILayoutDebugger._captureFrame(view),
frameAfter: null,
cacheBefore: _UILayoutDebugger._captureCache(view),
cacheAfter: null,
subviewRecords: [],
trigger: (_g = _UILayoutDebugger._triggerMap.get(viewIdx)) != null ? _g : null
};
_UILayoutDebugger._triggerMap.delete(viewIdx);
_UILayoutDebugger._pendingSubviewsBefore = /* @__PURE__ */ new Map();
const subviews = (_h = view == null ? void 0 : view.subviews) != null ? _h : [];
for (let i = 0; i < subviews.length; i++) {
const sv = subviews[i];
const idx = (_i = sv == null ? void 0 : sv._UIViewIndex) != null ? _i : -i;
_UILayoutDebugger._pendingSubviewsBefore.set(idx, _UILayoutDebugger._captureFrame(sv));
}
}
static didLayoutView(view) {
var _a, _b, _c;
if (!_UILayoutDebugger._isEnabled) {
return;
}
const step = _UILayoutDebugger._pendingStep;
if (!step) {
return;
}
step.frameAfter = _UILayoutDebugger._captureFrame(view);
step.cacheAfter = _UILayoutDebugger._captureCache(view);
const viewIdx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
const prev = (_b = _UILayoutDebugger._layoutCountsThisPass.get(viewIdx)) != null ? _b : 0;
_UILayoutDebugger._layoutCountsThisPass.set(viewIdx, prev + 1);
(_c = _UILayoutDebugger._currentTrace) == null ? void 0 : _c.steps.push(step);
_UILayoutDebugger._pendingStep = null;
}
static didFinishLayoutPass(iterationCount) {
var _a;
if (!_UILayoutDebugger._isEnabled) {
return;
}
const trace = _UILayoutDebugger._currentTrace;
if (!trace) {
return;
}
trace.totalIterations = iterationCount;
const anyView = _UILayoutDebugger._liveViewRegistry.values().next().value;
const rootView = anyView == null ? void 0 : anyView.rootView;
if (rootView) {
_UILayoutDebugger._lastKnownRootView = rootView;
const visited = /* @__PURE__ */ new Set();
const rootIdx = (_a = rootView._UIViewIndex) != null ? _a : -1;
if (rootIdx >= 0) {
visited.add(rootIdx);
}
trace.roots = [_UILayoutDebugger._buildTreeSnapshot(rootView, 0, visited)];
}
_UILayoutDebugger._traces.unshift(trace);
if (_UILayoutDebugger._traces.length > _UILayoutDebugger.maxStoredTraces) {
_UILayoutDebugger._traces.length = _UILayoutDebugger.maxStoredTraces;
}
_UILayoutDebugger._replayTraceIndex = 0;
_UILayoutDebugger._replayStepIndex = -1;
_UILayoutDebugger._currentTrace = null;
_UILayoutDebugger._liveViewRegistry.clear();
_UILayoutDebugger._renderOverlay();
}
static clearTraces() {
_UILayoutDebugger._traces = [];
_UILayoutDebugger._passIndex = 0;
_UILayoutDebugger._replayTraceIndex = 0;
_UILayoutDebugger._compareTraceIndex = 1;
_UILayoutDebugger._replayStepIndex = -1;
_UILayoutDebugger._compareStepIndex = -1;
_UILayoutDebugger._renderOverlay();
}
static captureBaseline() {
if (!_UILayoutDebugger._isEnabled) {
return;
}
const snap = _UILayoutDebugger._captureStateSnapshot("Baseline");
if (!snap) {
console.warn("[UILayoutDebugger] captureBaseline: no root view found yet \u2014 trigger a layout pass first.");
return;
}
_UILayoutDebugger._baseline = snap;
_UILayoutDebugger._diffSnapshot = null;
_UILayoutDebugger._diffMode = false;
_UILayoutDebugger._renderOverlay();
console.log(
`%c[UILayoutDebugger] Baseline captured \u2014 ${snap.views.size} views.`,
"color: #88ddff; font-weight: bold"
);
}
static captureAndDiff() {
if (!_UILayoutDebugger._isEnabled) {
return;
}
if (!_UILayoutDebugger._baseline) {
console.warn("[UILayoutDebugger] captureAndDiff: no baseline set. Call captureBaseline() first.");
return;
}
const snap = _UILayoutDebugger._captureStateSnapshot("Current");
if (!snap) {
console.warn("[UILayoutDebugger] captureAndDiff: could not find root view.");
return;
}
_UILayoutDebugger._diffSnapshot = snap;
_UILayoutDebugger._diffMode = true;
_UILayoutDebugger._renderOverlay();
}
static clearDiff() {
_UILayoutDebugger._baseline = null;
_UILayoutDebugger._diffSnapshot = null;
_UILayoutDebugger._diffMode = false;
_UILayoutDebugger._renderOverlay();
}
static captureStaleLayoutReport() {
var _a, _b, _c;
if (!_UILayoutDebugger._isEnabled) {
return;
}
const rootView = _UILayoutDebugger._lastKnownRootView;
if (!rootView) {
console.warn(
"[UILayoutDebugger] captureStaleLayoutReport: no root view found yet \u2014 trigger a layout pass first."
);
return;
}
const before = _UILayoutDebugger._captureStateSnapshotFromRoot(rootView, "Before (potentially stale)");
(_a = rootView.performForcedSubtreeLayout) == null ? void 0 : _a.call(rootView);
const after = _UILayoutDebugger._captureStateSnapshotFromRoot(rootView, "After (forced cold remeasure)");
const diffs = _UILayoutDebugger._diffSnapshots(before, after).filter((d) => d.kind !== "unchanged");
const forcedPassCacheChanges = /* @__PURE__ */ new Map();
const forcedTrace = (_b = _UILayoutDebugger._traces[0]) != null ? _b : null;
const forcedPassIndex = (_c = forcedTrace == null ? void 0 : forcedTrace.passIndex) != null ? _c : -1;
if (forcedTrace) {
for (const ev of forcedTrace.cacheChanges) {
let bucket = forcedPassCacheChanges.get(ev.viewIndex);
if (!bucket) {
bucket = [];
forcedPassCacheChanges.set(ev.viewIndex, bucket);
}
bucket.push(ev);
}
}
_UILayoutDebugger._staleReportResult = { before, after, diffs, forcedPassCacheChanges, passIndex: forcedPassIndex };
_UILayoutDebugger._staleReportMode = true;
_UILayoutDebugger._renderOverlay();
const correctedCount = diffs.length;
console.log(
`%c[UILayoutDebugger] Stale layout report: ${correctedCount} view(s) had stale state corrected by forced layout.`,
"color: #ffaa55; font-weight: bold"
);
}
static clearStaleReport() {
_UILayoutDebugger._staleReportResult = null;
_UILayoutDebugger._staleReportMode = false;
_UILayoutDebugger._renderOverlay();
}
static toggleLiveInspector() {
_UILayoutDebugger._liveInspectorMode = !_UILayoutDebugger._liveInspectorMode;
_UILayoutDebugger._renderOverlay();
}
static _captureStateSnapshot(label) {
const rootView = _UILayoutDebugger._lastKnownRootView;
if (!rootView) {
return null;
}
return _UILayoutDebugger._captureStateSnapshotFromRoot(rootView, label);
}
static _captureStateSnapshotFromRoot(rootView, label) {
var _a, _b, _c, _d;
const views = /* @__PURE__ */ new Map();
_UILayoutDebugger._walkViewTree(rootView, views, /* @__PURE__ */ new Set());
const tm = window.UITextMeasurement;
const textMeasurement = {
preparedCacheSize: (_b = (_a = tm == null ? void 0 : tm._preparedCache) == null ? void 0 : _a.size) != null ? _b : -1,
styleCacheSize: (_d = (_c = tm == null ? void 0 : tm.globalStyleCache) == null ? void 0 : _c.size) != null ? _d : -1
};
return { label, takenAt: Date.now(), views, textMeasurement };
}
static _walkViewTree(view, out, visited) {
var _a, _b, _c, _d, _e;
const idx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
if (idx < 0 || visited.has(idx)) {
return;
}
visited.add(idx);
out.set(idx, {
viewIndex: idx,
className: (_c = (_b = view == null ? void 0 : view.constructor) == null ? void 0 : _b.name) != null ? _c : "UnknownView",
elementID: (_d = view == null ? void 0 : view.elementID) != null ? _d : String(idx),
frame: _UILayoutDebugger._captureFrame(view),
cache: _UILayoutDebugger._captureCache(view)
});
const subviews = (_e = view == null ? void 0 : view.subviews) != null ? _e : [];
for (const sv of subviews) {
_UILayoutDebugger._walkViewTree(sv, out, visited);
}
}
static _diffSnapshots(baseline, current) {
var _a, _b;
const diffs = [];
const allKeys = /* @__PURE__ */ new Set([...baseline.views.keys(), ...current.views.keys()]);
for (const idx of allKeys) {
const b = (_a = baseline.views.get(idx)) != null ? _a : null;
const c = (_b = current.views.get(idx)) != null ? _b : null;
if (!b) {
diffs.push({
kind: "appeared",
viewIndex: idx,
className: c.className,
elementID: c.elementID,
baselineFrame: null,
currentFrame: c.frame,
baselineCache: null,
currentCache: c.cache
});
continue;
}
if (!c) {
diffs.push({
kind: "disappeared",
viewIndex: idx,
className: b.className,
elementID: b.elementID,
baselineFrame: b.frame,
currentFrame: null,
baselineCache: b.cache,
currentCache: null
});
continue;
}
const frameChanged = _UILayoutDebugger._framesEqual(b.frame, c.frame) === false;
const cacheChanged = _UILayoutDebugger._cachesEqual(b.cache, c.cache) === false;
const kind = frameChanged && cacheChanged ? "both" : frameChanged ? "frame" : cacheChanged ? "cache" : "unchanged";
diffs.push({
kind,
viewIndex: idx,
className: c.className,
elementID: c.elementID,
baselineFrame: b.frame,
currentFrame: c.frame,
baselineCache: b.cache,
currentCache: c.cache
});
}
const order = {
appeared: 0,
disappeared: 1,
both: 2,
frame: 3,
cache: 4,
unchanged: 5
};
diffs.sort((a, b) => order[a.kind] - order[b.kind]);
return diffs;
}
static _framesEqual(a, b) {
if (!a && !b) {
return true;
}
if (!a || !b) {
return false;
}
if (_UILayoutDebugger._boundsBasedDiff) {
return a.width === b.width && a.height === b.height;
}
return a.left === b.left && a.top === b.top && a.width === b.width && a.height === b.height;
}
static _cachesEqual(a, b) {
if (!a && !b) {
return true;
}
if (!a || !b) {
return false;
}
if (a.entryCount !== b.entryCount) {
return false;
}
for (const key of Object.keys(a.entries)) {
const ae = a.entries[key];
const be = b.entries[key];
if (!be || ae.width !== be.width || ae.height !== be.height) {
return false;
}
}
if (a.hasFrameCache !== b.hasFrameCache) {
return false;
}
if (a.hasFrameCache && b.hasFrameCache) {
const af = a.frameCache, bf = b.frameCache;
if (!af || !bf || af.top !== bf.top || af.left !== bf.left || af.width !== bf.width || af.height !== bf.height) {
return false;
}
}
if (a.hasVirtualFrameCache !== b.hasVirtualFrameCache) {
return false;
}
if (a.hasVirtualFrameCache && b.hasVirtualFrameCache) {
const av = a.virtualFrameCache, bv = b.virtualFrameCache;
if (!av || !bv || av.top !== bv.top || av.left !== bv.left || av.width !== bv.width || av.height !== bv.height) {
return false;
}
}
return true;
}
static willSetSubviewFrames(_view) {
}
static didSetSubviewFrames(view) {
var _a, _b, _c, _d, _e, _f;
if (!_UILayoutDebugger._isEnabled) {
return;
}
const step = _UILayoutDebugger._pendingStep;
if (!step) {
return;
}
const subviews = (_a = view == null ? void 0 : view.subviews) != null ? _a : [];
for (let i = 0; i < subviews.length; i++) {
const sv = subviews[i];
const idx = (_b = sv == null ? void 0 : sv._UIViewIndex) != null ? _b : -i;
const record = {
viewIndex: idx,
className: (_d = (_c = sv == null ? void 0 : sv.constructor) == null ? void 0 : _c.name) != null ? _d : "UnknownView",
elementID: (_e = sv == null ? void 0 : sv.elementID) != null ? _e : String(idx),
frameBefore: (_f = _UILayoutDebugger._pendingSubviewsBefore.get(idx)) != null ? _f : null,
frameAfter: _UILayoutDebugger._captureFrame(sv)
};
step.subviewRecords.push(record);
}
}
static _shouldHitBreakpoint(_view) {
return _UILayoutDebugger._isEnabled && _UILayoutDebugger._breakpointsEnabled;
}
static _cleanStack(rawStack) {
const lines = rawStack.split("\n");
let firstAppFrameIndex = 1;
for (let i = 1; i < lines.length; i++) {
const trimmed = lines[i].trim();
const atMatch = trimmed.match(/^at\s+([\w.<>$\s]+?)\s*(?:\(|$)/);
const frameName = atMatch ? atMatch[1].trim() : trimmed;
const methodName = frameName.includes(".") ? frameName.slice(frameName.lastIndexOf(".") + 1) : frameName;
const isNoise = _UILayoutDebugger._noiseFramePrefixes.some((prefix) => {
return frameName === prefix || methodName === prefix || frameName.endsWith("." + prefix);
});
if (!isNoise) {
firstAppFrameIndex = i;
break;
}
}
return lines.slice(firstAppFrameIndex).join("\n");
}
static _extractCallerFunctionName(cleanStack) {
var _a, _b;
const firstLine = (_b = (_a = cleanStack.split("\n")[0]) == null ? void 0 : _a.trim()) != null ? _b : "";
const atMatch = firstLine.match(/^at\s+([\w.<>$\s]+?)\s*(?:\(|$)/);
if (atMatch) {
return atMatch[1].trim();
}
return firstLine.substring(0, 80) || "(unknown)";
}
static _captureFrame(view) {
var _a, _b, _c, _d, _e, _f;
const f = view == null ? void 0 : view._frame;
if (!f) {
return null;
}
return {
top: (_b = (_a = f.top) != null ? _a : f.y) != null ? _b : 0,
left: (_d = (_c = f.left) != null ? _c : f.x) != null ? _d : 0,
width: (_e = f.width) != null ? _e : 0,
height: (_f = f.height) != null ? _f : 0
};
}
static _captureCache(view) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
if (!view) {
return null;
}
const sharedKey = view.sharedIntrinsicSizeCacheIdentifier;
const isShared = !!sharedKey;
let rawEntries;
if (isShared) {
rawEntries = (_f = (_e = (_d = (_a = view.constructor) == null ? void 0 : _a._sharedIntrinsicSizeCaches) != null ? _d : (_c = (_b = view.__proto__) == null ? void 0 : _b.constructor) == null ? void 0 : _c._sharedIntrinsicSizeCaches) == null ? void 0 : _e.get(sharedKey)) != null ? _f : {};
} else {
rawEntries = (_g = view._intrinsicSizesCache) != null ? _g : {};
}
const entries = {};
for (const key of Object.keys(rawEntries)) {
const r = rawEntries[key];
entries[key] = { width: (_h = r == null ? void 0 : r.width) != null ? _h : 0, height: (_i = r == null ? void 0 : r.height) != null ? _i : 0 };
}
const rawFrameCache = view._frameCache;
const rawVirtualFrameCache = view._frameCacheForVirtualLayouting;
const frameCache = rawFrameCache ? { top: (_k = (_j = rawFrameCache.top) != null ? _j : rawFrameCache.y) != null ? _k : 0, left: (_m = (_l = rawFrameCache.left) != null ? _l : rawFrameCache.x) != null ? _m : 0, width: (_n = rawFrameCache.width) != null ? _n : 0, height: (_o = rawFrameCache.height) != null ? _o : 0 } : null;
const virtualFrameCache = rawVirtualFrameCache ? { top: (_q = (_p = rawVirtualFrameCache.top) != null ? _p : rawVirtualFrameCache.y) != null ? _q : 0, left: (_s = (_r = rawVirtualFrameCache.left) != null ? _r : rawVirtualFrameCache.x) != null ? _s : 0, width: (_t = rawVirtualFrameCache.width) != null ? _t : 0, height: (_u = rawVirtualFrameCache.height) != null ? _u : 0 } : null;
return {
entryCount: Object.keys(entries).length,
entries,
isShared,
sharedKey,
hasFrameCache: rawFrameCache !== void 0,
frameCache,
hasVirtualFrameCache: rawVirtualFrameCache !== void 0,
virtualFrameCache
};
}
static _buildTreeSnapshot(view, depth, visited = /* @__PURE__ */ new Set()) {
var _a, _b, _c, _d, _e, _f, _g;
const idx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
const node = {
viewIndex: idx,
className: (_c = (_b = view == null ? void 0 : view.constructor) == null ? void 0 : _b.name) != null ? _c : "UnknownView",
elementID: (_d = view == null ? void 0 : view.elementID) != null ? _d : String(idx),
depth,
frame: _UILayoutDebugger._captureFrame(view),
layoutCount: (_e = _UILayoutDebugger._layoutCountsThisPass.get(idx)) != null ? _e : 0,
cacheAfterPass: _UILayoutDebugger._captureCache(view),
children: []
};
const subviews = (_f = view == null ? void 0 : view.subviews) != null ? _f : [];
for (let i = 0; i < subviews.length; i++) {
const sv = subviews[i];
const svIdx = (_g = sv == null ? void 0 : sv._UIViewIndex) != null ? _g : -1;
if (svIdx < 0 || visited.has(svIdx)) {
continue;
}
visited.add(svIdx);
node.children.push(_UILayoutDebugger._buildTreeSnapshot(sv, depth + 1, visited));
}
return node;
}
static _findCausingTrace(viewIndex, baselineTakenAt) {
for (let ti = _UILayoutDebugger._traces.length - 1; ti >= 0; ti--) {
const trace = _UILayoutDebugger._traces[ti];
const si = trace.steps.findIndex((s) => s.viewIndex === viewIndex);
if (si >= 0) {
return { traceIndex: ti, stepIndex: si, passIndex: trace.passIndex };
}
}
return null;
}
static _ensureOverlay() {
if (_UILayoutDebugger._overlayRoot) {
return;
}
const root = document.createElement("div");
root.id = "__UILayoutDebugger_overlay";
root.style.cssText = [
"position: fixed",
"top: 8px",
"right: 8px",
"max-height: calc(100vh - 16px)",
"background: rgba(15, 15, 20, 0.96)",
"color: #e8e8e8",
"font: 11px/1.4 'SF Mono', 'Menlo', 'Consolas', monospace",
"border-radius: 8px",
"border: 1px solid rgba(255,255,255,0.12)",
"box-shadow: 0 8px 32px rgba(0,0,0,0.6)",
"z-index: 2147483647",
"display: flex",
"flex-direction: column",
"overflow: hidden",
"user-select: none"
].join("; ");
document.body.appendChild(root);
_UILayoutDebugger._overlayRoot = root;
_UILayoutDebugger._makeDraggable(root);
}
static _removeOverlay() {
var _a;
(_a = _UILayoutDebugger._overlayRoot) == null ? void 0 : _a.remove();
_UILayoutDebugger._overlayRoot = null;
}
static _renderOverlay() {
const root = _UILayoutDebugger._overlayRoot;
if (!root) {
return;
}
const cmp = _UILayoutDebugger._compareMode;
const diff = _UILayoutDebugger._diffMode && !!_UILayoutDebugger._baseline && !!_UILayoutDebugger._diffSnapshot;
const live = _UILayoutDebugger._liveInspectorMode && !!_UILayoutDebugger._lastKnownRootView;
const stale = _UILayoutDebugger._staleReportMode && !!_UILayoutDebugger._staleReportResult;
const passWidth = cmp ? 1140 : 570;
const extraWidth = (diff ? 320 : 0) + (live ? 320 : 0) + (stale ? 360 : 0);
root.style.width = passWidth + extraWidth + "px";
root.innerHTML = "";
const headerRow1 = _UILayoutDebugger._el("div", [
"padding: 8px 10px 5px",
"background: rgba(255,255,255,0.05)",
"display: flex",
"align-items: center",
"gap: 6px",
"cursor: move",
"flex-shrink: 0"
]);
headerRow1.dataset.dragHandle = "1";
const title = _UILayoutDebugger._el("span", ["flex: 1", "font-weight: bold", "font-size: 11px", "color: #c8d8ff"]);
title.textContent = "\u2699 UILayoutDebugger";
const helpBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
_UILayoutDebugger._helpMode ? "#ffcc88" : "#9090a8"
));
helpBtn.textContent = "\u24D8";
helpBtn.title = "Show help";
helpBtn.onclick = () => {
_UILayoutDebugger._helpMode = !_UILayoutDebugger._helpMode;
_UILayoutDebugger._renderOverlay();
};
const bpBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
_UILayoutDebugger._breakpointsEnabled ? "#ffaa33" : "#9090a8"
));
bpBtn.textContent = _UILayoutDebugger._breakpointsEnabled ? "\u23F8 BP ON" : "\u23F8 BP OFF";
bpBtn.title = "Toggle breakpoint step-through";
bpBtn.onclick = () => {
_UILayoutDebugger._breakpointsEnabled ? _UILayoutDebugger.disableBreakpoints() : _UILayoutDebugger.enableBreakpoints();
_UILayoutDebugger._renderOverlay();
};
const cmpBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(cmp ? "#7bc8ff" : "#9090a8"));
cmpBtn.textContent = cmp ? "\u29C9 Compare ON" : "\u29C9 Compare";
cmpBtn.title = "Toggle side-by-side pass comparison";
cmpBtn.onclick = () => {
_UILayoutDebugger._compareMode = !_UILayoutDebugger._compareMode;
if (_UILayoutDebugger._compareMode) {
_UILayoutDebugger._compareTraceIndex = Math.min(1, _UILayoutDebugger._traces.length - 1);
_UILayoutDebugger._compareStepIndex = -1;
_UILayoutDebugger._sharedExpandState = /* @__PURE__ */ new Map();
}
_UILayoutDebugger._renderOverlay();
};
const filterLabels = {
all: "\u2298 All",
changed: "\u2298 Changed",
unchanged: "\u2298 Unchanged"
};
const filterColors = {
all: "#9090a8",
changed: "#7bc8ff",
unchanged: "#ffcc88"
};
const filterCycle = {
all: "changed",
changed: "unchanged",
unchanged: "all"
};
const filterBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
filterColors[_UILayoutDebugger._frameFilter]
));
filterBtn.textContent = filterLabels[_UILayoutDebugger._frameFilter];
filterBtn.title = "Cycle: show all steps \u2192 only changed frames \u2192 only unchanged frames";
filterBtn.onclick = () => {
_UILayoutDebugger._frameFilter = filterCycle[_UILayoutDebugger._frameFilter];
_UILayoutDebugger._replayStepIndex = -1;
_UILayoutDebugger._compareStepIndex = -1;
_UILayoutDebugger._renderOverlay();
};
const clearBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#9090a8"));
clearBtn.textContent = "\u232B Clear";
clearBtn.title = "Clear all recorded traces and restart recording";
clearBtn.onclick = () => _UILayoutDebugger.clearTraces();
const toggleBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#9090a8"));
toggleBtn.textContent = _UILayoutDebugger._overlayVisible ? "\u25BE" : "\u25B8";
toggleBtn.title = "Collapse / expand";
toggleBtn.onclick = () => {
_UILayoutDebugger._overlayVisible = !_UILayoutDebugger._overlayVisible;
_UILayoutDebugger._renderOverlay();
};
const closeBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#dd5555"));
closeBtn.textContent = "\u2715";
closeBtn.title = "Disable UILayoutDebugger";
closeBtn.onclick = () => _UILayoutDebugger.disable();
headerRow1.append(title, helpBtn, bpBtn, cmpBtn, filterBtn, clearBtn, toggleBtn, closeBtn);
const headerRow2 = _UILayoutDebugger._el("div", [
"padding: 4px 10px 6px",
"background: rgba(255,255,255,0.05)",
"border-bottom: 1px solid rgba(255,255,255,0.10)",
"display: flex",
"align-items: center",
"gap: 6px",
"flex-shrink: 0",
"flex-wrap: wrap"
]);
headerRow2.dataset.dragHandle = "1";
const hasBaseline = !!_UILayoutDebugger._baseline;
const baselineBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
_UILayoutDebugger._diffMode ? "#88ddff" : hasBaseline ? "#ffcc88" : "#9090a8"
));
baselineBtn.textContent = _UILayoutDebugger._diffMode ? "\u2295 Diff ON" : hasBaseline ? "\u{1F4CD} Baseline set" : "\u{1F4CD} Baseline";
baselineBtn.title = hasBaseline ? "Baseline captured \u2014 click to recapture, or use \u2295 to diff against it" : "Capture current view tree state as baseline";
baselineBtn.onclick = () => {
if (_UILayoutDebugger._diffMode) {
_UILayoutDebugger.clearDiff();
} else {
_UILayoutDebugger.captureBaseline();
}
};
const diffBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
hasBaseline ? "#88ff99" : "#9090a8"
));
diffBtn.textContent = "\u2295 Diff";
diffBtn.title = "Capture current state and diff against baseline";
diffBtn.disabled = !hasBaseline;
diffBtn.onclick = () => _UILayoutDebugger.captureAndDiff();
const liveBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
live ? "#88ff99" : "#9090a8"
));
liveBtn.textContent = live ? "\u{1F441} Live ON" : "\u{1F441} Live";
liveBtn.title = "Show live view tree with current frames and cache state";
liveBtn.onclick = () => _UILayoutDebugger.toggleLiveInspector();
const hasStaleResult = !!_UILayoutDebugger._staleReportResult;
const staleBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
stale ? "#ffaa55" : hasStaleResult ? "#a06030" : "#9090a8"
));
staleBtn.textContent = stale ? "\u2622 Stale ON" : "\u2622 Stale";
staleBtn.title = hasStaleResult ? "Stale layout report available \u2014 click to toggle the panel, or re-run to refresh" : "Run a forced full-tree remeasure and show which views had stale/missing invalidations";
staleBtn.onclick = () => {
if (stale) {
_UILayoutDebugger._staleReportMode = false;
_UILayoutDebugger._renderOverlay();
} else if (hasStaleResult) {
_UILayoutDebugger._staleReportMode = true;
_UILayoutDebugger._renderOverlay();
} else {
_UILayoutDebugger.captureStaleLayoutReport();
}
};
staleBtn.oncontextmenu = (e) => {
e.preventDefault();
_UILayoutDebugger.captureStaleLayoutReport();
};
const boundsToggleBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
_UILayoutDebugger._boundsBasedDiff ? "#c8d8ff" : "#9090a8"
));
boundsToggleBtn.textContent = _UILayoutDebugger._boundsBasedDiff ? "\u2B1A Bounds" : "\u2B1A Frame";
boundsToggleBtn.title = _UILayoutDebugger._boundsBasedDiff ? "Diffing by bounds (size only \u2014 origin ignored). Click to switch to frame diffing (position + size)." : "Diffing by frame (position + size). Click to switch to bounds diffing (size only \u2014 position changes are ignored).";
boundsToggleBtn.onclick = () => {
_UILayoutDebugger._boundsBasedDiff = !_UILayoutDebugger._boundsBasedDiff;
_UILayoutDebugger._renderOverlay();
};
headerRow2.append(baselineBtn, diffBtn, liveBtn, staleBtn, boundsToggleBtn);
const header = _UILayoutDebugger._el("div", ["flex-shrink: 0"]);
header.append(headerRow1, headerRow2);
root.appendChild(header);
if (!_UILayoutDebugger._overlayVisible) {
return;
}
if (_UILayoutDebugger._helpMode) {
root.appendChild(_UILayoutDebugger._renderHelpPanel());
return;
}
if (_UILayoutDebugger._baseline && !diff) {
const msg = _UILayoutDebugger._el("div", [
"padding: 8px 12px",
"color: #ffcc88",
"font-size: 10px",
"border-bottom: 1px solid rgba(255,255,255,0.08)",
"flex-shrink: 0"
]);
const ts = new Date(_UILayoutDebugger._baseline.takenAt);
msg.textContent = `\u{1F4CD} Baseline set at ${ts.toLocaleTimeString()} \u2014 ${_UILayoutDebugger._baseline.views.size} views. Hit \u2295 Diff to compare.`;
root.appendChild(msg);
}
if (_UILayoutDebugger._traces.length === 0 && !diff) {
const msg = _UILayoutDebugger._el("div", ["padding: 10px 12px", "color: #9090a8", "font-size: 10px"]);
msg.textContent = "No layout pass recorded yet. Trigger a layout to begin.";
root.appendChild(msg);
return;
}
const body = _UILayoutDebugger._el("div", [
"display: flex",
"flex: 1",
"overflow: hidden",
"min-height: 0"
]);
root.appendChild(body);
if (_UILayoutDebugger._traces.length > 0) {
const passSection = _UILayoutDebugger._el("div", [
"display: flex",
"flex: 1",
"overflow: hidden",
"min-height: 0"
]);
if (cmp) {
let leftTreeEl = null;
let rightTreeEl = null;
const leftCol = _UILayoutDebugger._renderPassColumn(
_UILayoutDebugger._replayTraceIndex,
_UILayoutDebugger._replayStepIndex,
(si) => {
_UILayoutDebugger._replayStepIndex = si;
_UILayoutDebugger._renderOverlay();
},
(ti) => {
_UILayoutDebugger._replayTraceIndex = ti;
_UILayoutDebugger._replayStepIndex = -1;
_UILayoutDebugger._renderOverlay();
},
_UILayoutDebugger._sharedExpandState,
(el) => {
leftTreeEl = el;
},
() => rightTreeEl
);
const colDivider = _UILayoutDebugger._el("div", [
"width: 1px",
"background: rgba(255,255,255,0.10)",
"flex-shrink: 0"
]);
const rightCol = _UILayoutDebugger._renderPassColumn(
_UILayoutDebugger._compareTraceIndex,
_UILayoutDebugger._compareStepIndex,
(si) => {
_UILayoutDebugger._compareStepIndex = si;
_UILayoutDebugger._renderOverlay();
},
(ti) => {
_UILayoutDebugger._compareTraceIndex = ti;
_UILayoutDebugger._compareStepIndex = -1;
_UILayoutDebugger._renderOverlay();
},
_UILayoutDebugger._sharedExpandState,
(el) => {
rightTreeEl = el;
},
() => leftTreeEl
);
passSection.append(leftCol, colDivider, rightCol);
} else {
const col = _UILayoutDebugger._renderPassColumn(
_UILayoutDebugger._replayTraceIndex,
_UILayoutDebugger._replayStepIndex,
(si) => {
_UILayoutDebugger._replayStepIndex = si;
_UILayoutDebugger._renderOverlay();
},
(ti) => {
_UILayoutDebugger._replayTraceIndex = ti;
_UILayoutDebugger._replayStepIndex = -1;
_UILayoutDebugger._renderOverlay();
},
_UILayoutDebugger._singleExpandState,
() => {
},
() => null
);
col.style.flex = "1";
passSection.appendChild(col);
}
body.appendChild(passSection);
}
if (diff) {
const divider = _UILayoutDebugger._el("div", [
"width: 1px",
"background: rgba(255,255,255,0.10)",
"flex-shrink: 0"
]);
const diffPanel = _UILayoutDebugger._renderDiffPanel(
_UILayoutDebugger._baseline,
_UILayoutDebugger._diffSnapshot,
(viewIndex) => {
for (let ti = 0; ti < _UILayoutDebugger._traces.length; ti++) {
const trace = _UILayoutDebugger._traces[ti];
const si = trace.steps.findIndex((s) => s.viewIndex === viewIndex);
if (si >= 0) {
_UILayoutDebugger._replayTraceIndex = ti;
_UILayoutDebugger._replayStepIndex = si;
_UILayoutDebugger._renderOverlay();
return;
}
}
},
_UILayoutDebugger._baseline.takenAt
);
diffPanel.style.width = "320px";
diffPanel.style.flexShrink = "0";
body.append(divider, diffPanel);
}
if (live) {
const divider = _UILayoutDebugger._el("div", [
"width: 1px",
"background: rgba(255,255,255,0.10)",
"flex-shrink: 0"
]);
const livePanel = _UILayoutDebugger._renderLiveInspectorPanel();
livePanel.style.width = "320px";
livePanel.style.flexShrink = "0";
body.append(divider, livePanel);
}
if (stale) {
const divider = _UILayoutDebugger._el("div", [
"width: 1px",
"background: rgba(255,255,255,0.10)",
"flex-shrink: 0"
]);
const stalePanel = _UILayoutDebugger._renderStaleReportPanel(_UILayoutDebugger._staleReportResult);
stalePanel.style.width = "360px";
stalePanel.style.flexShrink = "0";
body.append(divider, stalePanel);
}
}
static _renderPassColumn(traceIndex, stepIndex, onStepChange, onTraceChange, expandState, registerTree, getPeerTree) {
var _a, _b, _c, _d;
const col = _UILayoutDebugger._el("div", [
"display: flex",
"flex-direction: column",
"flex: 1",
"min-width: 0",
"overflow: hidden"
]);
const trace = (_a = _UILayoutDebugger._traces[traceIndex]) != null ? _a : null;
const pickerRow = _UILayoutDebugger._el("div", [
"padding: 5px 10px",
"border-bottom: 1px solid rgba(255,255,255,0.08)",
"display: flex",
"align-items: center",
"gap: 6px",
"flex-shrink: 0",
"font-size: 10px"
]);
const pickerLabel = _UILayoutDebugger._el("span", ["color: #a0a0b8", "flex-shrink: 0"]);
pickerLabel.textContent = "Pass:";
const sel = document.createElement("select");
sel.style.cssText = [
"flex: 1",
"background: #1e1e2e",
"color: #d8d8f0",
"border: 1px solid #444",
"border-radius: 3px",
"font: inherit",
"padding: 1px 4px"
].join("; ");
for (let i = 0; i < _UILayoutDebugger._traces.length; i++) {
const t = _UILayoutDebugger._traces[i];
const opt = document.createElement("option");
opt.value = String(i);
opt.textContent = `#${t.passIndex} (${t.steps.length} steps, ${t.totalIterations} iter)`;
if (i === traceIndex) {
opt.selected = true;
}
sel.appendChild(opt);
}
sel.onchange = () => onTraceChange(parseInt(sel.value, 10));
pickerRow.append(pickerLabel, sel);
col.appendChild(pickerRow);
if (!trace) {
return col;
}
const frameFilter = _UILayoutDebugger._frameFilter;
const visibleSteps = frameFilter === "all" ? trace.steps : trace.steps.filter((s) => {
const f = s.frameBefore;
const g = s.frameAfter;
const changed = !f || !g || f.left !== g.left || f.top !== g.top || f.width !== g.width || f.height !== g.height;
return frameFilter === "changed" ? changed : !changed;
});
const clampedStep = Math.max(-1, Math.min(stepIndex, visibleSteps.length - 1));
const activeStep = (_b = visibleSteps[clampedStep]) != null ? _b : null;
const activeViewIndex = (_c = activeStep == null ? void 0 : activeStep.viewIndex) != null ? _c : -1;
const countMap = /* @__PURE__ */ new Map();
const stepMap = /* @__PURE__ */ new Map();
for (const step of trace.steps) {
countMap.set(step.viewIndex, ((_d = countMap.get(step.viewIndex)) != null ? _d : 0) + 1);
stepMap.set(step.viewIndex, step);
}
const stepBar = _UILayoutDebugger._el("div", [
"padding: 5px 10px",
"border-bottom: 1px solid rgba(255,255,255,0.08)",
"display: flex",
"align-items: center",
"gap: 6px",
"flex-shrink: 0"
]);
const backBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#59599b"));
backBtn.textContent = "\u25C0";
backBtn.title = "Step back";
backBtn.disabled = clampedStep <= -1;
backBtn.onclick = () => onStepChange(Math.max(clampedStep - 1, -1));
const fwdBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#59599b"));
fwdBtn.textContent = "\u25B6";
fwdBtn.title = "Step forward";
fwdBtn.disabled = clampedStep >= visibleSteps.length - 1;
fwdBtn.onclick = () => onStepChange(Math.min(clampedStep + 1, visibleSteps.length - 1));
const slider = document.createElement("input");
slider.type = "range";
slider.min = "-1";
slider.max = String(visibleSteps.length - 1);
slider.value = String(clampedStep);
slider.style.cssText = "flex: 1; cursor: pointer; accent-color: #59599b;";
slider.oninput = () => onStepChange(parseInt(slider.value, 10));
const stepLabel = _UILayoutDebugger._el("span", ["color: #b0b0c8", "font-size: 10px", "white-space: nowrap"]);
const totalLabel = frameFilter === "all" ? String(visibleSteps.length) : `${visibleSteps.length}/${trace.steps.length}`;
stepLabel.textContent = clampedStep < 0 ? `\u2014 / ${totalLabel}` : `${clampedStep + 1} / ${totalLabel}`;
stepBar.append(backBtn, slider, fwdBtn, stepLabel);
col.appendChild(stepBar);
if (activeStep) {
col.appendChild(_UILayoutDebugger._renderStepDetail(activeStep));
}
const treeContainer = _UILayoutDebugger._el("div", [
"overflow-y: scroll",
"flex: 1",
"padding: 4px 0",
"min-height: 0",
"position: relative"
]);
registerTree(treeContainer);
treeContainer.addEventListener("scroll", () => {
const peer = getPeerTree();
if (peer && peer.scrollTop !== treeContainer.scrollTop) {
peer.scrollTop = treeContainer.scrollTop;
}
});
if (trace.roots.length > 0) {
let activeRow = null;
for (const treeRoot of trace.roots) {
const result = _UILayoutDebugger._renderTreeNode(
treeRoot,
treeContainer,
countMap,
activeViewIndex,
expandState,
stepMap
);
if (result) {
activeRow = result;
}
}
if (activeRow) {
const rowRef = activeRow;
const containerRef = treeContainer;
setTimeout(() => {
const rowTop = rowRef.offsetTop;
const rowBottom = rowTop + rowRef.offsetHeight;
const visTop = containerRef.scrollTop;
const visBottom = visTop + containerRef.clientHeight;
if (rowTop < visTop || rowBottom > visBottom) {
containerRef.scrollTop = rowTop - containerRef.clientHeight / 2;
}
}, 0);
}
} else {
const msg = _UILayoutDebugger._el("div", ["padding: 8px 10px", "color: #6a6a80"]);
msg.textContent = "No steps recorded in this pass.";
treeContainer.appendChild(msg);
}
col.appendChild(treeContainer);
if (trace.cacheChanges.length > 0) {
let cacheListExpanded = false;
const cacheHeader = _UILayoutDebugger._el("div", [
"padding: 5px 10px",
"border-top: 1px solid rgba(255,255,255,0.08)",
"display: flex",
"align-items: center",
"gap: 5px",
"flex-shrink: 0",
"cursor: pointer",
"font-size: 10px"
]);
const cacheChevron = _UILayoutDebugger._el("span", ["color: #7070a0", "font-size: 8px", "width: 10px"]);
cacheChevron.textContent = "\u25B8";
const cacheTitle = _UILayoutDebugger._el("span", ["color: #a0a0b8"]);
cacheTitle.textContent = `Cache writes (${trace.cacheChanges.length})`;
cacheHeader.append(cacheChevron, cacheTitle);
col.appendChild(cacheHeader);
const cacheList = _UILayoutDebugger._el("div", [
"display: none",
"overflow-y: auto",
"max-height: 180px",
"flex-shrink: 0",
"font-size: 10px",
"padding: 2px 0"
]);
for (const ev of trace.cacheChanges) {
const evRow = _UILayoutDebugger._el("div", [
"padding: 2px 10px 2px 14px",
"display: flex",
"flex-direction: column",
"gap: 1px",
"border-bottom: 1px solid rgba(255,255,255,0.04)"
]);
const topLine = _UILayoutDebugger._el("div", ["display: flex", "gap: 5px", "align-items: baseline"]);
const evIdx = _UILayoutDebugger._el("span", ["color: #5a5a70", "flex-shrink: 0"]);
evIdx.textContent = `#${ev.eventIndex}`;
const evClass = _UILayoutDebugger._el("span", ["color: #ffcc88", "font-weight: bold"]);
evClass.textContent = ev.className;
const evEid = _UILayoutDebugger._el("span", ["color: #6a6a80"]);
evEid.textContent = `#${ev.elementID}`;
const evKey = _UILayoutDebugger._el("span", ["color: #9090a8"]);
const match = ev.cacheKey.match(/h_(\d+(?:\.\d+)?)__w_(\d+(?:\.\d+)?)/);
evKey.textContent = match ? match[1] !== "0" && match[2] !== "0" ? `h\u2264${match[1]} w\u2264${match[2]}` : match[2] !== "0" ? `w\u2264${match[2]}` : `h\u2264${match[1]}` : ev.cacheKey;
const evVal = _UILayoutDebugger._el("span", ["color: #88ddff", "margin-left: auto"]);
evVal.textContent = `${ev.newValue.width.toFixed(0)}\xD7${ev.newValue.height.toFixed(0)}`;
topLine.append(evIdx, evClass, evEid, evKey, evVal);
const callerLine = _UILayoutDebugger._el("div", [
"display: flex",
"gap: 5px",
"align-items: baseline"
]);
const stepRef = _UILayoutDebugger._el("span", ["color: #5a5a70", "flex-shrink: 0"]);
stepRef.textContent = ev.stepIndex >= 0 ? `step ${ev.stepIndex}` : "between steps";
const callerFn = _UILayoutDebugger._el("span", ["color: #7878a0", "cursor: pointer"]);
callerFn.textContent = ev.callerFunction + "()";
callerFn.title = ev.cleanStack;
let stackExpanded = false;
const stackEl = _UILayoutDebugger._el("div", [
"display: none",
"margin-top: 2px",
"padding: 3px 6px",
"background: rgba(255,255,255,0.04)",
"border-radius: 3px",
"color: #6060808",
"font-size: 9px",
"white-space: pre",
"overflow-x: auto"
]);
stackEl.textContent = ev.cleanStack;
callerFn.onclick = () => {
stackExpanded = !stackExpanded;
stackEl.style.display = stackExpanded ? "block" : "none";
};
callerLine.append(stepRef, callerFn);
evRow.append(topLine, callerLine, stackEl);
cacheList.appendChild(evRow);
}
cacheHeader.onclick = () => {
cacheListExpanded = !cacheListExpanded;
cacheList.style.display = cacheListExpanded ? "block" : "none";
cacheChevron.textContent = cacheListExpanded ? "\u25BE" : "\u25B8";
};
col.appendChild(cacheList);
}
return col;
}
static _renderStaleReportPanel(result) {
const panel = _UILayoutDebugger._el("div", [
"display: flex",
"flex-direction: column",
"flex: 1",
"min-height: 0",
"ove