Skip to content
This repository has been archived by the owner on Dec 8, 2022. It is now read-only.

Added sky adapter service #81

Merged
merged 7 commits into from
Jun 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/app-extras.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
} from './demos/dynamic-component/dynamic-component-example.component';

import {
SkyCoreAdapterModule,
SkyDynamicComponentModule,
SkyMediaQueryModule,
SkyNumericModule
} from './public';

@NgModule({
exports: [
SkyCoreAdapterModule,
SkyDynamicComponentModule,
SkyMediaQueryModule,
SkyNumericModule
Expand Down
14 changes: 14 additions & 0 deletions src/app/public/modules/adapter-service/adapter.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
NgModule
} from '@angular/core';

import {
SkyCoreAdapterService
} from './adapter.service';

@NgModule({
providers: [
SkyCoreAdapterService
]
})
export class SkyCoreAdapterModule { }
165 changes: 165 additions & 0 deletions src/app/public/modules/adapter-service/adapter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import {
ElementRef,
Injectable,
Renderer2,
RendererFactory2
} from '@angular/core';

import {
SkyMediaBreakpoints
} from '../media-query';

const SKY_TABBABLE_SELECTOR = [
Blackbaud-TrevorBurch marked this conversation as resolved.
Show resolved Hide resolved
'a[href]',
'area[href]',
'input:not([disabled]):not([tabindex=\'-1\'])',
'button:not([disabled]):not([tabindex=\'-1\'])',
'select:not([disabled]):not([tabindex=\'-1\'])',
'textarea:not([disabled]):not([tabindex=\'-1\'])',
'iframe',
'object',
'embed',
'*[tabindex]:not([tabindex=\'-1\'])',
'*[contenteditable=true]'
].join(', ');

@Injectable()
export class SkyCoreAdapterService {

private renderer: Renderer2;
Blackbaud-TrevorBurch marked this conversation as resolved.
Show resolved Hide resolved

constructor(
private rendererFactory: RendererFactory2
) {
this.renderer = this.rendererFactory.createRenderer(undefined, undefined);
}

/**
* Set the responsive container CSS class for a given element.
*
* @param {ElementRef} elementRef - The element that will recieve the new CSS class.
* @param {SkyMediaBreakpoints} breakpoint - The SkyMediaBreakpoint will determine which class
* gets set. For example a SkyMediaBreakpoint of `xs` will set a CSS class of `sky-responsive-container-xs`.
*/
public setResponsiveContainerClass(elementRef: ElementRef, breakpoint: SkyMediaBreakpoints): void {
const nativeEl: HTMLElement = elementRef.nativeElement;

this.renderer.removeClass(nativeEl, 'sky-responsive-container-xs');
this.renderer.removeClass(nativeEl, 'sky-responsive-container-sm');
this.renderer.removeClass(nativeEl, 'sky-responsive-container-md');
this.renderer.removeClass(nativeEl, 'sky-responsive-container-lg');

let newClass: string;

switch (breakpoint) {
case SkyMediaBreakpoints.xs: {
newClass = 'sky-responsive-container-xs';
break;
}
case SkyMediaBreakpoints.sm: {
newClass = 'sky-responsive-container-sm';
break;
}
case SkyMediaBreakpoints.md: {
newClass = 'sky-responsive-container-md';
break;
}
default: {
newClass = 'sky-responsive-container-lg';
break;
}
}

this.renderer.addClass(nativeEl, newClass);
}

/**
* This method temporarily enables/disables pointer events.
* This is helpful to prevent iFrames from interfering with drag events.
*
* @param {boolean} enable - Set to `true` to enable pointer events. Set to `false` to disable.
*/
public toggleIframePointerEvents(enable: boolean): void {
Blackbaud-TrevorBurch marked this conversation as resolved.
Show resolved Hide resolved
const iframes = document.querySelectorAll('iframe');
for (let i = 0; i < iframes.length; i++) {
// Setting to empty string will allow iframe to fall back to its prior CSS assignment.
iframes[i].style.pointerEvents = enable ? '' : 'none';
}
}

/**
* Focuses on the first element found with an `autofocus` attribute inside the supplied `elementRef`.
*
* @param {ElementRef} elementRef - The element to search within.
* @return {boolean} Returns `true` if a child element with autofocus is found.
*/
public applyAutoFocus(elementRef: ElementRef): boolean {
const elementWithAutoFocus = elementRef.nativeElement.querySelector('[autofocus]');

// Child was found with the autofocus property. Set focus and return true.
if (elementWithAutoFocus) {
elementWithAutoFocus.focus();
return true;
}

// No children were found with autofocus property. Return false.
return false;
}

/**
* Sets focus on the first focusable child of the `elementRef` parameter.
* If no focusable children are found, and `focusOnContainerIfNoChildrenFound` is `true`,
* focus will be set on the container element.
*
* @param {ElementRef} elementRef - The element to search within.
* @param {string} containerSelector - A CSS selector indicating the container that should
* recieve focus if no focusable children are found.
* @param {boolean} focusOnContainerIfNoChildrenFound - It set to `true`, the container will
* recieve focus if no focusable children are found.
*/
public getFocusableChildrenAndApplyFocus(
elementRef: ElementRef,
containerSelector: string,
focusOnContainerIfNoChildrenFound: boolean = false
): void {
const containerElement = elementRef.nativeElement.querySelector(containerSelector);
const focusableChildren = this.loadFocusableChildren(containerElement);

// Focus first focusable child if available. Otherwise, set focus on container.
if (!this.focusFirstElement(focusableChildren) && focusOnContainerIfNoChildrenFound) {
containerElement.focus();
}
}

private focusFirstElement(list: Array<HTMLElement>): boolean {
if (list.length > 0) {
list[0].focus();
return true;
}
return false;
}

private loadFocusableChildren(element: HTMLElement): HTMLElement[] {
const elements: Array<HTMLElement>
= Array.prototype.slice.call(element.querySelectorAll(SKY_TABBABLE_SELECTOR));

return elements.filter((el) => {
return this.isVisible(el);
});
}

private isVisible(element: HTMLElement): boolean {
const style = window.getComputedStyle(element);
const isHidden = style.display === 'none' || style.visibility === 'hidden';
if (isHidden) {
return false;
}

const hasBounds = !!(
element.offsetWidth ||
element.offsetHeight ||
element.getClientRects().length
);
return hasBounds;
}
}
2 changes: 2 additions & 0 deletions src/app/public/modules/adapter-service/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './adapter.module';
export * from './adapter.service';
1 change: 1 addition & 0 deletions src/app/public/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './adapter-service';
export * from './dynamic-component';
export * from './format';
export * from './log';
Expand Down