UNPKG

duoyun-ui

Version:

A lightweight desktop UI component library, implemented using Gem

535 lines 23.6 kB
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; import { adoptedStyle, customElement, memo, property, shadow } from '@mantou/gem/lib/decorators'; import { createRef, createState, css, GemElement, html, TemplateResult } from '@mantou/gem/lib/element'; import { history } from '@mantou/gem/lib/history'; import { GemError, styleMap } from '@mantou/gem/lib/utils'; import { DuoyunDatePickerElement } from '../elements/date-picker'; import { DuoyunDateRangePickerElement } from '../elements/date-range-picker'; import { Drawer } from '../elements/drawer'; import { DuoyunInputElement } from '../elements/input'; import { Modal } from '../elements/modal'; import { DuoyunPickerElement } from '../elements/picker'; import { DuoyunSelectElement } from '../elements/select'; import { DuoyunWaitElement, waitLoading } from '../elements/wait'; import { icons } from '../lib/icons'; import { locale } from '../lib/locale'; import { blockContainer, focusStyle } from '../lib/styles'; import { theme } from '../lib/theme'; import { readProp } from '../lib/utils'; import '../elements/button'; import '../elements/form'; import '../elements/sort-box'; import '../elements/space'; const style = css ` dy-form { width: 100%; } dy-sort-item { display: flex; align-items: flex-start; gap: 1em; dy-form-item:first-of-type { flex-grow: 1; } } .template { margin-block-end: 1em; font-size: 0.875em; } dy-form-item[rows='0'] { field-sizing: content; } details { margin-block-end: 1.8em; } summary { cursor: pointer; display: flex; align-items: center; gap: 1px; border-radius: ${theme.normalRound}; } summary dy-use { width: 1.2em; } summary::marker, summary::-webkit-details-marker { display: none; } details[open] summary { display: none; } `; let DyPatFormElement = (() => { let _classDecorators = [customElement('dy-pat-form'), adoptedStyle(blockContainer), adoptedStyle(focusStyle), adoptedStyle(style), shadow()]; let _classDescriptor; let _classExtraInitializers = []; let _classThis; let _classSuper = GemElement; let _data_decorators; let _data_initializers = []; let _data_extraInitializers = []; let _formItems_decorators; let _formItems_initializers = []; let _formItems_extraInitializers = []; let _private_initState_decorators; let _private_initState_initializers = []; let _private_initState_extraInitializers = []; let _private_setDeps_decorators; let _private_setDeps_initializers = []; let _private_setDeps_extraInitializers = []; var DyPatFormElement = class extends _classSuper { static { _classThis = this; } static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; _data_decorators = [property]; _formItems_decorators = [property]; _private_initState_decorators = [memo((i) => [i.data])]; _private_setDeps_decorators = [memo((i) => [i.formItems])]; __esDecorate(null, null, _data_decorators, { kind: "field", name: "data", static: false, private: false, access: { has: obj => "data" in obj, get: obj => obj.data, set: (obj, value) => { obj.data = value; } }, metadata: _metadata }, _data_initializers, _data_extraInitializers); __esDecorate(null, null, _formItems_decorators, { kind: "field", name: "formItems", static: false, private: false, access: { has: obj => "formItems" in obj, get: obj => obj.formItems, set: (obj, value) => { obj.formItems = value; } }, metadata: _metadata }, _formItems_initializers, _formItems_extraInitializers); __esDecorate(null, null, _private_initState_decorators, { kind: "field", name: "#initState", static: false, private: true, access: { has: obj => #initState in obj, get: obj => obj.#initState, set: (obj, value) => { obj.#initState = value; } }, metadata: _metadata }, _private_initState_initializers, _private_initState_extraInitializers); __esDecorate(null, null, _private_setDeps_decorators, { kind: "field", name: "#setDeps", static: false, private: true, access: { has: obj => #setDeps in obj, get: obj => obj.#setDeps, set: (obj, value) => { obj.#setDeps = value; } }, metadata: _metadata }, _private_setDeps_initializers, _private_setDeps_extraInitializers); __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); DyPatFormElement = _classThis = _classDescriptor.value; if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); __runInitializers(_classThis, _classExtraInitializers); } data = __runInitializers(this, _data_initializers, void 0); formItems = (__runInitializers(this, _data_extraInitializers), __runInitializers(this, _formItems_initializers, void 0)); #formRef = (__runInitializers(this, _formItems_extraInitializers), createRef()); #initState = __runInitializers(this, _private_initState_initializers, () => { if (!this.data) return; this.state({ data: structuredClone(this.data) }); }); #deps = (__runInitializers(this, _private_initState_extraInitializers), new Map()); #setDeps = __runInitializers(this, _private_setDeps_initializers, () => { 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); }); }); }); #changeData = (__runInitializers(this, _private_setDeps_extraInitializers), (detail) => { const data = Object.keys(detail).reduce((prev, key) => { const val = detail[key]; const path = key.split(','); const prop = path.at(-1); const root = readProp(prev, path.slice(0, -1), { fill: true }); Reflect.set(root, prop, val); return prev; }, {}); this.state({ data }); this.#forEachFormItems((props) => { if (!props.list && !props.update && props.type !== 'number') return; const path = Array.isArray(props.field) ? props.field : [props.field]; const wrapObj = readProp(data, path.slice(0, -1)); const lastKey = path.at(-1); const val = wrapObj[lastKey]; if (props.list && val && !Array.isArray(val)) { Reflect.set(wrapObj, lastKey, Array.from({ ...val, length: Object.keys(val).length })); } if (props.type === 'number') { Reflect.set(wrapObj, lastKey, Number(val) || 0); } if (!props.update) return; Object.assign(props, props.update(data)); if (props.ignore) { this.state.ignoreCache[String(props.field)] = val; Reflect.deleteProperty(wrapObj, lastKey); } else if (val === undefined) { Reflect.set(wrapObj, lastKey, this.state.ignoreCache[String(props.field)]); } }); }); #onChange = ({ detail }) => this.#changeData(detail); #onOptionsChange = async (props, input) => { if (!props.getOptions) return; const { optionsRecord, data } = this.state; const options = (optionsRecord[String(props.field)] ||= {}); options.loading = true; this.update(); try { options.options = await props.getOptions(input, data); } finally { options.loading = false; this.update(); } }; #setStateInitValue = (props) => { 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, -1))[field.at(-1)] = initValue; } else { data[field] = initValue; } }; #onItemChange = ({ detail }) => { this.#deps.get(detail.name)?.forEach((props) => { this.#setStateInitValue(props); this.update(); this.#onOptionsChange(props, ''); }); }; #forEachFormItems = (process) => { const each = (items) => [items].flat().forEach(process); this.formItems?.forEach((item) => { if (item instanceof TemplateResult) return; if ('fieldset' in item) { item.fieldset.forEach(each); } else { each(item); } }); }; #isFormItemProps = (e) => !Array.isArray(e) && !('fieldset' in e) && !(e instanceof TemplateResult); #filterVisibleItems = (items) => items.filter((e) => !e.ignore && !e.hidden); #getInputGroup = (items) => { return Map.groupBy((items || []).filter(this.#isFormItemProps).filter((e) => !!e.label), (e) => e.label); }; #formInputEleMap = new Map(); #createFormInputElement = (type) => { switch (type) { case 'text': case 'textarea': case 'number': return new DuoyunInputElement(); case 'select': return new DuoyunSelectElement(); case 'date': case 'date-time': return new DuoyunDatePickerElement(); case 'date-range': return new DuoyunDateRangePickerElement(); case 'picker': return new DuoyunPickerElement(); default: throw new GemError('Not support type: `' + type + '`'); } }; #renderInputGroup = (allItems) => { const { optionsRecord, data } = this.state; const shadowFormItems = html ` ${allItems.map((props) => this.#renderItem({ label: '', field: props.field, type: props.type, multiple: props.multiple, ignore: props.ignore, hidden: true, }))} `; const items = this.#filterVisibleItems(allItems); const flatRules = items.flatMap(({ rules = [] }) => rules); const requiredItems = items.filter(({ required }) => required); if (requiredItems.length) { flatRules.push({ async validator() { const isInvalid = requiredItems.some((e) => { const value = readProp(data, e.field); return Array.isArray(value) ? !value.length : !value; }); if (isInvalid) throw new Error(locale.requiredMeg); }, }); } return html ` ${shadowFormItems} ${items.length ? html ` <dy-form-item .label=${items[0].label} .rules=${flatRules}> <dy-input-group> ${items.map((props) => { const name = String(props.field); const key = props.type + name; if (!this.#formInputEleMap.has(key)) { const ele = this.#createFormInputElement(props.type); ele.addEventListener('change', (evt) => { this.#onInputChange(evt, props); this.#formRef.value?.dispatchEvent(new CustomEvent('itemchange', { detail: { name: name, value: evt.detail }, })); }); ele.addEventListener('clear', (evt) => { this.#onInputChange(evt, props); }); ele.addEventListener('search', (evt) => { this.#onInputSearch(evt, props); }); this.#formInputEleMap.set(key, ele); } // 假设控件没有属性冲突 return Object.assign(this.#formInputEleMap.get(key), { type: props.type, time: props.type === 'date-time', style: styleMap(props.style || {}), value: readProp(data, props.field), loading: !!optionsRecord[name]?.loading, options: props.options || optionsRecord[name]?.options, dataList: props.options || optionsRecord[name]?.options, disabled: props.disabled, autofocus: props.autofocus, clearable: props.clearable, searchable: props.searchable, multiple: props.multiple, placeholder: props.placeholder, rows: props.rows, step: props.step, min: props.min, max: props.max, }); })} </dy-input-group> </dy-form-item> ` : ''} `; }; #onInputChange = (evt, props) => props.type === 'text' && this.#onOptionsChange(props, evt.detail); #onInputSearch = (evt, props) => props.type === 'select' && this.#onOptionsChange(props, evt.detail); #renderItem = (props) => { const { optionsRecord, data } = this.state; if (props.ignore) return html ``; const name = String(props.field); const onChange = (evt) => this.#onInputChange(evt, props); const onSearch = (evt) => this.#onInputSearch(evt, props); return html ` <dy-form-item .rules=${props.rules} .label=${props.label} .value=${readProp(data, props.field)} .name=${name} .type=${props.type} style=${styleMap(props.style || {}) // 以上是 `<dy-form-item>` 特有的 } .loading=${!!optionsRecord[name]?.loading} .options=${props.options || optionsRecord[name]?.options} ?hidden=${props.hidden} ?disabled=${props.disabled} ?autofocus=${props.autofocus} ?clearable=${props.clearable} ?searchable=${props.searchable || !!props.getOptions} ?required=${props.required} ?multiple=${props.multiple /**影响值 */} placeholder=${props.placeholder} rows=${props.rows} step=${props.step} min=${props.min} max=${props.max} @change=${onChange} @clear=${onChange} @search=${onSearch} > ${props.slot} </dy-form-item> `; }; #renderItemList = (item) => { const { add = true, remove = true, initItem, sortable } = typeof item.list === 'boolean' ? {} : item.list || {}; const addTemplate = typeof add === 'boolean' ? locale.add : add; const removeTemplate = typeof remove === 'boolean' ? '' : remove; const path = [item.field].flat(); const prop = path.at(-1); const value = readProp(this.state.data, path); const onSort = ({ detail }) => { [value[detail.new], value[detail.old]] = [value[detail.old], value[detail.new]]; this.state(); }; const addEle = () => { const root = readProp(this.state.data, path.slice(0, -1), { fill: true }); if (!root[prop]) root[prop] = []; root[prop].push(initItem); this.state(); }; const removeEle = (index) => { value.splice(index, 1); this.state(); }; return html ` <dy-form-item style="margin-bottom: 0" label=${item.label}></dy-form-item> <dy-sort-box @sort=${onSort}> ${value?.map((_, index) => html ` <dy-sort-item> <dy-sort-handle v-if=${!!sortable && !item.disabled}> <dy-button .icon=${icons.menu} square color="cancel"></dy-button> </dy-sort-handle> ${this.#renderItem({ ...item, field: [...path, index], label: '' })} <dy-form-item ?hidden=${item.disabled}> <dy-space> ${removeTemplate ? html `${removeTemplate}` : html ` <dy-button @click=${() => removeEle(index)} .icon=${icons.delete} square round color="cancel" title=${locale.remove} ></dy-button> `} </dy-space> </dy-form-item> </dy-sort-item> `)} </dy-sort-box> <dy-form-item> <dy-button type="reverse" .disabled=${item.disabled} .icon=${icons.add} @click=${addEle}> ${addTemplate} </dy-button> </dy-form-item> `; }; #renderInlineGroup = (items) => { return html `<dy-form-item-inline-group>${this.#renderItems(items)}</dy-form-item-inline-group>`; }; #renderItems = (items = []) => { const inputGroup = this.#getInputGroup(items); return html ` ${items.map((item) => { if (item instanceof TemplateResult) { return html `<div class="template">${item}</div>`; } if (Array.isArray(item)) { return this.#renderInlineGroup(item); } if ('fieldset' in item) { return html ` <details ?hidden=${!this.#filterVisibleItems(item.fieldset.flat().filter(this.#isFormItemProps)).length}> <summary><dy-use .element=${icons.right}></dy-use>${item.label}</summary> ${this.#renderItems(item.fieldset)} </details> `; } if (item.list) { return this.#renderItemList(item); } const inputs = inputGroup.get(item.label); if (inputs) { switch (inputs.length) { case 0: return ''; case 1: return this.#renderItem(item); default: { const result = this.#renderInputGroup(inputs); inputs.length = 0; return result; } } } return this.#renderItem(item); })} `; }; render = () => { return html ` <dy-form ${this.#formRef} @change=${this.#onChange} @itemchange=${this.#onItemChange}> ${this.#renderItems(this.formItems)} </dy-form> `; }; state = createState({ data: {}, optionsRecord: {}, ignoreCache: {}, }); valid = () => this.#formRef.value.valid(); }; return DyPatFormElement = _classThis; })(); export { DyPatFormElement }; /** * !WARNING * * form field not contain `,` */ export function createForm(options) { const containerType = options.type === 'modal' ? Modal : Drawer; const { query } = history.getParams(); if (options.query) { query.setAny(options.query[0], options.query[1]); history.replace({ query }); } return containerType .open({ header: options.header, body: html ` <dy-pat-form style=${styleMap(Object.assign({ minWidth: '30em' }, options.style))} .formItems=${options.formItems} .data=${options.data} ></dy-pat-form> `, 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.data)); await DuoyunWaitElement.instance?.removed; }, }) .then((ele) => ele.state.data) .catch((ele) => { throw ele.state.data; }) .finally(() => { if (options.query) { query.delete(options.query[0]); history.replace({ query }); } }); } //# sourceMappingURL=form.js.map