-
Notifications
You must be signed in to change notification settings - Fork 5
Added sky adapter service #81
Changes from 4 commits
66ef7d9
0502152
87a3829
6d5f58e
06bae5a
440674d
504da83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { | ||
NgModule | ||
} from '@angular/core'; | ||
|
||
import { | ||
SkyAdapterService | ||
} from './adapter.service'; | ||
|
||
@NgModule({ | ||
providers: [ | ||
SkyAdapterService | ||
] | ||
}) | ||
export class SkyAdapterModule { } | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import { | ||
ElementRef, | ||
Injectable, | ||
Renderer2, | ||
RendererFactory2 | ||
} from '@angular/core'; | ||
|
||
import { | ||
SkyMediaBreakpoints | ||
} from '../media-query'; | ||
|
||
/* tslint:disable */ | ||
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(', '); | ||
/* tslint:enable */ | ||
|
||
@Injectable() | ||
export class SkyAdapterService { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we might need a better name. I totally get that this is an adapter but I'm wondering if we will get confused down the road with what this is vs the other adapters we have. Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe? Lol. I think that is better than what is currently here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './adapter.module'; | ||
export * from './adapter.service'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should rename the module here too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good catch. Done.