PageSourceSearch

https://www.vulcano.pt/resources_celestra/assets/Textfield-Tc3EtuZQ.js

js vulcano.pt collected 2026-10-04 16:09:49 UTC 11,646 bytes, 268 lines download raw bytes

1import { i as isDev } from './env-M5RuEq8k.js';
2import { S as SUGGESTIONS_OPEN, s as setComboboxExpanded, a as setFocusTrap, b as SUGGESTIONS, r as removeFocusTrap } from './_a11y-BbTmbSnv.js';
3
4if (!isDev) {
5    import('./main.js').then(function (n) { return n.o; });
6}
7// Scoped to the search field, not to the document: a page can hold several
8// search fields (header + Searchresult), and a global lookup handed the header's
9// list to the page search — including its aria state.
10const findSuggestions = (wrapper) => wrapper.querySelector(SUGGESTIONS);
11// Only the header search opens as an overlay above the page, so only there may
12// the Tab key be locked inside the search (see setFocusTrap).
13const isHeaderSearch = (wrapper) => Boolean(wrapper.closest(".Header"));
14// Keyboard navigation is wired up once per search field: fetchSuggestions runs on
15// every keystroke, and registering the listeners there stacked one handler per
16// request — a single ArrowDown then jumped several entries at once.
17const keyboardWired = new WeakSet();
18let currentFocusIndex = -1;
19const createElementSuggestions = (ariaLabel) => {
20    const element = document.createElement("ul");
21    element.classList.add("SearchSuggestions");
22    element.classList.add("-primary");
23    element.setAttribute("id", "resultlist");
24    element.setAttribute("role", "listbox");
25    element.setAttribute("aria-label", ariaLabel);
26    return element;
27};
28const createLink = (item, index, setsize) => {
29    return `
30    <a class="Link Link-arrow Link-integrated -size-m"
31      href="${item.resourceLocation}"
32      role="option"
33      aria-setsize="${setsize}"
34      aria-posinset="${index}">
35      <span class="Link_label">${item.name}</span>
36    </a>
37  `;
38};
39const createListItem = (item, index, setsize) => {
40    const li = document.createElement("li");
41    li.setAttribute("role", "presentation");
42    li.innerHTML = createLink(item, index, setsize);
43    const link = li.querySelector("a");
44    link.setAttribute("aria-selected", "false");
45    link.addEventListener("focusin", () => link.setAttribute("aria-selected", "true"));
46    link.addEventListener("focusout", () => link.setAttribute("aria-selected", "false"));
47    return li;
48};
49// Suggestions closed: hide the list, reset the combobox state on the field and
50// release the focus/dismiss handlers.
51const hideSuggestions = (wrapper, input) => {
52    const suggestionsContainer = findSuggestions(wrapper);
53    const wasOpen = wrapper.classList.contains(SUGGESTIONS_OPEN);
54    if (suggestionsContainer) {
55        suggestionsContainer.style.display = "none";
56        suggestionsContainer.innerHTML = "";
57    }
58    wrapper.classList.remove(SUGGESTIONS_OPEN);
59    currentFocusIndex = -1;
60    setComboboxExpanded(input, false);
61    // The focus/dismiss handlers exist once per document, so only release them if
62    // it was this field's list that was open — otherwise typing in a second search
63    // field would strip another, still open field of its handlers.
64    if (wasOpen)
65        removeFocusTrap();
66};
67const fetchSuggestions = (wrapper, input, autocompleteUrl) => {
68    var _a;
69    const dataAriaLabel = (_a = input.dataset.autocompleteAriaLabel) !== null && _a !== void 0 ? _a : "Suggestions";
70    fetch(autocompleteUrl)
71        .then((response) => response.json())
72        .then((data) => {
73        // Handle empty results early
74        if (!data || data.length === 0) {
75            hideSuggestions(wrapper, input);
76            return; // stop here, nothing to render
77        }
78        // Create or reuse suggestion container
79        let suggestionsContainer = findSuggestions(wrapper);
80        if (!suggestionsContainer) {
81            suggestionsContainer = createElementSuggestions(dataAriaLabel);
82            input.insertAdjacentElement("afterend", suggestionsContainer);
83        }
84        // Clear previous results
85        suggestionsContainer.innerHTML = "";
86        const limitedData = data.slice(0, 4);
87        const lastItem = data[data.length - 1];
88        const hasExtraLastItem = Boolean(lastItem && !limitedData.some((item) => item.name === lastItem.name));
89        // aria-setsize has to match what is actually rendered (max. 4 + the last
90        // item), otherwise a screen reader announces "1 of 5" on a list of two.
91        const setsize = limitedData.length + (hasExtraLastItem ? 1 : 0);
92        limitedData.forEach((item, index) => {
93            const li = createListItem(item, index + 1, setsize);
94            suggestionsContainer.appendChild(li);
95        });
96        if (hasExtraLastItem) {
97            const li = createListItem(lastItem, limitedData.length + 1, setsize);
98            suggestionsContainer.appendChild(li);
99        }
100        suggestionsContainer.style.display = "block";
101        wrapper.classList.add(SUGGESTIONS_OPEN);
102        currentFocusIndex = -1;
103        setComboboxExpanded(input, true);
104        setupKeyboardNavigation(wrapper, input);
105        // Wired up only once the list exists, so the trap knows the suggestion
106        // links as focusable elements. Outside the header it stays a plain
107        // dismiss handler (Escape / focus leaves the search), no Tab lock.
108        setFocusTrap(wrapper, isHeaderSearch(wrapper));
109    })
110        .catch((err) => console.error("Autocomplete error:", err));
111};
112const setupKeyboardNavigation = (wrapper, searchField) => {
113    if (keyboardWired.has(wrapper))
114        return;
115    keyboardWired.add(wrapper);
116    // The list is created/emptied on demand, so read it per key press instead of
117    // capturing it here.
118    const getItems = () => { var _a, _b; return Array.from((_b = (_a = findSuggestions(wrapper)) === null || _a === void 0 ? void 0 : _a.querySelectorAll("a")) !== null && _b !== void 0 ? _b : []); };
119    const moveFocus = (direction) => {
120        var _a;
121        const items = getItems();
122        const total = items.length;
123        if (total === 0)
124            return;
125        if (direction === "down") {
126            currentFocusIndex = (currentFocusIndex + 1) % (total + 1);
127        }
128        else {
129            currentFocusIndex = (currentFocusIndex - 1 + (total + 1)) % (total + 1);
130        }
131        if (currentFocusIndex === 0) {
132            searchField.focus();
133        }
134        else {
135            (_a = items[currentFocusIndex - 1]) === null || _a === void 0 ? void 0 : _a.focus();
136        }
137    };
138    // One listener for the field and the suggestions list (both live inside the
139    // wrapper), so it survives the list being re-created.
140    wrapper.addEventListener("keydown", (e) => {
141        const inList = Boolean(e.target.closest(SUGGESTIONS));
142        // Only the field and the list navigate — the reset/submit buttons keep their
143        // default key behaviour.
144        if (e.target !== searchField && !inList)
145            return;
146        if (e.key === "ArrowDown" || e.key === "ArrowUp") {
147            // Nothing to navigate (no list open): leave the arrow keys to the browser
148            // instead of swallowing them.
149            if (getItems().length === 0)
150                return;
151            e.preventDefault();
152            if (currentFocusIndex === -1)
153                currentFocusIndex = 0;
154            moveFocus(e.key === "ArrowDown" ? "down" : "up");
155            return;
156        }
157        // Escape inside the list returns the focus to the field; closing the list is
158        // handled by the dismiss handler in _a11y. In the field itself Escape stays
159        // untouched (native "clear search field").
160        if (e.key === "Escape" && inList) {
161            e.preventDefault();
162            searchField.focus();
163            currentFocusIndex = -1;
164        }
165    });
166};
167let debounceTimer;
168const autocomplete = (wrapper) => {
169    clearTimeout(debounceTimer);
170    const input = wrapper.querySelector("input");
171    if (!input)
172        return;
173    debounceTimer = window.setTimeout(() => {
174        const query = input.value.trim();
175        // Hide suggestions when query too short or empty
176        if (query.length < 3) {
177            hideSuggestions(wrapper, input);
178            return; // stop here
179        }
180        if (input.dataset.autocompleteUrl) {
181            // Encoded, otherwise a term containing "&", "+" or "#" would break the
182            // query string ("Junkers & Bosch" arrived at the backend as "Junkers").
183            const autocompleteUrl = `${input.dataset.autocompleteUrl}?term=${encodeURIComponent(query)}`;
184            fetchSuggestions(wrapper, input, autocompleteUrl);
185        }
186    }, 250);
187};
188
189if (!isDev) {
190    import('./main.js').then(function (n) { return n.p; });
191}
192const valueModificator = () => {
193    const elements = document.querySelectorAll(".Textfield-valueModificator");
194    elements.forEach((element) => {
195        const input = element.querySelector(".Input-number");
196        const stepUpBtn = element.querySelector(".-step-up");
197        const stepDownBtn = element.querySelector(".-step-down");
198        stepUpBtn === null || stepUpBtn === void 0 ? void 0 : stepUpBtn.addEventListener("click", (event) => {
199            event.preventDefault();
200            input.stepUp();
201        });
202        stepDownBtn === null || stepDownBtn === void 0 ? void 0 : stepDownBtn.addEventListener("click", (event) => {
203            event.preventDefault();
204            input.stepDown();
205        });
206    });
207};
208if (document.querySelector(".Textfield-valueModificator")) {
209    valueModificator();
210}
211const searchbox = document.querySelectorAll(".SearchBox");
212const textfieldSearch = document.querySelectorAll(".Textfield-search");
213const toggleResetBtn = (input, resetBtn) => {
214    if (input.value.length > 0) {
215        resetBtn.style.display = "";
216    }
217    else {
218        resetBtn.style.display = "none";
219    }
220};
221[...searchbox, ...textfieldSearch].forEach((element) => {
222    const box = element;
223    const resetBtn = box.querySelector(".FormButton[type='reset']");
224    const input = box.querySelector("input");
225    if (!resetBtn || !input)
226        return;
227    // Initially hide reset button for non-header search boxes
228    const isHeaderSearch = box.closest(".Header");
229    if (!isHeaderSearch) {
230        resetBtn.style.display = "none";
231    }
232    input === null || input === void 0 ? void 0 : input.addEventListener("input", (e) => {
233        const current = e.currentTarget;
234        // Toggle reset button visibility for non-header search boxes
235        if (!isHeaderSearch) {
236            toggleResetBtn(input, resetBtn);
237        }
238        if (current.classList.contains("Input-autocomplete")) {
239            autocomplete(box);
240        }
241    });
242    resetBtn === null || resetBtn === void 0 ? void 0 : resetBtn.addEventListener("click", () => {
243        var _a;
244        input.value = "";
245        // Hide reset button for non-header search boxes
246        if (!isHeaderSearch) {
247            resetBtn.style.display = "none";
248        }
249        // Only keep focus for header search, blur for other search boxes
250        if (isHeaderSearch) {
251            input.focus();
252        }
253        else {
254            input.blur();
255        }
256        // The combobox state has to be reset even when no list was open, otherwise
257        // the field can stay at aria-expanded="true" after clearing it.
258        const wasOpen = box.classList.contains(SUGGESTIONS_OPEN);
259        (_a = box.querySelector(SUGGESTIONS)) === null || _a === void 0 ? void 0 : _a.remove();
260        box.classList.remove(SUGGESTIONS_OPEN);
261        setComboboxExpanded(input, false);
262        // The dismiss handlers exist once per document — only release them if this
263        // field's list was the open one.
264        if (wasOpen)
265            removeFocusTrap();
266    });
267});
268//# sourceMappingURL=Textfield-Tc3EtuZQ.js.map

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.