diff --git a/libs/portal-integration-angular/src/lib/core/components/button-dialog/button-dialog.component.html b/libs/portal-integration-angular/src/lib/core/components/button-dialog/button-dialog.component.html index ecab425b6..b554ec512 100644 --- a/libs/portal-integration-angular/src/lib/core/components/button-dialog/button-dialog.component.html +++ b/libs/portal-integration-angular/src/lib/core/components/button-dialog/button-dialog.component.html @@ -6,16 +6,17 @@ <div class="w-full flex-row justify-content-end flex flex-wrap gap-2"> <ng-container *ngFor="let button of leftCustomButtons; index as i"> <div [ngStyle]="{'margin-right': (i === leftCustomButtons.length - 1) ? 'auto' : 0}"> - <ng-container *ngTemplateOutlet="customButton; context: {button: button}"> </ng-container> + <ng-container *ngTemplateOutlet="customButtonTemplate; context: {button: button}"> </ng-container> </div> </ng-container> <ng-container *ngFor="let button of rightCustomButtons"> <div> - <ng-container *ngTemplateOutlet="customButton; context: {button: button}"> </ng-container> + <ng-container *ngTemplateOutlet="customButtonTemplate; context: {button: button}"> </ng-container> </div> </ng-container> <div> <button + #secondaryButton id="{{dialogData.config.secondaryButtonDetails?.id ?? 'buttonDialogSecondaryButton'}}" pButton *ngIf="dialogData.config.secondaryButtonIncluded" @@ -30,9 +31,9 @@ </div> <div> <button + #primaryButton id="{{dialogData.config.primaryButtonDetails?.id ?? 'buttonDialogPrimaryButton'}}" pButton - autofocus [icon]="dialogData.config.primaryButtonDetails!.icon !== undefined ? dialogData.config.primaryButtonDetails!.icon : ''" (click)="primaryButtonAction()" [label]="dialogData.config.primaryButtonDetails!.key | translate:dialogData.config.primaryButtonDetails?.parameters" @@ -45,8 +46,9 @@ </div> </div> -<ng-template #customButton let-button="button"> +<ng-template #customButtonTemplate let-button="button"> <button + #customButton id="{{button.id}}" pButton [icon]="button.icon !== undefined ? button.icon : ''" diff --git a/libs/portal-integration-angular/src/lib/core/components/button-dialog/button-dialog.component.ts b/libs/portal-integration-angular/src/lib/core/components/button-dialog/button-dialog.component.ts index fb35a2153..b21fc57c3 100644 --- a/libs/portal-integration-angular/src/lib/core/components/button-dialog/button-dialog.component.ts +++ b/libs/portal-integration-angular/src/lib/core/components/button-dialog/button-dialog.component.ts @@ -1,11 +1,15 @@ import { + AfterViewInit, Component, ComponentRef, + ElementRef, EventEmitter, Input, OnInit, Output, + QueryList, ViewChild, + ViewChildren, ViewContainerRef, } from '@angular/core' import { BehaviorSubject, Observable, from, isObservable, map, of, startWith, withLatestFrom } from 'rxjs' @@ -33,7 +37,7 @@ import { templateUrl: './button-dialog.component.html', styleUrls: ['./button-dialog.component.scss'], }) -export class ButtonDialogComponent implements OnInit { +export class ButtonDialogComponent implements OnInit, AfterViewInit { defaultPrimaryButtonDetails: ButtonDialogButtonDetails = { key: 'OCX_BUTTON_DIALOG.CONFIRM', } @@ -58,6 +62,19 @@ export class ButtonDialogComponent implements OnInit { @ViewChild('container', { static: true, read: ViewContainerRef }) dialogHost!: ViewContainerRef + @ViewChild('primaryButton', { static: true, read: ViewContainerRef }) + primaryButton!: ViewContainerRef + _secondaryButton!: ViewContainerRef + @ViewChild('secondaryButton', { static: false, read: ViewContainerRef }) + set secondaryButton(content: ViewContainerRef) { + if (content) { + this._secondaryButton = content + } + } + get secondaryButton(): ViewContainerRef { + return this._secondaryButton + } + @ViewChildren('customButton') customButtons!: QueryList<ElementRef> dialogData: ButtonDialogData = this.defaultDialogData componentRef!: ComponentRef<any> @@ -71,6 +88,20 @@ export class ButtonDialogComponent implements OnInit { public dynamicDialogConfig: DynamicDialogConfig, public dynamicDialogRef: DynamicDialogRef ) {} + ngAfterViewInit(): void { + if (this.dialogData.config.autoFocusButton === 'primary' || !this.dialogData.config.autoFocusButton) { + this.primaryButton.element.nativeElement.focus() + } else if (this.dialogData.config.autoFocusButton === 'secondary') { + this.secondaryButton.element.nativeElement.focus() + } else if (this.dialogData.config.autoFocusButton === 'custom') { + const button = this.customButtons.find((customButton) => { + return customButton.nativeElement.id === this.dialogData.config.autoFocusButtonCustomId + }) + setTimeout(() => { + button?.nativeElement.focus() + }) + } + } ngOnInit(): void { this.loadComponent() @@ -113,6 +144,8 @@ export class ButtonDialogComponent implements OnInit { if (!!dialogConfig.secondaryButtonDetails && !!dialogConfig.secondaryButtonDetails.key) { this.dialogData.config.secondaryButtonDetails = dialogConfig.secondaryButtonDetails } + this.dialogData.config.autoFocusButton = dialogConfig.autoFocusButton + this.dialogData.config.autoFocusButtonCustomId = dialogConfig.autoFocusButtonCustomId } if (dynamicConfigData.component) { this.dialogData.component = dynamicConfigData.component diff --git a/libs/portal-integration-angular/src/lib/model/button-dialog.ts b/libs/portal-integration-angular/src/lib/model/button-dialog.ts index 4d40364eb..dfca97956 100644 --- a/libs/portal-integration-angular/src/lib/model/button-dialog.ts +++ b/libs/portal-integration-angular/src/lib/model/button-dialog.ts @@ -1,5 +1,6 @@ import { Type } from '@angular/core' import { PrimeIcon } from '@onecx/angular-accelerator' +import { DialogButton } from '../services/portal-dialog.service' /** * Object describing details for button rendering containing key for translation, optional icon and optional parameters for translation @@ -39,6 +40,8 @@ export interface ButtonDialogConfig { secondaryButtonIncluded?: boolean secondaryButtonDetails?: ButtonDialogButtonDetails customButtons?: ButtonDialogCustomButtonDetails[] + autoFocusButton?: DialogButton + autoFocusButtonCustomId?: string } export interface ButtonDialogData { diff --git a/libs/portal-integration-angular/src/lib/services/portal-dialog-service.stories.ts b/libs/portal-integration-angular/src/lib/services/portal-dialog-service.stories.ts index 3895a038a..aa2bc1c02 100644 --- a/libs/portal-integration-angular/src/lib/services/portal-dialog-service.stories.ts +++ b/libs/portal-integration-angular/src/lib/services/portal-dialog-service.stories.ts @@ -1,10 +1,10 @@ -import { Component, Input, importProvidersFrom } from '@angular/core' +import { Component, EventEmitter, Input, OnInit, importProvidersFrom } from '@angular/core' import { Meta, applicationConfig, argsToTemplate, componentWrapperDecorator, moduleMetadata } from '@storybook/angular' import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog' import { BrowserModule } from '@angular/platform-browser' import { ButtonModule } from 'primeng/button' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { PortalDialogService } from './portal-dialog.service' +import { DialogCustomButtonsDisabled, PortalDialogService } from './portal-dialog.service' import { ButtonDialogComponent } from '../core/components/button-dialog/button-dialog.component' import { StorybookTranslateModule } from '../core/storybook-translate.module' import { DialogMessageContentComponent } from '../core/components/button-dialog/dialog-message-content/dialog-message-content.component' @@ -12,7 +12,7 @@ import { PrimeIcons } from 'primeng/api' import { TooltipModule } from 'primeng/tooltip' @Component({ - selector: 'ocx-ocx-button-dialog-with-portal-dialog-service', + selector: 'ocx-button-dialog-with-portal-dialog-service', template: `<button (click)="openDialog()">Open dialog</button>`, }) class ButtonDialogWithPortalDialogServiceComponent { @@ -88,6 +88,27 @@ export const CustomData = { }, } +export const CustomAutofocus = { + render: (args: any) => ({ + props: { + ...args, + }, + template: ` + <ocx-button-dialog-with-portal-dialog-service ${argsToTemplate(args)}> + </ocx-button-dialog-with-portal-dialog-service> + `, + }), + args: { + title: 'Custom title', + messageOrComponent: 'Custom message', + primaryKey: 'Primary Button', + secondaryKey: 'Secondary Button', + extras: { + autoFocusButton: 'secondary', + }, + }, +} + export const CustomDataWithExtendedButtons = { render: (args: any) => ({ props: { @@ -153,3 +174,97 @@ export const ComponentDisplayed = { extras: {}, }, } + +@Component({ + selector: 'ocx-my-component-to-display', + template: `<p>Hello, its my component to display custom buttons</p>`, +}) +class ComponentToDisplayCustomButtonsComponent implements DialogCustomButtonsDisabled, OnInit { + customButtonEnabled: EventEmitter<{ id: string; enabled: boolean }> = new EventEmitter() + + ngOnInit(): void { + this.customButtonEnabled.emit({ id: 'custom1', enabled: true }) + } +} + +export const CustomButtons = { + render: (args: any) => ({ + props: { + ...args, + }, + template: ` + <ocx-button-dialog-with-portal-dialog-service ${argsToTemplate(args)}> + </ocx-button-dialog-with-portal-dialog-service> + `, + }), + args: { + title: 'Custom title', + messageOrComponent: { + type: ComponentToDisplayCustomButtonsComponent, + }, + primaryKey: { + key: 'PRIMARY_KEY', + icon: PrimeIcons.BOOKMARK, + tooltipKey: 'TOOLTIP_KEY', + tooltipPosition: 'right', + }, + secondaryKey: { + key: 'SECONDARY_KEY', + icon: PrimeIcons.SEARCH, + tooltipKey: 'TOOLTIP_KEY', + tooltipPosition: 'left', + }, + extras: { + customButtons: [ + { + id: 'custom1', + alignment: 'right', + key: 'CUSTOM_KEY', + icon: 'pi pi-times', + }, + ], + }, + }, +} + +export const CustomButtonsWithAutofocus = { + render: (args: any) => ({ + props: { + ...args, + }, + template: ` + <ocx-button-dialog-with-portal-dialog-service ${argsToTemplate(args)}> + </ocx-button-dialog-with-portal-dialog-service> + `, + }), + args: { + title: 'Custom title', + messageOrComponent: { + type: ComponentToDisplayCustomButtonsComponent, + }, + primaryKey: { + key: 'PRIMARY_KEY', + icon: PrimeIcons.BOOKMARK, + tooltipKey: 'TOOLTIP_KEY', + tooltipPosition: 'right', + }, + secondaryKey: { + key: 'SECONDARY_KEY', + icon: PrimeIcons.SEARCH, + tooltipKey: 'TOOLTIP_KEY', + tooltipPosition: 'left', + }, + extras: { + customButtons: [ + { + id: 'custom1', + alignment: 'right', + key: 'CUSTOM_KEY', + icon: 'pi pi-times', + }, + ], + autoFocusButton: 'custom', + autoFocusButtonCustomId: 'custom1', + }, + }, +} diff --git a/libs/portal-integration-angular/src/lib/services/portal-dialog.service.ts b/libs/portal-integration-angular/src/lib/services/portal-dialog.service.ts index ef47ac77e..88546314a 100644 --- a/libs/portal-integration-angular/src/lib/services/portal-dialog.service.ts +++ b/libs/portal-integration-angular/src/lib/services/portal-dialog.service.ts @@ -211,6 +211,7 @@ type Component<T extends unknown> = unknown extends T inputs?: Record<string, unknown> } +export type DialogButton = 'primary' | 'secondary' | 'custom' export type DialogStateButtonClicked = 'primary' | 'secondary' | 'custom' /** @@ -225,6 +226,8 @@ export type DialogState<T> = { export type PortalDialogConfig = { showXButton?: boolean customButtons?: ButtonDialogCustomButtonDetails[] + autoFocusButton?: DialogButton + autoFocusButtonCustomId?: string ariaLabelledBy?: string width?: string height?: string @@ -250,7 +253,10 @@ export type PortalDialogConfig = { @Injectable({ providedIn: 'any' }) export class PortalDialogService { - constructor(private dialogService: DialogService, private translateService: TranslateService) {} + constructor( + private dialogService: DialogService, + private translateService: TranslateService + ) {} /** * @deprecated @@ -443,6 +449,8 @@ export class PortalDialogService { customButtons: dialogOptions.customButtons?.map( (button) => this.buttonDetailsOrTranslationKey(button) as ButtonDialogCustomButtonDetails ), + autoFocusButton: dialogOptions.autoFocusButton, + autoFocusButtonCustomId: dialogOptions.autoFocusButtonCustomId, }, componentData: componentToRender.inputs, } @@ -454,6 +462,7 @@ export class PortalDialogService { data: dynamicDialogDataConfig, closable: dialogOptions.showXButton && secondaryButtonTranslationKeyOrDetails !== undefined, ...dialogOptions, + focusOnShow: false, }).onClose }) )