Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
fix: assign correct described-by id to form input and control helpers
Browse files Browse the repository at this point in the history
 * provide another service for creating container/control ID
 * set correct role to elements
 * set describe by id and link it to control helper/error/success

Signed-off-by: Bozhidar Dryanovski <bozhidar.dryanovski@gmail.com>
  • Loading branch information
bdryanovski committed Mar 16, 2021
1 parent 2ffead7 commit bcae31d
Show file tree
Hide file tree
Showing 21 changed files with 631 additions and 181 deletions.
53 changes: 35 additions & 18 deletions packages/angular/golden/clr-angular.d.ts
Expand Up @@ -48,10 +48,12 @@ export declare const CLR_VERTICAL_NAV_DIRECTIVES: Type<any>[];

export declare const CLR_WIZARD_DIRECTIVES: any[];

export declare abstract class ClrAbstractContainer implements DynamicWrapper, OnDestroy {
export declare abstract class ClrAbstractContainer implements DynamicWrapper, OnDestroy, AfterContentInit {
_dynamic: boolean;
control: NgControl;
protected controlClassService: ControlClassService;
controlErrorComponent: ClrControlError;
controlHelperComponent: ClrControlHelper;
controlSuccessComponent: ClrControlSuccess;
protected ifControlStateService: IfControlStateService;
label: ClrLabel;
Expand All @@ -64,6 +66,7 @@ export declare abstract class ClrAbstractContainer implements DynamicWrapper, On
constructor(ifControlStateService: IfControlStateService, layoutService: LayoutService, controlClassService: ControlClassService, ngControlService: NgControlService);
addGrid(): boolean;
controlClass(): string;
ngAfterContentInit(): void;
ngOnDestroy(): void;
}

Expand Down Expand Up @@ -267,14 +270,17 @@ export declare class ClrCheckbox extends WrappedFormControl<ClrCheckboxWrapper>
ngOnInit(): void;
}

