diff --git a/.travis.yml b/.travis.yml index 970678b21..f862f9c75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,4 +31,4 @@ deploy: github_token: $GITHUB_TOKEN keep_history: true on: - branch: master + branch: sprint11-staging diff --git a/lighthouserc.json b/lighthouserc.json index 91bf649b9..040bccfe3 100644 --- a/lighthouserc.json +++ b/lighthouserc.json @@ -11,7 +11,7 @@ "assertions": { "first-contentful-paint": "off", "categories:performance": ["error", {"minScore": 0}], - "categories:accessibility": ["error", {"minScore": 0.8}], + "categories:accessibility": ["error", {"minScore": 0.9}], "categories:best-practices": ["error", {"minScore": 0}], "categories:seo": ["error", {"minScore": 0}], "categories:pwa":["error", {"minScore": 0}] diff --git a/package.json b/package.json index 4cd847248..89ceee5bd 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "git", "url": "git+https://github.com/TAMULib/weaver-components.git" }, - "version": "1.4.0", + "version": "1.5.0", "private": false, "license": "MIT", "bin": { @@ -45,7 +45,7 @@ "test:e2e": "ng e2e", "test:unit": "ng test --no-watch", "test:watch": "ng test", - "test:report": "cat ./static/weaver-components/reports/coverage/weaver-components/lcov.info | ./node_modules/coveralls/bin/coveralls.js", + "test:report": "cat ./static/weaver-components/reports/coverage/wvr-elements/lcov.info | ./node_modules/coveralls/bin/coveralls.js", "test:coverage": "ng test --no-watch --code-coverage", "test:ci": "npm run test:coverage && npm run test:audit" }, diff --git a/projects/wvr-elements/package.json b/projects/wvr-elements/package.json index f42c4a6da..5089092a9 100644 --- a/projects/wvr-elements/package.json +++ b/projects/wvr-elements/package.json @@ -1,6 +1,6 @@ { "name": "@wvr/elements", - "version": "1.4.0", + "version": "1.5.0", "description": "Collection of angular components for Weaver's Custom Web Component UI", "author": "Texas A&M University Libraries", "private": false, diff --git a/projects/wvr-elements/src/lib/core/component-registry.service.spec.ts b/projects/wvr-elements/src/lib/core/component-registry.service.spec.ts new file mode 100644 index 000000000..bd0c7f267 --- /dev/null +++ b/projects/wvr-elements/src/lib/core/component-registry.service.spec.ts @@ -0,0 +1,58 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { WvrBaseComponent } from '../shared/wvr-base.component'; +import { WvrItWorksComponent } from '../wvr-it-works/wvr-it-works.component'; + +import { ComponentRegistryService } from './component-registry.service'; + +describe('ComponentRegistryService', () => { + let service: ComponentRegistryService; + let component: WvrItWorksComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BrowserAnimationsModule], + declarations: [WvrItWorksComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents() + .catch(err => { console.error(err); }); + service = TestBed.inject(ComponentRegistryService); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WvrItWorksComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(service) + .toBeTruthy(); + }); + + it('should register a new component', () => { + expect(component.id >= 0) + .toBeTruthy(); + }); + + it('should retrieve components', () => { + expect(service.getComponent(component.id) === component) + .toBeTruthy(); + }); + + it('should retrieve components by element', () => { + // tslint:disable-next-line:no-string-literal + expect(service.getComponentByElement(component['_eRef'].nativeElement as HTMLElement) === component) + .toBeTruthy(); + }); + + it('should unregister components', () => { + service.unRegisterComponent(component.id); + expect(service.getComponent(component.id)) + .toBeUndefined(); + }); + +}); diff --git a/projects/wvr-elements/src/lib/core/component-registry.service.ts b/projects/wvr-elements/src/lib/core/component-registry.service.ts new file mode 100644 index 000000000..a28b489be --- /dev/null +++ b/projects/wvr-elements/src/lib/core/component-registry.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { WvrBaseComponent } from '../shared/wvr-base.component'; + +/** + * A registry for each WvrBaseComponent currently present on the page. + */ +@Injectable({ + providedIn: 'root' +}) +export class ComponentRegistryService { + + /** Incrementing index of all registered components. */ + private index = -1; + + /** Registry for all WvrBaseComponent. */ + private readonly registry: Map = new Map(); + + /** Adds a WvrBaseComponent to the registry. */ + register(component: WvrBaseComponent): number { + + // tslint:disable-next-line:no-string-literal + const element = (component['_eRef'].nativeElement as HTMLElement); + // tslint:disable-next-line:increment-decrement + this.registry.set(++this.index, component); + + return this.index; + } + + /** Removes a WvrBaseComponent from the registry. */ + unRegisterComponent(id: number | string): void { + this.registry.delete(id); + } + + /** Retrieves a WvrBaseComponent from the registry. */ + getComponent(id: number | string): WvrBaseComponent { + return this.registry.get(id); + } + + /** Retrieves a WvrBaseComponent from the registry by HTMLElement. */ + getComponentByElement(element: HTMLElement): WvrBaseComponent { + + const hasNativeId = element.hasAttribute('wvr-id'); + const htmlID = hasNativeId ? element.getAttribute('wvr-id') : element.getAttribute('id'); + + if (!htmlID) { + return; + } + + const id = parseInt(htmlID.replace(`${WvrBaseComponent.HTML_ID_BASE}-`, ''), 10); + + return this.getComponent(id); + } + +} diff --git a/projects/wvr-elements/src/lib/core/icon.service.ts b/projects/wvr-elements/src/lib/core/icon.service.ts index 749f8d49b..94e463ca9 100644 --- a/projects/wvr-elements/src/lib/core/icon.service.ts +++ b/projects/wvr-elements/src/lib/core/icon.service.ts @@ -6,11 +6,15 @@ import { AppConfig, APP_CONFIG } from '../shared/config/app-config'; import { Icon } from '../wvr-icon/icon'; import { IconSet } from '../wvr-icon/icon-set'; +/** + * A registry for each IconSet currently in use by any Icon Component. + */ @Injectable({ providedIn: 'root' }) export class IconService { + /** A registry of all icon sets. */ private readonly _iconRegister: Map = new Map(); constructor( @@ -19,16 +23,19 @@ export class IconService { ) { } + /** Registers an IconSet with the _iconRegister. */ registerIcons(icons: IconSet): void { this._iconRegister.set(icons.name, icons); } + /** Retrieves the specified icon from the _iconRegister. If the specified icon has not been retrieved previsouly it registers it. */ getIcon(set: string, name: string): Observable { const iSet = this.getOrSetIconSet(set); return this.getOrSetIcon(iSet, name).svg; } + /** Retireves the IconSet from the _iconRegister, or sets it if it has not been previously registered. */ private getOrSetIconSet(set: string): IconSet { let iSet = this._iconRegister.get(set); if (iSet) { @@ -43,6 +50,7 @@ export class IconService { return iSet; } + /** Retireves the Icon from the IconSet, or sets it if it has not been previously registered. */ private getOrSetIcon(set: IconSet, name: string): Icon { let icon: Icon = set.icons .find(i => i.name === name); @@ -58,6 +66,7 @@ export class IconService { return icon; } + /** Initialized the http request for the specified icon. */ private fetchIcon(set: IconSet, name: string): Observable { return this.http.get(`${this.appConfig.assetsUrl}/icons/${set.name}/${name}.svg`, { responseType: 'text' }) diff --git a/projects/wvr-elements/src/lib/core/mobile.service.ts b/projects/wvr-elements/src/lib/core/mobile.service.ts index 2eabb2825..97de1c20d 100644 --- a/projects/wvr-elements/src/lib/core/mobile.service.ts +++ b/projects/wvr-elements/src/lib/core/mobile.service.ts @@ -2,13 +2,18 @@ import { Injectable } from '@angular/core'; import { fromEvent, Observable } from 'rxjs'; import { delay, map, throttleTime } from 'rxjs/operators'; +/** + * A centralized uitlity for logic conerning mobile layout. + */ @Injectable({ providedIn: 'root' }) export class MobileService { + /** Indicates the layout state of the viewport. */ isMobileLayout: boolean; + /** An observable used for resize events. */ private readonly screenSizeChanged$: Observable; constructor() { @@ -23,6 +28,7 @@ export class MobileService { this.isMobileLayout = this.checkScreenSize(); } + /** A mapping method to map resize events to boolean. */ private readonly checkScreenSize = () => window.innerWidth < 767; } diff --git a/projects/wvr-elements/src/lib/core/wvr-animation.service.spec.ts b/projects/wvr-elements/src/lib/core/wvr-animation.service.spec.ts index 5fb215906..53c113f12 100644 --- a/projects/wvr-elements/src/lib/core/wvr-animation.service.spec.ts +++ b/projects/wvr-elements/src/lib/core/wvr-animation.service.spec.ts @@ -1,15 +1,49 @@ -import { TestBed } from '@angular/core/testing'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, tick } from '@angular/core/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { WvrItWorksComponent } from '../wvr-it-works/wvr-it-works.component'; import { WvrAnimationService } from './wvr-animation.service'; describe('WvrAnimationService', () => { let service: WvrAnimationService; + let componentOne: WvrItWorksComponent; + let componentTwo: WvrItWorksComponent; + let fixtureOne: ComponentFixture; + let fixtureTwo: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BrowserAnimationsModule], + declarations: [WvrItWorksComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + }); beforeEach(() => { - TestBed.configureTestingModule({ - imports: [BrowserAnimationsModule] - }); service = TestBed.inject(WvrAnimationService); + + fixtureOne = TestBed.createComponent(WvrItWorksComponent); + componentOne = fixtureOne.componentInstance; + componentOne.animate = `{ + click: "rotateToggle" + }`; + componentOne.animateConfig = `{ + rotateToggle: { + timing: '1000ms ease-in', + to: 180, + from: 0 + } + }`; + componentOne.animateId = 'animateComponentOne'; + + fixtureTwo = TestBed.createComponent(WvrItWorksComponent); + componentTwo = fixtureTwo.componentInstance; + componentTwo.animate = `{ + click: "animationTrigger" + }`; + componentTwo.animateTarget = 'componentOne.animateId'; + componentTwo.animateId = 'animateComponentTwo'; }); it('should be created', () => { @@ -17,4 +51,43 @@ describe('WvrAnimationService', () => { .toBeTruthy(); }); + it('should register new animation targets', () => { + // tslint:disable-next-line:no-string-literal + expect(service['_animationTargetsRegistry'].size) + .toEqual(0); + service.registerAnimationTargets(componentOne.animateId, componentOne); + // tslint:disable-next-line:no-string-literal + expect(service['_animationTargetsRegistry'].size) + .toEqual(1); + }); + + it('should register new animation states', () => { + // tslint:disable-next-line:no-string-literal + expect(componentOne['animationStateId']) + .toBeUndefined(); + + // tslint:disable-next-line:no-string-literal + componentOne['animationStateId'] = service.registerAnimationStates(); + + // tslint:disable-next-line:no-string-literal + expect(componentOne['animationStateId']) + .toBeDefined(); + }); + + // it('should trigger animation target', () => { + // componentOne.animationRootElem = fixtureOne.elementRef; + // // tslint:disable-next-line:no-string-literal + // componentOne['initializeAnimationRegistration'](); + // // tslint:disable-next-line:no-string-literal + // componentOne['initializeAnimationElement'](); + // // tslint:disable-next-line:no-string-literal + // componentTwo['initializeAnimationRegistration'](); + // // tslint:disable-next-line:no-string-literal + // componentTwo['initializeAnimationElement'](); + + // service.triggerAnimationTarget(componentTwo.animateTarget); + // // tslint:disable-next-line:no-string-literal + + // }); + }); diff --git a/projects/wvr-elements/src/lib/core/wvr-animation.service.ts b/projects/wvr-elements/src/lib/core/wvr-animation.service.ts index b7df907f1..df94f0a19 100644 --- a/projects/wvr-elements/src/lib/core/wvr-animation.service.ts +++ b/projects/wvr-elements/src/lib/core/wvr-animation.service.ts @@ -4,20 +4,27 @@ import { wvrAnimationDefaults } from '../shared/animation/wvr-animation-defaults import { wvrAnimationInitialization, wvrAnimations } from '../shared/animation/wvr-animations'; import { WvrBaseComponent } from '../shared/wvr-base.component'; +/** + * A centralized utility for handeling animation tasks. + */ @Injectable({ providedIn: 'root', deps: [AnimationBuilder] }) export class WvrAnimationService { + /** A registry of WvrBaseComponent which are participating in animations. */ private readonly _animationTargetsRegistry = new Map>(); + /** Records state for each WvrBaseComponent which is participating in animation */ private readonly animationStates = new Map>(); constructor(private readonly builder: AnimationBuilder) { } + /** Adds a component to the registry of targets */ registerAnimationTargets(targetName: string, component: WvrBaseComponent): void { let targets = this._animationTargetsRegistry.get(targetName); + /* istanbul ignore else */ if (!targets) { targets = new Array(); this._animationTargetsRegistry.set(targetName, targets); @@ -25,6 +32,7 @@ export class WvrAnimationService { targets.push(component); } + /** Creates an entry in the animation state registry and assigns a random identifier */ registerAnimationStates(): number { const id = Math.random(); this.animationStates.set(id, new Map()); @@ -32,6 +40,7 @@ export class WvrAnimationService { return id; } + /** Triggers associated animations from the _animationTargetsRegistry. */ triggerAnimationTarget(targetName: string): void { const targets = this._animationTargetsRegistry.get(targetName); if (targets) { @@ -41,6 +50,7 @@ export class WvrAnimationService { } } + /** Runs the initialization function for each configured animation. */ initializeAnimationElement(stateId, animationConfig: {}, animationRootElem): void { Object.keys(animationConfig) .forEach(animName => { @@ -53,6 +63,7 @@ export class WvrAnimationService { }); } + /** Plays the specified animation. */ playAnimation(stateId: number, animationName: string, animationConfig: {}, animationRoot: HTMLElement): AnimationPlayer { const timing = animationConfig[animationName] ? animationConfig[animationName].timing : @@ -74,6 +85,7 @@ export class WvrAnimationService { } } + /** Retrieves specified the animation. */ private selectAnimation(stateId: number, animationName: string, timing: string, to: string, from: string, animationRoot: HTMLElement): AnimationReferenceMetadata { const animationInput: AnimationMetadata | Array = @@ -88,6 +100,7 @@ export class WvrAnimationService { }); } + /** Compiles the specified animation. */ private compileAnimation(stateId, animationName, value): AnimationMetadata | Array { const a = wvrAnimations[animationName]; diff --git a/projects/wvr-elements/src/lib/shared/animation/wvr-animation-defaults.ts b/projects/wvr-elements/src/lib/shared/animation/wvr-animation-defaults.ts index 58f6b2df3..1dc97c3be 100644 --- a/projects/wvr-elements/src/lib/shared/animation/wvr-animation-defaults.ts +++ b/projects/wvr-elements/src/lib/shared/animation/wvr-animation-defaults.ts @@ -1,3 +1,4 @@ +/** Default settings for each predefined animation. */ export const wvrAnimationDefaults = { fade: { from: 1, diff --git a/projects/wvr-elements/src/lib/shared/animation/wvr-animations.ts b/projects/wvr-elements/src/lib/shared/animation/wvr-animations.ts index 2545ce0c8..62d5e347a 100644 --- a/projects/wvr-elements/src/lib/shared/animation/wvr-animations.ts +++ b/projects/wvr-elements/src/lib/shared/animation/wvr-animations.ts @@ -1,5 +1,6 @@ import { animate, AnimationMetadata, style } from '@angular/animations'; +/** Defines an initialization method for each predefined animaiton. */ const wvrAnimationInitialization = { fade: (s: Map, from: string, elem: HTMLElement): void => { elem.style.opacity = from; @@ -27,6 +28,7 @@ const wvrAnimationInitialization = { } }; +/** Describes the AnimationAnimateMetadata to be used for each predefined animation. */ const wvrAnimations = { fade: (s: Map, elem: HTMLElement): AnimationMetadata | Array => { diff --git a/projects/wvr-elements/src/lib/shared/config/app-config.ts b/projects/wvr-elements/src/lib/shared/config/app-config.ts index c072ba0a5..4bf8203c0 100644 --- a/projects/wvr-elements/src/lib/shared/config/app-config.ts +++ b/projects/wvr-elements/src/lib/shared/config/app-config.ts @@ -1,10 +1,16 @@ import { InjectionToken } from '@angular/core'; +/** Describes the application configuration values. */ interface AppConfig { + + /** A reference to the location from which Weaver Components is currently being served. */ baseUrl: string; + + /** A reference to the location from which Weaver Component's Assets are currently being served. */ assetsUrl?: string; } +/** An injection token for the AppConfig */ const APP_CONFIG = new InjectionToken('APP_CONFIG'); export { diff --git a/projects/wvr-elements/src/lib/shared/config/test-app-config.ts b/projects/wvr-elements/src/lib/shared/config/test-app-config.ts index f7fc094e3..52578845d 100644 --- a/projects/wvr-elements/src/lib/shared/config/test-app-config.ts +++ b/projects/wvr-elements/src/lib/shared/config/test-app-config.ts @@ -1,5 +1,6 @@ import { AppConfig } from './app-config'; +/** An application configuration to be used in testing. */ export const testAppConfig: AppConfig = { baseUrl: 'http://localhost:4200', assetsUrl: 'http://localhost:4200/assets' diff --git a/projects/wvr-elements/src/lib/shared/utility/bootstrap.utility.ts b/projects/wvr-elements/src/lib/shared/utility/bootstrap.utility.ts index d34ede712..a5079d137 100644 --- a/projects/wvr-elements/src/lib/shared/utility/bootstrap.utility.ts +++ b/projects/wvr-elements/src/lib/shared/utility/bootstrap.utility.ts @@ -2,6 +2,7 @@ import { Type } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppConfig, APP_CONFIG } from '../config'; +/** Interigates the current script tag for its src and extroplates the configuration path from that location. */ const obtainConfigPath = (): string => { const componentScript = document.getElementsByTagName('script'); const componentScriptSrc = componentScript[componentScript.length - 1].src; @@ -12,6 +13,7 @@ const obtainConfigPath = (): string => { return `${configBasePath}/config.json`; }; +/** Obtains, parses and injects the configuration. */ const weaverBootstrap = (module: Type) => () => fetch(obtainConfigPath()) .then((response: Response) => response.json()) .then((appConfig: AppConfig) => platformBrowserDynamic([{ diff --git a/projects/wvr-elements/src/lib/shared/utility/decorators.utilty.ts b/projects/wvr-elements/src/lib/shared/utility/decorators.utilty.ts index 34c446a27..3d0c6854a 100644 --- a/projects/wvr-elements/src/lib/shared/utility/decorators.utilty.ts +++ b/projects/wvr-elements/src/lib/shared/utility/decorators.utilty.ts @@ -1,3 +1,4 @@ +/** Used to delay the repeated execution of the decorated method until the specified time has elapsed. */ // tslint:disable-next-line:only-arrow-functions function debounce(delay = 300): MethodDecorator { return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { diff --git a/projects/wvr-elements/src/lib/shared/wvr-base-component-props.ts b/projects/wvr-elements/src/lib/shared/wvr-base-component-props.ts deleted file mode 100644 index 603ac8c7d..000000000 --- a/projects/wvr-elements/src/lib/shared/wvr-base-component-props.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from '@angular/core'; - -export const baseHostProps = { - -}; - -export const wvrBaseComponentProps: Component = { - host: baseHostProps -}; diff --git a/projects/wvr-elements/src/lib/shared/wvr-base.component.ts b/projects/wvr-elements/src/lib/shared/wvr-base.component.ts index c21c14f07..6ca454b3f 100644 --- a/projects/wvr-elements/src/lib/shared/wvr-base.component.ts +++ b/projects/wvr-elements/src/lib/shared/wvr-base.component.ts @@ -1,40 +1,56 @@ -import { AfterContentInit, Directive, ElementRef, EventEmitter, HostBinding, Injector, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { AfterContentInit, Directive, ElementRef, EventEmitter, HostBinding, Injector, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import * as JSON5 from 'json5'; -import { fromEvent, Observable } from 'rxjs'; -import { debounceTime, map } from 'rxjs/operators'; -import { WvrAnimationService } from '../core/wvr-animation.service'; -import { IconService } from '../core/icon.service'; +import { ComponentRegistryService } from '../core/component-registry.service'; import { MobileService } from '../core/mobile.service'; +import { WvrAnimationService } from '../core/wvr-animation.service'; @Directive() // tslint:disable-next-line:directive-class-suffix -export abstract class WvrBaseComponent implements AfterContentInit, OnInit { +export abstract class WvrBaseComponent implements AfterContentInit, OnInit, OnDestroy { + + /** A generated unique identifier for this comonent. */ + readonly id: number; + /** A statically accessible reference to the prefix used in deriving the HTML identifier. */ + static readonly HTML_ID_BASE = 'wvr-component'; + + /** A host binding used to ensure the presense of the `wvr-bootstrap` class. */ @HostBinding('class.wvr-bootstrap') wvrBootstrap = true; + /** An object representation of the animation instructions for this component. */ private _animationSettings: any = {}; + + /** A setter which parses a json string describing animation instructions and stores the derived object in `_animationSettings`. */ @Input() set animate(value: string) { this._animationSettings = JSON5.parse(value); } + /** An object representation of the settings specifying the specific behavior of the animation for this component. */ private _animationConfig: any = {}; + + /** A setter which parses a json string describing animation setting and stores the derived object in `_animationConfig`. */ @Input() set animateConfig(value: string) { this._animationConfig = JSON5.parse(value); } + /** An identifier used to access the animation state for this component. */ private animationStateId: number; + /** An attribute input allowing for the designation of an animation identifier for the purpose of animation targeting. */ @Input() animateId: string; + /** An attribute input allowing for the designation of an animation target for animation events. */ @Input() animateTarget: string; + /** A view child of the template element containing the #animationRoot identifier. */ @ViewChild('animationRoot') animationRootElem: ElementRef; + /** An accessor which uses the mobileService to determine if the current layout mode is mobile. */ get isMobileLayout(): boolean { return this.mobileService.isMobileLayout; } - + /** An accessor which determines if the current userAgent mode is mobile. */ get isMobileAgent(): boolean { const agent = navigator.userAgent || navigator.vendor || (window as any).opera; @@ -42,30 +58,76 @@ export abstract class WvrBaseComponent implements AfterContentInit, OnInit { return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(agent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(agent.substr(0, 4))); } + /** A reference to the ComponentRegistryService */ + protected readonly componentRegistry: ComponentRegistryService; + + /** A reference to the WvrAnimationService */ protected readonly _animationService: WvrAnimationService; + /** A reference to the DomSanitizer */ protected readonly _domSanitizer: DomSanitizer; + /** A reference to the ElementRef */ protected readonly _eRef: ElementRef; + /** A reference to the MobileService */ private readonly mobileService: MobileService; + /** A host bound accessor which applies the wvr-hidden class if both isMobileLayout and hiddenInMobile evaluate to true. */ @HostBinding('class.wvr-hidden') private get _hiddenInMobile(): boolean { return this.mobileService.isMobileLayout && this.hiddenInMobile; } + /** An attribute input specifying if this component should be hidden in the mobile layout. */ @Input() hiddenInMobile = false; + /** An Output biding used for triggering animations. */ @Output() protected readonly animationEventTrigger = new EventEmitter(); constructor(injector: Injector) { + this.componentRegistry = injector.get(ComponentRegistryService); this._animationService = injector.get(WvrAnimationService); this._domSanitizer = injector.get(DomSanitizer); this._eRef = injector.get(ElementRef); this.mobileService = injector.get(MobileService); + this.id = this.componentRegistry.register(this); + + const element = (this._eRef.nativeElement as HTMLElement); + const htmlIDAttrName = element.hasAttribute('id') ? 'wvr-id' : 'id'; + element.setAttribute(htmlIDAttrName, `${WvrBaseComponent.HTML_ID_BASE}-${this.id}`); } + /** Used to setup this component for animating. */ ngOnInit(): void { + this.initializeAnimationRegistration(); + } + + /** Used for post content initialization animation setup. */ + ngAfterContentInit(): void { + this.initializeAnimationElement(); + } + + /** Handles the the unregistering of this component with the component registry. */ + ngOnDestroy(): void { + this.componentRegistry.unRegisterComponent(this.id); + } + + /** Plays the animation specified by the incoming animation trigger. */ + triggerAnimations(animationTriggerType: string): void { + const animations: Array = Array.isArray(this._animationSettings[animationTriggerType]) + ? this._animationSettings[animationTriggerType] + : [this._animationSettings[animationTriggerType]]; + animations.forEach(an => { + if (an === 'animationTrigger') { + this._animationService.triggerAnimationTarget(this.animateTarget); + } else { + this._animationService + .playAnimation(this.animationStateId, an, this._animationConfig, this.animationRootElem.nativeElement); + } + }); + } + + private initializeAnimationRegistration(): void { const animationEvents = Object.keys(this._animationSettings); if (animationEvents.length) { if (this.animateId) { @@ -80,27 +142,14 @@ export abstract class WvrBaseComponent implements AfterContentInit, OnInit { } } - ngAfterContentInit(): void { + private initializeAnimationElement(): void { setTimeout(() => { this._animationService .initializeAnimationElement(this.animationStateId, this._animationConfig, this.animationRootElem); }, 1); } - triggerAnimations(animationTriggerType: string): void { - const animations: Array = Array.isArray(this._animationSettings[animationTriggerType]) - ? this._animationSettings[animationTriggerType] - : [this._animationSettings[animationTriggerType]]; - animations.forEach(an => { - if (an === 'animationTrigger') { - this._animationService.triggerAnimationTarget(this.animateTarget); - } else { - this._animationService - .playAnimation(this.animationStateId, an, this._animationConfig, this.animationRootElem.nativeElement); - } - }); - } - + /** Trigger's the animation specified by the incoming event. */ private onEvent($event): void { this.triggerAnimations($event.type); } diff --git a/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.html b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.html new file mode 100644 index 000000000..7104c7757 --- /dev/null +++ b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.html @@ -0,0 +1,52 @@ + + + + + + + +
+ +
+
+ + + + + + + + + + + diff --git a/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.scss b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.scss new file mode 100644 index 000000000..d4786a299 --- /dev/null +++ b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.scss @@ -0,0 +1,186 @@ +@import "../shared/styles/wvr-variables.scss"; + +:host { + + // alert-primary + --wvr-alert-primary-color-default: #004085; + --wvr-alert-primary-background-default: #cce5ff; + --wvr-alert-primary-border-default: #b8daff; + + --wvr-alert-link-primary-color-default: #002752; + + // .alert-secondary + --wvr-alert-secondary-color-default: #383d41; + --wvr-alert-secondary-background-default: #e2e3e5; + --wvr-alert-secondary-border-default: #d6d8db; + + --wvr-alert-link-secondary-color-default: #202326; + + // alert-success + --wvr-alert-success-color-default: #155724; + --wvr-alert-success-background-default: #d4edda; + --wvr-alert-success-border-default: #c3e6cb; + + --wvr-alert-link-success-color-default: #0b2e13; + + // alert-danger + --wvr-alert-danger-color-default: #721c24; + --wvr-alert-danger-background-default: #f8d7da; + --wvr-alert-danger-border-default: #f5c6cb; + + --wvr-alert-link-danger-color-default: #491217; + + // alert-warning + --wvr-alert-warning-color-default: #856404; + --wvr-alert-warning-background-default: #fff3cd; + --wvr-alert-warning-border-default: #ffeeba; + + --wvr-alert-link-warning-color-default: #533f03; + + // alert-info + --wvr-alert-info-color-default: #0c5460; + --wvr-alert-info-background-default: #d1ecf1; + --wvr-alert-info-border-default: #bee5eb; + + --wvr-alert-link-info-color-default: #062c33; + + // alert-light + --wvr-alert-light-color-default: #818182; + --wvr-alert-light-background-default: #fefefe; + --wvr-alert-light-border-default: #fdfdfe; + + --wvr-alert-light-warning-color-default: #686868; + + // alert-dark + --wvr-alert-dark-color-default: #1b1e21; + --wvr-alert-dark-background-default: #d6d8d9; + --wvr-alert-dark-border-default: #c6c8ca; + + --wvr-alert-link-dark-color-default : #040505; + + .alert { + position: relative; + padding: .75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: .25rem; + } + + .alert-dismissible { + padding-right: 4rem; + } + + .alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: .75rem 1.25rem; + color: inherit; + } + + .alert-heading { + color: inherit; + } + + .btn.btn-primary, + .btn.btn-secondary, + .btn.btn-success, + .btn.btn-danger, + .btn.btn-warning, + .btn.btn-info, + .btn.btn-light, + .btn.btn-dark { + font-size: 1.2rem; + box-shadow: none; + } + + a { + text-decoration: none; + background-color: transparent; + } + + .alert-link { + font-weight: 700; + } + + .alert-primary { + color: var(--wvr-alert-primary-color-default); + background-color: var(--wvr-alert-primary-background-default); + border-color: var(--wvr-alert-primary-border-default); + } + + .alert-primary .alert-link { + color: var(--wvr-alert-link-primary-color-default); + } + + .alert-secondary { + color: var(--wvr-alert-secondary-color-default); + background-color: var(--wvr-alert-secondary-background-default); + border-color: var(--wvr-alert-secondary-border-default); + } + + .alert-secondary .alert-link { + color: var(--wvr-alert-link-secondary-color-default); + } + + .alert-success { + color: var(--wvr-alert-success-color-default); + background-color: var(--wvr-alert-success-background-default); + border-color: var(--wvr-alert-success-border-default); + } + + .alert-success .alert-link { + color: var(--wvr-alert-link-success-color-default); + } + + .alert-danger { + color: var(--wvr-alert-danger-color-default); + background-color: var(--wvr-alert-danger-background-default); + border-color: var(--wvr-alert-danger-border-default); + } + + .alert-danger .alert-link { + color: var(--wvr-alert-link-danger-color-default); + } + + .alert-warning { + color: var(--wvr-alert-warning-color-default); + background-color: var(--wvr-alert-warning-background-default); + border-color: var(--wvr-alert-warning-border-default); + } + + .alert-warning .alert-link { + color: var(--wvr-alert-link-warning-color-default); + } + + .alert-info { + color: var(--wvr-alert-info-color-default); + background-color: var(--wvr-alert-info-background-default); + border-color: var(--wvr-alert-info-border-default); + } + + .alert-info .alert-link { + color: var(--wvr-alert-link-info-color-default); + } + + .alert-light { + color: var(--wvr-alert-light-color-default); + background-color: var(--wvr-alert-light-background-default); + border-color: var(--wvr-alert-light-border-default); + } + + .alert-light .alert-link { + color: var(--wvr-alert-light-warning-color-default); + } + + .alert-dark { + color: var(--wvr-alert-dark-color-default); + background-color: var(--wvr-alert-dark-background-default); + border-color: var(--wvr-alert-dark-border-default); + } + + .alert-dark .alert-link { + color: var(--wvr-alert-link-dark-color-default); + } + +} diff --git a/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.spec.ts b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.spec.ts new file mode 100644 index 000000000..757f3a000 --- /dev/null +++ b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.spec.ts @@ -0,0 +1,53 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { WvrAlertComponent } from './wvr-alert.component'; + +describe('WvrAlertComponent', () => { + let component: WvrAlertComponent; + let fixture: ComponentFixture; + + // tslint:disable-next-line: deprecation + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BrowserAnimationsModule], + declarations: [WvrAlertComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WvrAlertComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component) + .toBeTruthy(); + }); + + it("should have as alert Class as 'primary'", () => { + expect(component.alertClass) + .toEqual('primary'); + }); + + it("should have as alert type as 'basic'", () => { + expect(component.alertType) + .toEqual('basic'); + }); + + it('should close when close button is clicked', () => { + component.alertType = 'custom'; + fixture.detectChanges(); + const alertElem = fixture.elementRef.nativeElement as HTMLElement; + // tslint:disable-next-line: no-unnecessary-type-assertion + const closeButton = alertElem.querySelector('button.close') as HTMLElement; + expect(component.alertClosed) + .toBeFalse(); + closeButton.dispatchEvent(new MouseEvent('click')); + expect(component.alertClosed) + .toBeTrue(); + }); +}); diff --git a/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.ts b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.ts new file mode 100644 index 000000000..c569c1b60 --- /dev/null +++ b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.ts @@ -0,0 +1,48 @@ +import { AfterContentInit, Component, HostListener, Injector, Input, OnInit } from '@angular/core'; +import { WvrBaseComponent } from '../shared/wvr-base.component'; + +/** + * A message display with contextualized styling. + */ +@Component({ + selector: 'wvr-alert-element', + templateUrl: './wvr-alert.component.html', + styleUrls: ['./wvr-alert.component.scss'] +}) +export class WvrAlertComponent extends WvrBaseComponent implements AfterContentInit, OnInit { + + /** Used to define the class type of an alert component. */ + @Input() alertClass: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark' = 'primary'; + + /** Used to define the type of alert. */ + @Input() alertType: 'basic' | 'self-closing' | 'custom' = 'basic'; + + /** Used to self close the alert box. */ + alertClosed = false; + + /** Used to display the Close button. */ + @Input() closable: 'true' | 'false' = 'true'; + + /** Setting the delay timer for the self closing alert message */ + @Input() closeTimer = 5000; + + constructor(injector: Injector) { + super(injector); + } + + /** + * An event handle method for the `document:click` event. + * Closes the alert box once the `X` is clicked. + */ + clickClose($event: MouseEvent): void { + this.alertClosed = true; + } + + /** Initializes the closing timer for a self closing alert. */ + ngOnInit(): void { + setTimeout(() => { + this.alertClosed = (this.alertType === 'self-closing') ? true : false; + }, this.closeTimer); + } + +} diff --git a/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.ud-examples.html b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.ud-examples.html new file mode 100644 index 000000000..89bb92b05 --- /dev/null +++ b/projects/wvr-elements/src/lib/wvr-alert/wvr-alert.component.ud-examples.html @@ -0,0 +1,88 @@ + + + The basic wvr-alert component with no customizations. + + + + + + + + + + + + + + + + + + + + + + + The wvr-alert component with self closing feature based on the input closing timer. + + + + + + + + + + + The wvr-alert component with custom content. + + + + +

+ +

+

+ +

+
+

+ +

+
+
+
+ + + + +

+ +

+

+ +

+
+

+ +

+
+
+
+
diff --git a/projects/wvr-elements/src/lib/wvr-dropdown/wvr-dropdown.component.spec.ts b/projects/wvr-elements/src/lib/wvr-dropdown/wvr-dropdown.component.spec.ts index 4d4b12c0b..94264c42e 100644 --- a/projects/wvr-elements/src/lib/wvr-dropdown/wvr-dropdown.component.spec.ts +++ b/projects/wvr-elements/src/lib/wvr-dropdown/wvr-dropdown.component.spec.ts @@ -173,4 +173,229 @@ describe('WvrDropdownComponent', () => { }, 251); }); + it('should have customization for button background', () => { + expect(component.btnBackground) + .toEqual('var(--wvr-btn-plain-background-default)'); + component.btnBackground = 'var(--wvr-dropdown-menu-background)'; + fixture.detectChanges(); + expect(component.btnBackground) + .toEqual('var(--wvr-dropdown-menu-background)'); + }); + + it('should have customization for active button color', () => { + expect(component.btnBackgroundActive) + .toEqual('var(--wvr-btn-plain-active-background-default)'); + component.btnBackgroundActive = 'var(--wvr-btn-warning-active-background-default)'; + fixture.detectChanges(); + expect(component.btnBackgroundActive) + .toEqual('var(--wvr-btn-warning-active-background-default)'); + + }); + + it('should have customization for hover button color', () => { + expect(component.btnBackgroundHover) + .toEqual('var(--wvr-btn-plain-hover-background-default)'); + component.btnBackgroundHover = 'var(--wvr-btn-warning-hover-background-default)'; + fixture.detectChanges(); + expect(component.btnBackgroundHover) + .toEqual('var(--wvr-btn-warning-hover-background-default)'); + + }); + + it('should have customization for button border value', () => { + expect(component.btnBorderColor) + .toEqual('var(--wvr-btn-plain-border-default)'); + component.btnBorderColor = 'var(--wvr-btn-primary-border-default)'; + fixture.detectChanges(); + expect(component.btnBorderColor) + .toEqual('var(--wvr-btn-primary-border-default)'); + + }); + + it('should have customization for button border value in active state', () => { + expect(component.btnBorderActive) + .toEqual('var(--wvr-btn-plain-active-border-default)'); + component.btnBorderActive = 'var(--wvr-btn-primary-active-border-default)'; + fixture.detectChanges(); + expect(component.btnBorderActive) + .toEqual('var(--wvr-btn-primary-active-border-default)'); + + }); + + it('should have customization for button border value in focus state', () => { + expect(component.btnBorderFocus) + .toEqual('var(--wvr-btn-plain-focus-border-default)'); + component.btnBorderFocus = 'var(--wvr-btn-primary-focus-border-default)'; + fixture.detectChanges(); + expect(component.btnBorderFocus) + .toEqual('var(--wvr-btn-primary-focus-border-default)'); + + }); + + it('should have customization for button border value in hover state', () => { + expect(component.btnBorderHover) + .toEqual('var(--wvr-btn-plain-hover-border-default)'); + component.btnBorderHover = 'var(--wvr-btn-primary-hover-border-default)'; + fixture.detectChanges(); + expect(component.btnBorderHover) + .toEqual('var(--wvr-btn-primary-hover-border-default)'); + + }); + + it('should have customization for button color in default state', () => { + expect(component.btnColor) + .toEqual('var(--wvr-btn-plain-color-default)'); + component.btnColor = 'var(--wvr-btn-primary-color-default)'; + fixture.detectChanges(); + expect(component.btnColor) + .toEqual('var(--wvr-btn-primary-color-default)'); + + }); + + it('should have customization for button color in active state', () => { + expect(component.btnColorActive) + .toEqual('var(--wvr-btn-plain-active-color-default)'); + component.btnColorActive = 'var(--wvr-btn-primary-active-color-default)'; + fixture.detectChanges(); + expect(component.btnColorActive) + .toEqual('var(--wvr-btn-primary-active-color-default)'); + + }); + + it('should have customization for button color in hover state', () => { + expect(component.btnColorHover) + .toEqual('var(--wvr-btn-plain-hover-color-default)'); + component.btnColorHover = 'var(--wvr-btn-primary-hover-color-default)'; + fixture.detectChanges(); + expect(component.btnColorHover) + .toEqual('var(--wvr-btn-primary-hover-color-default)'); + + }); + + it('should have customization for button border radius', () => { + expect(component.btnBorderRadius) + .toEqual('var(--wvr-btn-border-radius)'); + component.btnBorderRadius = '12px'; + fixture.detectChanges(); + expect(component.btnBorderRadius) + .toEqual('12px'); + }); + + it('should have customization for button border radius', () => { + expect(component.btnBoxShadowFocus) + .toEqual('var(--wvr-btn-plain-focus-box-shadow-default)'); + component.btnBoxShadowFocus = 'var(--wvr-btn-primary-focus-box-shadow-default)'; + fixture.detectChanges(); + expect(component.btnBoxShadowFocus) + .toEqual('var(--wvr-btn-primary-focus-box-shadow-default)'); + }); + + it('should have customization for button cursor', () => { + expect(component.btnCursor) + .toEqual('var(--wvr-btn-cursor-default)'); + component.btnCursor = 'pointer'; + fixture.detectChanges(); + expect(component.btnCursor) + .toEqual('pointer'); + }); + + it('should have customization for button font family', () => { + expect(component.btnFontFamily) + .toEqual('var(--wvr-btn-font-family-sans-serif-default)'); + component.btnFontFamily = 'var(--wvr-font-family-sans-serif)'; + fixture.detectChanges(); + expect(component.btnFontFamily) + .toEqual('var(--wvr-font-family-sans-serif)'); + }); + + it('should have customization for button font size', () => { + expect(component.btnFontSize) + .toEqual('var(--wvr-btn-font-size-default)'); + component.btnFontSize = 'var(--wvr-btn-font-size)'; + fixture.detectChanges(); + expect(component.btnFontSize) + .toEqual('var(--wvr-btn-font-size)'); + }); + + it('should have customization for button font weight', () => { + expect(component.btnFontWeight) + .toEqual('var(--wvr-btn-font-weight-default)'); + component.btnFontWeight = 'var(--wvr-btn-font-weight)'; + fixture.detectChanges(); + expect(component.btnFontWeight) + .toEqual('var(--wvr-btn-font-weight)'); + }); + + it('should have customization for button line height', () => { + expect(component.btnLineHeight) + .toEqual('var(--wvr-btn-line-height-default)'); + component.btnLineHeight = 'var(--wvr-btn-line-height)'; + fixture.detectChanges(); + expect(component.btnLineHeight) + .toEqual('var(--wvr-btn-line-height)'); + }); + + it('should have customization for button padding rule', () => { + expect(component.btnPadding) + .toEqual('var(--wvr-btn-padding-default)'); + component.btnPadding = 'var(--wvr-btn-padding)'; + fixture.detectChanges(); + expect(component.btnPadding) + .toEqual('var(--wvr-btn-padding)'); + }); + + it('should have customization for button text align rule', () => { + expect(component.btnTextAlign) + .toEqual('var(--wvr-btn-text-align-default)'); + component.btnTextAlign = 'var(--wvr-btn-text-align)'; + fixture.detectChanges(); + expect(component.btnTextAlign) + .toEqual('var(--wvr-btn-text-align)'); + }); + + it('should have customization for button vertical align', () => { + expect(component.btnVerticalAlign) + .toEqual('var(--wvr-btn-vertical-align-default)'); + component.btnVerticalAlign = 'var(--wvr-btn-vertical-align)'; + fixture.detectChanges(); + expect(component.btnVerticalAlign) + .toEqual('var(--wvr-btn-vertical-align)'); + }); + + it('should have customization for button text decoration', () => { + expect(component.btnTextDecoration) + .toEqual('var(--wvr-btn-plain-text-decoration-default)'); + component.btnTextDecoration = 'var(--wvr-btn-primary-text-decoration)'; + fixture.detectChanges(); + expect(component.btnTextDecoration) + .toEqual('var(--wvr-btn-primary-text-decoration)'); + }); + + it('should have customization for button text decoration in active state', () => { + expect(component.btnTextDecorationActive) + .toEqual('var(--wvr-btn-plain-active-text-decoration-default)'); + component.btnTextDecorationActive = 'var(--wvr-btn-primary-active-text-decoration)'; + fixture.detectChanges(); + expect(component.btnTextDecorationActive) + .toEqual('var(--wvr-btn-primary-active-text-decoration)'); + }); + + it('should have customization for button text decoration in focus state', () => { + expect(component.btnTextDecorationFocus) + .toEqual('var(--wvr-btn-plain-focus-text-decoration-default)'); + component.btnTextDecorationFocus = 'var(--wvr-btn-primary-focus-text-decoration)'; + fixture.detectChanges(); + expect(component.btnTextDecorationFocus) + .toEqual('var(--wvr-btn-primary-focus-text-decoration)'); + }); + + it('should have customization for button text decoration in focus state', () => { + expect(component.btnTextDecorationHover) + .toEqual('var(--wvr-btn-plain-hover-text-decoration-default)'); + component.btnTextDecorationHover = 'var(--wvr-btn-primary-hover-text-decoration)'; + fixture.detectChanges(); + expect(component.btnTextDecorationHover) + .toEqual('var(--wvr-btn-primary-hover-text-decoration)'); + }); + }); diff --git a/projects/wvr-elements/src/lib/wvr-dropdown/wvr-dropdown.component.ts b/projects/wvr-elements/src/lib/wvr-dropdown/wvr-dropdown.component.ts index 1eb6275a2..c3179fd80 100644 --- a/projects/wvr-elements/src/lib/wvr-dropdown/wvr-dropdown.component.ts +++ b/projects/wvr-elements/src/lib/wvr-dropdown/wvr-dropdown.component.ts @@ -2,6 +2,9 @@ import { Component, HostBinding, HostListener, Injector, Input, ViewChild } from import { NgbDropdown, NgbDropdownConfig } from '@ng-bootstrap/ng-bootstrap'; import { WvrBaseComponent } from '../shared/wvr-base.component'; +/** + * A dropdown with button and contextualized styling. + */ @Component({ selector: 'wvr-dropdown-element', templateUrl: './wvr-dropdown.component.html', @@ -31,6 +34,7 @@ export class WvrDropdownComponent extends WvrBaseComponent { this._btnBackground = value; } + /** An accessor for the assigned background of this dropdowns button. */ get btnBackground(): string { return this._btnBackground ? this._btnBackground : `var(--wvr-btn-${this.btnType}-background-default)`; } @@ -109,7 +113,7 @@ export class WvrDropdownComponent extends WvrBaseComponent { } get btnColor(): string { - return this._btnColor ? this._btnColor : `var(--wvr-btn-${this.btnType}-color-default`; + return this._btnColor ? this._btnColor : `var(--wvr-btn-${this.btnType}-color-default)`; } /** Allows for override the button color in active state. */ diff --git a/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.html b/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.html index dfd474a0a..db89e8a36 100644 --- a/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.html +++ b/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.html @@ -11,7 +11,7 @@ - +
{{headerTitle}} @@ -42,7 +42,7 @@
-
+
diff --git a/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.scss b/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.scss index 215d9909b..7e709920a 100644 --- a/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.scss +++ b/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.scss @@ -67,8 +67,20 @@ cursor: pointer; } - .mobile-menu { - display: inline-block; + .mobile-menu-content { + white-space: nowrap !important; + ::ng-deep { + .section-title { + white-space: nowrap !important; + } + tl-nav-li > a { + white-space: nowrap !important; + } + wvr-dropdown-element, + .section-title { + min-width: 200px; + } + } } .mobile-menu.closed { diff --git a/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.spec.ts b/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.spec.ts index d53c1a422..61c79b676 100644 --- a/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.spec.ts +++ b/projects/wvr-elements/src/lib/wvr-header/wvr-header.component.spec.ts @@ -94,4 +94,13 @@ describe('WvrHeaderComponent', () => { }); + it('should have toggle menu', () => { + expect(component.mobileMenuClosed) + .toBeTruthy(); + component.toggleMobileMenu(); + fixture.detectChanges(); + expect(component.mobileMenuClosed) + .toBe(false); + }); + }); diff --git a/projects/wvr-elements/src/lib/wvr-icon/icon-set.ts b/projects/wvr-elements/src/lib/wvr-icon/icon-set.ts index 3ec8fdc03..32b90bead 100644 --- a/projects/wvr-elements/src/lib/wvr-icon/icon-set.ts +++ b/projects/wvr-elements/src/lib/wvr-icon/icon-set.ts @@ -1,6 +1,9 @@ import { Icon } from './icon'; +/** Describes a list of Icon */ export interface IconSet { + + /** The name of the icon set. **Must match the sets directory name within the assets location**. */ name: string; icons: Array; } diff --git a/projects/wvr-elements/src/lib/wvr-icon/icon.ts b/projects/wvr-elements/src/lib/wvr-icon/icon.ts index 7425de095..3fc4e2e4c 100644 --- a/projects/wvr-elements/src/lib/wvr-icon/icon.ts +++ b/projects/wvr-elements/src/lib/wvr-icon/icon.ts @@ -1,6 +1,11 @@ import { Observable } from 'rxjs'; +/** Describes an observable of string to be resolved as an icon svg. */ export interface Icon { + + /** The name of the given icon. **Must match filename**. */ name: string; + + /** An observable which resolves a string representation of the icon's SVG. */ svg: Observable; } diff --git a/projects/wvr-elements/src/lib/wvr-icon/wvr-icon.component.ts b/projects/wvr-elements/src/lib/wvr-icon/wvr-icon.component.ts index 1e905ede7..be39bf1b7 100644 --- a/projects/wvr-elements/src/lib/wvr-icon/wvr-icon.component.ts +++ b/projects/wvr-elements/src/lib/wvr-icon/wvr-icon.component.ts @@ -5,6 +5,9 @@ import { map } from 'rxjs/operators'; import { IconService } from '../core/icon.service'; import { WvrBaseComponent } from '../shared/wvr-base.component'; +/** + * A reference to an Icon currently available in the configured assets. + */ @Component({ selector: 'wvr-icon-element', templateUrl: './wvr-icon.component.html', @@ -12,20 +15,26 @@ import { WvrBaseComponent } from '../shared/wvr-base.component'; }) export class WvrIconComponent extends WvrBaseComponent implements AfterViewInit { + /** An attribute input describing the icon set to which this icon belongs. */ @Input() set: string; + /** An attribute input describing the icon name of this icon. */ @Input() name: string; + /** An attribute input bound to the css variable `--wvr-icon-color`. */ @HostBinding('style.--wvr-icon-color') @Input() color: string; + /** An attribute input bound to the css variable `--wvr-icon-size`. */ @HostBinding('style.--wvr-icon-size') @Input() size = '24px'; + /** An observable SafeHtml representation of the svg to be displayed for this icon. */ iconSvg: Observable; constructor(injector: Injector, private readonly iconService: IconService) { super(injector); } + /** Utilizes the icon service to request the svg specified by this icon. */ ngAfterViewInit(): void { this.iconSvg = this.iconService.getIcon(this.set, this.name) .pipe(map(svg => this._domSanitizer.bypassSecurityTrustHtml(svg))); diff --git a/projects/wvr-elements/src/lib/wvr-it-works/wvr-it-works.component.html b/projects/wvr-elements/src/lib/wvr-it-works/wvr-it-works.component.html index 1e49b0ab0..318c1e9d0 100644 --- a/projects/wvr-elements/src/lib/wvr-it-works/wvr-it-works.component.html +++ b/projects/wvr-elements/src/lib/wvr-it-works/wvr-it-works.component.html @@ -2,4 +2,4 @@

{{text}}!!!

-
\ No newline at end of file + diff --git a/projects/wvr-elements/src/lib/wvr-it-works/wvr-it-works.component.scss b/projects/wvr-elements/src/lib/wvr-it-works/wvr-it-works.component.scss index eaa228772..7e3d9bd7f 100644 --- a/projects/wvr-elements/src/lib/wvr-it-works/wvr-it-works.component.scss +++ b/projects/wvr-elements/src/lib/wvr-it-works/wvr-it-works.component.scss @@ -6,4 +6,8 @@ div.container { .it-works { color: var(--wvr-primary); -} \ No newline at end of file +} + +:host { + font-family: var(--wvr-font-family-sans-serif); +} diff --git a/projects/wvr-elements/src/lib/wvr-lib.module.ts b/projects/wvr-elements/src/lib/wvr-lib.module.ts index e1a237f91..5528368ad 100644 --- a/projects/wvr-elements/src/lib/wvr-lib.module.ts +++ b/projects/wvr-elements/src/lib/wvr-lib.module.ts @@ -7,6 +7,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { IconService } from './core/icon.service'; import { MobileService } from './core/mobile.service'; +import { WvrAlertComponent } from './wvr-alert/wvr-alert.component'; import { WvrAnimationService } from './core/wvr-animation.service'; import { WvrButtonComponent } from './wvr-button/wvr-button.component'; import { WvrDropdownComponent } from './wvr-dropdown/wvr-dropdown.component'; @@ -19,9 +20,12 @@ import { WvrListComponent } from './wvr-list/wvr-list.component'; import { WvrNavLiComponent } from './wvr-nav-list/wvr-nav-li/wvr-nav-li.component'; import { WvrNavListComponent } from './wvr-nav-list/wvr-nav-list.component'; import { WvrTextComponent } from './wvr-text/wvr-text.component'; +import { WvrTabsComponent } from './wvr-tabs/wvr-tabs.component'; +import { WvrTabComponent } from './wvr-tabs/wvr-tab/wvr-tab.component'; /** This property contains a list of components and the selector tags. */ const elements = [ + { component: WvrAlertComponent, selector: 'wvr-alert'}, { component: WvrButtonComponent, selector: 'wvr-button' }, { component: WvrDropdownComponent, selector: 'wvr-dropdown' }, { component: WvrFooterComponent, selector: 'wvr-footer' }, @@ -32,11 +36,14 @@ const elements = [ { component: WvrListItemComponent, selector: 'wvr-list-item' }, { component: WvrNavListComponent, selector: 'wvr-nav-list' }, { component: WvrNavLiComponent, selector: 'wvr-nav-li' }, - { component: WvrTextComponent, selector: 'wvr-text' } + { component: WvrTextComponent, selector: 'wvr-text' }, + { component: WvrTabsComponent, selector: 'wvr-tabs' }, + { component: WvrTabComponent, selector: 'wvr-tab' } ]; /** This property contains a list of components classes. */ const components = [ + WvrAlertComponent, WvrButtonComponent, WvrDropdownComponent, WvrFooterComponent, @@ -47,7 +54,9 @@ const components = [ WvrItWorksComponent, WvrNavListComponent, WvrNavLiComponent, - WvrTextComponent + WvrTextComponent, + WvrTabsComponent, + WvrTabComponent ]; /** The main module for the Weaver Elements library. */ diff --git a/projects/wvr-elements/src/lib/wvr-list/wvr-list-item/wvr-list-item.component.html b/projects/wvr-elements/src/lib/wvr-list/wvr-list-item/wvr-list-item.component.html index 5d228000e..0e9008ef1 100644 --- a/projects/wvr-elements/src/lib/wvr-list/wvr-list-item/wvr-list-item.component.html +++ b/projects/wvr-elements/src/lib/wvr-list/wvr-list-item/wvr-list-item.component.html @@ -1,20 +1,21 @@ - -
  • + - -
    + - + - + - -
  • + - - + + diff --git a/projects/wvr-elements/src/lib/wvr-list/wvr-list-item/wvr-list-item.component.ts b/projects/wvr-elements/src/lib/wvr-list/wvr-list-item/wvr-list-item.component.ts index 4e3134916..fa28dec76 100644 --- a/projects/wvr-elements/src/lib/wvr-list/wvr-list-item/wvr-list-item.component.ts +++ b/projects/wvr-elements/src/lib/wvr-list/wvr-list-item/wvr-list-item.component.ts @@ -1,43 +1,85 @@ -import { Component, Injector, Input, OnInit } from '@angular/core'; +import { AfterContentInit, Component, ElementRef, Injector, Input, OnInit, ViewChild } from '@angular/core'; +import { SafeHtml } from '@angular/platform-browser'; import { Theme } from '../../shared/theme.type'; import { WvrBaseComponent } from '../../shared/wvr-base.component'; +import { WvrListComponent } from '../wvr-list.component'; +/** + * A sub component to the WvrListComponent. + */ @Component({ selector: 'wvr-list-item-element', templateUrl: './wvr-list-item.component.html', styleUrls: ['./wvr-list-item.component.scss'] }) -export class WvrListItemComponent extends WvrBaseComponent implements OnInit { +export class WvrListItemComponent extends WvrBaseComponent implements OnInit, AfterContentInit { - private _parent: HTMLElement; + /** The WvrListComponent which contains this list item. */ + private parent: WvrListComponent; + /** The type of list which contains this list item. */ listType: string; + /** Attribute input used with descriptive lists as the text of the DT element. */ @Input() description: string; + /** The visual contextualization for this list item. */ @Input() context: Theme; + /** A heading to be displayed for list items with custom content. */ @Input() customContentItemHeading: string; - @Input() cusomContentHeadingSmallText: string; + /** A subtext to be displayed beneath the heading for list items with custom content. */ + @Input() customContentHeadingSmallText: string; + /** A subtext to be displayed beneath the main content for list items with custom content. */ @Input() customContentSmallText: string; + /** A contructed identifier dervied from this comonents id and the prefix `wvr-li` */ + htmlId = `wvr-li-${this.id}`; + + /** A view child reference to the html template contianing the projected content. */ + @ViewChild('liWrapper') contentProjection: ElementRef; + + /** A getter for the html content contined withing the `contentProjection` template */ + get htmlContent(): string { + const elems = this.contentProjection.nativeElement.children; + + let htmlString = ''; + for (let i = 0; i < elems.length; i++) { + const elem = elems.item(i); + htmlString += elem.outerHTML; + } + + return htmlString; + } + constructor(injector: Injector) { super(injector); } + /** Registers this list item with the parent list. */ ngOnInit(): void { - this._parent = (this._eRef.nativeElement as HTMLElement).closest('wvr-list'); - - const listTypeAttribute = this._parent ? this._parent.getAttribute('list-type') : undefined; - this.listType = listTypeAttribute ? listTypeAttribute : 'unordered'; - const contextAttribute = this._parent ? (this._parent.getAttribute('context') as Theme) : undefined; - this.context = this.context ? - this.context : - contextAttribute ? - contextAttribute : - undefined; + + const listElem: HTMLElement = (this._eRef.nativeElement as HTMLElement).closest('wvr-list'); + + if (listElem) { + this.parent = this.componentRegistry + .getComponentByElement(listElem) as WvrListComponent; + this.parent.addListItem(this); + + const listTypeAttribute = this.parent ? this.parent.listType : undefined; + this.listType = listTypeAttribute ? listTypeAttribute : 'unordered'; + const parentTheme = this.parent ? this.parent.context : undefined; + this.context = this.context ? + this.context : + parentTheme ? + parentTheme : + undefined; + } else { + console.warn('The wvr-list-item component must be contained within a wvr-list component.'); + } + } } diff --git a/projects/wvr-elements/src/lib/wvr-list/wvr-list.component.html b/projects/wvr-elements/src/lib/wvr-list/wvr-list.component.html index fc3f95188..05e21343e 100644 --- a/projects/wvr-elements/src/lib/wvr-list/wvr-list.component.html +++ b/projects/wvr-elements/src/lib/wvr-list/wvr-list.component.html @@ -1,35 +1,34 @@ -