duoyun-ui
Version:
A lightweight desktop UI component library, implemented using Gem
535 lines • 23.6 kB
JavaScript
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