Skip to content

Commit

Permalink
add angular
Browse files Browse the repository at this point in the history
  • Loading branch information
hirsch88 committed Feb 21, 2024
1 parent c120536 commit 12bc958
Show file tree
Hide file tree
Showing 55 changed files with 2,640 additions and 14 deletions.
8 changes: 6 additions & 2 deletions libs/nx/src/executors/build-core/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { dirname, join } from 'path'
import replace from 'replace-in-file'
import { mkdir, readFile, rm, writeFile } from 'fs/promises'
import { copy } from 'fs-extra'
import { NEWLINE } from '../utils'

export default async function runExecutor(options: BuildCoreExecutorSchema) {
try {
Expand Down Expand Up @@ -115,10 +116,13 @@ async function cleanUp(options: BuildCoreExecutorSchema) {
}

async function addPackageJsonAndTypesToCustomElements(options: BuildCoreExecutorSchema) {
await copy(
const contentIndex = await readFile(join(options.projectRoot, 'components', 'index.d.ts'), 'utf-8')
const contentCustom = await readFile(
join(options.projectRoot, 'config', 'custom-elements', 'custom-elements.d.ts'),
join(options.projectRoot, 'components', 'custom-elements.d.ts'),
'utf-8',
)

await writeFile(join(options.projectRoot, 'components', 'index.d.ts'), [contentIndex, contentCustom].join(NEWLINE))
await copy(
join(options.projectRoot, 'config', 'custom-elements', 'package.json.tmp'),
join(options.projectRoot, 'components', 'package.json'),
Expand Down
5 changes: 5 additions & 0 deletions packages/components-angular/common/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "src/index.ts"
}
}
105 changes: 105 additions & 0 deletions packages/components-angular/common/src/directives/error.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* eslint-disable @angular-eslint/directive-class-suffix */
import { AfterViewInit, ChangeDetectorRef, Directive, HostBinding, Inject, Injector, Input } from '@angular/core'
import { AbstractControl, ControlContainer } from '@angular/forms'
import { BehaviorSubject } from 'rxjs'

import type { BaloiseDesignSystemAngularConfig } from '../utils/config'
import { raf } from '../utils/utils'
import { BalTokenConfig } from '../utils/token'

// @Component({
// selector: 'bal-ng-error',
// template: `<ng-content *ngIf="(ready | async) && hasError"></ng-content>`,
// styles: [
// `
// :host {
// display: inline-block;
// }
// `,
// ],
// })

@Directive({
selector: 'bal-ng-error',
})
export class BalNgErrorComponent implements AfterViewInit {
/**
* The name of form validator to show.
*/
@Input() error?: string

/**
* The name of the form control, which is registered in the form group.
*/
@HostBinding('attr.controlname')
@Input()
controlName?: string

constructor(
@Inject(Injector) protected injector: Injector,
@Inject(ChangeDetectorRef) protected cd: ChangeDetectorRef,
) {}

private controlContainer?: ControlContainer
private control?: AbstractControl | null
private config?: BaloiseDesignSystemAngularConfig
private invalidateOn: 'dirty' | 'touched' = 'touched'
ready = new BehaviorSubject(false)

ngAfterViewInit(): void {
raf(() => {
try {
this.controlContainer = this.injector.get<ControlContainer>(ControlContainer)
} catch {
/* No ControlContainer provided */
}

if (!this.controlContainer) {
return
}

try {
this.config = this.injector.get<BaloiseDesignSystemAngularConfig>(BalTokenConfig)
} catch {
/* No config provided */
}

this.invalidateOn = this.config?.forms?.invalidateOn || this.invalidateOn

if (this.controlName) {
this.control = this.controlContainer.control?.get(this.controlName)
if (!this.control) {
console.warn('[BalNgErrorComponent] Could not find the given controlName in the form control container')
} else {
this.ready.next(true)
this.cd.detectChanges()
}
} else {
console.warn('[BalNgErrorComponent] Please provide a controlName')
}
})
}

get hasError(): boolean {
if (this.controlName && this.controlContainer && this.config && this.control) {
if (!this.control[this.invalidateOn]) {
return false
}

if (!this.error) {
return this.control.invalid
}

if (this.control.errors) {
const validationErrorKeys = Object.keys(this.control.errors).filter(k => k !== 'errorType')
const hasValidationErrors = validationErrorKeys.length > 0

if (hasValidationErrors) {
return validationErrorKeys[0] === this.error // isFirstKeyOurError
}
}
}

return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable @angular-eslint/directive-class-suffix */
import { AfterViewInit, Directive, ElementRef } from '@angular/core'

@Directive({
selector: '[balAutoFocus]',
})
export class BalAutoFocus implements AfterViewInit {
constructor(protected elementRef: ElementRef) {}

ngAfterViewInit() {
this.setFocus()
}

setFocus() {
const el = this.elementRef.nativeElement
if (el) {
if (el.setFocus) {
el.setFocus()
} else {
el.focus()
}
}
}
}
27 changes: 27 additions & 0 deletions packages/components-angular/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export type { BaloiseDesignSystemAngularConfig } from './utils/config'
export type { ProxyComponent } from './utils/utils'

export { AngularDelegate } from './providers/angular-delegate'
export { BalBreakpointsService } from './providers/breakpoints.service'
export { BalConfigService } from './providers/config.service'
export { BalModalService } from './providers/modal.service'
export { BalOrientationService } from './providers/orientation.service'
export { BalSnackbarService } from './providers/snackbar.service'
export { BalToastService } from './providers/toast.service'

export { BalNgErrorComponent } from './directives/error.component'
export { BalAutoFocus } from './directives/focus.directive'

export {
BalTokenUserConfig,
BalTokenConfig,
BalTokenToast,
BalTokenSnackbar,
BalTokenModal,
BalTokenBreakpoints,
BalTokenBreakpointSubject,
BalTokenDevice,
BalTokenOrientationSubject,
} from './utils/token'

export { raf, parseCustomEvent, element } from './utils/utils'
108 changes: 108 additions & 0 deletions packages/components-angular/common/src/providers/angular-delegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { ApplicationRef, ComponentFactoryResolver, Injectable, Injector, NgZone, ViewContainerRef } from '@angular/core'
import type { FrameworkDelegate } from '@baloise/ds-components'

@Injectable()
export class AngularDelegate {
constructor(
private zone: NgZone,
private appRef: ApplicationRef,
) {}

create(resolver: ComponentFactoryResolver, injector: Injector, location?: ViewContainerRef) {
return new AngularFrameworkDelegate(resolver, injector, location, this.appRef, this.zone)
}
}

export class AngularFrameworkDelegate implements FrameworkDelegate {
private elRefMap = new WeakMap<HTMLElement, any>()
private elEventsMap = new WeakMap<HTMLElement, () => void>()

constructor(
private resolver: ComponentFactoryResolver,
private injector: Injector,
private location: ViewContainerRef | undefined,
private appRef: ApplicationRef,
private zone: NgZone,
) {}

attachViewToDom(container: any, component: any, params?: any, cssClasses?: string[]): Promise<any> {
return this.zone.run(() => {
return new Promise(resolve => {
const el = attachView(
this.zone,
this.resolver,
this.injector,
this.location,
this.appRef,
this.elRefMap,
this.elEventsMap,
container,
component,
params,
cssClasses,
)
resolve(el)
})
})
}

removeViewFromDom(_container: any, component: any): Promise<void> {
return this.zone.run(() => {
return new Promise(resolve => {
const componentRef = this.elRefMap.get(component)
if (componentRef) {
componentRef.destroy()
this.elRefMap.delete(component)
const unbindEvents = this.elEventsMap.get(component)
if (unbindEvents) {
unbindEvents()
this.elEventsMap.delete(component)
}
}
resolve()
})
})
}
}

export const attachView = (
_zone: NgZone,
resolver: ComponentFactoryResolver,
injector: Injector,
location: ViewContainerRef | undefined,
appRef: ApplicationRef,
elRefMap: WeakMap<HTMLElement, any>,
_elEventsMap: WeakMap<HTMLElement, () => void>,
container: any,
component: any,
params: any,
cssClasses: string[] | undefined,
) => {
const factory = resolver.resolveComponentFactory(component)
const childInjector = Injector.create({
providers: [],
parent: injector,
})
const componentRef = location
? location.createComponent(factory, location.length, childInjector)
: factory.create(childInjector)

const instance = componentRef.instance
const hostElement = componentRef.location.nativeElement
if (params) {
Object.assign(instance as any, params)
}
if (cssClasses) {
for (const clazz of cssClasses) {
hostElement.classList.add(clazz)
}
}
container.appendChild(hostElement)

if (!location) {
appRef.attachView(componentRef.hostView)
}
componentRef.changeDetectorRef.reattach()
elRefMap.set(hostElement, componentRef)
return hostElement
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ApplicationRef, Inject, Injectable } from '@angular/core'
import { BehaviorSubject, Observable } from 'rxjs'
import { map } from 'rxjs/operators'

import type {
BalBreakpointObserver,
BalBreakpoints,
BalBreakpointSubject,
BalBreakpointsUtil,
} from '@baloise/ds-components'

import { BalTokenBreakpointSubject, BalTokenBreakpoints } from '../utils/token'

@Injectable({
providedIn: 'root',
})
export class BalBreakpointsService implements BalBreakpointObserver {
private _breakpoints$!: BehaviorSubject<BalBreakpoints>

state$: Observable<BalBreakpoints>
mobile$: Observable<boolean>
tablet$: Observable<boolean>
touch$: Observable<boolean>
desktop$: Observable<boolean>
highDefinition$: Observable<boolean>
widescreen$: Observable<boolean>
fullhd$: Observable<boolean>

constructor(
private app: ApplicationRef,
@Inject(BalTokenBreakpoints) private breakpoints: BalBreakpointsUtil,
@Inject(BalTokenBreakpointSubject) private breakpointSubject: BalBreakpointSubject,
) {
this._breakpoints$ = new BehaviorSubject<BalBreakpoints>(this.breakpoints.toObject())

this.state$ = this._breakpoints$.asObservable()
this.mobile$ = this._breakpoints$.asObservable().pipe(map(breakpoints => breakpoints.mobile))
this.tablet$ = this._breakpoints$.asObservable().pipe(map(breakpoints => breakpoints.tablet))
this.touch$ = this._breakpoints$.asObservable().pipe(map(breakpoints => breakpoints.touch))
this.desktop$ = this._breakpoints$.asObservable().pipe(map(breakpoints => breakpoints.desktop))
this.highDefinition$ = this._breakpoints$.asObservable().pipe(map(breakpoints => breakpoints.highDefinition))
this.widescreen$ = this._breakpoints$.asObservable().pipe(map(breakpoints => breakpoints.widescreen))
this.fullhd$ = this._breakpoints$.asObservable().pipe(map(breakpoints => breakpoints.fullhd))

this.breakpointSubject.attach(this)
}

breakpointListener(breakpoints: BalBreakpoints): void {
this._breakpoints$.next(breakpoints)
this.app.tick()
}

ngOnDestroy() {
this.breakpointSubject.detach(this)
}

get value(): BalBreakpoints {
if (this._breakpoints$) {
return this._breakpoints$.getValue()
}
return this.breakpoints.toObject()
}
}
Loading

0 comments on commit 12bc958

Please sign in to comment.