UNPKG

3.42 kBTypeScriptView Raw
1import React, { createElement } from 'react';
2
3import {
4 attachProps,
5 camelToDashCase,
6 createForwardRef,
7 dashToPascalCase,
8 isCoveredByReact,
9 mergeRefs,
10} from './utils';
11
12export interface HTMLStencilElement extends HTMLElement {
13 componentOnReady(): Promise<this>;
14}
15
16interface StencilReactInternalProps<ElementType> extends React.HTMLAttributes<ElementType> {
17 forwardedRef: React.RefObject<ElementType>;
18 ref?: React.Ref<any>;
19}
20
21export const createReactComponent = <
22 PropType,
23 ElementType extends HTMLStencilElement,
24 ContextStateType = {},
25 ExpandedPropsTypes = {}
26>(
27 tagName: string,
28 ReactComponentContext?: React.Context<ContextStateType>,
29 manipulatePropsFunction?: (
30 originalProps: StencilReactInternalProps<ElementType>,
31 propsToPass: any,
32 ) => ExpandedPropsTypes,
33 defineCustomElement?: () => void,
34) => {
35 if (defineCustomElement !== undefined) {
36 defineCustomElement();
37 }
38
39 const displayName = dashToPascalCase(tagName);
40 const ReactComponent = class extends React.Component<StencilReactInternalProps<ElementType>> {
41 componentEl!: ElementType;
42
43 setComponentElRef = (element: ElementType) => {
44 this.componentEl = element;
45 };
46
47 constructor(props: StencilReactInternalProps<ElementType>) {
48 super(props);
49 }
50
51 componentDidMount() {
52 this.componentDidUpdate(this.props);
53 }
54
55 componentDidUpdate(prevProps: StencilReactInternalProps<ElementType>) {
56 attachProps(this.componentEl, this.props, prevProps);
57 }
58
59 render() {
60 const { children, forwardedRef, style, className, ref, ...cProps } = this.props;
61
62 let propsToPass = Object.keys(cProps).reduce((acc: any, name) => {
63 const value = (cProps as any)[name];
64
65 if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
66 const eventName = name.substring(2).toLowerCase();
67 if (typeof document !== 'undefined' && isCoveredByReact(eventName)) {
68 acc[name] = value;
69 }
70 } else {
71 // we should only render strings, booleans, and numbers as attrs in html.
72 // objects, functions, arrays etc get synced via properties on mount.
73 const type = typeof value;
74
75 if (type === 'string' || type === 'boolean' || type === 'number') {
76 acc[camelToDashCase(name)] = value;
77 }
78 }
79 return acc;
80 }, {});
81
82 if (manipulatePropsFunction) {
83 propsToPass = manipulatePropsFunction(this.props, propsToPass);
84 }
85
86 const newProps: Omit<StencilReactInternalProps<ElementType>, 'forwardedRef'> = {
87 ...propsToPass,
88 ref: mergeRefs(forwardedRef, this.setComponentElRef),
89 style,
90 };
91
92 /**
93 * We use createElement here instead of
94 * React.createElement to work around a
95 * bug in Vite (https://github.com/vitejs/vite/issues/6104).
96 * React.createElement causes all elements to be rendered
97 * as <tagname> instead of the actual Web Component.
98 */
99 return createElement(tagName, newProps, children);
100 }
101
102 static get displayName() {
103 return displayName;
104 }
105 };
106
107 // If context was passed to createReactComponent then conditionally add it to the Component Class
108 if (ReactComponentContext) {
109 ReactComponent.contextType = ReactComponentContext;
110 }
111
112 return createForwardRef<PropType, ElementType>(ReactComponent, displayName);
113};