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