src/dropdown/dropdown.service.ts
Properties |
|
Methods |
Accessors |
constructor(placeholderService: PlaceholderService, animationFrameService: AnimationFrameService)
|
|||||||||
Defined in src/dropdown/dropdown.service.ts:30
|
|||||||||
Parameters :
|
appendToBody | ||||||||||||||||
appendToBody(parentRef: HTMLElement, menuRef: HTMLElement, classList)
|
||||||||||||||||
Defined in src/dropdown/dropdown.service.ts:44
|
||||||||||||||||
Appends the menu to the body, or a
Parameters :
Returns :
HTMLElement
|
appendToDropdown | ||||||||
appendToDropdown(hostRef: HTMLElement)
|
||||||||
Defined in src/dropdown/dropdown.service.ts:77
|
||||||||
Reattach the dropdown menu to the parent container
Parameters :
Returns :
HTMLElement
|
ngOnDestroy |
ngOnDestroy()
|
Defined in src/dropdown/dropdown.service.ts:102
|
Returns :
void
|
Protected positionDropdown | ||||||
positionDropdown(parentRef, menuRef)
|
||||||
Defined in src/dropdown/dropdown.service.ts:106
|
||||||
Parameters :
Returns :
void
|
updatePosition | ||||
updatePosition(parentRef)
|
||||
Defined in src/dropdown/dropdown.service.ts:98
|
||||
position an open dropdown relative to the given parentRef
Parameters :
Returns :
void
|
Protected _offset |
Default value : defaultOffset
|
Defined in src/dropdown/dropdown.service.ts:30
|
Protected animationFrameSubscription |
Default value : new Subscription()
|
Defined in src/dropdown/dropdown.service.ts:28
|
Maintains an Event Observable Subscription for the global requestAnimationFrame.
requestAnimationFrame is tracked only if the |
Protected menuInstance |
Type : HTMLElement
|
Defined in src/dropdown/dropdown.service.ts:22
|
reference to the body appended menu |
offset | ||||||
getoffset()
|
||||||
Defined in src/dropdown/dropdown.service.ts:16
|
||||||
setoffset(value: literal type)
|
||||||
Defined in src/dropdown/dropdown.service.ts:12
|
||||||
Parameters :
Returns :
void
|
import { Injectable, ElementRef, OnDestroy } from "@angular/core";
import { PlaceholderService } from "carbon-components-angular/placeholder";
import { Subscription } from "rxjs";
import { position } from "@carbon/utils-position";
import { AnimationFrameService } from "carbon-components-angular/utils";
import { closestAttr } from "carbon-components-angular/utils";
const defaultOffset = { top: 0, left: 0 };
@Injectable()
export class DropdownService implements OnDestroy {
public set offset(value: { top?: number, left?: number }) {
this._offset = Object.assign({}, defaultOffset, value);
}
public get offset() {
return this._offset;
}
/**
* reference to the body appended menu
*/
protected menuInstance: HTMLElement;
/**
* Maintains an Event Observable Subscription for the global requestAnimationFrame.
* requestAnimationFrame is tracked only if the `Dropdown` is appended to the body otherwise we don't need it
*/
protected animationFrameSubscription = new Subscription();
protected _offset = defaultOffset;
constructor(
protected placeholderService: PlaceholderService,
protected animationFrameService: AnimationFrameService
) {}
/**
* Appends the menu to the body, or a `cds-placeholder` (if defined)
*
* @param parentRef container to position relative to
* @param menuRef menu to be appended to body
* @param classList any extra classes we should wrap the container with
*/
appendToBody(parentRef: HTMLElement, menuRef: HTMLElement, classList): HTMLElement {
// build the dropdown list container
menuRef.style.display = "block";
const dropdownWrapper = document.createElement("div");
dropdownWrapper.className = `dropdown ${classList}`;
dropdownWrapper.style.width = parentRef.offsetWidth + "px";
dropdownWrapper.style.position = "absolute";
dropdownWrapper.appendChild(menuRef);
// append it to the placeholder
if (this.placeholderService.hasPlaceholderRef()) {
this.placeholderService.appendElement(dropdownWrapper);
// or append it directly to the body
} else {
document.body.appendChild(dropdownWrapper);
}
this.menuInstance = dropdownWrapper;
this.animationFrameSubscription = this.animationFrameService.tick.subscribe(() => {
this.positionDropdown(parentRef, dropdownWrapper);
});
// run one position in sync, so we're less likely to have the view "jump" as we focus
this.positionDropdown(parentRef, dropdownWrapper);
return dropdownWrapper;
}
/**
* Reattach the dropdown menu to the parent container
* @param hostRef container to append to
*/
appendToDropdown(hostRef: HTMLElement): HTMLElement {
// if the instance is already removed don't try and remove it again
if (!this.menuInstance) { return; }
const instance = this.menuInstance;
const menu = instance.firstElementChild as HTMLElement;
// clean up the instance
this.menuInstance = null;
menu.style.display = "none";
hostRef.appendChild(menu);
this.animationFrameSubscription.unsubscribe();
if (this.placeholderService.hasPlaceholderRef() && this.placeholderService.hasElement(instance)) {
this.placeholderService.removeElement(instance);
} else if (document.body.contains(instance)) {
document.body.removeChild(instance);
}
return instance;
}
/**
* position an open dropdown relative to the given parentRef
*/
updatePosition(parentRef) {
this.positionDropdown(parentRef, this.menuInstance);
}
ngOnDestroy() {
this.animationFrameSubscription.unsubscribe();
}
protected positionDropdown(parentRef, menuRef) {
if (!menuRef) {
return;
}
let leftOffset = 0;
const boxMenu = menuRef.querySelector(".cds--list-box__menu");
if (boxMenu) {
// If the parentRef and boxMenu are in a different left position relative to the
// window, the the boxMenu position has already been flipped and a check needs to be done
// to see if it needs to stay flipped.
if (parentRef.getBoundingClientRect().left !== boxMenu.getBoundingClientRect().left) {
// The getBoundingClientRect().right of the boxMenu if it were hypothetically flipped
// back into the original position before the flip.
const testBoxMenuRightEdgePos =
parentRef.getBoundingClientRect().left - boxMenu.getBoundingClientRect().left + boxMenu.getBoundingClientRect().right;
if (testBoxMenuRightEdgePos > (window.innerWidth || document.documentElement.clientWidth)) {
leftOffset = parentRef.offsetWidth - boxMenu.offsetWidth;
}
// If it has not already been flipped, check if it is necessary to flip, ie. if the
// boxMenu is outside of the right viewPort.
} else if (boxMenu.getBoundingClientRect().right > (window.innerWidth || document.documentElement.clientWidth)) {
leftOffset = parentRef.offsetWidth - boxMenu.offsetWidth;
}
}
// If cds-placeholder has a parent with a position(relative|fixed|absolute) account for the parent offset
const closestMenuWithPos = closestAttr("position", ["relative", "fixed", "absolute"], menuRef.parentElement);
const topPos = closestMenuWithPos ? closestMenuWithPos.getBoundingClientRect().top * -1 : this.offset.top;
const leftPos = closestMenuWithPos ? closestMenuWithPos.getBoundingClientRect().left * -1 : this.offset.left + leftOffset;
let pos = position.findAbsolute(parentRef, menuRef, "bottom");
pos = position.addOffset(pos, topPos, leftPos);
position.setElement(menuRef, pos);
}
}