Scope TailwindCSS to elsa-shell tag
This commit is contained in:
parent
908c5b1617
commit
0bb38205a5
|
|
@ -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']
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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} />;
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { createReactComponent } from './createComponent';
|
||||
export { createOverlayComponent } from './createOverlayComponent';
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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()}`);
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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';
|
||||
Loading…
Reference in a new issue