mirror of
https://github.com/lifegpc/eh-downloader.git
synced 2026-06-06 05:38:44 +08:00
218 lines
7.4 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
}
|