Files
fiddle/public/js/elements-panel.js
root 6ca8519250 Add responsive preview, editor themes, template gallery, devtools, and autocomplete
- Device breakpoint toggles (mobile 375px / tablet 768px / desktop 100%)
- Editor theme selector with 6 themes (VS Dark/Light, High Contrast, Monokai, Dracula, GitHub Dark)
- Starter template gallery with 8 pre-built templates (Todo, API Fetch, CSS Animation, etc.)
- Code autocomplete with DOM/React type definitions and snippet completions
- Devtools panels: console, network, elements, performance
- Code formatter (Prettier), diff view, and linter integration
2026-02-27 01:22:16 -06:00

91 lines
3.1 KiB
JavaScript

import { registerClearHandler } from './devtools.js';
function renderNode(node, depth, maxExpand) {
if (node.type === 'text') {
const text = node.text.trim();
if (!text) return '';
return `<div class="el-node"><span class="el-text">${escapeHtml(text)}</span></div>`;
}
const hasChildren = node.children && node.children.length > 0;
const expanded = depth < maxExpand;
let attrs = '';
if (node.attrs) {
for (const a of node.attrs) {
attrs += ` <span class="el-attr-name">${escapeHtml(a.name)}</span>=<span class="el-attr-value">"${escapeHtml(a.value)}"</span>`;
}
}
let html = '<div class="el-node">';
html += `<div class="el-node-header" data-has-children="${hasChildren}">`;
html += `<span class="el-toggle">${hasChildren ? (expanded ? '▼' : '▶') : ' '}</span>`;
html += `<span>&lt;<span class="el-tag">${escapeHtml(node.tag)}</span>${attrs}&gt;</span>`;
// Inline short text content
if (hasChildren && node.children.length === 1 && node.children[0].type === 'text' && node.children[0].text.trim().length < 60) {
html += `<span class="el-text">${escapeHtml(node.children[0].text.trim())}</span>`;
html += `<span>&lt;/<span class="el-tag">${escapeHtml(node.tag)}</span>&gt;</span>`;
html += '</div></div>';
return html;
}
html += '</div>';
if (hasChildren) {
html += `<div class="el-children ${expanded ? 'expanded' : ''}">`;
for (const child of node.children) {
html += renderNode(child, depth + 1, maxExpand);
}
html += `<div style="padding-left:16px">&lt;/<span class="el-tag">${escapeHtml(node.tag)}</span>&gt;</div>`;
html += '</div>';
}
html += '</div>';
return html;
}
function escapeHtml(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
function bindToggleHandlers(container) {
container.querySelectorAll('.el-node-header[data-has-children="true"]').forEach(header => {
header.addEventListener('click', () => {
const children = header.nextElementSibling;
if (!children || !children.classList.contains('el-children')) return;
const toggle = header.querySelector('.el-toggle');
const isExpanded = children.classList.contains('expanded');
children.classList.toggle('expanded');
toggle.textContent = isExpanded ? '▶' : '▼';
});
});
}
let lastTree = null;
function render(tree) {
lastTree = tree;
const out = document.getElementById('elements-output');
if (!tree) {
out.innerHTML = '<div style="padding:12px;color:var(--text-dim);font-size:11px;">No elements captured. Run your code to see the DOM tree.</div>';
return;
}
out.innerHTML = renderNode(tree, 0, 2);
bindToggleHandlers(out);
}
export function clearElements() {
lastTree = null;
document.getElementById('elements-output').innerHTML = '';
}
export function initElements() {
registerClearHandler('elements', clearElements);
window.addEventListener('message', (e) => {
if (!e.data || e.data.type !== 'devtools' || e.data.tab !== 'elements') return;
if (e.data.tree) render(e.data.tree);
});
}