Skip to content

Instantly share code, notes, and snippets.

@k1r0s
Last active October 6, 2022 21:52
Show Gist options
  • Save k1r0s/6167eab946514d6aab4b9ce6943bb443 to your computer and use it in GitHub Desktop.
Save k1r0s/6167eab946514d6aab4b9ce6943bb443 to your computer and use it in GitHub Desktop.
Preact native <select> implementation with search feature
/**
* <MySelector
* options={exampleArr}
* printBy="name"
* selected={this.selectedOpt}
* onOptionSelected={(opt) =>
* this.mySelectorValueChange(opt)}
* />
*/
import { h, Component } from "preact";
import { stylesheet } from "stylesheet-decorator";
export interface MySelectorAttibutes {
options: any[];
selected: any;
printBy: string;
onOptionSelected: (any) => void;
}
export interface MySelectorState {
visible: boolean;
value: string;
}
// <MySelector onOptionSelected={updateProp} options={arr} printBy="name" trackBy="id"/>
export class MySelector extends Component<MySelectorAttibutes, MySelectorState> {
public state = { visible: false, value: "" };
private styleVisible = { display: "block" };
private styleHidden = { display: "none" };
private clickHandler = (e) => this.onClickOutside(e);
public componentDidMount(): void {
this.props.selected && this.setState(() => ({ value: this.props.selected[this.props.printBy] }));
}
private onClickOutside(e: Event): void {
!this.base.contains(e.target as Node) && this.closeTooltip();
}
private closeTooltip(): void {
this.setState(() => ({ visible: false }));
window.removeEventListener("click", this.clickHandler);
this.checkMatch();
}
private checkMatch(): void {
const match = this.props.options.find((opt) => this.getMatcher(opt));
!!match ? this.selectOption(match) : this.setState(() => ({ value: "" }));
}
private onFocusHandler(e: Event): void {
(e.target as any).select();
this.setState(() => ({ visible: true }));
window.addEventListener("click", this.clickHandler);
}
private filterOptions(e: Event): void {
const { value } = e.target as any;
this.setState(() => ({ value }));
}
private getFilter(opt: any): boolean {
return opt[this.props.printBy].match(new RegExp(this.state.value, "i"));
}
private getMatcher(opt: any): boolean {
return opt[this.props.printBy].match(new RegExp(`^${this.state.value}$`, "i"));
}
private selectOption(opt: any): void {
this.props.onOptionSelected.call(null, opt);
}
private explicitSelection(opt: any): void {
this.setState(() => ({ value: opt[this.props.printBy] }));
this.closeTooltip();
}
@stylesheet(`
:host {
background-color: buttonface;
display: inline-block;
align-items: center;
border-radius: 0px;
border-color: lightgrey;
}
[data-selector-option]:hover {
cursor: pointer;
color: white;
background-color: grey;
}
`)
public render(): JSX.Element {
return (
<div data-selector-container>
<div data-selector-display>
<input onFocus={(e) => this.onFocusHandler(e)} onInput={(e) => this.filterOptions(e)} value={this.state.value}/>
</div>
<div data-selector-tooltip style={this.state.visible ? this.styleVisible : this.styleHidden}>
{this.props.options
.filter((opt) => !this.state.value || this.getFilter(opt))
.map((opt) => (
<div data-selector-option onClick={() => this.explicitSelection(opt)}>
<span>{opt[this.props.printBy]}</span>
</div>
))}
</div>
</div>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment