From 3e5796b2561e381aa0aea00e0bb00256d4c245d5 Mon Sep 17 00:00:00 2001 From: mantou132 <709922234@qq.com> Date: Sat, 2 Mar 2024 18:04:00 +0800 Subject: [PATCH] [duoyun-ui] Improve `` --- packages/duoyun-ui/package.json | 2 +- packages/duoyun-ui/src/elements/select.ts | 11 ++- packages/duoyun-ui/src/patterns/form.ts | 98 ++++++++++++++--------- packages/gem-examples/src/console/item.ts | 9 +++ packages/gem-port/src/react.ts | 2 +- 5 files changed, 80 insertions(+), 42 deletions(-) diff --git a/packages/duoyun-ui/package.json b/packages/duoyun-ui/package.json index 8097d24d..9003bf53 100644 --- a/packages/duoyun-ui/package.json +++ b/packages/duoyun-ui/package.json @@ -1,6 +1,6 @@ { "name": "duoyun-ui", - "version": "1.1.18", + "version": "1.1.19", "description": "A lightweight desktop UI component library, implemented using Gem", "keywords": [ "frontend", diff --git a/packages/duoyun-ui/src/elements/select.ts b/packages/duoyun-ui/src/elements/select.ts index cadd608a..e49a092f 100644 --- a/packages/duoyun-ui/src/elements/select.ts +++ b/packages/duoyun-ui/src/elements/select.ts @@ -278,7 +278,6 @@ export class DuoyunSelectElement extends GemElement implements BasePicker #onSearch = (evt: CustomEvent) => { this.setState({ search: evt.detail, open: true }); - this.search(evt.detail); evt.stopPropagation(); }; @@ -358,9 +357,9 @@ export class DuoyunSelectElement extends GemElement implements BasePicker ); this.effect( () => { - if (this.state.open && !this.searchable && !this.inline) { - const restoreInert = setBodyInert(this.optionsRef.element!); - this.optionsRef.element?.focus(); + if (this.state.open && !this.searchable && !this.inline && this.optionsRef.element) { + const restoreInert = setBodyInert(this.optionsRef.element); + this.optionsRef.element.focus(); return () => { restoreInert(); this.focus(); @@ -369,6 +368,10 @@ export class DuoyunSelectElement extends GemElement implements BasePicker }, () => [this.state.open], ); + this.effect( + ([search]) => this.search(search), + () => [this.state.search], + ); }; #getOptions = () => { diff --git a/packages/duoyun-ui/src/patterns/form.ts b/packages/duoyun-ui/src/patterns/form.ts index 754da34e..5554b7a4 100644 --- a/packages/duoyun-ui/src/patterns/form.ts +++ b/packages/duoyun-ui/src/patterns/form.ts @@ -20,6 +20,7 @@ type FormItemProps = { field: keyof T | string[]; options?: DuoyunFormItemElement['options']; + getOptions?: (input: string) => Promise; multiple?: boolean; placeholder?: string; searchable?: boolean; @@ -29,6 +30,8 @@ type FormItemProps = { rules?: DuoyunFormItemElement['rules']; slot?: TemplateResult | HTMLElement | HTMLElement[]; + + isHidden?: (data: T) => boolean; }; export type FormItem = @@ -65,6 +68,16 @@ const style = createCSSSheet(css` } `); +type OptionsRecord = { + loading: boolean; + options?: DuoyunFormItemElement['options']; +}; + +type State = { + data: T; + optionsRecord: Partial>; +}; + /** * @customElement dy-pat-form */ @@ -72,17 +85,20 @@ const style = createCSSSheet(css` @adoptedStyle(blockContainer) @adoptedStyle(focusStyle) @adoptedStyle(style) -export class DyPatFormElement> extends GemElement { +export class DyPatFormElement> extends GemElement> { @refobject formRef: RefObject; @property data?: T; @property formItems?: FormItem[]; - state: T = {} as T; + state: State = { + data: {} as T, + optionsRecord: {}, + }; #onChange = ({ detail }: CustomEvent) => { - this.setState( - Object.keys(detail).reduce((p, c) => { + this.setState({ + data: Object.keys(detail).reduce((p, c) => { const keys = c.split(','); if (keys.length === 1) { p[c] = detail[c]; @@ -92,37 +108,47 @@ export class DyPatFormElement> extends GemElement a[lastKey] = detail[c]; } return p; - }, this.state as any), - ); + }, this.state.data as any), + }); + }; + + #onOptionsChange = async (props: FormItemProps, input: string) => { + if (!props.getOptions) return; + const options = (this.state.optionsRecord[String(props.field)] ||= {} as OptionsRecord); + options.loading = true; + this.update(); + try { + options.options = await props.getOptions(input); + } finally { + options.loading = false; + this.update(); + } }; - #renderItem = ({ - label, - field, - type, - clearable, - multiple, - placeholder, - required, - rules, - options, - searchable, - slot, - }: FormItemProps) => { + #renderItem = (props: FormItemProps) => { + const { optionsRecord, data } = this.state; + const name = String(props.field); + const onChange = (evt: CustomEvent) => props.type === 'text' && this.#onOptionsChange(props, evt.detail); + const onSearch = (evt: CustomEvent) => props.type === 'select' && this.#onOptionsChange(props, evt.detail); return html` ${slot}${props.slot} `; }; @@ -141,7 +167,7 @@ export class DyPatFormElement> extends GemElement this.memo( () => { if (this.data) { - this.state = structuredClone(this.data); + this.state.data = structuredClone(this.data); } }, () => [this.data], @@ -194,17 +220,17 @@ export function createForm>(options: CreateFormOptio .data=${options.data} > `, - prepareClose: (ele) => options.prepareClose?.(ele.state), + prepareClose: (ele) => options.prepareClose?.(ele.state.data), prepareOk: async (ele) => { const valid = await ele.valid(); if (!valid) throw null; - await waitLoading(options.prepareOk?.(ele.state)); + await waitLoading(options.prepareOk?.(ele.state.data)); await DuoyunWaitElement.instance?.removed; }, }) - .then((ele) => ele.state) + .then((ele) => ele.state.data) .catch((ele) => { - throw ele.state; + throw ele.state.data; }) .finally(() => { if (options.query) { diff --git a/packages/gem-examples/src/console/item.ts b/packages/gem-examples/src/console/item.ts index 3b890e6e..d13c2dd5 100644 --- a/packages/gem-examples/src/console/item.ts +++ b/packages/gem-examples/src/console/item.ts @@ -85,11 +85,20 @@ export class ConsolePageItemElement extends GemElement { field: 'username', label: 'Username', required: true, + async getOptions(input) { + await sleep(300); + return Array(4) + .fill(null) + .map((_, index) => ({ label: `${input}-${index}` })); + }, }, { type: 'text', field: 'name', label: 'Name', + isHidden(data) { + return !data.username; + }, }, ], { diff --git a/packages/gem-port/src/react.ts b/packages/gem-port/src/react.ts index b8ab4201..d03239b1 100644 --- a/packages/gem-port/src/react.ts +++ b/packages/gem-port/src/react.ts @@ -54,7 +54,7 @@ function createReactSourceFile(elementFilePath: string, outDir: string) { .join(';\n')} } - const ${componentName}: ForwardRefExoticComponent & RefAttributes<${componentMethodsName}>> = forwardRef<${componentMethodsName}, ${componentPropsName}>(function (props, ref): JSX.Element { + export const ${componentName}: ForwardRefExoticComponent & RefAttributes<${componentMethodsName}>> = forwardRef<${componentMethodsName}, ${componentPropsName}>(function (props, ref): JSX.Element { const elementRef = useRef<${constructorName}>(null); useImperativeHandle(ref, () => { return {