import util from "hoctable/utils"; import Popups from "hoctable/services/popups"; import * as React from "react"; export type DataLoadCallback = (results : Array) => void; export type ItemSelectedCallback = (selected_item : any) => void; export interface SearchDelegate { translate : (identifier : string, item? : any) => string; search : (query : string, callback : DataLoadCallback) => void; select : (item : any, callback : ItemSelectedCallback) => void; } export interface ItemContainerSignals { select : (item : any) => void; } interface ControlsProps { clear : () => void; } export interface ItemContainerProps { signals : ItemContainerSignals; item : any; children? : any; } export interface SearchProps { delegate : SearchDelegate; } export interface RenderedItemMapping { uuid : string; node : React.ReactNode; } export const CLASSES = { SEARCH: "hoctable__search", SEARCH_FILLED: "hoctable__search--filled", SEARCH_POPUP: "hoctable__search-popup", INPUT_CONTAINER: "hoctable__search-input-container", RESULTS_PLACEHOLDER: "hoctable__search-results-placeholder", SELECTION_CONTROLS: "hoctable__search-selection-control", SELECTION_CLEAR_CONTROL: "hoctable__search-clear-selection-control", CLEAR_CONTROL_ICON: "hoctable__search-selection-clear-icon", ITEM_CONTAINER: "hoctable-search-results-item", CONTROLS: "hoctable__search-controls" }; export interface SearchItemProps { option : any; delegate? : SearchDelegate; } export const DEBOUNCE_TIME = 300; function c(lookup : string) : string { return CLASSES[lookup]; } export function DefaultItem(props : SearchItemProps) : React.ReactElement { const { option, delegate } = props; return (

{delegate.translate("option", option)}

); } function SelectionControl(props : ControlsProps) : React.ReactElement { const { clear } = props; return (
); } function ItemContainer(props : ItemContainerProps) : React.ReactElement { const { signals, item } = props; const select = signals.select.bind(null, item); return (
{props.children}
); } function Factory(ItemComponent? : React.ComponentClass) : React.ComponentClass { class Search extends React.Component { private popup_handle : any; private rendered_items : Array; private render_request : string; constructor(props) { super(props); this.state = { }; this.rendered_items = [ ]; } componentWillUnmount() : void { const { popup_handle } = this; this.render_request = null; Popups.close(popup_handle); } transclude(search_query : string) : void { const { props, rendered_items } = this; const { delegate } = props; const done = (selected_item : any) : void => { const { popup_handle } = this; Popups.close(popup_handle); this.setState({ selected_item }); }; const select = (item : any) : void => { delegate.select(item, done); }; const signals = { select }; const current_request = this.render_request = util.uuid(); const render = (results : Array) : void => { const placeholder = this.refs["placeholder"] as HTMLElement; const { render_request } = this; // Do nothing if the request id we aquired is no longer up to date. if(render_request === null || render_request !== current_request) { return; } const { left, top } = placeholder.getBoundingClientRect(); const popup_children : Array = [ ]; for(let i = 0, x = results.length; i < x; i++) { const item_data = results[i]; const uuid = util.uuid(); const item_props = ItemComponent ? { option: item_data } : { option: item_data, delegate }; const inner = ItemComponent ? () : (); const node = ({inner}); rendered_items.push({ node, uuid }); popup_children.push(node); } const popup_node = (
{popup_children}
); // We should now safely close the previous popup Popups.close(this.popup_handle); this.popup_handle = Popups.open(popup_node, { left, top }); }; delegate.search(search_query, render); } render() : React.ReactElement { const { props, state } = this; const { delegate } = props; const { selected_item } = state; const value = selected_item ? delegate.translate("selection", selected_item) : (state.value || ""); const done = (selection : any) : void => { this.setState({ selected_item: selection }); }; const search = ({ target } : React.SyntheticEvent) => { const { value: new_value } = target as HTMLInputElement; const update = this.transclude.bind(this, new_value); if(this.state.timer) { clearTimeout(this.state.timer); } const timer = setTimeout(update, DEBOUNCE_TIME); if(selected_item) { state.timer = timer; state.value = new_value; return delegate.select(null, done); } this.setState({ timer, value: new_value, selected_item: null }); }; const clear = () : void => { const { state: current_state } = this; clearTimeout(current_state.timer); // Clear out the value in the input element. current_state.value = ""; return delegate.select(null, done); }; const control = selected_item ? : null; const classlist = selected_item ? [c("SEARCH"), c("SEARCH_FILLED")] : [c("SEARCH")]; return (
{control}
); } } return Search; } export default Factory;