PageSourceSearch

https://dniprorada.gov.ua/content/app/js/table-modal.js?v=639258494013789686

js dniprorada.gov.ua collected 2026-09-24 09:23:35 UTC 17,448 bytes, 510 lines download raw bytes

1(function () {
2    "use strict";
3
4    const minimumDesktopWidthInPixels = 1024;
5
6    if (window.innerWidth < minimumDesktopWidthInPixels) {
7        return;
8    }
9
10    const initializationFlagPropertyName = "__tableFullscreenExpanderInitialized";
11    const styleElementIdentifier = "table-fullscreen-expander-style";
12
13    const visibleClassName = "table-expander-visible";
14    const modalOpenClassName = "table-expander-open";
15
16    const buttonSizeInPixels = 38;
17    const buttonMarginInPixels = 8;
18    const hideDelayInMilliseconds = 80;
19
20    if (window[initializationFlagPropertyName] === true) {
21        return;
22    }
23
24    window[initializationFlagPropertyName] = true;
25
26    let activeTableElement = null;
27    let floatingButtonElement = null;
28    let modalElement = null;
29    let modalContentElement = null;
30    let modalCloseButtonElement = null;
31
32    let hideTimeoutIdentifier = 0;
33    let positionAnimationFrameIdentifier = 0;
34
35    let isPointerInsideFloatingButton = false;
36
37    let previousDocumentOverflowValue = "";
38    let previousBodyOverflowValue = "";
39    let previouslyFocusedElement = null;
40
41    function initializeTableFullscreenExpander() {
42        injectStyles();
43
44        floatingButtonElement = createFloatingButtonElement();
45        modalElement = createModalElement();
46
47        document.body.appendChild(floatingButtonElement);
48        document.body.appendChild(modalElement);
49
50        document.addEventListener("pointerover", handleDocumentPointerOver, true);
51        document.addEventListener("pointerout", handleDocumentPointerOut, true);
52        document.addEventListener("keydown", handleDocumentKeyDown, true);
53
54        window.addEventListener("scroll", requestFloatingButtonPositionUpdate, true);
55        window.addEventListener("resize", handleWindowResize);
56    }
57
58    function handleWindowResize() {
59        if (window.innerWidth < minimumDesktopWidthInPixels) {
60            hideFloatingButton();
61            closeExpansionModal();
62            return;
63        }
64
65        requestFloatingButtonPositionUpdate();
66    }
67
68    function injectStyles() {
69        if (document.getElementById(styleElementIdentifier) !== null) {
70            return;
71        }
72
73        const styleElement = document.createElement("style");
74        styleElement.id = styleElementIdentifier;
75        styleElement.textContent = `
76            @media (min-width: ${minimumDesktopWidthInPixels}px) {
77                .table-expander-floating-button {
78                    position: fixed;
79                    left: -9999px;
80                    top: -9999px;
81                    z-index: 2147483646;
82                    width: ${buttonSizeInPixels}px;
83                    height: ${buttonSizeInPixels}px;
84                    display: none;
85                    align-items: center;
86                    justify-content: center;
87                    box-sizing: border-box;
88                    border: 0;
89                    border-radius: 8px;
90                    background: #111827;
91                    color: #ffffff;
92                    font: 20px/1 Arial, sans-serif;
93                    cursor: pointer;
94                    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
95                    opacity: 0.92;
96                    user-select: none;
97                    touch-action: manipulation;
98                }
99
100                .table-expander-floating-button:hover {
101                    opacity: 1;
102                }
103
104                .table-expander-floating-button.${visibleClassName} {
105                    display: flex;
106                }
107
108                .table-expander-modal {
109                    position: fixed;
110                    inset: 0;
111                    z-index: 2147483647;
112                    display: none;
113                    background: #ffffff;
114                }
115
116                .table-expander-modal.${modalOpenClassName} {
117                    display: block;
118                }
119
120                .table-expander-window {
121                    width: 100vw;
122                    height: 100vh;
123                    display: flex;
124                    flex-direction: column;
125                    background: #ffffff;
126                }
127
128                .table-expander-header {
129                    flex: 0 0 auto;
130                    min-height: 52px;
131                    display: flex;
132                    align-items: center;
133                    justify-content: space-between;
134                    gap: 16px;
135                    padding: 0 16px;
136                    box-sizing: border-box;
137                    border-bottom: 1px solid #e5e7eb;
138                    background: #ffffff;
139                }
140
141                .table-expander-title {
142                    font: 600 16px/1.2 Arial, sans-serif;
143                    color: #111827;
144                }
145
146                .table-expander-close-button {
147                    width: 40px;
148                    height: 40px;
149                    border: 0;
150                    background: transparent;
151                    color: #111827;
152                    font: 32px/1 Arial, sans-serif;
153                    cursor: pointer;
154                }
155
156                .table-expander-content {
157                    flex: 1 1 auto;
158                    overflow: auto;
159                    padding: 16px;
160                    box-sizing: border-box;
161                    background: #ffffff;
162                }
163
164                .table-expander-expanded-table {
165                    width: max-content !important;
166                    min-width: 100% !important;
167                    max-width: none !important;
168                    height: auto !important;
169                }
170            }
171        `;
172
173        document.head.appendChild(styleElement);
174    }
175
176    function createFloatingButtonElement() {
177        const createdButtonElement = document.createElement("button");
178
179        createdButtonElement.type = "button";
180        createdButtonElement.className = "table-expander-floating-button";
181        createdButtonElement.textContent = "⛶";
182        createdButtonElement.title = "Розгорнути таблицю";
183        createdButtonElement.setAttribute("aria-label", "Розгорнути таблицю на весь екран");
184
185        createdButtonElement.addEventListener("click", handleFloatingButtonClick);
186
187        createdButtonElement.addEventListener("pointerenter", function () {
188            isPointerInsideFloatingButton = true;
189            clearScheduledHide();
190        });
191
192        createdButtonElement.addEventListener("pointerleave", function () {
193            isPointerInsideFloatingButton = false;
194            scheduleFloatingButtonHide();
195        });
196
197        return createdButtonElement;
198    }
199
200    function createModalElement() {
201        const createdModalElement = document.createElement("div");
202
203        createdModalElement.className = "table-expander-modal";
204        createdModalElement.setAttribute("role", "dialog");
205        createdModalElement.setAttribute("aria-modal", "true");
206        createdModalElement.setAttribute("aria-label", "Розгорнута таблиця");
207
208        createdModalElement.innerHTML = `
209            <div class="table-expander-window">
210                <div class="table-expander-header">
211                    <div class="table-expander-title">Розгорнута таблиця</div>
212                    <button class="table-expander-close-button" type="button" aria-label="Закрити">×</button>
213                </div>
214                <div class="table-expander-content"></div>
215            </div>
216        `;
217
218        modalContentElement = createdModalElement.querySelector(".table-expander-content");
219        modalCloseButtonElement = createdModalElement.querySelector(".table-expander-close-button");
220
221        modalCloseButtonElement.addEventListener("click", closeExpansionModal);
222
223        return createdModalElement;
224    }
225
226    function handleDocumentPointerOver(event) {
227        if (window.innerWidth < minimumDesktopWidthInPixels) {
228            return;
229        }
230
231        const tableElement = findTableElementFromEventTarget(event.target);
232
233        if (tableElement === null) {
234            return;
235        }
236
237        activeTableElement = tableElement;
238        clearScheduledHide();
239        showFloatingButton();
240    }
241
242    function handleDocumentPointerOut(event) {
243        if (activeTableElement === null || floatingButtonElement === null) {
244            return;
245        }
246
247        const targetElement = event.target instanceof Element ? event.target : null;
248        const relatedElement = event.relatedTarget instanceof Element ? event.relatedTarget : null;
249
250        if (targetElement === null || !activeTableElement.contains(targetElement)) {
251            return;
252        }
253
254        if (
255            relatedElement !== null &&
256            (
257                activeTableElement.contains(relatedElement) ||
258                floatingButtonElement.contains(relatedElement)
259            )
260        ) {
261            return;
262        }
263
264        scheduleFloatingButtonHide();
265    }
266
267    function handleFloatingButtonClick() {
268        if (activeTableElement === null) {
269            return;
270        }
271
272        openExpansionModal(activeTableElement);
273        hideFloatingButton();
274    }
275
276    function handleDocumentKeyDown(event) {
277        if (event.key !== "Escape") {
278            return;
279        }
280
281        if (modalElement !== null && modalElement.classList.contains(modalOpenClassName)) {
282            closeExpansionModal();
283        }
284    }
285
286    function findTableElementFromEventTarget(eventTarget) {
287        if (!(eventTarget instanceof Element)) {
288            return null;
289        }
290
291        if (modalElement !== null && modalElement.contains(eventTarget)) {
292            return null;
293        }
294
295        if (eventTarget.closest("[data-table-expander-ignore]") !== null) {
296            return null;
297        }
298
299        const tableElement = eventTarget.closest("table");
300
301        if (!(tableElement instanceof HTMLTableElement)) {
302            return null;
303        }
304
305        return tableElement;
306    }
307
308    function showFloatingButton() {
309        if (floatingButtonElement === null) {
310            return;
311        }
312
313        floatingButtonElement.classList.add(visibleClassName);
314        updateFloatingButtonPosition();
315    }
316
317    function hideFloatingButton() {
318        if (floatingButtonElement === null) {
319            return;
320        }
321
322        floatingButtonElement.classList.remove(visibleClassName);
323        activeTableElement = null;
324    }
325
326    function scheduleFloatingButtonHide() {
327        clearScheduledHide();
328
329        hideTimeoutIdentifier = window.setTimeout(function () {
330            if (!isPointerInsideFloatingButton) {
331                hideFloatingButton();
332            }
333        }, hideDelayInMilliseconds);
334    }
335
336    function clearScheduledHide() {
337        if (hideTimeoutIdentifier !== 0) {
338            window.clearTimeout(hideTimeoutIdentifier);
339            hideTimeoutIdentifier = 0;
340        }
341    }
342
343    function requestFloatingButtonPositionUpdate() {
344        if (positionAnimationFrameIdentifier !== 0) {
345            return;
346        }
347
348        positionAnimationFrameIdentifier = window.requestAnimationFrame(function () {
349            positionAnimationFrameIdentifier = 0;
350            updateFloatingButtonPosition();
351        });
352    }
353
354    function updateFloatingButtonPosition() {
355        if (activeTableElement === null || floatingButtonElement === null) {
356            return;
357        }
358
359        if (!document.documentElement.contains(activeTableElement)) {
360            hideFloatingButton();
361            return;
362        }
363
364        const visibleRectangle = calculateVisibleRectangle(activeTableElement);
365
366        if (visibleRectangle.width <= 0 || visibleRectangle.height <= 0) {
367            floatingButtonElement.classList.remove(visibleClassName);
368            return;
369        }
370
371        const actualButtonWidth = floatingButtonElement.offsetWidth || buttonSizeInPixels;
372        const actualButtonHeight = floatingButtonElement.offsetHeight || buttonSizeInPixels;
373
374        const leftPosition = clampNumber(
375            visibleRectangle.right - actualButtonWidth - buttonMarginInPixels,
376            buttonMarginInPixels,
377            window.innerWidth - actualButtonWidth - buttonMarginInPixels
378        );
379
380        const topPosition = clampNumber(
381            visibleRectangle.top + visibleRectangle.height / 2 - actualButtonHeight / 2,
382            buttonMarginInPixels,
383            window.innerHeight - actualButtonHeight - buttonMarginInPixels
384        );
385
386        floatingButtonElement.style.left = `${Math.round(leftPosition)}px`;
387        floatingButtonElement.style.top = `${Math.round(topPosition)}px`;
388    }
389
390    function calculateVisibleRectangle(element) {
391        const elementRectangle = element.getBoundingClientRect();
392
393        let visibleTop = Math.max(elementRectangle.top, 0);
394        let visibleRight = Math.min(elementRectangle.right, window.innerWidth);
395        let visibleBottom = Math.min(elementRectangle.bottom, window.innerHeight);
396        let visibleLeft = Math.max(elementRectangle.left, 0);
397
398        let currentAncestorElement = element.parentElement;
399
400        while (
401            currentAncestorElement !== null &&
402            currentAncestorElement !== document.body &&
403            currentAncestorElement !== document.documentElement
404        ) {
405            const computedStyle = window.getComputedStyle(currentAncestorElement);
406
407            if (doesElementClipContent(computedStyle)) {
408                const ancestorRectangle = currentAncestorElement.getBoundingClientRect();
409
410                visibleTop = Math.max(visibleTop, ancestorRectangle.top);
411                visibleRight = Math.min(visibleRight, ancestorRectangle.right);
412                visibleBottom = Math.min(visibleBottom, ancestorRectangle.bottom);
413                visibleLeft = Math.max(visibleLeft, ancestorRectangle.left);
414            }
415
416            currentAncestorElement = currentAncestorElement.parentElement;
417        }
418
419        return {
420            top: visibleTop,
421            right: visibleRight,
422            bottom: visibleBottom,
423            left: visibleLeft,
424            width: Math.max(0, visibleRight - visibleLeft),
425            height: Math.max(0, visibleBottom - visibleTop)
426        };
427    }
428
429    function doesElementClipContent(computedStyle) {
430        const overflowValue = [
431            computedStyle.overflow,
432            computedStyle.overflowX,
433            computedStyle.overflowY
434        ].join(" ");
435
436        return /auto|scroll|hidden|clip|overlay/.test(overflowValue);
437    }
438
439    function clampNumber(value, minimumValue, maximumValue) {
440        return Math.min(Math.max(value, minimumValue), maximumValue);
441    }
442
443    function openExpansionModal(sourceTableElement) {
444        if (
445            modalElement === null ||
446            modalContentElement === null ||
447            modalCloseButtonElement === null
448        ) {
449            return;
450        }
451
452        const expandedTableElement = createExpandedTableClone(sourceTableElement);
453
454        modalContentElement.replaceChildren(expandedTableElement);
455
456        previouslyFocusedElement = document.activeElement instanceof HTMLElement
457            ? document.activeElement
458            : null;
459
460        previousDocumentOverflowValue = document.documentElement.style.overflow;
461        previousBodyOverflowValue = document.body.style.overflow;
462
463        document.documentElement.style.overflow = "hidden";
464        document.body.style.overflow = "hidden";
465
466        modalElement.classList.add(modalOpenClassName);
467        modalCloseButtonElement.focus({ preventScroll: true });
468    }
469
470    function closeExpansionModal() {
471        if (modalElement === null || modalContentElement === null) {
472            return;
473        }
474
475        modalElement.classList.remove(modalOpenClassName);
476        modalContentElement.replaceChildren();
477
478        document.documentElement.style.overflow = previousDocumentOverflowValue;
479        document.body.style.overflow = previousBodyOverflowValue;
480
481        if (
482            previouslyFocusedElement !== null &&
483            document.documentElement.contains(previouslyFocusedElement)
484        ) {
485            previouslyFocusedElement.focus({ preventScroll: true });
486        }
487
488        previouslyFocusedElement = null;
489    }
490
491    function createExpandedTableClone(sourceTableElement) {
492        const expandedTableElement = sourceTableElement.cloneNode(true);
493
494        expandedTableElement.removeAttribute("id");
495
496        expandedTableElement.querySelectorAll("[id]").forEach(function (elementWithIdentifier) {
497            elementWithIdentifier.removeAttribute("id");
498        });
499
500        expandedTableElement.classList.add("table-expander-expanded-table");
501
502        return expandedTableElement;
503    }
504
505    if (document.readyState === "loading") {
506        document.addEventListener("DOMContentLoaded", initializeTableFullscreenExpander, { once: true });
507    } else {
508        initializeTableFullscreenExpander();
509    }
510})();

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.