app-sleepy-manatee-skip/public/_kleap/kleap-component-selector-cl...

964 lines
28 KiB
JavaScript

(() => {
const OVERLAY_ID = "__kleap_overlay__";
let overlay, label;
// The possible states are:
// { type: 'inactive' }
// { type: 'inspecting', element: ?HTMLElement }
// { type: 'selected', element: HTMLElement }
let state = { type: "inactive" };
/* ---------- helpers --------------------------------------------------- */
const css = (el, obj) => Object.assign(el.style, obj);
function getElementPath(element) {
const path = [];
let current = element;
let depth = 0;
const maxDepth = 5;
while (current && current !== document.body && depth < maxDepth) {
let identifier = current.tagName.toLowerCase();
if (current.id) {
identifier += `#${current.id}`;
} else if (current.className && typeof current.className === 'string') {
const firstClass = current.className.trim().split(' ').filter(c => c && !c.startsWith('_'))[0];
if (firstClass) {
identifier += `.${firstClass}`;
}
}
path.unshift(identifier);
current = current.parentElement;
depth++;
}
return path.join(' > ');
}
function isTextElement(el) {
// Check if element contains ANY text (even nested)
if (!el) return false;
// Get all text content including nested elements
const textContent = el.textContent || '';
// Check if it's a text-focused element
const textTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'BUTTON', 'LI', 'TD', 'TH', 'LABEL', 'DIV'];
const hasText = textContent.trim().length > 0;
const isTextTag = textTags.includes(el.tagName);
// If element has text and is a common text element, it's editable
return hasText && (isTextTag || el.childNodes.length === 1);
}
function isImageElement(el) {
return el && el.tagName === 'IMG';
}
function enableImageEdit(img) {
// Get image position
const rect = img.getBoundingClientRect();
// Create context menu
const menu = document.createElement('div');
css(menu, {
position: 'fixed',
top: `${Math.min(rect.bottom + 8, window.innerHeight - 200)}px`,
left: `${Math.min(rect.left, window.innerWidth - 250)}px`,
background: 'white',
borderRadius: '12px',
boxShadow: '0 8px 32px rgba(0,0,0,0.15)',
zIndex: '2147483648',
padding: '8px',
minWidth: '240px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
animation: 'kleapMenuSlideIn 0.2s ease-out',
});
// Add animation
const style = document.createElement('style');
style.textContent = `
@keyframes kleapMenuSlideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.kleap-menu-item {
padding: 10px 12px;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s;
display: flex;
align-items: center;
gap: 12px;
color: #333;
font-size: 14px;
}
.kleap-menu-item:hover {
background: #f5f5f5;
}
.kleap-menu-icon {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
`;
document.head.appendChild(style);
// Menu items with icons
const menuItems = [
{
icon: '📚',
label: 'Choose from Library',
action: () => showLibraryDialog(img)
},
{
icon: '🔗',
label: 'Change URL',
action: () => showUrlDialog(img)
},
{
icon: '📤',
label: 'Upload Image',
action: () => showUploadDialog(img)
},
{
icon: '✨',
label: 'Generate with AI',
action: () => showAIDialog(img)
}
];
menuItems.forEach(item => {
const menuItem = document.createElement('div');
menuItem.className = 'kleap-menu-item';
const icon = document.createElement('span');
icon.className = 'kleap-menu-icon';
icon.textContent = item.icon;
const label = document.createElement('span');
label.textContent = item.label;
menuItem.appendChild(icon);
menuItem.appendChild(label);
menuItem.onclick = () => {
menu.remove();
style.remove();
item.action();
};
menu.appendChild(menuItem);
});
document.body.appendChild(menu);
// Close menu on click outside
const closeMenu = (e) => {
if (!menu.contains(e.target) && e.target !== img) {
menu.remove();
style.remove();
document.removeEventListener('click', closeMenu);
}
};
setTimeout(() => {
document.addEventListener('click', closeMenu);
}, 100);
}
function showLibraryDialog(img) {
// Send message to parent to open Asset Manager
window.parent.postMessage({
type: 'kleap-image-library-select',
id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`,
oldSrc: img.src,
alt: img.alt || '',
}, '*');
}
function showUrlDialog(img) {
const dialog = createDialog('Change Image URL');
const input = document.createElement('input');
input.type = 'text';
input.value = img.src;
input.placeholder = 'Enter image URL...';
css(input, {
width: '100%',
padding: '10px 12px',
border: '1px solid #ddd',
borderRadius: '8px',
fontSize: '14px',
marginBottom: '16px',
boxSizing: 'border-box',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
});
dialog.content.appendChild(input);
dialog.onSave = () => {
const newSrc = input.value.trim();
if (newSrc && newSrc !== img.src) {
updateImage(img, newSrc);
}
};
input.focus();
input.select();
}
function showUploadDialog(img) {
// Open Asset Manager directly - it has full upload functionality
window.parent.postMessage({
type: 'kleap-image-library-select',
id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`,
oldSrc: img.src,
alt: img.alt || '',
}, '*');
}
function showAIDialog(img) {
const dialog = createDialog('Generate with AI');
// Model selection
const modelLabel = document.createElement('div');
css(modelLabel, {
fontSize: '12px',
color: '#666',
marginBottom: '8px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
});
modelLabel.textContent = 'Choose Model:';
const modelContainer = document.createElement('div');
css(modelContainer, {
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '8px',
marginBottom: '16px',
});
const models = [
{ id: 'flux-pro', name: '🚀 Flux Pro', desc: 'Fast & High quality' },
{ id: 'flux-dev', name: '⚡ Flux Dev', desc: 'Good balance' },
{ id: 'sdxl', name: '🎨 Stable Diffusion', desc: 'Classic & reliable' },
{ id: 'playground-v2', name: '✨ Playground v2', desc: 'Aesthetic focus' },
{ id: 'kandinsky', name: '🖼️ Kandinsky', desc: 'Artistic style' },
{ id: 'dalle-3', name: '🤖 DALL-E 3', desc: 'OpenAI (if available)' }
];
let selectedModel = 'flux-pro';
const modelButtons = [];
models.forEach(model => {
const button = document.createElement('div');
css(button, {
padding: '8px',
borderRadius: '8px',
border: '2px solid #ddd',
cursor: 'pointer',
transition: 'all 0.2s',
textAlign: 'center',
background: model.id === selectedModel ? '#ff0055' : 'white',
color: model.id === selectedModel ? 'white' : '#333',
});
button.innerHTML = `
<div style="font-size: 20px; margin-bottom: 4px;">${model.name.split(' ')[0]}</div>
<div style="font-size: 11px; font-weight: 500;">${model.name.substring(model.name.indexOf(' ') + 1)}</div>
<div style="font-size: 9px; opacity: 0.8; margin-top: 2px;">${model.desc}</div>
`;
button.onclick = () => {
selectedModel = model.id;
// Update button styles
modelButtons.forEach(btn => {
css(btn, {
background: btn === button ? '#ff0055' : 'white',
color: btn === button ? 'white' : '#333',
border: btn === button ? '2px solid #ff0055' : '2px solid #ddd',
});
});
};
modelButtons.push(button);
modelContainer.appendChild(button);
});
// Prompt textarea
const promptLabel = document.createElement('div');
css(promptLabel, {
fontSize: '12px',
color: '#666',
marginBottom: '8px',
marginTop: '16px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
});
promptLabel.textContent = 'Describe your image:';
const textarea = document.createElement('textarea');
textarea.placeholder = 'A beautiful landscape with mountains and a sunset...';
css(textarea, {
width: '100%',
minHeight: '80px',
padding: '10px 12px',
border: '1px solid #ddd',
borderRadius: '8px',
fontSize: '14px',
marginBottom: '12px',
resize: 'vertical',
boxSizing: 'border-box',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
});
// Style presets
const styleLabel = document.createElement('div');
css(styleLabel, {
fontSize: '12px',
color: '#666',
marginBottom: '8px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
});
styleLabel.textContent = 'Style presets (optional):';
const styleContainer = document.createElement('div');
css(styleContainer, {
display: 'flex',
flexWrap: 'wrap',
gap: '6px',
marginBottom: '16px',
});
const styles = ['Photorealistic', 'Artistic', 'Anime', '3D Render', 'Watercolor', 'Oil Painting', 'Minimalist', 'Vintage'];
styles.forEach(style => {
const chip = document.createElement('div');
css(chip, {
padding: '4px 10px',
borderRadius: '12px',
border: '1px solid #ddd',
fontSize: '12px',
cursor: 'pointer',
transition: 'all 0.15s',
background: 'white',
color: '#666',
});
chip.textContent = style;
chip.onclick = () => {
const isSelected = chip.style.background === 'rgb(255, 0, 85)';
css(chip, {
background: isSelected ? 'white' : '#ff0055',
color: isSelected ? '#666' : 'white',
border: isSelected ? '1px solid #ddd' : '1px solid #ff0055',
});
// Add/remove from prompt
if (!isSelected) {
if (!textarea.value.includes(style.toLowerCase())) {
textarea.value = textarea.value.trim() + (textarea.value ? ', ' : '') + style.toLowerCase() + ' style';
}
} else {
textarea.value = textarea.value.replace(new RegExp(`,?\\s*${style.toLowerCase()}\\s*style`, 'gi'), '');
}
};
styleContainer.appendChild(chip);
});
// Append all elements
dialog.content.appendChild(modelLabel);
dialog.content.appendChild(modelContainer);
dialog.content.appendChild(promptLabel);
dialog.content.appendChild(textarea);
dialog.content.appendChild(styleLabel);
dialog.content.appendChild(styleContainer);
dialog.saveBtn.textContent = '🎨 Generate';
dialog.onSave = () => {
const prompt = textarea.value.trim();
if (prompt) {
// Send to parent for AI generation with selected model
window.parent.postMessage({
type: 'kleap-image-ai-generate',
id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`,
prompt: prompt,
model: selectedModel,
oldSrc: img.src,
}, '*');
// Show generating state
dialog.content.innerHTML = `
<div style="text-align: center; padding: 30px;">
<div style="font-size: 48px; margin-bottom: 12px; animation: spin 2s linear infinite;">✨</div>
<div style="color: #333; font-size: 16px; font-weight: 600;">Generating with ${models.find(m => m.id === selectedModel)?.name || 'AI'}...</div>
<div style="color: #666; font-size: 14px; margin-top: 8px;">"${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"</div>
<div style="color: #999; font-size: 12px; margin-top: 12px;">This may take 10-30 seconds</div>
</div>
<style>
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
`;
dialog.saveBtn.style.display = 'none';
}
};
textarea.focus();
}
function createDialog(title) {
const overlay = document.createElement('div');
css(overlay, {
position: 'fixed',
top: '0',
left: '0',
right: '0',
bottom: '0',
background: 'rgba(0,0,0,0.5)',
zIndex: '2147483647',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
const dialog = document.createElement('div');
css(dialog, {
background: 'white',
borderRadius: '16px',
boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
minWidth: '400px',
maxWidth: '500px',
animation: 'kleapDialogIn 0.2s ease-out',
});
const header = document.createElement('div');
css(header, {
padding: '20px 24px',
borderBottom: '1px solid #eee',
fontSize: '16px',
fontWeight: '600',
color: '#333',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
});
header.textContent = title;
const content = document.createElement('div');
css(content, {
padding: '20px 24px',
});
const footer = document.createElement('div');
css(footer, {
padding: '16px 24px',
borderTop: '1px solid #eee',
display: 'flex',
gap: '8px',
justifyContent: 'flex-end',
});
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
css(cancelBtn, {
padding: '8px 16px',
border: '1px solid #ddd',
borderRadius: '8px',
background: 'white',
color: '#666',
fontSize: '14px',
cursor: 'pointer',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
});
const saveBtn = document.createElement('button');
saveBtn.textContent = 'Apply';
css(saveBtn, {
padding: '8px 20px',
border: 'none',
borderRadius: '8px',
background: '#ff0055',
color: 'white',
fontSize: '14px',
cursor: 'pointer',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
});
footer.appendChild(cancelBtn);
footer.appendChild(saveBtn);
dialog.appendChild(header);
dialog.appendChild(content);
dialog.appendChild(footer);
overlay.appendChild(dialog);
document.body.appendChild(overlay);
const close = () => overlay.remove();
cancelBtn.onclick = close;
overlay.onclick = (e) => {
if (e.target === overlay) close();
};
const result = {
content,
saveBtn,
close,
onSave: null,
};
saveBtn.onclick = () => {
if (result.onSave) result.onSave();
close();
};
// Handle keyboard
document.addEventListener('keydown', function handleKey(e) {
if (e.key === 'Escape') {
close();
document.removeEventListener('keydown', handleKey);
}
});
return result;
}
function updateImage(img, newSrc) {
const originalSrc = img.src;
img.src = newSrc;
// Send the change to parent
window.parent.postMessage({
type: 'kleap-image-edited',
id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`,
oldSrc: originalSrc,
newSrc: newSrc,
alt: img.alt || '',
}, '*');
}
function enableInlineEdit(el) {
// Store original state
const originalText = el.textContent;
const originalHTML = el.innerHTML;
// Store ALL original styles
const originalStyles = {
outline: el.style.outline,
outlineOffset: el.style.outlineOffset,
boxShadow: el.style.boxShadow,
cursor: el.style.cursor,
contentEditable: el.contentEditable,
// Don't modify background, border, or any other styles!
};
// Make element editable
el.contentEditable = 'true';
el.focus();
// Add ONLY non-intrusive visual feedback
css(el, {
outline: '2px solid #ff0055',
outlineOffset: '3px',
cursor: 'text',
boxShadow: '0 0 0 4px rgba(255, 0, 85, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08)',
// DO NOT change background, border, borderRadius, or any other properties!
});
// Select all text
const range = document.createRange();
range.selectNodeContents(el);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
// Handle blur to save changes
const handleBlur = () => {
const newText = el.textContent;
if (newText !== originalText) {
// Show saving status on the border
css(el, {
outline: '2px solid #FFA500', // Orange for saving
boxShadow: '0 0 0 4px rgba(255, 165, 0, 0.2), 0 4px 12px rgba(0, 0, 0, 0.08)',
});
// Send the change to parent
// Generate ID and name if not present
const id = el.dataset.kleapId || `element-${Math.random().toString(36).substr(2, 9)}`;
let name = el.dataset.kleapName || el.tagName.toLowerCase();
if (!el.dataset.kleapName && el.className && typeof el.className === 'string') {
const classes = el.className.trim().split(' ').filter(c => c);
if (classes.length > 0) {
name = `${el.tagName.toLowerCase()}.${classes[0]}`;
}
}
window.parent.postMessage({
type: 'kleap-text-edited',
id: id,
name: name,
path: el.dataset.kleapPath || getElementPath(el),
oldText: originalText,
newText: newText,
tagName: el.tagName.toLowerCase()
}, '*');
// Show success after a moment
setTimeout(() => {
css(el, {
outline: '2px solid #00C851', // Green for success
boxShadow: '0 0 0 4px rgba(0, 200, 81, 0.2), 0 4px 12px rgba(0, 0, 0, 0.08)',
});
// Remove all styles after showing success
setTimeout(() => {
el.contentEditable = originalStyles.contentEditable || 'false';
css(el, {
outline: originalStyles.outline || '',
outlineOffset: originalStyles.outlineOffset || '',
boxShadow: originalStyles.boxShadow || '',
cursor: originalStyles.cursor || ''
});
}, 1000);
}, 500);
} else {
// Restore original HTML if no changes
el.innerHTML = originalHTML;
// Restore styles immediately if no changes
el.contentEditable = originalStyles.contentEditable || 'false';
css(el, {
outline: originalStyles.outline || '',
outlineOffset: originalStyles.outlineOffset || '',
boxShadow: originalStyles.boxShadow || '',
cursor: originalStyles.cursor || ''
});
}
el.removeEventListener('blur', handleBlur);
el.removeEventListener('keydown', handleKeydown);
};
// Handle escape key to cancel
const handleKeydown = (e) => {
if (e.key === 'Escape') {
e.preventDefault();
el.innerHTML = originalHTML;
el.blur();
}
};
el.addEventListener('blur', handleBlur);
el.addEventListener('keydown', handleKeydown);
}
function makeOverlay() {
overlay = document.createElement("div");
overlay.id = OVERLAY_ID;
css(overlay, {
position: "absolute",
border: "2px solid #ff0055",
background: "rgba(255,0,85,.05)",
pointerEvents: "none",
zIndex: "2147483647", // max
borderRadius: "8px",
boxShadow: "0 0 0 1px rgba(255,0,85,0.2), 0 4px 12px rgba(0,0,0,0.08)",
transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
});
label = document.createElement("div");
css(label, {
position: "absolute",
left: "50%",
top: "100%",
transform: "translateX(-50%) translateY(8px)",
background: "linear-gradient(135deg, #1a1a1a 0%, #0a0a0a 100%)",
color: "#fff",
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
fontSize: "13px",
fontWeight: "500",
lineHeight: "1.4",
padding: "0",
borderRadius: "12px",
boxShadow: "0 8px 32px rgba(0,0,0,0.24), 0 2px 8px rgba(0,0,0,0.16), inset 0 1px 0 rgba(255,255,255,0.06)",
border: "1px solid rgba(255,255,255,0.08)",
overflow: "hidden",
minWidth: "180px",
animation: "kleapFadeIn 0.2s ease-out",
pointerEvents: "auto", // Enable clicks on the label
});
overlay.appendChild(label);
document.body.appendChild(overlay);
// Add animation keyframes
const style = document.createElement("style");
style.textContent = `
@keyframes kleapFadeIn {
from {
opacity: 0;
transform: translateX(-50%) translateY(4px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(8px);
}
}
#${OVERLAY_ID} .kleap-option {
transition: all 0.15s ease;
}
#${OVERLAY_ID} .kleap-option:hover {
background: rgba(255,255,255,0.06);
}
#${OVERLAY_ID} .kleap-option:active {
transform: scale(0.98);
}
`;
document.head.appendChild(style);
}
function updateOverlay(el) {
if (!overlay) makeOverlay();
const rect = el.getBoundingClientRect();
css(overlay, {
top: `${rect.top + window.scrollY}px`,
left: `${rect.left + window.scrollX}px`,
width: `${rect.width}px`,
height: `${rect.height}px`,
display: "block",
});
// Clear previous contents
while (label.firstChild) {
label.removeChild(label.firstChild);
}
// Always show minimal info when hovering
const info = document.createElement("div");
css(info, {
padding: "8px 12px",
fontSize: "12px",
opacity: "0.9",
});
// Get a descriptive name for the element
let name = el.dataset.kleapName || el.tagName.toLowerCase();
// Try to get a better description
if (!el.dataset.kleapName) {
const tag = el.tagName.toLowerCase();
// Add class names if available
if (el.className && typeof el.className === 'string') {
const classes = el.className.trim().split(' ').filter(c => c && !c.startsWith('_'));
if (classes.length > 0) {
// Filter out utility classes
const meaningfulClass = classes.find(c =>
!c.match(/^(flex|grid|block|inline|absolute|relative|fixed|sticky|w-|h-|p-|m-|text-|bg-|border-|rounded-|shadow-|opacity-|z-)/)
);
name = meaningfulClass ? `${tag}.${meaningfulClass}` : `${tag}.${classes[0]}`;
}
} else if (el.id) {
name = `${tag}#${el.id}`;
} else if (el.getAttribute('aria-label')) {
name = `${tag}[${el.getAttribute('aria-label')}]`;
} else if (el.getAttribute('role')) {
name = `${tag}[role=${el.getAttribute('role')}]`;
}
}
let actionHint = "";
if (isTextElement(el)) {
actionHint = " • Click to edit text";
} else if (isImageElement(el)) {
actionHint = " • Click to change image";
}
info.textContent = name + actionHint;
label.appendChild(info);
}
/* ---------- event handlers -------------------------------------------- */
function onMouseMove(e) {
if (state.type !== "inspecting") return;
let el = e.target;
// Don't require data-kleap-id - any element can be selected
// Skip only truly non-selectable elements
const nonSelectableTags = ['SCRIPT', 'STYLE', 'META', 'LINK', 'HTML', 'BODY'];
while (el && nonSelectableTags.includes(el.tagName)) {
el = el.parentElement;
}
if (state.element === el) return;
state.element = el;
if (el) {
updateOverlay(el);
} else {
if (overlay) overlay.style.display = "none";
}
}
function onClick(e) {
if (state.type !== "inspecting" || !state.element) return;
e.preventDefault();
e.stopPropagation();
const el = state.element;
// If it's a text element, enable inline edit directly
if (isTextElement(el)) {
enableInlineEdit(el);
deactivate();
} else if (isImageElement(el)) {
// If it's an image, show image edit dialog
enableImageEdit(el);
deactivate();
} else {
// For non-text elements, send to parent for AI editing
// Generate better ID and name
let id = el.dataset.kleapId;
let name = el.dataset.kleapName;
let path = el.dataset.kleapPath;
// Generate ID if not present
if (!id) {
// Try to create a meaningful ID from element properties
const tag = el.tagName.toLowerCase();
const uniqueId = Math.random().toString(36).substr(2, 9);
if (el.id) {
id = `${tag}#${el.id}`;
} else if (el.className && typeof el.className === 'string') {
const firstClass = el.className.trim().split(' ')[0];
id = `${tag}.${firstClass}-${uniqueId}`;
} else {
id = `${tag}-${uniqueId}`;
}
}
// Generate name if not present
if (!name) {
const tag = el.tagName.toLowerCase();
if (el.className && typeof el.className === 'string') {
const classes = el.className.trim().split(' ').filter(c => c && !c.startsWith('_'));
const meaningfulClass = classes.find(c =>
!c.match(/^(flex|grid|block|inline|absolute|relative|fixed|sticky|w-|h-|p-|m-|text-|bg-|border-|rounded-|shadow-|opacity-|z-)/)
);
name = meaningfulClass ? `${tag}.${meaningfulClass}` : `${tag}.${classes[0] || ''}`;
} else {
name = tag;
}
}
// Generate path if not present
if (!path) {
path = getElementPath(el);
}
window.parent.postMessage(
{
type: "kleap-component-selected",
id: id,
name: name,
path: path,
tagName: el.tagName.toLowerCase(),
className: el.className || '',
textContent: el.textContent ? el.textContent.substring(0, 100) : '',
hasChildren: el.children.length > 0,
rect: {
width: el.offsetWidth,
height: el.offsetHeight
}
},
"*",
);
deactivate();
}
}
/* ---------- activation / deactivation --------------------------------- */
function activate() {
if (state.type === "inactive") {
window.addEventListener("mousemove", onMouseMove, true);
window.addEventListener("click", onClick, true);
}
state = { type: "inspecting", element: null };
if (overlay) {
overlay.style.display = "none";
}
}
function deactivate() {
if (state.type === "inactive") return;
window.removeEventListener("mousemove", onMouseMove, true);
window.removeEventListener("click", onClick, true);
if (overlay) {
overlay.remove();
overlay = null;
label = null;
}
state = { type: "inactive" };
}
/* ---------- message bridge -------------------------------------------- */
window.addEventListener("message", (e) => {
if (e.source !== window.parent) return;
if (e.data.type === "activate-kleap-component-selector") activate();
if (e.data.type === "deactivate-kleap-component-selector") deactivate();
});
function initializeComponentSelector() {
if (!document.body) {
console.error(
"Kleap component selector initialization failed: document.body not found.",
);
return;
}
setTimeout(() => {
if (document.body.querySelector("[data-kleap-id]")) {
window.parent.postMessage(
{
type: "kleap-component-selector-initialized",
},
"*",
);
console.debug("Kleap component selector initialized");
} else {
console.warn(
"Kleap component selector not initialized because no DOM elements were tagged",
);
}
}, 0);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initializeComponentSelector);
} else {
initializeComponentSelector();
}
})();