naive-ui
Version:
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
498 lines • 20 kB
JavaScript
var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function (resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import Schema from 'async-validator';
import { get } from 'lodash-es';
import { createId } from 'seemly';
import { computed, defineComponent, h, inject, onMounted, provide, ref, toRef, Transition, watch } from 'vue';
import { useConfig, useTheme, useThemeClass } from "../../_mixins/index.mjs";
import { formItemInjectionKey } from "../../_mixins/use-form-item.mjs";
import { createKey, keysOf, resolveWrappedSlot, useInjectionInstanceCollection, warn } from "../../_utils/index.mjs";
import { formLight } from "../styles/index.mjs";
import { formInjectionKey, formItemInstsInjectionKey } from "./context.mjs";
import style from "./styles/form-item.cssr.mjs";
import { formItemMisc, formItemRule, formItemSize } from "./utils.mjs";
export const formItemProps = Object.assign(Object.assign({}, useTheme.props), {
label: String,
labelWidth: [Number, String],
labelStyle: [String, Object],
labelAlign: String,
labelPlacement: String,
path: String,
first: Boolean,
rulePath: String,
required: Boolean,
showRequireMark: {
type: Boolean,
default: undefined
},
requireMarkPlacement: String,
showFeedback: {
type: Boolean,
default: undefined
},
rule: [Object, Array],
size: String,
ignorePathChange: Boolean,
validationStatus: String,
feedback: String,
feedbackClass: String,
feedbackStyle: [String, Object],
showLabel: {
type: Boolean,
default: undefined
},
labelProps: Object
});
export const formItemPropKeys = keysOf(formItemProps);
// wrap sync validator
function wrapValidator(validator, async) {
return (...args) => {
try {
const validateResult = validator(...args);
if (!async && (typeof validateResult === 'boolean' || validateResult instanceof Error || Array.isArray(validateResult)) // Error[]
|| (validateResult === null || validateResult === void 0 ? void 0 : validateResult.then)) {
return validateResult;
} else if (validateResult === undefined) {
return true;
} else {
warn('form-item/validate', `You return a ${typeof validateResult} ` + `typed value in the validator method, which is not recommended. Please use ${async ? '`Promise`' : '`boolean`, `Error` or `Promise`'} typed value instead.`);
return true;
}
} catch (err) {
warn('form-item/validate', 'An error is catched in the validation, ' + 'so the validation won\'t be done. Your callback in `validate` method of ' + '`n-form` or `n-form-item` won\'t be called in this validation.');
console.error(err);
// If returns undefined, async-validator won't trigger callback
// so the result will be abandoned, which means not true and not false
return undefined;
}
};
}
export default defineComponent({
name: 'FormItem',
props: formItemProps,
setup(props) {
useInjectionInstanceCollection(formItemInstsInjectionKey, 'formItems', toRef(props, 'path'));
const {
mergedClsPrefixRef,
inlineThemeDisabled
} = useConfig(props);
const NForm = inject(formInjectionKey, null);
const formItemSizeRefs = formItemSize(props);
const formItemMiscRefs = formItemMisc(props);
const {
validationErrored: validationErroredRef,
validationWarned: validationWarnedRef
} = formItemMiscRefs;
const {
mergedRequired: mergedRequiredRef,
mergedRules: mergedRulesRef
} = formItemRule(props);
const {
mergedSize: mergedSizeRef
} = formItemSizeRefs;
const {
mergedLabelPlacement: labelPlacementRef,
mergedLabelAlign: labelTextAlignRef,
mergedRequireMarkPlacement: mergedRequireMarkPlacementRef
} = formItemMiscRefs;
const renderExplainsRef = ref([]);
const feedbackIdRef = ref(createId());
const mergedDisabledRef = NForm ? toRef(NForm.props, 'disabled') : ref(false);
const themeRef = useTheme('Form', '-form-item', style, formLight, props, mergedClsPrefixRef);
watch(toRef(props, 'path'), () => {
if (props.ignorePathChange) return;
restoreValidation();
});
function restoreValidation() {
renderExplainsRef.value = [];
validationErroredRef.value = false;
validationWarnedRef.value = false;
if (props.feedback) {
feedbackIdRef.value = createId();
}
}
const internalValidate = (...args_1) => __awaiter(this, [...args_1], void 0, function* (trigger = null, shouldRuleBeApplied = () => true, options = {
suppressWarning: true
}) {
const {
path
} = props;
if (!options) {
options = {};
} else {
if (!options.first) options.first = props.first;
}
const {
value: rules
} = mergedRulesRef;
const value = NForm ? get(NForm.props.model, path || '') : undefined;
const messageRenderers = {};
const originalMessageRendersMessage = {};
const activeRules = (!trigger ? rules : rules.filter(rule => {
if (Array.isArray(rule.trigger)) {
return rule.trigger.includes(trigger);
} else {
return rule.trigger === trigger;
}
})).filter(shouldRuleBeApplied).map((rule, i) => {
const shallowClonedRule = Object.assign({}, rule);
if (shallowClonedRule.validator) {
shallowClonedRule.validator = wrapValidator(shallowClonedRule.validator, false);
}
if (shallowClonedRule.asyncValidator) {
shallowClonedRule.asyncValidator = wrapValidator(shallowClonedRule.asyncValidator, true);
}
if (shallowClonedRule.renderMessage) {
const rendererKey = `__renderMessage__${i}`;
originalMessageRendersMessage[rendererKey] = shallowClonedRule.message;
shallowClonedRule.message = rendererKey;
messageRenderers[rendererKey] = shallowClonedRule.renderMessage;
}
return shallowClonedRule;
});
const activeErrorRules = activeRules.filter(r => r.level !== 'warning');
const activeWarningRules = activeRules.filter(r => r.level === 'warning');
const validationResult = {
valid: true,
errors: undefined,
warnings: undefined
};
if (!activeRules.length) return validationResult;
const mergedPath = path !== null && path !== void 0 ? path : '__n_no_path__';
const validator = new Schema({
[mergedPath]: activeErrorRules
});
const warningValidator = new Schema({
[mergedPath]: activeWarningRules
});
const {
validateMessages
} = (NForm === null || NForm === void 0 ? void 0 : NForm.props) || {};
if (validateMessages) {
validator.messages(validateMessages);
warningValidator.messages(validateMessages);
}
const renderMessages = errors => {
renderExplainsRef.value = errors.map(error => {
const transformedMessage = (error === null || error === void 0 ? void 0 : error.message) || '';
return {
key: transformedMessage,
render: () => {
if (transformedMessage.startsWith('__renderMessage__')) {
return messageRenderers[transformedMessage]();
}
return transformedMessage;
}
};
});
errors.forEach(error => {
var _a;
if ((_a = error.message) === null || _a === void 0 ? void 0 : _a.startsWith('__renderMessage__')) {
error.message = originalMessageRendersMessage[error.message];
}
});
};
if (activeErrorRules.length) {
const errors = yield new Promise(resolve => {
void validator.validate({
[mergedPath]: value
}, options, resolve);
});
if (errors === null || errors === void 0 ? void 0 : errors.length) {
validationResult.valid = false;
validationResult.errors = errors;
renderMessages(errors);
}
}
// if there are already errors, warning check can be skipped
if (activeWarningRules.length && !validationResult.errors) {
const warnings = yield new Promise(resolve => {
void warningValidator.validate({
[mergedPath]: value
}, options, resolve);
});
if (warnings === null || warnings === void 0 ? void 0 : warnings.length) {
renderMessages(warnings);
validationResult.warnings = warnings;
}
}
if (!validationResult.errors && !validationResult.warnings) {
restoreValidation();
} else {
validationErroredRef.value = !!validationResult.errors;
validationWarnedRef.value = !!validationResult.warnings;
}
return validationResult;
});
function handleContentBlur() {
void internalValidate('blur');
}
function handleContentChange() {
void internalValidate('change');
}
function handleContentFocus() {
void internalValidate('focus');
}
function handleContentInput() {
void internalValidate('input');
}
function validate(options, callback) {
return __awaiter(this, void 0, void 0, function* () {
/** the following code is for compatibility */
let trigger;
let validateCallback;
let shouldRuleBeApplied;
let asyncValidatorOptions;
if (typeof options === 'string') {
trigger = options;
validateCallback = callback;
} else if (options !== null && typeof options === 'object') {
trigger = options.trigger;
validateCallback = options.callback;
shouldRuleBeApplied = options.shouldRuleBeApplied;
asyncValidatorOptions = options.options;
}
return yield new Promise((resolve, reject) => {
void internalValidate(trigger, shouldRuleBeApplied, asyncValidatorOptions).then(({
valid,
errors,
warnings
}) => {
if (valid) {
if (validateCallback) {
validateCallback(undefined, {
warnings
});
}
resolve({
warnings
});
} else {
if (validateCallback) {
validateCallback(errors, {
warnings
});
}
reject(errors);
}
});
});
});
}
provide(formItemInjectionKey, {
path: toRef(props, 'path'),
disabled: mergedDisabledRef,
mergedSize: formItemSizeRefs.mergedSize,
mergedValidationStatus: formItemMiscRefs.mergedValidationStatus,
restoreValidation,
handleContentBlur,
handleContentChange,
handleContentFocus,
handleContentInput
});
const exposedRef = {
validate,
restoreValidation,
internalValidate
};
const labelElementRef = ref(null);
onMounted(() => {
if (!formItemMiscRefs.isAutoLabelWidth.value) return;
const labelElement = labelElementRef.value;
if (labelElement !== null) {
const memoizedWhitespace = labelElement.style.whiteSpace;
labelElement.style.whiteSpace = 'nowrap';
labelElement.style.width = '';
NForm === null || NForm === void 0 ? void 0 : NForm.deriveMaxChildLabelWidth(Number(getComputedStyle(labelElement).width.slice(0, -2)));
labelElement.style.whiteSpace = memoizedWhitespace;
}
});
const cssVarsRef = computed(() => {
var _a;
const {
value: size
} = mergedSizeRef;
const {
value: labelPlacement
} = labelPlacementRef;
const direction = labelPlacement === 'top' ? 'vertical' : 'horizontal';
const {
common: {
cubicBezierEaseInOut
},
self: {
labelTextColor,
asteriskColor,
lineHeight,
feedbackTextColor,
feedbackTextColorWarning,
feedbackTextColorError,
feedbackPadding,
labelFontWeight,
[createKey('labelHeight', size)]: labelHeight,
[createKey('blankHeight', size)]: blankHeight,
[createKey('feedbackFontSize', size)]: feedbackFontSize,
[createKey('feedbackHeight', size)]: feedbackHeight,
[createKey('labelPadding', direction)]: labelPadding,
[createKey('labelTextAlign', direction)]: labelTextAlign,
[createKey(createKey('labelFontSize', labelPlacement), size)]: labelFontSize
}
} = themeRef.value;
let mergedLabelTextAlign = (_a = labelTextAlignRef.value) !== null && _a !== void 0 ? _a : labelTextAlign;
if (labelPlacement === 'top') {
mergedLabelTextAlign = mergedLabelTextAlign === 'right' ? 'flex-end' : 'flex-start';
}
const cssVars = {
'--n-bezier': cubicBezierEaseInOut,
'--n-line-height': lineHeight,
'--n-blank-height': blankHeight,
'--n-label-font-size': labelFontSize,
'--n-label-text-align': mergedLabelTextAlign,
'--n-label-height': labelHeight,
'--n-label-padding': labelPadding,
'--n-label-font-weight': labelFontWeight,
'--n-asterisk-color': asteriskColor,
'--n-label-text-color': labelTextColor,
'--n-feedback-padding': feedbackPadding,
'--n-feedback-font-size': feedbackFontSize,
'--n-feedback-height': feedbackHeight,
'--n-feedback-text-color': feedbackTextColor,
'--n-feedback-text-color-warning': feedbackTextColorWarning,
'--n-feedback-text-color-error': feedbackTextColorError
};
return cssVars;
});
const themeClassHandle = inlineThemeDisabled ? useThemeClass('form-item', computed(() => {
var _a;
return `${mergedSizeRef.value[0]}${labelPlacementRef.value[0]}${((_a = labelTextAlignRef.value) === null || _a === void 0 ? void 0 : _a[0]) || ''}`;
}), cssVarsRef, props) : undefined;
const reverseColSpaceRef = computed(() => {
// label placement left
// require-mark-placement | label align | areas (1fr auto)
// left | left | mark text (need reverse)
// left | right | mark text (okay)
// right | left | mark text (okay)
// right | right | mark text (okay)
// right-hanging | left | text mark (okay)
// right-hanging | right | text mark (okay)
return labelPlacementRef.value === 'left' && mergedRequireMarkPlacementRef.value === 'left' && labelTextAlignRef.value === 'left';
});
return Object.assign(Object.assign(Object.assign(Object.assign({
labelElementRef,
mergedClsPrefix: mergedClsPrefixRef,
mergedRequired: mergedRequiredRef,
feedbackId: feedbackIdRef,
renderExplains: renderExplainsRef,
reverseColSpace: reverseColSpaceRef
}, formItemMiscRefs), formItemSizeRefs), exposedRef), {
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender
});
},
render() {
const {
$slots,
mergedClsPrefix,
mergedShowLabel,
mergedShowRequireMark,
mergedRequireMarkPlacement,
onRender
} = this;
const renderedShowRequireMark = mergedShowRequireMark !== undefined ? mergedShowRequireMark : this.mergedRequired;
onRender === null || onRender === void 0 ? void 0 : onRender();
const renderLabel = () => {
const labelText = this.$slots.label ? this.$slots.label() : this.label;
if (!labelText) return null;
const textNode = h("span", {
class: `${mergedClsPrefix}-form-item-label__text`
}, labelText);
const markNode = renderedShowRequireMark ? h("span", {
class: `${mergedClsPrefix}-form-item-label__asterisk`
}, mergedRequireMarkPlacement !== 'left' ? '\u00A0*' : '*\u00A0') : mergedRequireMarkPlacement === 'right-hanging' && h("span", {
class: `${mergedClsPrefix}-form-item-label__asterisk-placeholder`
}, '\u00A0*');
const {
labelProps
} = this;
return h("label", Object.assign({}, labelProps, {
class: [labelProps === null || labelProps === void 0 ? void 0 : labelProps.class, `${mergedClsPrefix}-form-item-label`, `${mergedClsPrefix}-form-item-label--${mergedRequireMarkPlacement}-mark`, this.reverseColSpace && `${mergedClsPrefix}-form-item-label--reverse-columns-space`],
style: this.mergedLabelStyle,
ref: "labelElementRef"
}), mergedRequireMarkPlacement === 'left' ? [markNode, textNode] : [textNode, markNode]);
};
return h("div", {
class: [`${mergedClsPrefix}-form-item`, this.themeClass, `${mergedClsPrefix}-form-item--${this.mergedSize}-size`, `${mergedClsPrefix}-form-item--${this.mergedLabelPlacement}-labelled`, this.isAutoLabelWidth && `${mergedClsPrefix}-form-item--auto-label-width`, !mergedShowLabel && `${mergedClsPrefix}-form-item--no-label`],
style: this.cssVars
}, mergedShowLabel && renderLabel(), h("div", {
class: [`${mergedClsPrefix}-form-item-blank`, this.mergedValidationStatus && `${mergedClsPrefix}-form-item-blank--${this.mergedValidationStatus}`]
}, $slots), this.mergedShowFeedback ? h("div", {
key: this.feedbackId,
style: this.feedbackStyle,
class: [`${mergedClsPrefix}-form-item-feedback-wrapper`, this.feedbackClass]
}, h(Transition, {
name: "fade-down-transition",
mode: "out-in"
}, {
default: () => {
const {
mergedValidationStatus
} = this;
return resolveWrappedSlot($slots.feedback, children => {
var _a;
const {
feedback
} = this;
const feedbackNodes = children || feedback ? h("div", {
key: "__feedback__",
class: `${mergedClsPrefix}-form-item-feedback__line`
}, children || feedback) : this.renderExplains.length ? (_a = this.renderExplains) === null || _a === void 0 ? void 0 : _a.map(({
key,
render
}) => h("div", {
key: key,
class: `${mergedClsPrefix}-form-item-feedback__line`
}, render())) : null;
return feedbackNodes ? mergedValidationStatus === 'warning' ? h("div", {
key: "controlled-warning",
class: `${mergedClsPrefix}-form-item-feedback ${mergedClsPrefix}-form-item-feedback--warning`
}, feedbackNodes) : mergedValidationStatus === 'error' ? h("div", {
key: "controlled-error",
class: `${mergedClsPrefix}-form-item-feedback ${mergedClsPrefix}-form-item-feedback--error`
}, feedbackNodes) : mergedValidationStatus === 'success' ? h("div", {
key: "controlled-success",
class: `${mergedClsPrefix}-form-item-feedback ${mergedClsPrefix}-form-item-feedback--success`
}, feedbackNodes) : h("div", {
key: "controlled-default",
class: `${mergedClsPrefix}-form-item-feedback`
}, feedbackNodes) : null;
});
}
})) : null);
}
});