|
7 | 7 | const DEFAULT_AUTOPLAY_MS = 4000; |
8 | 8 | const CAROUSEL_ITEM_SELECTOR = '[data-carousel-item]'; |
9 | 9 |
|
10 | | - function updateArrowState(track, prevBtn, nextBtn) { |
11 | | - if (!track || !prevBtn || !nextBtn) return; |
12 | | - const maxScroll = track.scrollWidth - track.clientWidth; |
13 | | - if (maxScroll <= 0) { |
14 | | - prevBtn.disabled = true; |
15 | | - nextBtn.disabled = true; |
16 | | - return; |
| 10 | + function setDisabled(btn, disabled) { |
| 11 | + if (!btn) return; |
| 12 | + if (btn.tagName === 'BUTTON') { |
| 13 | + btn.disabled = disabled; |
| 14 | + } else { |
| 15 | + if (disabled) { |
| 16 | + btn.setAttribute('aria-disabled', 'true'); |
| 17 | + } else { |
| 18 | + btn.removeAttribute('aria-disabled'); |
| 19 | + } |
17 | 20 | } |
18 | | - const atStart = track.scrollLeft <= 0; |
19 | | - const atEnd = track.scrollLeft >= maxScroll - SCROLL_END_EPSILON; |
20 | | - prevBtn.disabled = atStart; |
21 | | - nextBtn.disabled = atEnd; |
| 21 | + } |
| 22 | + |
| 23 | + function isDisabled(btn) { |
| 24 | + if (!btn) return true; |
| 25 | + if (btn.tagName === 'BUTTON') return btn.disabled; |
| 26 | + return btn.getAttribute('aria-disabled') === 'true'; |
| 27 | + } |
| 28 | + |
| 29 | + function updateArrowState(track, prevBtns, nextBtns) { |
| 30 | + if (!track) return; |
| 31 | + const maxScroll = track.scrollWidth - track.clientWidth; |
| 32 | + const noScroll = maxScroll <= 0; |
| 33 | + const atStart = !noScroll && track.scrollLeft <= 0; |
| 34 | + const atEnd = !noScroll && track.scrollLeft >= maxScroll - SCROLL_END_EPSILON; |
| 35 | + prevBtns.forEach(function (b) { setDisabled(b, noScroll || atStart); }); |
| 36 | + nextBtns.forEach(function (b) { setDisabled(b, noScroll || atEnd); }); |
22 | 37 | } |
23 | 38 |
|
24 | 39 | function getStepPx(track) { |
|
100 | 115 | }); |
101 | 116 |
|
102 | 117 | const controls = document.getElementById(root.id + '-controls'); |
103 | | - const prevBtn = controls && controls.querySelector('[data-carousel-prev]'); |
104 | | - const nextBtn = controls && controls.querySelector('[data-carousel-next]'); |
| 118 | + const prevBtns = controls ? controls.querySelectorAll('[data-carousel-prev]') : []; |
| 119 | + const nextBtns = controls ? controls.querySelectorAll('[data-carousel-next]') : []; |
105 | 120 |
|
106 | | - if (prevBtn) { |
107 | | - prevBtn.addEventListener('click', function () { |
| 121 | + prevBtns.forEach(function (prevBtn) { |
| 122 | + prevBtn.addEventListener('click', function (e) { |
| 123 | + e.preventDefault(); |
108 | 124 | if (track.scrollLeft < step) { |
109 | 125 | scrollResetInProgress = true; |
110 | 126 | track.scrollLeft = setWidth; |
|
116 | 132 | scrollCarousel(track, 'prev', true); |
117 | 133 | } |
118 | 134 | }); |
119 | | - } |
| 135 | + }); |
120 | 136 |
|
121 | | - if (nextBtn) { |
122 | | - nextBtn.addEventListener('click', function () { |
| 137 | + nextBtns.forEach(function (nextBtn) { |
| 138 | + nextBtn.addEventListener('click', function (e) { |
| 139 | + e.preventDefault(); |
123 | 140 | scrollCarousel(track, 'next', true); |
124 | 141 | }); |
125 | | - } |
| 142 | + }); |
126 | 143 |
|
127 | 144 | const autoplayDelay = root.getAttribute('data-carousel-autoplay'); |
128 | 145 | if (autoplayDelay) { |
|
139 | 156 | } |
140 | 157 | } |
141 | 158 |
|
| 159 | + function setupRadioCarousel(root, track) { |
| 160 | + const radios = root.querySelectorAll('input[type="radio"].testimonial-card__state'); |
| 161 | + if (radios.length === 0) return; |
| 162 | + const items = track.querySelectorAll(CAROUSEL_ITEM_SELECTOR); |
| 163 | + |
| 164 | + let scrolling = false; |
| 165 | + let scrollTimeout = null; |
| 166 | + |
| 167 | + function syncInert(activeIdx) { |
| 168 | + items.forEach(function (item, i) { |
| 169 | + if (i === activeIdx) item.removeAttribute('inert'); |
| 170 | + else item.setAttribute('inert', ''); |
| 171 | + }); |
| 172 | + } |
| 173 | + |
| 174 | + const initialIdx = Array.prototype.findIndex.call(radios, function (r) { return r.checked; }); |
| 175 | + syncInert(initialIdx >= 0 ? initialIdx : 0); |
| 176 | + |
| 177 | + radios.forEach(function (radio, idx) { |
| 178 | + radio.addEventListener('change', function () { |
| 179 | + if (!radio.checked) return; |
| 180 | + syncInert(idx); |
| 181 | + const target = items[idx]; |
| 182 | + if (!target) return; |
| 183 | + scrolling = true; |
| 184 | + target.scrollIntoView({ inline: 'start', block: 'nearest', behavior: 'smooth' }); |
| 185 | + clearTimeout(scrollTimeout); |
| 186 | + scrollTimeout = setTimeout(function () { scrolling = false; }, 400); |
| 187 | + }); |
| 188 | + }); |
| 189 | + |
| 190 | + track.addEventListener('scroll', function () { |
| 191 | + if (scrolling) return; |
| 192 | + const first = items[0]; |
| 193 | + if (!first) return; |
| 194 | + const itemWidth = first.offsetWidth; |
| 195 | + if (itemWidth === 0) return; |
| 196 | + const idx = Math.round(track.scrollLeft / itemWidth); |
| 197 | + const radio = radios[Math.max(0, Math.min(radios.length - 1, idx))]; |
| 198 | + if (radio && !radio.checked) { |
| 199 | + radio.checked = true; |
| 200 | + syncInert(idx); |
| 201 | + } |
| 202 | + }, { passive: true }); |
| 203 | + } |
| 204 | + |
142 | 205 | function initCarousel(root) { |
143 | 206 | if (!root || !root.id) return; |
144 | 207 | const track = root.querySelector('[data-carousel-track]'); |
145 | 208 | const controls = document.getElementById(root.id + '-controls'); |
146 | 209 | if (!track || !controls) return; |
147 | 210 |
|
| 211 | + if (root.hasAttribute('data-carousel-radios')) { |
| 212 | + setupRadioCarousel(root, track); |
| 213 | + return; |
| 214 | + } |
| 215 | + |
148 | 216 | if (root.hasAttribute('data-carousel-infinite')) { |
149 | 217 | setupInfiniteCarousel(root, track); |
150 | 218 | return; |
151 | 219 | } |
152 | 220 |
|
153 | | - const prevBtn = controls.querySelector('[data-carousel-prev]'); |
154 | | - const nextBtn = controls.querySelector('[data-carousel-next]'); |
155 | | - if (prevBtn) { |
156 | | - prevBtn.addEventListener('click', function () { |
157 | | - if (prevBtn.disabled) return; |
| 221 | + const prevBtns = controls.querySelectorAll('[data-carousel-prev]'); |
| 222 | + const nextBtns = controls.querySelectorAll('[data-carousel-next]'); |
| 223 | + prevBtns.forEach(function (prevBtn) { |
| 224 | + prevBtn.addEventListener('click', function (e) { |
| 225 | + e.preventDefault(); |
| 226 | + if (isDisabled(prevBtn)) return; |
158 | 227 | scrollCarousel(track, 'prev', true); |
159 | 228 | }); |
160 | | - } |
161 | | - if (nextBtn) { |
162 | | - nextBtn.addEventListener('click', function () { |
163 | | - if (nextBtn.disabled) return; |
| 229 | + }); |
| 230 | + nextBtns.forEach(function (nextBtn) { |
| 231 | + nextBtn.addEventListener('click', function (e) { |
| 232 | + e.preventDefault(); |
| 233 | + if (isDisabled(nextBtn)) return; |
164 | 234 | scrollCarousel(track, 'next', true); |
165 | 235 | }); |
166 | | - } |
| 236 | + }); |
167 | 237 |
|
168 | 238 | const syncArrows = function () { |
169 | | - updateArrowState(track, prevBtn, nextBtn); |
| 239 | + updateArrowState(track, prevBtns, nextBtns); |
170 | 240 | }; |
171 | 241 | requestAnimationFrame(syncArrows); |
172 | 242 | track.addEventListener('scroll', syncArrows, { passive: true }); |
|
0 commit comments