| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- /* eslint-disable n/no-unsupported-features/node-builtins */
- /* global document, window */
- /**
- * PROTOTYPE DEV MODE
- *
- * Developer/feedback mode that allows users to easily copy Object IDs to clipboard
- * for providing precise feedback on prototype elements.
- *
- * Features:
- * - Toggle dev mode with button or Ctrl+E
- * - Prototype works NORMALLY when dev mode is on
- * - Hold Shift + Click any element to copy its Object ID
- * - Visual highlights show what will be copied (green when Shift is held)
- * - Tooltip shows Object ID on hover
- * - Success feedback when copied
- *
- * Usage:
- * 1. Include this script in your prototype HTML
- * 2. Add the HTML toggle button and tooltip (see HTML template)
- * 3. Add the CSS styles (see CSS template)
- * 4. Call initDevMode() on page load
- *
- * How it works:
- * - Activate dev mode (Ctrl+E or click button)
- * - Hover over elements to see their Object IDs (gray outline)
- * - Hold Shift key (outline turns green)
- * - Click while holding Shift to copy Object ID
- * - Prototype works normally without Shift held
- * - **Shift is disabled when typing in form fields** (input, textarea, etc.)
- */
- // ============================================================================
- // DEV MODE STATE
- // ============================================================================
- let devModeActive = false;
- let shiftKeyPressed = false;
- let currentHighlightedElement = null;
- // ============================================================================
- // INITIALIZATION
- // ============================================================================
- function initDevMode() {
- const toggleButton = document.querySelector('#dev-mode-toggle');
- const tooltip = document.querySelector('#dev-mode-tooltip');
- if (!toggleButton || !tooltip) {
- console.warn('⚠️ Dev Mode: Toggle button or tooltip not found');
- return;
- }
- // Check if user agent supports clipboard API
- if (typeof navigator !== 'undefined' && navigator.clipboard) {
- // Clipboard API available
- } else {
- console.warn('⚠️ Clipboard API not supported in this browser');
- return;
- }
- setupKeyboardShortcuts();
- setupToggleButton(toggleButton, tooltip);
- setupHoverHighlight(tooltip);
- setupClickCopy();
- console.log('%c💡 Dev Mode available: Press Ctrl+E or click the Dev Mode button', 'color: #0066CC; font-weight: bold;');
- }
- // ============================================================================
- // KEYBOARD SHORTCUTS
- // ============================================================================
- function setupKeyboardShortcuts() {
- // Track Shift key for container selection
- document.addEventListener('keydown', (e) => {
- if (e.key === 'Shift') {
- // Don't activate if user is typing in a form field
- if (isTypingInField()) {
- return;
- }
- shiftKeyPressed = true;
- document.body.classList.add('shift-held');
- if (devModeActive) {
- console.log('%c⬆️ Shift held: Click any element to copy its Object ID', 'color: #10B981; font-weight: bold;');
- }
- }
- // Ctrl+E toggle
- if (e.ctrlKey && e.key === 'e') {
- e.preventDefault();
- document.querySelector('#dev-mode-toggle')?.click();
- }
- });
- document.addEventListener('keyup', (e) => {
- if (e.key === 'Shift') {
- shiftKeyPressed = false;
- document.body.classList.remove('shift-held');
- if (devModeActive) {
- console.log('%c⬇️ Shift released: Prototype works normally (hold Shift to copy)', 'color: #6b7280;');
- }
- }
- });
- }
- // ============================================================================
- // TOGGLE BUTTON
- // ============================================================================
- function setupToggleButton(toggleButton, tooltip) {
- toggleButton.addEventListener('click', function (e) {
- e.stopPropagation();
- if (typeof globalThis !== 'undefined') {
- globalThis.devModeActive = true;
- } else if (globalThis.window !== undefined) {
- globalThis.devModeActive = true;
- }
- devModeActive = !devModeActive;
- // Update UI
- document.body.classList.toggle('dev-mode-active', devModeActive);
- toggleButton.classList.toggle('active', devModeActive);
- const statusText = toggleButton.querySelector('span');
- if (statusText) {
- statusText.textContent = devModeActive ? 'Dev Mode: ON' : 'Dev Mode: OFF';
- }
- // Log status
- console.log(`🔧 Dev Mode: ${devModeActive ? 'ACTIVATED' : 'DEACTIVATED'}`);
- if (devModeActive) {
- console.log('%c🔧 DEV MODE ACTIVE', 'color: #0066CC; font-size: 16px; font-weight: bold;');
- console.log('%c⚠️ Hold SHIFT + Click any element to copy its Object ID', 'color: #FFB800; font-size: 14px; font-weight: bold;');
- console.log('%cWithout Shift: Prototype works normally', 'color: #6b7280;');
- console.log('%cPress Ctrl+E to toggle Dev Mode', 'color: #6b7280;');
- } else {
- tooltip.style.display = 'none';
- if (currentHighlightedElement) {
- clearHighlight();
- }
- }
- });
- }
- // ============================================================================
- // HOVER HIGHLIGHT
- // ============================================================================
- function setupHoverHighlight(tooltip) {
- // Show tooltip and highlight on hover
- document.addEventListener('mouseover', function (e) {
- if (!devModeActive) return;
- // Don't highlight if user is typing in a field
- if (isTypingInField()) {
- tooltip.style.display = 'none';
- clearHighlight();
- return;
- }
- clearHighlight();
- let element = findElementWithId(e.target);
- if (!element || !element.id || isSystemElement(element.id)) {
- tooltip.style.display = 'none';
- return;
- }
- // Highlight element
- highlightElement(element, shiftKeyPressed);
- currentHighlightedElement = element;
- // Show tooltip
- const prefix = shiftKeyPressed ? '✓ Click to Copy: ' : '⬆️ Hold Shift + Click: ';
- tooltip.textContent = prefix + element.id;
- tooltip.style.display = 'block';
- tooltip.style.background = shiftKeyPressed ? '#10B981' : '#6b7280';
- tooltip.style.color = '#fff';
- updateTooltipPosition(e, tooltip);
- });
- // Update tooltip position on mouse move
- document.addEventListener('mousemove', function (e) {
- if (devModeActive && tooltip.style.display === 'block') {
- updateTooltipPosition(e, tooltip);
- }
- });
- // Clear highlight on mouse out
- document.addEventListener('mouseout', function (e) {
- if (!devModeActive) return;
- if (e.target.id) {
- tooltip.style.display = 'none';
- clearHighlight();
- }
- });
- }
- // ============================================================================
- // CLICK TO COPY
- // ============================================================================
- function setupClickCopy() {
- // Use capture phase to intercept clicks with Shift
- document.addEventListener(
- 'click',
- function (e) {
- if (!devModeActive) return;
- // Allow toggle button to work normally
- if (isToggleButton(e.target)) return;
- // ONLY copy if Shift is held
- if (!shiftKeyPressed) {
- // Let prototype work normally without Shift
- return;
- }
- // Don't intercept if user is clicking in/around a form field
- if (isTypingInField() || isFormElement(e.target)) {
- return;
- }
- // Shift is held and not in a form field - intercept and copy
- e.preventDefault();
- e.stopPropagation();
- e.stopImmediatePropagation();
- let element = findElementWithId(e.target);
- if (!element || !element.id || isSystemElement(element.id)) {
- console.log('❌ No Object ID found');
- return false;
- }
- // Copy to clipboard
- const objectId = element.id;
- copyToClipboard(objectId);
- // Show feedback
- showCopyFeedback(element, objectId);
- return false;
- },
- true,
- ); // Capture phase
- }
- // ============================================================================
- // HELPER FUNCTIONS
- // ============================================================================
- function findElementWithId(element) {
- let current = element;
- let attempts = 0;
- while (current && !current.id && attempts < 10) {
- current = current.parentElement;
- attempts++;
- }
- return current;
- }
- function isSystemElement(id) {
- const systemIds = ['app', 'dev-mode-toggle', 'dev-mode-tooltip'];
- return systemIds.includes(id);
- }
- function isToggleButton(element) {
- return element.id === 'dev-mode-toggle' || element.closest('#dev-mode-toggle') || element.classList.contains('dev-mode-toggle');
- }
- function isTypingInField() {
- const activeElement = document.activeElement;
- if (!activeElement) return false;
- const tagName = activeElement.tagName.toLowerCase();
- const isEditable = activeElement.isContentEditable;
- // Check if user is currently typing in a form field
- return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || isEditable;
- }
- function isFormElement(element) {
- if (!element) return false;
- const tagName = element.tagName.toLowerCase();
- const isEditable = element.isContentEditable;
- // Check if the clicked element is a form element
- return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || isEditable;
- }
- function highlightElement(element, isShiftHeld) {
- const color = isShiftHeld ? '#10B981' : '#6b7280';
- const width = isShiftHeld ? '3px' : '2px';
- const offset = isShiftHeld ? '3px' : '2px';
- const shadowSpread = isShiftHeld ? '5px' : '2px';
- const shadowOpacity = isShiftHeld ? '0.4' : '0.2';
- element.style.outline = `${width} solid ${color}`;
- element.style.outlineOffset = offset;
- element.style.boxShadow = `0 0 0 ${shadowSpread} rgba(${isShiftHeld ? '16, 185, 129' : '107, 114, 128'}, ${shadowOpacity})`;
- }
- function clearHighlight() {
- if (currentHighlightedElement) {
- currentHighlightedElement.style.outline = '';
- currentHighlightedElement.style.boxShadow = '';
- currentHighlightedElement = null;
- }
- }
- function updateTooltipPosition(e, tooltip) {
- const offset = 15;
- let x = e.clientX + offset;
- let y = e.clientY + offset;
- // Keep tooltip on screen
- const rect = tooltip.getBoundingClientRect();
- if (x + rect.width > window.innerWidth) {
- x = e.clientX - rect.width - offset;
- }
- if (y + rect.height > window.innerHeight) {
- y = e.clientY - rect.height - offset;
- }
- tooltip.style.left = x + 'px';
- tooltip.style.top = y + 'px';
- }
- function copyToClipboard(text) {
- if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
- navigator.clipboard
- .writeText(text)
- .then(() => {
- console.log(`📋 Copied to clipboard: ${text}`);
- })
- .catch((error) => {
- console.error('Dev Mode error:', error);
- fallbackCopy(text);
- });
- } else {
- fallbackCopy(text);
- }
- }
- function fallbackCopy(text) {
- const textarea = document.createElement('textarea');
- textarea.value = text;
- textarea.style.position = 'fixed';
- textarea.style.left = '-999999px';
- document.body.append(textarea);
- textarea.focus();
- textarea.select();
- try {
- document.execCommand('copy');
- console.log(`📋 Copied (fallback): ${text}`);
- } catch (error) {
- console.error('Dev Mode error:', error);
- }
- textarea.remove();
- }
- function showCopyFeedback(element, objectId) {
- // Create feedback overlay
- const feedback = document.createElement('div');
- feedback.textContent = '✓ Copied: ' + objectId;
- feedback.style.cssText = `
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: #10B981;
- color: #fff;
- padding: 16px 32px;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 600;
- z-index: 100000;
- box-shadow: 0 10px 25px rgba(0,0,0,0.3);
- animation: fadeInOut 1.5s ease-in-out;
- pointer-events: none;
- `;
- document.body.append(feedback);
- setTimeout(() => {
- feedback.remove();
- }, 1500);
- // Flash element
- const originalOutline = element.style.outline;
- element.style.outline = '3px solid #10B981';
- setTimeout(() => {
- element.style.outline = originalOutline;
- }, 300);
- }
- // Add CSS animation
- const style = document.createElement('style');
- style.textContent = `
- @keyframes fadeInOut {
- 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
- 20% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
- 80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
- 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
- }
- `;
- document.head.append(style);
- // ============================================================================
- // EXPORT
- // ============================================================================
- // Make available globally
- globalThis.initDevMode = initDevMode;
- // Export for use in other scripts
- if (typeof globalThis !== 'undefined' && globalThis.exports) {
- globalThis.exports = { initDevMode };
- }
|