import React from "react"; import { ApiAdapterPromise, ApiAdapterUtils, ApiResponse, } from "@dndbeyond/character-rules-engine/es"; interface Props { loadOptions?: () => ApiAdapterPromise>>; clsNames: Array; id?: any; //TODO type? options: Array; //TODO type Options disabled: boolean; value: React.ReactText | null; placeholder: string; onChange?: (value: string) => void; onChangePromise?: ( newValue: string, oldValue: string, accept: () => void, reject: () => void ) => void; parseLoadedOptions?: (data: Array) => Array; resetAfterChoice: boolean; initialOptionRemoved: boolean; preventClickPropagating: boolean; isReadonly?: boolean; className: string; } interface State { isLazy: boolean; lazyOptions: Array; value: React.ReactText; resetAfterChoiceValues: Array; } export default class Select extends React.Component { static defaultProps = { clsNames: [], className: "", disabled: false, resetAfterChoice: false, initialOptionRemoved: false, placeholder: "-- Choose an Option --", options: [], preventClickPropagating: false, }; constructor(props: Props) { super(props); this.state = { isLazy: !!props.loadOptions, lazyOptions: [], value: props.value === null ? "" : props.value, resetAfterChoiceValues: this.getResetAfterValueChoices(props), }; } componentDidUpdate( prevProps: Readonly, prevState: Readonly, snapshot?: any ): void { const { value } = this.props; if (value !== prevProps.value) { this.setState({ value: value === null ? "" : value, }); } } componentDidMount() { const { isLazy } = this.state; const { loadOptions, parseLoadedOptions } = this.props; if (isLazy && loadOptions && parseLoadedOptions) { loadOptions().then((response) => { const data = ApiAdapterUtils.getResponseData(response); if (data === null) { return; } this.setState({ lazyOptions: parseLoadedOptions(data), }); }); } } getResetAfterValueChoices = (props: Props): Array => { const { options, resetAfterChoice } = props; if (resetAfterChoice) { return options.reduce((acc, option) => { if (option.options) { acc.push(...option.options.map((opt) => "" + opt.value)); } else { acc.push("" + option.value); } return acc; }, []); } return options.reduce((acc, option) => { if (option.options && option.resetAfterChoice) { acc.push(...option.options.map((opt) => "" + opt.value)); } return acc; }, []); }; handleChange = (evt: React.ChangeEvent): void => { const { value, resetAfterChoiceValues } = this.state; const { onChangePromise, onChange, resetAfterChoice } = this.props; const newValue: string = evt.target.value; if (onChangePromise) { onChangePromise( newValue, String(value), () => { this.setState({ value: newValue, }); if (onChange) { onChange(newValue); } if (resetAfterChoice) { evt.target.selectedIndex = 0; } }, () => {} ); } else { if (resetAfterChoice || resetAfterChoiceValues.includes(newValue)) { evt.target.selectedIndex = 0; } else { this.setState({ value: newValue, }); } if (onChange) { onChange(newValue); } } }; handleClick = (evt: React.MouseEvent): void => { const { preventClickPropagating } = this.props; if (preventClickPropagating) { evt.stopPropagation(); evt.nativeEvent.stopImmediatePropagation(); } }; renderOption = (option: any): React.ReactNode => { return ( ); }; renderOptGroup = ( label: string, options: Array, resetAfterChoice: boolean ) => { if (!options.length) { return null; } return ( {options.map((option) => this.renderOption(option))} ); }; render() { const { isLazy, lazyOptions, value } = this.state; const { options, placeholder, initialOptionRemoved, id, disabled, clsNames, isReadonly, className, } = this.props; let displayOptions: Array = options; if (isLazy) { displayOptions = lazyOptions; } const conClassNames: Array = [ "ddbc-select", ...clsNames, className, ]; return ( ); } }