Skip to content

Commit

Permalink
[duoyun-ui] dy-pat-form support dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Mar 9, 2024
1 parent 3e5796b commit 83fa7b1
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 22 deletions.
14 changes: 14 additions & 0 deletions packages/duoyun-ui/src/elements/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
globalemitter,
Emitter,
emitter,
numattribute,
} from '@mantou/gem/lib/decorators';
import { GemElement, html, TemplateResult } from '@mantou/gem/lib/element';
import { createCSSSheet, css } from '@mantou/gem/lib/utils';
Expand Down Expand Up @@ -99,6 +100,7 @@ const formStyle = createCSSSheet(css`
export class DuoyunFormElement<Data = Record<string, any>> extends GemElement {
@boolattribute inline: boolean;

/**event order: change, itemchange */
@globalemitter change: Emitter<Data>;

get items() {
Expand Down Expand Up @@ -185,6 +187,7 @@ const formItemStyle = createCSSSheet(css`
}
.input {
width: 100%;
field-sizing: inherit;
}
.input + .input,
.footer {
Expand Down Expand Up @@ -256,6 +259,13 @@ export class DuoyunFormItemElement extends GemElement<FormItemState> {
@property value?: number | string | any[] | any;
@property renderLabel?: (e: SelectOption) => string | TemplateResult;

// textarea
@numattribute rows: number;
// number
@numattribute step: number;
@numattribute min: number;
@numattribute max: number;

@property rules?: FormItemRule[];

/**@deprecated */
Expand Down Expand Up @@ -468,6 +478,10 @@ export class DuoyunFormItemElement extends GemElement<FormItemState> {
?disabled=${this.disabled}
@change=${this.#onChange}
@clear=${(evt: any) => evt.target.change('')}
.rows=${this.rows}
.step=${this.step}
.min=${this.min}
.max=${this.max}
.autofocus=${this.autofocus}
.clearable=${this.clearable}
.value=${this.value as string}
Expand Down
4 changes: 4 additions & 0 deletions packages/duoyun-ui/src/elements/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ const style = createCSSSheet(css`
border: none;
background-color: transparent;
resize: none;
field-sizing: inherit;
}
textarea {
min-height: 5em;
}
.input::-webkit-calendar-picker-indicator,
.input::-webkit-search-decoration,
Expand Down
8 changes: 5 additions & 3 deletions packages/duoyun-ui/src/elements/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,14 @@ export class DuoyunSelectElement extends GemElement<State> implements BasePicker
this.memo(
() => {
const map = new Map<any, Option>();
this.options?.forEach((option) => {
const forEach = (option: Option) => {
const { value, label } = option;
map.set(value ?? label, option);
});
};
this.#valueOptions?.forEach(forEach);
this.options?.forEach(forEach);
this.#valueSet = new Set(this.#value);
this.#valueOptions = this.#value?.map((value) => map.get(value)).filter(isNotNullish);
this.#valueOptions = this.#value?.map((value) => map.get(value) || { value, label: value });
},
() => [this.value, this.options],
);
Expand Down
102 changes: 84 additions & 18 deletions packages/duoyun-ui/src/patterns/form.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html, GemElement, TemplateResult } from '@mantou/gem/lib/element';
import { html, GemElement, TemplateResult, ifDefined } from '@mantou/gem/lib/element';
import { adoptedStyle, customElement, property, refobject, RefObject } from '@mantou/gem/lib/decorators';
import { StyleObject, createCSSSheet, css, styleMap } from '@mantou/gem/lib/utils';
import { history } from '@mantou/gem/lib/history';
Expand All @@ -20,17 +20,29 @@ type FormItemProps<T = unknown> = {
field: keyof T | string[];

options?: DuoyunFormItemElement['options'];
getOptions?: (input: string) => Promise<DuoyunFormItemElement['options']>;
dependencies?: (keyof T | string[])[];
/**dependencies change, text change, text clear select search */
getOptions?: (search: string, data: T) => Promise<DuoyunFormItemElement['options']>;
/**dependencies change, this.data change */
getInitValue?: (data: T) => any;
multiple?: boolean;
placeholder?: string;
searchable?: boolean;
clearable?: boolean;

// textarea
rows?: number;
// number input
step?: number;
min?: number;
max?: number;

required?: boolean;
rules?: DuoyunFormItemElement['rules'];

slot?: TemplateResult | HTMLElement | HTMLElement[];

/**all change */
isHidden?: (data: T) => boolean;
};

Expand All @@ -46,6 +58,9 @@ const style = createCSSSheet(css`
dy-form {
width: 100%;
}
dy-form-item[rows='0'] {
field-sizing: content;
}
details {
margin-block-end: 1.8em;
}
Expand Down Expand Up @@ -98,41 +113,44 @@ export class DyPatFormElement<T = Record<string, unknown>> extends GemElement<St

#onChange = ({ detail }: CustomEvent<any>) => {
this.setState({
data: Object.keys(detail).reduce((p, c) => {
const keys = c.split(',');
if (keys.length === 1) {
p[c] = detail[c];
data: Object.keys(detail).reduce((data, key) => {
const val = detail[key];
const path = key.split(',');
if (path.length === 1) {
data[key] = val;
} else {
const lastKey = keys.pop()!;
const a = keys.reduce((p, c) => (p[c] ||= {}), p);
a[lastKey] = detail[c];
const lastKey = path.pop()!;
const wrapObj = path.reduce((obj, key) => (obj[key] ||= {}), data);
wrapObj[lastKey] = val;
}
return p;
}, this.state.data as any),
return data;
}, {} as any),
});
};

#onOptionsChange = async <T>(props: FormItemProps<T>, input: string) => {
#onOptionsChange = async (props: FormItemProps<T>, input: string) => {
if (!props.getOptions) return;
const options = (this.state.optionsRecord[String(props.field)] ||= {} as OptionsRecord);
const { optionsRecord, data } = this.state;
const options = (optionsRecord[String(props.field)] ||= {} as OptionsRecord);
options.loading = true;
this.update();
try {
options.options = await props.getOptions(input);
options.options = await props.getOptions(input, data);
} finally {
options.loading = false;
this.update();
}
};

#renderItem = <T>(props: FormItemProps<T>) => {
#renderItem = (props: FormItemProps<T>) => {
const { optionsRecord, data } = this.state;
if (props.isHidden?.(data as unknown as T)) return html``;

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`
<dy-form-item
?hidden=${props.isHidden?.(data as unknown as T)}
.label=${props.label}
.value=${readProp(this.state.data!, props.field)}
.name=${name}
Expand All @@ -145,6 +163,10 @@ export class DyPatFormElement<T = Record<string, unknown>> extends GemElement<St
?clearable=${props.clearable}
?searchable=${props.searchable || !!props.getOptions}
?required=${props.required}
rows=${ifDefined(props.rows)}
step=${ifDefined(props.step)}
min=${ifDefined(props.min)}
max=${ifDefined(props.max)}
@change=${onChange}
@clear=${onChange}
@search=${onSearch}
Expand All @@ -153,7 +175,7 @@ export class DyPatFormElement<T = Record<string, unknown>> extends GemElement<St
`;
};

#renderItems = <T>(items: FormItemProps<T> | FormItemProps<T>[]) => {
#renderItems = (items: FormItemProps<T> | FormItemProps<T>[]) => {
if (Array.isArray(items)) {
return html`
<dy-form-item-inline-group>${items.map((item) => this.#renderItem(item))}</dy-form-item-inline-group>
Expand All @@ -163,6 +185,37 @@ export class DyPatFormElement<T = Record<string, unknown>> extends GemElement<St
}
};

#setStateInitValue = (props: FormItemProps<T>) => {
const { data } = this.state;
const { field } = props;
const initValue = props.getInitValue?.(data) ?? readProp(this.data || {}, field);
if (Array.isArray(field)) {
readProp(data!, field.slice(0, field.length - 1))[field.at(-1)!] = initValue;
} else {
data[field] = initValue;
}
};

#deps = new Map<string, Set<FormItemProps<T>>>();
#onItemChange = ({ detail: { name } }: CustomEvent<{ name: string }>) => {
this.#deps.get(name)?.forEach((props) => {
this.#setStateInitValue(props);
this.update();
this.#onOptionsChange(props, '');
});
};

#forEachFormItems = (process: (props: FormItemProps<T>) => void) => {
const each = (items: FormItemProps<T> | FormItemProps<T>[]) => [items].flat().forEach(process);
this.formItems?.forEach((item) => {
if ('group' in item) {
item.group.forEach(each);
} else {
each(item);
}
});
};

willMount() {
this.memo(
() => {
Expand All @@ -172,11 +225,24 @@ export class DyPatFormElement<T = Record<string, unknown>> extends GemElement<St
},
() => [this.data],
);
this.memo(
() => {
this.#forEachFormItems((props) => {
props.dependencies?.forEach((field) => {
const key = String(field);
const set = this.#deps.get(key) || new Set();
set.add(props);
this.#deps.set(key, set);
});
});
},
() => [this.formItems],
);
}

render = () => {
return html`
<dy-form @change=${this.#onChange} ref=${this.formRef.ref}>
<dy-form @change=${this.#onChange} @itemchange=${this.#onItemChange} ref=${this.formRef.ref}>
${this.formItems?.map((item) => {
if ('group' in item) {
return html`
Expand Down
9 changes: 8 additions & 1 deletion packages/gem-examples/src/console/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export class ConsolePageItemElement extends GemElement {
label: 'Company name',
},
{
type: 'text',
type: 'textarea',
rows: 0,
field: ['company', 'catchPhrase'],
label: 'Company Catch phrase',
},
Expand All @@ -125,6 +126,12 @@ export class ConsolePageItemElement extends GemElement {
type: 'date-time',
field: 'updated',
label: 'Last Updated',
dependencies: ['username'],
getInitValue(data) {
if (data.username === 'now') {
return Date.now();
}
},
},
];

Expand Down

0 comments on commit 83fa7b1

Please sign in to comment.