@progress/sitefinity-nextjs-sdk
Version:
Provides OOB widgets developed using the Next.js framework, which includes an abstraction layer for Sitefinity communication. Additionally, it offers an expanded API, typings, and tools for further development and integration.
227 lines (226 loc) • 10.6 kB
JavaScript
'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import React from 'react';
import { classNames } from '../../editor/utils/classNames';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { RootUrlService } from '../../rest-sdk/root-url.service';
const dataSfItemAttribute = 'data-sfitem';
const activeAttribute = 'data-sf-active';
const SUGGESTIONS_TRIGGER_CHAR_COUNT = 3;
const DEBOUNCE_DELAY = 300;
export function AskBoxDefaultView(props) {
const isEdit = props.widgetContext.requestContext.isEdit;
const [searchItems, setSearchItems] = React.useState([]);
const [dropDownWidth, setDropDownWidth] = React.useState(undefined);
const [dropDownShow, setDropDownShow] = React.useState(false);
const inputRef = React.useRef(null);
const dropdownRef = React.useRef(null);
const debounceRef = React.useRef(null);
const abortControllerRef = React.useRef(null);
const inputId = React.useId();
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
const activeClass = props.activeClass;
const suggestionsList = props.suggestions ? JSON.parse(props.suggestions) : [];
const handleShowDropdown = () => {
const inputWidth = inputRef.current?.clientWidth;
setDropDownWidth(inputWidth);
setDropDownShow(true);
};
const handleHideDropdown = (clear = true) => {
if (clear) {
setSearchItems([]);
}
setDropDownWidth(undefined);
setDropDownShow(false);
};
const buildResultsUrl = (query) => {
const knowledgeBoxName = props.knowledgeBoxName || '';
const searchConfigurationName = props.searchConfigurationName;
const trimmedQuery = query.trim();
if (props.resultsPageUrl) {
const separator = props.resultsPageUrl.indexOf('?') === -1 ? '?' : '&';
let url = props.resultsPageUrl + separator + 'knowledgeBoxName=' + encodeURIComponent(knowledgeBoxName);
url += '&searchQuery=' + encodeURIComponent(trimmedQuery);
if (searchConfigurationName) {
url += '&searchConfigurationName=' + encodeURIComponent(searchConfigurationName);
}
return url;
}
else {
const params = new URLSearchParams(searchParams.toString());
params.set('knowledgeBoxName', knowledgeBoxName);
params.set('searchQuery', trimmedQuery);
if (searchConfigurationName) {
params.set('searchConfigurationName', searchConfigurationName);
}
else {
params.delete('searchConfigurationName');
}
params.delete('page');
return pathname + '?' + params.toString();
}
};
const sendAnalytics = (query) => {
if (window.DataIntelligenceSubmitScript) {
window.DataIntelligenceSubmitScript._client.fetchClient.sendInteraction({
P: 'Search for',
O: query,
OM: { PageUrl: location.href }
});
}
};
const navigateToResults = () => {
const query = inputRef.current?.value || '';
if (query && query.trim() && props.knowledgeBoxName) {
if (debounceRef.current) {
clearTimeout(debounceRef.current);
debounceRef.current = null;
}
if (abortControllerRef.current) {
abortControllerRef.current.abort();
abortControllerRef.current = null;
}
sendAnalytics(query.trim());
handleHideDropdown();
router.push(buildResultsUrl(query));
}
};
const getSuggestions = (input) => {
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
debounceRef.current = setTimeout(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;
const requestUrl = `/${RootUrlService.getWebServicePath()}/Default.GetPARAGSuggestions()` +
'?knowledgeBoxName=' + encodeURIComponent(props.knowledgeBoxName || '') +
'&searchQuery=' + encodeURIComponent(input.value);
fetch(requestUrl, { signal })
.then(res => {
if (res.status === 200) {
res.json().then((data) => {
const items = data.value || [];
setSearchItems(items);
if (items.length) {
handleShowDropdown();
}
else {
handleHideDropdown();
}
});
}
else {
handleHideDropdown();
}
})
.catch((err) => {
if (err?.name !== 'AbortError') {
handleHideDropdown();
}
});
}, DEBOUNCE_DELAY);
};
const inputKeydownHandler = (e) => {
if (e.keyCode === 13 || e.code === 'Enter') {
navigateToResults();
}
};
const inputKeyupHandler = (e) => {
const code = e.code;
const keyCode = e.keyCode;
if (code !== 'ArrowUp' && code !== 'ArrowDown' && code !== 'Escape' && keyCode !== 13) {
const searchText = e.target.value.trim();
if (searchText.length >= SUGGESTIONS_TRIGGER_CHAR_COUNT) {
getSuggestions(e.target);
}
else {
handleHideDropdown();
}
}
if (code === 'ArrowDown' && searchItems.length) {
handleShowDropdown();
firstItemFocus();
}
else if (code === 'Escape') {
handleHideDropdown();
}
};
const handleDropDownClick = (e) => {
const target = e.target;
const content = target.innerText;
inputRef.current.value = content;
handleHideDropdown();
navigateToResults();
};
const handleDropDownKeyUp = (e) => {
const dropdown = dropdownRef.current;
const key = e.keyCode;
const activeLinkSelector = `[${dataSfItemAttribute}][${activeAttribute}]`;
const activeLink = dropdown.querySelector(activeLinkSelector);
if (!activeLink) {
return;
}
const previousParent = activeLink.parentElement.previousElementSibling;
const nextParent = activeLink.parentElement.nextElementSibling;
if (key === 38 && previousParent) {
e.preventDefault();
focusItem(previousParent);
}
else if (key === 40 && nextParent) {
e.preventDefault();
focusItem(nextParent);
}
else if (key === 13) {
inputRef.current.value = activeLink.innerText;
navigateToResults();
handleHideDropdown();
inputRef.current.focus();
}
else if (key === 27) {
resetActiveClass();
handleHideDropdown(false);
inputRef.current.focus();
}
};
const handleDropDownBlur = (e) => {
if (dropdownRef.current != null && !dropdownRef.current.contains(e.relatedTarget)) {
handleHideDropdown(false);
}
};
const firstItemFocus = () => {
const dropdown = dropdownRef.current;
if (dropdown && dropdown.children.length) {
const item = dropdown.children[0].querySelector(`[${dataSfItemAttribute}]`);
focusItem(item?.parentElement);
}
};
const focusItem = (item) => {
resetActiveClass();
const link = item.querySelector(`[${dataSfItemAttribute}]`);
if (link && activeClass) {
link.classList.add(...activeClass.split(' '));
}
link.setAttribute(activeAttribute, '');
link.focus();
};
const resetActiveClass = () => {
const dropdown = dropdownRef.current;
const activeLink = dropdown.querySelector(`[${activeAttribute}]`);
if (activeLink && activeClass) {
activeLink.classList.remove(...activeClass.split(' '));
activeLink.removeAttribute(activeAttribute);
}
};
const handlePillClick = (suggestion) => {
if (inputRef.current) {
inputRef.current.value = suggestion;
}
navigateToResults();
};
return (_jsx("div", { ...props.attributes, children: props.knowledgeBoxName && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "d-flex gap-3", children: [_jsx("label", { htmlFor: inputId, className: "visually-hidden", children: "Search" }), _jsx("input", { type: "text", id: inputId, className: "form-control", placeholder: props.placeholder, "aria-label": props.placeholder, defaultValue: !isEdit ? (searchParams.get('searchQuery') || '') : undefined, ref: inputRef, onKeyUp: !isEdit ? inputKeyupHandler : undefined, onKeyDown: !isEdit ? inputKeydownHandler : undefined, onBlur: !isEdit ? handleDropDownBlur : undefined }), _jsx("button", { type: "button", className: "btn btn-primary", onClick: !isEdit ? navigateToResults : undefined, children: props.buttonLabel })] }), !isEdit && (_jsx("ul", { className: classNames('border bg-body list-unstyled position-absolute dropdown-menu show', { [props.visibilityClassHidden]: !dropDownShow }), role: "listbox", ref: dropdownRef, style: { width: dropDownWidth }, onClick: handleDropDownClick, onKeyUp: handleDropDownKeyUp, onBlur: handleDropDownBlur, children: searchItems.map((item, idx) => (item && _jsx("li", { role: "option", "aria-selected": false, children: _jsx("button", { className: props.searchAutocompleteItemClass, "data-sfitem": "", title: item, tabIndex: -1, children: item }) }, idx))) })), suggestionsList.length > 0 && (_jsxs("div", { className: "d-flex flex-wrap align-items-center gap-2 mt-3", children: [_jsx("span", { className: "text-muted", children: props.suggestionsLabel }), suggestionsList.map((suggestion, i) => (_jsx("button", { className: "badge rounded-pill border-0 text-truncate bg-opacity-10 bg-secondary text-dark fw-normal fs-6 py-2 px-3", tabIndex: 0, title: suggestion, onClick: !isEdit ? () => handlePillClick(suggestion) : undefined, children: suggestion }, i)))] }))] })) }));
}