mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-04-12 21:50:23 +08:00
complete validation trait with new trait
This commit is contained in:
parent
f50d201b0d
commit
88b1aad48e
123
packages/runtime/example/input-components/inputValidation.html
Normal file
123
packages/runtime/example/input-components/inputValidation.html
Normal file
@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>meta-ui runtime example: input validation component</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">
|
||||
import renderApp from '../../src/main.tsx';
|
||||
|
||||
renderApp({
|
||||
version: 'example/v1',
|
||||
metadata: {
|
||||
name: 'inputValidation',
|
||||
description: 'input validation example',
|
||||
},
|
||||
spec: {
|
||||
components: [
|
||||
{
|
||||
id: 'emailInput',
|
||||
type: 'chakra_ui/v1/input',
|
||||
properties: {
|
||||
size: 'lg',
|
||||
left: {
|
||||
type: 'addon',
|
||||
children: '邮箱',
|
||||
},
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/validation',
|
||||
properties: {
|
||||
value: '{{ emailInput.value || "" }}',
|
||||
maxLength: 20,
|
||||
minLength: 10,
|
||||
rule: 'email',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'emailValidationText',
|
||||
type: 'core/v1/text',
|
||||
properties: {
|
||||
value: {
|
||||
raw: '{{ emailInput.data.validationResult.errorMsg }}',
|
||||
format: 'plain',
|
||||
},
|
||||
},
|
||||
traits: [],
|
||||
},
|
||||
{
|
||||
id: 'phoneInput',
|
||||
type: 'chakra_ui/v1/input',
|
||||
properties: {
|
||||
size: 'lg',
|
||||
left: {
|
||||
type: 'addon',
|
||||
children: '手机',
|
||||
},
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/validation',
|
||||
properties: {
|
||||
value: '{{ phoneInput.value || "" }}',
|
||||
maxLength: 100,
|
||||
minLength: 0,
|
||||
rule: 'phoneNumber',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'phoneValidationText',
|
||||
type: 'core/v1/text',
|
||||
properties: {
|
||||
value: {
|
||||
raw: '{{ phoneInput.data.validationResult.errorMsg }}',
|
||||
format: 'plain',
|
||||
},
|
||||
},
|
||||
traits: [],
|
||||
},
|
||||
{
|
||||
id: 'submitButton',
|
||||
type: 'plain/v1/button',
|
||||
properties: {
|
||||
text: {
|
||||
raw: '提交',
|
||||
format: 'plain',
|
||||
},
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/event',
|
||||
properties: {
|
||||
events: [
|
||||
{
|
||||
event: 'click',
|
||||
componentId: '$utils',
|
||||
method: {
|
||||
name: 'alert',
|
||||
parameters:
|
||||
'{{ `邮箱:${ emailInput.value } 手机号:${ phoneInput.value }` }}',
|
||||
},
|
||||
wait: {},
|
||||
disabled:
|
||||
'{{ !emailInput.data.validationResult.isValid || !phoneInput.data.validationResult.isValid }}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -85,12 +85,12 @@ const ImplWrapper = React.forwardRef<
|
||||
return prev;
|
||||
}, new Map())
|
||||
);
|
||||
|
||||
// eval traits' properties
|
||||
useEffect(() => {
|
||||
console.log('traitPropertiesMap更新了');
|
||||
const stops: ReturnType<typeof watch>[] = [];
|
||||
for (const t of c.traits) {
|
||||
const { stop, result } = deepEval(t.properties, ({ result }) => {
|
||||
console.log('更新了 map 的trait', result);
|
||||
setTraitPropertiesMap(
|
||||
new Map(traitPropertiesMap.set(t, { ...result }))
|
||||
);
|
||||
@ -101,18 +101,7 @@ const ImplWrapper = React.forwardRef<
|
||||
return () => stops.forEach(s => s());
|
||||
}, [c.traits]);
|
||||
|
||||
// const propsFromTraits: Record<string, unknown> = useMemo(() => {
|
||||
// return c.traits.reduce((prevProps, t) => {
|
||||
// const tImpl = registry.getTrait(
|
||||
// t.parsedType.version,
|
||||
// t.parsedType.name
|
||||
// ).impl;
|
||||
// const traitProps = traitPropertiesMap.get(t)
|
||||
// const traitResult = tImpl({...traitProps, mergeState, subscribeMethods})
|
||||
// return {...prevProps, ...traitResult.props}
|
||||
// }, {})
|
||||
// }, [c.traits, traitPropertiesMap])
|
||||
|
||||
// excecute traits and get result
|
||||
const propsFromTraits: Record<string, unknown> = c.traits.reduce(
|
||||
(prevProps, t) => {
|
||||
const tImpl = registry.getTrait(
|
||||
@ -134,24 +123,22 @@ const ImplWrapper = React.forwardRef<
|
||||
merge(deepEval(c.properties).result, propsFromTraits)
|
||||
);
|
||||
|
||||
// eval component properties
|
||||
useEffect(() => {
|
||||
const { stop, result } = deepEval(c.properties, ({ result }) => {
|
||||
setComponentProps(result);
|
||||
console.log('updateresult', result);
|
||||
setComponentProps({ ...result });
|
||||
});
|
||||
console.log('result', result);
|
||||
|
||||
setComponentProps(result);
|
||||
return stop;
|
||||
}, []);
|
||||
|
||||
const mergeProps = { ...componentProps, ...propsFromTraits };
|
||||
console.log('mergedProps', componentProps);
|
||||
const mergedProps = { ...componentProps, ...propsFromTraits };
|
||||
|
||||
let C = (
|
||||
<Impl
|
||||
key={c.id}
|
||||
{...mergeProps}
|
||||
{...mergedProps}
|
||||
mergeState={mergeState}
|
||||
subscribeMethods={subscribeMethods}
|
||||
slotsMap={slotsMap}
|
||||
|
@ -67,6 +67,7 @@ const Input: ComponentImplementation<{
|
||||
left,
|
||||
right,
|
||||
mergeState,
|
||||
data,
|
||||
}) => {
|
||||
const [value, setValue] = React.useState(''); // TODO: pin input
|
||||
const onChange = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
@ -74,7 +75,8 @@ const Input: ComponentImplementation<{
|
||||
|
||||
useEffect(() => {
|
||||
mergeState({ value });
|
||||
}, [value]);
|
||||
mergeState({ data });
|
||||
}, [value, data]);
|
||||
|
||||
return (
|
||||
<InputGroup size={size}>
|
||||
|
@ -50,6 +50,7 @@ export type ComponentImplementation<T = any> = React.FC<
|
||||
subscribeMethods: SubscribeMethods;
|
||||
slotsMap: SlotsMap | undefined;
|
||||
style?: CSSProperties;
|
||||
data?: unknown;
|
||||
}
|
||||
>;
|
||||
|
||||
|
@ -55,11 +55,15 @@ function maskedEval(raw: string) {
|
||||
if (!dynamic) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
return new Function(`with(this) { return ${expression} }`).call({
|
||||
...stateStore,
|
||||
...builtIn,
|
||||
});
|
||||
try {
|
||||
const result = new Function(`with(this) { return ${expression} }`).call({
|
||||
...stateStore,
|
||||
...builtIn,
|
||||
});
|
||||
return result;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const mapValuesDeep = (
|
||||
|
127
packages/runtime/src/traits/core/validation.tsx
Normal file
127
packages/runtime/src/traits/core/validation.tsx
Normal file
@ -0,0 +1,127 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { createTrait } from '@meta-ui/core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { TraitImplementation } from '../../registry';
|
||||
import { min } from 'lodash';
|
||||
|
||||
type ValidationResult = { isValid: boolean; errorMsg: string };
|
||||
type ValidationRule = (text: string) => { isValid: boolean; errorMsg: string };
|
||||
|
||||
const rules = new Map<string, ValidationRule>();
|
||||
|
||||
export function addValidationRule(name: string, rule: ValidationRule) {
|
||||
rules.set(name, rule);
|
||||
}
|
||||
(window as any).rules = rules;
|
||||
|
||||
addValidationRule('email', text => {
|
||||
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(text)) {
|
||||
return {
|
||||
isValid: true,
|
||||
errorMsg: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isValid: false,
|
||||
errorMsg: '请输入正确的 email',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
addValidationRule('phoneNumber', text => {
|
||||
if (/^1[3456789]\d{9}$/.test(text)) {
|
||||
return {
|
||||
isValid: true,
|
||||
errorMsg: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isValid: false,
|
||||
errorMsg: '请输入正确的手机号码',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
type ValidationProps = {
|
||||
value: string;
|
||||
minLength: number;
|
||||
maxLength: number;
|
||||
rule: string;
|
||||
};
|
||||
|
||||
const useValidationTrait: TraitImplementation<ValidationProps> = props => {
|
||||
const { value, minLength, maxLength, rule, mergeState } = props;
|
||||
let result: ValidationResult = {
|
||||
isValid: true,
|
||||
errorMsg: '',
|
||||
};
|
||||
|
||||
if (value.length > maxLength) {
|
||||
result = {
|
||||
isValid: false,
|
||||
errorMsg: `最长不能超过${maxLength}个字符`,
|
||||
};
|
||||
} else if (value.length < minLength) {
|
||||
result = {
|
||||
isValid: false,
|
||||
errorMsg: `不能少于${minLength}个字符`,
|
||||
};
|
||||
}
|
||||
|
||||
const rulesArr = rule.split(',');
|
||||
for (const ruleName of rulesArr) {
|
||||
const validateFunc = rules.get(ruleName);
|
||||
if (validateFunc) {
|
||||
result = validateFunc(value);
|
||||
if (!result.isValid) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
data: {
|
||||
validationResult: result,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ValidationValuePropertySchema = Type.String();
|
||||
const ValidationRulePropertySchema = Type.String();
|
||||
const ValidationMinLengthPropertySchema = Type.Integer();
|
||||
const ValidationMaxLengthPropertySchema = Type.Integer();
|
||||
|
||||
export default {
|
||||
...createTrait({
|
||||
version: 'core/v1',
|
||||
metadata: {
|
||||
name: 'validation',
|
||||
description: 'validation trait',
|
||||
},
|
||||
spec: {
|
||||
properties: [
|
||||
{
|
||||
name: 'value',
|
||||
...ValidationValuePropertySchema,
|
||||
},
|
||||
{
|
||||
name: 'rule',
|
||||
...ValidationRulePropertySchema,
|
||||
},
|
||||
{
|
||||
name: 'minLength',
|
||||
...ValidationMinLengthPropertySchema,
|
||||
},
|
||||
{
|
||||
name: 'maxLength',
|
||||
...ValidationMaxLengthPropertySchema,
|
||||
},
|
||||
],
|
||||
state: {},
|
||||
methods: [],
|
||||
},
|
||||
}),
|
||||
impl: useValidationTrait,
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user