diff --git a/apps/browser/src/platform/popup/layout/popup-back.directive.ts b/apps/browser/src/platform/popup/layout/popup-back.directive.ts index ce8ebff5ec5..62d66ab87e5 100644 --- a/apps/browser/src/platform/popup/layout/popup-back.directive.ts +++ b/apps/browser/src/platform/popup/layout/popup-back.directive.ts @@ -1,8 +1,6 @@ -import { Directive, Optional } from "@angular/core"; +import { Directive, inject, model } from "@angular/core"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { BitActionDirective, ButtonLikeAbstraction } from "@bitwarden/components"; +import { BitActionDirective, FunctionReturningAwaitable } from "@bitwarden/components"; import { PopupRouterCacheService } from "../view-cache/popup-router-cache.service"; @@ -11,15 +9,10 @@ import { PopupRouterCacheService } from "../view-cache/popup-router-cache.servic selector: "[popupBackAction]", }) export class PopupBackBrowserDirective extends BitActionDirective { - constructor( - buttonComponent: ButtonLikeAbstraction, - private router: PopupRouterCacheService, - @Optional() validationService?: ValidationService, - @Optional() logService?: LogService, - ) { - super(buttonComponent, validationService, logService); - - // override `bitAction` input; the parent handles the rest - this.handler.set(() => this.router.back()); - } + private routerCacheService = inject(PopupRouterCacheService); + // Override the required input to make it optional since we set it automatically + override readonly handler = model( + () => this.routerCacheService.back(), + { alias: "popupBackAction" }, + ); } diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index e7f157328bb..aeeed6f65ce 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -343,7 +343,7 @@ export default { generator: "Generator", send: "Send", settings: "Settings", - labelWithNotification: (label: string) => `${label}: New Notification`, + labelWithNotification: (label: string | undefined) => `${label}: New Notification`, }); }, }, diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index 0b7304a3657..cc919a929a9 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -179,7 +179,7 @@ type Story = StoryObj< const Template: Story = { render: (args) => ({ props: args, - template: ` + template: /*html*/ `
@@ -191,7 +191,7 @@ const Template: Story = {
- +
diff --git a/libs/components/src/a11y/a11y-title.directive.ts b/libs/components/src/a11y/a11y-title.directive.ts index 80486ab9bcf..8bcff2cff4e 100644 --- a/libs/components/src/a11y/a11y-title.directive.ts +++ b/libs/components/src/a11y/a11y-title.directive.ts @@ -1,39 +1,24 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; +import { Directive, effect, ElementRef, input, Renderer2 } from "@angular/core"; @Directive({ selector: "[appA11yTitle]", }) -export class A11yTitleDirective implements OnInit { - // TODO: Skipped for signal migration because: - // Accessor inputs cannot be migrated as they are too complex. - @Input() set appA11yTitle(title: string) { - this.title = title; - this.setAttributes(); - } - - private title: string; - private originalTitle: string | null; - private originalAriaLabel: string | null; +export class A11yTitleDirective { + title = input.required({ alias: "appA11yTitle" }); constructor( private el: ElementRef, private renderer: Renderer2, - ) {} - - ngOnInit() { - this.originalTitle = this.el.nativeElement.getAttribute("title"); - this.originalAriaLabel = this.el.nativeElement.getAttribute("aria-label"); - this.setAttributes(); - } - - private setAttributes() { - if (this.originalTitle === null) { - this.renderer.setAttribute(this.el.nativeElement, "title", this.title); - } - if (this.originalAriaLabel === null) { - this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title); - } + ) { + const originalTitle = this.el.nativeElement.getAttribute("title"); + const originalAriaLabel = this.el.nativeElement.getAttribute("aria-label"); + effect(() => { + if (originalTitle === null) { + this.renderer.setAttribute(this.el.nativeElement, "title", this.title()); + } + if (originalAriaLabel === null) { + this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title()); + } + }); } } diff --git a/libs/components/src/anon-layout/anon-layout-wrapper.component.ts b/libs/components/src/anon-layout/anon-layout-wrapper.component.ts index 4b570df9814..33b90f7eb8a 100644 --- a/libs/components/src/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/components/src/anon-layout/anon-layout-wrapper.component.ts @@ -1,9 +1,7 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ChangeDetectorRef, Component, OnInit, inject, DestroyRef } from "@angular/core"; +import { ChangeDetectorRef, Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router"; -import { filter, switchMap, tap } from "rxjs"; +import { Subject, filter, of, switchMap, tap } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -53,13 +51,15 @@ export interface AnonLayoutWrapperData { imports: [AnonLayoutComponent, RouterModule], }) export class AnonLayoutWrapperComponent implements OnInit { - protected pageTitle: string; - protected pageSubtitle: string; - protected pageIcon: Icon; - protected showReadonlyHostname: boolean; - protected maxWidth: AnonLayoutMaxWidth; - protected hideCardWrapper: boolean; - protected hideIcon: boolean = false; + private destroy$ = new Subject(); + + protected pageTitle?: string | null; + protected pageSubtitle?: string | null; + protected pageIcon?: Icon | null; + protected showReadonlyHostname?: boolean | null; + protected maxWidth?: AnonLayoutMaxWidth | null; + protected hideCardWrapper?: boolean | null; + protected hideIcon?: boolean | null; constructor( private router: Router, @@ -85,7 +85,7 @@ export class AnonLayoutWrapperComponent implements OnInit { filter((event) => event instanceof NavigationEnd), // reset page data on page changes tap(() => this.resetPageData()), - switchMap(() => this.route.firstChild?.data || null), + switchMap(() => this.route.firstChild?.data || of(null)), takeUntilDestroyed(this.destroyRef), ) .subscribe((firstChildRouteData: Data | null) => { @@ -93,7 +93,7 @@ export class AnonLayoutWrapperComponent implements OnInit { }); } - private setAnonLayoutWrapperDataFromRouteData(firstChildRouteData: Data | null) { + private setAnonLayoutWrapperDataFromRouteData(firstChildRouteData?: Data | null) { if (!firstChildRouteData) { return; } diff --git a/libs/components/src/anon-layout/anon-layout.component.ts b/libs/components/src/anon-layout/anon-layout.component.ts index 355f3aef6eb..8b002cae7f0 100644 --- a/libs/components/src/anon-layout/anon-layout.component.ts +++ b/libs/components/src/anon-layout/anon-layout.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, @@ -56,8 +54,8 @@ export class AnonLayoutComponent implements OnInit, OnChanges { protected logo = BitwardenLogo; protected year: string; protected clientType: ClientType; - protected hostname: string; - protected version: string; + protected hostname?: string; + protected version?: string; protected hideYearAndVersion = false; diff --git a/libs/components/src/async-actions/bit-action.directive.ts b/libs/components/src/async-actions/bit-action.directive.ts index 2de8a16dd31..c89ba932583 100644 --- a/libs/components/src/async-actions/bit-action.directive.ts +++ b/libs/components/src/async-actions/bit-action.directive.ts @@ -1,6 +1,4 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, HostListener, model, Optional, inject, DestroyRef } from "@angular/core"; +import { DestroyRef, Directive, HostListener, inject, model, Optional } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { BehaviorSubject, finalize, tap } from "rxjs"; @@ -38,7 +36,7 @@ export class BitActionDirective { disabled = false; - readonly handler = model(undefined, { alias: "bitAction" }); + readonly handler = model.required({ alias: "bitAction" }); private readonly destroyRef = inject(DestroyRef); diff --git a/libs/components/src/async-actions/bit-submit.directive.ts b/libs/components/src/async-actions/bit-submit.directive.ts index e7911196fc3..2d662493cd3 100644 --- a/libs/components/src/async-actions/bit-submit.directive.ts +++ b/libs/components/src/async-actions/bit-submit.directive.ts @@ -1,6 +1,4 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnInit, Optional, input, inject, DestroyRef } from "@angular/core"; +import { DestroyRef, Directive, OnInit, Optional, inject, input } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormGroupDirective } from "@angular/forms"; import { BehaviorSubject, catchError, filter, of, switchMap } from "rxjs"; @@ -22,7 +20,7 @@ export class BitSubmitDirective implements OnInit { private _loading$ = new BehaviorSubject(false); private _disabled$ = new BehaviorSubject(false); - readonly handler = input(undefined, { alias: "bitSubmit" }); + readonly handler = input.required({ alias: "bitSubmit" }); readonly allowDisabledFormSubmit = input(false); @@ -63,7 +61,7 @@ export class BitSubmitDirective implements OnInit { ngOnInit(): void { this.formGroupDirective.statusChanges - .pipe(takeUntilDestroyed(this.destroyRef)) + ?.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((c) => { if (this.allowDisabledFormSubmit()) { this._disabled$.next(false); diff --git a/libs/components/src/async-actions/form-button.directive.ts b/libs/components/src/async-actions/form-button.directive.ts index dc8c095fd18..a1d28f627d5 100644 --- a/libs/components/src/async-actions/form-button.directive.ts +++ b/libs/components/src/async-actions/form-button.directive.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Directive, Optional, input } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index fca5a457fac..59a9492f8c8 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { NgClass } from "@angular/common"; import { Component, OnChanges, input } from "@angular/core"; import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; @@ -41,7 +39,7 @@ export class AvatarComponent implements OnChanges { private svgFontSize = 20; private svgFontWeight = 300; private svgSize = 48; - src: SafeResourceUrl; + src?: SafeResourceUrl; constructor(public sanitizer: DomSanitizer) {} @@ -56,8 +54,14 @@ export class AvatarComponent implements OnChanges { } private generate() { - let chars: string = null; - const upperCaseText = this.text()?.toUpperCase() ?? ""; + const color = this.color(); + const text = this.text(); + const id = this.id(); + if (!text && !color && !id) { + throw new Error("Must supply `text`, `color`, or `id` input."); + } + let chars: string | null = null; + const upperCaseText = text?.toUpperCase() ?? ""; chars = this.getFirstLetters(upperCaseText, this.svgCharCount); @@ -66,18 +70,17 @@ export class AvatarComponent implements OnChanges { } // If the chars contain an emoji, only show it. - if (chars.match(Utils.regexpEmojiPresentation)) { - chars = chars.match(Utils.regexpEmojiPresentation)[0]; + const emojiMatch = chars.match(Utils.regexpEmojiPresentation); + if (emojiMatch) { + chars = emojiMatch[0]; } let svg: HTMLElement; - let hexColor = this.color(); - - const id = this.id(); - if (!Utils.isNullOrWhitespace(this.color())) { + let hexColor = color ?? ""; + if (!Utils.isNullOrWhitespace(hexColor)) { svg = this.createSvgElement(this.svgSize, hexColor); - } else if (!Utils.isNullOrWhitespace(id)) { - hexColor = Utils.stringToColor(id.toString()); + } else if (!Utils.isNullOrWhitespace(id ?? "")) { + hexColor = Utils.stringToColor(id!.toString()); svg = this.createSvgElement(this.svgSize, hexColor); } else { hexColor = Utils.stringToColor(upperCaseText); @@ -95,7 +98,7 @@ export class AvatarComponent implements OnChanges { ); } - private getFirstLetters(data: string, count: number): string { + private getFirstLetters(data: string, count: number): string | null { const parts = data.split(" "); if (parts.length > 1) { let text = ""; diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.ts b/libs/components/src/breadcrumbs/breadcrumb.component.ts index 54678f3e4ee..783cb2655f7 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumb.component.ts @@ -1,7 +1,4 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore - -import { Component, EventEmitter, Output, TemplateRef, ViewChild, input } from "@angular/core"; +import { Component, EventEmitter, Output, TemplateRef, input, viewChild } from "@angular/core"; import { QueryParamsHandling } from "@angular/router"; @Component({ @@ -20,7 +17,7 @@ export class BreadcrumbComponent { @Output() click = new EventEmitter(); - @ViewChild(TemplateRef, { static: true }) content: TemplateRef; + readonly content = viewChild(TemplateRef); onClick(args: unknown) { this.click.next(args); diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.html b/libs/components/src/breadcrumbs/breadcrumbs.component.html index 820b100afd3..d062e82548e 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.html +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.html @@ -8,7 +8,7 @@ [queryParams]="breadcrumb.queryParams()" [queryParamsHandling]="breadcrumb.queryParamsHandling()" > - + } @else { } @if (!last) { @@ -46,11 +46,11 @@ [queryParams]="breadcrumb.queryParams()" [queryParamsHandling]="breadcrumb.queryParamsHandling()" > - + } @else { } } @@ -66,7 +66,7 @@ [queryParams]="breadcrumb.queryParams()" [queryParamsHandling]="breadcrumb.queryParamsHandling()" > - + } @else { } @if (!last) { diff --git a/libs/components/src/callout/callout.component.html b/libs/components/src/callout/callout.component.html index b990e57a767..b98679766d5 100644 --- a/libs/components/src/callout/callout.component.html +++ b/libs/components/src/callout/callout.component.html @@ -1,6 +1,6 @@