mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-20 12:28:08 +00:00
Merge branch 'preset-vision/0.9.0' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into preset-vision/0.9.0
This commit is contained in:
commit
9bf2e3b4ef
3
.gitignore
vendored
3
.gitignore
vendored
@ -100,3 +100,6 @@ typings/
|
|||||||
|
|
||||||
# mac config files
|
# mac config files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# codealike
|
||||||
|
codealike.json
|
||||||
@ -3,6 +3,17 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.9.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.9.2...@ali/lowcode-material-parser@0.9.3) (2020-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 support parsing sub components ([70f3e32](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/70f3e325c64bafe6a098e7eb872a81308566e811))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.9.2"></a>
|
<a name="0.9.2"></a>
|
||||||
## [0.9.2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.9.1...@ali/lowcode-material-parser@0.9.2) (2020-05-07)
|
## [0.9.2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.9.1...@ali/lowcode-material-parser@0.9.2) (2020-05-07)
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ali/lowcode-material-parser",
|
"name": "@ali/lowcode-material-parser",
|
||||||
"version": "0.9.2",
|
"version": "0.9.3",
|
||||||
"description": "material parser for Ali lowCode engine",
|
"description": "material parser for Ali lowCode engine",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
@ -54,7 +54,8 @@
|
|||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"react-docgen": "^5.3.0",
|
"react-docgen": "^5.3.0",
|
||||||
"semver": "^7.1.3",
|
"semver": "^7.1.3",
|
||||||
"short-uuid": "^3.1.1"
|
"short-uuid": "^3.1.1",
|
||||||
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://registry.npm.alibaba-inc.com"
|
"registry": "https://registry.npm.alibaba-inc.com"
|
||||||
|
|||||||
@ -45,10 +45,10 @@ export async function genManifest(
|
|||||||
npm: {
|
npm: {
|
||||||
package: matScanModel.pkgName,
|
package: matScanModel.pkgName,
|
||||||
version: matScanModel.pkgVersion,
|
version: matScanModel.pkgVersion,
|
||||||
exportName: matParsedModel.componentName,
|
exportName: matParsedModel.meta?.exportName || matParsedModel.componentName,
|
||||||
main: matScanModel.mainFilePath,
|
main: matScanModel.mainFilePath,
|
||||||
destructuring: false,
|
destructuring: matParsedModel.meta?.exportName !== 'default',
|
||||||
subName: '',
|
subName: matParsedModel.meta?.subName || '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
const {
|
||||||
|
getMemberValuePath,
|
||||||
|
isReactComponentClass,
|
||||||
|
isReactComponentMethod,
|
||||||
|
resolveToValue,
|
||||||
|
match,
|
||||||
|
} = require('react-docgen').utils;
|
||||||
|
import getMethodDocumentation from '../utils/getMethodDocumentation';
|
||||||
|
const { traverseShallow } = require('react-docgen/dist/utils/traverse');
|
||||||
|
/**
|
||||||
|
* The following values/constructs are considered methods:
|
||||||
|
*
|
||||||
|
* - Method declarations in classes (except "constructor" and React lifecycle
|
||||||
|
* methods
|
||||||
|
* - Public class fields in classes whose value are a functions
|
||||||
|
* - Object properties whose values are functions
|
||||||
|
*/
|
||||||
|
function isMethod(path: any) {
|
||||||
|
const isProbablyMethod =
|
||||||
|
(t.MethodDefinition.check(path.node) && path.node.kind !== 'constructor') ||
|
||||||
|
((t.ClassProperty.check(path.node) || t.Property.check(path.node)) && t.Function.check(path.get('value').node));
|
||||||
|
|
||||||
|
return isProbablyMethod && !isReactComponentMethod(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAssignedMethods(scope: any, idPath: any) {
|
||||||
|
const results: any[] = [];
|
||||||
|
|
||||||
|
if (!t.Identifier.check(idPath.node)) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = idPath.node.name;
|
||||||
|
const idScope = idPath.scope.lookup(idPath.node.name);
|
||||||
|
|
||||||
|
traverseShallow(scope.path, {
|
||||||
|
visitAssignmentExpression: function(path: any) {
|
||||||
|
const node = path.node;
|
||||||
|
if (
|
||||||
|
match(node.left, {
|
||||||
|
type: 'MemberExpression',
|
||||||
|
object: { type: 'Identifier', name },
|
||||||
|
}) &&
|
||||||
|
path.scope.lookup(name) === idScope &&
|
||||||
|
t.Function.check(resolveToValue(path.get('right')).node)
|
||||||
|
) {
|
||||||
|
results.push(path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.traverse(path);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all flow types for the methods of a react component. Doesn't
|
||||||
|
* return any react specific lifecycle methods.
|
||||||
|
*/
|
||||||
|
export default function componentMethodsHandler(documentation: any, path: any) {
|
||||||
|
// Extract all methods from the class or object.
|
||||||
|
let methodPaths = [];
|
||||||
|
if (isReactComponentClass(path)) {
|
||||||
|
methodPaths = path.get('body', 'body').filter(isMethod);
|
||||||
|
} else if (t.ObjectExpression.check(path.node)) {
|
||||||
|
methodPaths = path.get('properties').filter(isMethod);
|
||||||
|
|
||||||
|
// Add the statics object properties.
|
||||||
|
const statics = getMemberValuePath(path, 'statics');
|
||||||
|
if (statics) {
|
||||||
|
statics.get('properties').each((p: any) => {
|
||||||
|
if (isMethod(p)) {
|
||||||
|
p.node.static = true;
|
||||||
|
methodPaths.push(p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
t.VariableDeclarator.check(path.parent.node) &&
|
||||||
|
path.parent.node.init === path.node &&
|
||||||
|
t.Identifier.check(path.parent.node.id)
|
||||||
|
) {
|
||||||
|
methodPaths = findAssignedMethods(path.parent.scope, path.parent.get('id'));
|
||||||
|
} else if (
|
||||||
|
t.AssignmentExpression.check(path.parent.node) &&
|
||||||
|
path.parent.node.right === path.node &&
|
||||||
|
t.Identifier.check(path.parent.node.left)
|
||||||
|
) {
|
||||||
|
methodPaths = findAssignedMethods(path.parent.scope, path.parent.get('left'));
|
||||||
|
} else if (t.FunctionDeclaration.check(path.node)) {
|
||||||
|
methodPaths = findAssignedMethods(path.parent.scope, path.get('id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
documentation.set('methods', methodPaths.map(getMethodDocumentation).filter(Boolean));
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
import getTSType from '../utils/getTSType';
|
||||||
|
const { unwrapUtilityType } = require('react-docgen/dist/utils/flowUtilityTypes');
|
||||||
|
const { getFlowType, getPropertyName, resolveToValue } = require('react-docgen').utils;
|
||||||
|
const setPropDescription = require('react-docgen/dist/utils/setPropDescription').default;
|
||||||
|
import getFlowTypeFromReactComponent from '../utils/getFlowTypeFromReactComponent';
|
||||||
|
import { applyToFlowTypeProperties } from '../utils/getFlowTypeFromReactComponent';
|
||||||
|
|
||||||
|
function setPropDescriptor(documentation: any, path: any, typeParams: any) {
|
||||||
|
if (t.ObjectTypeSpreadProperty.check(path.node)) {
|
||||||
|
const argument = unwrapUtilityType(path.get('argument'));
|
||||||
|
|
||||||
|
if (t.ObjectTypeAnnotation.check(argument.node)) {
|
||||||
|
applyToFlowTypeProperties(
|
||||||
|
documentation,
|
||||||
|
argument,
|
||||||
|
(propertyPath: any, innerTypeParams: any) => {
|
||||||
|
setPropDescriptor(documentation, propertyPath, innerTypeParams);
|
||||||
|
},
|
||||||
|
typeParams,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = argument.get('id').get('name');
|
||||||
|
const resolvedPath = resolveToValue(name);
|
||||||
|
|
||||||
|
if (resolvedPath && t.TypeAlias.check(resolvedPath.node)) {
|
||||||
|
const right = resolvedPath.get('right');
|
||||||
|
applyToFlowTypeProperties(
|
||||||
|
documentation,
|
||||||
|
right,
|
||||||
|
(propertyPath: any, innerTypeParams: any) => {
|
||||||
|
setPropDescriptor(documentation, propertyPath, innerTypeParams);
|
||||||
|
},
|
||||||
|
typeParams,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
documentation.addComposes(name.node.name);
|
||||||
|
}
|
||||||
|
} else if (t.ObjectTypeProperty.check(path.node)) {
|
||||||
|
const type = getFlowType(path.get('value'), typeParams);
|
||||||
|
const propName = getPropertyName(path);
|
||||||
|
if (!propName) return;
|
||||||
|
|
||||||
|
const propDescriptor = documentation.getPropDescriptor(propName);
|
||||||
|
propDescriptor.required = !path.node.optional;
|
||||||
|
propDescriptor.flowType = type;
|
||||||
|
|
||||||
|
// We are doing this here instead of in a different handler
|
||||||
|
// to not need to duplicate the logic for checking for
|
||||||
|
// imported types that are spread in to props.
|
||||||
|
setPropDescription(documentation, path);
|
||||||
|
} else if (t.TSPropertySignature.check(path.node)) {
|
||||||
|
const type = getTSType(path.get('typeAnnotation'), typeParams);
|
||||||
|
|
||||||
|
const propName = getPropertyName(path);
|
||||||
|
if (!propName) return;
|
||||||
|
|
||||||
|
const propDescriptor = documentation.getPropDescriptor(propName);
|
||||||
|
propDescriptor.required = !path.node.optional;
|
||||||
|
propDescriptor.tsType = type;
|
||||||
|
|
||||||
|
// We are doing this here instead of in a different handler
|
||||||
|
// to not need to duplicate the logic for checking for
|
||||||
|
// imported types that are spread in to props.
|
||||||
|
setPropDescription(documentation, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This handler tries to find flow Type annotated react components and extract
|
||||||
|
* its types to the documentation. It also extracts docblock comments which are
|
||||||
|
* inlined in the type definition.
|
||||||
|
*/
|
||||||
|
export default function flowTypeHandler(documentation: any, path: any) {
|
||||||
|
const flowTypesPath = getFlowTypeFromReactComponent(path);
|
||||||
|
|
||||||
|
if (!flowTypesPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyToFlowTypeProperties(documentation, flowTypesPath, (propertyPath: any, typeParams: any) => {
|
||||||
|
setPropDescriptor(documentation, propertyPath, typeParams);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,23 +1,23 @@
|
|||||||
import {
|
import { propTypeHandler, contextTypeHandler, childContextTypeHandler } from './propTypeHandler';
|
||||||
propTypeHandler,
|
|
||||||
contextTypeHandler,
|
|
||||||
childContextTypeHandler,
|
|
||||||
} from './propTypeHandler';
|
|
||||||
import defaultPropsHandler from './defaultPropsHandler';
|
import defaultPropsHandler from './defaultPropsHandler';
|
||||||
|
import flowTypeHandler from './flowTypeHandler';
|
||||||
|
import componentMethodsHandler from './componentMethodsHandler';
|
||||||
|
import preProcessHandler from './preProcessHandler';
|
||||||
|
|
||||||
const { handlers } = require('react-docgen');
|
const { handlers } = require('react-docgen');
|
||||||
|
|
||||||
const defaultHandlers = [
|
const defaultHandlers = [
|
||||||
|
preProcessHandler,
|
||||||
propTypeHandler,
|
propTypeHandler,
|
||||||
contextTypeHandler,
|
contextTypeHandler,
|
||||||
childContextTypeHandler,
|
childContextTypeHandler,
|
||||||
handlers.propTypeCompositionHandler,
|
handlers.propTypeCompositionHandler,
|
||||||
handlers.propDocBlockHandler,
|
handlers.propDocBlockHandler,
|
||||||
handlers.flowTypeHandler,
|
flowTypeHandler,
|
||||||
defaultPropsHandler,
|
defaultPropsHandler,
|
||||||
handlers.componentDocblockHandler,
|
handlers.componentDocblockHandler,
|
||||||
handlers.displayNameHandler,
|
handlers.displayNameHandler,
|
||||||
handlers.componentMethodsHandler,
|
componentMethodsHandler,
|
||||||
handlers.componentMethodsJsDocHandler,
|
handlers.componentMethodsJsDocHandler,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
export default function preProcessHandler(documentation: any, path: any) {
|
||||||
|
documentation.set('meta', path.__meta);
|
||||||
|
}
|
||||||
@ -14,6 +14,9 @@ export default function parse(params: { fileContent: string; filePath: string })
|
|||||||
return resolver(ast);
|
return resolver(ast);
|
||||||
},
|
},
|
||||||
handlers,
|
handlers,
|
||||||
|
{
|
||||||
|
filename: filePath,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
const coms = result.reduce((res: any[], info: any) => {
|
const coms = result.reduce((res: any[], info: any) => {
|
||||||
if (!info || !info.props) return res;
|
if (!info || !info.props) return res;
|
||||||
@ -29,6 +32,7 @@ export default function parse(params: { fileContent: string; filePath: string })
|
|||||||
res.push({
|
res.push({
|
||||||
componentName: info.displayName,
|
componentName: info.displayName,
|
||||||
props,
|
props,
|
||||||
|
meta: info.meta || {},
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export default function checkIsIIFE(path: any) {
|
export default function checkIsIIFE(path: any) {
|
||||||
return (
|
return (
|
||||||
|
path.value &&
|
||||||
path.value.callee &&
|
path.value.callee &&
|
||||||
path.value.callee.type === 'FunctionExpression' &&
|
path.value.callee.type === 'FunctionExpression' &&
|
||||||
path.node.type === 'CallExpression'
|
path.node.type === 'CallExpression'
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
const { match, resolveToValue } = require('react-docgen').utils;
|
||||||
|
const { traverseShallow } = require('react-docgen/dist/utils/traverse');
|
||||||
|
import isReactComponentStaticMember from './isReactComponentStaticMember';
|
||||||
|
import getRoot from '../utils/getRoot';
|
||||||
|
|
||||||
|
function findAssignedMethods(scope: any, idPath: any) {
|
||||||
|
const results: any[] = [];
|
||||||
|
|
||||||
|
if (!t.Identifier.check(idPath.node)) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = idPath.node.name;
|
||||||
|
const idScope = idPath.scope.lookup(idPath.node.name);
|
||||||
|
|
||||||
|
traverseShallow(scope.path, {
|
||||||
|
visitAssignmentExpression: function(path: any) {
|
||||||
|
const node = path.node;
|
||||||
|
if (
|
||||||
|
match(node.left, {
|
||||||
|
type: 'MemberExpression',
|
||||||
|
object: { type: 'Identifier', name },
|
||||||
|
})
|
||||||
|
// && path.scope.lookup(name) === idScope
|
||||||
|
) {
|
||||||
|
results.push(path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.traverse(path);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return results.filter((x) => !isReactComponentStaticMember(x.get('left')));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default findAssignedMethods;
|
||||||
|
|
||||||
|
// const findAssignedMethodsFromScopes = (scope: any, idPath: any) => {
|
||||||
|
// const rootNode = getRoot(idPath);
|
||||||
|
// let { __scope: scopes = [] } = rootNode;
|
||||||
|
// if (!scopes.find((x: any) => x.scope === scope && x.idPath === idPath)) {
|
||||||
|
// scopes = [
|
||||||
|
// ...scopes,
|
||||||
|
// {
|
||||||
|
// scope,
|
||||||
|
// idPath,
|
||||||
|
// },
|
||||||
|
// ];
|
||||||
|
// }
|
||||||
|
// return scopes.map(({ scope: s, idPath: id }: any) => findAssignedMethods(s, id)).flatMap((x: any) => x);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export { findAssignedMethodsFromScopes };
|
||||||
@ -7,11 +7,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { namedTypes as t, visit } from 'ast-types';
|
import { namedTypes as t, visit } from 'ast-types';
|
||||||
|
import { uniqBy } from 'lodash';
|
||||||
import checkIsIIFE from './checkIsIIFE';
|
import checkIsIIFE from './checkIsIIFE';
|
||||||
import resolveHOC from './resolveHOC';
|
import resolveHOC from './resolveHOC';
|
||||||
import resolveIIFE from './resolveIIFE';
|
import resolveIIFE from './resolveIIFE';
|
||||||
import resolveImport from './resolveImport';
|
import resolveImport, { isImportLike } from './resolveImport';
|
||||||
import resolveTranspiledClass from './resolveTranspiledClass';
|
import resolveTranspiledClass from './resolveTranspiledClass';
|
||||||
|
import isStaticMethod from './isStaticMethod';
|
||||||
|
import findAssignedMethods from './findAssignedMethods';
|
||||||
|
import resolveExportDeclaration from './resolveExportDeclaration';
|
||||||
|
import makeProxy from '../utils/makeProxy';
|
||||||
|
const expressionTo = require('react-docgen/dist/utils/expressionTo');
|
||||||
|
import { get, set, has, ICache } from '../utils/cache';
|
||||||
|
import getName from '../utils/getName';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isExportsOrModuleAssignment,
|
isExportsOrModuleAssignment,
|
||||||
@ -20,8 +28,8 @@ const {
|
|||||||
isReactForwardRefCall,
|
isReactForwardRefCall,
|
||||||
isStatelessComponent,
|
isStatelessComponent,
|
||||||
normalizeClassDefinition,
|
normalizeClassDefinition,
|
||||||
resolveExportDeclaration,
|
|
||||||
resolveToValue,
|
resolveToValue,
|
||||||
|
getMemberValuePath,
|
||||||
} = require('react-docgen').utils;
|
} = require('react-docgen').utils;
|
||||||
|
|
||||||
function ignore() {
|
function ignore() {
|
||||||
@ -47,16 +55,14 @@ function resolveDefinition(definition: any) {
|
|||||||
} else if (isReactComponentClass(definition)) {
|
} else if (isReactComponentClass(definition)) {
|
||||||
normalizeClassDefinition(definition);
|
normalizeClassDefinition(definition);
|
||||||
return definition;
|
return definition;
|
||||||
} else if (
|
} else if (isStatelessComponent(definition) || isReactForwardRefCall(definition)) {
|
||||||
isStatelessComponent(definition) ||
|
|
||||||
isReactForwardRefCall(definition)
|
|
||||||
) {
|
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefinition(definition: any): any {
|
function getDefinition(definition: any, cache: ICache = {}): any {
|
||||||
|
const { __meta: exportMeta = {} } = definition;
|
||||||
if (checkIsIIFE(definition)) {
|
if (checkIsIIFE(definition)) {
|
||||||
definition = resolveToValue(resolveIIFE(definition));
|
definition = resolveToValue(resolveIIFE(definition));
|
||||||
if (!isComponentDefinition(definition)) {
|
if (!isComponentDefinition(definition)) {
|
||||||
@ -64,24 +70,195 @@ function getDefinition(definition: any): any {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
definition = resolveToValue(resolveHOC(definition));
|
definition = resolveToValue(resolveHOC(definition));
|
||||||
|
if (isComponentDefinition(definition)) {
|
||||||
|
definition = makeProxy(definition, {
|
||||||
|
__meta: exportMeta,
|
||||||
|
});
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
if (checkIsIIFE(definition)) {
|
if (checkIsIIFE(definition)) {
|
||||||
definition = resolveToValue(resolveIIFE(definition));
|
definition = resolveToValue(resolveIIFE(definition));
|
||||||
if (!isComponentDefinition(definition)) {
|
if (!isComponentDefinition(definition)) {
|
||||||
definition = resolveTranspiledClass(definition);
|
definition = resolveTranspiledClass(definition);
|
||||||
}
|
}
|
||||||
} else if (t.SequenceExpression.check(definition.node)) {
|
} else if (t.SequenceExpression.check(definition.node)) {
|
||||||
return getDefinition(
|
return getDefinition(resolveToValue(definition.get('expressions').get(0)), cache);
|
||||||
resolveToValue(definition.get('expressions').get(0)),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
definition = resolveImport(
|
return resolveImport(definition, (ast: any, sourcePath: string) => {
|
||||||
definition,
|
const importMeta: any[] = [];
|
||||||
findAllExportedComponentDefinition,
|
if (t.ImportDeclaration.check(definition.node)) {
|
||||||
);
|
// @ts-ignore
|
||||||
|
const specifiers = definition.get('specifiers');
|
||||||
|
specifiers.each((spec: any) => {
|
||||||
|
const { node } = spec;
|
||||||
|
importMeta.push({
|
||||||
|
localName: node.local.name,
|
||||||
|
importedName: node.imported ? node.imported.name : 'default',
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let result;
|
||||||
|
if (has('ast-export', ast.__path)) {
|
||||||
|
result = get('ast-export', ast.__path);
|
||||||
|
} else {
|
||||||
|
result = findAllExportedComponentDefinition(ast);
|
||||||
|
set('ast-export', ast.__path, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportList: any[] = [];
|
||||||
|
const importList: any[] = [];
|
||||||
|
result = result.forEach((def: any) => {
|
||||||
|
let { __meta: meta = {} } = def;
|
||||||
|
let exportName = meta.exportName;
|
||||||
|
for (let item of importMeta) {
|
||||||
|
if (exportName === item.importedName) {
|
||||||
|
exportName = item.localName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportName) {
|
||||||
|
importList.push(makeProxy(def, { __meta: { exportName } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextMeta: any = {
|
||||||
|
exportName,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (exportName === exportMeta.localName) {
|
||||||
|
nextMeta.exportName = exportMeta.exportName;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportMeta.subName) {
|
||||||
|
nextMeta.subName = exportMeta.subName;
|
||||||
|
} else if (meta.subName) {
|
||||||
|
nextMeta.subName = meta.subName;
|
||||||
|
}
|
||||||
|
exportList.push(makeProxy(def, { __meta: nextMeta }));
|
||||||
|
});
|
||||||
|
cache[sourcePath] = importList;
|
||||||
|
|
||||||
|
// result = result.filter((x) => !x.__shouldDelete);
|
||||||
|
return exportList;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (definition && !definition.__meta) {
|
||||||
|
definition.__meta = exportMeta;
|
||||||
}
|
}
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IMethodsPath {
|
||||||
|
subName: string;
|
||||||
|
localName: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all flow types for the methods of a react component. Doesn't
|
||||||
|
* return any react specific lifecycle methods.
|
||||||
|
*/
|
||||||
|
function getSubComponents(path: any, scope: any, cache: ICache) {
|
||||||
|
// Extract all methods from the class or object.
|
||||||
|
let methodPaths = [];
|
||||||
|
if (isReactComponentClass(path)) {
|
||||||
|
methodPaths = path.get('body', 'body').filter(isStaticMethod);
|
||||||
|
methodPaths = [...methodPaths, ...findAssignedMethods(scope || path.scope, path.get('id'))];
|
||||||
|
} else if (t.ObjectExpression.check(path.node)) {
|
||||||
|
methodPaths = path.get('properties').filter(isStaticMethod);
|
||||||
|
methodPaths = [...methodPaths, ...findAssignedMethods(scope || path.scope, path.get('id'))];
|
||||||
|
// Add the statics object properties.
|
||||||
|
const statics = getMemberValuePath(path, 'statics');
|
||||||
|
if (statics) {
|
||||||
|
statics.get('properties').each((p: any) => {
|
||||||
|
if (isStaticMethod(p)) {
|
||||||
|
p.node.static = true;
|
||||||
|
methodPaths.push(p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
t.VariableDeclarator.check(path.parent.node) &&
|
||||||
|
path.parent.node.init === path.node &&
|
||||||
|
t.Identifier.check(path.parent.node.id)
|
||||||
|
) {
|
||||||
|
methodPaths = findAssignedMethods(scope || path.parent.scope, path.parent.get('id'));
|
||||||
|
} else if (
|
||||||
|
t.AssignmentExpression.check(path.parent.node) &&
|
||||||
|
path.parent.node.right === path.node &&
|
||||||
|
t.Identifier.check(path.parent.node.left)
|
||||||
|
) {
|
||||||
|
methodPaths = findAssignedMethods(scope || path.parent.scope, path.parent.get('left'));
|
||||||
|
} else if (t.FunctionDeclaration.check(path.node)) {
|
||||||
|
methodPaths = findAssignedMethods(scope || path.parent.scope, path.get('id'));
|
||||||
|
} else if (t.ArrowFunctionExpression.check(path.node)) {
|
||||||
|
methodPaths = findAssignedMethods(scope || path.parent.scope, path.parent.get('id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
methodPaths
|
||||||
|
.map((x: any) => {
|
||||||
|
if (t.ClassProperty.check(x.node)) {
|
||||||
|
return {
|
||||||
|
value: x.get('value'),
|
||||||
|
subName: x.node.key.name,
|
||||||
|
localName: getName(x.get('value')),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: x,
|
||||||
|
subName: x.node.left.property.name,
|
||||||
|
localName: getName(x.get('right')),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.map(({ subName, localName, value }: IMethodsPath) => ({
|
||||||
|
subName,
|
||||||
|
localName,
|
||||||
|
value: resolveToValue(value),
|
||||||
|
}))
|
||||||
|
.map(({ subName, localName, value }: IMethodsPath) => {
|
||||||
|
let def = getDefinition(
|
||||||
|
makeProxy(value, {
|
||||||
|
__meta: {
|
||||||
|
localName,
|
||||||
|
subName,
|
||||||
|
exportName: path.__meta && path.__meta.exportName,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
cache,
|
||||||
|
);
|
||||||
|
if (!Array.isArray(def)) {
|
||||||
|
def = [def];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
subName,
|
||||||
|
localName,
|
||||||
|
value: def.flatMap((x: any) => x).filter((x: any) => isComponentDefinition(x)),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.map(({ subName, localName, value }: IMethodsPath) =>
|
||||||
|
value.map((x: any) => ({
|
||||||
|
subName,
|
||||||
|
localName,
|
||||||
|
value: x,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
// @ts-ignore
|
||||||
|
.flatMap((x: any) => x)
|
||||||
|
.map(({ subName, localName, value }: IMethodsPath) => {
|
||||||
|
const __meta = {
|
||||||
|
subName: subName,
|
||||||
|
exportName: path.__meta && path.__meta.exportName,
|
||||||
|
};
|
||||||
|
return makeProxy(value, { __meta });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an AST, this function tries to find the exported component definition.
|
* Given an AST, this function tries to find the exported component definition.
|
||||||
*
|
*
|
||||||
@ -99,6 +276,8 @@ function getDefinition(definition: any): any {
|
|||||||
*/
|
*/
|
||||||
export default function findAllExportedComponentDefinition(ast: any) {
|
export default function findAllExportedComponentDefinition(ast: any) {
|
||||||
const components: any[] = [];
|
const components: any[] = [];
|
||||||
|
const cache: ICache = {};
|
||||||
|
let programScope: any;
|
||||||
|
|
||||||
function exportDeclaration(path: any) {
|
function exportDeclaration(path: any) {
|
||||||
const definitions = resolveExportDeclaration(path)
|
const definitions = resolveExportDeclaration(path)
|
||||||
@ -106,7 +285,7 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
|||||||
if (isComponentDefinition(definition)) {
|
if (isComponentDefinition(definition)) {
|
||||||
acc.push(definition);
|
acc.push(definition);
|
||||||
} else {
|
} else {
|
||||||
definition = getDefinition(definition);
|
definition = getDefinition(definition, cache);
|
||||||
if (!Array.isArray(definition)) {
|
if (!Array.isArray(definition)) {
|
||||||
definition = [definition];
|
definition = [definition];
|
||||||
}
|
}
|
||||||
@ -118,7 +297,11 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
|||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
.map((definition: any) => resolveDefinition(definition));
|
.map((definition: any) => {
|
||||||
|
const { __meta: meta } = definition;
|
||||||
|
const def = resolveDefinition(definition);
|
||||||
|
return makeProxy(def, { __meta: meta });
|
||||||
|
});
|
||||||
|
|
||||||
if (definitions.length === 0) {
|
if (definitions.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
@ -132,6 +315,10 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visit(ast, {
|
visit(ast, {
|
||||||
|
visitProgram: function(path) {
|
||||||
|
programScope = path.scope;
|
||||||
|
return this.traverse(path);
|
||||||
|
},
|
||||||
visitFunctionDeclaration: ignore,
|
visitFunctionDeclaration: ignore,
|
||||||
visitFunctionExpression: ignore,
|
visitFunctionExpression: ignore,
|
||||||
visitClassDeclaration: ignore,
|
visitClassDeclaration: ignore,
|
||||||
@ -149,9 +336,7 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
|||||||
visitExportNamedDeclaration: exportDeclaration,
|
visitExportNamedDeclaration: exportDeclaration,
|
||||||
visitExportDefaultDeclaration: exportDeclaration,
|
visitExportDefaultDeclaration: exportDeclaration,
|
||||||
visitExportAllDeclaration: function(path) {
|
visitExportAllDeclaration: function(path) {
|
||||||
components.push(
|
components.push(...resolveImport(path, findAllExportedComponentDefinition));
|
||||||
...resolveImport(path, findAllExportedComponentDefinition),
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -161,20 +346,44 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
|||||||
if (!isExportsOrModuleAssignment(path)) {
|
if (!isExportsOrModuleAssignment(path)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const arr = expressionTo.Array(path.get('left'));
|
||||||
|
const meta: any = {
|
||||||
|
exportName: arr[1] === 'exports' ? 'default' : arr[1],
|
||||||
|
};
|
||||||
// Resolve the value of the right hand side. It should resolve to a call
|
// Resolve the value of the right hand side. It should resolve to a call
|
||||||
// expression, something like React.createClass
|
// expression, something like React.createClass
|
||||||
path = resolveToValue(path.get('right'));
|
path = resolveToValue(path.get('right'));
|
||||||
if (!isComponentDefinition(path)) {
|
if (!isComponentDefinition(path)) {
|
||||||
path = getDefinition(path);
|
path = getDefinition(path, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
const definition = resolveDefinition(path);
|
let definitions = resolveDefinition(path);
|
||||||
|
if (!Array.isArray(definitions)) {
|
||||||
|
definitions = [definitions];
|
||||||
|
}
|
||||||
|
definitions.forEach((definition: any) => {
|
||||||
if (definition && components.indexOf(definition) === -1) {
|
if (definition && components.indexOf(definition) === -1) {
|
||||||
|
// if (definition.__meta) {
|
||||||
|
definition = makeProxy(definition, {
|
||||||
|
__meta: meta,
|
||||||
|
});
|
||||||
|
// }
|
||||||
components.push(definition);
|
components.push(definition);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return components;
|
const result = components.reduce((acc, item) => {
|
||||||
|
let subModuleDefinitions = [];
|
||||||
|
subModuleDefinitions = getSubComponents(item, programScope, cache);
|
||||||
|
return [...acc, item, ...subModuleDefinitions];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const res = uniqBy(result, (x: any) => {
|
||||||
|
return `${x.__meta.exportName}/${x.__meta.subName}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
const { getPropertyName } = require('react-docgen').utils;
|
||||||
|
|
||||||
|
const reactStaticMembers = ['propTypes', 'defaultProps', 'contextTypes'];
|
||||||
|
export default function isReactComponentStaticMember(methodPath: any) {
|
||||||
|
let name;
|
||||||
|
if (t.MemberExpression.check(methodPath.node)) {
|
||||||
|
name = methodPath.node.property.name;
|
||||||
|
} else {
|
||||||
|
name = getPropertyName(methodPath);
|
||||||
|
}
|
||||||
|
return !!name && reactStaticMembers.indexOf(name) !== -1;
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
import isReactComponentStaticMember from './isReactComponentStaticMember';
|
||||||
|
const { isReactComponentMethod } = require('react-docgen').utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* judge if static method
|
||||||
|
*/
|
||||||
|
function isStaticMethod(path: any) {
|
||||||
|
const isProbablyStaticMethod = t.ClassProperty.check(path.node) && path.node.static === true;
|
||||||
|
|
||||||
|
return isProbablyStaticMethod && !isReactComponentStaticMember(path) && !isReactComponentMethod(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default isStaticMethod;
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
import makeProxy from '../utils/makeProxy';
|
||||||
|
import getName from '../utils/getName';
|
||||||
|
|
||||||
|
export default function resolveExportDeclaration(path: any) {
|
||||||
|
const definitions = [];
|
||||||
|
if (path.node.default || t.ExportDefaultDeclaration.check(path.node)) {
|
||||||
|
const def = path.get('declaration');
|
||||||
|
const meta: { [name: string]: string } = {
|
||||||
|
exportName: 'default',
|
||||||
|
localName: getName(def),
|
||||||
|
};
|
||||||
|
|
||||||
|
definitions.push(makeProxy(def, { __meta: meta }));
|
||||||
|
} else if (path.node.declaration) {
|
||||||
|
if (t.VariableDeclaration.check(path.node.declaration)) {
|
||||||
|
path.get('declaration', 'declarations').each((declarator: any) => {
|
||||||
|
definitions.push(
|
||||||
|
makeProxy(declarator, {
|
||||||
|
__meta: {
|
||||||
|
exportName: declarator.get('id').node.name,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const def = path.get('declaration');
|
||||||
|
definitions.push(
|
||||||
|
makeProxy(def, {
|
||||||
|
__meta: {
|
||||||
|
exportName: 'default',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (path.node.specifiers) {
|
||||||
|
path.get('specifiers').each((specifier: any) => {
|
||||||
|
const def = specifier.node.id ? specifier.get('id') : specifier.get('local');
|
||||||
|
const exportName = specifier.get('exported').node.name;
|
||||||
|
const localName = def.get('local').node.name;
|
||||||
|
|
||||||
|
definitions.push(
|
||||||
|
makeProxy(def, {
|
||||||
|
__meta: {
|
||||||
|
exportName: exportName,
|
||||||
|
localName: localName,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return definitions;
|
||||||
|
}
|
||||||
@ -1,21 +1,10 @@
|
|||||||
import { namedTypes as t } from 'ast-types';
|
import { namedTypes as t } from 'ast-types';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import p from 'path';
|
import p from 'path';
|
||||||
|
import getRoot from '../utils/getRoot';
|
||||||
|
|
||||||
function getRoot(node: any) {
|
export function isImportLike(node: any) {
|
||||||
let root = node.parent;
|
return t.ImportDeclaration.check(node) || t.ExportAllDeclaration.check(node) || t.ExportNamedDeclaration.check(node);
|
||||||
while (root.parent) {
|
|
||||||
root = root.parent;
|
|
||||||
}
|
|
||||||
return root.node;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isImportLike(node: any) {
|
|
||||||
return (
|
|
||||||
t.ImportDeclaration.check(node) ||
|
|
||||||
t.ExportAllDeclaration.check(node) ||
|
|
||||||
t.ExportNamedDeclaration.check(node)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPath(path: any, name: any) {
|
function getPath(path: any, name: any) {
|
||||||
@ -27,7 +16,7 @@ function getPath(path: any, name: any) {
|
|||||||
if (fs.existsSync(p.resolve(__path, name))) {
|
if (fs.existsSync(p.resolve(__path, name))) {
|
||||||
name = name + '/index';
|
name = name + '/index';
|
||||||
}
|
}
|
||||||
const suffix = suffixes.find(suf => {
|
const suffix = suffixes.find((suf) => {
|
||||||
return fs.existsSync(p.resolve(__path, name + suf));
|
return fs.existsSync(p.resolve(__path, name + suf));
|
||||||
});
|
});
|
||||||
if (!suffix) return;
|
if (!suffix) return;
|
||||||
@ -35,9 +24,12 @@ function getPath(path: any, name: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buildParser = require('react-docgen/dist/babelParser').default;
|
const buildParser = require('react-docgen/dist/babelParser').default;
|
||||||
const parser = buildParser();
|
|
||||||
const suffixes = ['.js', '.jsx', '.ts', '.tsx'];
|
const suffixes = ['.js', '.jsx', '.ts', '.tsx'];
|
||||||
|
|
||||||
|
const cache: {
|
||||||
|
[name: string]: any;
|
||||||
|
} = {};
|
||||||
|
|
||||||
export default function resolveImport(path: any, callback: any) {
|
export default function resolveImport(path: any, callback: any) {
|
||||||
let name;
|
let name;
|
||||||
if (path.name === 'local') {
|
if (path.name === 'local') {
|
||||||
@ -50,11 +42,19 @@ export default function resolveImport(path: any, callback: any) {
|
|||||||
if (name) {
|
if (name) {
|
||||||
const __path = getPath(path, name);
|
const __path = getPath(path, name);
|
||||||
if (!__path) return path;
|
if (!__path) return path;
|
||||||
|
let ast;
|
||||||
|
if (!cache[__path]) {
|
||||||
const fileContent = fs.readFileSync(__path, 'utf8');
|
const fileContent = fs.readFileSync(__path, 'utf8');
|
||||||
const ast = parser.parse(fileContent);
|
const parser = buildParser({ filename: __path });
|
||||||
|
ast = parser.parse(fileContent);
|
||||||
ast.__src = fileContent;
|
ast.__src = fileContent;
|
||||||
ast.__path = __path;
|
ast.__path = __path;
|
||||||
return callback(ast);
|
cache[__path] = ast;
|
||||||
|
} else {
|
||||||
|
ast = cache[__path];
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(ast, __path);
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
export function transformType(type: any) {
|
export function transformType(itemType: any) {
|
||||||
if (typeof type === 'string') return type;
|
if (typeof itemType === 'string') return itemType;
|
||||||
const { name, elements, value = elements, computed, required } = type;
|
const { name, elements, value = elements, computed, required, type } = itemType;
|
||||||
if (!value && !required) {
|
// if (!value && !required && !type) {
|
||||||
return name;
|
// return name;
|
||||||
}
|
// }
|
||||||
if (computed !== undefined && value) {
|
if (computed !== undefined && value) {
|
||||||
return eval(value);
|
return eval(value);
|
||||||
}
|
}
|
||||||
@ -21,6 +21,7 @@ export function transformType(type: any) {
|
|||||||
case 'func':
|
case 'func':
|
||||||
case 'symbol':
|
case 'symbol':
|
||||||
case 'object':
|
case 'object':
|
||||||
|
case 'null':
|
||||||
break;
|
break;
|
||||||
case 'literal':
|
case 'literal':
|
||||||
return eval(value);
|
return eval(value);
|
||||||
@ -36,13 +37,24 @@ export function transformType(type: any) {
|
|||||||
case 'boolean':
|
case 'boolean':
|
||||||
result.type = 'bool';
|
result.type = 'bool';
|
||||||
break;
|
break;
|
||||||
case 'Array': {
|
case 'Function':
|
||||||
|
result.type = 'func';
|
||||||
|
break;
|
||||||
|
case 'unknown':
|
||||||
|
result.type = 'any';
|
||||||
|
break;
|
||||||
|
case 'Array':
|
||||||
|
case 'arrayOf': {
|
||||||
result.type = 'arrayOf';
|
result.type = 'arrayOf';
|
||||||
const v = transformType(value[0]);
|
const v = transformType(value[0]);
|
||||||
if (typeof v.type === 'string') result.value = v.type;
|
if (typeof v.type === 'string') result.value = v.type;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'signature': {
|
case 'signature': {
|
||||||
|
if (typeof type === 'string') {
|
||||||
|
result.type = type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
result.type = 'shape';
|
result.type = 'shape';
|
||||||
const {
|
const {
|
||||||
signature: { properties },
|
signature: { properties },
|
||||||
@ -103,22 +115,28 @@ export function transformType(type: any) {
|
|||||||
result.value = name;
|
result.value = name;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (Object.keys(result).length === 1) {
|
||||||
|
return result.type;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformItem(name: string, item: any) {
|
export function transformItem(name: string, item: any) {
|
||||||
const { description, flowType, type = flowType, required, defaultValue } = item;
|
const { description, flowType, tsType, type = tsType || flowType, required, defaultValue } = item;
|
||||||
const result: any = {
|
const result: any = {
|
||||||
name,
|
name,
|
||||||
propType: transformType({
|
};
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
result.propType = transformType({
|
||||||
...type,
|
...type,
|
||||||
required: !!required,
|
required: !!required,
|
||||||
}),
|
});
|
||||||
};
|
}
|
||||||
if (description) {
|
if (description) {
|
||||||
result.description = description;
|
result.description = description;
|
||||||
}
|
}
|
||||||
if (defaultValue) {
|
if (defaultValue !== undefined) {
|
||||||
try {
|
try {
|
||||||
const value = eval(defaultValue.value);
|
const value = eval(defaultValue.value);
|
||||||
result.defaultValue = value;
|
result.defaultValue = value;
|
||||||
@ -127,6 +145,5 @@ export function transformItem(name: string, item: any) {
|
|||||||
if (result.propType === undefined) {
|
if (result.propType === undefined) {
|
||||||
delete result.propType;
|
delete result.propType;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
18
packages/material-parser/src/parse/utils/cache.ts
Normal file
18
packages/material-parser/src/parse/utils/cache.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export interface ICache {
|
||||||
|
[name: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache: ICache = {};
|
||||||
|
|
||||||
|
export function set(scope: string, name: string, value: any) {
|
||||||
|
cache[scope] = cache[scope] || {};
|
||||||
|
cache[scope][name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get(scope: string, name: string) {
|
||||||
|
return (cache[scope] || {})[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function has(scope: string, name: string) {
|
||||||
|
return cache[scope] && cache[scope].hasOwnProperty(name);
|
||||||
|
}
|
||||||
49
packages/material-parser/src/parse/utils/flowUtilityTypes.ts
Normal file
49
packages/material-parser/src/parse/utils/flowUtilityTypes.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
const supportedUtilityTypes = new Set(['$Exact', '$ReadOnly']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See `supportedUtilityTypes` for which types are supported and
|
||||||
|
* https://flow.org/en/docs/types/utilities/ for which types are available.
|
||||||
|
*/
|
||||||
|
function isSupportedUtilityType(path: any) {
|
||||||
|
if (t.GenericTypeAnnotation.check(path.node)) {
|
||||||
|
const idPath = path.get('id');
|
||||||
|
return !!idPath && supportedUtilityTypes.has(idPath.node.name);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isSupportedUtilityType };
|
||||||
|
|
||||||
|
function isReactUtilityType(path: any) {
|
||||||
|
if (t.TSTypeReference.check(path.node)) {
|
||||||
|
const objName = path.get('typeName', 'left').node.name;
|
||||||
|
if (objName === 'React') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwraps well known utility types. For example:
|
||||||
|
*
|
||||||
|
* $ReadOnly<T> => T
|
||||||
|
*/
|
||||||
|
function unwrapUtilityType(path: any) {
|
||||||
|
while (isSupportedUtilityType(path) || isReactUtilityType(path)) {
|
||||||
|
path = path.get('typeParameters', 'params', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { unwrapUtilityType };
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
const {
|
||||||
|
isReactComponentClass,
|
||||||
|
isReactForwardRefCall,
|
||||||
|
getTypeAnnotation,
|
||||||
|
resolveToValue,
|
||||||
|
getMemberValuePath,
|
||||||
|
} = require('react-docgen').utils;
|
||||||
|
import resolveGenericTypeAnnotation from './resolveGenericTypeAnnotation';
|
||||||
|
const getTypeParameters = require('react-docgen/dist/utils/getTypeParameters').default;
|
||||||
|
|
||||||
|
function getStatelessPropsPath(componentDefinition: any) {
|
||||||
|
const value = resolveToValue(componentDefinition);
|
||||||
|
if (isReactForwardRefCall(value)) {
|
||||||
|
const inner = resolveToValue(value.get('arguments', 0));
|
||||||
|
return inner.get('params', 0);
|
||||||
|
}
|
||||||
|
if (t.VariableDeclarator.check(componentDefinition.parent.node)) {
|
||||||
|
const id = componentDefinition.parent.get('id');
|
||||||
|
if (id.node.typeAnnotation) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.get('params', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an React component (stateless or class) tries to find the
|
||||||
|
* flow type for the props. If not found or not one of the supported
|
||||||
|
* component types returns null.
|
||||||
|
*/
|
||||||
|
export default (path: any) => {
|
||||||
|
let typePath = null;
|
||||||
|
|
||||||
|
if (isReactComponentClass(path)) {
|
||||||
|
const superTypes = path.get('superTypeParameters');
|
||||||
|
|
||||||
|
if (superTypes.value) {
|
||||||
|
const params = superTypes.get('params');
|
||||||
|
if (params.value.length === 3) {
|
||||||
|
typePath = params.get(1);
|
||||||
|
} else {
|
||||||
|
typePath = params.get(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const propsMemberPath = getMemberValuePath(path, 'props');
|
||||||
|
if (!propsMemberPath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
typePath = getTypeAnnotation(propsMemberPath.parentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return typePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsParam = getStatelessPropsPath(path);
|
||||||
|
|
||||||
|
if (propsParam) {
|
||||||
|
typePath = getTypeAnnotation(propsParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
return typePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
function applyToFlowTypeProperties(documentation: any, path: any, callback: any, typeParams?: any) {
|
||||||
|
if (path.node.properties) {
|
||||||
|
path.get('properties').each((propertyPath: any) => callback(propertyPath, typeParams));
|
||||||
|
} else if (path.node.members) {
|
||||||
|
path.get('members').each((propertyPath: any) => callback(propertyPath, typeParams));
|
||||||
|
} else if (path.node.type === 'InterfaceDeclaration') {
|
||||||
|
if (path.node.extends) {
|
||||||
|
applyExtends(documentation, path, callback, typeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.get('body', 'properties').each((propertyPath: any) => callback(propertyPath, typeParams));
|
||||||
|
} else if (path.node.type === 'TSInterfaceDeclaration') {
|
||||||
|
if (path.node.extends) {
|
||||||
|
applyExtends(documentation, path, callback, typeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.get('body', 'body').each((propertyPath: any) => callback(propertyPath, typeParams));
|
||||||
|
} else if (path.node.type === 'IntersectionTypeAnnotation' || path.node.type === 'TSIntersectionType') {
|
||||||
|
path
|
||||||
|
.get('types')
|
||||||
|
.each((typesPath: any) => applyToFlowTypeProperties(documentation, typesPath, callback, typeParams));
|
||||||
|
} else if (path.node.type !== 'UnionTypeAnnotation') {
|
||||||
|
// The react-docgen output format does not currently allow
|
||||||
|
// for the expression of union types
|
||||||
|
const typePath = resolveGenericTypeAnnotation(path);
|
||||||
|
if (typePath) {
|
||||||
|
applyToFlowTypeProperties(documentation, typePath, callback, typeParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyExtends(documentation: any, path: any, callback: any, typeParams: any) {
|
||||||
|
path.get('extends').each((extendsPath: any) => {
|
||||||
|
const resolvedPath = resolveGenericTypeAnnotation(extendsPath);
|
||||||
|
if (resolvedPath) {
|
||||||
|
if (resolvedPath.node.typeParameters && extendsPath.node.typeParameters) {
|
||||||
|
typeParams = getTypeParameters(
|
||||||
|
resolvedPath.get('typeParameters'),
|
||||||
|
extendsPath.get('typeParameters'),
|
||||||
|
typeParams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
applyToFlowTypeProperties(documentation, resolvedPath, callback, typeParams);
|
||||||
|
} else {
|
||||||
|
const id = extendsPath.node.id || extendsPath.node.typeName || extendsPath.node.expression;
|
||||||
|
if (id && id.type === 'Identifier') {
|
||||||
|
documentation.addComposes(id.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { applyToFlowTypeProperties };
|
||||||
@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import getTSType from './getTSType';
|
||||||
|
|
||||||
|
const { namedTypes: t } = require('ast-types');
|
||||||
|
const {
|
||||||
|
resolveToValue,
|
||||||
|
getFlowType,
|
||||||
|
getParameterName,
|
||||||
|
getPropertyName,
|
||||||
|
getTypeAnnotation,
|
||||||
|
} = require('react-docgen').utils;
|
||||||
|
const { getDocblock } = require('react-docgen/dist/utils/docblock');
|
||||||
|
|
||||||
|
function getMethodFunctionExpression(methodPath) {
|
||||||
|
if (t.AssignmentExpression.check(methodPath.node)) {
|
||||||
|
return resolveToValue(methodPath.get('right'));
|
||||||
|
}
|
||||||
|
// Otherwise this is a method/property node
|
||||||
|
return methodPath.get('value');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMethodParamsDoc(methodPath) {
|
||||||
|
const params = [];
|
||||||
|
const functionExpression = getMethodFunctionExpression(methodPath);
|
||||||
|
|
||||||
|
// Extract param flow types.
|
||||||
|
functionExpression.get('params').each((paramPath) => {
|
||||||
|
let type = null;
|
||||||
|
const typePath = getTypeAnnotation(paramPath);
|
||||||
|
if (typePath && t.Flow.check(typePath.node)) {
|
||||||
|
type = getFlowType(typePath);
|
||||||
|
if (t.GenericTypeAnnotation.check(typePath.node)) {
|
||||||
|
type.alias = typePath.node.id.name;
|
||||||
|
}
|
||||||
|
} else if (typePath) {
|
||||||
|
type = getTSType(typePath);
|
||||||
|
if (t.TSTypeReference.check(typePath.node)) {
|
||||||
|
type.alias = typePath.node.typeName.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const param = {
|
||||||
|
name: getParameterName(paramPath),
|
||||||
|
optional: paramPath.node.optional,
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
|
||||||
|
params.push(param);
|
||||||
|
});
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract flow return type.
|
||||||
|
function getMethodReturnDoc(methodPath) {
|
||||||
|
const functionExpression = getMethodFunctionExpression(methodPath);
|
||||||
|
|
||||||
|
if (functionExpression.node.returnType) {
|
||||||
|
const returnType = getTypeAnnotation(functionExpression.get('returnType'));
|
||||||
|
if (returnType && t.Flow.check(returnType.node)) {
|
||||||
|
return { type: getFlowType(returnType) };
|
||||||
|
}
|
||||||
|
if (returnType) {
|
||||||
|
return { type: getTSType(returnType) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMethodModifiers(methodPath) {
|
||||||
|
if (t.AssignmentExpression.check(methodPath.node)) {
|
||||||
|
return ['static'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise this is a method/property node
|
||||||
|
|
||||||
|
const modifiers = [];
|
||||||
|
|
||||||
|
if (methodPath.node.static) {
|
||||||
|
modifiers.push('static');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodPath.node.kind === 'get' || methodPath.node.kind === 'set') {
|
||||||
|
modifiers.push(methodPath.node.kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
const functionExpression = methodPath.get('value').node;
|
||||||
|
if (functionExpression.generator) {
|
||||||
|
modifiers.push('generator');
|
||||||
|
}
|
||||||
|
if (functionExpression.async) {
|
||||||
|
modifiers.push('async');
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMethodName(methodPath) {
|
||||||
|
if (t.AssignmentExpression.check(methodPath.node) && t.MemberExpression.check(methodPath.node.left)) {
|
||||||
|
const { left } = methodPath.node;
|
||||||
|
const { property } = left;
|
||||||
|
if (!left.computed) {
|
||||||
|
return property.name;
|
||||||
|
}
|
||||||
|
if (t.Literal.check(property)) {
|
||||||
|
return String(property.value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getPropertyName(methodPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMethodAccessibility(methodPath) {
|
||||||
|
if (t.AssignmentExpression.check(methodPath.node)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise this is a method/property node
|
||||||
|
return methodPath.node.accessibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMethodDocblock(methodPath) {
|
||||||
|
if (t.AssignmentExpression.check(methodPath.node)) {
|
||||||
|
let path = methodPath;
|
||||||
|
do {
|
||||||
|
path = path.parent;
|
||||||
|
} while (path && !t.ExpressionStatement.check(path.node));
|
||||||
|
if (path) {
|
||||||
|
return getDocblock(path);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise this is a method/property node
|
||||||
|
return getDocblock(methodPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the documentation object for a component method.
|
||||||
|
// Component methods may be represented as class/object method/property nodes
|
||||||
|
// or as assignment expresions of the form `Component.foo = function() {}`
|
||||||
|
export default function getMethodDocumentation(methodPath) {
|
||||||
|
if (getMethodAccessibility(methodPath) === 'private') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = getMethodName(methodPath);
|
||||||
|
if (!name) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
docblock: getMethodDocblock(methodPath),
|
||||||
|
modifiers: getMethodModifiers(methodPath),
|
||||||
|
params: getMethodParamsDoc(methodPath),
|
||||||
|
returns: getMethodReturnDoc(methodPath),
|
||||||
|
};
|
||||||
|
}
|
||||||
14
packages/material-parser/src/parse/utils/getName.ts
Normal file
14
packages/material-parser/src/parse/utils/getName.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
|
||||||
|
export default function(def: any) {
|
||||||
|
let name = '';
|
||||||
|
if (def.node.name) {
|
||||||
|
name = def.node.name;
|
||||||
|
// hoc
|
||||||
|
} else if (t.CallExpression.check(def.node)) {
|
||||||
|
if (def.node.arguments && def.node.arguments.length && t.Identifier.check(def.get('arguments', 0).node))
|
||||||
|
name = def.get('arguments', 0).node.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
7
packages/material-parser/src/parse/utils/getRoot.ts
Normal file
7
packages/material-parser/src/parse/utils/getRoot.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function getRoot(path: any) {
|
||||||
|
let root = path.parent;
|
||||||
|
while (root.parent) {
|
||||||
|
root = root.parent;
|
||||||
|
}
|
||||||
|
return root.node;
|
||||||
|
}
|
||||||
355
packages/material-parser/src/parse/utils/getTSType.js
Normal file
355
packages/material-parser/src/parse/utils/getTSType.js
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
const { namedTypes: t } = require('ast-types');
|
||||||
|
const {
|
||||||
|
getPropertyName,
|
||||||
|
printValue,
|
||||||
|
resolveToValue,
|
||||||
|
getTypeAnnotation,
|
||||||
|
resolveObjectToNameArray,
|
||||||
|
getTypeParameters,
|
||||||
|
} = require('react-docgen').utils;
|
||||||
|
|
||||||
|
const tsTypes = {
|
||||||
|
TSAnyKeyword: 'any',
|
||||||
|
TSBooleanKeyword: 'boolean',
|
||||||
|
TSUnknownKeyword: 'unknown',
|
||||||
|
TSNeverKeyword: 'never',
|
||||||
|
TSNullKeyword: 'null',
|
||||||
|
TSUndefinedKeyword: 'undefined',
|
||||||
|
TSNumberKeyword: 'number',
|
||||||
|
TSStringKeyword: 'string',
|
||||||
|
TSSymbolKeyword: 'symbol',
|
||||||
|
TSThisType: 'this',
|
||||||
|
TSObjectKeyword: 'object',
|
||||||
|
TSVoidKeyword: 'void',
|
||||||
|
};
|
||||||
|
|
||||||
|
const namedTypes = {
|
||||||
|
TSArrayType: handleTSArrayType,
|
||||||
|
TSTypeReference: handleTSTypeReference,
|
||||||
|
TSTypeLiteral: handleTSTypeLiteral,
|
||||||
|
TSInterfaceDeclaration: handleTSInterfaceDeclaration,
|
||||||
|
TSUnionType: handleTSUnionType,
|
||||||
|
TSFunctionType: handleTSFunctionType,
|
||||||
|
TSIntersectionType: handleTSIntersectionType,
|
||||||
|
TSMappedType: handleTSMappedType,
|
||||||
|
TSTupleType: handleTSTupleType,
|
||||||
|
TSTypeQuery: handleTSTypeQuery,
|
||||||
|
TSTypeOperator: handleTSTypeOperator,
|
||||||
|
TSIndexedAccessType: handleTSIndexedAccessType,
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleTSArrayType(path, typeParams) {
|
||||||
|
return {
|
||||||
|
name: 'Array',
|
||||||
|
elements: [getTSTypeWithResolvedTypes(path.get('elementType'), typeParams)],
|
||||||
|
raw: printValue(path),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSTypeReference(path, typeParams) {
|
||||||
|
let type;
|
||||||
|
if (t.TSQualifiedName.check(path.node.typeName)) {
|
||||||
|
const typeName = path.get('typeName');
|
||||||
|
|
||||||
|
if (typeName.node.left.name === 'React') {
|
||||||
|
type = {
|
||||||
|
name: `${typeName.node.left.name}${typeName.node.right.name}`,
|
||||||
|
raw: printValue(typeName),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
type = { name: printValue(typeName).replace(/<.*>$/, '') };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
type = { name: path.node.typeName.name };
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedPath = (typeParams && typeParams[type.name]) || resolveToValue(path.get('typeName'));
|
||||||
|
|
||||||
|
if (path.node.typeParameters && resolvedPath.node.typeParameters) {
|
||||||
|
typeParams = getTypeParameters(resolvedPath.get('typeParameters'), path.get('typeParameters'), typeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeParams && typeParams[type.name]) {
|
||||||
|
type = getTSTypeWithResolvedTypes(resolvedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedPath && resolvedPath.node.typeAnnotation) {
|
||||||
|
type = getTSTypeWithResolvedTypes(resolvedPath.get('typeAnnotation'), typeParams);
|
||||||
|
} else if (path.node.typeParameters) {
|
||||||
|
const params = path.get('typeParameters').get('params');
|
||||||
|
|
||||||
|
type = {
|
||||||
|
...type,
|
||||||
|
elements: params.map((param) => getTSTypeWithResolvedTypes(param, typeParams)),
|
||||||
|
raw: printValue(path),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTSTypeWithRequirements(path, typeParams) {
|
||||||
|
const type = getTSTypeWithResolvedTypes(path, typeParams);
|
||||||
|
type.required = !path.parentPath.node.optional;
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSTypeLiteral(path, typeParams) {
|
||||||
|
const type = {
|
||||||
|
name: 'signature',
|
||||||
|
type: 'object',
|
||||||
|
raw: printValue(path),
|
||||||
|
signature: { properties: [] },
|
||||||
|
};
|
||||||
|
|
||||||
|
path.get('members').each((param) => {
|
||||||
|
if (t.TSPropertySignature.check(param.node) || t.TSMethodSignature.check(param.node)) {
|
||||||
|
const propName = getPropertyName(param);
|
||||||
|
if (!propName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
type.signature.properties.push({
|
||||||
|
key: propName,
|
||||||
|
value: getTSTypeWithRequirements(param.get('typeAnnotation'), typeParams),
|
||||||
|
});
|
||||||
|
} else if (t.TSCallSignatureDeclaration.check(param.node)) {
|
||||||
|
type.signature.constructor = handleTSFunctionType(param, typeParams);
|
||||||
|
} else if (t.TSIndexSignature.check(param.node)) {
|
||||||
|
type.signature.properties.push({
|
||||||
|
key: getTSTypeWithResolvedTypes(
|
||||||
|
param
|
||||||
|
.get('parameters')
|
||||||
|
.get(0)
|
||||||
|
.get('typeAnnotation'),
|
||||||
|
typeParams,
|
||||||
|
),
|
||||||
|
value: getTSTypeWithRequirements(param.get('typeAnnotation'), typeParams),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSInterfaceDeclaration(path) {
|
||||||
|
// Interfaces are handled like references which would be documented separately,
|
||||||
|
// rather than inlined like type aliases.
|
||||||
|
return {
|
||||||
|
name: path.node.id.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSUnionType(path, typeParams) {
|
||||||
|
return {
|
||||||
|
name: 'union',
|
||||||
|
raw: printValue(path),
|
||||||
|
elements: path.get('types').map((subType) => getTSTypeWithResolvedTypes(subType, typeParams)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSIntersectionType(path, typeParams) {
|
||||||
|
return {
|
||||||
|
name: 'intersection',
|
||||||
|
raw: printValue(path),
|
||||||
|
elements: path.get('types').map((subType) => getTSTypeWithResolvedTypes(subType, typeParams)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSMappedType(path, typeParams) {
|
||||||
|
const key = getTSTypeWithResolvedTypes(path.get('typeParameter').get('constraint'), typeParams);
|
||||||
|
key.required = !path.node.optional;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'signature',
|
||||||
|
type: 'object',
|
||||||
|
raw: printValue(path),
|
||||||
|
signature: {
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
value: getTSTypeWithResolvedTypes(path.get('typeAnnotation'), typeParams),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSFunctionType(path, typeParams) {
|
||||||
|
const type = {
|
||||||
|
name: 'signature',
|
||||||
|
type: 'function',
|
||||||
|
raw: printValue(path),
|
||||||
|
signature: {
|
||||||
|
arguments: [],
|
||||||
|
return: getTSTypeWithResolvedTypes(path.get('typeAnnotation'), typeParams),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
path.get('parameters').each((param) => {
|
||||||
|
const typeAnnotation = getTypeAnnotation(param);
|
||||||
|
const arg = {
|
||||||
|
name: param.node.name || '',
|
||||||
|
type: typeAnnotation ? getTSTypeWithResolvedTypes(typeAnnotation, typeParams) : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (param.node.name === 'this') {
|
||||||
|
type.signature.this = arg.type;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.node.type === 'RestElement') {
|
||||||
|
arg.name = param.node.argument.name;
|
||||||
|
arg.rest = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
type.signature.arguments.push(arg);
|
||||||
|
});
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSTupleType(path, typeParams) {
|
||||||
|
const type = {
|
||||||
|
name: 'tuple',
|
||||||
|
raw: printValue(path),
|
||||||
|
elements: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
path.get('elementTypes').each((param) => {
|
||||||
|
type.elements.push(getTSTypeWithResolvedTypes(param, typeParams));
|
||||||
|
});
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSTypeQuery(path, typeParams) {
|
||||||
|
const resolvedPath = resolveToValue(path.get('exprName'));
|
||||||
|
if (resolvedPath && resolvedPath.node.typeAnnotation) {
|
||||||
|
return getTSTypeWithResolvedTypes(resolvedPath.get('typeAnnotation'), typeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name: path.node.exprName.name };
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSTypeOperator(path) {
|
||||||
|
if (path.node.operator !== 'keyof') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = path.get('typeAnnotation');
|
||||||
|
if (t.TSTypeQuery.check(value.node)) {
|
||||||
|
value = value.get('exprName');
|
||||||
|
} else if (value.node.id) {
|
||||||
|
value = value.get('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedPath = resolveToValue(value);
|
||||||
|
if (resolvedPath && (t.ObjectExpression.check(resolvedPath.node) || t.TSTypeLiteral.check(resolvedPath.node))) {
|
||||||
|
const keys = resolveObjectToNameArray(resolvedPath, true);
|
||||||
|
|
||||||
|
if (keys) {
|
||||||
|
return {
|
||||||
|
name: 'union',
|
||||||
|
raw: printValue(path),
|
||||||
|
elements: keys.map((key) => ({ name: 'literal', value: key })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTSIndexedAccessType(path, typeParams) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const objectType = getTSTypeWithResolvedTypes(path.get('objectType'), typeParams);
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const indexType = getTSTypeWithResolvedTypes(path.get('indexType'), typeParams);
|
||||||
|
|
||||||
|
// We only get the signature if the objectType is a type (vs interface)
|
||||||
|
if (!objectType.signature) {
|
||||||
|
return {
|
||||||
|
name: `${objectType.name}[${(indexType.value || indexType.name).toString()}]`,
|
||||||
|
raw: printValue(path),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const resolvedType = objectType.signature.properties.find(
|
||||||
|
(p) =>
|
||||||
|
// indexType.value = "'foo'"
|
||||||
|
p.key === indexType.value.replace(/['"]+/g, ''),
|
||||||
|
);
|
||||||
|
if (!resolvedType) {
|
||||||
|
return { name: 'unknown' };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: resolvedType.value.name,
|
||||||
|
raw: printValue(path),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let visitedTypes = {};
|
||||||
|
|
||||||
|
function getTSTypeWithResolvedTypes(path, typeParams) {
|
||||||
|
if (t.TSTypeAnnotation.check(path.node)) {
|
||||||
|
path = path.get('typeAnnotation');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { node } = path;
|
||||||
|
let type;
|
||||||
|
const isTypeAlias = t.TSTypeAliasDeclaration.check(path.parentPath.node);
|
||||||
|
|
||||||
|
// When we see a typealias mark it as visited so that the next
|
||||||
|
// call of this function does not run into an endless loop
|
||||||
|
if (isTypeAlias) {
|
||||||
|
if (visitedTypes[path.parentPath.node.id.name] === true) {
|
||||||
|
// if we are currently visiting this node then just return the name
|
||||||
|
// as we are starting to endless loop
|
||||||
|
return { name: path.parentPath.node.id.name };
|
||||||
|
}
|
||||||
|
if (typeof visitedTypes[path.parentPath.node.id.name] === 'object') {
|
||||||
|
// if we already resolved the type simple return it
|
||||||
|
return visitedTypes[path.parentPath.node.id.name];
|
||||||
|
}
|
||||||
|
// mark the type as visited
|
||||||
|
visitedTypes[path.parentPath.node.id.name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type in tsTypes) {
|
||||||
|
type = { name: tsTypes[node.type] };
|
||||||
|
} else if (t.TSLiteralType.check(node)) {
|
||||||
|
type = {
|
||||||
|
name: 'literal',
|
||||||
|
value: node.literal.raw || `${node.literal.value}`,
|
||||||
|
};
|
||||||
|
} else if (node.type in namedTypes) {
|
||||||
|
type = namedTypes[node.type](path, typeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
type = { name: 'unknown' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTypeAlias) {
|
||||||
|
// mark the type as unvisited so that further calls can resolve the type again
|
||||||
|
visitedTypes[path.parentPath.node.id.name] = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to identify the typescript type by inspecting the path for known
|
||||||
|
* typescript type names. This method doesn't check whether the found type is actually
|
||||||
|
* existing. It simply assumes that a match is always valid.
|
||||||
|
*
|
||||||
|
* If there is no match, "unknown" is returned.
|
||||||
|
*/
|
||||||
|
export default function getTSType(path, typeParamMap) {
|
||||||
|
// Empty visited types before an after run
|
||||||
|
// Before: in case the detection threw and we rerun again
|
||||||
|
// After: cleanup memory after we are done here
|
||||||
|
visitedTypes = {};
|
||||||
|
const type = getTSTypeWithResolvedTypes(path, typeParamMap);
|
||||||
|
visitedTypes = {};
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
19
packages/material-parser/src/parse/utils/makeProxy.ts
Normal file
19
packages/material-parser/src/parse/utils/makeProxy.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
function makeProxy(target: { [name: string]: any }, meta: any = {}): any {
|
||||||
|
if (target.__isProxy) {
|
||||||
|
const value = target.__getRaw();
|
||||||
|
const rawMeta = target.__getMeta();
|
||||||
|
return makeProxy(value, Object.assign({}, rawMeta, meta));
|
||||||
|
}
|
||||||
|
return new Proxy(target, {
|
||||||
|
get: (obj, prop: string | number) => {
|
||||||
|
if (prop === '__isProxy') return true;
|
||||||
|
if (prop === '__getRaw') return () => target;
|
||||||
|
if (prop === '__getMeta') return () => meta;
|
||||||
|
return meta.hasOwnProperty(prop) ? meta[prop] : obj[prop];
|
||||||
|
// return obj[prop];
|
||||||
|
},
|
||||||
|
has: (obj, prop) => obj.hasOwnProperty(prop) || meta.hasOwnProperty(prop),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makeProxy;
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { namedTypes as t } from 'ast-types';
|
||||||
|
const { resolveToValue } = require('react-docgen').utils;
|
||||||
|
const { unwrapUtilityType } = require('./flowUtilityTypes');
|
||||||
|
const isUnreachableFlowType = require('react-docgen/dist/utils/isUnreachableFlowType').default;
|
||||||
|
|
||||||
|
function tryResolveGenericTypeAnnotation(path: any): any {
|
||||||
|
let typePath = unwrapUtilityType(path);
|
||||||
|
let idPath;
|
||||||
|
|
||||||
|
if (typePath.node.id) {
|
||||||
|
idPath = typePath.get('id');
|
||||||
|
} else if (t.TSTypeReference.check(typePath.node)) {
|
||||||
|
idPath = typePath.get('typeName');
|
||||||
|
} else if (t.TSExpressionWithTypeArguments.check(typePath.node)) {
|
||||||
|
idPath = typePath.get('expression');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idPath) {
|
||||||
|
typePath = resolveToValue(idPath);
|
||||||
|
if (isUnreachableFlowType(typePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.TypeAlias.check(typePath.node)) {
|
||||||
|
return tryResolveGenericTypeAnnotation(typePath.get('right'));
|
||||||
|
} else if (t.TSTypeAliasDeclaration.check(typePath.node)) {
|
||||||
|
return tryResolveGenericTypeAnnotation(typePath.get('typeAnnotation'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return typePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return typePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an React component (stateless or class) tries to find the
|
||||||
|
* flow type for the props. If not found or not one of the supported
|
||||||
|
* component types returns undefined.
|
||||||
|
*/
|
||||||
|
export default function resolveGenericTypeAnnotation(path: any) {
|
||||||
|
if (!path) return;
|
||||||
|
|
||||||
|
const typePath = tryResolveGenericTypeAnnotation(path);
|
||||||
|
|
||||||
|
if (!typePath || typePath === path) return;
|
||||||
|
|
||||||
|
return typePath;
|
||||||
|
}
|
||||||
@ -15,35 +15,8 @@ export interface IMaterialParsedModel {
|
|||||||
// filePath: string;
|
// filePath: string;
|
||||||
componentName: string;
|
componentName: string;
|
||||||
props?: PropsSection['props'];
|
props?: PropsSection['props'];
|
||||||
// componentNames: {
|
meta?: {
|
||||||
// exportedName: string;
|
exportName?: string;
|
||||||
// localName: string;
|
subName?: string;
|
||||||
// source?: string;
|
};
|
||||||
// }[];
|
|
||||||
// importModules: {
|
|
||||||
// importDefaultName?: string;
|
|
||||||
// importName?: string;
|
|
||||||
// localName?: string;
|
|
||||||
// source: string;
|
|
||||||
// }[];
|
|
||||||
// exportModules: {
|
|
||||||
// exportedName: string;
|
|
||||||
// localName: string;
|
|
||||||
// source?: string;
|
|
||||||
// }[];
|
|
||||||
// /**
|
|
||||||
// * 子模块,形如:Demo.SubModule = value; 或者 Demo.SubModule.Sub = subValue;
|
|
||||||
// */
|
|
||||||
// subModules: {
|
|
||||||
// objectName: string[];
|
|
||||||
// propertyName: string;
|
|
||||||
// value?: string;
|
|
||||||
// // value 是否对应匿名函数
|
|
||||||
// isValueAnonymousFunc: boolean;
|
|
||||||
// }[];
|
|
||||||
// propsTypes: IPropTypes;
|
|
||||||
// propsDefaults: {
|
|
||||||
// name: string;
|
|
||||||
// defaultValue: any;
|
|
||||||
// }[];
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
packages/material-parser/src/types/Meta.ts
Normal file
14
packages/material-parser/src/types/Meta.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { NodePath, Path } from 'ast-types';
|
||||||
|
|
||||||
|
export interface IFileMeta {
|
||||||
|
src: string;
|
||||||
|
path: string;
|
||||||
|
exports: IDefinitionMeta[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDefinitionMeta {
|
||||||
|
subDefinitions: IDefinitionMeta[];
|
||||||
|
nodePath: typeof Path;
|
||||||
|
exportName: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -0,0 +1,70 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`antd exports modules correctly 1`] = `
|
||||||
|
Array [
|
||||||
|
"Affix",
|
||||||
|
"Anchor",
|
||||||
|
"AutoComplete",
|
||||||
|
"Alert",
|
||||||
|
"Avatar",
|
||||||
|
"BackTop",
|
||||||
|
"Badge",
|
||||||
|
"Breadcrumb",
|
||||||
|
"Button",
|
||||||
|
"Calendar",
|
||||||
|
"Card",
|
||||||
|
"Collapse",
|
||||||
|
"Carousel",
|
||||||
|
"Cascader",
|
||||||
|
"Checkbox",
|
||||||
|
"Col",
|
||||||
|
"Comment",
|
||||||
|
"ConfigProvider",
|
||||||
|
"DatePicker",
|
||||||
|
"Descriptions",
|
||||||
|
"Divider",
|
||||||
|
"Dropdown",
|
||||||
|
"Drawer",
|
||||||
|
"Empty",
|
||||||
|
"Form",
|
||||||
|
"Grid",
|
||||||
|
"Input",
|
||||||
|
"InputNumber",
|
||||||
|
"Layout",
|
||||||
|
"List",
|
||||||
|
"message",
|
||||||
|
"Menu",
|
||||||
|
"Mentions",
|
||||||
|
"Modal",
|
||||||
|
"Statistic",
|
||||||
|
"notification",
|
||||||
|
"PageHeader",
|
||||||
|
"Pagination",
|
||||||
|
"Popconfirm",
|
||||||
|
"Popover",
|
||||||
|
"Progress",
|
||||||
|
"Radio",
|
||||||
|
"Rate",
|
||||||
|
"Result",
|
||||||
|
"Row",
|
||||||
|
"Select",
|
||||||
|
"Skeleton",
|
||||||
|
"Slider",
|
||||||
|
"Space",
|
||||||
|
"Spin",
|
||||||
|
"Steps",
|
||||||
|
"Switch",
|
||||||
|
"Table",
|
||||||
|
"Transfer",
|
||||||
|
"Tree",
|
||||||
|
"TreeSelect",
|
||||||
|
"Tabs",
|
||||||
|
"Tag",
|
||||||
|
"TimePicker",
|
||||||
|
"Timeline",
|
||||||
|
"Tooltip",
|
||||||
|
"Typography",
|
||||||
|
"Upload",
|
||||||
|
"version",
|
||||||
|
]
|
||||||
|
`;
|
||||||
21
packages/material-parser/test/fixtures/antd-component/components/__tests__/index.test.js
vendored
Normal file
21
packages/material-parser/test/fixtures/antd-component/components/__tests__/index.test.js
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const OLD_NODE_ENV = process.env.NODE_ENV;
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
const antd = require('..');
|
||||||
|
|
||||||
|
describe('antd', () => {
|
||||||
|
afterAll(() => {
|
||||||
|
process.env.NODE_ENV = OLD_NODE_ENV;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exports modules correctly', () => {
|
||||||
|
expect(Object.keys(antd)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hint when import all components in dev mode', () => {
|
||||||
|
expect(warnSpy).toHaveBeenCalledWith(
|
||||||
|
'You are using a whole package of antd, please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size.',
|
||||||
|
);
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
71
packages/material-parser/test/fixtures/antd-component/components/__tests__/util/domHook.ts
vendored
Normal file
71
packages/material-parser/test/fixtures/antd-component/components/__tests__/util/domHook.ts
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const __NULL__ = { notExist: true };
|
||||||
|
|
||||||
|
type ElementType<P> = {
|
||||||
|
prototype: P;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function spyElementPrototypes<P extends {}>(Element: ElementType<P>, properties: P) {
|
||||||
|
const propNames = Object.keys(properties);
|
||||||
|
const originDescriptors = {};
|
||||||
|
|
||||||
|
propNames.forEach(propName => {
|
||||||
|
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
|
||||||
|
originDescriptors[propName] = originDescriptor || __NULL__;
|
||||||
|
|
||||||
|
const spyProp = properties[propName];
|
||||||
|
|
||||||
|
if (typeof spyProp === 'function') {
|
||||||
|
// If is a function
|
||||||
|
Element.prototype[propName] = function spyFunc(...args) {
|
||||||
|
return spyProp.call(this, originDescriptor, ...args);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Otherwise tread as a property
|
||||||
|
Object.defineProperty(Element.prototype, propName, {
|
||||||
|
...spyProp,
|
||||||
|
set(value) {
|
||||||
|
if (spyProp.set) {
|
||||||
|
return spyProp.set.call(this, originDescriptor, value);
|
||||||
|
}
|
||||||
|
return originDescriptor.set(value);
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
if (spyProp.get) {
|
||||||
|
return spyProp.get.call(this, originDescriptor);
|
||||||
|
}
|
||||||
|
return originDescriptor.get();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
mockRestore() {
|
||||||
|
propNames.forEach(propName => {
|
||||||
|
const originDescriptor = originDescriptors[propName];
|
||||||
|
if (originDescriptor === __NULL__) {
|
||||||
|
delete Element.prototype[propName];
|
||||||
|
} else if (typeof originDescriptor === 'function') {
|
||||||
|
Element.prototype[propName] = originDescriptor;
|
||||||
|
} else {
|
||||||
|
Object.defineProperty(Element.prototype, propName, originDescriptor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type FunctionPropertyNames<T> = {
|
||||||
|
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
|
||||||
|
}[keyof T] &
|
||||||
|
string;
|
||||||
|
|
||||||
|
export function spyElementPrototype<P extends {}, K extends FunctionPropertyNames<P>>(
|
||||||
|
Element: ElementType<P>,
|
||||||
|
propName: K,
|
||||||
|
property: P[K],
|
||||||
|
) {
|
||||||
|
return spyElementPrototypes(Element, {
|
||||||
|
[propName]: property,
|
||||||
|
});
|
||||||
|
}
|
||||||
13
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/easings.test.js
vendored
Normal file
13
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/easings.test.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { easeInOutCubic } from '../easings';
|
||||||
|
|
||||||
|
describe('Test easings', () => {
|
||||||
|
it('easeInOutCubic return value', () => {
|
||||||
|
const nums = [];
|
||||||
|
// eslint-disable-next-line no-plusplus
|
||||||
|
for (let index = 0; index < 5; index++) {
|
||||||
|
nums.push(easeInOutCubic(index, 1, 5, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(nums).toEqual([1, 1.25, 3, 4.75, 5]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @jest-environment node
|
||||||
|
*/
|
||||||
|
import getScroll from '../getScroll';
|
||||||
|
|
||||||
|
describe('getScroll', () => {
|
||||||
|
it('getScroll return 0 in node envioronment', async () => {
|
||||||
|
expect(getScroll(null, true)).toBe(0);
|
||||||
|
expect(getScroll(null, false)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import scrollTo from '../scrollTo';
|
||||||
|
import { sleep } from '../../../tests/utils';
|
||||||
|
|
||||||
|
describe('Test ScrollTo function', () => {
|
||||||
|
let dateNowMock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dateNowMock = jest
|
||||||
|
.spyOn(Date, 'now')
|
||||||
|
.mockImplementationOnce(() => 0)
|
||||||
|
.mockImplementationOnce(() => 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
dateNowMock.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test scrollTo', async () => {
|
||||||
|
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((x, y) => {
|
||||||
|
window.scrollY = y;
|
||||||
|
window.pageYOffset = y;
|
||||||
|
});
|
||||||
|
|
||||||
|
scrollTo(1000);
|
||||||
|
await sleep(20);
|
||||||
|
|
||||||
|
expect(window.pageYOffset).toBe(1000);
|
||||||
|
|
||||||
|
scrollToSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test callback - option', async () => {
|
||||||
|
const cbMock = jest.fn();
|
||||||
|
scrollTo(1000, {
|
||||||
|
callback: cbMock,
|
||||||
|
});
|
||||||
|
await sleep(20);
|
||||||
|
expect(cbMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test getContainer - option', async () => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
scrollTo(1000, {
|
||||||
|
getContainer: () => div,
|
||||||
|
});
|
||||||
|
await sleep(20);
|
||||||
|
expect(div.scrollTop).toBe(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import UnreachableException from '../unreachableException';
|
||||||
|
|
||||||
|
describe('UnreachableException', () => {
|
||||||
|
it('error thrown matches snapshot', () => {
|
||||||
|
const exception = new UnreachableException('some value');
|
||||||
|
expect(exception.message).toMatchInlineSnapshot(`"unreachable case: \\"some value\\""`);
|
||||||
|
});
|
||||||
|
});
|
||||||
206
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/util.test.js
vendored
Normal file
206
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/util.test.js
vendored
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import raf from 'raf';
|
||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import KeyCode from 'rc-util/lib/KeyCode';
|
||||||
|
import delayRaf from '../raf';
|
||||||
|
import throttleByAnimationFrame from '../throttleByAnimationFrame';
|
||||||
|
import getDataOrAriaProps from '../getDataOrAriaProps';
|
||||||
|
import Wave from '../wave';
|
||||||
|
import TransButton from '../transButton';
|
||||||
|
import openAnimation from '../openAnimation';
|
||||||
|
import { sleep } from '../../../tests/utils';
|
||||||
|
import focusTest from '../../../tests/shared/focusTest';
|
||||||
|
|
||||||
|
describe('Test utils function', () => {
|
||||||
|
focusTest(TransButton);
|
||||||
|
|
||||||
|
it('throttle function should work', async () => {
|
||||||
|
const callback = jest.fn();
|
||||||
|
const throttled = throttleByAnimationFrame(callback);
|
||||||
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
throttled();
|
||||||
|
throttled();
|
||||||
|
await sleep(20);
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalled();
|
||||||
|
expect(callback.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throttle function should be canceled', async () => {
|
||||||
|
const callback = jest.fn();
|
||||||
|
const throttled = throttleByAnimationFrame(callback);
|
||||||
|
|
||||||
|
throttled();
|
||||||
|
throttled.cancel();
|
||||||
|
await sleep(20);
|
||||||
|
|
||||||
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getDataOrAriaProps', () => {
|
||||||
|
it('returns all data-* properties from an object', () => {
|
||||||
|
const props = {
|
||||||
|
onClick: () => {},
|
||||||
|
isOpen: true,
|
||||||
|
'data-test': 'test-id',
|
||||||
|
'data-id': 1234,
|
||||||
|
};
|
||||||
|
const results = getDataOrAriaProps(props);
|
||||||
|
expect(results).toEqual({
|
||||||
|
'data-test': 'test-id',
|
||||||
|
'data-id': 1234,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not return data-__ properties from an object', () => {
|
||||||
|
const props = {
|
||||||
|
onClick: () => {},
|
||||||
|
isOpen: true,
|
||||||
|
'data-__test': 'test-id',
|
||||||
|
'data-__id': 1234,
|
||||||
|
};
|
||||||
|
const results = getDataOrAriaProps(props);
|
||||||
|
expect(results).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns all aria-* properties from an object', () => {
|
||||||
|
const props = {
|
||||||
|
onClick: () => {},
|
||||||
|
isOpen: true,
|
||||||
|
'aria-labelledby': 'label-id',
|
||||||
|
'aria-label': 'some-label',
|
||||||
|
};
|
||||||
|
const results = getDataOrAriaProps(props);
|
||||||
|
expect(results).toEqual({
|
||||||
|
'aria-labelledby': 'label-id',
|
||||||
|
'aria-label': 'some-label',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns role property from an object', () => {
|
||||||
|
const props = {
|
||||||
|
onClick: () => {},
|
||||||
|
isOpen: true,
|
||||||
|
role: 'search',
|
||||||
|
};
|
||||||
|
const results = getDataOrAriaProps(props);
|
||||||
|
expect(results).toEqual({ role: 'search' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delayRaf', done => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
|
||||||
|
let bamboo = false;
|
||||||
|
delayRaf(() => {
|
||||||
|
bamboo = true;
|
||||||
|
}, 3);
|
||||||
|
|
||||||
|
// Do nothing, but insert in the frame
|
||||||
|
// https://github.com/ant-design/ant-design/issues/16290
|
||||||
|
delayRaf(() => {}, 3);
|
||||||
|
|
||||||
|
// Variable bamboo should be false in frame 2 but true in frame 4
|
||||||
|
raf(() => {
|
||||||
|
expect(bamboo).toBe(false);
|
||||||
|
|
||||||
|
// Frame 2
|
||||||
|
raf(() => {
|
||||||
|
expect(bamboo).toBe(false);
|
||||||
|
|
||||||
|
// Frame 3
|
||||||
|
raf(() => {
|
||||||
|
// Frame 4
|
||||||
|
raf(() => {
|
||||||
|
expect(bamboo).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('wave', () => {
|
||||||
|
it('bindAnimationEvent should return when node is null', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Wave>
|
||||||
|
<button type="button" disabled>
|
||||||
|
button
|
||||||
|
</button>
|
||||||
|
</Wave>,
|
||||||
|
).instance();
|
||||||
|
expect(wrapper.bindAnimationEvent()).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bindAnimationEvent.onClick should return when children is hidden', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Wave>
|
||||||
|
<button type="button" style={{ display: 'none' }}>
|
||||||
|
button
|
||||||
|
</button>
|
||||||
|
</Wave>,
|
||||||
|
).instance();
|
||||||
|
expect(wrapper.bindAnimationEvent()).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bindAnimationEvent.onClick should return when children is input', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Wave>
|
||||||
|
<input />
|
||||||
|
</Wave>,
|
||||||
|
).instance();
|
||||||
|
expect(wrapper.bindAnimationEvent()).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw when click it', () => {
|
||||||
|
expect(() => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Wave>
|
||||||
|
<div />
|
||||||
|
</Wave>,
|
||||||
|
);
|
||||||
|
wrapper.simulate('click');
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw when no children', () => {
|
||||||
|
expect(() => mount(<Wave />)).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TransButton', () => {
|
||||||
|
it('can be focus/blur', () => {
|
||||||
|
const wrapper = mount(<TransButton>TransButton</TransButton>);
|
||||||
|
expect(typeof wrapper.instance().focus).toBe('function');
|
||||||
|
expect(typeof wrapper.instance().blur).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger onClick when press enter', () => {
|
||||||
|
const onClick = jest.fn();
|
||||||
|
const preventDefault = jest.fn();
|
||||||
|
const wrapper = mount(<TransButton onClick={onClick}>TransButton</TransButton>);
|
||||||
|
wrapper.simulate('keyUp', { keyCode: KeyCode.ENTER });
|
||||||
|
expect(onClick).toHaveBeenCalled();
|
||||||
|
wrapper.simulate('keyDown', { keyCode: KeyCode.ENTER, preventDefault });
|
||||||
|
expect(preventDefault).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('openAnimation', () => {
|
||||||
|
it('should support openAnimation', () => {
|
||||||
|
const done = jest.fn();
|
||||||
|
const domNode = document.createElement('div');
|
||||||
|
expect(typeof openAnimation.enter).toBe('function');
|
||||||
|
expect(typeof openAnimation.leave).toBe('function');
|
||||||
|
expect(typeof openAnimation.appear).toBe('function');
|
||||||
|
const appear = openAnimation.appear(domNode, done);
|
||||||
|
const enter = openAnimation.enter(domNode, done);
|
||||||
|
const leave = openAnimation.leave(domNode, done);
|
||||||
|
expect(typeof appear.stop).toBe('function');
|
||||||
|
expect(typeof enter.stop).toBe('function');
|
||||||
|
expect(typeof leave.stop).toBe('function');
|
||||||
|
expect(done).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
107
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/wave.test.js
vendored
Normal file
107
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/wave.test.js
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import Wave from '../wave';
|
||||||
|
import ConfigProvider from '../../config-provider';
|
||||||
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
|
import { sleep } from '../../../tests/utils';
|
||||||
|
|
||||||
|
describe('Wave component', () => {
|
||||||
|
mountTest(Wave);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
for (let i = 0; i < styles.length; i += 1) {
|
||||||
|
styles[i].remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isHidden works', () => {
|
||||||
|
const TEST_NODE_ENV = process.env.NODE_ENV;
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
const wrapper = mount(<Wave><button type="button">button</button></Wave>);
|
||||||
|
expect(wrapper.find('button').getDOMNode().className).toBe('');
|
||||||
|
wrapper.find('button').getDOMNode().click();
|
||||||
|
expect(wrapper.find('button').getDOMNode().hasAttribute('ant-click-animating-without-extra-node')).toBe(false);
|
||||||
|
wrapper.unmount();
|
||||||
|
process.env.NODE_ENV = TEST_NODE_ENV;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isHidden is mocked', () => {
|
||||||
|
const wrapper = mount(<Wave><button type="button">button</button></Wave>);
|
||||||
|
expect(wrapper.find('button').getDOMNode().className).toBe('');
|
||||||
|
wrapper.find('button').getDOMNode().click();
|
||||||
|
expect(wrapper.find('button').getDOMNode().getAttribute('ant-click-animating-without-extra-node')).toBe('false');
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wave color is grey', async () => {
|
||||||
|
const wrapper = mount(<Wave><button type="button" style={{ borderColor: 'rgb(0, 0, 0)' }}>button</button></Wave>);
|
||||||
|
wrapper.find('button').getDOMNode().click();
|
||||||
|
await sleep(0);
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
expect(styles.length).toBe(0);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wave color is not grey', async () => {
|
||||||
|
const wrapper = mount(<Wave><button type="button" style={{ borderColor: 'red' }}>button</button></Wave>);
|
||||||
|
wrapper.find('button').getDOMNode().click();
|
||||||
|
await sleep(200);
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
expect(styles.length).toBe(1);
|
||||||
|
expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: red;');
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('read wave color from border-top-color', async () => {
|
||||||
|
const wrapper = mount(<Wave><div style={{ borderTopColor: 'blue' }}>button</div></Wave>);
|
||||||
|
wrapper.find('div').getDOMNode().click();
|
||||||
|
await sleep(0);
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
expect(styles.length).toBe(1);
|
||||||
|
expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: blue;');
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('read wave color from background color', async () => {
|
||||||
|
const wrapper = mount(<Wave><div style={{ backgroundColor: 'green' }}>button</div></Wave>);
|
||||||
|
wrapper.find('div').getDOMNode().click();
|
||||||
|
await sleep(0);
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
expect(styles.length).toBe(1);
|
||||||
|
expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: green;');
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('read wave color from border firstly', async () => {
|
||||||
|
const wrapper = mount(<Wave><div style={{ borderColor: 'yellow', backgroundColor: 'green' }}>button</div></Wave>);
|
||||||
|
wrapper.find('div').getDOMNode().click();
|
||||||
|
await sleep(0);
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
expect(styles.length).toBe(1);
|
||||||
|
expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: yellow;');
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hidden element with -leave className', async () => {
|
||||||
|
const wrapper = mount(<Wave><button type="button" className="xx-leave">button</button></Wave>);
|
||||||
|
wrapper.find('button').getDOMNode().click();
|
||||||
|
await sleep(0);
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
expect(styles.length).toBe(0);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ConfigProvider csp', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<ConfigProvider csp={{ nonce: 'YourNonceCode' }}>
|
||||||
|
<Wave><button type="button">button</button></Wave>
|
||||||
|
</ConfigProvider>,
|
||||||
|
);
|
||||||
|
wrapper.find('button').getDOMNode().click();
|
||||||
|
await sleep(0);
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
expect(styles[0].getAttribute('nonce')).toBe('YourNonceCode');
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
22
packages/material-parser/test/fixtures/antd-component/components/_util/colors.ts
vendored
Normal file
22
packages/material-parser/test/fixtures/antd-component/components/_util/colors.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ElementOf, tuple } from './type';
|
||||||
|
|
||||||
|
export const PresetStatusColorTypes = tuple('success', 'processing', 'error', 'default', 'warning');
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export const PresetColorTypes = tuple(
|
||||||
|
'pink',
|
||||||
|
'red',
|
||||||
|
'yellow',
|
||||||
|
'orange',
|
||||||
|
'cyan',
|
||||||
|
'green',
|
||||||
|
'blue',
|
||||||
|
'purple',
|
||||||
|
'geekblue',
|
||||||
|
'magenta',
|
||||||
|
'volcano',
|
||||||
|
'gold',
|
||||||
|
'lime',
|
||||||
|
);
|
||||||
|
|
||||||
|
export type PresetColorType = ElementOf<typeof PresetColorTypes>;
|
||||||
|
export type PresetStatusColorType = ElementOf<typeof PresetStatusColorTypes>;
|
||||||
9
packages/material-parser/test/fixtures/antd-component/components/_util/easings.ts
vendored
Normal file
9
packages/material-parser/test/fixtures/antd-component/components/_util/easings.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export function easeInOutCubic(t: number, b: number, c: number, d: number) {
|
||||||
|
const cc = c - b;
|
||||||
|
t /= d / 2;
|
||||||
|
if (t < 1) {
|
||||||
|
return (cc / 2) * t * t * t + b;
|
||||||
|
}
|
||||||
|
return (cc / 2) * ((t -= 2) * t * t + 2) + b;
|
||||||
|
}
|
||||||
11
packages/material-parser/test/fixtures/antd-component/components/_util/getDataOrAriaProps.ts
vendored
Normal file
11
packages/material-parser/test/fixtures/antd-component/components/_util/getDataOrAriaProps.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export default function getDataOrAriaProps(props: any) {
|
||||||
|
return Object.keys(props).reduce((prev: any, key: string) => {
|
||||||
|
if (
|
||||||
|
(key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-' || key === 'role') &&
|
||||||
|
key.substr(0, 7) !== 'data-__'
|
||||||
|
) {
|
||||||
|
prev[key] = props[key];
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
18
packages/material-parser/test/fixtures/antd-component/components/_util/getRenderPropValue.ts
vendored
Normal file
18
packages/material-parser/test/fixtures/antd-component/components/_util/getRenderPropValue.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export type RenderFunction = () => React.ReactNode;
|
||||||
|
|
||||||
|
export const getRenderPropValue = (
|
||||||
|
propValue?: React.ReactNode | RenderFunction,
|
||||||
|
): React.ReactNode => {
|
||||||
|
if (!propValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRenderFunction = typeof propValue === 'function';
|
||||||
|
if (isRenderFunction) {
|
||||||
|
return (propValue as RenderFunction)();
|
||||||
|
}
|
||||||
|
|
||||||
|
return propValue;
|
||||||
|
};
|
||||||
27
packages/material-parser/test/fixtures/antd-component/components/_util/getScroll.tsx
vendored
Normal file
27
packages/material-parser/test/fixtures/antd-component/components/_util/getScroll.tsx
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export function isWindow(obj: any) {
|
||||||
|
return obj !== null && obj !== undefined && obj === obj.window;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function getScroll(
|
||||||
|
target: HTMLElement | Window | Document | null,
|
||||||
|
top: boolean,
|
||||||
|
): number {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const method = top ? 'scrollTop' : 'scrollLeft';
|
||||||
|
let result = 0;
|
||||||
|
if (isWindow(target)) {
|
||||||
|
result = (target as Window)[top ? 'pageYOffset' : 'pageXOffset'];
|
||||||
|
} else if (target instanceof Document) {
|
||||||
|
result = target.documentElement[method];
|
||||||
|
} else if (target) {
|
||||||
|
result = (target as HTMLElement)[method];
|
||||||
|
}
|
||||||
|
if (target && !isWindow(target) && typeof result !== 'number') {
|
||||||
|
result = ((target as HTMLElement).ownerDocument || (target as Document)).documentElement[
|
||||||
|
method
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
5
packages/material-parser/test/fixtures/antd-component/components/_util/interopDefault.ts
vendored
Normal file
5
packages/material-parser/test/fixtures/antd-component/components/_util/interopDefault.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// https://github.com/moment/moment/issues/3650
|
||||||
|
// since we are using ts 3.5.1, it should be safe to remove.
|
||||||
|
export default function interopDefault(m: any) {
|
||||||
|
return m.default || m;
|
||||||
|
}
|
||||||
5
packages/material-parser/test/fixtures/antd-component/components/_util/isNumeric.ts
vendored
Normal file
5
packages/material-parser/test/fixtures/antd-component/components/_util/isNumeric.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const isNumeric = (value: any): boolean => {
|
||||||
|
return !isNaN(parseFloat(value)) && isFinite(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isNumeric;
|
||||||
40
packages/material-parser/test/fixtures/antd-component/components/_util/motion.tsx
vendored
Normal file
40
packages/material-parser/test/fixtures/antd-component/components/_util/motion.tsx
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
type MotionFunc = (element: HTMLElement) => React.CSSProperties;
|
||||||
|
|
||||||
|
interface Motion {
|
||||||
|
visible?: boolean;
|
||||||
|
motionName?: string; // It also support object, but we only use string here.
|
||||||
|
motionAppear?: boolean;
|
||||||
|
motionEnter?: boolean;
|
||||||
|
motionLeave?: boolean;
|
||||||
|
motionLeaveImmediately?: boolean; // Trigger leave motion immediately
|
||||||
|
removeOnLeave?: boolean;
|
||||||
|
leavedClassName?: string;
|
||||||
|
onAppearStart?: MotionFunc;
|
||||||
|
onAppearActive?: MotionFunc;
|
||||||
|
onAppearEnd?: MotionFunc;
|
||||||
|
onEnterStart?: MotionFunc;
|
||||||
|
onEnterActive?: MotionFunc;
|
||||||
|
onEnterEnd?: MotionFunc;
|
||||||
|
onLeaveStart?: MotionFunc;
|
||||||
|
onLeaveActive?: MotionFunc;
|
||||||
|
onLeaveEnd?: MotionFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================== Collapse Motion ==================
|
||||||
|
const getCollapsedHeight: MotionFunc = () => ({ height: 0, opacity: 0 });
|
||||||
|
const getRealHeight: MotionFunc = node => ({ height: node.scrollHeight, opacity: 1 });
|
||||||
|
const getCurrentHeight: MotionFunc = node => ({ height: node.offsetHeight });
|
||||||
|
|
||||||
|
const collapseMotion: Motion = {
|
||||||
|
motionName: 'ant-motion-collapse',
|
||||||
|
onAppearStart: getCollapsedHeight,
|
||||||
|
onEnterStart: getCollapsedHeight,
|
||||||
|
onAppearActive: getRealHeight,
|
||||||
|
onEnterActive: getRealHeight,
|
||||||
|
onLeaveStart: getCurrentHeight,
|
||||||
|
onLeaveActive: getCollapsedHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default collapseMotion;
|
||||||
54
packages/material-parser/test/fixtures/antd-component/components/_util/openAnimation.tsx
vendored
Normal file
54
packages/material-parser/test/fixtures/antd-component/components/_util/openAnimation.tsx
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Deprecated. We should replace the animation with pure react motion instead of modify style directly.
|
||||||
|
* If you are creating new component with animation, please use `./motion`.
|
||||||
|
*/
|
||||||
|
import cssAnimation from 'css-animation';
|
||||||
|
import raf from 'raf';
|
||||||
|
|
||||||
|
function animate(node: HTMLElement, show: boolean, done: () => void) {
|
||||||
|
let height: number;
|
||||||
|
let requestAnimationFrameId: number;
|
||||||
|
return cssAnimation(node, 'ant-motion-collapse-legacy', {
|
||||||
|
start() {
|
||||||
|
if (!show) {
|
||||||
|
node.style.height = `${node.offsetHeight}px`;
|
||||||
|
node.style.opacity = '1';
|
||||||
|
} else {
|
||||||
|
height = node.offsetHeight;
|
||||||
|
node.style.height = '0px';
|
||||||
|
node.style.opacity = '0';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
active() {
|
||||||
|
if (requestAnimationFrameId) {
|
||||||
|
raf.cancel(requestAnimationFrameId);
|
||||||
|
}
|
||||||
|
requestAnimationFrameId = raf(() => {
|
||||||
|
node.style.height = `${show ? height : 0}px`;
|
||||||
|
node.style.opacity = show ? '1' : '0';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
end() {
|
||||||
|
if (requestAnimationFrameId) {
|
||||||
|
raf.cancel(requestAnimationFrameId);
|
||||||
|
}
|
||||||
|
node.style.height = '';
|
||||||
|
node.style.opacity = '';
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const animation = {
|
||||||
|
enter(node: HTMLElement, done: () => void) {
|
||||||
|
return animate(node, true, done);
|
||||||
|
},
|
||||||
|
leave(node: HTMLElement, done: () => void) {
|
||||||
|
return animate(node, false, done);
|
||||||
|
},
|
||||||
|
appear(node: HTMLElement, done: () => void) {
|
||||||
|
return animate(node, true, done);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default animation;
|
||||||
38
packages/material-parser/test/fixtures/antd-component/components/_util/raf.ts
vendored
Normal file
38
packages/material-parser/test/fixtures/antd-component/components/_util/raf.ts
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import raf from 'raf';
|
||||||
|
|
||||||
|
interface RafMap {
|
||||||
|
[id: number]: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id: number = 0;
|
||||||
|
const ids: RafMap = {};
|
||||||
|
|
||||||
|
// Support call raf with delay specified frame
|
||||||
|
export default function wrapperRaf(callback: () => void, delayFrames: number = 1): number {
|
||||||
|
const myId: number = id++;
|
||||||
|
let restFrames: number = delayFrames;
|
||||||
|
|
||||||
|
function internalCallback() {
|
||||||
|
restFrames -= 1;
|
||||||
|
|
||||||
|
if (restFrames <= 0) {
|
||||||
|
callback();
|
||||||
|
delete ids[myId];
|
||||||
|
} else {
|
||||||
|
ids[myId] = raf(internalCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ids[myId] = raf(internalCallback);
|
||||||
|
|
||||||
|
return myId;
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapperRaf.cancel = function cancel(pid?: number) {
|
||||||
|
if (pid === undefined) return;
|
||||||
|
|
||||||
|
raf.cancel(ids[pid]);
|
||||||
|
delete ids[pid];
|
||||||
|
};
|
||||||
|
|
||||||
|
wrapperRaf.ids = ids; // export this for test usage
|
||||||
8
packages/material-parser/test/fixtures/antd-component/components/_util/reactNode.ts
vendored
Normal file
8
packages/material-parser/test/fixtures/antd-component/components/_util/reactNode.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export function cloneElement(element: React.ReactNode, ...restArgs: any[]) {
|
||||||
|
if (!React.isValidElement(element)) return element;
|
||||||
|
|
||||||
|
return React.cloneElement(element, ...restArgs);
|
||||||
|
}
|
||||||
17
packages/material-parser/test/fixtures/antd-component/components/_util/ref.ts
vendored
Normal file
17
packages/material-parser/test/fixtures/antd-component/components/_util/ref.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function fillRef<T>(ref: React.Ref<T>, node: T) {
|
||||||
|
if (typeof ref === 'function') {
|
||||||
|
ref(node);
|
||||||
|
} else if (typeof ref === 'object' && ref && 'current' in ref) {
|
||||||
|
(ref as any).current = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function composeRef<T>(...refs: React.Ref<T>[]): React.Ref<T> {
|
||||||
|
return (node: T) => {
|
||||||
|
refs.forEach(ref => {
|
||||||
|
fillRef(ref, node);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
87
packages/material-parser/test/fixtures/antd-component/components/_util/responsiveObserve.ts
vendored
Normal file
87
packages/material-parser/test/fixtures/antd-component/components/_util/responsiveObserve.ts
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
||||||
|
export type BreakpointMap = Partial<Record<Breakpoint, string>>;
|
||||||
|
export type ScreenMap = Partial<Record<Breakpoint, boolean>>;
|
||||||
|
|
||||||
|
export const responsiveArray: Breakpoint[] = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs'];
|
||||||
|
|
||||||
|
export const responsiveMap: BreakpointMap = {
|
||||||
|
xs: '(max-width: 575px)',
|
||||||
|
sm: '(min-width: 576px)',
|
||||||
|
md: '(min-width: 768px)',
|
||||||
|
lg: '(min-width: 992px)',
|
||||||
|
xl: '(min-width: 1200px)',
|
||||||
|
xxl: '(min-width: 1600px)',
|
||||||
|
};
|
||||||
|
|
||||||
|
type SubscribeFunc = (screens: ScreenMap) => void;
|
||||||
|
|
||||||
|
let subscribers: Array<{
|
||||||
|
token: string;
|
||||||
|
func: SubscribeFunc;
|
||||||
|
}> = [];
|
||||||
|
let subUid = -1;
|
||||||
|
let screens = {};
|
||||||
|
|
||||||
|
const responsiveObserve = {
|
||||||
|
matchHandlers: {},
|
||||||
|
dispatch(pointMap: ScreenMap) {
|
||||||
|
screens = pointMap;
|
||||||
|
if (subscribers.length < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribers.forEach(item => {
|
||||||
|
item.func(screens);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
subscribe(func: SubscribeFunc) {
|
||||||
|
if (subscribers.length === 0) {
|
||||||
|
this.register();
|
||||||
|
}
|
||||||
|
const token = (++subUid).toString();
|
||||||
|
subscribers.push({
|
||||||
|
token,
|
||||||
|
func,
|
||||||
|
});
|
||||||
|
func(screens);
|
||||||
|
return token;
|
||||||
|
},
|
||||||
|
unsubscribe(token: string) {
|
||||||
|
subscribers = subscribers.filter(item => item.token !== token);
|
||||||
|
if (subscribers.length === 0) {
|
||||||
|
this.unregister();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unregister() {
|
||||||
|
Object.keys(responsiveMap).forEach((screen: Breakpoint) => {
|
||||||
|
const matchMediaQuery = responsiveMap[screen]!;
|
||||||
|
const handler = this.matchHandlers[matchMediaQuery];
|
||||||
|
if (handler && handler.mql && handler.listener) {
|
||||||
|
handler.mql.removeListener(handler.listener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
register() {
|
||||||
|
Object.keys(responsiveMap).forEach((screen: Breakpoint) => {
|
||||||
|
const matchMediaQuery = responsiveMap[screen]!;
|
||||||
|
const listener = ({ matches }: { matches: boolean }) => {
|
||||||
|
this.dispatch({
|
||||||
|
...screens,
|
||||||
|
[screen]: matches,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const mql = window.matchMedia(matchMediaQuery);
|
||||||
|
mql.addListener(listener);
|
||||||
|
this.matchHandlers[matchMediaQuery] = {
|
||||||
|
mql,
|
||||||
|
listener,
|
||||||
|
};
|
||||||
|
|
||||||
|
listener(mql);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default responsiveObserve;
|
||||||
39
packages/material-parser/test/fixtures/antd-component/components/_util/scrollTo.ts
vendored
Normal file
39
packages/material-parser/test/fixtures/antd-component/components/_util/scrollTo.ts
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import raf from 'raf';
|
||||||
|
import getScroll, { isWindow } from './getScroll';
|
||||||
|
import { easeInOutCubic } from './easings';
|
||||||
|
|
||||||
|
interface ScrollToOptions {
|
||||||
|
/** Scroll container, default as window */
|
||||||
|
getContainer?: () => HTMLElement | Window | Document;
|
||||||
|
/** Scroll end callback */
|
||||||
|
callback?: () => any;
|
||||||
|
/** Animation duration, default as 450 */
|
||||||
|
duration?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function scrollTo(y: number, options: ScrollToOptions = {}) {
|
||||||
|
const { getContainer = () => window, callback, duration = 450 } = options;
|
||||||
|
|
||||||
|
const container = getContainer();
|
||||||
|
const scrollTop = getScroll(container, true);
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
const frameFunc = () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const time = timestamp - startTime;
|
||||||
|
const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration);
|
||||||
|
if (isWindow(container)) {
|
||||||
|
(container as Window).scrollTo(window.pageXOffset, nextScrollTop);
|
||||||
|
} else if (container instanceof Document) {
|
||||||
|
container.documentElement.scrollTop = nextScrollTop;
|
||||||
|
} else {
|
||||||
|
(container as HTMLElement).scrollTop = nextScrollTop;
|
||||||
|
}
|
||||||
|
if (time < duration) {
|
||||||
|
raf(frameFunc);
|
||||||
|
} else if (typeof callback === 'function') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
raf(frameFunc);
|
||||||
|
}
|
||||||
13
packages/material-parser/test/fixtures/antd-component/components/_util/styleChecker.tsx
vendored
Normal file
13
packages/material-parser/test/fixtures/antd-component/components/_util/styleChecker.tsx
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const isStyleSupport = (styleName: string | Array<string>): boolean => {
|
||||||
|
if (typeof window !== 'undefined' && window.document && window.document.documentElement) {
|
||||||
|
const styleNameList = Array.isArray(styleName) ? styleName : [styleName];
|
||||||
|
const { documentElement } = window.document;
|
||||||
|
|
||||||
|
return styleNameList.some(name => name in documentElement.style);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isFlexSupported = isStyleSupport(['flex', 'webkitFlex', 'Flex', 'msFlex']);
|
||||||
|
|
||||||
|
export default isStyleSupport;
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import raf from 'raf';
|
||||||
|
|
||||||
|
export default function throttleByAnimationFrame(fn: (...args: any[]) => void) {
|
||||||
|
let requestId: number | null;
|
||||||
|
|
||||||
|
const later = (args: any[]) => () => {
|
||||||
|
requestId = null;
|
||||||
|
fn(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
const throttled = (...args: any[]) => {
|
||||||
|
if (requestId == null) {
|
||||||
|
requestId = raf(later(args));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(throttled as any).cancel = () => raf.cancel(requestId!);
|
||||||
|
|
||||||
|
return throttled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function throttleByAnimationFrameDecorator() {
|
||||||
|
// eslint-disable-next-line func-names
|
||||||
|
return function(target: any, key: string, descriptor: any) {
|
||||||
|
const fn = descriptor.value;
|
||||||
|
let definingProperty = false;
|
||||||
|
return {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (definingProperty || this === target.prototype || this.hasOwnProperty(key)) {
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundFn = throttleByAnimationFrame(fn.bind(this));
|
||||||
|
definingProperty = true;
|
||||||
|
Object.defineProperty(this, key, {
|
||||||
|
value: boundFn,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
definingProperty = false;
|
||||||
|
return boundFn;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
82
packages/material-parser/test/fixtures/antd-component/components/_util/transButton.tsx
vendored
Normal file
82
packages/material-parser/test/fixtures/antd-component/components/_util/transButton.tsx
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Wrap of sub component which need use as Button capacity (like Icon component).
|
||||||
|
* This helps accessibility reader to tread as a interactive button to operation.
|
||||||
|
*/
|
||||||
|
import * as React from 'react';
|
||||||
|
import KeyCode from 'rc-util/lib/KeyCode';
|
||||||
|
|
||||||
|
interface TransButtonProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
onClick?: (e?: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
|
noStyle?: boolean;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inlineStyle: React.CSSProperties = {
|
||||||
|
border: 0,
|
||||||
|
background: 'transparent',
|
||||||
|
padding: 0,
|
||||||
|
lineHeight: 'inherit',
|
||||||
|
display: 'inline-block',
|
||||||
|
};
|
||||||
|
|
||||||
|
class TransButton extends React.Component<TransButtonProps> {
|
||||||
|
div?: HTMLDivElement;
|
||||||
|
|
||||||
|
lastKeyCode?: number;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { autoFocus } = this.props;
|
||||||
|
if (autoFocus) {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
|
||||||
|
const { keyCode } = event;
|
||||||
|
if (keyCode === KeyCode.ENTER) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyUp: React.KeyboardEventHandler<HTMLDivElement> = event => {
|
||||||
|
const { keyCode } = event;
|
||||||
|
const { onClick } = this.props;
|
||||||
|
if (keyCode === KeyCode.ENTER && onClick) {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setRef = (btn: HTMLDivElement) => {
|
||||||
|
this.div = btn;
|
||||||
|
};
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
if (this.div) {
|
||||||
|
this.div.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blur() {
|
||||||
|
if (this.div) {
|
||||||
|
this.div.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { style, noStyle, ...restProps } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
ref={this.setRef}
|
||||||
|
{...restProps}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
onKeyUp={this.onKeyUp}
|
||||||
|
style={{ ...(!noStyle ? inlineStyle : null), ...style }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TransButton;
|
||||||
16
packages/material-parser/test/fixtures/antd-component/components/_util/type.ts
vendored
Normal file
16
packages/material-parser/test/fixtures/antd-component/components/_util/type.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||||
|
// https://stackoverflow.com/questions/46176165/ways-to-get-string-literal-type-of-array-values-without-enum-overhead
|
||||||
|
export const tuple = <T extends string[]>(...args: T) => args;
|
||||||
|
|
||||||
|
export const tupleNum = <T extends number[]>(...args: T) => args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/a/59187769
|
||||||
|
* Extract the type of an element of an array/tuple without performing indexing
|
||||||
|
*/
|
||||||
|
export type ElementOf<T> = T extends (infer E)[] ? E : T extends readonly (infer E)[] ? E : never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://github.com/Microsoft/TypeScript/issues/29729
|
||||||
|
*/
|
||||||
|
export type LiteralUnion<T extends U, U> = T | (U & {});
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export default class UnreachableException {
|
||||||
|
constructor(value: never) {
|
||||||
|
return new Error(`unreachable case: ${JSON.stringify(value)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
packages/material-parser/test/fixtures/antd-component/components/_util/usePatchElement.tsx
vendored
Normal file
18
packages/material-parser/test/fixtures/antd-component/components/_util/usePatchElement.tsx
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default function usePatchElement(): [
|
||||||
|
React.ReactElement[],
|
||||||
|
(element: React.ReactElement) => Function,
|
||||||
|
] {
|
||||||
|
const [elements, setElements] = React.useState<React.ReactElement[]>([]);
|
||||||
|
|
||||||
|
function patchElement(element: React.ReactElement) {
|
||||||
|
setElements(originElements => [...originElements, element]);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setElements(originElements => originElements.filter(ele => ele !== element));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return [elements, patchElement];
|
||||||
|
}
|
||||||
7
packages/material-parser/test/fixtures/antd-component/components/_util/warning.ts
vendored
Normal file
7
packages/material-parser/test/fixtures/antd-component/components/_util/warning.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import warning, { resetWarned } from 'rc-util/lib/warning';
|
||||||
|
|
||||||
|
export { resetWarned };
|
||||||
|
|
||||||
|
export default (valid: boolean, component: string, message: string): void => {
|
||||||
|
warning(valid, `[antd: ${component}] ${message}`);
|
||||||
|
};
|
||||||
195
packages/material-parser/test/fixtures/antd-component/components/_util/wave.tsx
vendored
Normal file
195
packages/material-parser/test/fixtures/antd-component/components/_util/wave.tsx
vendored
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { findDOMNode } from 'react-dom';
|
||||||
|
import TransitionEvents from 'css-animation/lib/Event';
|
||||||
|
import raf from './raf';
|
||||||
|
import { ConfigConsumer, ConfigConsumerProps, CSPConfig } from '../config-provider';
|
||||||
|
|
||||||
|
let styleForPesudo: HTMLStyleElement | null;
|
||||||
|
|
||||||
|
// Where el is the DOM element you'd like to test for visibility
|
||||||
|
function isHidden(element: HTMLElement) {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !element || element.offsetParent === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotGrey(color: string) {
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\d.]*)?\)/);
|
||||||
|
if (match && match[1] && match[2] && match[3]) {
|
||||||
|
return !(match[1] === match[2] && match[2] === match[3]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Wave extends React.Component<{ insertExtraNode?: boolean }> {
|
||||||
|
private instance?: {
|
||||||
|
cancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
private extraNode: HTMLDivElement;
|
||||||
|
|
||||||
|
private clickWaveTimeoutId: number;
|
||||||
|
|
||||||
|
private animationStartId: number;
|
||||||
|
|
||||||
|
private animationStart: boolean = false;
|
||||||
|
|
||||||
|
private destroyed: boolean = false;
|
||||||
|
|
||||||
|
private csp?: CSPConfig;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const node = findDOMNode(this) as HTMLElement;
|
||||||
|
if (!node || node.nodeType !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.instance = this.bindAnimationEvent(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.instance) {
|
||||||
|
this.instance.cancel();
|
||||||
|
}
|
||||||
|
if (this.clickWaveTimeoutId) {
|
||||||
|
clearTimeout(this.clickWaveTimeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.destroyed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = (node: HTMLElement, waveColor: string) => {
|
||||||
|
if (!node || isHidden(node) || node.className.indexOf('-leave') >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { insertExtraNode } = this.props;
|
||||||
|
this.extraNode = document.createElement('div');
|
||||||
|
const { extraNode } = this;
|
||||||
|
extraNode.className = 'ant-click-animating-node';
|
||||||
|
const attributeName = this.getAttributeName();
|
||||||
|
node.setAttribute(attributeName, 'true');
|
||||||
|
// Not white or transparnt or grey
|
||||||
|
styleForPesudo = styleForPesudo || document.createElement('style');
|
||||||
|
if (
|
||||||
|
waveColor &&
|
||||||
|
waveColor !== '#ffffff' &&
|
||||||
|
waveColor !== 'rgb(255, 255, 255)' &&
|
||||||
|
isNotGrey(waveColor) &&
|
||||||
|
!/rgba\((?:\d*, ){3}0\)/.test(waveColor) && // any transparent rgba color
|
||||||
|
waveColor !== 'transparent'
|
||||||
|
) {
|
||||||
|
// Add nonce if CSP exist
|
||||||
|
if (this.csp && this.csp.nonce) {
|
||||||
|
styleForPesudo.nonce = this.csp.nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
extraNode.style.borderColor = waveColor;
|
||||||
|
styleForPesudo.innerHTML = `
|
||||||
|
[ant-click-animating-without-extra-node='true']::after, .ant-click-animating-node {
|
||||||
|
--antd-wave-shadow-color: ${waveColor};
|
||||||
|
}`;
|
||||||
|
if (!document.body.contains(styleForPesudo)) {
|
||||||
|
document.body.appendChild(styleForPesudo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (insertExtraNode) {
|
||||||
|
node.appendChild(extraNode);
|
||||||
|
}
|
||||||
|
TransitionEvents.addStartEventListener(node, this.onTransitionStart);
|
||||||
|
TransitionEvents.addEndEventListener(node, this.onTransitionEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
onTransitionStart = (e: AnimationEvent) => {
|
||||||
|
if (this.destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = findDOMNode(this) as HTMLElement;
|
||||||
|
if (!e || e.target !== node || this.animationStart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resetEffect(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
onTransitionEnd = (e: AnimationEvent) => {
|
||||||
|
if (!e || e.animationName !== 'fadeEffect') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.resetEffect(e.target as HTMLElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
getAttributeName() {
|
||||||
|
const { insertExtraNode } = this.props;
|
||||||
|
return insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
|
||||||
|
}
|
||||||
|
|
||||||
|
bindAnimationEvent = (node: HTMLElement) => {
|
||||||
|
if (
|
||||||
|
!node ||
|
||||||
|
!node.getAttribute ||
|
||||||
|
node.getAttribute('disabled') ||
|
||||||
|
node.className.indexOf('disabled') >= 0
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const onClick = (e: MouseEvent) => {
|
||||||
|
// Fix radio button click twice
|
||||||
|
if ((e.target as HTMLElement).tagName === 'INPUT' || isHidden(e.target as HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.resetEffect(node);
|
||||||
|
// Get wave color from target
|
||||||
|
const waveColor =
|
||||||
|
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
|
||||||
|
getComputedStyle(node).getPropertyValue('border-color') ||
|
||||||
|
getComputedStyle(node).getPropertyValue('background-color');
|
||||||
|
this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0);
|
||||||
|
|
||||||
|
raf.cancel(this.animationStartId);
|
||||||
|
this.animationStart = true;
|
||||||
|
|
||||||
|
// Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this.
|
||||||
|
this.animationStartId = raf(() => {
|
||||||
|
this.animationStart = false;
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
node.addEventListener('click', onClick, true);
|
||||||
|
return {
|
||||||
|
cancel: () => {
|
||||||
|
node.removeEventListener('click', onClick, true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
resetEffect(node: HTMLElement) {
|
||||||
|
if (!node || node === this.extraNode || !(node instanceof Element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { insertExtraNode } = this.props;
|
||||||
|
const attributeName = this.getAttributeName();
|
||||||
|
node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466
|
||||||
|
|
||||||
|
if (styleForPesudo) {
|
||||||
|
styleForPesudo.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) {
|
||||||
|
node.removeChild(this.extraNode);
|
||||||
|
}
|
||||||
|
TransitionEvents.removeStartEventListener(node, this.onTransitionStart);
|
||||||
|
TransitionEvents.removeEndEventListener(node, this.onTransitionEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderWave = ({ csp }: ConfigConsumerProps) => {
|
||||||
|
const { children } = this.props;
|
||||||
|
this.csp = csp;
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <ConfigConsumer>{this.renderWave}</ConfigConsumer>;
|
||||||
|
}
|
||||||
|
}
|
||||||
209
packages/material-parser/test/fixtures/antd-component/components/affix/__tests__/Affix.test.tsx
vendored
Normal file
209
packages/material-parser/test/fixtures/antd-component/components/affix/__tests__/Affix.test.tsx
vendored
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import Affix from '..';
|
||||||
|
import { getObserverEntities } from '../utils';
|
||||||
|
import Button from '../../button';
|
||||||
|
import { spyElementPrototype } from '../../__tests__/util/domHook';
|
||||||
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
|
import { sleep } from '../../../tests/utils';
|
||||||
|
|
||||||
|
const events: any = {};
|
||||||
|
|
||||||
|
class AffixMounter extends React.Component<{
|
||||||
|
offsetBottom?: number;
|
||||||
|
offsetTop?: number;
|
||||||
|
onTestUpdatePosition?(): void;
|
||||||
|
}> {
|
||||||
|
private container: HTMLDivElement;
|
||||||
|
|
||||||
|
private affix: Affix;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.container.addEventListener = jest.fn().mockImplementation((event, cb) => {
|
||||||
|
events[event] = cb;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTarget = () => this.container;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={node => {
|
||||||
|
this.container = node;
|
||||||
|
}}
|
||||||
|
className="container"
|
||||||
|
>
|
||||||
|
<Affix
|
||||||
|
className="fixed"
|
||||||
|
target={this.getTarget}
|
||||||
|
ref={ele => {
|
||||||
|
this.affix = ele;
|
||||||
|
}}
|
||||||
|
{...this.props}
|
||||||
|
>
|
||||||
|
<Button type="primary">Fixed at the top of container</Button>
|
||||||
|
</Affix>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Affix Render', () => {
|
||||||
|
rtlTest(Affix);
|
||||||
|
|
||||||
|
let wrapper;
|
||||||
|
let domMock;
|
||||||
|
|
||||||
|
const classRect: any = {
|
||||||
|
container: {
|
||||||
|
top: 0,
|
||||||
|
bottom: 100,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
domMock = spyElementPrototype(HTMLElement, 'getBoundingClientRect', function mockBounding() {
|
||||||
|
return (
|
||||||
|
classRect[this.className] || {
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
domMock.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const movePlaceholder = async top => {
|
||||||
|
classRect.fixed = {
|
||||||
|
top,
|
||||||
|
bottom: top,
|
||||||
|
};
|
||||||
|
events.scroll({
|
||||||
|
type: 'scroll',
|
||||||
|
});
|
||||||
|
await sleep(20);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('Anchor render perfectly', async () => {
|
||||||
|
document.body.innerHTML = '<div id="mounter" />';
|
||||||
|
|
||||||
|
wrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
|
||||||
|
await sleep(20);
|
||||||
|
|
||||||
|
await movePlaceholder(0);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||||
|
|
||||||
|
await movePlaceholder(-100);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||||
|
|
||||||
|
await movePlaceholder(0);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('support offsetBottom', async () => {
|
||||||
|
document.body.innerHTML = '<div id="mounter" />';
|
||||||
|
|
||||||
|
wrapper = mount(<AffixMounter offsetBottom={0} />, {
|
||||||
|
attachTo: document.getElementById('mounter'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(20);
|
||||||
|
|
||||||
|
await movePlaceholder(300);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||||
|
|
||||||
|
await movePlaceholder(0);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||||
|
|
||||||
|
await movePlaceholder(300);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updatePosition when offsetTop changed', async () => {
|
||||||
|
document.body.innerHTML = '<div id="mounter" />';
|
||||||
|
|
||||||
|
wrapper = mount(<AffixMounter offsetTop={0} />, {
|
||||||
|
attachTo: document.getElementById('mounter'),
|
||||||
|
});
|
||||||
|
await sleep(20);
|
||||||
|
|
||||||
|
await movePlaceholder(-100);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle.top).toBe(0);
|
||||||
|
wrapper.setProps({
|
||||||
|
offsetTop: 10,
|
||||||
|
});
|
||||||
|
await sleep(20);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle.top).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updatePosition when target changed', () => {
|
||||||
|
it('function change', () => {
|
||||||
|
document.body.innerHTML = '<div id="mounter" />';
|
||||||
|
const container = document.querySelector('#id') as HTMLDivElement;
|
||||||
|
const getTarget = () => container;
|
||||||
|
wrapper = mount(<Affix target={getTarget}>{null}</Affix>);
|
||||||
|
wrapper.setProps({ target: null });
|
||||||
|
expect(wrapper.instance().state.status).toBe(0);
|
||||||
|
expect(wrapper.instance().state.affixStyle).toBe(undefined);
|
||||||
|
expect(wrapper.instance().state.placeholderStyle).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('instance change', async () => {
|
||||||
|
const getObserverLength = () => Object.keys(getObserverEntities()).length;
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
let target = container;
|
||||||
|
|
||||||
|
const originLength = getObserverLength();
|
||||||
|
const getTarget = () => target;
|
||||||
|
wrapper = mount(<Affix target={getTarget}>{null}</Affix>);
|
||||||
|
await sleep(50);
|
||||||
|
|
||||||
|
expect(getObserverLength()).toBe(originLength + 1);
|
||||||
|
target = null;
|
||||||
|
wrapper.setProps({});
|
||||||
|
wrapper.update();
|
||||||
|
await sleep(50);
|
||||||
|
expect(getObserverLength()).toBe(originLength);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updatePosition when size changed', () => {
|
||||||
|
function test(name, index) {
|
||||||
|
it(name, async () => {
|
||||||
|
document.body.innerHTML = '<div id="mounter" />';
|
||||||
|
|
||||||
|
const updateCalled = jest.fn();
|
||||||
|
wrapper = mount(<AffixMounter offsetBottom={0} onTestUpdatePosition={updateCalled} />, {
|
||||||
|
attachTo: document.getElementById('mounter'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(20);
|
||||||
|
|
||||||
|
await movePlaceholder(300);
|
||||||
|
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||||
|
await sleep(20);
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
// Mock trigger resize
|
||||||
|
updateCalled.mockReset();
|
||||||
|
wrapper
|
||||||
|
.find('ResizeObserver')
|
||||||
|
.at(index)
|
||||||
|
.instance()
|
||||||
|
.onResize([{ target: { getBoundingClientRect: () => ({ width: 99, height: 99 }) } }]);
|
||||||
|
await sleep(20);
|
||||||
|
|
||||||
|
expect(updateCalled).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('inner', 0);
|
||||||
|
test('outer', 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Affix Render rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`renders ./components/affix/demo/basic.md correctly 1`] = `
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-primary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Affix top
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-primary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Affix bottom
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/affix/demo/debug.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
style="height:10000px"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
Top
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="background:red"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-primary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Affix top
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Bottom
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/affix/demo/on-change.md correctly 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
120px to affix top
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/affix/demo/target.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="scrollable-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="background"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-primary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Fixed at the top of container
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import demoTest from '../../../tests/shared/demoTest';
|
||||||
|
|
||||||
|
demoTest('affix');
|
||||||
42
packages/material-parser/test/fixtures/antd-component/components/affix/demo/basic.md
vendored
Normal file
42
packages/material-parser/test/fixtures/antd-component/components/affix/demo/basic.md
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
order: 0
|
||||||
|
title:
|
||||||
|
zh-CN: 基本
|
||||||
|
en-US: Basic
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
最简单的用法。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
The simplest usage.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Affix, Button } from 'antd';
|
||||||
|
|
||||||
|
const Demo: React.FC = () => {
|
||||||
|
const [top, setTop] = useState(10);
|
||||||
|
const [bottom, setBottom] = useState(10);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Affix offsetTop={top}>
|
||||||
|
<Button type="primary" onClick={() => setTop(top + 10)}>
|
||||||
|
Affix top
|
||||||
|
</Button>
|
||||||
|
</Affix>
|
||||||
|
<br />
|
||||||
|
<Affix offsetBottom={bottom}>
|
||||||
|
<Button type="primary" onClick={() => setBottom(bottom + 10)}>
|
||||||
|
Affix bottom
|
||||||
|
</Button>
|
||||||
|
</Affix>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
|
```
|
||||||
39
packages/material-parser/test/fixtures/antd-component/components/affix/demo/debug.md
vendored
Normal file
39
packages/material-parser/test/fixtures/antd-component/components/affix/demo/debug.md
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
order: 99
|
||||||
|
title:
|
||||||
|
zh-CN: 调整浏览器大小,观察 Affix 容器是否发生变化。跟随变化为正常。#17678
|
||||||
|
en-US:
|
||||||
|
debug: true
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
DEBUG
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
DEBUG
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Affix, Button } from 'antd';
|
||||||
|
|
||||||
|
const Demo: React.FC = () => {
|
||||||
|
const [top, setTop] = useState(10);
|
||||||
|
return (
|
||||||
|
<div style={{ height: 10000 }}>
|
||||||
|
<div>Top</div>
|
||||||
|
<Affix offsetTop={top}>
|
||||||
|
<div style={{ background: 'red' }}>
|
||||||
|
<Button type="primary" onClick={() => setTop(top + 10)}>
|
||||||
|
Affix top
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Affix>
|
||||||
|
<div>Bottom</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
|
```
|
||||||
25
packages/material-parser/test/fixtures/antd-component/components/affix/demo/on-change.md
vendored
Normal file
25
packages/material-parser/test/fixtures/antd-component/components/affix/demo/on-change.md
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
order: 1
|
||||||
|
title:
|
||||||
|
zh-CN: 固定状态改变的回调
|
||||||
|
en-US: Callback
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
可以获得是否固定的状态。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Callback with affixed state.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Affix, Button } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Affix offsetTop={120} onChange={affixed => console.log(affixed)}>
|
||||||
|
<Button>120px to affix top</Button>
|
||||||
|
</Affix>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
46
packages/material-parser/test/fixtures/antd-component/components/affix/demo/target.md
vendored
Normal file
46
packages/material-parser/test/fixtures/antd-component/components/affix/demo/target.md
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
order: 2
|
||||||
|
title:
|
||||||
|
zh-CN: 滚动容器
|
||||||
|
en-US: Container to scroll.
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
用 `target` 设置 `Affix` 需要监听其滚动事件的元素,默认为 `window`。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Set a `target` for 'Affix', which is listen to scroll event of target element (default is `window`).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Affix, Button } from 'antd';
|
||||||
|
|
||||||
|
const Demo: React.FC = () => {
|
||||||
|
const [container, setContainer] = useState(null);
|
||||||
|
return (
|
||||||
|
<div className="scrollable-container" ref={setContainer}>
|
||||||
|
<div className="background">
|
||||||
|
<Affix target={() => container}>
|
||||||
|
<Button type="primary">Fixed at the top of container</Button>
|
||||||
|
</Affix>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
|
```
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#components-affix-demo-target .scrollable-container {
|
||||||
|
height: 100px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
#components-affix-demo-target .background {
|
||||||
|
padding-top: 60px;
|
||||||
|
height: 300px;
|
||||||
|
background-image: url('https://zos.alipayobjects.com/rmsportal/RmjwQiJorKyobvI.jpg');
|
||||||
|
}
|
||||||
|
</style>
|
||||||
36
packages/material-parser/test/fixtures/antd-component/components/affix/index.en-US.md
vendored
Normal file
36
packages/material-parser/test/fixtures/antd-component/components/affix/index.en-US.md
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
category: Components
|
||||||
|
type: Navigation
|
||||||
|
title: Affix
|
||||||
|
---
|
||||||
|
|
||||||
|
Wrap Affix around another component to make it stick the viewport.
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
On longer web pages, its helpful for some content to stick to the viewport. This is common for menus and actions.
|
||||||
|
|
||||||
|
Please note that Affix should not cover other content on the page, especially when the size of the viewport is small.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| offsetBottom | Offset from the bottom of the viewport (in pixels) | number | - |
|
||||||
|
| offsetTop | Offset from the top of the viewport (in pixels) | number | 0 |
|
||||||
|
| target | Specifies the scrollable area DOM node | () => HTMLElement | () => window |
|
||||||
|
| onChange | Callback for when Affix state is changed | Function(affixed) | - |
|
||||||
|
|
||||||
|
**Note:** Children of `Affix` must not have the property `position: absolute`, but you can set `position: absolute` on `Affix` itself:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<Affix style={{ position: 'absolute', top: y, left: x }}>...</Affix>
|
||||||
|
```
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Affix bind container with `target`, sometime move out of container.
|
||||||
|
|
||||||
|
We don't listen window scroll for performance consideration. You can add listener if you still want: <https://codesandbox.io/s/2xyj5zr85p>
|
||||||
|
|
||||||
|
Related issues:[#3938](https://github.com/ant-design/ant-design/issues/3938) [#5642](https://github.com/ant-design/ant-design/issues/5642) [#16120](https://github.com/ant-design/ant-design/issues/16120)
|
||||||
292
packages/material-parser/test/fixtures/antd-component/components/affix/index.tsx
vendored
Normal file
292
packages/material-parser/test/fixtures/antd-component/components/affix/index.tsx
vendored
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import omit from 'omit.js';
|
||||||
|
import ResizeObserver from 'rc-resize-observer';
|
||||||
|
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||||
|
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';
|
||||||
|
|
||||||
|
import {
|
||||||
|
addObserveTarget,
|
||||||
|
removeObserveTarget,
|
||||||
|
getTargetRect,
|
||||||
|
getFixedTop,
|
||||||
|
getFixedBottom,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
function getDefaultTarget() {
|
||||||
|
return typeof window !== 'undefined' ? window : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Affix
|
||||||
|
export interface AffixProps {
|
||||||
|
/**
|
||||||
|
* 距离窗口顶部达到指定偏移量后触发
|
||||||
|
*/
|
||||||
|
offsetTop?: number;
|
||||||
|
/** 距离窗口底部达到指定偏移量后触发 */
|
||||||
|
offsetBottom?: number;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
/** 固定状态改变时触发的回调函数 */
|
||||||
|
onChange?: (affixed?: boolean) => void;
|
||||||
|
/** 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 */
|
||||||
|
target?: () => Window | HTMLElement | null;
|
||||||
|
prefixCls?: string;
|
||||||
|
className?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AffixStatus {
|
||||||
|
None,
|
||||||
|
Prepare,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AffixState {
|
||||||
|
affixStyle?: React.CSSProperties;
|
||||||
|
placeholderStyle?: React.CSSProperties;
|
||||||
|
status: AffixStatus;
|
||||||
|
lastAffix: boolean;
|
||||||
|
|
||||||
|
prevTarget: Window | HTMLElement | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Affix extends React.Component<AffixProps, AffixState> {
|
||||||
|
static defaultProps = {
|
||||||
|
target: getDefaultTarget,
|
||||||
|
};
|
||||||
|
|
||||||
|
state: AffixState = {
|
||||||
|
status: AffixStatus.None,
|
||||||
|
lastAffix: false,
|
||||||
|
prevTarget: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
placeholderNode: HTMLDivElement;
|
||||||
|
|
||||||
|
fixedNode: HTMLDivElement;
|
||||||
|
|
||||||
|
private timeout: number;
|
||||||
|
|
||||||
|
// Event handler
|
||||||
|
componentDidMount() {
|
||||||
|
const { target } = this.props;
|
||||||
|
if (target) {
|
||||||
|
// [Legacy] Wait for parent component ref has its value.
|
||||||
|
// We should use target as directly element instead of function which makes element check hard.
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
addObserveTarget(target(), this);
|
||||||
|
// Mock Event object.
|
||||||
|
this.updatePosition();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: AffixProps) {
|
||||||
|
const { prevTarget } = this.state;
|
||||||
|
const { target } = this.props;
|
||||||
|
let newTarget = null;
|
||||||
|
if (target) {
|
||||||
|
newTarget = target() || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevTarget !== newTarget) {
|
||||||
|
removeObserveTarget(this);
|
||||||
|
if (newTarget) {
|
||||||
|
addObserveTarget(newTarget, this);
|
||||||
|
// Mock Event object.
|
||||||
|
this.updatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ prevTarget: newTarget });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
prevProps.offsetTop !== this.props.offsetTop ||
|
||||||
|
prevProps.offsetBottom !== this.props.offsetBottom
|
||||||
|
) {
|
||||||
|
this.updatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.measure();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
removeObserveTarget(this);
|
||||||
|
(this.updatePosition as any).cancel();
|
||||||
|
// https://github.com/ant-design/ant-design/issues/22683
|
||||||
|
(this.lazyUpdatePosition as any).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
getOffsetTop = () => {
|
||||||
|
const { offsetBottom } = this.props;
|
||||||
|
let { offsetTop } = this.props;
|
||||||
|
if (offsetBottom === undefined && offsetTop === undefined) {
|
||||||
|
offsetTop = 0;
|
||||||
|
}
|
||||||
|
return offsetTop;
|
||||||
|
};
|
||||||
|
|
||||||
|
getOffsetBottom = () => {
|
||||||
|
return this.props.offsetBottom;
|
||||||
|
};
|
||||||
|
|
||||||
|
savePlaceholderNode = (node: HTMLDivElement) => {
|
||||||
|
this.placeholderNode = node;
|
||||||
|
};
|
||||||
|
|
||||||
|
saveFixedNode = (node: HTMLDivElement) => {
|
||||||
|
this.fixedNode = node;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =================== Measure ===================
|
||||||
|
measure = () => {
|
||||||
|
const { status, lastAffix } = this.state;
|
||||||
|
const { target, onChange } = this.props;
|
||||||
|
if (status !== AffixStatus.Prepare || !this.fixedNode || !this.placeholderNode || !target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetTop = this.getOffsetTop();
|
||||||
|
const offsetBottom = this.getOffsetBottom();
|
||||||
|
|
||||||
|
const targetNode = target();
|
||||||
|
if (!targetNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newState: Partial<AffixState> = {
|
||||||
|
status: AffixStatus.None,
|
||||||
|
};
|
||||||
|
const targetRect = getTargetRect(targetNode);
|
||||||
|
const placeholderReact = getTargetRect(this.placeholderNode);
|
||||||
|
const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
|
||||||
|
const fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);
|
||||||
|
|
||||||
|
if (fixedTop !== undefined) {
|
||||||
|
newState.affixStyle = {
|
||||||
|
position: 'fixed',
|
||||||
|
top: fixedTop,
|
||||||
|
width: placeholderReact.width,
|
||||||
|
height: placeholderReact.height,
|
||||||
|
};
|
||||||
|
newState.placeholderStyle = {
|
||||||
|
width: placeholderReact.width,
|
||||||
|
height: placeholderReact.height,
|
||||||
|
};
|
||||||
|
} else if (fixedBottom !== undefined) {
|
||||||
|
newState.affixStyle = {
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: fixedBottom,
|
||||||
|
width: placeholderReact.width,
|
||||||
|
height: placeholderReact.height,
|
||||||
|
};
|
||||||
|
newState.placeholderStyle = {
|
||||||
|
width: placeholderReact.width,
|
||||||
|
height: placeholderReact.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
newState.lastAffix = !!newState.affixStyle;
|
||||||
|
if (onChange && lastAffix !== newState.lastAffix) {
|
||||||
|
onChange(newState.lastAffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(newState as AffixState);
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore TS6133
|
||||||
|
prepareMeasure = () => {
|
||||||
|
// event param is used before. Keep compatible ts define here.
|
||||||
|
this.setState({
|
||||||
|
status: AffixStatus.Prepare,
|
||||||
|
affixStyle: undefined,
|
||||||
|
placeholderStyle: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test if `updatePosition` called
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
const { onTestUpdatePosition } = this.props as any;
|
||||||
|
if (onTestUpdatePosition) {
|
||||||
|
onTestUpdatePosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle realign logic
|
||||||
|
@throttleByAnimationFrameDecorator()
|
||||||
|
updatePosition() {
|
||||||
|
this.prepareMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@throttleByAnimationFrameDecorator()
|
||||||
|
lazyUpdatePosition() {
|
||||||
|
const { target } = this.props;
|
||||||
|
const { affixStyle } = this.state;
|
||||||
|
|
||||||
|
// Check position change before measure to make Safari smooth
|
||||||
|
if (target && affixStyle) {
|
||||||
|
const offsetTop = this.getOffsetTop();
|
||||||
|
const offsetBottom = this.getOffsetBottom();
|
||||||
|
|
||||||
|
const targetNode = target();
|
||||||
|
if (targetNode && this.placeholderNode) {
|
||||||
|
const targetRect = getTargetRect(targetNode);
|
||||||
|
const placeholderReact = getTargetRect(this.placeholderNode);
|
||||||
|
const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
|
||||||
|
const fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(fixedTop !== undefined && affixStyle.top === fixedTop) ||
|
||||||
|
(fixedBottom !== undefined && affixStyle.bottom === fixedBottom)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directly call prepare measure since it's already throttled.
|
||||||
|
this.prepareMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================== Render ===================
|
||||||
|
renderAffix = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||||
|
const { affixStyle, placeholderStyle } = this.state;
|
||||||
|
const { prefixCls, children } = this.props;
|
||||||
|
const className = classNames({
|
||||||
|
[getPrefixCls('affix', prefixCls)]: affixStyle,
|
||||||
|
});
|
||||||
|
|
||||||
|
let props = omit(this.props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target', 'onChange']);
|
||||||
|
// Omit this since `onTestUpdatePosition` only works on test.
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
props = omit(props, ['onTestUpdatePosition']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResizeObserver
|
||||||
|
onResize={() => {
|
||||||
|
this.updatePosition();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div {...props} ref={this.savePlaceholderNode}>
|
||||||
|
{affixStyle && <div style={placeholderStyle} aria-hidden="true" />}
|
||||||
|
<div className={className} ref={this.saveFixedNode} style={affixStyle}>
|
||||||
|
<ResizeObserver
|
||||||
|
onResize={() => {
|
||||||
|
this.updatePosition();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ResizeObserver>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ResizeObserver>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <ConfigConsumer>{this.renderAffix}</ConfigConsumer>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Affix;
|
||||||
37
packages/material-parser/test/fixtures/antd-component/components/affix/index.zh-CN.md
vendored
Normal file
37
packages/material-parser/test/fixtures/antd-component/components/affix/index.zh-CN.md
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
category: Components
|
||||||
|
subtitle: 固钉
|
||||||
|
type: 导航
|
||||||
|
title: Affix
|
||||||
|
---
|
||||||
|
|
||||||
|
将页面元素钉在可视范围。
|
||||||
|
|
||||||
|
## 何时使用
|
||||||
|
|
||||||
|
当内容区域比较长,需要滚动页面时,这部分内容对应的操作或者导航需要在滚动范围内始终展现。常用于侧边菜单和按钮组合。
|
||||||
|
|
||||||
|
页面可视范围过小时,慎用此功能以免遮挡页面内容。
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| 成员 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| offsetBottom | 距离窗口底部达到指定偏移量后触发 | number | - |
|
||||||
|
| offsetTop | 距离窗口顶部达到指定偏移量后触发 | number | - |
|
||||||
|
| target | 设置 `Affix` 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 | () => HTMLElement | () => window |
|
||||||
|
| onChange | 固定状态改变时触发的回调函数 | Function(affixed) | - |
|
||||||
|
|
||||||
|
**注意:**`Affix` 内的元素不要使用绝对定位,如需要绝对定位的效果,可以直接设置 `Affix` 为绝对定位:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<Affix style={{ position: 'absolute', top: y, left: x }}>...</Affix>
|
||||||
|
```
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Affix 使用 `target` 绑定容器时,元素会跑到容器外。
|
||||||
|
|
||||||
|
从性能角度考虑,我们只监听容器滚动事件。如果希望任意滚动,你可以在窗体添加滚动监听:<https://codesandbox.io/s/2xyj5zr85p>
|
||||||
|
|
||||||
|
相关 issue:[#3938](https://github.com/ant-design/ant-design/issues/3938) [#5642](https://github.com/ant-design/ant-design/issues/5642) [#16120](https://github.com/ant-design/ant-design/issues/16120)
|
||||||
6
packages/material-parser/test/fixtures/antd-component/components/affix/style/index.less
vendored
Normal file
6
packages/material-parser/test/fixtures/antd-component/components/affix/style/index.less
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@import '../../style/themes/index';
|
||||||
|
|
||||||
|
.@{ant-prefix}-affix {
|
||||||
|
position: fixed;
|
||||||
|
z-index: @zindex-affix;
|
||||||
|
}
|
||||||
2
packages/material-parser/test/fixtures/antd-component/components/affix/style/index.tsx
vendored
Normal file
2
packages/material-parser/test/fixtures/antd-component/components/affix/style/index.tsx
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import '../../style/index.less';
|
||||||
|
import './index.less';
|
||||||
106
packages/material-parser/test/fixtures/antd-component/components/affix/utils.ts
vendored
Normal file
106
packages/material-parser/test/fixtures/antd-component/components/affix/utils.ts
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import addEventListener from 'rc-util/lib/Dom/addEventListener';
|
||||||
|
import Affix from '.';
|
||||||
|
|
||||||
|
export type BindElement = HTMLElement | Window | null | undefined;
|
||||||
|
export type Rect = ClientRect | DOMRect;
|
||||||
|
|
||||||
|
export function getTargetRect(target: BindElement): ClientRect {
|
||||||
|
return target !== window
|
||||||
|
? (target as HTMLElement).getBoundingClientRect()
|
||||||
|
: ({ top: 0, bottom: window.innerHeight } as ClientRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFixedTop(
|
||||||
|
placeholderReact: Rect,
|
||||||
|
targetRect: Rect,
|
||||||
|
offsetTop: number | undefined,
|
||||||
|
) {
|
||||||
|
if (offsetTop !== undefined && targetRect.top > placeholderReact.top - offsetTop) {
|
||||||
|
return offsetTop + targetRect.top;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFixedBottom(
|
||||||
|
placeholderReact: Rect,
|
||||||
|
targetRect: Rect,
|
||||||
|
offsetBottom: number | undefined,
|
||||||
|
) {
|
||||||
|
if (offsetBottom !== undefined && targetRect.bottom < placeholderReact.bottom + offsetBottom) {
|
||||||
|
const targetBottomOffset = window.innerHeight - targetRect.bottom;
|
||||||
|
return offsetBottom + targetBottomOffset;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== Observer ========================
|
||||||
|
const TRIGGER_EVENTS = [
|
||||||
|
'resize',
|
||||||
|
'scroll',
|
||||||
|
'touchstart',
|
||||||
|
'touchmove',
|
||||||
|
'touchend',
|
||||||
|
'pageshow',
|
||||||
|
'load',
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ObserverEntity {
|
||||||
|
target: HTMLElement | Window;
|
||||||
|
affixList: Affix[];
|
||||||
|
eventHandlers: { [eventName: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
|
let observerEntities: ObserverEntity[] = [];
|
||||||
|
|
||||||
|
export function getObserverEntities() {
|
||||||
|
// Only used in test env. Can be removed if refactor.
|
||||||
|
return observerEntities;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addObserveTarget(target: HTMLElement | Window | null, affix: Affix): void {
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
let entity: ObserverEntity | undefined = observerEntities.find(item => item.target === target);
|
||||||
|
|
||||||
|
if (entity) {
|
||||||
|
entity.affixList.push(affix);
|
||||||
|
} else {
|
||||||
|
entity = {
|
||||||
|
target,
|
||||||
|
affixList: [affix],
|
||||||
|
eventHandlers: {},
|
||||||
|
};
|
||||||
|
observerEntities.push(entity);
|
||||||
|
|
||||||
|
// Add listener
|
||||||
|
TRIGGER_EVENTS.forEach(eventName => {
|
||||||
|
entity!.eventHandlers[eventName] = addEventListener(target, eventName, () => {
|
||||||
|
entity!.affixList.forEach(targetAffix => {
|
||||||
|
targetAffix.lazyUpdatePosition();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeObserveTarget(affix: Affix): void {
|
||||||
|
const observerEntity = observerEntities.find(oriObserverEntity => {
|
||||||
|
const hasAffix = oriObserverEntity.affixList.some(item => item === affix);
|
||||||
|
if (hasAffix) {
|
||||||
|
oriObserverEntity.affixList = oriObserverEntity.affixList.filter(item => item !== affix);
|
||||||
|
}
|
||||||
|
return hasAffix;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (observerEntity && observerEntity.affixList.length === 0) {
|
||||||
|
observerEntities = observerEntities.filter(item => item !== observerEntity);
|
||||||
|
|
||||||
|
// Remove listener
|
||||||
|
TRIGGER_EVENTS.forEach(eventName => {
|
||||||
|
const handler = observerEntity.eventHandlers[eventName];
|
||||||
|
if (handler && handler.remove) {
|
||||||
|
handler.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
42
packages/material-parser/test/fixtures/antd-component/components/alert/ErrorBoundary.tsx
vendored
Normal file
42
packages/material-parser/test/fixtures/antd-component/components/alert/ErrorBoundary.tsx
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import Alert from '.';
|
||||||
|
|
||||||
|
interface ErrorBoundaryProps {
|
||||||
|
message?: React.ReactNode;
|
||||||
|
description?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends React.Component<
|
||||||
|
ErrorBoundaryProps,
|
||||||
|
{
|
||||||
|
error?: Error | null;
|
||||||
|
info: {
|
||||||
|
componentStack?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
> {
|
||||||
|
state = {
|
||||||
|
error: undefined,
|
||||||
|
info: {
|
||||||
|
componentStack: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidCatch(error: Error | null, info: object) {
|
||||||
|
this.setState({ error, info });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { message, description, children } = this.props;
|
||||||
|
const { error, info } = this.state;
|
||||||
|
const componentStack = info && info.componentStack ? info.componentStack : null;
|
||||||
|
const errorMessage = typeof message === 'undefined' ? (error || '').toString() : message;
|
||||||
|
const errorDescription = typeof description === 'undefined' ? componentStack : description;
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Alert type="error" message={errorMessage} description={<pre>{errorDescription}</pre>} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,37 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Alert ErrorBoundary 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon"
|
||||||
|
data-show="true"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-alert-message"
|
||||||
|
>
|
||||||
|
ReferenceError: NotExisted is not defined
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-alert-description"
|
||||||
|
>
|
||||||
|
<pre>
|
||||||
|
in ThrowError
|
||||||
|
in ErrorBoundary (created by WrapperComponent)
|
||||||
|
in WrapperComponent
|
||||||
|
</pre>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Alert rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-alert ant-alert-info ant-alert-no-icon ant-alert-rtl"
|
||||||
|
data-show="true"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-alert-message"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-alert-description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import demoTest from '../../../tests/shared/demoTest';
|
||||||
|
|
||||||
|
demoTest('alert');
|
||||||
115
packages/material-parser/test/fixtures/antd-component/components/alert/__tests__/index.test.js
vendored
Normal file
115
packages/material-parser/test/fixtures/antd-component/components/alert/__tests__/index.test.js
vendored
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import Alert from '..';
|
||||||
|
import Tooltip from '../../tooltip';
|
||||||
|
import Popconfirm from '../../popconfirm';
|
||||||
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
|
import { sleep } from '../../../tests/utils';
|
||||||
|
|
||||||
|
const { ErrorBoundary } = Alert;
|
||||||
|
|
||||||
|
describe('Alert', () => {
|
||||||
|
rtlTest(Alert);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('could be closed', () => {
|
||||||
|
const onClose = jest.fn();
|
||||||
|
const afterClose = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<Alert
|
||||||
|
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||||
|
type="warning"
|
||||||
|
closable
|
||||||
|
onClose={onClose}
|
||||||
|
afterClose={afterClose}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
wrapper.find('.ant-alert-close-icon').simulate('click');
|
||||||
|
expect(onClose).toHaveBeenCalled();
|
||||||
|
jest.runAllTimers();
|
||||||
|
expect(afterClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('data and aria props', () => {
|
||||||
|
it('sets data attributes on input', () => {
|
||||||
|
const wrapper = mount(<Alert data-test="test-id" data-id="12345" />);
|
||||||
|
const input = wrapper.find('.ant-alert').getDOMNode();
|
||||||
|
expect(input.getAttribute('data-test')).toBe('test-id');
|
||||||
|
expect(input.getAttribute('data-id')).toBe('12345');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets aria attributes on input', () => {
|
||||||
|
const wrapper = mount(<Alert aria-describedby="some-label" />);
|
||||||
|
const input = wrapper.find('.ant-alert').getDOMNode();
|
||||||
|
expect(input.getAttribute('aria-describedby')).toBe('some-label');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets role attribute on input', () => {
|
||||||
|
const wrapper = mount(<Alert role="status" />);
|
||||||
|
const input = wrapper.find('.ant-alert').getDOMNode();
|
||||||
|
expect(input.getAttribute('role')).toBe('status');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const testIt = process.env.REACT === '15' ? it.skip : it;
|
||||||
|
testIt('ErrorBoundary', () => {
|
||||||
|
// eslint-disable-next-line react/jsx-no-undef
|
||||||
|
const ThrowError = () => <NotExisted />;
|
||||||
|
const wrapper = mount(
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ThrowError />
|
||||||
|
</ErrorBoundary>,
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line jest/no-standalone-expect
|
||||||
|
expect(wrapper.render()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('could be used with Tooltip', async () => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
const wrapper = mount(
|
||||||
|
<Tooltip title="xxx" mouseEnterDelay={0}>
|
||||||
|
<Alert
|
||||||
|
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||||
|
type="warning"
|
||||||
|
/>
|
||||||
|
</Tooltip>,
|
||||||
|
);
|
||||||
|
wrapper.find('.ant-alert').simulate('mouseenter');
|
||||||
|
await sleep(0);
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(Tooltip)
|
||||||
|
.instance()
|
||||||
|
.getPopupDomNode(),
|
||||||
|
).toBeTruthy();
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('could be used with Popconfirm', async () => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
const wrapper = mount(
|
||||||
|
<Popconfirm title="xxx">
|
||||||
|
<Alert
|
||||||
|
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||||
|
type="warning"
|
||||||
|
/>
|
||||||
|
</Popconfirm>,
|
||||||
|
);
|
||||||
|
wrapper.find('.ant-alert').simulate('click');
|
||||||
|
await sleep(0);
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(Popconfirm)
|
||||||
|
.instance()
|
||||||
|
.getPopupDomNode(),
|
||||||
|
).toBeTruthy();
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
});
|
||||||
36
packages/material-parser/test/fixtures/antd-component/components/alert/demo/banner.md
vendored
Normal file
36
packages/material-parser/test/fixtures/antd-component/components/alert/demo/banner.md
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
order: 6
|
||||||
|
iframe: 250
|
||||||
|
title:
|
||||||
|
zh-CN: 顶部公告
|
||||||
|
en-US: Banner
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
页面顶部通告形式,默认有图标且 `type` 为 'warning'。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Display Alert as a banner at top of page.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<>
|
||||||
|
<Alert message="Warning text" banner />
|
||||||
|
<br />
|
||||||
|
<Alert
|
||||||
|
message="Very long warning text warning text text text text text text text"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<Alert showIcon={false} message="Warning text without icon" banner />
|
||||||
|
<br />
|
||||||
|
<Alert type="error" message="Error text" banner />
|
||||||
|
</>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
26
packages/material-parser/test/fixtures/antd-component/components/alert/demo/basic.md
vendored
Normal file
26
packages/material-parser/test/fixtures/antd-component/components/alert/demo/basic.md
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
order: 0
|
||||||
|
title:
|
||||||
|
zh-CN: 基本
|
||||||
|
en-US: Basic
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
最简单的用法,适用于简短的警告提示。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
The simplest usage for short messages.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(<Alert message="Success Text" type="success" />, mountNode);
|
||||||
|
```
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.code-box-demo .ant-alert {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
41
packages/material-parser/test/fixtures/antd-component/components/alert/demo/closable.md
vendored
Normal file
41
packages/material-parser/test/fixtures/antd-component/components/alert/demo/closable.md
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
order: 2
|
||||||
|
title:
|
||||||
|
zh-CN: 可关闭的警告提示
|
||||||
|
en-US: Closable
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
显示关闭按钮,点击可关闭警告提示。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
To show close button.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
const onClose = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
console.log(e, 'I was closed.');
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div>
|
||||||
|
<Alert
|
||||||
|
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||||
|
type="warning"
|
||||||
|
closable
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
message="Error Text"
|
||||||
|
description="Error Description Error Description Error Description Error Description Error Description Error Description"
|
||||||
|
type="error"
|
||||||
|
closable
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
20
packages/material-parser/test/fixtures/antd-component/components/alert/demo/close-text.md
vendored
Normal file
20
packages/material-parser/test/fixtures/antd-component/components/alert/demo/close-text.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
order: 5
|
||||||
|
title:
|
||||||
|
zh-CN: 自定义关闭
|
||||||
|
en-US: Customized Close Text
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
可以自定义关闭,自定义的文字会替换原先的关闭 `Icon`。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Replace the default icon with customized text.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(<Alert message="Info Text" type="info" closeText="Close Now" />, mountNode);
|
||||||
|
```
|
||||||
61
packages/material-parser/test/fixtures/antd-component/components/alert/demo/custom-icon.md
vendored
Normal file
61
packages/material-parser/test/fixtures/antd-component/components/alert/demo/custom-icon.md
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
order: 12
|
||||||
|
debug: true
|
||||||
|
title:
|
||||||
|
zh-CN: 自定义图标
|
||||||
|
en-US: Custom Icon
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
可口的图标让信息类型更加醒目。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
A relevant icon makes information clearer and more friendly.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
import { SmileOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const icon = <SmileOutlined />;
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div>
|
||||||
|
<Alert icon={icon} message="showIcon = false" type="success" />
|
||||||
|
<Alert icon={icon} message="Success Tips" type="success" showIcon />
|
||||||
|
<Alert icon={icon} message="Informational Notes" type="info" showIcon />
|
||||||
|
<Alert icon={icon} message="Warning" type="warning" showIcon />
|
||||||
|
<Alert icon={icon} message="Error" type="error" showIcon />
|
||||||
|
<Alert
|
||||||
|
icon={icon}
|
||||||
|
message="Success Tips"
|
||||||
|
description="Detailed description and advices about successful copywriting."
|
||||||
|
type="success"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
icon={icon}
|
||||||
|
message="Informational Notes"
|
||||||
|
description="Additional description and informations about copywriting."
|
||||||
|
type="info"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
icon={icon}
|
||||||
|
message="Warning"
|
||||||
|
description="This is a warning notice about copywriting."
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
icon={icon}
|
||||||
|
message="Error"
|
||||||
|
description="This is an error message about copywriting."
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
44
packages/material-parser/test/fixtures/antd-component/components/alert/demo/description.md
vendored
Normal file
44
packages/material-parser/test/fixtures/antd-component/components/alert/demo/description.md
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
order: 3
|
||||||
|
title:
|
||||||
|
zh-CN: 含有辅助性文字介绍
|
||||||
|
en-US: Description
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
含有辅助性文字介绍的警告提示。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Additional description for alert message.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div>
|
||||||
|
<Alert
|
||||||
|
message="Success Text"
|
||||||
|
description="Success Description Success Description Success Description"
|
||||||
|
type="success"
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
message="Info Text"
|
||||||
|
description="Info Description Info Description Info Description Info Description"
|
||||||
|
type="info"
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
message="Warning Text"
|
||||||
|
description="Warning Description Warning Description Warning Description Warning Description"
|
||||||
|
type="warning"
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
message="Error Text"
|
||||||
|
description="Error Description Error Description Error Description Error Description"
|
||||||
|
type="error"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
43
packages/material-parser/test/fixtures/antd-component/components/alert/demo/error-boundary.md
vendored
Normal file
43
packages/material-parser/test/fixtures/antd-component/components/alert/demo/error-boundary.md
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
order: 8
|
||||||
|
title:
|
||||||
|
zh-CN: ErrorBoundary
|
||||||
|
en-US: React 错误处理
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
友好的 [React 错误处理](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) 包裹组件。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
ErrorBoundary Component for making error handling easier in [React](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button, Alert } from 'antd';
|
||||||
|
|
||||||
|
const { ErrorBoundary } = Alert;
|
||||||
|
const ThrowError: React.FC = () => {
|
||||||
|
const [error, setError] = useState<Error>();
|
||||||
|
const onClick = () => {
|
||||||
|
setError(new Error('An Uncaught Error'));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button type="danger" onClick={onClick}>
|
||||||
|
Click me to throw a error
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ThrowError />
|
||||||
|
</ErrorBoundary>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
52
packages/material-parser/test/fixtures/antd-component/components/alert/demo/icon.md
vendored
Normal file
52
packages/material-parser/test/fixtures/antd-component/components/alert/demo/icon.md
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
order: 4
|
||||||
|
title:
|
||||||
|
zh-CN: 图标
|
||||||
|
en-US: Icon
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
可口的图标让信息类型更加醒目。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
A relevant icon will make information clearer and more friendly.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div>
|
||||||
|
<Alert message="Success Tips" type="success" showIcon />
|
||||||
|
<Alert message="Informational Notes" type="info" showIcon />
|
||||||
|
<Alert message="Warning" type="warning" showIcon />
|
||||||
|
<Alert message="Error" type="error" showIcon />
|
||||||
|
<Alert
|
||||||
|
message="Success Tips"
|
||||||
|
description="Detailed description and advice about successful copywriting."
|
||||||
|
type="success"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
message="Informational Notes"
|
||||||
|
description="Additional description and information about copywriting."
|
||||||
|
type="info"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
message="Warning"
|
||||||
|
description="This is a warning notice about copywriting."
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
message="Error"
|
||||||
|
description="This is an error message about copywriting."
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
34
packages/material-parser/test/fixtures/antd-component/components/alert/demo/loop-banner.md
vendored
Normal file
34
packages/material-parser/test/fixtures/antd-component/components/alert/demo/loop-banner.md
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
order: 6.1
|
||||||
|
title:
|
||||||
|
zh-CN: 轮播的公告
|
||||||
|
en-US: Loop Banner
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
配合 [react-text-loop](https://npmjs.com/package/react-text-loop) 实现消息轮播通知栏。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Show a loop banner by using with [react-text-loop](https://npmjs.com/package/react-text-loop).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
import TextLoop from 'react-text-loop';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Alert
|
||||||
|
banner
|
||||||
|
message={
|
||||||
|
<TextLoop mask>
|
||||||
|
<div>Notice message one</div>
|
||||||
|
<div>Notice message two</div>
|
||||||
|
<div>Notice message three</div>
|
||||||
|
<div>Notice message four</div>
|
||||||
|
</TextLoop>
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
36
packages/material-parser/test/fixtures/antd-component/components/alert/demo/smooth-closed.md
vendored
Normal file
36
packages/material-parser/test/fixtures/antd-component/components/alert/demo/smooth-closed.md
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
order: 7
|
||||||
|
title:
|
||||||
|
zh-CN: 平滑地卸载
|
||||||
|
en-US: Smoothly Unmount
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
平滑、自然的卸载提示。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Smoothly unmount Alert upon close.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const [visible, setVisible] = useState(true);
|
||||||
|
const handleClose = () => {
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{visible ? (
|
||||||
|
<Alert message="Alert Message Text" type="success" closable afterClose={handleClose} />
|
||||||
|
) : null}
|
||||||
|
<p>placeholder text here</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, mountNode);
|
||||||
|
```
|
||||||
28
packages/material-parser/test/fixtures/antd-component/components/alert/demo/style.md
vendored
Normal file
28
packages/material-parser/test/fixtures/antd-component/components/alert/demo/style.md
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
order: 1
|
||||||
|
title:
|
||||||
|
zh-CN: 四种样式
|
||||||
|
en-US: More types
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
共有四种样式 `success`、`info`、`warning`、`error`。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
There are 4 types of Alert: `success`, `info`, `warning`, `error`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div>
|
||||||
|
<Alert message="Success Text" type="success" />
|
||||||
|
<Alert message="Info Text" type="info" />
|
||||||
|
<Alert message="Warning Text" type="warning" />
|
||||||
|
<Alert message="Error Text" type="error" />
|
||||||
|
</div>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
||||||
34
packages/material-parser/test/fixtures/antd-component/components/alert/index.en-US.md
vendored
Normal file
34
packages/material-parser/test/fixtures/antd-component/components/alert/index.en-US.md
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
category: Components
|
||||||
|
type: Feedback
|
||||||
|
title: Alert
|
||||||
|
---
|
||||||
|
|
||||||
|
Alert component for feedback.
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
- When you need to show alert messages to users.
|
||||||
|
- When you need a persistent static container which is closable by user actions.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| afterClose | Called when close animation is finished | () => void | - |
|
||||||
|
| banner | Whether to show as banner | boolean | false |
|
||||||
|
| closable | Whether Alert can be closed | boolean | - |
|
||||||
|
| closeText | Close text to show | string\|ReactNode | - |
|
||||||
|
| description | Additional content of Alert | string\|ReactNode | - |
|
||||||
|
| icon | Custom icon, effective when `showIcon` is `true` | ReactNode | - |
|
||||||
|
| message | Content of Alert | string\|ReactNode | - |
|
||||||
|
| showIcon | Whether to show icon | boolean | false, in `banner` mode default is true |
|
||||||
|
| type | Type of Alert styles, options: `success`, `info`, `warning`, `error` | string | `info`, in `banner` mode default is `warning` |
|
||||||
|
| onClose | Callback when Alert is closed | (e: MouseEvent) => void | - |
|
||||||
|
|
||||||
|
### Alert.ErrorBoundary
|
||||||
|
|
||||||
|
| Property | Description | Type | Default | Version |
|
||||||
|
| ----------- | -------------------------------- | --------- | ------------------- | ------- |
|
||||||
|
| message | custom error message to show | ReactNode | `{{ error }}` | |
|
||||||
|
| description | custom error description to show | ReactNode | `{{ error stack }}` | |
|
||||||
203
packages/material-parser/test/fixtures/antd-component/components/alert/index.tsx
vendored
Executable file
203
packages/material-parser/test/fixtures/antd-component/components/alert/index.tsx
vendored
Executable file
@ -0,0 +1,203 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDOM from 'react-dom';
|
||||||
|
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||||
|
import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
|
||||||
|
import ExclamationCircleOutlined from '@ant-design/icons/ExclamationCircleOutlined';
|
||||||
|
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined';
|
||||||
|
import CloseCircleOutlined from '@ant-design/icons/CloseCircleOutlined';
|
||||||
|
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
|
||||||
|
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
|
||||||
|
import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
|
||||||
|
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
|
||||||
|
import Animate from 'rc-animate';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||||
|
import getDataOrAriaProps from '../_util/getDataOrAriaProps';
|
||||||
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
export interface AlertProps {
|
||||||
|
/**
|
||||||
|
* Type of Alert styles, options:`success`, `info`, `warning`, `error`
|
||||||
|
*/
|
||||||
|
type?: 'success' | 'info' | 'warning' | 'error';
|
||||||
|
/** Whether Alert can be closed */
|
||||||
|
closable?: boolean;
|
||||||
|
/** Close text to show */
|
||||||
|
closeText?: React.ReactNode;
|
||||||
|
/** Content of Alert */
|
||||||
|
message: React.ReactNode;
|
||||||
|
/** Additional content of Alert */
|
||||||
|
description?: React.ReactNode;
|
||||||
|
/** Callback when close Alert */
|
||||||
|
onClose?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
/** Trigger when animation ending of Alert */
|
||||||
|
afterClose?: () => void;
|
||||||
|
/** Whether to show icon */
|
||||||
|
showIcon?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
prefixCls?: string;
|
||||||
|
className?: string;
|
||||||
|
banner?: boolean;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||||
|
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
||||||
|
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlertState {
|
||||||
|
closing: boolean;
|
||||||
|
closed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconMapFilled = {
|
||||||
|
success: CheckCircleFilled,
|
||||||
|
info: InfoCircleFilled,
|
||||||
|
error: CloseCircleFilled,
|
||||||
|
warning: ExclamationCircleFilled,
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconMapOutlined = {
|
||||||
|
success: CheckCircleOutlined,
|
||||||
|
info: InfoCircleOutlined,
|
||||||
|
error: CloseCircleOutlined,
|
||||||
|
warning: ExclamationCircleOutlined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Alert extends React.Component<AlertProps, AlertState> {
|
||||||
|
static ErrorBoundary = ErrorBoundary;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
closing: false,
|
||||||
|
closed: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClose = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const dom = ReactDOM.findDOMNode(this) as HTMLElement;
|
||||||
|
dom.style.height = `${dom.offsetHeight}px`;
|
||||||
|
// Magic code
|
||||||
|
// 重复一次后才能正确设置 height
|
||||||
|
dom.style.height = `${dom.offsetHeight}px`;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
closing: true,
|
||||||
|
});
|
||||||
|
(this.props.onClose || noop)(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
animationEnd = () => {
|
||||||
|
this.setState({
|
||||||
|
closing: false,
|
||||||
|
closed: true,
|
||||||
|
});
|
||||||
|
(this.props.afterClose || noop)();
|
||||||
|
};
|
||||||
|
|
||||||
|
renderAlert = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||||
|
const {
|
||||||
|
description,
|
||||||
|
prefixCls: customizePrefixCls,
|
||||||
|
message,
|
||||||
|
closeText,
|
||||||
|
banner,
|
||||||
|
className = '',
|
||||||
|
style,
|
||||||
|
icon,
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave,
|
||||||
|
onClick,
|
||||||
|
} = this.props;
|
||||||
|
let { closable, type, showIcon } = this.props;
|
||||||
|
const { closing, closed } = this.state;
|
||||||
|
|
||||||
|
const prefixCls = getPrefixCls('alert', customizePrefixCls);
|
||||||
|
|
||||||
|
// banner模式默认有 Icon
|
||||||
|
showIcon = banner && showIcon === undefined ? true : showIcon;
|
||||||
|
// banner模式默认为警告
|
||||||
|
type = banner && type === undefined ? 'warning' : type || 'info';
|
||||||
|
|
||||||
|
// use outline icon in alert with description
|
||||||
|
const iconType = (description ? iconMapOutlined : iconMapFilled)[type] || null;
|
||||||
|
|
||||||
|
// closeable when closeText is assigned
|
||||||
|
if (closeText) {
|
||||||
|
closable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const alertCls = classNames(
|
||||||
|
prefixCls,
|
||||||
|
`${prefixCls}-${type}`,
|
||||||
|
{
|
||||||
|
[`${prefixCls}-closing`]: closing,
|
||||||
|
[`${prefixCls}-with-description`]: !!description,
|
||||||
|
[`${prefixCls}-no-icon`]: !showIcon,
|
||||||
|
[`${prefixCls}-banner`]: !!banner,
|
||||||
|
[`${prefixCls}-closable`]: closable,
|
||||||
|
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||||
|
},
|
||||||
|
className,
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeIcon = closable ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={this.handleClose}
|
||||||
|
className={`${prefixCls}-close-icon`}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
{closeText ? (
|
||||||
|
<span className={`${prefixCls}-close-text`}>{closeText}</span>
|
||||||
|
) : (
|
||||||
|
<CloseOutlined />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const dataOrAriaProps = getDataOrAriaProps(this.props);
|
||||||
|
|
||||||
|
const iconNode =
|
||||||
|
(icon &&
|
||||||
|
(React.isValidElement<{ className?: string }>(icon) ? (
|
||||||
|
React.cloneElement(icon, {
|
||||||
|
className: classNames(`${prefixCls}-icon`, {
|
||||||
|
[icon.props.className as string]: icon.props.className,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<span className={`${prefixCls}-icon`}>{icon}</span>
|
||||||
|
))) ||
|
||||||
|
React.createElement(iconType, { className: `${prefixCls}-icon` });
|
||||||
|
|
||||||
|
return closed ? null : (
|
||||||
|
<Animate
|
||||||
|
component=""
|
||||||
|
showProp="data-show"
|
||||||
|
transitionName={`${prefixCls}-slide-up`}
|
||||||
|
onEnd={this.animationEnd}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-show={!closing}
|
||||||
|
className={alertCls}
|
||||||
|
style={style}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onClick={onClick}
|
||||||
|
{...dataOrAriaProps}
|
||||||
|
>
|
||||||
|
{showIcon ? iconNode : null}
|
||||||
|
<span className={`${prefixCls}-message`}>{message}</span>
|
||||||
|
<span className={`${prefixCls}-description`}>{description}</span>
|
||||||
|
{closeIcon}
|
||||||
|
</div>
|
||||||
|
</Animate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <ConfigConsumer>{this.renderAlert}</ConfigConsumer>;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
packages/material-parser/test/fixtures/antd-component/components/alert/index.zh-CN.md
vendored
Normal file
35
packages/material-parser/test/fixtures/antd-component/components/alert/index.zh-CN.md
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
category: Components
|
||||||
|
subtitle: 警告提示
|
||||||
|
type: 反馈
|
||||||
|
title: Alert
|
||||||
|
---
|
||||||
|
|
||||||
|
警告提示,展现需要关注的信息。
|
||||||
|
|
||||||
|
## 何时使用
|
||||||
|
|
||||||
|
- 当某个页面需要向用户显示警告的信息时。
|
||||||
|
- 非浮层的静态展现形式,始终展现,不会自动消失,用户可以点击关闭。
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| afterClose | 关闭动画结束后触发的回调函数 | () => void | - |
|
||||||
|
| banner | 是否用作顶部公告 | boolean | false |
|
||||||
|
| closable | 默认不显示关闭按钮 | boolean | 无 |
|
||||||
|
| closeText | 自定义关闭按钮 | string\|ReactNode | 无 |
|
||||||
|
| description | 警告提示的辅助性文字介绍 | string\|ReactNode | 无 |
|
||||||
|
| icon | 自定义图标,`showIcon` 为 `true` 时有效 | ReactNode | - |
|
||||||
|
| message | 警告提示内容 | string\|ReactNode | 无 |
|
||||||
|
| showIcon | 是否显示辅助图标 | boolean | false,`banner` 模式下默认值为 true |
|
||||||
|
| type | 指定警告提示的样式,有四种选择 `success`、`info`、`warning`、`error` | string | `info`,`banner` 模式下默认值为 `warning` |
|
||||||
|
| onClose | 关闭时触发的回调函数 | (e: MouseEvent) => void | 无 |
|
||||||
|
|
||||||
|
### Alert.ErrorBoundary
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| message | 自定义错误标题,如果未指定会展示原生报错信息 | ReactNode | `{{ error }}` | |
|
||||||
|
| description | 自定义错误内容,如果未指定会展示报错堆栈 | ReactNode | `{{ error stack }}` | |
|
||||||
191
packages/material-parser/test/fixtures/antd-component/components/alert/style/index.less
vendored
Normal file
191
packages/material-parser/test/fixtures/antd-component/components/alert/style/index.less
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
@import '../../style/themes/index';
|
||||||
|
@import '../../style/mixins/index';
|
||||||
|
|
||||||
|
@alert-prefix-cls: ~'@{ant-prefix}-alert';
|
||||||
|
|
||||||
|
.@{alert-prefix-cls} {
|
||||||
|
.reset-component;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
padding: 8px 15px 8px 37px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
|
||||||
|
&&-no-icon {
|
||||||
|
padding: @alert-no-icon-padding-vertical 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&&-closable {
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px + @font-size-base * @line-height-base / 2 - @font-size-base / 2;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-description {
|
||||||
|
display: none;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-success {
|
||||||
|
background-color: @alert-success-bg-color;
|
||||||
|
border: @border-width-base @border-style-base @alert-success-border-color;
|
||||||
|
.@{alert-prefix-cls}-icon {
|
||||||
|
color: @alert-success-icon-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info {
|
||||||
|
background-color: @alert-info-bg-color;
|
||||||
|
border: @border-width-base @border-style-base @alert-info-border-color;
|
||||||
|
.@{alert-prefix-cls}-icon {
|
||||||
|
color: @alert-info-icon-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-warning {
|
||||||
|
background-color: @alert-warning-bg-color;
|
||||||
|
border: @border-width-base @border-style-base @alert-warning-border-color;
|
||||||
|
.@{alert-prefix-cls}-icon {
|
||||||
|
color: @alert-warning-icon-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
background-color: @alert-error-bg-color;
|
||||||
|
border: @border-width-base @border-style-base @alert-error-border-color;
|
||||||
|
|
||||||
|
.@{alert-prefix-cls}-icon {
|
||||||
|
color: @alert-error-icon-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{alert-prefix-cls}-description > pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-close-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: @padding-xs;
|
||||||
|
right: 16px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
line-height: 22px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.@{iconfont-css-prefix}-close {
|
||||||
|
color: @alert-close-color;
|
||||||
|
transition: color 0.3s;
|
||||||
|
&:hover {
|
||||||
|
color: @alert-close-hover-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-close-text {
|
||||||
|
color: @alert-close-color;
|
||||||
|
transition: color 0.3s;
|
||||||
|
&:hover {
|
||||||
|
color: @alert-close-hover-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description {
|
||||||
|
position: relative;
|
||||||
|
padding: 15px 15px 15px 64px;
|
||||||
|
color: @alert-text-color;
|
||||||
|
line-height: @line-height-base;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description&-no-icon {
|
||||||
|
padding: @alert-with-description-no-icon-padding-vertical 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description &-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 24px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description &-close-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description &-message {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: @alert-message-color;
|
||||||
|
font-size: @font-size-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-message {
|
||||||
|
color: @alert-message-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description &-description {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&&-closing {
|
||||||
|
height: 0 !important;
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
transform-origin: 50% 0;
|
||||||
|
transition: all 0.3s @ease-in-out-circ;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-slide-up-leave {
|
||||||
|
animation: antAlertSlideUpOut 0.3s @ease-in-out-circ;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-banner {
|
||||||
|
margin-bottom: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antAlertSlideUpIn {
|
||||||
|
0% {
|
||||||
|
transform: scaleY(0);
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scaleY(1);
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antAlertSlideUpOut {
|
||||||
|
0% {
|
||||||
|
transform: scaleY(1);
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scaleY(0);
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@import './rtl.less';
|
||||||
2
packages/material-parser/test/fixtures/antd-component/components/alert/style/index.tsx
vendored
Normal file
2
packages/material-parser/test/fixtures/antd-component/components/alert/style/index.tsx
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import '../../style/index.less';
|
||||||
|
import './index.less';
|
||||||
58
packages/material-parser/test/fixtures/antd-component/components/alert/style/rtl.less
vendored
Normal file
58
packages/material-parser/test/fixtures/antd-component/components/alert/style/rtl.less
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
@import '../../style/themes/index';
|
||||||
|
@import '../../style/mixins/index';
|
||||||
|
|
||||||
|
@alert-prefix-cls: ~'@{ant-prefix}-alert';
|
||||||
|
|
||||||
|
.@{alert-prefix-cls} {
|
||||||
|
&-rtl {
|
||||||
|
padding: 8px 37px 8px 15px;
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
&&-closable {
|
||||||
|
.@{alert-prefix-cls}-rtl& {
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
.@{alert-prefix-cls}-rtl & {
|
||||||
|
right: 16px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-close-icon {
|
||||||
|
.@{alert-prefix-cls}-rtl & {
|
||||||
|
right: auto;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description {
|
||||||
|
.@{alert-prefix-cls}-rtl& {
|
||||||
|
padding: 15px 64px 15px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description&-no-icon {
|
||||||
|
.@{alert-prefix-cls}-rtl& {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description &-icon {
|
||||||
|
.@{alert-prefix-cls}-rtl& {
|
||||||
|
right: 24px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-description &-close-icon {
|
||||||
|
.@{alert-prefix-cls}-rtl& {
|
||||||
|
right: auto;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
328
packages/material-parser/test/fixtures/antd-component/components/anchor/Anchor.tsx
vendored
Normal file
328
packages/material-parser/test/fixtures/antd-component/components/anchor/Anchor.tsx
vendored
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDOM from 'react-dom';
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import addEventListener from 'rc-util/lib/Dom/addEventListener';
|
||||||
|
import Affix from '../affix';
|
||||||
|
import AnchorLink from './AnchorLink';
|
||||||
|
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||||
|
import scrollTo from '../_util/scrollTo';
|
||||||
|
import getScroll from '../_util/getScroll';
|
||||||
|
|
||||||
|
function getDefaultContainer() {
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOffsetTop(element: HTMLElement, container: AnchorContainer): number {
|
||||||
|
if (!element) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!element.getClientRects().length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (rect.width || rect.height) {
|
||||||
|
if (container === window) {
|
||||||
|
container = element.ownerDocument!.documentElement!;
|
||||||
|
return rect.top - container.clientTop;
|
||||||
|
}
|
||||||
|
return rect.top - (container as HTMLElement).getBoundingClientRect().top;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rect.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharpMatcherRegx = /#([^#]+)$/;
|
||||||
|
|
||||||
|
type Section = {
|
||||||
|
link: string;
|
||||||
|
top: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AnchorContainer = HTMLElement | Window;
|
||||||
|
|
||||||
|
interface AnchorProps {
|
||||||
|
prefixCls?: string;
|
||||||
|
// className?: string;
|
||||||
|
// style?: React.CSSProperties;
|
||||||
|
// children?: React.ReactNode;
|
||||||
|
// offsetTop?: number;
|
||||||
|
// bounds?: number;
|
||||||
|
// affix?: boolean;
|
||||||
|
// showInkInFixed?: boolean;
|
||||||
|
// getContainer?: () => AnchorContainer;
|
||||||
|
// /** Return customize highlight anchor */
|
||||||
|
// getCurrentAnchor?: () => string;
|
||||||
|
// onClick?: (e: React.MouseEvent<HTMLElement>, link: { title: React.ReactNode; href: string }) => void;
|
||||||
|
// /** Scroll to target offset value, if none, it's offsetTop prop value or 0. */
|
||||||
|
// targetOffset?: number;
|
||||||
|
// /** Listening event when scrolling change active link */
|
||||||
|
// onChange?: (currentActiveLink: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnchorState {
|
||||||
|
activeLink: null | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnchorDefaultProps extends AnchorProps {
|
||||||
|
prefixCls: string;
|
||||||
|
affix: boolean;
|
||||||
|
showInkInFixed: boolean;
|
||||||
|
getContainer: () => AnchorContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AntAnchor {
|
||||||
|
registerLink: (link: string) => void;
|
||||||
|
unregisterLink: (link: string) => void;
|
||||||
|
activeLink: string | null;
|
||||||
|
scrollTo: (link: string) => void;
|
||||||
|
onClick?: (e: React.MouseEvent<HTMLElement>, link: { title: React.ReactNode; href: string }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||||
|
static Link: typeof AnchorLink;
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
affix: true,
|
||||||
|
showInkInFixed: false,
|
||||||
|
getContainer: getDefaultContainer,
|
||||||
|
};
|
||||||
|
|
||||||
|
static childContextTypes = {
|
||||||
|
antAnchor: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
activeLink: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
private inkNode: HTMLSpanElement;
|
||||||
|
|
||||||
|
// scroll scope's container
|
||||||
|
private scrollContainer: HTMLElement | Window;
|
||||||
|
|
||||||
|
private links: string[] = [];
|
||||||
|
|
||||||
|
private scrollEvent: any;
|
||||||
|
|
||||||
|
private animating: boolean;
|
||||||
|
|
||||||
|
private prefixCls?: string;
|
||||||
|
|
||||||
|
getChildContext() {
|
||||||
|
const antAnchor: AntAnchor = {
|
||||||
|
registerLink: (link: string) => {
|
||||||
|
if (!this.links.includes(link)) {
|
||||||
|
this.links.push(link);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unregisterLink: (link: string) => {
|
||||||
|
const index = this.links.indexOf(link);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.links.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activeLink: this.state.activeLink,
|
||||||
|
scrollTo: this.handleScrollTo,
|
||||||
|
onClick: this.props.onClick,
|
||||||
|
};
|
||||||
|
return { antAnchor };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { getContainer } = this.props as AnchorDefaultProps;
|
||||||
|
this.scrollContainer = getContainer();
|
||||||
|
this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll);
|
||||||
|
this.handleScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (this.scrollEvent) {
|
||||||
|
const { getContainer } = this.props as AnchorDefaultProps;
|
||||||
|
const currentContainer = getContainer();
|
||||||
|
if (this.scrollContainer !== currentContainer) {
|
||||||
|
this.scrollContainer = currentContainer;
|
||||||
|
this.scrollEvent.remove();
|
||||||
|
this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll);
|
||||||
|
this.handleScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateInk();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.scrollEvent) {
|
||||||
|
this.scrollEvent.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentAnchor(offsetTop = 0, bounds = 5): string {
|
||||||
|
const { getCurrentAnchor } = this.props;
|
||||||
|
|
||||||
|
if (typeof getCurrentAnchor === 'function') {
|
||||||
|
return getCurrentAnchor();
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeLink = '';
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return activeLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkSections: Array<Section> = [];
|
||||||
|
const { getContainer } = this.props as AnchorDefaultProps;
|
||||||
|
const container = getContainer();
|
||||||
|
this.links.forEach((link) => {
|
||||||
|
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
|
||||||
|
if (!sharpLinkMatch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const target = document.getElementById(sharpLinkMatch[1]);
|
||||||
|
if (target) {
|
||||||
|
const top = getOffsetTop(target, container);
|
||||||
|
if (top < offsetTop + bounds) {
|
||||||
|
linkSections.push({
|
||||||
|
link,
|
||||||
|
top,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (linkSections.length) {
|
||||||
|
const maxSection = linkSections.reduce((prev, curr) => (curr.top > prev.top ? curr : prev));
|
||||||
|
return maxSection.link;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScrollTo = (link: string) => {
|
||||||
|
const { offsetTop, getContainer, targetOffset } = this.props as AnchorDefaultProps;
|
||||||
|
|
||||||
|
this.setCurrentActiveLink(link);
|
||||||
|
const container = getContainer();
|
||||||
|
const scrollTop = getScroll(container, true);
|
||||||
|
const sharpLinkMatch = sharpMatcherRegx.exec(link);
|
||||||
|
if (!sharpLinkMatch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetElement = document.getElementById(sharpLinkMatch[1]);
|
||||||
|
if (!targetElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eleOffsetTop = getOffsetTop(targetElement, container);
|
||||||
|
let y = scrollTop + eleOffsetTop;
|
||||||
|
y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
|
||||||
|
this.animating = true;
|
||||||
|
|
||||||
|
scrollTo(y, {
|
||||||
|
callback: () => {
|
||||||
|
this.animating = false;
|
||||||
|
},
|
||||||
|
getContainer,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
saveInkNode = (node: HTMLSpanElement) => {
|
||||||
|
this.inkNode = node;
|
||||||
|
};
|
||||||
|
|
||||||
|
setCurrentActiveLink = (link: string) => {
|
||||||
|
const { activeLink } = this.state;
|
||||||
|
const { onChange } = this.props;
|
||||||
|
|
||||||
|
if (activeLink !== link) {
|
||||||
|
this.setState({
|
||||||
|
activeLink: link,
|
||||||
|
});
|
||||||
|
if (onChange) {
|
||||||
|
onChange(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleScroll = () => {
|
||||||
|
if (this.animating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { offsetTop, bounds, targetOffset } = this.props;
|
||||||
|
const currentActiveLink = this.getCurrentAnchor(targetOffset !== undefined ? targetOffset : offsetTop || 0, bounds);
|
||||||
|
this.setCurrentActiveLink(currentActiveLink);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateInk = () => {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { prefixCls } = this;
|
||||||
|
const anchorNode = ReactDOM.findDOMNode(this) as Element;
|
||||||
|
const linkNode = anchorNode.getElementsByClassName(`${prefixCls}-link-title-active`)[0];
|
||||||
|
if (linkNode) {
|
||||||
|
this.inkNode.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderAnchor = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||||
|
const {
|
||||||
|
prefixCls: customizePrefixCls,
|
||||||
|
className = '',
|
||||||
|
style,
|
||||||
|
offsetTop,
|
||||||
|
affix,
|
||||||
|
showInkInFixed,
|
||||||
|
children,
|
||||||
|
getContainer,
|
||||||
|
} = this.props;
|
||||||
|
const { activeLink } = this.state;
|
||||||
|
|
||||||
|
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
||||||
|
|
||||||
|
// To support old version react.
|
||||||
|
// Have to add prefixCls on the instance.
|
||||||
|
// https://github.com/facebook/react/issues/12397
|
||||||
|
this.prefixCls = prefixCls;
|
||||||
|
|
||||||
|
const inkClass = classNames(`${prefixCls}-ink-ball`, {
|
||||||
|
visible: activeLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapperClass = classNames(className, `${prefixCls}-wrapper`, {
|
||||||
|
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||||
|
});
|
||||||
|
|
||||||
|
const anchorClass = classNames(prefixCls, {
|
||||||
|
fixed: !affix && !showInkInFixed,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapperStyle = {
|
||||||
|
maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
|
||||||
|
...style,
|
||||||
|
};
|
||||||
|
|
||||||
|
const anchorContent = (
|
||||||
|
<div className={wrapperClass} style={wrapperStyle}>
|
||||||
|
<div className={anchorClass}>
|
||||||
|
<div className={`${prefixCls}-ink`}>
|
||||||
|
<span className={inkClass} ref={this.saveInkNode} />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return !affix ? (
|
||||||
|
anchorContent
|
||||||
|
) : (
|
||||||
|
<Affix offsetTop={offsetTop} target={getContainer}>
|
||||||
|
{anchorContent}
|
||||||
|
</Affix>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <ConfigConsumer>{this.renderAnchor}</ConfigConsumer>;
|
||||||
|
}
|
||||||
|
}
|
||||||
85
packages/material-parser/test/fixtures/antd-component/components/anchor/AnchorLink.tsx
vendored
Normal file
85
packages/material-parser/test/fixtures/antd-component/components/anchor/AnchorLink.tsx
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { AntAnchor } from './Anchor';
|
||||||
|
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||||
|
|
||||||
|
interface AnchorLinkProps {
|
||||||
|
prefixCls?: string;
|
||||||
|
href: string;
|
||||||
|
target?: string;
|
||||||
|
title: React.ReactNode;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnchorLink extends React.Component<AnchorLinkProps, any> {
|
||||||
|
static defaultProps = {
|
||||||
|
href: '#',
|
||||||
|
};
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
antAnchor: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
context: {
|
||||||
|
antAnchor: AntAnchor;
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.context.antAnchor.registerLink(this.props.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate({ href: prevHref }: AnchorLinkProps) {
|
||||||
|
const { href } = this.props;
|
||||||
|
if (prevHref !== href) {
|
||||||
|
this.context.antAnchor.unregisterLink(prevHref);
|
||||||
|
this.context.antAnchor.registerLink(href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.context.antAnchor.unregisterLink(this.props.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
const { scrollTo, onClick } = this.context.antAnchor;
|
||||||
|
const { href, title } = this.props;
|
||||||
|
if (onClick) {
|
||||||
|
onClick(e, { title, href });
|
||||||
|
}
|
||||||
|
scrollTo(href);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderAnchorLink = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||||
|
const { prefixCls: customizePrefixCls, href, title, children, className, target } = this.props;
|
||||||
|
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
||||||
|
const active = this.context.antAnchor.activeLink === href;
|
||||||
|
const wrapperClassName = classNames(className, `${prefixCls}-link`, {
|
||||||
|
[`${prefixCls}-link-active`]: active,
|
||||||
|
});
|
||||||
|
const titleClassName = classNames(`${prefixCls}-link-title`, {
|
||||||
|
[`${prefixCls}-link-title-active`]: active,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className={wrapperClassName}>
|
||||||
|
<a
|
||||||
|
className={titleClassName}
|
||||||
|
href={href}
|
||||||
|
title={typeof title === 'string' ? title : ''}
|
||||||
|
target={target}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</a>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <ConfigConsumer>{this.renderAnchorLink}</ConfigConsumer>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnchorLink;
|
||||||
336
packages/material-parser/test/fixtures/antd-component/components/anchor/__tests__/Anchor.test.js
vendored
Normal file
336
packages/material-parser/test/fixtures/antd-component/components/anchor/__tests__/Anchor.test.js
vendored
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import Anchor from '..';
|
||||||
|
import { spyElementPrototypes } from '../../__tests__/util/domHook';
|
||||||
|
import { sleep } from '../../../tests/utils';
|
||||||
|
|
||||||
|
const { Link } = Anchor;
|
||||||
|
|
||||||
|
describe('Anchor Render', () => {
|
||||||
|
const getBoundingClientRectMock = jest.fn(() => ({
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
top: 1000,
|
||||||
|
}));
|
||||||
|
const getClientRectsMock = jest.fn(() => ({
|
||||||
|
length: 1,
|
||||||
|
}));
|
||||||
|
const headingSpy = spyElementPrototypes(HTMLHeadingElement, {
|
||||||
|
getBoundingClientRect: getBoundingClientRectMock,
|
||||||
|
getClientRects: getClientRectsMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
headingSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor render perfectly', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor>
|
||||||
|
<Link href="#API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find('a[href="#API"]').simulate('click');
|
||||||
|
|
||||||
|
wrapper.instance().handleScroll();
|
||||||
|
expect(wrapper.instance().state).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor render perfectly for complete href - click', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor>
|
||||||
|
<Link href="http://www.example.com/#API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
wrapper.find('a[href="http://www.example.com/#API"]').simulate('click');
|
||||||
|
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor render perfectly for complete href - scroll', () => {
|
||||||
|
let root = document.getElementById('root');
|
||||||
|
if (!root) {
|
||||||
|
root = document.createElement('div', { id: 'root' });
|
||||||
|
root.id = 'root';
|
||||||
|
document.body.appendChild(root);
|
||||||
|
}
|
||||||
|
mount(<div id="API">Hello</div>, { attachTo: root });
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor>
|
||||||
|
<Link href="http://www.example.com/#API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
wrapper.instance().handleScroll();
|
||||||
|
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor render perfectly for complete href - scrollTo', async () => {
|
||||||
|
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||||
|
let root = document.getElementById('root');
|
||||||
|
if (!root) {
|
||||||
|
root = document.createElement('div', { id: 'root' });
|
||||||
|
root.id = 'root';
|
||||||
|
document.body.appendChild(root);
|
||||||
|
}
|
||||||
|
mount(<div id="API">Hello</div>, { attachTo: root });
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor>
|
||||||
|
<Link href="##API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
wrapper.instance().handleScrollTo('##API');
|
||||||
|
expect(wrapper.instance().state.activeLink).toBe('##API');
|
||||||
|
expect(scrollToSpy).not.toHaveBeenCalled();
|
||||||
|
await sleep(1000);
|
||||||
|
expect(scrollToSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove listener when unmount', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor>
|
||||||
|
<Link href="#API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
|
||||||
|
wrapper.unmount();
|
||||||
|
expect(removeListenerSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unregister link when unmount children', async () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor>
|
||||||
|
<Link href="#API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
expect(wrapper.instance().links).toEqual(['#API']);
|
||||||
|
wrapper.setProps({ children: null });
|
||||||
|
expect(wrapper.instance().links).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update links when link href update', async () => {
|
||||||
|
let anchorInstance = null;
|
||||||
|
function AnchorUpdate({ href }) {
|
||||||
|
return (
|
||||||
|
<Anchor
|
||||||
|
ref={c => {
|
||||||
|
anchorInstance = c;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link href={href} title="API" />
|
||||||
|
</Anchor>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const wrapper = mount(<AnchorUpdate href="#API" />);
|
||||||
|
|
||||||
|
expect(anchorInstance.links).toEqual(['#API']);
|
||||||
|
wrapper.setProps({ href: '#API_1' });
|
||||||
|
expect(anchorInstance.links).toEqual(['#API_1']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor onClick event', () => {
|
||||||
|
let event;
|
||||||
|
let link;
|
||||||
|
const handleClick = (...arg) => {
|
||||||
|
[event, link] = arg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const href = '#API';
|
||||||
|
const title = 'API';
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor onClick={handleClick}>
|
||||||
|
<Link href={href} title={title} />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.find(`a[href="${href}"]`).simulate('click');
|
||||||
|
|
||||||
|
wrapper.instance().handleScroll();
|
||||||
|
expect(event).not.toBe(undefined);
|
||||||
|
expect(link).toEqual({ href, title });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Different function returns the same DOM', async () => {
|
||||||
|
let root = document.getElementById('root');
|
||||||
|
if (!root) {
|
||||||
|
root = document.createElement('div', { id: 'root' });
|
||||||
|
root.id = 'root';
|
||||||
|
document.body.appendChild(root);
|
||||||
|
}
|
||||||
|
mount(<div id="API">Hello</div>, { attachTo: root });
|
||||||
|
const getContainerA = () => {
|
||||||
|
return document.getElementById('API');
|
||||||
|
};
|
||||||
|
const getContainerB = () => {
|
||||||
|
return document.getElementById('API');
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor getContainer={getContainerA}>
|
||||||
|
<Link href="#API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
|
||||||
|
await sleep(1000);
|
||||||
|
wrapper.setProps({ getContainer: getContainerB });
|
||||||
|
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Different function returns different DOM', async () => {
|
||||||
|
let root = document.getElementById('root');
|
||||||
|
if (!root) {
|
||||||
|
root = document.createElement('div', { id: 'root' });
|
||||||
|
root.id = 'root';
|
||||||
|
document.body.appendChild(root);
|
||||||
|
}
|
||||||
|
mount(
|
||||||
|
<div>
|
||||||
|
<div id="API1">Hello</div>
|
||||||
|
<div id="API2">World</div>
|
||||||
|
</div>,
|
||||||
|
{ attachTo: root },
|
||||||
|
);
|
||||||
|
const getContainerA = () => {
|
||||||
|
return document.getElementById('API1');
|
||||||
|
};
|
||||||
|
const getContainerB = () => {
|
||||||
|
return document.getElementById('API2');
|
||||||
|
};
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor getContainer={getContainerA}>
|
||||||
|
<Link href="#API1" title="API1" />
|
||||||
|
<Link href="#API2" title="API2" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
|
||||||
|
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||||
|
await sleep(1000);
|
||||||
|
wrapper.setProps({ getContainer: getContainerB });
|
||||||
|
expect(removeListenerSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Same function returns the same DOM', () => {
|
||||||
|
let root = document.getElementById('root');
|
||||||
|
if (!root) {
|
||||||
|
root = document.createElement('div', { id: 'root' });
|
||||||
|
root.id = 'root';
|
||||||
|
document.body.appendChild(root);
|
||||||
|
}
|
||||||
|
mount(<div id="API">Hello</div>, { attachTo: root });
|
||||||
|
const getContainer = () => document.getElementById('API');
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor getContainer={getContainer}>
|
||||||
|
<Link href="#API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
wrapper.find('a[href="#API"]').simulate('click');
|
||||||
|
wrapper.instance().handleScroll();
|
||||||
|
expect(wrapper.instance().state).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Same function returns different DOM', async () => {
|
||||||
|
let root = document.getElementById('root');
|
||||||
|
if (!root) {
|
||||||
|
root = document.createElement('div', { id: 'root' });
|
||||||
|
root.id = 'root';
|
||||||
|
document.body.appendChild(root);
|
||||||
|
}
|
||||||
|
mount(
|
||||||
|
<div>
|
||||||
|
<div id="API1">Hello</div>
|
||||||
|
<div id="API2">World</div>
|
||||||
|
</div>,
|
||||||
|
{ attachTo: root },
|
||||||
|
);
|
||||||
|
const holdContainer = {
|
||||||
|
container: document.getElementById('API1'),
|
||||||
|
};
|
||||||
|
const getContainer = () => {
|
||||||
|
return holdContainer.container;
|
||||||
|
};
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor getContainer={getContainer}>
|
||||||
|
<Link href="#API1" title="API1" />
|
||||||
|
<Link href="#API2" title="API2" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
|
||||||
|
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||||
|
await sleep(1000);
|
||||||
|
holdContainer.container = document.getElementById('API2');
|
||||||
|
wrapper.setProps({ 'data-only-trigger-re-render': true });
|
||||||
|
expect(removeListenerSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor getCurrentAnchor prop', () => {
|
||||||
|
const getCurrentAnchor = () => '#API2';
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor getCurrentAnchor={getCurrentAnchor}>
|
||||||
|
<Link href="#API1" title="API1" />
|
||||||
|
<Link href="#API2" title="API2" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
expect(wrapper.instance().state.activeLink).toBe('#API2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor targetOffset prop', async () => {
|
||||||
|
let dateNowMock;
|
||||||
|
|
||||||
|
function dataNowMockFn() {
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
|
const handler = () => {
|
||||||
|
return (start += 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return jest.spyOn(Date, 'now').mockImplementation(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
dateNowMock = dataNowMockFn();
|
||||||
|
|
||||||
|
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||||
|
let root = document.getElementById('root');
|
||||||
|
if (!root) {
|
||||||
|
root = document.createElement('div', { id: 'root' });
|
||||||
|
root.id = 'root';
|
||||||
|
document.body.appendChild(root);
|
||||||
|
}
|
||||||
|
mount(<h1 id="API">Hello</h1>, { attachTo: root });
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor>
|
||||||
|
<Link href="#API" title="API" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
wrapper.instance().handleScrollTo('#API');
|
||||||
|
await sleep(20);
|
||||||
|
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
|
||||||
|
dateNowMock = dataNowMockFn();
|
||||||
|
|
||||||
|
wrapper.setProps({ offsetTop: 100 });
|
||||||
|
wrapper.instance().handleScrollTo('#API');
|
||||||
|
await sleep(20);
|
||||||
|
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
|
||||||
|
dateNowMock = dataNowMockFn();
|
||||||
|
|
||||||
|
wrapper.setProps({ targetOffset: 200 });
|
||||||
|
wrapper.instance().handleScrollTo('#API');
|
||||||
|
await sleep(20);
|
||||||
|
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
|
||||||
|
|
||||||
|
dateNowMock.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Anchor onChange prop', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<Anchor onChange={onChange}>
|
||||||
|
<Link href="#API1" title="API1" />
|
||||||
|
<Link href="#API2" title="API2" />
|
||||||
|
</Anchor>,
|
||||||
|
);
|
||||||
|
expect(onChange).toHaveBeenCalledTimes(1);
|
||||||
|
wrapper.instance().handleScrollTo('#API2');
|
||||||
|
expect(onChange).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onChange).toHaveBeenCalledWith('#API2');
|
||||||
|
});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user