Scope TailwindCSS to elsa-shell tag

This commit is contained in:
Sipke Schoorstra 2023-04-01 15:16:32 +02:00
parent 908c5b1617
commit 0bb38205a5
13 changed files with 598 additions and 4 deletions

View file

@ -121,14 +121,14 @@ export declare interface ElsaActivityPropertiesEditor extends Components.ElsaAct
@ProxyCmp({
inputs: ['notification']
inputs: ['notification', 'showDuration']
})
@Component({
selector: 'elsa-awhile-notifications',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['notification'],
inputs: ['notification', 'showDuration'],
})
export class ElsaAwhileNotifications {
protected el: HTMLElement;
@ -762,6 +762,28 @@ export class ElsaMultiTextInput {
export declare interface ElsaMultiTextInput extends Components.ElsaMultiTextInput {}
@ProxyCmp({
inputs: ['notification']
})
@Component({
selector: 'elsa-notification-template',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['notification'],
})
export class ElsaNotificationTemplate {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
export declare interface ElsaNotificationTemplate extends Components.ElsaNotificationTemplate {}
@ProxyCmp({
inputs: ['modalState']
})
@ -1020,6 +1042,27 @@ export class ElsaSwitchEditor {
export declare interface ElsaSwitchEditor extends Components.ElsaSwitchEditor {}
@ProxyCmp({
})
@Component({
selector: 'elsa-toast-manager',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [],
})
export class ElsaToastManager {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
export declare interface ElsaToastManager extends Components.ElsaToastManager {}
@ProxyCmp({
inputs: ['tooltipContent', 'tooltipPosition']
})

View file

@ -31,6 +31,7 @@ export const DIRECTIVES = [
d.ElsaMonacoEditor,
d.ElsaMultiLineInput,
d.ElsaMultiTextInput,
d.ElsaNotificationTemplate,
d.ElsaNotificationsManager,
d.ElsaOutcomePickerInput,
d.ElsaOutputPickerInput,
@ -42,6 +43,7 @@ export const DIRECTIVES = [
d.ElsaSlideOverPanel,
d.ElsaStudio,
d.ElsaSwitchEditor,
d.ElsaToastManager,
d.ElsaTooltip,
d.ElsaTypePickerInput,
d.ElsaVariableEditorDialogContent,

View file

@ -58,12 +58,15 @@ export class ActivityNodeShape extends Shape.HTML {
return;
const wrapper = document.createElement('div');
wrapper.className = 'w-full flex items-center pl-10 pr-2 py-2 absolute';
wrapper.className = 'inline-block flex items-center pl-10 pr-2 py-2 absolute';
wrapper.style.left = '-1000px';
wrapper.style.top = '-1000px';
wrapper.innerHTML = this.createHtml();
document.body.append(wrapper);
// Append the temporary element to the DOM.
// Important: this needs to be a child of the elsa-shell element, otherwise the tailwind CSS classes will not be applied due to the "important" rule in tailwind.config.js.
const elsaStudioShell = document.getElementsByTagName('elsa-shell')[0];
elsaStudioShell.append(wrapper);
// Wait for activity element to be completely rendered.
// When using custom elements, they are rendered after they are mounted. Before then, they have a 0 width and height.

View file

@ -7,6 +7,7 @@ const dev = process.argv && process.argv.indexOf('--dev') > -1;
module.exports = {
content: ['./src/**/*.tsx', './src/**/*.ts'],
darkMode: 'media',
important: 'elsa-shell',
theme: {
extend: {
colors: {

View file

@ -0,0 +1,76 @@
/* eslint-disable */
/* tslint:disable */
/* auto-generated react proxies */
import { createReactComponent } from './react-component-lib';
import type { JSX } from '@elsa-workflows/elsa-workflows-designer';
export const ElsaActivityInputEditorDialogContent = /*@__PURE__*/createReactComponent<JSX.ElsaActivityInputEditorDialogContent, HTMLElsaActivityInputEditorDialogContentElement>('elsa-activity-input-editor-dialog-content');
export const ElsaActivityOutputEditorDialogContent = /*@__PURE__*/createReactComponent<JSX.ElsaActivityOutputEditorDialogContent, HTMLElsaActivityOutputEditorDialogContentElement>('elsa-activity-output-editor-dialog-content');
export const ElsaActivityProperties = /*@__PURE__*/createReactComponent<JSX.ElsaActivityProperties, HTMLElsaActivityPropertiesElement>('elsa-activity-properties');
export const ElsaActivityPropertiesEditor = /*@__PURE__*/createReactComponent<JSX.ElsaActivityPropertiesEditor, HTMLElsaActivityPropertiesEditorElement>('elsa-activity-properties-editor');
export const ElsaAwhileNotifications = /*@__PURE__*/createReactComponent<JSX.ElsaAwhileNotifications, HTMLElsaAwhileNotificationsElement>('elsa-awhile-notifications');
export const ElsaButtonGroup = /*@__PURE__*/createReactComponent<JSX.ElsaButtonGroup, HTMLElsaButtonGroupElement>('elsa-button-group');
export const ElsaCheckListInput = /*@__PURE__*/createReactComponent<JSX.ElsaCheckListInput, HTMLElsaCheckListInputElement>('elsa-check-list-input');
export const ElsaCheckboxInput = /*@__PURE__*/createReactComponent<JSX.ElsaCheckboxInput, HTMLElsaCheckboxInputElement>('elsa-checkbox-input');
export const ElsaCodeEditorInput = /*@__PURE__*/createReactComponent<JSX.ElsaCodeEditorInput, HTMLElsaCodeEditorInputElement>('elsa-code-editor-input');
export const ElsaContextMenu = /*@__PURE__*/createReactComponent<JSX.ElsaContextMenu, HTMLElsaContextMenuElement>('elsa-context-menu');
export const ElsaCopyButton = /*@__PURE__*/createReactComponent<JSX.ElsaCopyButton, HTMLElsaCopyButtonElement>('elsa-copy-button');
export const ElsaDefaultActivityTemplate = /*@__PURE__*/createReactComponent<JSX.ElsaDefaultActivityTemplate, HTMLElsaDefaultActivityTemplateElement>('elsa-default-activity-template');
export const ElsaDropdownButton = /*@__PURE__*/createReactComponent<JSX.ElsaDropdownButton, HTMLElsaDropdownButtonElement>('elsa-dropdown-button');
export const ElsaDropdownInput = /*@__PURE__*/createReactComponent<JSX.ElsaDropdownInput, HTMLElsaDropdownInputElement>('elsa-dropdown-input');
export const ElsaFlowSwitchEditor = /*@__PURE__*/createReactComponent<JSX.ElsaFlowSwitchEditor, HTMLElsaFlowSwitchEditorElement>('elsa-flow-switch-editor');
export const ElsaFlowchart = /*@__PURE__*/createReactComponent<JSX.ElsaFlowchart, HTMLElsaFlowchartElement>('elsa-flowchart');
export const ElsaFormPanel = /*@__PURE__*/createReactComponent<JSX.ElsaFormPanel, HTMLElsaFormPanelElement>('elsa-form-panel');
export const ElsaHomePage = /*@__PURE__*/createReactComponent<JSX.ElsaHomePage, HTMLElsaHomePageElement>('elsa-home-page');
export const ElsaHttpStatusCodesEditor = /*@__PURE__*/createReactComponent<JSX.ElsaHttpStatusCodesEditor, HTMLElsaHttpStatusCodesEditorElement>('elsa-http-status-codes-editor');
export const ElsaInputControlSwitch = /*@__PURE__*/createReactComponent<JSX.ElsaInputControlSwitch, HTMLElsaInputControlSwitchElement>('elsa-input-control-switch');
export const ElsaInputTags = /*@__PURE__*/createReactComponent<JSX.ElsaInputTags, HTMLElsaInputTagsElement>('elsa-input-tags');
export const ElsaInputTagsDropdown = /*@__PURE__*/createReactComponent<JSX.ElsaInputTagsDropdown, HTMLElsaInputTagsDropdownElement>('elsa-input-tags-dropdown');
export const ElsaLabelPicker = /*@__PURE__*/createReactComponent<JSX.ElsaLabelPicker, HTMLElsaLabelPickerElement>('elsa-label-picker');
export const ElsaLoginPage = /*@__PURE__*/createReactComponent<JSX.ElsaLoginPage, HTMLElsaLoginPageElement>('elsa-login-page');
export const ElsaModalDialog = /*@__PURE__*/createReactComponent<JSX.ElsaModalDialog, HTMLElsaModalDialogElement>('elsa-modal-dialog');
export const ElsaModalDialogContainer = /*@__PURE__*/createReactComponent<JSX.ElsaModalDialogContainer, HTMLElsaModalDialogContainerElement>('elsa-modal-dialog-container');
export const ElsaMonacoEditor = /*@__PURE__*/createReactComponent<JSX.ElsaMonacoEditor, HTMLElsaMonacoEditorElement>('elsa-monaco-editor');
export const ElsaMultiLineInput = /*@__PURE__*/createReactComponent<JSX.ElsaMultiLineInput, HTMLElsaMultiLineInputElement>('elsa-multi-line-input');
export const ElsaMultiTextInput = /*@__PURE__*/createReactComponent<JSX.ElsaMultiTextInput, HTMLElsaMultiTextInputElement>('elsa-multi-text-input');
export const ElsaNotificationTemplate = /*@__PURE__*/createReactComponent<JSX.ElsaNotificationTemplate, HTMLElsaNotificationTemplateElement>('elsa-notification-template');
export const ElsaNotificationsManager = /*@__PURE__*/createReactComponent<JSX.ElsaNotificationsManager, HTMLElsaNotificationsManagerElement>('elsa-notifications-manager');
export const ElsaOutcomePickerInput = /*@__PURE__*/createReactComponent<JSX.ElsaOutcomePickerInput, HTMLElsaOutcomePickerInputElement>('elsa-outcome-picker-input');
export const ElsaOutputPickerInput = /*@__PURE__*/createReactComponent<JSX.ElsaOutputPickerInput, HTMLElsaOutputPickerInputElement>('elsa-output-picker-input');
export const ElsaPager = /*@__PURE__*/createReactComponent<JSX.ElsaPager, HTMLElsaPagerElement>('elsa-pager');
export const ElsaPanel = /*@__PURE__*/createReactComponent<JSX.ElsaPanel, HTMLElsaPanelElement>('elsa-panel');
export const ElsaRadioListInput = /*@__PURE__*/createReactComponent<JSX.ElsaRadioListInput, HTMLElsaRadioListInputElement>('elsa-radio-list-input');
export const ElsaShell = /*@__PURE__*/createReactComponent<JSX.ElsaShell, HTMLElsaShellElement>('elsa-shell');
export const ElsaSingleLineInput = /*@__PURE__*/createReactComponent<JSX.ElsaSingleLineInput, HTMLElsaSingleLineInputElement>('elsa-single-line-input');
export const ElsaSlideOverPanel = /*@__PURE__*/createReactComponent<JSX.ElsaSlideOverPanel, HTMLElsaSlideOverPanelElement>('elsa-slide-over-panel');
export const ElsaStudio = /*@__PURE__*/createReactComponent<JSX.ElsaStudio, HTMLElsaStudioElement>('elsa-studio');
export const ElsaSwitchEditor = /*@__PURE__*/createReactComponent<JSX.ElsaSwitchEditor, HTMLElsaSwitchEditorElement>('elsa-switch-editor');
export const ElsaToastManager = /*@__PURE__*/createReactComponent<JSX.ElsaToastManager, HTMLElsaToastManagerElement>('elsa-toast-manager');
export const ElsaTooltip = /*@__PURE__*/createReactComponent<JSX.ElsaTooltip, HTMLElsaTooltipElement>('elsa-tooltip');
export const ElsaTypePickerInput = /*@__PURE__*/createReactComponent<JSX.ElsaTypePickerInput, HTMLElsaTypePickerInputElement>('elsa-type-picker-input');
export const ElsaVariableEditorDialogContent = /*@__PURE__*/createReactComponent<JSX.ElsaVariableEditorDialogContent, HTMLElsaVariableEditorDialogContentElement>('elsa-variable-editor-dialog-content');
export const ElsaVariablePickerInput = /*@__PURE__*/createReactComponent<JSX.ElsaVariablePickerInput, HTMLElsaVariablePickerInputElement>('elsa-variable-picker-input');
export const ElsaVariablesEditor = /*@__PURE__*/createReactComponent<JSX.ElsaVariablesEditor, HTMLElsaVariablesEditorElement>('elsa-variables-editor');
export const ElsaVariablesViewer = /*@__PURE__*/createReactComponent<JSX.ElsaVariablesViewer, HTMLElsaVariablesViewerElement>('elsa-variables-viewer');
export const ElsaWidgets = /*@__PURE__*/createReactComponent<JSX.ElsaWidgets, HTMLElsaWidgetsElement>('elsa-widgets');
export const ElsaWorkflowDefinitionActivityVersionSettings = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionActivityVersionSettings, HTMLElsaWorkflowDefinitionActivityVersionSettingsElement>('elsa-workflow-definition-activity-version-settings');
export const ElsaWorkflowDefinitionBrowser = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionBrowser, HTMLElsaWorkflowDefinitionBrowserElement>('elsa-workflow-definition-browser');
export const ElsaWorkflowDefinitionEditor = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionEditor, HTMLElsaWorkflowDefinitionEditorElement>('elsa-workflow-definition-editor');
export const ElsaWorkflowDefinitionEditorToolbar = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionEditorToolbar, HTMLElsaWorkflowDefinitionEditorToolbarElement>('elsa-workflow-definition-editor-toolbar');
export const ElsaWorkflowDefinitionEditorToolbox = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionEditorToolbox, HTMLElsaWorkflowDefinitionEditorToolboxElement>('elsa-workflow-definition-editor-toolbox');
export const ElsaWorkflowDefinitionEditorToolboxActivities = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionEditorToolboxActivities, HTMLElsaWorkflowDefinitionEditorToolboxActivitiesElement>('elsa-workflow-definition-editor-toolbox-activities');
export const ElsaWorkflowDefinitionInputOutputSettings = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionInputOutputSettings, HTMLElsaWorkflowDefinitionInputOutputSettingsElement>('elsa-workflow-definition-input-output-settings');
export const ElsaWorkflowDefinitionPickerInput = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionPickerInput, HTMLElsaWorkflowDefinitionPickerInputElement>('elsa-workflow-definition-picker-input');
export const ElsaWorkflowDefinitionPropertiesEditor = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionPropertiesEditor, HTMLElsaWorkflowDefinitionPropertiesEditorElement>('elsa-workflow-definition-properties-editor');
export const ElsaWorkflowDefinitionVersionHistory = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowDefinitionVersionHistory, HTMLElsaWorkflowDefinitionVersionHistoryElement>('elsa-workflow-definition-version-history');
export const ElsaWorkflowInstanceBrowser = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowInstanceBrowser, HTMLElsaWorkflowInstanceBrowserElement>('elsa-workflow-instance-browser');
export const ElsaWorkflowInstanceProperties = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowInstanceProperties, HTMLElsaWorkflowInstancePropertiesElement>('elsa-workflow-instance-properties');
export const ElsaWorkflowInstanceViewer = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowInstanceViewer, HTMLElsaWorkflowInstanceViewerElement>('elsa-workflow-instance-viewer');
export const ElsaWorkflowJournal = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowJournal, HTMLElsaWorkflowJournalElement>('elsa-workflow-journal');
export const ElsaWorkflowNavigator = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowNavigator, HTMLElsaWorkflowNavigatorElement>('elsa-workflow-navigator');
export const ElsaWorkflowPublishButton = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowPublishButton, HTMLElsaWorkflowPublishButtonElement>('elsa-workflow-publish-button');
export const ElsaWorkflowToolbar = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowToolbar, HTMLElsaWorkflowToolbarElement>('elsa-workflow-toolbar');
export const ElsaWorkflowToolbarMenu = /*@__PURE__*/createReactComponent<JSX.ElsaWorkflowToolbarMenu, HTMLElsaWorkflowToolbarMenuElement>('elsa-workflow-toolbar-menu');

View file

@ -0,0 +1,106 @@
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);
};

View file

@ -0,0 +1,142 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { OverlayEventDetail } from './interfaces';
import { StencilReactForwardedRef, attachProps, dashToPascalCase, defineCustomElement, setRef } from './utils';
interface OverlayElement extends HTMLElement {
present: () => Promise<void>;
dismiss: (data?: any, role?: string | undefined) => Promise<boolean>;
}
export interface ReactOverlayProps {
children?: React.ReactNode;
isOpen: boolean;
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
}
export const createOverlayComponent = <OverlayComponent extends object, OverlayType extends OverlayElement>(
tagName: string,
controller: { create: (options: any) => Promise<OverlayType> },
customElement?: any
) => {
defineCustomElement(tagName, customElement);
const displayName = dashToPascalCase(tagName);
const didDismissEventName = `on${displayName}DidDismiss`;
const didPresentEventName = `on${displayName}DidPresent`;
const willDismissEventName = `on${displayName}WillDismiss`;
const willPresentEventName = `on${displayName}WillPresent`;
type Props = OverlayComponent &
ReactOverlayProps & {
forwardedRef?: StencilReactForwardedRef<OverlayType>;
};
let isDismissing = false;
class Overlay extends React.Component<Props> {
overlay?: OverlayType;
el!: HTMLDivElement;
constructor(props: Props) {
super(props);
if (typeof document !== 'undefined') {
this.el = document.createElement('div');
}
this.handleDismiss = this.handleDismiss.bind(this);
}
static get displayName() {
return displayName;
}
componentDidMount() {
if (this.props.isOpen) {
this.present();
}
}
componentWillUnmount() {
if (this.overlay) {
this.overlay.dismiss();
}
}
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
setRef(this.props.forwardedRef, null);
}
shouldComponentUpdate(nextProps: Props) {
// Check if the overlay component is about to dismiss
if (this.overlay && nextProps.isOpen !== this.props.isOpen && nextProps.isOpen === false) {
isDismissing = true;
}
return true;
}
async componentDidUpdate(prevProps: Props) {
if (this.overlay) {
attachProps(this.overlay, this.props, prevProps);
}
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps);
}
if (this.overlay && prevProps.isOpen !== this.props.isOpen && this.props.isOpen === false) {
await this.overlay.dismiss();
isDismissing = false;
/**
* Now that the overlay is dismissed
* we need to render again so that any
* inner components will be unmounted
*/
this.forceUpdate();
}
}
async present(prevProps?: Props) {
const { children, isOpen, onDidDismiss, onDidPresent, onWillDismiss, onWillPresent, ...cProps } = this.props;
const elementProps = {
...cProps,
ref: this.props.forwardedRef,
[didDismissEventName]: this.handleDismiss,
[didPresentEventName]: (e: CustomEvent) => this.props.onDidPresent && this.props.onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) => this.props.onWillDismiss && this.props.onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) => this.props.onWillPresent && this.props.onWillPresent(e),
};
this.overlay = await controller.create({
...elementProps,
component: this.el,
componentProps: {},
});
setRef(this.props.forwardedRef, this.overlay);
attachProps(this.overlay, elementProps, prevProps);
await this.overlay.present();
}
render() {
/**
* Continue to render the component even when
* overlay is dismissing otherwise component
* will be hidden before animation is done.
*/
return ReactDOM.createPortal(this.props.isOpen || isDismissing ? this.props.children : null, this.el);
}
}
return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...props} forwardedRef={ref} />;
});
};

View file

@ -0,0 +1,2 @@
export { createReactComponent } from './createComponent';
export { createOverlayComponent } from './createOverlayComponent';

View file

@ -0,0 +1,34 @@
// General types important to applications using stencil built components
export interface EventEmitter<T = any> {
emit: (data?: T) => CustomEvent<T>;
}
export interface StyleReactProps {
class?: string;
className?: string;
style?: { [key: string]: any };
}
export interface OverlayEventDetail<T = any> {
data?: T;
role?: string;
}
export interface OverlayInterface {
el: HTMLElement;
animated: boolean;
keyboardClose: boolean;
overlayIndex: number;
presented: boolean;
enterAnimation?: any;
leaveAnimation?: any;
didPresent: EventEmitter<void>;
willPresent: EventEmitter<void>;
willDismiss: EventEmitter<OverlayEventDetail>;
didDismiss: EventEmitter<OverlayEventDetail>;
present(): Promise<void>;
dismiss(data?: any, role?: string): Promise<boolean>;
}

View file

@ -0,0 +1,114 @@
import { camelToDashCase } from './case';
export const attachProps = (node: HTMLElement, newProps: any, oldProps: any = {}) => {
// some test frameworks don't render DOM elements, so we test here to make sure we are dealing with DOM first
if (node instanceof Element) {
// add any classes in className to the class list
const className = getClassName(node.classList, newProps, oldProps);
if (className !== '') {
node.className = className;
}
Object.keys(newProps).forEach((name) => {
if (
name === 'children' ||
name === 'style' ||
name === 'ref' ||
name === 'class' ||
name === 'className' ||
name === 'forwardedRef'
) {
return;
}
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2);
const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);
if (!isCoveredByReact(eventNameLc)) {
syncEvent(node, eventNameLc, newProps[name]);
}
} else {
(node as any)[name] = newProps[name];
const propType = typeof newProps[name];
if (propType === 'string') {
node.setAttribute(camelToDashCase(name), newProps[name]);
}
}
});
}
};
export const getClassName = (classList: DOMTokenList, newProps: any, oldProps: any) => {
const newClassProp: string = newProps.className || newProps.class;
const oldClassProp: string = oldProps.className || oldProps.class;
// map the classes to Maps for performance
const currentClasses = arrayToMap(classList);
const incomingPropClasses = arrayToMap(newClassProp ? newClassProp.split(' ') : []);
const oldPropClasses = arrayToMap(oldClassProp ? oldClassProp.split(' ') : []);
const finalClassNames: string[] = [];
// loop through each of the current classes on the component
// to see if it should be a part of the classNames added
currentClasses.forEach((currentClass) => {
if (incomingPropClasses.has(currentClass)) {
// add it as its already included in classnames coming in from newProps
finalClassNames.push(currentClass);
incomingPropClasses.delete(currentClass);
} else if (!oldPropClasses.has(currentClass)) {
// add it as it has NOT been removed by user
finalClassNames.push(currentClass);
}
});
incomingPropClasses.forEach((s) => finalClassNames.push(s));
return finalClassNames.join(' ');
};
/**
* Checks if an event is supported in the current execution environment.
* @license Modernizr 3.0.0pre (Custom Build) | MIT
*/
export const isCoveredByReact = (eventNameSuffix: string) => {
if (typeof document === 'undefined') {
return true;
} else {
const eventName = 'on' + eventNameSuffix;
let isSupported = eventName in document;
if (!isSupported) {
const element = document.createElement('div');
element.setAttribute(eventName, 'return;');
isSupported = typeof (element as any)[eventName] === 'function';
}
return isSupported;
}
};
export const syncEvent = (
node: Element & { __events?: { [key: string]: ((e: Event) => any) | undefined } },
eventName: string,
newEventHandler?: (e: Event) => any
) => {
const eventStore = node.__events || (node.__events = {});
const oldEventHandler = eventStore[eventName];
// Remove old listener so they don't double up.
if (oldEventHandler) {
node.removeEventListener(eventName, oldEventHandler);
}
// Bind new listener.
node.addEventListener(
eventName,
(eventStore[eventName] = function handler(e: Event) {
if (newEventHandler) {
newEventHandler.call(this, e);
}
})
);
};
const arrayToMap = (arr: string[] | DOMTokenList) => {
const map = new Map<string, string>();
(arr as string[]).forEach((s: string) => map.set(s, s));
return map;
};

