@@ -396,153 +396,181 @@ document.addEventListener('alpine:init', () => {
396396 * Show info badge with element details
397397 */
398398 showInfoBadge ( element ) {
399- // Get element data
400- const template = element . getAttribute ( 'data-mageforge-template' ) || '' ;
401- const blockClass = element . getAttribute ( 'data-mageforge-block' ) || '' ;
402- const module = element . getAttribute ( 'data-mageforge-module' ) || '' ;
403- const viewModel = element . getAttribute ( 'data-mageforge-viewmodel' ) || '' ;
404- const parentBlock = element . getAttribute ( 'data-mageforge-parent' ) || '' ;
405- const blockAlias = element . getAttribute ( 'data-mageforge-alias' ) || '' ;
406- const isOverride = element . getAttribute ( 'data-mageforge-override' ) === '1' ;
407-
408- // Get target element for dimensions (handle display:contents)
399+ const rect = this . getElementRect ( element ) ;
400+ const elementId = element . getAttribute ( 'data-mageforge-id' ) ;
401+
402+ // Only rebuild badge content if it's a different element
403+ if ( this . infoBadge . dataset . currentElement !== elementId ) {
404+ this . buildBadgeContent ( element , rect ) ;
405+ this . infoBadge . dataset . currentElement = elementId ;
406+ }
407+
408+ this . positionBadge ( rect ) ;
409+ } ,
410+
411+ /**
412+ * Get element rectangle (handles display:contents)
413+ */
414+ getElementRect ( element ) {
409415 let targetElement = element ;
410416 const style = window . getComputedStyle ( element ) ;
411417 if ( style . display === 'contents' && element . children . length > 0 ) {
412418 targetElement = element . children [ 0 ] ;
413419 }
414- const rect = targetElement . getBoundingClientRect ( ) ;
420+ return targetElement . getBoundingClientRect ( ) ;
421+ } ,
415422
416- // Store element ID to detect if we're updating the same element
417- const elementId = element . getAttribute ( 'data-mageforge-id' ) ;
423+ /**
424+ * Build badge content with element metadata
425+ */
426+ buildBadgeContent ( element , rect ) {
427+ const data = {
428+ template : element . getAttribute ( 'data-mageforge-template' ) || '' ,
429+ blockClass : element . getAttribute ( 'data-mageforge-block' ) || '' ,
430+ module : element . getAttribute ( 'data-mageforge-module' ) || '' ,
431+ viewModel : element . getAttribute ( 'data-mageforge-viewmodel' ) || '' ,
432+ parentBlock : element . getAttribute ( 'data-mageforge-parent' ) || '' ,
433+ blockAlias : element . getAttribute ( 'data-mageforge-alias' ) || '' ,
434+ isOverride : element . getAttribute ( 'data-mageforge-override' ) === '1'
435+ } ;
418436
419- // Only rebuild badge content if it's a different element
420- if ( this . infoBadge . dataset . currentElement !== elementId ) {
421- // Clear badge
422- this . infoBadge . innerHTML = '' ;
437+ // Clear badge
438+ this . infoBadge . innerHTML = '' ;
423439
424- // Template section with override indicator
425- let templateDisplay = template ;
426- if ( isOverride ) {
427- templateDisplay = '🔧 ' + template ;
428- }
429- const templateDiv = this . createInfoSection ( '📄 Template' , templateDisplay , '#60a5fa' ) ;
430- this . infoBadge . appendChild ( templateDiv ) ;
440+ // Template section with override indicator
441+ const templateDisplay = data . isOverride ? '🔧 ' + data . template : data . template ;
442+ this . infoBadge . appendChild ( this . createInfoSection ( '📄 Template' , templateDisplay , '#60a5fa' ) ) ;
431443
432- // Block section
433- const blockDiv = this . createInfoSection ( '📦 Block' , blockClass , '#a78bfa' ) ;
434- this . infoBadge . appendChild ( blockDiv ) ;
444+ // Block section
445+ this . infoBadge . appendChild ( this . createInfoSection ( '📦 Block' , data . blockClass , '#a78bfa' ) ) ;
435446
436- // Block Alias section (if exists)
437- if ( blockAlias ) {
438- const aliasDiv = this . createInfoSection ( '🏷️ Block Name' , blockAlias , '#34d399' ) ;
439- this . infoBadge . appendChild ( aliasDiv ) ;
440- }
447+ // Optional sections
448+ if ( data . blockAlias ) {
449+ this . infoBadge . appendChild ( this . createInfoSection ( '🏷️ Block Name' , data . blockAlias , '#34d399' ) ) ;
450+ }
451+ if ( data . parentBlock ) {
452+ this . infoBadge . appendChild ( this . createInfoSection ( '⬆️ Parent Block' , data . parentBlock , '#fb923c' ) ) ;
453+ }
454+ if ( data . viewModel ) {
455+ this . infoBadge . appendChild ( this . createInfoSection ( '⚡ ViewModel' , data . viewModel , '#22d3ee' ) ) ;
456+ }
441457
442- // Parent Block section (if exists)
443- if ( parentBlock ) {
444- const parentDiv = this . createInfoSection ( '⬆️ Parent Block' , parentBlock , '#fb923c' ) ;
445- this . infoBadge . appendChild ( parentDiv ) ;
446- }
458+ // Module section
459+ this . infoBadge . appendChild ( this . createInfoSection ( '📍 Module' , data . module , '#fbbf24' ) ) ;
447460
448- // ViewModel section (if exists)
449- if ( viewModel ) {
450- const viewModelDiv = this . createInfoSection ( '⚡ ViewModel' , viewModel , '#22d3ee' ) ;
451- this . infoBadge . appendChild ( viewModelDiv ) ;
452- }
461+ // Dimensions section
462+ const dimensionsDiv = this . createInfoSection ( '📐 Dimensions' , `${ Math . round ( rect . width ) } × ${ Math . round ( rect . height ) } px` , '#6b7280' ) ;
463+ dimensionsDiv . style . borderTop = '1px solid rgba(148, 163, 184, 0.12)' ;
464+ dimensionsDiv . style . paddingTop = '12px' ;
465+ dimensionsDiv . style . marginTop = '12px' ;
466+ this . infoBadge . appendChild ( dimensionsDiv ) ;
453467
454- // Module section
455- const moduleDiv = this . createInfoSection ( '📍 Module' , module , '#fbbf24' ) ;
456- this . infoBadge . appendChild ( moduleDiv ) ;
457-
458- // Dimensions
459- const dimensionsDiv = this . createInfoSection ( '📐 Dimensions' , `${ Math . round ( rect . width ) } × ${ Math . round ( rect . height ) } px` , '#6b7280' ) ;
460- dimensionsDiv . style . borderTop = '1px solid rgba(148, 163, 184, 0.12)' ;
461- dimensionsDiv . style . paddingTop = '12px' ;
462- dimensionsDiv . style . marginTop = '12px' ;
463- this . infoBadge . appendChild ( dimensionsDiv ) ;
464-
465- // Branding footer
466- const brandingDiv = document . createElement ( 'div' ) ;
467- brandingDiv . style . cssText = `
468- margin-top: 16px;
469- padding-top: 12px;
470- border-top: 1px solid rgba(148, 163, 184, 0.12);
471- text-align: center;
472- font-size: 10px;
473- color: #94a3b8;
474- font-weight: 500;
475- letter-spacing: 0.025em;
476- ` ;
477- brandingDiv . innerHTML = 'Created with <span style="color: #ff6b6b; font-size: 12px;">🧡</span> by <span style="color: #60a5fa; font-weight: 600;">MageForge</span>' ;
478- this . infoBadge . appendChild ( brandingDiv ) ;
479-
480- // Store current element ID
481- this . infoBadge . dataset . currentElement = elementId ;
482- }
468+ // Branding footer
469+ this . infoBadge . appendChild ( this . createBrandingFooter ( ) ) ;
470+ } ,
483471
472+ /**
473+ * Create branding footer
474+ */
475+ createBrandingFooter ( ) {
476+ const brandingDiv = document . createElement ( 'div' ) ;
477+ brandingDiv . style . cssText = `
478+ margin-top: 16px;
479+ padding-top: 12px;
480+ border-top: 1px solid rgba(148, 163, 184, 0.12);
481+ text-align: center;
482+ font-size: 10px;
483+ color: #94a3b8;
484+ font-weight: 500;
485+ letter-spacing: 0.025em;
486+ ` ;
487+ brandingDiv . innerHTML = 'Created with <span style="color: #ff6b6b; font-size: 12px;">🧡</span> by <span style="color: #60a5fa; font-weight: 600;">MageForge</span>' ;
488+ return brandingDiv ;
489+ } ,
490+
491+ /**
492+ * Position badge relative to element
493+ */
494+ positionBadge ( rect ) {
484495 this . infoBadge . style . display = 'block' ;
485496
486- // Get badge dimensions after display:block
487497 const badgeRect = this . infoBadge . getBoundingClientRect ( ) ;
488-
489- // Position badge directly below element (no gap)
490498 const badgeOffset = 0 ;
499+
500+ // Calculate initial position
491501 let x = rect . left + window . scrollX ;
492502 let y = rect . bottom + window . scrollY + badgeOffset ;
493503
494- // Ensure valid coordinates (prevent NaN or negative values)
504+ // Validate coordinates
495505 if ( ! isFinite ( x ) || ! isFinite ( y ) || x < 0 || y < 0 ) {
496- // Fallback to safe position
497506 x = 10 ;
498507 y = 10 ;
499508 }
500509
501- // Keep badge on screen horizontally
502- if ( x + badgeRect . width > window . innerWidth + window . scrollX ) {
503- x = window . innerWidth + window . scrollX - badgeRect . width - 10 ;
504- }
505- if ( x < window . scrollX + 10 ) {
506- x = window . scrollX + 10 ;
507- }
510+ // Constrain horizontally
511+ x = this . constrainHorizontally ( x , badgeRect . width ) ;
508512
509- // Keep badge on screen vertically - if no space below, show above element
510- if ( y + badgeRect . height > window . innerHeight + window . scrollY ) {
511- // Show above element instead
513+ // Check vertical space and adjust if needed
514+ const showAbove = this . shouldShowAbove ( y , badgeRect . height ) ;
515+ if ( showAbove ) {
512516 y = rect . top + window . scrollY - badgeRect . height - badgeOffset ;
517+ if ( y < window . scrollY + 10 ) {
518+ y = window . scrollY + 10 ;
519+ }
520+ }
513521
514- // Update border radius - no bottom corners when pinned above
515- this . infoBadge . style . borderRadius = '12px 12px 0 0' ;
522+ // Update badge styling based on position
523+ this . updateBadgePlacement ( showAbove ) ;
524+
525+ // Apply position
526+ this . infoBadge . style . left = `${ x } px` ;
527+ this . infoBadge . style . top = `${ y } px` ;
528+ } ,
516529
517- // Update arrow position and direction
518- const arrow = this . infoBadge . querySelector ( '.mageforge-inspector-arrow' ) ;
530+ /**
531+ * Constrain x position horizontally within viewport
532+ */
533+ constrainHorizontally ( x , badgeWidth ) {
534+ const maxX = window . innerWidth + window . scrollX - badgeWidth - 10 ;
535+ const minX = window . scrollX + 10 ;
536+
537+ if ( x > maxX ) return maxX ;
538+ if ( x < minX ) return minX ;
539+ return x ;
540+ } ,
541+
542+ /**
543+ * Check if badge should be shown above element
544+ */
545+ shouldShowAbove ( y , badgeHeight ) {
546+ return y + badgeHeight > window . innerHeight + window . scrollY ;
547+ } ,
548+
549+ /**
550+ * Update badge styling based on placement (above/below)
551+ */
552+ updateBadgePlacement ( showAbove ) {
553+ const arrow = this . infoBadge . querySelector ( '.mageforge-inspector-arrow' ) ;
554+
555+ if ( showAbove ) {
556+ // Badge above element
557+ this . infoBadge . style . borderRadius = '12px 12px 0 0' ;
519558 if ( arrow ) {
520559 arrow . style . top = 'auto' ;
521560 arrow . style . bottom = '-8px' ;
522561 arrow . style . borderBottom = 'none' ;
523562 arrow . style . borderTop = '8px solid rgba(15, 23, 42, 0.98)' ;
524563 }
525-
526- // If also no space above, keep it within viewport
527- if ( y < window . scrollY + 10 ) {
528- y = window . scrollY + 10 ;
529- }
530564 } else {
531- // Badge is below element - no top corners
565+ // Badge below element
532566 this . infoBadge . style . borderRadius = '0 0 12px 12px' ;
533-
534- // Reset arrow position and direction for bottom placement
535- const arrow = this . infoBadge . querySelector ( '.mageforge-inspector-arrow' ) ;
536567 if ( arrow ) {
537568 arrow . style . top = '-8px' ;
538569 arrow . style . bottom = 'auto' ;
539570 arrow . style . borderTop = 'none' ;
540571 arrow . style . borderBottom = '8px solid rgba(15, 23, 42, 0.98)' ;
541572 }
542573 }
543-
544- this . infoBadge . style . left = `${ x } px` ;
545- this . infoBadge . style . top = `${ y } px` ;
546574 } ,
547575
548576 /**
0 commit comments