PageSourceSearch

https://www.sb.by/local/templates/media/components/ui-tabs.js?177995308111565

js sb.by collected 2026-09-24 08:46:28 UTC 11,565 bytes, 320 lines download raw bytes

1function TabsScript() {
2    const app = {
3        init() {
4            // Инициализируем все табы, и с wrap=true, и с wrap=false
5            this.blocks = document.querySelectorAll('.tabs')
6
7            if (!this.blocks.length) return
8
9            this.resizeTimeout = null
10
11            this.initOverflow()
12            this.initBurgerToggles()
13            this.initTabItemSelection()
14            this.initClickOutside()
15            this.initResize()
16            this.initScrollArrows()
17        },
18
19        initOverflow() {
20            const { blocks } = this
21
22            blocks.forEach((tabs) => {
23                const list = tabs.querySelector('.tabs__list')
24                const overflowContainer = tabs.querySelector('.tabs-select')
25                const burger = tabs.querySelector('.tabs__burger')
26
27                if (!list || !overflowContainer) return
28
29                const items = Array.from(list.children)
30
31                const selected = overflowContainer.querySelector('.tabs-select__selected')
32                Array.from(overflowContainer.children).forEach((child) => {
33                    if (child !== selected) {
34                        overflowContainer.removeChild(child)
35                    }
36                })
37
38                const listStyles = window.getComputedStyle(list)
39                const gap =
40                    parseFloat(
41                        listStyles.gap || listStyles.columnGap || listStyles.rowGap || '0'
42                    ) || 0
43
44                const containerWidth = list.clientWidth
45                let usedWidth = 0
46
47                items.forEach((item, index) => {
48                    const itemWidth = item.offsetWidth
49
50                    if (index > 0) {
51                        usedWidth += gap
52                    }
53
54                    usedWidth += itemWidth
55
56                    if (usedWidth > containerWidth) {
57                        const text = item.textContent.trim()
58                        const overflowItem = document.createElement('div')
59
60                        overflowItem.className = 'tabs-select__item js-tab-item'
61                        overflowItem.textContent = text
62
63                        if (item.dataset && item.dataset.ajaxUrl) {
64                            overflowItem.dataset.ajaxUrl = item.dataset.ajaxUrl
65                        }
66
67                        overflowContainer.appendChild(overflowItem)
68
69                        // Убираем таб из основной разметки
70                        list.removeChild(item)
71                    }
72                })
73
74                const overflowItemsCount =
75                    overflowContainer.children.length - (selected ? 1 : 0)
76
77                if (burger) {
78                    burger.style.display = overflowItemsCount > 0 ? '' : 'none'
79                }
80            })
81        },
82
83        initBurgerToggles() {
84            const { blocks } = this
85
86            blocks.forEach((tabs) => {
87                const burger = tabs.querySelector('.tabs__burger')
88                if (!burger) return
89
90                const button = burger.querySelector('.btn')
91                if (!button) return
92
93                button.addEventListener('click', (ev) => {
94                    ev.preventDefault()
95
96                    const isActive = burger.classList.contains('active')
97
98                    const allBurgersInBlock = tabs.querySelectorAll('.tabs__burger')
99                    allBurgersInBlock.forEach((b) => {
100                        b.classList.remove('active')
101
102                        const btn = b.querySelector('.btn')
103                        if (btn && window.slam && window.slam.app && window.slam.app.setIcon) {
104                            window.slam.app.setIcon(btn, 'i-burger')
105                        }
106                    })
107
108                    if (!isActive) {
109                        burger.classList.add('active')
110
111                        if (window.slam && window.slam.app && window.slam.app.setIcon) {
112                            window.slam.app.setIcon(button, 'i-close')
113                        }
114                    } else {
115                        if (window.slam && window.slam.app && window.slam.app.setIcon) {
116                            window.slam.app.setIcon(button, 'i-burger')
117                        }
118                    }
119                })
120            })
121        },
122
123        initTabItemSelection() {
124            const { blocks } = this
125
126            blocks.forEach((tabs) => {
127                const selectedWrapper = tabs.querySelector('.tabs-select__selected')
128                const selectedSpan =
129                    selectedWrapper && selectedWrapper.querySelector('.tabs-select__item span')
130
131                tabs.addEventListener('click', (ev) => {
132                    const overflowItem = ev.target.closest('.tabs-select .js-tab-item')
133                    const mainItem = ev.target.closest('.tabs__list .js-tab-item')
134
135                    if (!overflowItem && !mainItem) return
136
137                    const clickedItem = overflowItem || mainItem
138
139                    // клик по элементу в overflow (только если есть блок выбора)
140                    if (overflowItem && tabs.contains(overflowItem) && selectedWrapper && selectedSpan) {
141                        const text = overflowItem.textContent.trim()
142                        selectedSpan.textContent = text
143                        selectedWrapper.style.display = ''
144                    }
145
146                    // клик по основному табу (сбрасываем выбранный элемент в селекте, если он есть)
147                    if (mainItem && tabs.contains(mainItem) && selectedWrapper && selectedSpan) {
148                        selectedSpan.textContent = ''
149                        selectedWrapper.style.display = 'none'
150                    }
151
152                    // меняем активный таб
153                    app.setActiveTab(tabs, clickedItem)
154
155                    // загружаем контент по AJAX
156                    app.loadContent(tabs, clickedItem)
157                })
158            })
159        },
160
161        setActiveTab(tabs, clickedItem) {
162            if (!tabs || !clickedItem) return
163
164            const listItems = tabs.querySelectorAll('.tabs__list .tabs__item')
165            const clickedText = clickedItem.textContent.trim()
166
167            // всегда снимаем активность со всех основных табов
168            listItems.forEach((item) => {
169                item.classList.remove('active')
170            })
171
172            let targetMainItem = clickedItem.closest('.tabs__item')
173
174            if (!targetMainItem) {
175                listItems.forEach((item) => {
176                    if (!targetMainItem && item.textContent.trim() === clickedText) {
177                        targetMainItem = item
178                    }
179                })
180            }
181
182            if (targetMainItem) {
183                targetMainItem.classList.add('active')
184            }
185        },
186
187        loadContent(tabs, clickedItem) {
188            if (!tabs || !clickedItem || typeof window === 'undefined') return
189
190            const targetId = tabs.dataset.ajaxTargetId
191            if (!targetId) return
192
193            const targetEl = document.getElementById(targetId)
194            if (!targetEl) return
195
196            const url =
197                (clickedItem.dataset && clickedItem.dataset.ajaxUrl) ||
198                (tabs.dataset && tabs.dataset.ajaxUrl) ||
199                '/ajax/tabs-news-grid-example.html'
200
201            if (!url) return
202
203            const requestUrl = new URL(url, window.location.origin)
204
205            requestUrl.searchParams.set('IS_AJAX', 'Y')
206
207            fetch(requestUrl.toString(), {
208                headers: {
209                    'X-Requested-With': 'XMLHttpRequest',
210                },
211            })
212                .then((response) => {
213                    if (!response.ok) {
214                        throw new Error(`Tabs AJAX request failed with status ${response.status}`)
215                    }
216
217                    return response.text()
218                })
219                .then((html) => {
220                    targetEl.innerHTML = html
221                })
222                .catch((error) => {
223                    // eslint-disable-next-line no-console
224                    console.error('Tabs AJAX load error:', error)
225                })
226        },
227
228        initClickOutside() {
229            const { blocks } = this
230
231            if (!window.slam || !window.slam.app || !window.slam.app.clickOutside) return
232
233            blocks.forEach((tabs) => {
234                const burger = tabs.querySelector('.tabs__burger')
235                if (!burger) return
236
237                const button = burger.querySelector('.btn')
238
239                window.slam.app.clickOutside([burger, button], () => {
240                    burger.classList.remove('active')
241
242                    if (button && window.slam.app && window.slam.app.setIcon) {
243                        window.slam.app.setIcon(button, 'i-burger')
244                    }
245                })
246            })
247        },
248
249        scrollToActiveTab(list) {
250            const activeItem = list.querySelector('.tabs__item.active')
251            if (!activeItem) return
252
253            const listRect = list.getBoundingClientRect()
254            const itemRect = activeItem.getBoundingClientRect()
255
256            const offsetLeft = itemRect.left - listRect.left + list.scrollLeft
257            const scrollPos = offsetLeft - (listRect.width - itemRect.width) / 2
258
259            list.scrollTo({ left: scrollPos, behavior: 'smooth' })
260        },
261
262        initScrollArrows() {
263            const { blocks } = this
264
265            blocks.forEach((tabs) => {
266                const btnLeft = tabs.querySelector('.js-tabs-scroll-left')
267                const btnRight = tabs.querySelector('.js-tabs-scroll-right')
268
269                if (!btnLeft || !btnRight) return
270
271                const list = tabs.querySelector('.tabs__list')
272                if (!list) return
273
274                const scrollStep = 200
275
276                const updateArrowStates = () => {
277                    const { scrollLeft, scrollWidth, clientWidth } = list
278                    const atStart = scrollLeft <= 0
279                    const atEnd = scrollLeft + clientWidth >= scrollWidth - 1
280
281                    btnLeft.classList.toggle('disabled', atStart)
282                    btnRight.classList.toggle('disabled', atEnd)
283                }
284
285                this.scrollToActiveTab(list)
286                updateArrowStates()
287
288                list.addEventListener('scroll', updateArrowStates)
289
290                btnLeft.addEventListener('click', (ev) => {
291                    ev.preventDefault()
292                    list.scrollBy({ left: -scrollStep, behavior: 'smooth' })
293                })
294
295                btnRight.addEventListener('click', (ev) => {
296                    ev.preventDefault()
297                    list.scrollBy({ left: scrollStep, behavior: 'smooth' })
298                })
299            })
300        },
301
302        initResize() {
303            window.addEventListener('resize', () => {
304                clearTimeout(this.resizeTimeout)
305
306                this.resizeTimeout = setTimeout(() => {
307                    this.initOverflow()
308                }, 150)
309            })
310        },
311    }
312
313    app.init()
314}
315
316if (typeof window !== 'undefined') {
317    window.addEventListener('ReactContentLoaded', TabsScript)
318    document.addEventListener('DOMContentLoaded', TabsScript)
319}
320

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.