Skip to content

Commit 5deae89

Browse files
committed
Fix keydown listener leak and deduplicate prop error message logic
1 parent e0345a5 commit 5deae89

1 file changed

Lines changed: 112 additions & 105 deletions

File tree

slippers/static/slippers/main.js

Lines changed: 112 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
(function () {
2-
"use strict";
3-
4-
// ---------------------------------------------------------------------------
5-
// Safety
6-
// ---------------------------------------------------------------------------
7-
8-
function esc(value) {
9-
return String(value)
10-
.replace(/&/g, "&")
11-
.replace(/</g, "&lt;")
12-
.replace(/>/g, "&gt;")
13-
.replace(/"/g, "&quot;")
14-
.replace(/'/g, "&#39;");
15-
}
16-
17-
// ---------------------------------------------------------------------------
18-
// Styles
19-
// ---------------------------------------------------------------------------
20-
21-
const STYLES = `
2+
"use strict";
3+
4+
// ---------------------------------------------------------------------------
5+
// Escaping function for HTML content
6+
// ---------------------------------------------------------------------------
7+
8+
function esc(value) {
9+
return String(value)
10+
.replace(/&/g, "&amp;")
11+
.replace(/</g, "&lt;")
12+
.replace(/>/g, "&gt;")
13+
.replace(/"/g, "&quot;")
14+
.replace(/'/g, "&#39;");
15+
}
16+
17+
// ---------------------------------------------------------------------------
18+
// Styles
19+
// ---------------------------------------------------------------------------
20+
21+
const STYLES = `
22+
#slippers_errors_ui_root {
23+
position: relative;
24+
z-index: 99999;
25+
}
2226
#slippers-overlay {
2327
position: fixed; inset: 0;
2428
background: rgba(0,0,0,0.92);
@@ -47,40 +51,53 @@
4751
.slippers-type { color: #2dd4bf; font-family: monospace; }
4852
`;
4953

50-
// ---------------------------------------------------------------------------
51-
// HTML component functions (pure: data in, HTML string out)
52-
// ---------------------------------------------------------------------------
53-
54-
function renderPropError(propError, tagName) {
55-
const name = `<span class="slippers-var">${esc(propError.name)}</span>`;
56-
const tag = `<span class="slippers-tag">${esc(tagName)}</span>`;
57-
const exp = `<span class="slippers-type">${esc(propError.expected)}</span>`;
58-
const act = `<span class="slippers-type">${esc(propError.actual)}</span>`;
59-
60-
if (propError.error === "invalid")
61-
return `Invalid prop ${name} set on ${tag}. Expected ${exp}, got ${act}.`;
62-
if (propError.error === "missing")
63-
return `Required prop ${name} of type ${exp} not set on ${tag}.`;
64-
if (propError.error === "extra")
65-
return `Extra prop ${name} of type ${act} set on ${tag}.`;
66-
return esc(propError.error);
67-
}
68-
69-
function renderErrorBox(slippersError) {
70-
const items = slippersError.errors
71-
.map((e) => `<li>${renderPropError(e, slippersError.tag_name)}</li>`)
72-
.join("");
73-
return `
54+
// ---------------------------------------------------------------------------
55+
// Components
56+
// ---------------------------------------------------------------------------
57+
58+
function formatPropErrorMessage(propError, tagName) {
59+
if (propError.error === "invalid")
60+
return `Invalid prop ${propError.name} set on ${tagName}. Expected ${propError.expected}, got ${propError.actual}.`;
61+
if (propError.error === "missing")
62+
return `Required prop ${propError.name} of type ${propError.expected} not set on ${tagName}.`;
63+
if (propError.error === "extra")
64+
return `Extra prop ${propError.name} of type ${propError.actual} set on ${tagName}.`;
65+
return propError.error;
66+
}
67+
68+
function renderPropError(propError, tagName) {
69+
const name = `<span class="slippers-var">${esc(propError.name)}</span>`;
70+
const tag = `<span class="slippers-tag">${esc(tagName)}</span>`;
71+
const exp = `<span class="slippers-type">${esc(propError.expected)}</span>`;
72+
const act = `<span class="slippers-type">${esc(propError.actual)}</span>`;
73+
74+
if (propError.error === "invalid")
75+
return `Invalid prop ${name} set on ${tag}. Expected ${exp}, got ${act}.`;
76+
if (propError.error === "missing")
77+
return `Required prop ${name} of type ${exp} not set on ${tag}.`;
78+
if (propError.error === "extra")
79+
return `Extra prop ${name} of type ${act} set on ${tag}.`;
80+
return esc(propError.error);
81+
}
82+
83+
function renderErrorBox(slippersError) {
84+
const items = slippersError.errors
85+
.map(
86+
(e) => `<li>${renderPropError(e, slippersError.tag_name)}</li>`,
87+
)
88+
.join("");
89+
90+
return `
7491
<div class="slippers-error-box">
7592
<p class="slippers-tag-label">${esc(slippersError.tag_name)}</p>
7693
<p class="slippers-location">${esc(slippersError.template_name)}:${esc(slippersError.lineno)}</p>
7794
<ul class="slippers-error-list">${items}</ul>
7895
</div>`;
79-
}
96+
}
8097

81-
function renderOverlay(errors) {
82-
const boxes = errors.map(renderErrorBox).join("");
83-
return `
98+
function renderOverlay(errors) {
99+
const boxes = errors.map(renderErrorBox).join("");
100+
return `
84101
<style>${STYLES}</style>
85102
<div id="slippers-overlay">
86103
<div class="slippers-header">
@@ -89,70 +106,60 @@
89106
</div>
90107
${boxes}
91108
</div>`;
92-
}
93-
94-
// ---------------------------------------------------------------------------
95-
// Console reporting
96-
// ---------------------------------------------------------------------------
97-
98-
function reportErrorsToConsole(errors) {
99-
console.group("Slippers: Failed prop types");
100-
errors.forEach(function (slippersError) {
101-
console.groupCollapsed(
102-
slippersError.tag_name + " in " + slippersError.template_name + ":" + slippersError.lineno
103-
);
104-
slippersError.errors.forEach(function (propError) {
105-
if (propError.error === "invalid")
106-
console.error(
107-
"Invalid prop " + propError.name + " set on " + slippersError.tag_name +
108-
". Expected " + propError.expected + ", got " + propError.actual + "."
109-
);
110-
else if (propError.error === "missing")
111-
console.error(
112-
"Required prop " + propError.name + " of type " + propError.expected +
113-
" not set on " + slippersError.tag_name + "."
114-
);
115-
else if (propError.error === "extra")
116-
console.error(
117-
"Extra prop " + propError.name + " of type " + propError.actual +
118-
" set on " + slippersError.tag_name + "."
119-
);
120-
});
121-
console.groupEnd();
122-
});
123-
console.groupEnd();
124-
}
109+
}
125110

126-
// ---------------------------------------------------------------------------
127-
// Entry point
128-
// ---------------------------------------------------------------------------
111+
// ---------------------------------------------------------------------------
112+
// Console reporting
113+
// ---------------------------------------------------------------------------
114+
115+
function reportErrorsToConsole(errors) {
116+
console.group("Slippers: Failed prop types");
117+
errors.forEach(function (slippersError) {
118+
console.groupCollapsed(
119+
slippersError.tag_name +
120+
" in " +
121+
slippersError.template_name +
122+
":" +
123+
slippersError.lineno,
124+
);
125+
slippersError.errors.forEach(function (propError) {
126+
console.error(formatPropErrorMessage(propError, slippersError.tag_name));
127+
});
128+
console.groupEnd();
129+
});
130+
console.groupEnd();
131+
}
129132

130-
document.addEventListener("DOMContentLoaded", function () {
131-
var errors = window.slippersPropErrors || [];
132-
var configEl = document.getElementById("slippers_type_checking_output");
133-
var config = JSON.parse((configEl && configEl.textContent) || "[]");
133+
// ---------------------------------------------------------------------------
134+
// Entry point
135+
// ---------------------------------------------------------------------------
134136

135-
if (errors.length === 0) return;
137+
document.addEventListener("DOMContentLoaded", function () {
138+
var errors = window.slippersPropErrors || [];
139+
var configEl = document.getElementById("slippers_type_checking_output");
140+
var config = JSON.parse((configEl && configEl.textContent) || "[]");
136141

137-
if (config.includes("console")) reportErrorsToConsole(errors);
142+
if (errors.length === 0) return;
138143

139-
if (config.includes("overlay")) {
140-
var root = document.getElementById("slippers_errors_ui_root");
141-
if (!root) return;
144+
if (config.includes("console")) reportErrorsToConsole(errors);
142145

143-
root.innerHTML = renderOverlay(errors);
146+
if (config.includes("overlay")) {
147+
var root = document.getElementById("slippers_errors_ui_root");
148+
if (!root) return;
144149

145-
document.getElementById("slippers-close-btn").addEventListener("click", function () {
146-
root.innerHTML = "";
147-
});
150+
root.innerHTML = renderOverlay(errors);
148151

149-
document.addEventListener("keydown", function onKey(ev) {
150-
if (ev.key === "Escape") {
151-
root.innerHTML = "";
152-
document.removeEventListener("keydown", onKey);
153-
}
154-
});
155-
}
156-
});
152+
function closeOverlay() {
153+
root.innerHTML = "";
154+
document.removeEventListener("keydown", onKey);
155+
}
156+
157+
function onKey(ev) {
158+
if (ev.key === "Escape") closeOverlay();
159+
}
157160

161+
document.getElementById("slippers-close-btn").addEventListener("click", closeOverlay);
162+
document.addEventListener("keydown", onKey);
163+
}
164+
});
158165
})();

0 commit comments

Comments
 (0)