@taiga-ui/kit
Version:
Taiga UI Angular main components kit
235 lines (230 loc) • 11 kB
JavaScript
import { __decorate, __param } from 'tslib';
import { DOCUMENT } from '@angular/common';
import { Inject, ComponentFactoryResolver, Injector, ElementRef, Host, Optional, ChangeDetectorRef, NgZone, Renderer2, ViewContainerRef, Input, Directive, forwardRef, NgModule } from '@angular/core';
import { ALWAYS_TRUE_HANDLER, typedFromEvent, getNativeFocused, px, CHAR_ZERO_WIDTH_SPACE, CHAR_NO_BREAK_SPACE, TuiPortalService, TuiActiveZoneDirective, TuiDestroyService, TuiParentsScrollService } from '@taiga-ui/cdk';
import { AbstractTuiDropdown, TUI_DOCUMENT_OR_SHADOW_ROOT, TUI_ELEMENT_REF, TUI_DROPDOWN_DIRECTIVE, TuiDropdownBoxModule } from '@taiga-ui/core';
import { getWordRange } from '@taiga-ui/kit/utils/dom';
import { merge } from 'rxjs';
import { switchMapTo, takeUntil, map } from 'rxjs/operators';
var TuiDropdownSelectionDirective_1;
// @dynamic
let TuiDropdownSelectionDirective = TuiDropdownSelectionDirective_1 = class TuiDropdownSelectionDirective extends AbstractTuiDropdown {
constructor(documentRef, componentFactoryResolver, injector, portalService, elementRef, activeZone, shadowRootRef, customElementRef, destroy$, refresh$, changeDetectorRef, ngZone, renderer, viewContainerRef) {
super(componentFactoryResolver, injector, portalService, customElementRef || elementRef, activeZone);
this.refresh$ = refresh$;
this.changeDetectorRef = changeDetectorRef;
this.ngZone = ngZone;
this.renderer = renderer;
this.viewContainerRef = viewContainerRef;
this.position = "selection" /* Selection */;
this.visibilityHandler = ALWAYS_TRUE_HANDLER;
this.documentRef = shadowRootRef || documentRef;
this.range = this.documentRef.createRange();
const { nativeElement } = this.elementRef;
merge(typedFromEvent(this.documentRef, 'mouseup'), typedFromEvent(nativeElement, 'mousedown').pipe(switchMapTo(typedFromEvent(nativeElement, 'mousemove').pipe(takeUntil(typedFromEvent(this.documentRef, 'mouseup'))))), typedFromEvent(nativeElement, 'keyup'))
.pipe(map(() => {
const active = getNativeFocused(this.documentRef);
const selection = this.documentRef.getSelection();
if ((active instanceof HTMLInputElement ||
active instanceof HTMLTextAreaElement) &&
nativeElement.contains(active)) {
return this.veryVerySadInputFix(active);
}
return selection && selection.rangeCount
? selection.getRangeAt(0)
: this.range;
}), takeUntil(destroy$))
.subscribe(range => {
const contained = nativeElement.contains(range.commonAncestorContainer);
this.range = contained ? range : this.range;
const valid = contained &&
(!this.visibilityHandler || this.visibilityHandler(this.range));
this.toggleDropdownBox(valid || this.inDropdown(range));
});
}
set tuiDropdownSelection(handler) {
if (!handler) {
return;
}
const inHostAndValid = this.elementRef.nativeElement.contains(this.range.commonAncestorContainer) &&
handler(this.range);
this.visibilityHandler = handler;
this.toggleDropdownBox(inHostAndValid);
}
get clientRect() {
const { defaultView } = this.documentRef;
const { rangeRect } = this;
const frameElement = defaultView ? defaultView.frameElement : null;
if (!frameElement) {
return rangeRect;
}
const documentRect = frameElement.getBoundingClientRect();
return {
top: rangeRect.top + documentRect.top,
left: rangeRect.left + documentRect.left,
right: rangeRect.left + documentRect.left + rangeRect.width,
bottom: rangeRect.top + documentRect.top + rangeRect.height,
width: rangeRect.width,
height: rangeRect.height,
};
}
ngOnDestroy() {
this.closeDropdownBox();
if (this.ghost) {
this.renderer.removeChild(this.viewContainerRef.element.nativeElement, this.ghost);
}
}
/**
* get ClientRect of current Range according to provided position
*/
get rangeRect() {
switch (this.position) {
case "tag" /* Tag */:
const { commonAncestorContainer } = this.range;
const element = commonAncestorContainer.nodeType === Node.ELEMENT_NODE
? commonAncestorContainer
: commonAncestorContainer.parentNode;
return element.getBoundingClientRect();
case "word" /* Word */:
return getWordRange(this.range).getBoundingClientRect();
default:
return this.range.getBoundingClientRect();
}
}
/**
* Toggle dropdown visibility (has to be in ngZone.run because it could be initiated inside iframe in Editor)
*/
toggleDropdownBox(visible) {
this.ngZone.run(() => {
if (visible) {
this.openDropdownBox();
}
else {
this.closeDropdownBox();
}
this.changeDetectorRef.markForCheck();
});
}
/**
* Check if Node is inside dropdown
*/
boxContains(node) {
return (!!this.dropdownBoxRef &&
this.dropdownBoxRef.location.nativeElement.contains(node));
}
/**
* Check if given range is at leaset partially inside dropdown
*/
inDropdown(range) {
const { startContainer, endContainer } = range;
const inDropdown = this.boxContains(range.commonAncestorContainer);
const hostToDropdown = this.boxContains(endContainer) &&
this.elementRef.nativeElement.contains(startContainer);
const dropdownToHost = this.boxContains(startContainer) &&
this.elementRef.nativeElement.contains(endContainer);
return inDropdown || hostToDropdown || dropdownToHost;
}
/**
* Position invisible DIV and create Range similar to selected range inside input/textarea
*/
veryVerySadInputFix(element) {
const { ghost = this.initGhost(element) } = this;
const { top, left, width, height } = element.getBoundingClientRect();
const { selectionStart, selectionEnd } = element;
const range = this.documentRef.createRange();
const hostRect = this.elementRef.nativeElement.getBoundingClientRect();
ghost.style.top = px(top - hostRect.top);
ghost.style.left = px(left - hostRect.left);
ghost.style.width = px(width);
ghost.style.height = px(height);
ghost.textContent = CHAR_ZERO_WIDTH_SPACE + element.value + CHAR_NO_BREAK_SPACE;
range.setStart(ghost.firstChild, selectionStart || 0);
range.setEnd(ghost.firstChild, selectionEnd || 0);
return range;
}
/**
* Create an invisible DIV styled exactly like input/textarea element inside directive
*/
initGhost(element) {
const ghost = this.renderer.createElement('div');
const { nativeElement } = this.viewContainerRef.element;
const { font, letterSpacing, textTransform, padding } = getComputedStyle(element);
ghost.style.position = 'absolute';
ghost.style.pointerEvents = 'none';
ghost.style.opacity = '0';
ghost.style.whiteSpace = 'pre-wrap';
ghost.style.font = font;
ghost.style.letterSpacing = letterSpacing;
ghost.style.textTransform = textTransform;
ghost.style.padding = padding;
this.renderer.appendChild(nativeElement, ghost);
this.ghost = ghost;
return ghost;
}
};
TuiDropdownSelectionDirective.ctorParameters = () => [
{ type: Document, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
{ type: ComponentFactoryResolver, decorators: [{ type: Inject, args: [ComponentFactoryResolver,] }] },
{ type: Injector, decorators: [{ type: Inject, args: [Injector,] }] },
{ type: TuiPortalService, decorators: [{ type: Inject, args: [TuiPortalService,] }] },
{ type: ElementRef, decorators: [{ type: Host }, { type: Inject, args: [ElementRef,] }] },
{ type: TuiActiveZoneDirective, decorators: [{ type: Inject, args: [TuiActiveZoneDirective,] }, { type: Optional }] },
{ type: Document, decorators: [{ type: Inject, args: [TUI_DOCUMENT_OR_SHADOW_ROOT,] }, { type: Optional }] },
{ type: ElementRef, decorators: [{ type: Inject, args: [TUI_ELEMENT_REF,] }, { type: Optional }] },
{ type: TuiDestroyService, decorators: [{ type: Inject, args: [TuiDestroyService,] }] },
{ type: TuiParentsScrollService, decorators: [{ type: Inject, args: [TuiParentsScrollService,] }] },
{ type: ChangeDetectorRef, decorators: [{ type: Inject, args: [ChangeDetectorRef,] }] },
{ type: NgZone, decorators: [{ type: Inject, args: [NgZone,] }] },
{ type: Renderer2, decorators: [{ type: Inject, args: [Renderer2,] }] },
{ type: ViewContainerRef, decorators: [{ type: Inject, args: [ViewContainerRef,] }] }
];
__decorate([
Input()
], TuiDropdownSelectionDirective.prototype, "tuiDropdownSelection", null);
__decorate([
Input('tuiDropdownSelectionPosition')
], TuiDropdownSelectionDirective.prototype, "position", void 0);
TuiDropdownSelectionDirective = TuiDropdownSelectionDirective_1 = __decorate([
Directive({
selector: '[tuiDropdownSelection]:not(ng-container)',
providers: [
{
provide: TUI_DROPDOWN_DIRECTIVE,
useExisting: forwardRef(() => TuiDropdownSelectionDirective_1),
},
TuiParentsScrollService,
TuiDestroyService,
],
}),
__param(0, Inject(DOCUMENT)),
__param(1, Inject(ComponentFactoryResolver)),
__param(2, Inject(Injector)),
__param(3, Inject(TuiPortalService)),
__param(4, Host()), __param(4, Inject(ElementRef)),
__param(5, Inject(TuiActiveZoneDirective)),
__param(5, Optional()),
__param(6, Inject(TUI_DOCUMENT_OR_SHADOW_ROOT)),
__param(6, Optional()),
__param(7, Inject(TUI_ELEMENT_REF)),
__param(7, Optional()),
__param(8, Inject(TuiDestroyService)),
__param(9, Inject(TuiParentsScrollService)),
__param(10, Inject(ChangeDetectorRef)),
__param(11, Inject(NgZone)),
__param(12, Inject(Renderer2)),
__param(13, Inject(ViewContainerRef))
], TuiDropdownSelectionDirective);
let TuiDropdownSelectionModule = class TuiDropdownSelectionModule {
};
TuiDropdownSelectionModule = __decorate([
NgModule({
imports: [TuiDropdownBoxModule],
declarations: [TuiDropdownSelectionDirective],
exports: [TuiDropdownSelectionDirective],
})
], TuiDropdownSelectionModule);
/**
* Generated bundle index. Do not edit.
*/
export { TuiDropdownSelectionDirective, TuiDropdownSelectionModule };
//# sourceMappingURL=taiga-ui-kit-directives-dropdown-selection.js.map