Skip to content

Commit

Permalink
feat(stark-ui): implement SvgViewBox directive to enable resizing of …
Browse files Browse the repository at this point in the history
…Angular Material's mat-icon directive

ISSUES CLOSED: #455
  • Loading branch information
christophercr committed Jun 25, 2018
1 parent 0d654ca commit b4dc8ec
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 5 deletions.
2 changes: 1 addition & 1 deletion packages/stark-build/config/tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"pipe-impure": true,
"templates-no-negated-async": true,
"use-pipe-decorator": true,
"use-view-encapsulation": true,
"use-view-encapsulation": false,
"trackBy-function": true,
"no-unused-css": true,
"template-cyclomatic-complexity": [true, 5],
Expand Down
23 changes: 23 additions & 0 deletions packages/stark-core/tslint.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
{
"extends": ["tslint:latest", "tslint-sonarts", "codelyzer", "tslint-config-prettier", "../stark-build/config/tslint.json"],
"rules": {
"completed-docs": [
true,
{
"enums": true,
"variables": {
"tags": { "existence": ["ignore", "link", "param", "returns"] }
},
"functions": {
"tags": { "existence": ["ignore", "link", "param", "returns"] }
},
"interfaces": {
"tags": { "existence": ["ignore", "link", "param", "returns"] },
"visibilities": ["exported", "internal"]
},
"classes": {
"tags": { "existence": ["ignore", "link", "param", "returns"] },
"visibilities": ["internal"]
},
"methods": {
"tags": { "existence": ["ignore", "link", "param", "returns"] }
}
}
],
"jsdoc-format": [true, "check-multiline-start"],
"no-redundant-jsdoc": true
}
Expand Down
1 change: 1 addition & 0 deletions packages/stark-ui/src/modules.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./modules/app-logo";
export * from "./modules/svg-view-box";
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { STARK_LOGGING_SERVICE, STARK_ROUTING_SERVICE, StarkLoggingService, Star
const componentName: string = "stark-app-logo";

// FIXME: tslint rules temporarily disabled. Enable them once we decide the final implementation of component styles
/* tslint:disable:enforce-component-selector use-view-encapsulation use-host-property-decorator */
/* tslint:disable:enforce-component-selector use-host-property-decorator */
@Component({
selector: componentName,
templateUrl: "./app-logo.component.html",
Expand All @@ -26,8 +26,8 @@ export class StarkAppLogoComponent implements OnInit {

/**
* Class constructor
* @param logger : the logger of the application
* @param routingService : the routing service of the application
* @param logger - The logger of the application
* @param routingService - The routing service of the application
*/
public constructor(
@Inject(STARK_LOGGING_SERVICE) public logger: StarkLoggingService,
Expand All @@ -45,7 +45,7 @@ export class StarkAppLogoComponent implements OnInit {

/**
* Handles the event when a click is made on the logo
* @param $event: the handled event
* @param $event - The handled event
*/
public logoClickHandler($event: Event): void {
// cancel the event otherwise Angular triggers a full page reload :(
Expand Down
2 changes: 2 additions & 0 deletions packages/stark-ui/src/modules/svg-view-box.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./svg-view-box/svg-view-box.module";
export * from "./svg-view-box/directives";
1 change: 1 addition & 0 deletions packages/stark-ui/src/modules/svg-view-box/directives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./directives/svg-view-box.directive";
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*tslint:disable:completed-docs*/
import { ComponentFixture, fakeAsync, TestBed } from "@angular/core/testing";
import { StarkSvgViewBoxDirective } from "./svg-view-box.directive";
import { Component, DebugElement, NO_ERRORS_SCHEMA } from "@angular/core";
import { By } from "@angular/platform-browser";

describe("SvgViewBoxDirective", () => {
@Component({
selector: "test-component",
template: getTemplate("starkSvgViewBox")
})
class TestComponent {}

let fixture: ComponentFixture<TestComponent>;

function getTemplate(svgViewBoxDirective: string, viewBoxAttribute?: string): string {
return (
"<div " +
svgViewBoxDirective +
"><svg xmlns='http://www.w3.org/2000/svg' " +
viewBoxAttribute +
">" +
"<text font-size='8' font-family='serif' y='6'><![CDATA[dummy icon]]></text>" +
"</svg></div>"
);
}

function initializeComponentFixture(): void {
fixture = TestBed.createComponent(TestComponent);
// trigger initial data binding
fixture.detectChanges();
}

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [StarkSvgViewBoxDirective, TestComponent],
schemas: [NO_ERRORS_SCHEMA]
});
});

describe("when viewBox value is not defined", () => {
beforeEach(
fakeAsync(() => {
// compile template and css
return TestBed.compileComponents();
})
);

beforeEach(() => {
initializeComponentFixture();
});

it("should add the default values to the viewBox attribute of the svg element", () => {
expect(fixture).toBeDefined();
const parentElement: DebugElement = fixture.debugElement.query(By.directive(StarkSvgViewBoxDirective));
expect(parentElement).toBeDefined();
const svgElement: SVGElement = parentElement.nativeElement.querySelector("svg");
expect(svgElement).toBeDefined();
expect(svgElement.hasAttribute("viewBox")).toBe(true);
expect(svgElement.getAttribute("viewBox")).toBe("0 0 24 24");
});
});

describe("when viewBox value is given", () => {
const viewBoxValue: number = 48;

// overriding the components's template
beforeEach(
fakeAsync(() => {
const newTemplate: string = getTemplate("starkSvgViewBox='" + viewBoxValue + "'");

TestBed.overrideTemplate(TestComponent, newTemplate);

// compile template and css
return TestBed.compileComponents();
})
);

beforeEach(() => {
initializeComponentFixture();
});

it("should add the provided value as the width and height of the viewBox attribute of the svg element", () => {
expect(fixture).toBeDefined();
const parentElement: DebugElement = fixture.debugElement.query(By.directive(StarkSvgViewBoxDirective));
expect(parentElement).toBeDefined();
const svgElement: SVGElement = parentElement.nativeElement.querySelector("svg");
expect(svgElement).toBeDefined();
expect(svgElement.hasAttribute("viewBox")).toBe(true);
expect(svgElement.getAttribute("viewBox")).toBe(`0 0 ${viewBoxValue} ${viewBoxValue}`);
});
});

describe("when SVG has already the viewBox attribute", () => {
const viewBoxAttribute: string = "0 0 12 12";

// overriding the components's template
beforeEach(
fakeAsync(() => {
const newTemplate: string = getTemplate("starkSvgViewBox", "viewBox='" + viewBoxAttribute + "'");

TestBed.overrideTemplate(TestComponent, newTemplate);

// compile template and css
return TestBed.compileComponents();
})
);

beforeEach(() => {
initializeComponentFixture();
});

it("should keep the viewBox attribute as is", () => {
expect(fixture).toBeDefined();
const parentElement: DebugElement = fixture.debugElement.query(By.directive(StarkSvgViewBoxDirective));
expect(parentElement).toBeDefined();
const svgElement: SVGElement = parentElement.nativeElement.querySelector("svg");
expect(svgElement).toBeDefined();
expect(svgElement.hasAttribute("viewBox")).toBe(true);
expect(svgElement.getAttribute("viewBox")).toBe(viewBoxAttribute);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { AfterViewChecked, Directive, ElementRef, Input, Renderer2 } from "@angular/core";

/**
* Name of the directive
*/
const directiveName: string = "[starkSvgViewBox]";

@Directive({
selector: directiveName
})
/**
* Directive to add the 'viewBox' attribute to an SVG element.
* Specially useful to fix the issue with the Angular Material's MatIcon directive which prevents the icons from
* being re-sized via CSS due to the 'viewBox' property not being added to the SVG element.
* @link https://github.com/angular/material2/issues/4422
* @link https://github.com/angular/material2/issues/5488
*/
export class StarkSvgViewBoxDirective implements AfterViewChecked {
/**
* Width and height to be set to the 'viewBox' attribute of the SVG element.
*/
/* tslint:disable:no-input-rename */
@Input("starkSvgViewBox") private viewBoxSize: number;

/**
* Default value for the width and height of the 'viewBox' attribute if it is not given as an input.
*/
private defaultViewBoxSize: number = 24;

/**
* SVG element to which the viewBox attribute should be added.
*/
private svgIcon?: SVGElement;

/**
* Class constructor
* @param element - Reference to the DOM element where this directive is applied to.
* @param renderer - Angular Renderer wrapper for DOM manipulations.
*/
public constructor(public element: ElementRef<HTMLElement>, public renderer: Renderer2) {}

/**
* Directive lifecycle hook
*/
public ngAfterViewChecked(): void {
// the svg icon inside the <mat-icon> is only present at this point
// ensure that this should be set only once since the ngAfterViewChecked is triggered continuously
if (!this.svgIcon) {
this.svgIcon = this.element.nativeElement.querySelector("svg") || undefined;
this.viewBoxSize = this.viewBoxSize || this.defaultViewBoxSize;

// set the "viewBox" attribute only if the SVG element doesn't have any defined
if (this.svgIcon && !this.svgIcon.hasAttribute("viewBox")) {
// viewBox value: the points "seen" in the SVG drawing area. Four values separated by white space or commas. (min x, min y, width, height)
const viewBoxValue: string = `0 0 ${this.viewBoxSize} ${this.viewBoxSize}`;
this.renderer.setAttribute(this.svgIcon, "viewBox", viewBoxValue);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { NgModule } from "@angular/core";
import { StarkSvgViewBoxDirective } from "./directives";

@NgModule({
declarations: [StarkSvgViewBoxDirective],
exports: [StarkSvgViewBoxDirective]
})
export class StarkSvgViewBoxModule {}
23 changes: 23 additions & 0 deletions packages/stark-ui/tslint.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
{
"extends": ["tslint:latest", "tslint-sonarts", "codelyzer", "tslint-config-prettier", "../stark-build/config/tslint.json"],
"rules": {
"completed-docs": [
true,
{
"enums": true,
"variables": {
"tags": { "existence": ["ignore", "link", "param", "returns"] }
},
"functions": {
"tags": { "existence": ["ignore", "link", "param", "returns"] }
},
"interfaces": {
"tags": { "existence": ["ignore", "link", "param", "returns"] },
"visibilities": ["exported", "internal"]
},
"classes": {
"tags": { "existence": ["ignore", "link", "param", "returns"] },
"visibilities": ["internal"]
},
"methods": {
"tags": { "existence": ["ignore", "link", "param", "returns"] }
}
}
],
"jsdoc-format": [true, "check-multiline-start"],
"no-redundant-jsdoc": true
}
Expand Down

0 comments on commit b4dc8ec

Please sign in to comment.