mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 01:52:19 +08:00
107 lines
3.3 KiB
TypeScript
107 lines
3.3 KiB
TypeScript
import React, { createElement } from 'react';
|
|
|
|
import { attachProps, camelToDashCase, createForwardRef, dashToPascalCase, isCoveredByReact, mergeRefs } from './utils';
|
|
|
|
export interface HTMLStencilElement extends HTMLElement {
|
|
componentOnReady(): Promise<this>;
|
|
}
|
|
|
|
interface StencilReactInternalProps<ElementType> extends React.HTMLAttributes<ElementType> {
|
|
forwardedRef: React.RefObject<ElementType>;
|
|
ref?: React.Ref<any>;
|
|
}
|
|
|
|
export const createReactComponent = <
|
|
PropType,
|
|
ElementType extends HTMLStencilElement,
|
|
ContextStateType = {},
|
|
ExpandedPropsTypes = {}
|
|
>(
|
|
tagName: string,
|
|
ReactComponentContext?: React.Context<ContextStateType>,
|
|
manipulatePropsFunction?: (
|
|
originalProps: StencilReactInternalProps<ElementType>,
|
|
propsToPass: any
|
|
) => ExpandedPropsTypes,
|
|
defineCustomElement?: () => void
|
|
) => {
|
|
if (defineCustomElement !== undefined) {
|
|
defineCustomElement();
|
|
}
|
|
|
|
const displayName = dashToPascalCase(tagName);
|
|
const ReactComponent = class extends React.Component<StencilReactInternalProps<ElementType>> {
|
|
componentEl!: ElementType;
|
|
|
|
setComponentElRef = (element: ElementType) => {
|
|
this.componentEl = element;
|
|
};
|
|
|
|
constructor(props: StencilReactInternalProps<ElementType>) {
|
|
super(props);
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.componentDidUpdate(this.props);
|
|
}
|
|
|
|
componentDidUpdate(prevProps: StencilReactInternalProps<ElementType>) {
|
|
attachProps(this.componentEl, this.props, prevProps);
|
|
}
|
|
|
|
render() {
|
|
const { children, forwardedRef, style, className, ref, ...cProps } = this.props;
|
|
|
|
let propsToPass = Object.keys(cProps).reduce((acc: any, name) => {
|
|
const value = (cProps as any)[name];
|
|
|
|
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
|
|
const eventName = name.substring(2).toLowerCase();
|
|
if (typeof document !== 'undefined' && isCoveredByReact(eventName)) {
|
|
acc[name] = value;
|
|
}
|
|
} else {
|
|
// we should only render strings, booleans, and numbers as attrs in html.
|
|
// objects, functions, arrays etc get synced via properties on mount.
|
|
const type = typeof value;
|
|
|
|
if (type === 'string' || type === 'boolean' || type === 'number') {
|
|
acc[camelToDashCase(name)] = value;
|
|
}
|
|
}
|
|
return acc;
|
|
}, {} as ExpandedPropsTypes);
|
|
|
|
if (manipulatePropsFunction) {
|
|
propsToPass = manipulatePropsFunction(this.props, propsToPass);
|
|
}
|
|
|
|
const newProps: Omit<StencilReactInternalProps<ElementType>, 'forwardedRef'> = {
|
|
...propsToPass,
|
|
ref: mergeRefs(forwardedRef, this.setComponentElRef),
|
|
style,
|
|
};
|
|
|
|
/**
|
|
* We use createElement here instead of
|
|
* React.createElement to work around a
|
|
* bug in Vite (https://github.com/vitejs/vite/issues/6104).
|
|
* React.createElement causes all elements to be rendered
|
|
* as <tagname> instead of the actual Web Component.
|
|
*/
|
|
return createElement(tagName, newProps, children);
|
|
}
|
|
|
|
static get displayName() {
|
|
return displayName;
|
|
}
|
|
};
|
|
|
|
// If context was passed to createReactComponent then conditionally add it to the Component Class
|
|
if (ReactComponentContext) {
|
|
ReactComponent.contextType = ReactComponentContext;
|
|
}
|
|
|
|
return createForwardRef<PropType, ElementType>(ReactComponent, displayName);
|
|
};
|