diff --git a/projects/tui-editor/components/editor/dropdown/dropdown-toolbar.directive.ts b/projects/tui-editor/components/editor/dropdown/dropdown-toolbar.directive.ts index 7529f7d02..6ca16d02c 100644 --- a/projects/tui-editor/components/editor/dropdown/dropdown-toolbar.directive.ts +++ b/projects/tui-editor/components/editor/dropdown/dropdown-toolbar.directive.ts @@ -1,36 +1,17 @@ -import {DOCUMENT} from '@angular/common'; +import {Directive, Input} from '@angular/core'; import { - Directive, - ElementRef, - Inject, - Input, - OnDestroy, - ViewContainerRef, -} from '@angular/core'; -import { - ALWAYS_TRUE_HANDLER, - CHAR_NO_BREAK_SPACE, - CHAR_ZERO_WIDTH_SPACE, EMPTY_CLIENT_RECT, - TUI_RANGE, TuiBooleanHandler, - tuiGetNativeFocused, tuiIsElement, - tuiIsString, - tuiIsTextfield, tuiIsTextNode, - tuiPx, } from '@taiga-ui/cdk'; import { - TUI_SELECTION_STREAM, tuiAsDriver, tuiAsRectAccessor, - TuiDriver, - TuiDropdownDirective, + TuiDropdownSelectionDirective, tuiGetWordRange, - TuiRectAccessor, } from '@taiga-ui/core'; -import {BehaviorSubject, combineLatest, Observable} from 'rxjs'; +import {combineLatest} from 'rxjs'; import {distinctUntilChanged, map} from 'rxjs/operators'; @Directive({ @@ -40,15 +21,8 @@ import {distinctUntilChanged, map} from 'rxjs/operators'; tuiAsRectAccessor(TuiDropdownToolbarDirective), ], }) -export class TuiDropdownToolbarDirective - extends TuiDriver - implements TuiRectAccessor, OnDestroy -{ - private readonly handler$ = new BehaviorSubject>( - ALWAYS_TRUE_HANDLER, - ); - - private readonly stream$ = combineLatest([ +export class TuiDropdownToolbarDirective extends TuiDropdownSelectionDirective { + protected override readonly stream$ = combineLatest([ this.handler$, this.selection$.pipe( map(() => this.getRange()), @@ -71,32 +45,15 @@ export class TuiDropdownToolbarDirective }), ); - private ghost?: HTMLElement; - @Input('tuiToolbarDropdownPosition') - position: 'selection' | 'tag' | 'word' = 'selection'; + override position: 'selection' | 'tag' | 'word' = 'selection'; @Input() set tuiToolbarDropdown(visible: TuiBooleanHandler | string) { - if (!tuiIsString(visible)) { - this.handler$.next(visible); - } - } - - readonly type = 'dropdown'; - - constructor( - @Inject(TUI_RANGE) private range: Range, - @Inject(DOCUMENT) private readonly doc: Document, - @Inject(TUI_SELECTION_STREAM) private readonly selection$: Observable, - @Inject(ElementRef) private readonly el: ElementRef, - @Inject(ViewContainerRef) private readonly vcr: ViewContainerRef, - @Inject(TuiDropdownDirective) private readonly dropdown: TuiDropdownDirective, - ) { - super(subscriber => this.stream$.subscribe(subscriber)); + this.tuiDropdownSelection = visible; } - getClientRect(): ClientRect { + override getClientRect(): ClientRect { switch (this.position) { case 'tag': { const {commonAncestorContainer} = this.range; @@ -105,7 +62,9 @@ export class TuiDropdownToolbarDirective : commonAncestorContainer.parentNode; return element && tuiIsElement(element) - ? element.getBoundingClientRect() + ? this.doc + .querySelector('.ProseMirror-selectednode') + ?.getBoundingClientRect() || element.getBoundingClientRect() : EMPTY_CLIENT_RECT; } case 'word': @@ -128,84 +87,4 @@ export class TuiDropdownToolbarDirective } } } - - ngOnDestroy(): void { - if (this.ghost) { - this.vcr.element.nativeElement.removeChild(this.ghost); - } - } - - private getRange(): Range { - const active = tuiGetNativeFocused(this.doc); - const selection = this.doc.getSelection(); - const range = - active && tuiIsTextfield(active) && this.el.nativeElement.contains(active) - ? this.veryVerySadInputFix(active) - : (selection?.rangeCount && selection.getRangeAt(0)) || this.range; - - return range.cloneRange(); - } - - /** - * Check if Node is inside dropdown - */ - private boxContains(node: Node): boolean { - return !!this.dropdown.dropdownBoxRef?.location.nativeElement.contains(node); - } - - /** - * Check if given range is at least partially inside dropdown - */ - private inDropdown(range: Range): boolean { - const {startContainer, endContainer} = range; - const {nativeElement} = this.el; - const inDropdown = this.boxContains(range.commonAncestorContainer); - const hostToDropdown = - this.boxContains(endContainer) && nativeElement.contains(startContainer); - const dropdownToHost = - this.boxContains(startContainer) && nativeElement.contains(endContainer); - - return inDropdown || hostToDropdown || dropdownToHost; - } - - private veryVerySadInputFix(element: HTMLInputElement | HTMLTextAreaElement): Range { - const {ghost = this.initGhost(element)} = this; - const {top, left, width, height} = element.getBoundingClientRect(); - const {selectionStart, selectionEnd, value} = element; - const range = this.doc.createRange(); - const hostRect = this.el.nativeElement.getBoundingClientRect(); - - ghost.style.top = tuiPx(top - hostRect.top); - ghost.style.left = tuiPx(left - hostRect.left); - ghost.style.width = tuiPx(width); - ghost.style.height = tuiPx(height); - ghost.textContent = CHAR_ZERO_WIDTH_SPACE + value + CHAR_NO_BREAK_SPACE; - - range.setStart(ghost.firstChild as Node, selectionStart || 0); - range.setEnd(ghost.firstChild as Node, selectionEnd || 0); - - return range; - } - - /** - * Create an invisible DIV styled exactly like input/textarea element inside directive - */ - private initGhost(element: HTMLInputElement | HTMLTextAreaElement): HTMLElement { - const ghost = this.doc.createElement('div'); - 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.vcr.element.nativeElement.appendChild(ghost); - this.ghost = ghost; - - return ghost; - } } diff --git a/projects/tui-editor/components/editor/editor.component.ts b/projects/tui-editor/components/editor/editor.component.ts index 920fc3018..0b4a60c0d 100644 --- a/projects/tui-editor/components/editor/editor.component.ts +++ b/projects/tui-editor/components/editor/editor.component.ts @@ -142,12 +142,14 @@ export class TuiEditorComponent } get isLinkSelected(): boolean { - const node = this.doc.getSelection()?.focusNode?.parentNode; + const focusElement = this.doc.getSelection()?.focusNode; + const parentFocusElement = focusElement?.parentNode; return ( - node?.nodeName.toLowerCase() === 'a' || - node?.parentNode?.nodeName.toLowerCase() === 'a' || - !!node?.parentElement?.closest('tui-edit-link') + parentFocusElement?.nodeName.toLowerCase() === 'a' || + parentFocusElement?.parentNode?.nodeName.toLowerCase() === 'a' || + focusElement?.nodeName.toLowerCase() === 'a' || + !!parentFocusElement?.parentElement?.closest('tui-edit-link') ); } diff --git a/taiga-ui b/taiga-ui index af7e3ae1b..5e768bdef 160000 --- a/taiga-ui +++ b/taiga-ui @@ -1 +1 @@ -Subproject commit af7e3ae1b9068fbfdfa14fb1888ef4ea5f9ad581 +Subproject commit 5e768bdef90aeb9e18adbbd527ef58aa26dd8d80