View file

@ -0,0 +1,7 @@
export const dashToPascalCase = (str: string) =>
str
.toLowerCase()
.split('-')
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
.join('');
export const camelToDashCase = (str: string) => str.replace(/([A-Z])/g, (m: string) => `-${m[0].toLowerCase()}`);

View file

@ -0,0 +1,14 @@
export const isDevMode = () => {
return process && process.env && process.env.NODE_ENV === 'development';
};
const warnings: { [key: string]: boolean } = {};
export const deprecationWarning = (key: string, message: string) => {
if (isDevMode()) {
if (!warnings[key]) {
console.warn(message);
warnings[key] = true;
}
}
};

View file

@ -0,0 +1,50 @@
import React from 'react';
import type { StyleReactProps } from '../interfaces';
export type StencilReactExternalProps<PropType, ElementType> = PropType &
Omit<React.HTMLAttributes<ElementType>, 'style'> &
StyleReactProps;
// This will be replaced with React.ForwardedRef when react-output-target is upgraded to React v17
export type StencilReactForwardedRef<T> = ((instance: T | null) => void) | React.MutableRefObject<T | null> | null;
export const setRef = (ref: StencilReactForwardedRef<any> | React.Ref<any> | undefined, value: any) => {
if (typeof ref === 'function') {
ref(value);
} else if (ref != null) {
// Cast as a MutableRef so we can assign current
(ref as React.MutableRefObject<any>).current = value;
}
};
export const mergeRefs = (
...refs: (StencilReactForwardedRef<any> | React.Ref<any> | undefined)[]
): React.RefCallback<any> => {
return (value: any) => {
refs.forEach((ref) => {
setRef(ref, value);
});
};
};
export const createForwardRef = <PropType, ElementType>(ReactComponent: any, displayName: string) => {
const forwardRef = (
props: StencilReactExternalProps<PropType, ElementType>,
ref: StencilReactForwardedRef<ElementType>
) => {
return <ReactComponent {...props} forwardedRef={ref} />;
};
forwardRef.displayName = displayName;
return React.forwardRef(forwardRef);
};
export const defineCustomElement = (tagName: string, customElement: any) => {
if (customElement !== undefined && typeof customElements !== 'undefined' && !customElements.get(tagName)) {
customElements.define(tagName, customElement);
}
};
export * from './attachProps';
export * from './case';