Files
eh-downloader/components/BTextField.tsx
2023-09-07 21:34:02 +08:00

218 lines
7.4 KiB
TypeScript

import { Component, ComponentChildren, ContextType } from "preact";
import TextField from "preact-material-components/TextField.js";
import { Ref, useRef, useState } from "preact/hooks";
import { BCtx } from "./BContext.tsx";
import List from "preact-material-components/List.js";
interface TextType {
text: string;
password: string;
number: number;
}
interface DataType {
text: never;
password: never;
number: number;
}
type Props<T extends keyof TextType> = {
/**@default {true} */
clear_cache?: boolean;
value?: TextType[T];
name?: string;
description?: string;
type: T;
label?: string;
helpertext?: string;
textarea?: boolean;
fullwidth?: boolean;
disabled?: boolean;
children?: ComponentChildren;
set_value?: (v?: TextType[T]) => void;
min?: DataType[T];
max?: DataType[T];
outlined?: boolean;
id?: string;
list?: string;
datalist?: { value: TextType[T]; label?: string }[];
onPaste?: (
clipboard: string,
) => { text: string; overwrite?: boolean } | undefined;
};
export default class BTextField<T extends keyof TextType>
extends Component<Props<T>, unknown> {
static contextType = BCtx;
ref: Ref<TextField | undefined> | undefined;
declare context: ContextType<typeof BCtx>;
clear() {
const e = this.ref?.current;
if (e) {
const b = e.base;
if (b) {
const t = b as HTMLElement;
const d = t.querySelector("input");
if (d) {
d.value = "";
}
}
}
}
get clear_cache() {
return this.props.clear_cache !== undefined
? this.props.clear_cache
: true;
}
update(value: TextType[T]) {
const e = this.ref?.current;
if (e) {
const b = e.base;
if (b) {
const t = b as HTMLElement;
const d = t.querySelector("input");
if (d) {
const type = this.props.type;
// @ts-ignore Checked
if (type === "text" || type === "password") d.value = value;
// @ts-ignore Checked
else d.valueAsNumber = value;
}
}
}
}
set_value(value?: TextType[T]) {
if (this.props.set_value) {
this.props.set_value(value);
} else if (this.context) {
this.context.set_value((v) => {
v[this.props.name || ""] = value;
return v;
});
}
}
componentDidMount() {
if (this.props.value !== undefined) this.update(this.props.value);
else if (this.clear_cache) this.clear();
}
componentDidUpdate(
previousProps: Readonly<Props<T>>,
previousState: Readonly<unknown>,
snapshot: unknown,
): void {
if (this.props.value !== undefined) this.update(this.props.value);
else if (this.clear_cache) this.clear();
}
get value(): TextType[T] | undefined {
const e = this.ref?.current;
if (e) {
const b = e.base;
if (b) {
const t = b as HTMLElement;
const d = t.querySelector("input");
if (d) {
return this.get_value(d);
}
}
}
return undefined;
}
get_value(e: HTMLInputElement): TextType[T] | undefined {
const type = this.props.type;
if (!e.value.length) return undefined;
// @ts-ignore Checked
if (type === "text" || type === "password") return e.value;
// @ts-ignore Checked
return e.valueAsNumber;
}
render() {
this.ref = useRef<TextField>();
let cn = "b-text-field text";
let datalist_div = null;
const [display_datalist, set_display_datalist] = useState(false);
if (this.props.helpertext) cn += " helper";
if (this.props.outlined) cn += " outlined";
if (this.props.label) cn += " label";
if (this.props.datalist && this.props.datalist.length) {
cn += " datalist";
let cn2 = "datalist";
if (display_datalist) cn2 += " open";
const v = this.value?.toString();
datalist_div = (
<List class={cn2}>
{this.props.datalist.map((d) => {
if (v !== undefined) {
if (!d.value.toString().startsWith(v)) return null;
}
let label_div = null;
if (d.label) {
label_div = <div class="label">{d.label}</div>;
}
return (
<List.Item
onMousedown={() => {
this.set_value(d.value);
}}
>
<div class="value">{d.value}</div>
{label_div}
</List.Item>
);
})}
</List>
);
}
let desc = null;
if (this.props.description) {
desc = <label>{this.props.description}</label>;
}
return (
<div class={cn} id={this.props.id}>
{desc}
<TextField
fullwidth={this.props.fullwidth}
textarea={this.props.textarea}
type={this.props.type}
disabled={this.props.disabled}
helperText={this.props.helpertext}
label={this.props.label}
ref={this.ref}
onInput={(ev: InputEvent) => {
if (ev.target) {
const e = ev.target as HTMLInputElement;
this.set_value(this.get_value(e));
}
}}
min={this.props.min}
max={this.props.max}
outlined={this.props.outlined}
list={this.props.list}
onFocus={() => set_display_datalist(true)}
onBlur={() => set_display_datalist(false)}
onPaste={this.props.onPaste
? ((e: ClipboardEvent) => {
if (!this.props.onPaste) return;
const clipboard =
e.clipboardData?.getData("text") || "";
const v = this.props.onPaste(clipboard);
if (!v) return;
e.preventDefault();
if (e.target) {
const i = e.target as HTMLInputElement;
if (v.overwrite) {
i.value = v.text;
} else {
i.setRangeText(v.text);
}
this.set_value(this.get_value(i));
}
})
: undefined}
/>
{datalist_div}
{this.props.children}
</div>
);
}
}