From ceed261516bfeae4caff8e1fffeea41443350751 Mon Sep 17 00:00:00 2001 From: kleap-admin Date: Thu, 15 Jan 2026 13:40:18 +0000 Subject: [PATCH] Update public/_kleap/kleap-component-selector-client.js --- .../_kleap/kleap-component-selector-client.js | 963 ++++++++++++++++++ 1 file changed, 963 insertions(+) create mode 100644 public/_kleap/kleap-component-selector-client.js diff --git a/public/_kleap/kleap-component-selector-client.js b/public/_kleap/kleap-component-selector-client.js new file mode 100644 index 0000000..2198c42 --- /dev/null +++ b/public/_kleap/kleap-component-selector-client.js @@ -0,0 +1,963 @@ +(() => { + 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 = ` +
${model.name.split(' ')[0]}
+
${model.name.substring(model.name.indexOf(' ') + 1)}
+
${model.desc}
+ `; + + 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 = ` +
+
+
Generating with ${models.find(m => m.id === selectedModel)?.name || 'AI'}...
+
"${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"
+
This may take 10-30 seconds
+
+ + `; + 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(); + } +})();