Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
feat(react-wrapper): support event type (#425)
Browse files Browse the repository at this point in the history
This change adds type definitions for event handlers in React wrapper.
Also adds `propTypes` definitions.

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
asudoh and kodiakhq[bot] authored Aug 7, 2020
1 parent 9ec63a2 commit 9c6f01f
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 4 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ const App = () => (
render(<App />, document.getElementById('root'));
```

Note: Using the React wrapper requires an additional dependency, [`prop-types`](https://www.npmjs.com/package/prop-types).

### Vue

[![Edit carbon-web-components with Vue](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/carbon-design-system/carbon-custom-elements/tree/master/examples/codesandbox/vue)
Expand Down
1 change: 1 addition & 0 deletions examples/codesandbox/form/redux-form/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"lit-element": "~2.2.0",
"lit-html": "^1.2.0",
"lodash-es": "^4.17.0",
"prop-types": "^15.7.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-redux": "^6.0.0",
Expand Down
1 change: 1 addition & 0 deletions examples/codesandbox/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"lit-element": "~2.2.0",
"lit-html": "^1.2.0",
"lodash-es": "^4.17.0",
"prop-types": "^15.7.0",
"react": "^16.0.0",
"react-dom": "^16.12.0"
},
Expand Down
13 changes: 13 additions & 0 deletions tools/babel-plugin-create-react-custom-element-type-def.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
const { default: template } = require('@babel/template');
const { createMetadataVisitor } = require('./babel-plugin-create-react-custom-element-type');

const regexEvent = /^event/;
const regexMagicComment = /The name of the custom event/g;
const magicCommentForReact = 'The event handler for the custom event';

module.exports = function generateCreateReactCustomElementType(api) {
const { types: t } = api;
const metadataVisitor = createMetadataVisitor(api);
Expand All @@ -36,13 +40,22 @@ module.exports = function generateCreateReactCustomElementType(api) {
const { comments = [], type } = declaredProps[key];
return [...acc, comments.map(({ value }) => `/*${value}*/`).join('\n'), `${key}?: ${types[type] || 'string'};`];
}, []);
const events = Object.keys(customEvents).reduce((acc, key) => {
const { comments = [] } = customEvents[key];
return [
...acc,
comments.map(({ value }) => `/*${value.replace(regexMagicComment, magicCommentForReact)}*/`).join('\n'),
`${key.replace(regexEvent, 'on')}?: (event: CustomEvent) => void;`,
];
}, []);

const build = template(
`
import { Component } from 'react';
interface ComponentProps {
${props.join('\n')}
${events.join('\n')}
}
${classComments.map(({ value }) => `/*${value}*/`).join('\n')}
Expand Down
77 changes: 73 additions & 4 deletions tools/babel-plugin-create-react-custom-element-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,14 @@ function createMetadataVisitor(api) {
'`static get eventFoo` must have and be only with a return statement with a string literal or a template literal.'
);
}
customEvents[name] = t.cloneDeep(argument.node);
const metadata = {
eventName: t.cloneDeep(argument.node),
};
const leadingComments = path.get('leadingComments');
if (leadingComments) {
metadata.comments = (Array.isArray(leadingComments) ? leadingComments : [leadingComments]).map(item => item.node);
}
customEvents[name] = metadata;
}
},

Expand All @@ -147,7 +154,14 @@ function createMetadataVisitor(api) {
if (!value.isStringLiteral() && !value.isTemplateLiteral()) {
throw value.buildCodeFrameError('`static eventFoo` must refer to a string literal or a template literal.');
}
customEvents[name] = t.cloneDeep(value.node);
const metadata = {
eventName: t.cloneDeep(value.node),
};
const leadingComments = path.get('leadingComments');
if (leadingComments) {
metadata.comments = (Array.isArray(leadingComments) ? leadingComments : [leadingComments]).map(item => item.node);
}
customEvents[name] = metadata;
}
},

Expand Down Expand Up @@ -210,6 +224,17 @@ module.exports = function generateCreateReactCustomElementType(api) {
Object: objectSerializerIdentifier,
};

