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
297 lines (296 loc) • 12.9 kB
JavaScript
;
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 UILayoutCycleTracer_exports = {};
__export(UILayoutCycleTracer_exports, {
UILayoutCycleTracer: () => UILayoutCycleTracer
});
module.exports = __toCommonJS(UILayoutCycleTracer_exports);
const _UILayoutCycleTracer = class {
static get isEnabled() {
return _UILayoutCycleTracer._isEnabled;
}
static enable(reportThreshold = 1) {
;
Error.stackTraceLimit = 100;
_UILayoutCycleTracer.reportThreshold = reportThreshold;
_UILayoutCycleTracer._isEnabled = true;
console.log(
`%c[UILayoutCycleTracer] Layout cycle tracing ENABLED (threshold=${reportThreshold}, Error.stackTraceLimit=100)`,
"color: #4CAF50; font-weight: bold"
);
}
static disable() {
;
Error.stackTraceLimit = 10;
_UILayoutCycleTracer._isEnabled = false;
console.log(
"%c[UILayoutCycleTracer] Layout cycle tracing DISABLED",
"color: #9E9E9E; font-weight: bold"
);
}
static willBeginLayoutPass() {
if (!_UILayoutCycleTracer._isEnabled) {
return;
}
_UILayoutCycleTracer._isPassActive = true;
_UILayoutCycleTracer._layoutCountsThisPass = /* @__PURE__ */ new Map();
_UILayoutCycleTracer._eventsThisPass = /* @__PURE__ */ new Map();
_UILayoutCycleTracer._currentIteration = 0;
_UILayoutCycleTracer._totalReportsThisPass = 0;
}
static willBeginIteration(iteration) {
if (!_UILayoutCycleTracer._isEnabled) {
return;
}
_UILayoutCycleTracer._currentIteration = iteration;
if (iteration > 0 && _UILayoutCycleTracer._totalReportsThisPass > 0) {
console.warn(
`%c[UILayoutCycleTracer] Layout pass iteration ${iteration + 1} \u2014 views were re-queued during the previous iteration`,
"color: #FF9800; font-weight: bold"
);
}
}
static didLayoutView(view) {
var _a;
if (!_UILayoutCycleTracer._isEnabled || !_UILayoutCycleTracer._isPassActive) {
return;
}
const previous = (_a = _UILayoutCycleTracer._layoutCountsThisPass.get(view)) != null ? _a : 0;
_UILayoutCycleTracer._layoutCountsThisPass.set(view, previous + 1);
}
static viewDidCallSetNeedsLayout(view) {
var _a, _b, _c;
if (!_UILayoutCycleTracer._isEnabled || !_UILayoutCycleTracer._isPassActive) {
return;
}
const layoutCount = (_a = _UILayoutCycleTracer._layoutCountsThisPass.get(view)) != null ? _a : 0;
if (layoutCount < _UILayoutCycleTracer.reportThreshold) {
return;
}
if (_UILayoutCycleTracer._totalReportsThisPass >= _UILayoutCycleTracer.maxReportsPerPass) {
if (_UILayoutCycleTracer._totalReportsThisPass === _UILayoutCycleTracer.maxReportsPerPass) {
console.warn(
`%c[UILayoutCycleTracer] Maximum reports per pass (${_UILayoutCycleTracer.maxReportsPerPass}) reached. Further reports suppressed.`,
"color: #F44336"
);
_UILayoutCycleTracer._totalReportsThisPass++;
}
return;
}
_UILayoutCycleTracer._totalReportsThisPass++;
const rawStack = (_b = new Error().stack) != null ? _b : "(stack unavailable)";
const cleanStack = _UILayoutCycleTracer._cleanStack(rawStack);
const snapshot = _UILayoutCycleTracer._snapshotView(view);
const callerFunction = _UILayoutCycleTracer._extractCallerFunctionName(cleanStack);
const existingHistory = (_c = _UILayoutCycleTracer._eventsThisPass.get(view)) != null ? _c : [];
const newEvent = {
occurrenceIndex: existingHistory.length + 1,
iteration: _UILayoutCycleTracer._currentIteration,
layoutCountAtTime: layoutCount,
callerFunction,
snapshot,
cleanStack
};
existingHistory.push(newEvent);
_UILayoutCycleTracer._eventsThisPass.set(view, existingHistory);
_UILayoutCycleTracer._reportCycle(view, existingHistory);
}
static didFinishLayoutPass(iterationCount) {
if (!_UILayoutCycleTracer._isEnabled) {
return;
}
_UILayoutCycleTracer._isPassActive = false;
if (iterationCount > 1 && _UILayoutCycleTracer._totalReportsThisPass > 0) {
console.warn(
`%c[UILayoutCycleTracer] Layout pass completed in ${iterationCount} iteration(s). ${_UILayoutCycleTracer._totalReportsThisPass} cycle event(s) recorded.`,
"color: #FF9800"
);
}
}
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 isNoise = _UILayoutCycleTracer._noiseFramePrefixes.some(
(prefix) => trimmed.includes(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 match = firstLine.match(/at\s+([\w.<>$]+)\s+\(/);
if (match) {
return match[1];
}
return firstLine.substring(0, 80) || "(unknown)";
}
static _snapshotView(view) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
const frame = view == null ? void 0 : view.frame;
const bounds = view == null ? void 0 : view.bounds;
return {
isVirtualLayouting: (_a = view == null ? void 0 : view.isVirtualLayouting) != null ? _a : false,
frameWidth: (_b = frame == null ? void 0 : frame.width) != null ? _b : -1,
frameHeight: (_c = frame == null ? void 0 : frame.height) != null ? _c : -1,
boundsWidth: (_d = bounds == null ? void 0 : bounds.width) != null ? _d : -1,
boundsHeight: (_e = bounds == null ? void 0 : bounds.height) != null ? _e : -1,
className: (_g = (_f = view == null ? void 0 : view.constructor) == null ? void 0 : _f.name) != null ? _g : "UnknownView",
elementID: (_i = (_h = view == null ? void 0 : view.elementID) != null ? _h : view == null ? void 0 : view._UIViewIndex) != null ? _i : "?"
};
}
static _formatSnapshotInline(snapshot) {
const virtualTag = snapshot.isVirtualLayouting ? "\u{1F52E} VIRTUAL" : "\u{1F4D0} real";
return `${virtualTag} frame ${snapshot.frameWidth.toFixed(1)}\xD7${snapshot.frameHeight.toFixed(1)} bounds ${snapshot.boundsWidth.toFixed(1)}\xD7${snapshot.boundsHeight.toFixed(1)}`;
}
static _viewIdentifier(view) {
var _a, _b, _c, _d;
const className = (_b = (_a = view == null ? void 0 : view.constructor) == null ? void 0 : _a.name) != null ? _b : "UnknownView";
const elementID = (_d = (_c = view == null ? void 0 : view.elementID) != null ? _c : view == null ? void 0 : view._UIViewIndex) != null ? _d : "?";
return `${className}#${elementID}`;
}
static _superviewChain(view) {
const parts = [];
let current = view;
let depth = 0;
while (current && depth < 20) {
const snapshot = _UILayoutCycleTracer._snapshotView(current);
const virtualTag = snapshot.isVirtualLayouting ? "[VIRTUAL]" : "[real]";
parts.push(
`${_UILayoutCycleTracer._viewIdentifier(current)} ${virtualTag} frame=${snapshot.frameWidth.toFixed(0)}\xD7${snapshot.frameHeight.toFixed(0)} bounds=${snapshot.boundsWidth.toFixed(0)}\xD7${snapshot.boundsHeight.toFixed(0)}`
);
current = current.superview;
depth++;
}
return parts.join("\n \u2192 ");
}
static _printHistoryEvent(event, isCurrent) {
const label = isCurrent ? "\u{1F195} NOW" : `#${event.occurrenceIndex}`;
const iterationLabel = `iter ${event.iteration + 1}`;
const snapshotInline = _UILayoutCycleTracer._formatSnapshotInline(event.snapshot);
const title = ` ${label} ${event.callerFunction}() [${iterationLabel}] ${snapshotInline}`;
const titleStyle = isCurrent ? "color: #F44336; font-weight: bold" : "color: #888; font-weight: normal";
if (isCurrent) {
console.group(`%c${title}`, titleStyle);
} else {
console.groupCollapsed(`%c${title}`, titleStyle);
}
console.log(`%c layoutCount at time: ${event.layoutCountAtTime}`, "color: #aaa");
console.log("%c Stack:", "font-weight: bold");
event.cleanStack.split("\n").forEach((frame) => console.log(" " + frame.trim()));
console.groupEnd();
}
static _reportCycle(view, history) {
const currentEvent = history[history.length - 1];
const identifier = _UILayoutCycleTracer._viewIdentifier(view);
const chain = _UILayoutCycleTracer._superviewChain(view);
const virtualLabel = currentEvent.snapshot.isVirtualLayouting ? "VIRTUAL" : "real";
console.groupCollapsed(
`%c[UILayoutCycleTracer] \u26A0\uFE0F Cycle #${history.length}: ${identifier} from ${currentEvent.callerFunction}() [${virtualLabel}, ${currentEvent.snapshot.frameWidth.toFixed(0)}\xD7${currentEvent.snapshot.frameHeight.toFixed(0)}] laid out ${currentEvent.layoutCountAtTime}x iter ${currentEvent.iteration + 1}`,
"color: #F44336; font-weight: bold"
);
console.group(
`%c\u{1F4DC} Full history for this view this pass (${history.length} event${history.length === 1 ? "" : "s"})`,
"font-weight: bold; color: #FF9800"
);
for (let i = 0; i < history.length; i++) {
_UILayoutCycleTracer._printHistoryEvent(history[i], i === history.length - 1);
}
console.groupEnd();
console.groupCollapsed(
"%c\u{1F517} Superview chain (innermost \u2192 root)",
"font-weight: bold; color: #9C27B0"
);
console.log(" " + chain);
console.groupEnd();
console.log("%c\u{1F5BC} Raw view object:", "font-weight: bold", view);
console.groupEnd();
}
static printViewReport(view) {
if (!view) {
console.warn("[UILayoutCycleTracer] printViewReport: no view provided");
return;
}
const identifier = _UILayoutCycleTracer._viewIdentifier(view);
const snapshot = _UILayoutCycleTracer._snapshotView(view);
const chain = _UILayoutCycleTracer._superviewChain(view);
const history = _UILayoutCycleTracer._eventsThisPass.get(view);
console.group(
`%c[UILayoutCycleTracer] \u{1F50D} Manual report: ${identifier}`,
"color: #2196F3; font-weight: bold"
);
console.group("%c\u{1F4F8} Current state", "font-weight: bold; color: #2196F3");
console.log(_UILayoutCycleTracer._formatSnapshotInline(snapshot));
console.groupEnd();
if (history && history.length > 0) {
console.group(
`%c\u{1F4DC} Cycle history from last pass (${history.length} event${history.length === 1 ? "" : "s"})`,
"font-weight: bold; color: #FF9800"
);
for (let i = 0; i < history.length; i++) {
_UILayoutCycleTracer._printHistoryEvent(history[i], i === history.length - 1);
}
console.groupEnd();
} else {
console.log("%c\u{1F4DC} No cycle history recorded for this view in the last pass", "color: #4CAF50");
}
console.groupCollapsed(
"%c\u{1F517} Superview chain (innermost \u2192 root)",
"font-weight: bold; color: #9C27B0"
);
console.log(" " + chain);
console.groupEnd();
console.log("%c\u{1F5BC} Raw view object:", "font-weight: bold", view);
console.groupEnd();
}
};
let UILayoutCycleTracer = _UILayoutCycleTracer;
UILayoutCycleTracer._isEnabled = false;
UILayoutCycleTracer._isPassActive = false;
UILayoutCycleTracer._layoutCountsThisPass = /* @__PURE__ */ new Map();
UILayoutCycleTracer._eventsThisPass = /* @__PURE__ */ new Map();
UILayoutCycleTracer._currentIteration = 0;
UILayoutCycleTracer._totalReportsThisPass = 0;
UILayoutCycleTracer.reportThreshold = 1;
UILayoutCycleTracer.maxReportsPerPass = 10;
UILayoutCycleTracer._noiseFramePrefixes = [
"UILayoutCycleTracer",
"UIView.setNeedsLayout",
"setNeedsLayout",
"UIView.didLayoutSubviews",
"didLayoutSubviews",
"UIView.layoutSubviews",
"UIView.layoutIfNeeded",
"layoutIfNeeded",
"UIView.layoutViewsIfNeeded",
"layoutViewsIfNeeded"
];
window.UILayoutCycleTracer = UILayoutCycleTracer;
window.layoutReport = (view) => UILayoutCycleTracer.printViewReport(view);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
UILayoutCycleTracer
});
//# sourceMappingURL=UILayoutCycleTracer.js.map