export declare class ClrCheckboxContainer extends ClrAbstractContainer {
export declare class ClrCheckboxContainer extends ClrAbstractContainer implements AfterContentInit {
checkboxes: QueryList<ClrCheckbox>;
set clrInline(value: boolean | string);
get clrInline(): boolean | string;
protected controlClassService: ControlClassService;
protected ifControlStateService: IfControlStateService;
protected layoutService: LayoutService;
protected ngControlService: NgControlService;
role: string;
constructor(layoutService: LayoutService, controlClassService: ControlClassService, ngControlService: NgControlService, ifControlStateService: IfControlStateService);
ngAfterContentInit(): void;
}

export declare class ClrCheckboxModule {
Expand Down Expand Up @@ -452,19 +458,25 @@ export declare class ClrControl extends WrappedFormControl<ClrControlContainer>
export declare class ClrControlContainer extends ClrAbstractContainer {
}

export declare class ClrControlError {
controlIdService: ControlIdService;
constructor(controlIdService: ControlIdService);
export declare class ClrControlError extends ClrAbstractControl {
protected containerIdService: ContainerIdService;
protected controlIdService: ControlIdService;
controlIdSuffix: string;
constructor(controlIdService: ControlIdService, containerIdService: ContainerIdService);
}

export declare class ClrControlHelper {
controlIdService: ControlIdService;
constructor(controlIdService: ControlIdService);
export declare class ClrControlHelper extends ClrAbstractControl {
protected containerIdService: ContainerIdService;
protected controlIdService: ControlIdService;
controlIdSuffix: string;
constructor(controlIdService: ControlIdService, containerIdService: ContainerIdService);
}

export declare class ClrControlSuccess {
controlIdService: ControlIdService;
constructor(controlIdService: ControlIdService);
export declare class ClrControlSuccess extends ClrAbstractControl {
protected containerIdService: ContainerIdService;
protected controlIdService: ControlIdService;
controlIdSuffix: string;
constructor(controlIdService: ControlIdService, containerIdService: ContainerIdService);
}

export declare class ClrDatagrid<T = any> implements AfterContentInit, AfterViewInit, OnDestroy {
Expand Down Expand Up @@ -847,26 +859,29 @@ export declare class ClrDatalistModule {
export declare class ClrDataModule {
}

export declare class ClrDateContainer implements DynamicWrapper, OnDestroy, AfterViewInit {
export declare class ClrDateContainer implements DynamicWrapper, OnDestroy, AfterViewInit, AfterContentInit {
_dynamic: boolean;
set actionButton(button: ElementRef);
set clrPosition(position: string);
commonStrings: ClrCommonStringsService;
control: NgControl;
controlErrorComponent: ClrControlError;
controlHelperComponent: ClrControlHelper;
controlSuccessComponent: ClrControlSuccess;
focus: boolean;
get isEnabled(): boolean;
get isInputDateDisabled(): boolean;
label: ClrLabel;
get open(): boolean;
get popoverPosition(): ClrPopoverPosition;
showHelper: boolean;
showInvalid: boolean;
showValid: boolean;
get showHelper(): boolean;
get showInvalid(): boolean;
get showValid(): boolean;
state: CONTROL_STATE;
constructor(toggleService: ClrPopoverToggleService, dateNavigationService: DateNavigationService, datepickerEnabledService: DatepickerEnabledService, dateFormControlService: DateFormControlService, commonStrings: ClrCommonStringsService, focusService: FocusService, viewManagerService: ViewManagerService, controlClassService: ControlClassService, layoutService: LayoutService, ngControlService: NgControlService, ifControlStateService: IfControlStateService);
addGrid(): boolean;
controlClass(): string;
ngAfterContentInit(): void;
ngAfterViewInit(): void;
ngOnDestroy(): void;
ngOnInit(): void;
Expand Down Expand Up @@ -1462,14 +1477,17 @@ export declare class ClrRadio extends WrappedFormControl<ClrRadioWrapper> {
constructor(vcr: ViewContainerRef, injector: Injector, control: NgControl, renderer: Renderer2, el: ElementRef);
}

export declare class ClrRadioContainer extends ClrAbstractContainer {
export declare class ClrRadioContainer extends ClrAbstractContainer implements AfterContentInit {
set clrInline(value: boolean | string);
get clrInline(): boolean | string;
protected controlClassService: ControlClassService;
protected ifControlStateService: IfControlStateService;
protected layoutService: LayoutService;
protected ngControlService: NgControlService;
radios: QueryList<ClrRadio>;
role: string;
constructor(layoutService: LayoutService, controlClassService: ControlClassService, ngControlService: NgControlService, ifControlStateService: IfControlStateService);
ngAfterContentInit(): void;
}

export declare class ClrRadioModule {
Expand Down Expand Up @@ -2239,7 +2257,7 @@ export declare const TOGGLE_SERVICE_PROVIDER: {

export declare function ToggleServiceFactory(): BehaviorSubject<boolean>;

export declare class WrappedFormControl<W extends DynamicWrapper> implements OnInit, AfterViewInit, OnDestroy {
export declare class WrappedFormControl<W extends DynamicWrapper> implements OnInit, OnDestroy {
_id: string;
protected controlIdService: ControlIdService;
protected el: ElementRef<any>;
Expand All @@ -2253,7 +2271,6 @@ export declare class WrappedFormControl<W extends DynamicWrapper> implements OnI
protected wrapperType: Type<W>;
constructor(vcr: ViewContainerRef, wrapperType: Type<W>, injector: Injector, ngControl: NgControl, renderer: Renderer2, el: ElementRef);
protected getProviderFromContainer<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T): T;
ngAfterViewInit(): void;
ngOnDestroy(): void;
ngOnInit(): void;
triggerValidation(): void;
Expand Down
@@ -1,16 +1,18 @@
/**
* Copyright (c) 2016-2020 VMware, Inc. All Rights Reserved.
* Copyright (c) 2016-2021 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { Component, Input, Optional } from '@angular/core';
import { AfterContentInit, Component, ContentChildren, Input, Optional, QueryList } from '@angular/core';

import { ControlClassService } from '../common/providers/control-class.service';
import { NgControlService } from '../common/providers/ng-control.service';
import { ClrAbstractContainer } from '../common/abstract-container';
import { LayoutService } from '../common/providers/layout.service';
import { IfControlStateService } from '../common/if-control-state/if-control-state.service';
import { ContainerIdService } from '../common/providers/container-id.service';
import { ClrCheckbox } from './checkbox';

@Component({
selector: 'clr-checkbox-container,clr-toggle-container',
Expand Down Expand Up @@ -44,11 +46,15 @@ import { IfControlStateService } from '../common/if-control-state/if-control-sta
'[class.clr-form-control]': 'true',
'[class.clr-form-control-disabled]': 'control?.disabled',
'[class.clr-row]': 'addGrid()',
'[attr.role]': 'role',
},
providers: [IfControlStateService, NgControlService, ControlClassService],
providers: [IfControlStateService, NgControlService, ControlClassService, ContainerIdService],
})
export class ClrCheckboxContainer extends ClrAbstractContainer {
export class ClrCheckboxContainer extends ClrAbstractContainer implements AfterContentInit {
private inline = false;
public role: string;

@ContentChildren(ClrCheckbox, { descendants: true }) checkboxes: QueryList<ClrCheckbox>;

constructor(
@Optional() protected layoutService: LayoutService,
Expand Down Expand Up @@ -76,4 +82,12 @@ export class ClrCheckboxContainer extends ClrAbstractContainer {
get clrInline() {
return this.inline;
}

ngAfterContentInit() {
this.setAriaRoles();
}

private setAriaRoles() {
this.role = this.checkboxes.length ? 'group' : null;
}
}
Expand Up @@ -388,7 +388,6 @@ export class ClrCombobox<T> extends WrappedFormControl<ClrComboboxContainer>
// This assignment is needed by the wrapper, so it can set
// the aria properties on the input element, not on the component.
this.el = this.textbox;
super.ngAfterViewInit();
}

ngOnDestroy() {
Expand Down
@@ -1,10 +1,10 @@
/**
* Copyright (c) 2016-2020 VMware, Inc. All Rights Reserved.
* Copyright (c) 2016-2021 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { ContentChild, Directive, OnDestroy, Optional } from '@angular/core';
import { AfterContentInit, ContentChild, Directive, OnDestroy, Optional } from '@angular/core';
import { NgControl } from '@angular/forms';

import { NgControlService } from './providers/ng-control.service';
Expand All @@ -15,9 +15,11 @@ import { ControlClassService } from './providers/control-class.service';
import { Subscription } from 'rxjs';
import { IfControlStateService, CONTROL_STATE } from './if-control-state/if-control-state.service';
import { ClrControlSuccess } from './success';
import { ClrControlError } from './error';
import { ClrControlHelper } from './helper';

@Directive()
export abstract class ClrAbstractContainer implements DynamicWrapper, OnDestroy {
export abstract class ClrAbstractContainer implements DynamicWrapper, OnDestroy, AfterContentInit {
protected subscriptions: Subscription[] = [];
_dynamic = false;
@ContentChild(ClrLabel, { static: false })
Expand All @@ -26,20 +28,43 @@ export abstract class ClrAbstractContainer implements DynamicWrapper, OnDestroy
private state: CONTROL_STATE;

/**
* Search for `ClrSuccessComponent` to know do we want to display clr-success or not
* Find Success, Error and Helper control components.
*/
@ContentChild(ClrControlSuccess) controlSuccessComponent: ClrControlSuccess;
@ContentChild(ClrControlError) controlErrorComponent: ClrControlError;
@ContentChild(ClrControlHelper) controlHelperComponent: ClrControlHelper;

/**
* @NOTE
* Helper control is a bit different than the others, it must be visible most of the time:
* - Helper must NOT be visible when CONTROL_STATE is not NONE and Success or Error components are \
* defined.
*
* For example user implement only Error control then if CONTROL_STATE is VALID then helper
* control must be visible.
*/
get showHelper(): boolean {
return this.state === CONTROL_STATE.NONE || (!this.showInvalid && !this.controlSuccessComponent);
// without existence of helper component there is no need of additional checks.
if (!!this.controlHelperComponent === false) {
return false;
}

return (
/* Helper Component exist and the state of the form is NONE (not touched) */
(!!this.controlHelperComponent && this.state === CONTROL_STATE.NONE) ||
/* or there is no success component but the state of the form is VALID - show helper information */
(!!this.controlSuccessComponent === false && this.state === CONTROL_STATE.VALID) ||
/* or there is no error component but the state of the form is INVALID - show helper information */
(!!this.controlErrorComponent === false && this.state === CONTROL_STATE.INVALID)
);
}

get showValid(): boolean {
return this.state === CONTROL_STATE.VALID && !!this.controlSuccessComponent;
}

get showInvalid(): boolean {
return this.state === CONTROL_STATE.INVALID;
return this.state === CONTROL_STATE.INVALID && !!this.controlErrorComponent;
}

constructor(
Expand All @@ -51,6 +76,7 @@ export abstract class ClrAbstractContainer implements DynamicWrapper, OnDestroy
this.subscriptions.push(
this.ifControlStateService.statusChanges.subscribe((state: CONTROL_STATE) => {
this.state = state;
this.updateHelpers();
})
);

Expand All @@ -61,17 +87,30 @@ export abstract class ClrAbstractContainer implements DynamicWrapper, OnDestroy
);
}

ngAfterContentInit() {
/**
* We gonna set the helper control state, after all or most of the components
* are ready - also this will trigger some initial flows into wrappers and controls,
* like locating IDs and setting attributes.
*/
this.updateHelpers();
}

ngOnDestroy() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}

controlClass() {
/**
* Decide what subtext to display:
* - element is valid but no success component is implemented - show helper
* - element is valid and success component is implemented - show success
* - container is valid but no success component is implemented - use helper class
* - container is valid and success component is implemented - use success class
*/
if (!this.controlSuccessComponent && this.state === CONTROL_STATE.VALID) {
return this.controlClassService.controlClass(CONTROL_STATE.NONE, this.addGrid());
}
/**
* Pass form control state and return string of classes to be applyed to the container.
* Pass form control state and return string of classes to be applied to the container.
*/
return this.controlClassService.controlClass(this.state, this.addGrid());
}
Expand All @@ -80,7 +119,14 @@ export abstract class ClrAbstractContainer implements DynamicWrapper, OnDestroy
return this.layoutService && !this.layoutService.isVertical();
}

ngOnDestroy() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
private updateHelpers() {
if (this.ngControlService) {
this.ngControlService.setHelpers({
show: this.showInvalid || this.showHelper || this.showValid,
showInvalid: this.showInvalid,
showHelper: this.showHelper,
showValid: this.showValid,
});
}
}
}
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2016-2021 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { Directive, Optional } from '@angular/core';
import { ControlIdService } from './providers/control-id.service';
import { ContainerIdService } from './providers/container-id.service';

export const CONTROL_SUFFIX: { [key: string]: string | null } = {
HELPER: 'helper',
ERROR: 'error',
SUCCESS: 'success',
NONE: null,
};

@Directive()
export abstract class ClrAbstractControl {
/**
* Hold the suffix for the ID
*/
public controlIdSuffix = 'abstract';

constructor(
@Optional() protected controlIdService: ControlIdService,
@Optional() protected containerIdService: ContainerIdService
) {}

public get id(): string {
/**
* The order of witch the id will be pick is:
* - Container ID (Wrapper arround multiple Controls like, Checkbox, Radio, ...)
* - Control ID (Single Control wrapper like Input, Textarea, Password, ...)
* - None
*/
if (this.containerIdService) {
return `${this.containerIdService.id}-${this.controlIdSuffix}`;
}

if (this.controlIdService) {
return `${this.controlIdService.id}-${this.controlIdSuffix}`;
}

return null;
}
}

0 comments on commit bcae31d

Please sign in to comment.