/**
* The prop types associated with `type` in `@property`.
* @type {Object<string, Identifier>}
*/
const propTypesForLitTypes = {
String: t.memberExpression(t.identifier('PropTypes'), t.identifier('string')),
Boolean: t.memberExpression(t.identifier('PropTypes'), t.identifier('bool')),
Number: t.memberExpression(t.identifier('PropTypes'), t.identifier('number')),
Object: t.memberExpression(t.identifier('PropTypes'), t.identifier('object')),
};

/**
* @param {Object<string, PropertyMetadata>} The list of metadata harvested from `@property()` decorator calls.
* @returns {ImportDeclaration} The `import` statement for `src/globals/wrappers/createReactCustomElementType`.
Expand Down Expand Up @@ -263,7 +288,34 @@ module.exports = function generateCreateReactCustomElementType(api) {
Object.keys(customEvents).map(name =>
t.objectProperty(
t.identifier(name.replace(regexEvent, 'on')),
t.objectExpression([t.objectProperty(t.identifier('event'), customEvents[name])])
t.objectExpression([t.objectProperty(t.identifier('event'), customEvents[name].eventName)])
)
);

/**
* @param {Object<string, PropertyMetadata>} The list of metadata harvested from `@property()` decorator calls.
* @returns {ObjectProperty[]} The list of `PropTypes.someType` generated from `@property()` decorators.
*/
const buildPropTypes = declaredProps =>
Object.keys(declaredProps).map(name => {
const { type } = declaredProps[name];
const propType = propTypesForLitTypes[type || 'String'];
if (!propType) {
throw new Error(`No React prop type found for type: ${type}`);
}
return t.objectProperty(t.identifier(name), propType);
});

/**
* @param {Object<string, StringLiteral|TemplateLiteral>}
* The list of metadata harvested from `eventSomething` static properties.
* @returns {ObjectProperty[]} The list of `PropTypes.func` generated from `eventSomething` static properties.
*/
const buildEventsPropTypes = customEvents =>
Object.keys(customEvents).map(name =>
t.objectProperty(
t.identifier(name.replace(regexEvent, 'on')),
t.memberExpression(t.identifier('PropTypes'), t.identifier('func'))
)
);

Expand Down Expand Up @@ -297,6 +349,15 @@ module.exports = function generateCreateReactCustomElementType(api) {
descriptors,
]);

const propTypes = t.objectExpression([...buildPropTypes(declaredProps), ...buildEventsPropTypes(customEvents)]);
const propTypesWithParent = !context.parentDescriptorSource
? propTypes
: t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('assign')), [
t.objectExpression([]),
t.identifier('parentPropTypes'),
propTypes,
]);

let body;
if (!context.customElementName) {
// Custom element name not found means that it's likely a module not for custom element
Expand All @@ -312,10 +373,14 @@ module.exports = function generateCreateReactCustomElementType(api) {
),
buildCreateReactCustomElementTypeImport(declaredProps),
...template.ast`
import PropTypes from "prop-types";
import settings from "carbon-components/es/globals/js/settings";
var prefix = settings.prefix;
export var descriptor = ${descriptorsWithParent};
export default createReactCustomElementType(${context.customElementName}, descriptor);
export var propTypes = ${propTypesWithParent};
const Component = createReactCustomElementType(${context.customElementName}, descriptor);
Component.propTypes = propTypes;
export default Component;
`,
];
}
Expand All @@ -324,6 +389,10 @@ module.exports = function generateCreateReactCustomElementType(api) {
t.importDeclaration(
[t.importSpecifier(t.identifier('parentDescriptor'), t.identifier('descriptor'))],
t.stringLiteral(context.parentDescriptorSource)
),
t.importDeclaration(
[t.importSpecifier(t.identifier('parentPropTypes'), t.identifier('propTypes'))],
t.stringLiteral(context.parentDescriptorSource)
)
);
}
Expand Down

0 comments on commit 9c6f01f

Please sign in to comment.