Skip to content

Commit

Permalink
add output targets
Browse files Browse the repository at this point in the history
  • Loading branch information
hirsch88 committed Feb 11, 2024
1 parent bd5b847 commit e3b1c70
Show file tree
Hide file tree
Showing 48 changed files with 2,058 additions and 38 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {
"@typescript-eslint/no-explicit-any": "off"
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-extra-semi": "off"
}
},
{
Expand Down
6 changes: 5 additions & 1 deletion libs/output-target-angular/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": ["{projectRoot}/vite.config.{js,ts,mjs,mts}"]
"ignoredFiles": [
"{projectRoot}/vite.config.{js,ts,mjs,mts}",
"{projectRoot}/resources/**/*.ts",
"{projectRoot}/angular-component-lib/**/*.ts"
]
}
]
}
Expand Down
3 changes: 0 additions & 3 deletions libs/output-target-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
"name": "@baloise/ds-output-target-angular",
"version": "0.0.1",
"dependencies": {
"@angular/core": "~15.0.0",
"@angular/forms": "~15.0.0",
"@stencil/core": "~4.11.0",
"rxjs": "^7.8.1",
"tslib": "^2.3.0"
},
"type": "commonjs",
Expand Down
4 changes: 2 additions & 2 deletions libs/output-target-angular/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { angularOutputTarget } from './plugin'
export type { OutputTargetAngular, ValueAccessorConfig } from './types'
export { angularOutputTarget } from './lib/plugin'
export type { OutputTargetAngular, ValueAccessorConfig } from './lib/types'
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions libs/output-target-angular/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
},
"include": [
"src/**/*.ts",
"src/generate-angular-component.spec.ts",
"src/output-angular.spec.ts",
"src/plugin.spec.ts"
"src/lib/generate-angular-component.spec.ts",
"src/lib/output-angular.spec.ts",
"src/lib/plugin.spec.ts"
],
"exclude": ["vite.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}
6 changes: 3 additions & 3 deletions libs/output-target-angular/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts",
"src/generate-angular-component.spec.ts",
"src/output-angular.spec.ts",
"src/plugin.spec.ts"
"src/lib/generate-angular-component.spec.ts",
"src/lib/output-angular.spec.ts",
"src/lib/plugin.spec.ts"
]
}
2 changes: 1 addition & 1 deletion libs/output-target-react/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": ["{projectRoot}/vite.config.{js,ts,mjs,mts}"]
"ignoredFiles": ["{projectRoot}/vite.config.{js,ts,mjs,mts}", "{projectRoot}/react-component-lib/**/*.ts"]
}
]
}
Expand Down
3 changes: 2 additions & 1 deletion libs/output-target-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "@baloise/output-target-react",
"version": "0.0.1",
"dependencies": {
"tslib": "^2.3.0"
"tslib": "^2.3.0",
"@stencil/core": "~4.11.0"
},
"type": "commonjs",
"main": "./src/index.js",
Expand Down
103 changes: 103 additions & 0 deletions libs/output-target-react/react-component-lib/createComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* eslint-disable @typescript-eslint/ban-types */
import React, { createElement, Fragment } from 'react'

import { attachProps, 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() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
const { children, forwardedRef, style, className, ref, ...cProps } = this.props

let propsToPass: any = Object.keys(cProps).reduce((acc, name) => {
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2).toLowerCase()
if (typeof document !== 'undefined' && isCoveredByReact(eventName)) {
(acc as any)[name] = (cProps as any)[name]
}
} else {
(acc as any)[name] = (cProps as any)[name]
}
return acc
}, {})

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.
*/
const areChildrenEmpty = children === '' || children === undefined || children === null
const fragment = !areChildrenEmpty ? createElement(Fragment, {}, children) : createElement(Fragment, {}, ' ')
const component = createElement(tagName, newProps, fragment)
return component
}

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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
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} />
})
}
2 changes: 2 additions & 0 deletions libs/output-target-react/react-component-lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { createReactComponent } from './createComponent'
export { createOverlayComponent } from './createOverlayComponent'
34 changes: 34 additions & 0 deletions libs/output-target-react/react-component-lib/interfaces.ts
Original file line number Diff line number Diff line change
@@ -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>
}
Loading

0 comments on commit e3b1c70

Please sign in to comment.