import debounce from "lodash.debounce"; import { StopsAPI } from "mobility-toolbox-js/api"; import { memo } from "preact/compat"; import { useCallback, useEffect, useMemo, useRef, useState, } from "preact/hooks"; import { twMerge } from "tailwind-merge"; import Cancel from "../icons/Cancel"; import Search from "../icons/Search"; import IconButton from "../ui/IconButton"; import Input from "../ui/Input"; import useI18n from "../utils/hooks/useI18n"; import MobilityEvent from "../utils/MobilityEvent"; import type { StopsParameters, StopsResponse } from "mobility-toolbox-js/types"; import type { HTMLAttributes, PreactDOMAttributes, TargetedKeyboardEvent, } from "preact"; import type { StopsFeature } from "../utils/hooks/useSearchStops"; export type StopsSearchProps = { apikey: string; bbox?: string; cancelButtonClassName?: string; className?: string; countrycode?: string; event?: string; field?: string; inputClassName?: string; inputContainerClassName?: string; lang?: string; limit?: number; mots?: string; onselect?: (arg: StopsFeature) => void; params?: string; // JSONstring prefagencies?: string; reflocation?: string; resultClassName?: string; resultsClassName?: string; resultsContainerClassName?: string; searchIconContainerClassName?: string; url: string; withResultsClassName?: string; } & HTMLAttributes & PreactDOMAttributes; const getQueryForSelectedStation = (selectedStation?: StopsFeature): string => { return selectedStation?.properties?.name || ""; }; /** * Input field to search for stations * * @fires stationselect */ function StopsSearch({ apikey, bbox, cancelButtonClassName, className, countrycode, event, field, inputClassName, inputContainerClassName, lang, limit, mots, onselect, params, prefagencies, reflocation, resultClassName, resultsClassName, resultsContainerClassName, searchIconContainerClassName, url = "https://api.geops.io/stops/v1/", withResultsClassName, }: StopsSearchProps) { const { t } = useI18n(); const [query, setQuery] = useState(""); const [selectedStation, setSelectedStation] = useState(); const [results, setResults] = useState(); const myRef = useRef(); useEffect(() => { myRef.current?.dispatchEvent( new MobilityEvent("mwc:attribute", { apikey, bbox, countrycode, event, field, lang, limit, mots, onselect, params, prefagencies, reflocation, url, }), ); }, [ apikey, bbox, countrycode, event, field, limit, mots, onselect, params, prefagencies, reflocation, url, lang, ]); const api: StopsAPI = useMemo(() => { return new StopsAPI({ apiKey: apikey, url: url }); }, [apikey, url]); const dispatchEvent = useCallback( (station?: StopsFeature) => { const customEvt = new MobilityEvent( event || "mwc:stopssearchselect", station, { bubbles: true, }, ); myRef.current?.dispatchEvent(customEvt); if (onselect && typeof onselect === "function") { onselect(station); } }, [event, onselect], ); const debouncedSearch = useMemo(() => { let abortCtrl: AbortController | undefined; return debounce((q) => { abortCtrl?.abort(); abortCtrl = new AbortController(); const reqParams = { bbox, field, limit, mots, prefagencies, q, ref_location: reflocation, ...JSON.parse(params || "{}"), } as StopsParameters; api .search(reqParams, { signal: abortCtrl.signal }) .then((res: StopsResponse) => { setResults( res.features.filter((f) => { return !countrycode || f.properties?.country_code === countrycode; }), ); }) .catch((e) => { // AbortError is expected if (e.code !== 20) { // eslint-disable-next-line no-console console.error("Failed to fetch stations", e); return; } }); }, 150); }, [ api, bbox, countrycode, field, limit, mots, params, prefagencies, reflocation, ]); useEffect(() => { if ( selectedStation && query === getQueryForSelectedStation(selectedStation) ) { return; } if (!query) { setSelectedStation(undefined); return; } debouncedSearch(query); return () => { debouncedSearch.cancel(); }; }, [query, selectedStation, debouncedSearch]); useEffect(() => { setResults(undefined); if (selectedStation) { setQuery(getQueryForSelectedStation(selectedStation)); } dispatchEvent(selectedStation); }, [dispatchEvent, selectedStation]); return ( <>
{ // @ts-expect-error target is missing setQuery(evt.target.value); }} onKeyUp={(evt: TargetedKeyboardEvent) => { if (evt.key === "Enter") { if (results?.length > 0) { setSelectedStation(results[0]); } } }} placeholder={t("stops_search_placeholder")} type="text" value={query || ""} /> {query.length > 0 && ( { setQuery(""); setResults(undefined); }} > )}
{results && (
{!results?.length && (
{t("no_stops_found")}
)} {!!results?.length && (
    {results?.map((station) => { return (
  • ); })}
)}
)} ); } export default memo(StopsSearch);