1(function($) { 2 /** 3 * v4Accordion 4 * Supports accessible, multiple, and nested accordions matching the WP block markup. 5 * 6 * @param {object} $inner The inner wrapper jQuery element (.wp-block-visual4-accordion-inner). 7 * @param {object} options Configuration options. 8 */ 9 function initializeBlock($inner, options = {}) { 10 if ($inner.hasClass('is-initialized')) return; 11 $inner.addClass('is-initialized'); 12 13 // Merge settings 14 const settings = { 15 allowMultipleOpen: false, // one open per level by default 16 targetClass: '.wp-block-visual4-accordion-inner', 17 openClass: 'is-open-on-load', 18 closedClass: 'is-closed', 19 ...options 20 }; 21 22 // Locate title and content within this inner wrapper 23 const $title = $inner.children('.v4-accordion-title'); 24 const $content = $inner.children('.v4-accordion-content'); 25 26 // Ensure IDs for ARIA 27 const innerId = $inner.attr('id') || `v4-accordion-${Math.random().toString(36).substr(2,9)}`; 28 $inner.attr('id', innerId); 29 $title.attr({ 30 'role': 'button', 31 'tabindex': '0', 32 'aria-expanded': 'false', 33 'aria-controls': `${innerId}-content` 34 }); 35 $content.attr({ 36 'id': `${innerId}-content`, 37 'role': 'region', 38 'aria-labelledby': $title.attr('id') || innerId 39 }); 40 41 // Initial state: open if not closed or flagged open on load 42 const $block = $inner.closest('.v4-accordion'); 43 const shouldOpen = 44 $block.hasClass(settings.openClass) || // outer block requests open-on-load 45 !$inner.hasClass(settings.closedClass); // inner missing closed class 46 47 if (shouldOpen) { 48 $title.attr('aria-expanded', 'true'); 49 $inner.addClass('is-open').removeClass(settings.closedClass); 50 $content.stop().slideDown(0); 51 } else { 52 $inner.removeClass('is-open').addClass(settings.closedClass); 53 $content.stop().slideUp(0); 54 } 55 56 // Toggle function for this level 57 function toggle($current) { 58 const $curTitle = $current.children('.v4-accordion-title'); 59 const $curContent = $current.children('.v4-accordion-content'); 60 const isOpen = $current.hasClass('is-open'); 61 62 // Scope sibling closure to same-level .v4-accordion blocks 63 const $currentBlock = $current.closest('.v4-accordion'); 64 const $siblings = settings.allowMultipleOpen 65 ? $() // no auto-close 66 : $currentBlock.siblings('.v4-accordion').children(settings.targetClass); 67 68 if (isOpen) { 69 // Close current 70 $curTitle.attr('aria-expanded', 'false'); 71 $curContent.stop().slideUp(); 72 $current.removeClass('is-open').addClass(settings.closedClass); 73 } else { 74 // Close siblings first 75 $siblings.each(function() { 76 const $sib = $(this); 77 $sib.children('.v4-accordion-title').attr('aria-expanded', 'false'); 78 $sib.children('.v4-accordion-content').stop().slideUp(); 79 $sib.removeClass('is-open').addClass(settings.closedClass); 80 }); 81 // Open current 82 $curTitle.attr('aria-expanded', 'true'); 83 $curContent.stop().slideDown(); 84 $current.addClass('is-open').removeClass(settings.closedClass); 85 } 86 } 87 88 // Click handler (stop propagation so parent doesn't toggle) 89 $title.on('click', function(e) { 90 e.stopPropagation(); 91 toggle($inner); 92 }); 93 94 // Keyboard navigation: cycle through same-level accordion titles 95 $title.on('keydown', function(e) { 96 const $currentBlock = $inner.closest('.v4-accordion'); 97 const $innersAtLevel = $currentBlock.parent().children('.v4-accordion').children(settings.targetClass); 98 const idx = $innersAtLevel.index($inner); 99 let $targetInner; 100 101 switch (e.key) { 102 case 'Enter': 103 case ' ': 104 e.preventDefault(); e.stopPropagation(); toggle($inner); 105 break; 106 case 'ArrowDown': 107 e.preventDefault(); 108 $targetInner = $innersAtLevel.eq((idx + 1) % $innersAtLevel.length); 109 $targetInner.children('.v4-accordion-title').focus(); 110 break; 111 case 'ArrowUp': 112 e.preventDefault(); 113 $targetInner = $innersAtLevel.eq(idx - 1 >
113= 0 ? idx - 1 : $innersAtLevel.length - 1); 114 $targetInner.children('.v4-accordion-title').focus(); 115 break; 116 case 'Home': 117 e.preventDefault(); $innersAtLevel.first().children('.v4-accordion-title').focus(); 118 break; 119 case 'End': 120 e.preventDefault(); $innersAtLevel.last().children('.v4-accordion-title').focus(); 121 break; 122 } 123 }); 124 125 // Recursively init nested accordions inside this content 126 $content.find(settings.targetClass).each(function() { 127 initializeBlock($(this), settings); 128 }); 129 } 130 131 // Global initializer 132 window.v4InitAccordions = function(options = {}) { 133 const defaults = { allowMultipleOpen: false, targetClass: '.wp-block-visual4-accordion-inner' }; 134 const cfg = { ...defaults, ...options }; 135 $(cfg.targetClass).each(function() { 136 initializeBlock($(this), cfg); 137 }); 138 }; 139 140 // Run on DOM ready 141 $(document).ready(function() { 142 window.v4InitAccordions(); 143 }); 144})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.