|
1 | 1 | /** |
2 | | - * Accordion Block |
3 | | - * Accessible accordion component with keyboard navigation |
| 2 | + * accordion Block |
| 3 | + * Generated by NxPlatform Migration Tool |
4 | 4 | */ |
5 | 5 |
|
6 | | -let accordionIdCounter = 0; |
7 | | - |
8 | | -function generateId() { |
9 | | - accordionIdCounter += 1; |
10 | | - return `accordion-panel-${accordionIdCounter}`; |
11 | | -} |
12 | | - |
13 | | -function toggleAccordion(header, content, children, forceState = null) { |
14 | | - const isActive = forceState !== null ? !forceState : header.classList.contains('active'); |
15 | | - |
16 | | - // Close all panels |
17 | | - children.forEach((child) => { |
18 | | - child.classList.remove('active'); |
19 | | - if (child.hasAttribute('aria-expanded')) { |
20 | | - child.setAttribute('aria-expanded', 'false'); |
21 | | - } |
22 | | - }); |
23 | | - |
24 | | - // Open clicked panel if it wasn't active |
25 | | - if (!isActive) { |
26 | | - header.classList.add('active'); |
27 | | - header.setAttribute('aria-expanded', 'true'); |
28 | | - content.classList.add('active'); |
29 | | - } |
30 | | -} |
31 | | - |
32 | | -function handleKeydown(e, header, content, children, headers) { |
33 | | - const currentIndex = headers.indexOf(header); |
34 | | - |
35 | | - switch (e.key) { |
36 | | - case 'Enter': |
37 | | - case ' ': |
38 | | - e.preventDefault(); |
39 | | - toggleAccordion(header, content, children); |
40 | | - break; |
41 | | - case 'ArrowDown': |
42 | | - e.preventDefault(); |
43 | | - if (currentIndex < headers.length - 1) { |
44 | | - headers[currentIndex + 1].focus(); |
45 | | - } |
46 | | - break; |
47 | | - case 'ArrowUp': |
48 | | - e.preventDefault(); |
49 | | - if (currentIndex > 0) { |
50 | | - headers[currentIndex - 1].focus(); |
51 | | - } |
52 | | - break; |
53 | | - case 'Home': |
54 | | - e.preventDefault(); |
55 | | - headers[0].focus(); |
56 | | - break; |
57 | | - case 'End': |
58 | | - e.preventDefault(); |
59 | | - headers[headers.length - 1].focus(); |
60 | | - break; |
61 | | - default: |
62 | | - break; |
63 | | - } |
64 | | -} |
65 | | - |
66 | 6 | export default function decorate(block) { |
67 | | - const container = block.querySelector(':scope > div'); |
68 | | - if (!container) return; |
| 7 | + // Get block configuration |
| 8 | + const config = readBlockConfig(block); |
69 | 9 |
|
70 | | - const children = Array.from(container.children); |
71 | | - const headers = []; |
| 10 | + // Process rows and cells |
| 11 | + const rows = [...block.children]; |
72 | 12 |
|
73 | | - // Set up accordion role |
74 | | - block.setAttribute('role', 'presentation'); |
| 13 | + rows.forEach((row, index) => { |
| 14 | + const cells = [...row.children]; |
75 | 15 |
|
76 | | - for (let i = 0; i < children.length; i += 2) { |
77 | | - const header = children[i]; |
78 | | - const content = children[i + 1]; |
| 16 | + cells.forEach((cell, cellIndex) => { |
| 17 | + // Add semantic classes based on field mapping |
| 18 | + const fieldName = getFieldName(index, cellIndex); |
| 19 | + if (fieldName) { |
| 20 | + cell.classList.add(fieldName); |
| 21 | + } |
79 | 22 |
|
80 | | - if (header && content) { |
81 | | - const panelId = generateId(); |
| 23 | + // Process images |
| 24 | + processImages(cell); |
82 | 25 |
|
83 | | - // Set up header accessibility attributes |
84 | | - header.setAttribute('role', 'button'); |
85 | | - header.setAttribute('tabindex', '0'); |
86 | | - header.setAttribute('aria-expanded', 'false'); |
87 | | - header.setAttribute('aria-controls', panelId); |
88 | | - header.classList.add('accordion-header'); |
| 26 | + // Process links |
| 27 | + processLinks(cell); |
| 28 | + }); |
| 29 | + }); |
89 | 30 |
|
90 | | - // Set up content accessibility attributes |
91 | | - content.setAttribute('id', panelId); |
92 | | - content.setAttribute('role', 'region'); |
93 | | - content.setAttribute('aria-labelledby', `${panelId}-header`); |
94 | | - header.setAttribute('id', `${panelId}-header`); |
95 | | - content.classList.add('accordion-content'); |
| 31 | + // Add block-specific initialization |
| 32 | + initializeBlock(block, config); |
| 33 | +} |
96 | 34 |
|
97 | | - headers.push(header); |
| 35 | +/** |
| 36 | + * Read block configuration from data attributes |
| 37 | + */ |
| 38 | +function readBlockConfig(block) { |
| 39 | + const config = {}; |
| 40 | + [...block.querySelectorAll(':scope > div > div')].forEach((cell) => { |
| 41 | + const key = cell.textContent?.trim().toLowerCase().replace(/\s+/g, '-'); |
| 42 | + const value = cell.nextElementSibling?.textContent?.trim(); |
| 43 | + if (key && value) { |
| 44 | + config[key] = value; |
| 45 | + } |
| 46 | + }); |
| 47 | + return config; |
| 48 | +} |
98 | 49 |
|
99 | | - // Click handler |
100 | | - header.addEventListener('click', () => { |
101 | | - toggleAccordion(header, content, children); |
102 | | - }); |
| 50 | +/** |
| 51 | + * Map row/cell index to field name |
| 52 | + */ |
| 53 | +function getFieldName(rowIndex, cellIndex) { |
| 54 | + const fieldMap = { |
| 55 | + '0-0': 'items' |
| 56 | + }; |
| 57 | + return fieldMap[`${rowIndex}-${cellIndex}`]; |
| 58 | +} |
103 | 59 |
|
104 | | - // Keyboard handler |
105 | | - header.addEventListener('keydown', (e) => { |
106 | | - handleKeydown(e, header, content, children, headers); |
107 | | - }); |
| 60 | +/** |
| 61 | + * Process images in a cell |
| 62 | + */ |
| 63 | +function processImages(cell) { |
| 64 | + const images = cell.querySelectorAll('img'); |
| 65 | + images.forEach((img) => { |
| 66 | + // Add lazy loading |
| 67 | + img.loading = 'lazy'; |
| 68 | + |
| 69 | + // Wrap in picture element if not already |
| 70 | + if (img.parentElement.tagName !== 'PICTURE') { |
| 71 | + const picture = document.createElement('picture'); |
| 72 | + img.parentElement.insertBefore(picture, img); |
| 73 | + picture.appendChild(img); |
| 74 | + } |
| 75 | + }); |
| 76 | +} |
108 | 77 |
|
109 | | - // Open first panel by default |
110 | | - if (i === 0) { |
111 | | - header.classList.add('active'); |
112 | | - header.setAttribute('aria-expanded', 'true'); |
113 | | - content.classList.add('active'); |
114 | | - } |
| 78 | +/** |
| 79 | + * Process links in a cell |
| 80 | + */ |
| 81 | +function processLinks(cell) { |
| 82 | + const links = cell.querySelectorAll('a'); |
| 83 | + links.forEach((link) => { |
| 84 | + // Add target blank for external links |
| 85 | + if (link.hostname !== window.location.hostname) { |
| 86 | + link.target = '_blank'; |
| 87 | + link.rel = 'noopener noreferrer'; |
115 | 88 | } |
116 | | - } |
| 89 | + }); |
| 90 | +} |
| 91 | + |
| 92 | +/** |
| 93 | + * Initialize block functionality |
| 94 | + */ |
| 95 | +function initializeBlock(block, config) { |
| 96 | + // Add loaded class for CSS transitions |
| 97 | + block.classList.add('loaded'); |
| 98 | + |
| 99 | + // Dispatch custom event for extensibility |
| 100 | + block.dispatchEvent(new CustomEvent('block:loaded', { |
| 101 | + detail: { config }, |
| 102 | + bubbles: true, |
| 103 | + })); |
117 | 104 | } |
0 commit comments