Skip to content

Commit 26eedb2

Browse files
authored
Merge pull request #17 from aidenybai/feat/select
Select Components
2 parents 97f7327 + b5b2efc commit 26eedb2

File tree

2 files changed

+84
-7
lines changed

2 files changed

+84
-7
lines changed

src/core/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from './web/outline';
1010
import { createOverlay } from './web/index';
1111
import { logIntro } from './web/log';
12-
import { createToolbar } from './web/toolbar';
12+
import { createToolbar, renderCheckbox } from './web/toolbar';
1313
import { playGeigerClickSound } from './web/geiger';
1414
import { createPerfObserver } from './web/perf-observer';
1515

@@ -97,6 +97,7 @@ interface Internals {
9797
isInIframe: boolean;
9898
isPaused: boolean;
9999
componentAllowList: WeakMap<React.ComponentType<any>, Options> | null;
100+
componentNameAllowList: Set<string>;
100101
options: Options;
101102
scheduledOutlines: PendingOutline[];
102103
activeOutlines: ActiveOutline[];
@@ -123,6 +124,7 @@ export const ReactScanInternals: Internals = {
123124
isInIframe: window.self !== window.top,
124125
isPaused: false,
125126
componentAllowList: null,
127+
componentNameAllowList: new Set<string>(),
126128
options: {
127129
enabled: true,
128130
includeChildren: true,
@@ -180,7 +182,15 @@ export const start = () => {
180182
options.onRender?.(fiber, render);
181183
const outline = getOutline(fiber, render);
182184
if (!outline) return;
183-
ReactScanInternals.scheduledOutlines.push(outline);
185+
const { componentNameAllowList } = ReactScanInternals;
186+
if (
187+
!render.name ||
188+
!componentNameAllowList.size ||
189+
componentNameAllowList.has(render.name)
190+
) {
191+
ReactScanInternals.scheduledOutlines.push(outline);
192+
}
193+
184194

185195
if (options.playSound && audioContext) {
186196
const renderTimeThreshold = 10;
@@ -199,6 +209,7 @@ export const start = () => {
199209
time: (prev?.time ?? 0) + render.time,
200210
renders: prev.renders,
201211
};
212+
renderCheckbox();
202213
}
203214

204215
requestAnimationFrame(() => {

src/core/web/toolbar.ts

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ReactScanInternals } from '../../index';
1+
import { getReport, ReactScanInternals } from '../../index';
22
import { createElement } from './utils';
33
import { MONO_FONT } from './outline';
44

@@ -7,16 +7,32 @@ export const createToolbar = () => {
77
`<div id="react-scan-toolbar" title="Number of unnecessary renders and time elapsed" style="position:fixed;bottom:3px;right:3px;background:rgba(0,0,0,0.5);padding:4px 8px;border-radius:4px;color:white;z-index:2147483647;font-family:${MONO_FONT}" aria-hidden="true">react-scan</div>`,
88
) as HTMLDivElement;
99

10+
// Create a scrollable and resizable div containing checkboxes
11+
const checkboxContainer = createElement(
12+
`<div id="react-scan-checkbox-list" style="position:fixed;bottom:3px;left:3px;min-width:200px;max-width:400px;height:200px;background:rgba(0,0,0,0.5);padding:8px;border:1px solid rgba(255,255,255,0.4);border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,0.15);z-index:2147483647;font-family:${MONO_FONT};overflow-y:auto;resize:horizontal;display:none;">
13+
<div style="font-weight:400;margin-bottom:8px;color:white;border-bottom:1px solid rgba(255,255,255,0.4);padding-bottom:4px; font-size:14px;">Component Filters</div>
14+
</div>`,
15+
) as HTMLDivElement;
16+
17+
document.documentElement.appendChild(checkboxContainer);
18+
1019
let isHidden =
11-
// discord doesn't support localStorage
20+
// Discord doesn't support localStorage
1221
'localStorage' in globalThis &&
1322
localStorage.getItem('react-scan-hidden') === 'true';
1423

24+
let isCheckboxContainerHidden = true;
25+
26+
const toggleButton = createElement(
27+
`<button style="margin-left:8px;background:rgba(255,255,255,0.2);border:1px solid rgba(255,255,255,0.4);color:white;cursor:pointer;padding:3px 6px;border-radius:4px;font-size:16px;transition:all 0.2s;" title="Toggle component list">☰</button>`
28+
) as HTMLButtonElement;
29+
1530
const updateVisibility = () => {
1631
const overlay = document.getElementById('react-scan-overlay');
1732
if (!overlay) return;
1833
overlay.style.display = isHidden ? 'none' : 'block';
19-
status.textContent = isHidden ? 'start ►' : 'stop ⏹';
34+
status.textContent = isHidden ? 'start' : 'stop';
35+
status.appendChild(toggleButton);
2036
ReactScanInternals.isPaused = isHidden;
2137
if (ReactScanInternals.isPaused) {
2238
ReactScanInternals.activeOutlines = [];
@@ -29,18 +45,30 @@ export const createToolbar = () => {
2945

3046
updateVisibility();
3147

32-
status.addEventListener('click', () => {
48+
status.addEventListener('click', (e) => {
49+
if (e.target === toggleButton) return;
3350
isHidden = !isHidden;
3451
updateVisibility();
3552
});
3653

54+
toggleButton.addEventListener('click', () => {
55+
isCheckboxContainerHidden = !isCheckboxContainerHidden;
56+
checkboxContainer.style.display = isCheckboxContainerHidden ? 'none' : 'block';
57+
renderCheckbox();
58+
});
59+
3760
status.addEventListener('mouseenter', () => {
38-
status.textContent = isHidden ? 'start ►' : 'stop ⏹';
61+
if (status.textContent !== '☰') {
62+
status.textContent = isHidden ? 'start' : 'stop';
63+
status.appendChild(toggleButton);
64+
}
3965
status.style.backgroundColor = 'rgba(0,0,0,1)';
66+
toggleButton.style.backgroundColor = 'rgba(255,255,255,0.3)';
4067
});
4168

4269
status.addEventListener('mouseleave', () => {
4370
status.style.backgroundColor = 'rgba(0,0,0,0.5)';
71+
toggleButton.style.backgroundColor = 'rgba(255,255,255,0.2)';
4472
});
4573

4674
const prevElement = document.getElementById('react-scan-toolbar');
@@ -51,3 +79,41 @@ export const createToolbar = () => {
5179

5280
return status;
5381
};
82+
83+
export const renderCheckbox = () => {
84+
const checkboxContainer = document.getElementById('react-scan-checkbox-list');
85+
if (!checkboxContainer) return;
86+
87+
checkboxContainer.innerHTML = `
88+
<div style="font-weight:600;margin-bottom:8px;color:white;border-bottom:1px solid rgba(255,255,255,0.4);padding-bottom:4px;">Component Filters</div>
89+
`;
90+
91+
for (const [name, { count, time }] of Object.entries(getReport())) {
92+
const label = createElement(
93+
`<label style="display:flex;align-items:center;padding:4px 0;border-radius:4px;cursor:pointer;transition:background 0.2s;margin:2px 0;color:white;font-size:13px;">
94+
<input type="checkbox" value="${name}" style="margin-right:8px;">
95+
<div style="flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${name}</div>
96+
<div style="margin-left:8px;color:rgba(255,255,255,0.7);white-space:nowrap;">✖︎${count} · ${time.toFixed(1)}ms</div>
97+
</label>`,
98+
) as HTMLLabelElement;
99+
100+
const checkbox = label.querySelector('input')!;
101+
checkbox.checked = ReactScanInternals.componentNameAllowList.has(name);
102+
103+
label.addEventListener('mouseenter', () => {
104+
label.style.background = 'rgba(255,255,255,0.1)';
105+
});
106+
107+
label.addEventListener('mouseleave', () => {
108+
label.style.background = 'transparent';
109+
});
110+
111+
checkbox.addEventListener('change', () => {
112+
if (checkbox.checked)
113+
ReactScanInternals.componentNameAllowList.add(name);
114+
else ReactScanInternals.componentNameAllowList.delete(name);
115+
});
116+
117+
checkboxContainer.appendChild(label);
118+
}
119+
};

0 commit comments

Comments
 (0)