PageSourceSearch

https://tagankateatr.ru/_inc/search-desktop.js?7937

js tagankateatr.ru collected 2026-09-24 21:43:24 UTC 5,481 bytes, 164 lines download raw bytes

1/* ============================================================
2   DESKTOP SEARCH OVERLAY (≥1280px)
3   Оболочка: открытие/закрытие, оверлей, фокус-ловушка, Esc.
4   Логика самого поиска — в components/search-core/search-core.js
5   (window.TagankaSearch), подключается ДО этого файла.
6   ============================================================ */
7(function () {
8  'use strict';
9
10  class SearchDesktop {
11    constructor() {
12      this._overlay  = document.getElementById('searchDesktop');
13      this._input    = document.getElementById('searchDesktopInput');
14      this._clear    = document.getElementById('searchDesktopClear');
15      this._state    = document.getElementById('searchDesktopState');
16      this._chips    = document.getElementById('searchDesktopChips');
17      this._results  = document.getElementById('searchDesktopResults');
18      this._list     = document.getElementById('searchDesktopList');
19      this._count    = document.getElementById('searchDesktopCount');
20      this._closeBtn = document.getElementById('searchDesktopClose');
21
22      this._openBtns = []; /* все кнопки-триггеры (полная и сокращённая шапки) */
23
24      this._isOpen        = false;
25      this._previousFocus = null;
26
27      this._menuOverlay = document.querySelector('.menu-overlay');
28      this._boundKeydown = this._onKeydown.bind(this);
29
30      // Вся механика поиска — общий контроллер.
31      // layout: 'columns' — текстовые группы в правую колонку, остальные в левую.
32      this._search = new window.TagankaSearch.SearchController({
33        input:    this._input,
34        clearBtn: this._clear,
35        chips:    this._chips,
36        results:  this._results,
37        list:     this._list,
38        count:    this._count,
39        layout:   'columns'
40      });
41
42      this._init();
43    }
44
45    _init() {
46
47      document.querySelectorAll('[data-search-trigger]').forEach(btn => {
48        this._openBtns.push(btn);
49      });
50
51      const idBtn = document.getElementById('searchOpenBtnDesktop');
52      if (idBtn && this._openBtns.indexOf(idBtn) === -1) {
53        this._openBtns.push(idBtn);
54      }
55
56      document.querySelectorAll('.header__item').forEach(btn => {
57        const text = btn.querySelector('.header__item-text');
58        if (text && text.textContent.trim() === 'Поиск' &&
59            this._openBtns.indexOf(btn) === -1) {
60          this._openBtns.push(btn);
61        }
62      });
63
64      this._openBtns.forEach(btn => {
65        btn.setAttribute('aria-expanded', 'false');
66        btn.setAttribute('aria-controls', 'searchDesktop');
67        btn.addEventListener('click', () => this.open());
68      });
69
70      if (this._closeBtn) {
71        this._closeBtn.addEventListener('click', () => this.close());
72      }
73
74      // Клик по оверлею закрывает поиск
75      if (this._menuOverlay) {
76        this._menuOverlay.addEventListener('click', () => {
77          if (this._isOpen) this.close();
78        });
79      }
80    }
81
82    // aria-expanded — на всех триггерах, а не только на первом найденном
83    _setExpanded(value) {
84      this._openBtns.forEach(btn => btn.setAttribute('aria-expanded', String(value)));
85    }
86
87    open() {
88      if (!window.matchMedia('(min-width: 1280px)').matches) return;
89
90      this._previousFocus = document.activeElement;
91      this._isOpen = true;
92
93      this._overlay.classList.add('is-open');
94      this._setExpanded(true);
95      if (this._menuOverlay) this._menuOverlay.classList.add('is-visible');
96
97      if (this._state) this._state.hidden = false;
98      this._search.showChips();
99
100      document.addEventListener('keydown', this._boundKeydown);
101
102      requestAnimationFrame(() => {
103        requestAnimationFrame(() => {
104          if (this._input) this._input.focus();
105        });
106      });
107    }
108
109    close() {
110      this._isOpen = false;
111      this._overlay.classList.remove('is-open');
112
113      this._setExpanded(false);
114      if (this._menuOverlay) this._menuOverlay.classList.remove('is-visible');
115
116      document.removeEventListener('keydown', this._boundKeydown);
117
118      this._search.reset();
119      if (this._state) this._state.hidden = true;
120
121      if (this._previousFocus) {
122        this._previousFocus.focus();
123        this._previousFocus = null;
124      }
125    }
126
127    _onKeydown(e) {
128      if (e.key === 'Escape') {
129        this.close();
130        return;
131      }
132
133      if (e.key === 'Tab') {
134        const focusable = [...this._overlay.querySelectorAll(
135          'button:not([disabled]), [href], input, [tabindex="0"]'
136        )].filter(el => el.offsetParent !== null);
137
138        if (!focusable.length) return;
139        const first = focusable[0];
140        const last  = focusable[focusable.length - 1];
141
142        if (e.shiftKey && document.activeElement === first) {
143          e.preventDefault();
144          last.focus();
145        } else if (!e.shiftKey && document.activeElement === last) {
146          e.preventDefault();
147          first.focus();
148        }
149      }
150    }
151
152    destroy() {
153      document.removeEventListener('keydown', this._boundKeydown);
154      this._search.destroy();
155    }
156  }
157
158  document.addEventListener('DOMContentLoaded', () => {
159    if (document.getElementById('searchDesktop')) {
160      window._searchDesktop = new SearchDesktop();
161    }
162  });
163
164})();

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.