I’ll build this as a powerful standalone search chunk for RX Theme: safe DOM handling, debounce, keyboard support, suggestions, recent searches, highlighting, accessibility, and optional WordPress REST API integration.
Yes friend. Put this full advanced code inside:
assets/static-js/static-chunks-js/chunk-004-search.js
This file is written as a safe standalone RX Theme search module. It supports:
Search overlay, instant search, debounce, REST API search, keyboard navigation, recent searches, popular searches, search highlight, accessibility, body scroll lock, escape close, outside click close, no-result message, loading state, mobile friendly behavior, and auto-init.
/*!
* RX Theme - Advanced Search Chunk
* File: assets/static-js/static-chunks-js/chunk-004-search.js
* Theme: RX Theme
* Author: RxHarun
*
* Purpose:
* Advanced front-end search system for WordPress theme.
*
* Supported HTML selectors:
* .rx-search-toggle
* .rx-search-close
* .rx-search-overlay
* .rx-search-panel
* .rx-search-input
* .rx-search-results
* .rx-search-form
* .rx-search-submit
* .rx-search-clear
* .rx-search-popular
* .rx-search-recent
*/
(function () {
"use strict";
/**
* Prevent duplicate loading.
*/
if (window.RXThemeSearchChunkLoaded) {
return;
}
window.RXThemeSearchChunkLoaded = true;
/**
* Main namespace.
*/
window.RXTheme = window.RXTheme || {};
/**
* RX Advanced Search Module.
*/
var RXSearch = {
version: "1.0.0",
config: {
selectors: {
toggle: ".rx-search-toggle",
close: ".rx-search-close",
overlay: ".rx-search-overlay",
panel: ".rx-search-panel",
input: ".rx-search-input",
form: ".rx-search-form",
results: ".rx-search-results",
submit: ".rx-search-submit",
clear: ".rx-search-clear",
popular: ".rx-search-popular",
recent: ".rx-search-recent",
bodyOpenClass: "rx-search-is-open",
overlayOpenClass: "is-open",
activeClass: "is-active",
loadingClass: "is-loading",
hiddenClass: "is-hidden"
},
search: {
minChars: 2,
debounceDelay: 350,
maxResults: 8,
maxRecent: 8,
requestTimeout: 10000,
highlightTerms: true,
cacheResults: true,
cacheTTL: 5 * 60 * 1000,
allowEmptySubmit: false,
submitToWordPressSearch: true,
wpSearchPath: "/?s="
},
rest: {
enabled: true,
postsEndpoint: "/wp-json/wp/v2/search",
postTypes: ["post", "page"],
perPage: 8,
subtype: "any"
},
storage: {
enabled: true,
key: "rx_theme_recent_searches"
},
popularSearches: [
"back pain",
"neck pain",
"knee pain",
"diabetes",
"hypertension",
"heart disease",
"arthritis",
"vitamin D"
],
messages: {
start: "Start typing to search RX medical articles.",
loading: "Searching...",
noResults: "No results found. Try another keyword.",
error: "Search is temporarily unavailable. Please try again.",
tooShort: "Type at least 2 characters.",
empty: "Please enter a search term."
}
},
state: {
initialized: false,
isOpen: false,
lastQuery: "",
activeIndex: -1,
results: [],
cache: {},
abortController: null,
debounceTimer: null,
lastFocusedElement: null
},
elements: {
toggles: [],
closes: [],
overlay: null,
panel: null,
input: null,
form: null,
results: null,
submit: null,
clear: null,
popular: null,
recent: null
},
/**
* Initialize module.
*/
init: function () {
if (this.state.initialized) {
return;
}
this.collectElements();
if (!this.elements.input && !this.elements.overlay) {
return;
}
this.bindEvents();
this.setupAccessibility();
this.renderInitialState();
this.renderPopularSearches();
this.renderRecentSearches();
this.state.initialized = true;
this.dispatch("rxSearchReady", {
version: this.version
});
},
/**
* Collect DOM elements.
*/
collectElements: function () {
var s = this.config.selectors;
this.elements.toggles = Array.prototype.slice.call(document.querySelectorAll(s.toggle));
this.elements.closes = Array.prototype.slice.call(document.querySelectorAll(s.close));
this.elements.overlay = document.querySelector(s.overlay);
this.elements.panel = document.querySelector(s.panel);
this.elements.input = document.querySelector(s.input);
this.elements.form = document.querySelector(s.form);
this.elements.results = document.querySelector(s.results);
this.elements.submit = document.querySelector(s.submit);
this.elements.clear = document.querySelector(s.clear);
this.elements.popular = document.querySelector(s.popular);
this.elements.recent = document.querySelector(s.recent);
},
/**
* Bind all events.
*/
bindEvents: function () {
var self = this;
this.elements.toggles.forEach(function (button) {
button.addEventListener("click", function (event) {
event.preventDefault();
self.open();
});
});
this.elements.closes.forEach(function (button) {
button.addEventListener("click", function (event) {
event.preventDefault();
self.close();
});
});
if (this.elements.overlay) {
this.elements.overlay.addEventListener("click", function (event) {
if (event.target === self.elements.overlay) {
self.close();
}
});
}
if (this.elements.panel) {
this.elements.panel.addEventListener("click", function (event) {
event.stopPropagation();
});
}
if (this.elements.input) {
this.elements.input.addEventListener("input", function () {
self.handleInput(this.value);
});
this.elements.input.addEventListener("keydown", function (event) {
self.handleInputKeydown(event);
});
this.elements.input.addEventListener("focus", function () {
self.renderRecentSearches();
});
}
if (this.elements.form) {
this.elements.form.addEventListener("submit", function (event) {
self.handleSubmit(event);
});
}
if (this.elements.clear) {
this.elements.clear.addEventListener("click", function (event) {
event.preventDefault();
self.clearSearch();
});
}
document.addEventListener("keydown", function (event) {
self.handleDocumentKeydown(event);
});
document.addEventListener("click", function (event) {
self.handleDocumentClick(event);
});
window.addEventListener("resize", function () {
self.adjustPanelHeight();
});
window.addEventListener("orientationchange", function () {
self.adjustPanelHeight();
});
},
/**
* Accessibility setup.
*/
setupAccessibility: function () {
var overlay = this.elements.overlay;
var panel = this.elements.panel;
var input = this.elements.input;
var results = this.elements.results;
if (overlay) {
overlay.setAttribute("aria-hidden", "true");
}
if (panel) {
panel.setAttribute("role", "dialog");
panel.setAttribute("aria-modal", "true");
panel.setAttribute("aria-label", "Search");
}
if (input) {
input.setAttribute("autocomplete", "off");
input.setAttribute("spellcheck", "false");
input.setAttribute("aria-label", "Search articles");
input.setAttribute("aria-autocomplete", "list");
input.setAttribute("aria-expanded", "false");
if (!input.id) {
input.id = "rx-search-input";
}
}
if (results) {
results.setAttribute("role", "listbox");
if (!results.id) {
results.id = "rx-search-results";
}
if (input) {
input.setAttribute("aria-controls", results.id);
}
}
},
/**
* Open search overlay.
*/
open: function () {
var s = this.config.selectors;
this.state.lastFocusedElement = document.activeElement;
this.state.isOpen = true;
document.body.classList.add(s.bodyOpenClass);
if (this.elements.overlay) {
this.elements.overlay.classList.add(s.overlayOpenClass);
this.elements.overlay.setAttribute("aria-hidden", "false");
}
this.adjustPanelHeight();
if (this.elements.input) {
setTimeout(function () {
RXSearch.elements.input.focus();
}, 40);
}
this.dispatch("rxSearchOpen", {});
},
/**
* Close search overlay.
*/
close: function () {
var s = this.config.selectors;
this.state.isOpen = false;
this.state.activeIndex = -1;
document.body.classList.remove(s.bodyOpenClass);
if (this.elements.overlay) {
this.elements.overlay.classList.remove(s.overlayOpenClass);
this.elements.overlay.setAttribute("aria-hidden", "true");
}
if (this.elements.input) {
this.elements.input.setAttribute("aria-expanded", "false");
}
if (
this.state.lastFocusedElement &&
typeof this.state.lastFocusedElement.focus === "function"
) {
this.state.lastFocusedElement.focus();
}
this.dispatch("rxSearchClose", {});
},
/**
* Toggle overlay.
*/
toggle: function () {
if (this.state.isOpen) {
this.close();
} else {
this.open();
}
},
/**
* Input handler.
*/
handleInput: function (rawValue) {
var self = this;
var query = this.cleanQuery(rawValue);
this.state.lastQuery = query;
this.state.activeIndex = -1;
this.updateClearButton(query);
clearTimeout(this.state.debounceTimer);
if (!query.length) {
this.renderInitialState();
return;
}
if (query.length < this.config.search.minChars) {
this.renderMessage(this.config.messages.tooShort);
return;
}
this.renderLoading();
this.state.debounceTimer = setTimeout(function () {
self.performSearch(query);
}, this.config.search.debounceDelay);
},
/**
* Submit handler.
*/
handleSubmit: function (event) {
var query = this.elements.input ? this.cleanQuery(this.elements.input.value) : "";
if (!query && !this.config.search.allowEmptySubmit) {
event.preventDefault();
this.renderMessage(this.config.messages.empty);
return;
}
if (query) {
this.saveRecentSearch(query);
}
var activeItem = this.getActiveResultItem();
if (activeItem) {
event.preventDefault();
this.goToResult(activeItem);
return;
}
if (this.config.search.submitToWordPressSearch) {
event.preventDefault();
window.location.href = this.config.search.wpSearchPath + encodeURIComponent(query);
}
},
/**
* Keyboard handling inside input.
*/
handleInputKeydown: function (event) {
var key = event.key;
if (key === "ArrowDown") {
event.preventDefault();
this.moveActive(1);
}
if (key === "ArrowUp") {
event.preventDefault();
this.moveActive(-1);
}
if (key === "Enter") {
var activeItem = this.getActiveResultItem();
if (activeItem) {
event.preventDefault();
this.goToResult(activeItem);
}
}
if (key === "Escape") {
event.preventDefault();
this.close();
}
},
/**
* Global keyboard events.
*/
handleDocumentKeydown: function (event) {
if (event.key === "Escape" && this.state.isOpen) {
this.close();
}
/**
* Optional shortcut:
* Press "/" to open search when not typing.
*/
if (event.key === "/" && !this.state.isOpen && !this.isTypingContext(event.target)) {
event.preventDefault();
this.open();
}
},
/**
* Global click handler for dynamic result buttons.
*/
handleDocumentClick: function (event) {
var target = event.target;
var popularButton = this.closest(target, "[data-rx-popular-search]");
var recentButton = this.closest(target, "[data-rx-recent-search]");
var removeRecentButton = this.closest(target, "[data-rx-remove-recent]");
var resultItem = this.closest(target, "[data-rx-search-result]");
if (popularButton) {
event.preventDefault();
this.setInputAndSearch(popularButton.getAttribute("data-rx-popular-search"));
return;
}
if (recentButton) {
event.preventDefault();
this.setInputAndSearch(recentButton.getAttribute("data-rx-recent-search"));
return;
}
if (removeRecentButton) {
event.preventDefault();
event.stopPropagation();
this.removeRecentSearch(removeRecentButton.getAttribute("data-rx-remove-recent"));
return;
}
if (resultItem) {
this.saveRecentSearch(this.state.lastQuery);
}
},
/**
* Perform search.
*/
performSearch: function (query) {
var self = this;
if (this.config.search.cacheResults) {
var cached = this.getCachedResult(query);
if (cached) {
this.state.results = cached;
this.renderResults(cached, query);
return;
}
}
if (!this.config.rest.enabled) {
this.fallbackSearch(query);
return;
}
this.abortPreviousRequest();
this.state.abortController = this.createAbortController();
var endpoint = this.buildRestUrl(query);
var fetchOptions = {
method: "GET",
credentials: "same-origin",
headers: {
Accept: "application/json"
}
};
if (this.state.abortController) {
fetchOptions.signal = this.state.abortController.signal;
}
var timeoutId = setTimeout(function () {
self.abortPreviousRequest();
}, this.config.search.requestTimeout);
fetch(endpoint, fetchOptions)
.then(function (response) {
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error("Search request failed");
}
return response.json();
})
.then(function (data) {
var results = self.normalizeResults(data);
self.state.results = results;
if (self.config.search.cacheResults) {
self.setCachedResult(query, results);
}
self.renderResults(results, query);
})
.catch(function (error) {
clearTimeout(timeoutId);
if (error && error.name === "AbortError") {
return;
}
self.renderError();
});
},
/**
* Build REST URL.
*/
buildRestUrl: function (query) {
var rest = this.config.rest;
var url = rest.postsEndpoint;
var params = new URLSearchParams();
params.set("search", query);
params.set("per_page", String(rest.perPage));
params.set("subtype", rest.subtype || "any");
if (rest.postTypes && rest.postTypes.length) {
params.set("type", rest.postTypes.join(","));
}
return url + "?" + params.toString();
},
/**
* Normalize WP REST search result.
*/
normalizeResults: function (data) {
if (!Array.isArray(data)) {
return [];
}
return data.slice(0, this.config.search.maxResults).map(function (item) {
return {
id: item.id || "",
title: item.title || item.name || "Untitled",
url: item.url || item.link || "#",
type: item.subtype || item.type || "post",
excerpt: item.excerpt || "",
date: item.date || "",
raw: item
};
});
},
/**
* Fallback search redirect.
*/
fallbackSearch: function (query) {
var url = this.config.search.wpSearchPath + encodeURIComponent(query);
this.renderResults(
[
{
id: "fallback",
title: 'Search for "' + query + '"',
url: url,
type: "search",
excerpt: "Open full WordPress search results."
}
],
query
);
},
/**
* Render initial empty state.
*/
renderInitialState: function () {
this.state.results = [];
this.state.activeIndex = -1;
this.renderMessage(this.config.messages.start);
if (this.elements.input) {
this.elements.input.setAttribute("aria-expanded", "false");
}
},
/**
* Render loading.
*/
renderLoading: function () {
var results = this.elements.results;
if (!results) {
return;
}
results.classList.add(this.config.selectors.loadingClass);
results.innerHTML =
'<div class="rx-search-status" role="status" aria-live="polite">' +
'<span class="rx-search-spinner" aria-hidden="true"></span>' +
'<span>' + this.escapeHTML(this.config.messages.loading) + '</span>' +
"</div>";
},
/**
* Render error.
*/
renderError: function () {
this.renderMessage(this.config.messages.error, "error");
},
/**
* Render message.
*/
renderMessage: function (message, type) {
var results = this.elements.results;
if (!results) {
return;
}
results.classList.remove(this.config.selectors.loadingClass);
results.innerHTML =
'<div class="rx-search-message rx-search-message-' +
this.escapeAttribute(type || "info") +
'" role="status" aria-live="polite">' +
this.escapeHTML(message) +
"</div>";
},
/**
* Render search results.
*/
renderResults: function (items, query) {
var results = this.elements.results;
if (!results) {
return;
}
results.classList.remove(this.config.selectors.loadingClass);
if (!items || !items.length) {
this.renderMessage(this.config.messages.noResults);
return;
}
var html = '<div class="rx-search-result-list">';
for (var i = 0; i < items.length; i++) {
html += this.resultTemplate(items[i], i, query);
}
html += "</div>";
results.innerHTML = html;
if (this.elements.input) {
this.elements.input.setAttribute("aria-expanded", "true");
}
},
/**
* Result HTML template.
*/
resultTemplate: function (item, index, query) {
var title = item.title || "Untitled";
var excerpt = item.excerpt || "";
var type = item.type || "post";
var url = item.url || "#";
if (this.config.search.highlightTerms && query) {
title = this.highlight(title, query);
excerpt = this.highlight(excerpt, query);
} else {
title = this.escapeHTML(title);
excerpt = this.escapeHTML(excerpt);
}
return (
'<a class="rx-search-result-item" ' +
'href="' + this.escapeAttribute(url) + '" ' +
'role="option" ' +
'aria-selected="false" ' +
'data-rx-search-result ' +
'data-rx-result-index="' + index + '">' +
'<span class="rx-search-result-title">' + title + "</span>" +
'<span class="rx-search-result-meta">' + this.escapeHTML(type) + "</span>" +
(excerpt
? '<span class="rx-search-result-excerpt">' + excerpt + "</span>"
: "") +
"</a>"
);
},
/**
* Render popular searches.
*/
renderPopularSearches: function () {
var wrapper = this.elements.popular;
if (!wrapper || !this.config.popularSearches.length) {
return;
}
var html = '<div class="rx-search-chip-group" aria-label="Popular searches">';
this.config.popularSearches.forEach(function (term) {
html +=
'<button type="button" class="rx-search-chip" data-rx-popular-search="' +
RXSearch.escapeAttribute(term) +
'">' +
RXSearch.escapeHTML(term) +
"</button>";
});
html += "</div>";
wrapper.innerHTML = html;
},
/**
* Render recent searches.
*/
renderRecentSearches: function () {
var wrapper = this.elements.recent;
if (!wrapper || !this.config.storage.enabled) {
return;
}
var searches = this.getRecentSearches();
if (!searches.length) {
wrapper.innerHTML = "";
return;
}
var html = '<div class="rx-search-recent-list" aria-label="Recent searches">';
searches.forEach(function (term) {
html +=
'<div class="rx-search-recent-item">' +
'<button type="button" class="rx-search-recent-term" data-rx-recent-search="' +
RXSearch.escapeAttribute(term) +
'">' +
RXSearch.escapeHTML(term) +
"</button>" +
'<button type="button" class="rx-search-recent-remove" aria-label="Remove ' +
RXSearch.escapeAttribute(term) +
'" data-rx-remove-recent="' +
RXSearch.escapeAttribute(term) +
'">×</button>' +
"</div>";
});
html += "</div>";
wrapper.innerHTML = html;
},
/**
* Set input and search.
*/
setInputAndSearch: function (query) {
query = this.cleanQuery(query);
if (!query || !this.elements.input) {
return;
}
this.elements.input.value = query;
this.elements.input.focus();
this.handleInput(query);
this.saveRecentSearch(query);
},
/**
* Clear search.
*/
clearSearch: function () {
if (this.elements.input) {
this.elements.input.value = "";
this.elements.input.focus();
}
this.state.lastQuery = "";
this.state.activeIndex = -1;
this.updateClearButton("");
this.renderInitialState();
},
/**
* Update clear button state.
*/
updateClearButton: function (query) {
if (!this.elements.clear) {
return;
}
if (query.length) {
this.elements.clear.classList.remove(this.config.selectors.hiddenClass);
this.elements.clear.setAttribute("aria-hidden", "false");
} else {
this.elements.clear.classList.add(this.config.selectors.hiddenClass);
this.elements.clear.setAttribute("aria-hidden", "true");
}
},
/**
* Move active result by keyboard.
*/
moveActive: function (direction) {
var items = this.getResultItems();
if (!items.length) {
return;
}
this.state.activeIndex += direction;
if (this.state.activeIndex < 0) {
this.state.activeIndex = items.length - 1;
}
if (this.state.activeIndex >= items.length) {
this.state.activeIndex = 0;
}
this.updateActiveResult(items);
},
/**
* Update active result UI.
*/
updateActiveResult: function (items) {
var activeClass = this.config.selectors.activeClass;
items.forEach(function (item, index) {
var isActive = index === RXSearch.state.activeIndex;
item.classList.toggle(activeClass, isActive);
item.setAttribute("aria-selected", isActive ? "true" : "false");
if (isActive) {
item.scrollIntoView({
block: "nearest"
});
}
});
},
/**
* Get result items.
*/
getResultItems: function () {
if (!this.elements.results) {
return [];
}
return Array.prototype.slice.call(
this.elements.results.querySelectorAll("[data-rx-search-result]")
);
},
/**
* Get active result item.
*/
getActiveResultItem: function () {
var items = this.getResultItems();
if (this.state.activeIndex < 0 || this.state.activeIndex >= items.length) {
return null;
}
return items[this.state.activeIndex];
},
/**
* Go to selected result.
*/
goToResult: function (item) {
if (!item) {
return;
}
var href = item.getAttribute("href");
if (href) {
this.saveRecentSearch(this.state.lastQuery);
window.location.href = href;
}
},
/**
* Save recent search.
*/
saveRecentSearch: function (query) {
if (!this.config.storage.enabled) {
return;
}
query = this.cleanQuery(query);
if (!query) {
return;
}
var searches = this.getRecentSearches();
searches = searches.filter(function (item) {
return item.toLowerCase() !== query.toLowerCase();
});
searches.unshift(query);
searches = searches.slice(0, this.config.search.maxRecent);
try {
localStorage.setItem(this.config.storage.key, JSON.stringify(searches));
} catch (error) {
return;
}
this.renderRecentSearches();
},
/**
* Get recent searches.
*/
getRecentSearches: function () {
if (!this.config.storage.enabled) {
return [];
}
try {
var raw = localStorage.getItem(this.config.storage.key);
var parsed = raw ? JSON.parse(raw) : [];
if (!Array.isArray(parsed)) {
return [];
}
return parsed.filter(function (item) {
return typeof item === "string" && item.trim().length;
});
} catch (error) {
return [];
}
},
/**
* Remove recent search.
*/
removeRecentSearch: function (query) {
var searches = this.getRecentSearches();
searches = searches.filter(function (item) {
return item.toLowerCase() !== query.toLowerCase();
});
try {
localStorage.setItem(this.config.storage.key, JSON.stringify(searches));
} catch (error) {
return;
}
this.renderRecentSearches();
},
/**
* Cache result.
*/
setCachedResult: function (query, results) {
this.state.cache[query.toLowerCase()] = {
time: Date.now(),
results: results
};
},
/**
* Get cached result.
*/
getCachedResult: function (query) {
var key = query.toLowerCase();
var cached = this.state.cache[key];
if (!cached) {
return null;
}
if (Date.now() - cached.time > this.config.search.cacheTTL) {
delete this.state.cache[key];
return null;
}
return cached.results;
},
/**
* Abort previous fetch request.
*/
abortPreviousRequest: function () {
if (
this.state.abortController &&
typeof this.state.abortController.abort === "function"
) {
this.state.abortController.abort();
}
this.state.abortController = null;
},
/**
* Create abort controller safely.
*/
createAbortController: function () {
if ("AbortController" in window) {
return new AbortController();
}
return null;
},
/**
* Clean search query.
*/
cleanQuery: function (query) {
if (typeof query !== "string") {
return "";
}
return query
.replace(/\s+/g, " ")
.replace(/[<>]/g, "")
.trim();
},
/**
* Highlight query terms.
*/
highlight: function (text, query) {
text = this.escapeHTML(String(text || ""));
query = this.cleanQuery(query);
if (!query) {
return text;
}
var words = query
.split(" ")
.filter(function (word) {
return word.length > 1;
})
.map(this.escapeRegExp);
if (!words.length) {
return text;
}
var regex = new RegExp("(" + words.join("|") + ")", "gi");
return text.replace(regex, '<mark class="rx-search-highlight">$1</mark>');
},
/**
* Escape HTML.
*/
escapeHTML: function (value) {
return String(value || "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
},
/**
* Escape HTML attribute.
*/
escapeAttribute: function (value) {
return this.escapeHTML(value).replace(/`/g, "`");
},
/**
* Escape regex.
*/
escapeRegExp: function (value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
},
/**
* Closest helper.
*/
closest: function (element, selector) {
if (!element || element === document) {
return null;
}
if (element.closest) {
return element.closest(selector);
}
while (element && element.nodeType === 1) {
if (this.matches(element, selector)) {
return element;
}
element = element.parentElement;
}
return null;
},
/**
* Matches helper.
*/
matches: function (element, selector) {
var proto = Element.prototype;
var fn =
proto.matches ||
proto.msMatchesSelector ||
proto.webkitMatchesSelector;
if (!fn) {
return false;
}
return fn.call(element, selector);
},
/**
* Check typing context.
*/
isTypingContext: function (element) {
if (!element) {
return false;
}
var tag = element.tagName ? element.tagName.toLowerCase() : "";
return (
tag === "input" ||
tag === "textarea" ||
tag === "select" ||
element.isContentEditable
);
},
/**
* Adjust panel height for mobile browsers.
*/
adjustPanelHeight: function () {
if (!this.elements.overlay) {
return;
}
var height = window.innerHeight || document.documentElement.clientHeight;
this.elements.overlay.style.setProperty("--rx-search-vh", height + "px");
},
/**
* Dispatch custom event.
*/
dispatch: function (name, detail) {
var event;
try {
event = new CustomEvent(name, {
detail: detail || {}
});
} catch (error) {
event = document.createEvent("CustomEvent");
event.initCustomEvent(name, true, true, detail || {});
}
document.dispatchEvent(event);
}
};
/**
* Expose globally.
*/
window.RXTheme.search = RXSearch;
/**
* Auto init.
*/
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {
RXSearch.init();
});
} else {
RXSearch.init();
}
})();
Use this minimum HTML in your theme header/search template:
<button class="rx-search-toggle" type="button" aria-label="Open search">
Search
</button>
<div class="rx-search-overlay">
<div class="rx-search-panel">
<button class="rx-search-close" type="button" aria-label="Close search">×</button>
<form class="rx-search-form" role="search">
<input
class="rx-search-input"
type="search"
name="s"
placeholder="Search medical articles..."
/>
<button class="rx-search-submit" type="submit">
Search
</button>
<button class="rx-search-clear is-hidden" type="button" aria-label="Clear search">
Clear
</button>
</form>
<div class="rx-search-popular"></div>
<div class="rx-search-recent"></div>
<div class="rx-search-results"></div>
</div>
</div>
Add this CSS later in your search CSS file:
.rx-search-overlay {
position: fixed;
inset: 0;
z-index: 99999;
display: none;
min-height: var(--rx-search-vh, 100vh);
background: rgba(0, 0, 0, 0.58);
}
.rx-search-overlay.is-open {
display: block;
}
.rx-search-is-open {
overflow: hidden;
}
.rx-search-panel {
width: min(760px, calc(100% - 32px));
margin: 48px auto;
padding: 24px;
border-radius: 18px;
background: #fff;
}
.rx-search-form {
display: flex;
gap: 10px;
}
.rx-search-input {
flex: 1;
width: 100%;
min-height: 46px;
padding: 0 14px;
}
.rx-search-result-list {
margin-top: 18px;
}
.rx-search-result-item {
display: block;
padding: 14px;
border-bottom: 1px solid #eee;
text-decoration: none;
}
.rx-search-result-item.is-active,
.rx-search-result-item:hover {
background: #f5f7fb;
}
.rx-search-result-title {
display: block;
font-weight: 700;
}
.rx-search-result-meta {
display: block;
margin-top: 4px;
font-size: 12px;
opacity: 0.7;
}
.rx-search-result-excerpt {
display: block;
margin-top: 6px;
font-size: 14px;
}
.rx-search-highlight {
padding: 0 2px;
}
.rx-search-chip-group,
.rx-search-recent-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 16px;
}
.rx-search-chip,
.rx-search-recent-term,
.rx-search-recent-remove {
cursor: pointer;
}
.rx-search-message,
.rx-search-status {
padding: 18px 0;
}
.is-hidden {
display: none !important;
}
Then enqueue it in your RX Theme:
wp_enqueue_script(
'rx-theme-search-chunk',
get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-004-search.js',
array(),
filemtime(get_template_directory() . '/assets/static-js/static-chunks-js/chunk-004-search.js'),
true
);
For performance, this file is best loaded with defer or in footer. Since wp_enqueue_script(..., true) loads it near footer, it is already good for RX Theme.