diff --git a/src/designer/designer_packages/angular-workspace/projects/component-library/src/lib/stencil-generated/components.ts b/src/designer/designer_packages/angular-workspace/projects/component-library/src/lib/stencil-generated/components.ts index 988fd4abb..9b9bcbae4 100644 --- a/src/designer/designer_packages/angular-workspace/projects/component-library/src/lib/stencil-generated/components.ts +++ b/src/designer/designer_packages/angular-workspace/projects/component-library/src/lib/stencil-generated/components.ts @@ -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: '', // 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: '', + // 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: '', + // 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'] }) diff --git a/src/designer/designer_packages/angular-workspace/projects/component-library/src/lib/stencil-generated/index.ts b/src/designer/designer_packages/angular-workspace/projects/component-library/src/lib/stencil-generated/index.ts index 9230f65a7..47e41de80 100644 --- a/src/designer/designer_packages/angular-workspace/projects/component-library/src/lib/stencil-generated/index.ts +++ b/src/designer/designer_packages/angular-workspace/projects/component-library/src/lib/stencil-generated/index.ts @@ -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, diff --git a/src/designer/designer_packages/elsa-workflows-designer/src/modules/flowchart/shapes.ts b/src/designer/designer_packages/elsa-workflows-designer/src/modules/flowchart/shapes.ts index 1d8755393..3f11def78 100644 --- a/src/designer/designer_packages/elsa-workflows-designer/src/modules/flowchart/shapes.ts +++ b/src/designer/designer_packages/elsa-workflows-designer/src/modules/flowchart/shapes.ts @@ -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. diff --git a/src/designer/designer_packages/elsa-workflows-designer/tailwind.config.js b/src/designer/designer_packages/elsa-workflows-designer/tailwind.config.js index b082356da..3bf019e2e 100644 --- a/src/designer/designer_packages/elsa-workflows-designer/tailwind.config.js +++ b/src/designer/designer_packages/elsa-workflows-designer/tailwind.config.js @@ -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: { diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/index.ts b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/index.ts new file mode 100644 index 000000000..21bbfe342 --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/index.ts @@ -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('elsa-activity-input-editor-dialog-content'); +export const ElsaActivityOutputEditorDialogContent = /*@__PURE__*/createReactComponent('elsa-activity-output-editor-dialog-content'); +export const ElsaActivityProperties = /*@__PURE__*/createReactComponent('elsa-activity-properties'); +export const ElsaActivityPropertiesEditor = /*@__PURE__*/createReactComponent('elsa-activity-properties-editor'); +export const ElsaAwhileNotifications = /*@__PURE__*/createReactComponent('elsa-awhile-notifications'); +export const ElsaButtonGroup = /*@__PURE__*/createReactComponent('elsa-button-group'); +export const ElsaCheckListInput = /*@__PURE__*/createReactComponent('elsa-check-list-input'); +export const ElsaCheckboxInput = /*@__PURE__*/createReactComponent('elsa-checkbox-input'); +export const ElsaCodeEditorInput = /*@__PURE__*/createReactComponent('elsa-code-editor-input'); +export const ElsaContextMenu = /*@__PURE__*/createReactComponent('elsa-context-menu'); +export const ElsaCopyButton = /*@__PURE__*/createReactComponent('elsa-copy-button'); +export const ElsaDefaultActivityTemplate = /*@__PURE__*/createReactComponent('elsa-default-activity-template'); +export const ElsaDropdownButton = /*@__PURE__*/createReactComponent('elsa-dropdown-button'); +export const ElsaDropdownInput = /*@__PURE__*/createReactComponent('elsa-dropdown-input'); +export const ElsaFlowSwitchEditor = /*@__PURE__*/createReactComponent('elsa-flow-switch-editor'); +export const ElsaFlowchart = /*@__PURE__*/createReactComponent('elsa-flowchart'); +export const ElsaFormPanel = /*@__PURE__*/createReactComponent('elsa-form-panel'); +export const ElsaHomePage = /*@__PURE__*/createReactComponent('elsa-home-page'); +export const ElsaHttpStatusCodesEditor = /*@__PURE__*/createReactComponent('elsa-http-status-codes-editor'); +export const ElsaInputControlSwitch = /*@__PURE__*/createReactComponent('elsa-input-control-switch'); +export const ElsaInputTags = /*@__PURE__*/createReactComponent('elsa-input-tags'); +export const ElsaInputTagsDropdown = /*@__PURE__*/createReactComponent('elsa-input-tags-dropdown'); +export const ElsaLabelPicker = /*@__PURE__*/createReactComponent('elsa-label-picker'); +export const ElsaLoginPage = /*@__PURE__*/createReactComponent('elsa-login-page'); +export const ElsaModalDialog = /*@__PURE__*/createReactComponent('elsa-modal-dialog'); +export const ElsaModalDialogContainer = /*@__PURE__*/createReactComponent('elsa-modal-dialog-container'); +export const ElsaMonacoEditor = /*@__PURE__*/createReactComponent('elsa-monaco-editor'); +export const ElsaMultiLineInput = /*@__PURE__*/createReactComponent('elsa-multi-line-input'); +export const ElsaMultiTextInput = /*@__PURE__*/createReactComponent('elsa-multi-text-input'); +export const ElsaNotificationTemplate = /*@__PURE__*/createReactComponent('elsa-notification-template'); +export const ElsaNotificationsManager = /*@__PURE__*/createReactComponent('elsa-notifications-manager'); +export const ElsaOutcomePickerInput = /*@__PURE__*/createReactComponent('elsa-outcome-picker-input'); +export const ElsaOutputPickerInput = /*@__PURE__*/createReactComponent('elsa-output-picker-input'); +export const ElsaPager = /*@__PURE__*/createReactComponent('elsa-pager'); +export const ElsaPanel = /*@__PURE__*/createReactComponent('elsa-panel'); +export const ElsaRadioListInput = /*@__PURE__*/createReactComponent('elsa-radio-list-input'); +export const ElsaShell = /*@__PURE__*/createReactComponent('elsa-shell'); +export const ElsaSingleLineInput = /*@__PURE__*/createReactComponent('elsa-single-line-input'); +export const ElsaSlideOverPanel = /*@__PURE__*/createReactComponent('elsa-slide-over-panel'); +export const ElsaStudio = /*@__PURE__*/createReactComponent('elsa-studio'); +export const ElsaSwitchEditor = /*@__PURE__*/createReactComponent('elsa-switch-editor'); +export const ElsaToastManager = /*@__PURE__*/createReactComponent('elsa-toast-manager'); +export const ElsaTooltip = /*@__PURE__*/createReactComponent('elsa-tooltip'); +export const ElsaTypePickerInput = /*@__PURE__*/createReactComponent('elsa-type-picker-input'); +export const ElsaVariableEditorDialogContent = /*@__PURE__*/createReactComponent('elsa-variable-editor-dialog-content'); +export const ElsaVariablePickerInput = /*@__PURE__*/createReactComponent('elsa-variable-picker-input'); +export const ElsaVariablesEditor = /*@__PURE__*/createReactComponent('elsa-variables-editor'); +export const ElsaVariablesViewer = /*@__PURE__*/createReactComponent('elsa-variables-viewer'); +export const ElsaWidgets = /*@__PURE__*/createReactComponent('elsa-widgets'); +export const ElsaWorkflowDefinitionActivityVersionSettings = /*@__PURE__*/createReactComponent('elsa-workflow-definition-activity-version-settings'); +export const ElsaWorkflowDefinitionBrowser = /*@__PURE__*/createReactComponent('elsa-workflow-definition-browser'); +export const ElsaWorkflowDefinitionEditor = /*@__PURE__*/createReactComponent('elsa-workflow-definition-editor'); +export const ElsaWorkflowDefinitionEditorToolbar = /*@__PURE__*/createReactComponent('elsa-workflow-definition-editor-toolbar'); +export const ElsaWorkflowDefinitionEditorToolbox = /*@__PURE__*/createReactComponent('elsa-workflow-definition-editor-toolbox'); +export const ElsaWorkflowDefinitionEditorToolboxActivities = /*@__PURE__*/createReactComponent('elsa-workflow-definition-editor-toolbox-activities'); +export const ElsaWorkflowDefinitionInputOutputSettings = /*@__PURE__*/createReactComponent('elsa-workflow-definition-input-output-settings'); +export const ElsaWorkflowDefinitionPickerInput = /*@__PURE__*/createReactComponent('elsa-workflow-definition-picker-input'); +export const ElsaWorkflowDefinitionPropertiesEditor = /*@__PURE__*/createReactComponent('elsa-workflow-definition-properties-editor'); +export const ElsaWorkflowDefinitionVersionHistory = /*@__PURE__*/createReactComponent('elsa-workflow-definition-version-history'); +export const ElsaWorkflowInstanceBrowser = /*@__PURE__*/createReactComponent('elsa-workflow-instance-browser'); +export const ElsaWorkflowInstanceProperties = /*@__PURE__*/createReactComponent('elsa-workflow-instance-properties'); +export const ElsaWorkflowInstanceViewer = /*@__PURE__*/createReactComponent('elsa-workflow-instance-viewer'); +export const ElsaWorkflowJournal = /*@__PURE__*/createReactComponent('elsa-workflow-journal'); +export const ElsaWorkflowNavigator = /*@__PURE__*/createReactComponent('elsa-workflow-navigator'); +export const ElsaWorkflowPublishButton = /*@__PURE__*/createReactComponent('elsa-workflow-publish-button'); +export const ElsaWorkflowToolbar = /*@__PURE__*/createReactComponent('elsa-workflow-toolbar'); +export const ElsaWorkflowToolbarMenu = /*@__PURE__*/createReactComponent('elsa-workflow-toolbar-menu'); diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/createComponent.tsx b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/createComponent.tsx new file mode 100644 index 000000000..7c7415a29 --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/createComponent.tsx @@ -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; +} + +interface StencilReactInternalProps extends React.HTMLAttributes { + forwardedRef: React.RefObject; + ref?: React.Ref; +} + +export const createReactComponent = < + PropType, + ElementType extends HTMLStencilElement, + ContextStateType = {}, + ExpandedPropsTypes = {} +>( + tagName: string, + ReactComponentContext?: React.Context, + manipulatePropsFunction?: ( + originalProps: StencilReactInternalProps, + propsToPass: any + ) => ExpandedPropsTypes, + defineCustomElement?: () => void +) => { + if (defineCustomElement !== undefined) { + defineCustomElement(); + } + + const displayName = dashToPascalCase(tagName); + const ReactComponent = class extends React.Component> { + componentEl!: ElementType; + + setComponentElRef = (element: ElementType) => { + this.componentEl = element; + }; + + constructor(props: StencilReactInternalProps) { + super(props); + } + + componentDidMount() { + this.componentDidUpdate(this.props); + } + + componentDidUpdate(prevProps: StencilReactInternalProps) { + 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, '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 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(ReactComponent, displayName); +}; diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/createOverlayComponent.tsx b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/createOverlayComponent.tsx new file mode 100644 index 000000000..288c38dd5 --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/createOverlayComponent.tsx @@ -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; + dismiss: (data?: any, role?: string | undefined) => Promise; +} + +export interface ReactOverlayProps { + children?: React.ReactNode; + isOpen: boolean; + onDidDismiss?: (event: CustomEvent) => void; + onDidPresent?: (event: CustomEvent) => void; + onWillDismiss?: (event: CustomEvent) => void; + onWillPresent?: (event: CustomEvent) => void; +} + +export const createOverlayComponent = ( + tagName: string, + controller: { create: (options: any) => Promise }, + 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; + }; + + let isDismissing = false; + + class Overlay extends React.Component { + 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>) { + 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((props, ref) => { + return ; + }); +}; diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/index.ts b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/index.ts new file mode 100644 index 000000000..85e81ad19 --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/index.ts @@ -0,0 +1,2 @@ +export { createReactComponent } from './createComponent'; +export { createOverlayComponent } from './createOverlayComponent'; diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/interfaces.ts b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/interfaces.ts new file mode 100644 index 000000000..92e5389c8 --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/interfaces.ts @@ -0,0 +1,34 @@ +// General types important to applications using stencil built components +export interface EventEmitter { + emit: (data?: T) => CustomEvent; +} + +export interface StyleReactProps { + class?: string; + className?: string; + style?: { [key: string]: any }; +} + +export interface OverlayEventDetail { + data?: T; + role?: string; +} + +export interface OverlayInterface { + el: HTMLElement; + animated: boolean; + keyboardClose: boolean; + overlayIndex: number; + presented: boolean; + + enterAnimation?: any; + leaveAnimation?: any; + + didPresent: EventEmitter; + willPresent: EventEmitter; + willDismiss: EventEmitter; + didDismiss: EventEmitter; + + present(): Promise; + dismiss(data?: any, role?: string): Promise; +} diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/attachProps.ts b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/attachProps.ts new file mode 100644 index 000000000..de2cc499b --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/attachProps.ts @@ -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(); + (arr as string[]).forEach((s: string) => map.set(s, s)); + return map; +}; diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/case.ts b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/case.ts new file mode 100644 index 000000000..786689dc9 --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/case.ts @@ -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()}`); diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/dev.ts b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/dev.ts new file mode 100644 index 000000000..cc6f9ed08 --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/dev.ts @@ -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; + } + } +}; diff --git a/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/index.tsx b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/index.tsx new file mode 100644 index 000000000..a66bd02de --- /dev/null +++ b/src/designer/designer_packages/react-wrapper/lib/components/stencil-generated/react-component-lib/utils/index.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import type { StyleReactProps } from '../interfaces'; + +export type StencilReactExternalProps = PropType & + Omit, 'style'> & + StyleReactProps; + +// This will be replaced with React.ForwardedRef when react-output-target is upgraded to React v17 +export type StencilReactForwardedRef = ((instance: T | null) => void) | React.MutableRefObject | null; + +export const setRef = (ref: StencilReactForwardedRef | React.Ref | 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).current = value; + } +}; + +export const mergeRefs = ( + ...refs: (StencilReactForwardedRef | React.Ref | undefined)[] +): React.RefCallback => { + return (value: any) => { + refs.forEach((ref) => { + setRef(ref, value); + }); + }; +}; + +export const createForwardRef = (ReactComponent: any, displayName: string) => { + const forwardRef = ( + props: StencilReactExternalProps, + ref: StencilReactForwardedRef + ) => { + return ; + }; + 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';