complete validation trait with new trait

This commit is contained in:
Bowen Tan 2021-08-25 14:45:09 +08:00
parent f50d201b0d
commit 88b1aad48e
6 changed files with 270 additions and 26 deletions

View 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>

View File

@ -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}

View File

@ -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}>

View File

@ -50,6 +50,7 @@ export type ComponentImplementation<T = any> = React.FC<
subscribeMethods: SubscribeMethods;
slotsMap: SlotsMap | undefined;
style?: CSSProperties;
data?: unknown;
}
>;

View File

@ -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 = (

View 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,
};