Skip to content

Commit

Permalink
fix: incorrect dropdown behaviour (#940)
Browse files Browse the repository at this point in the history
  • Loading branch information
splincode authored Apr 1, 2024
1 parent ebbd82b commit 3634bba
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 137 deletions.
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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<TuiBooleanHandler<Range>>(
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()),
Expand All @@ -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<Range> | 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<unknown>,
@Inject(ElementRef) private readonly el: ElementRef<HTMLElement>,
@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;
Expand All @@ -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':
Expand All @@ -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;
}
}
10 changes: 6 additions & 4 deletions projects/tui-editor/components/editor/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
);
}

Expand Down
2 changes: 1 addition & 1 deletion taiga-ui
Submodule taiga-ui updated 93 files
+43 −0 CHANGELOG.md
+683 −637 package-lock.json
+2 −2 package.json
+7 −10 projects/addon-charts/components/pie-chart/pie-chart.style.less
+2 −2 projects/addon-charts/components/ring-chart/ring-chart.component.ts
+17 −8 projects/addon-charts/components/ring-chart/ring-chart.style.less
+1 −0 projects/addon-charts/components/ring-chart/ring-chart.template.html
+3 −3 projects/addon-charts/package.json
+2 −0 projects/addon-commerce/components/input-card-grouped/input-card-grouped.component.ts
+2 −0 projects/addon-commerce/components/input-card-grouped/input-card-grouped.options.ts
+4 −0 projects/addon-commerce/components/input-card-grouped/input-card-grouped.style.less
+1 −0 projects/addon-commerce/components/input-card-grouped/input-card-grouped.template.html
+3 −0 projects/addon-commerce/components/input-cvc/input-cvc.component.ts
+4 −0 projects/addon-commerce/components/input-cvc/input-cvc.style.less
+1 −0 projects/addon-commerce/components/input-cvc/input-cvc.template.html
+5 −5 projects/addon-commerce/package.json
+2 −2 projects/addon-doc/components/main/main.component.ts
+1 −1 projects/addon-doc/components/main/main.template.html
+109 −53 projects/addon-doc/components/navigation/navigation.template.html
+5 −5 projects/addon-doc/package.json
+4 −4 projects/addon-mobile/package.json
+5 −5 projects/addon-preview/package.json
+4 −0 projects/addon-table/components/table-pagination/table-pagination.template.html
+1 −1 projects/addon-table/components/table/directives/head.directive.ts
+1 −1 projects/addon-table/components/table/th/th.component.ts
+6 −2 projects/addon-table/directives/table-filters/table-filter.directive.ts
+5 −5 projects/addon-table/package.json
+3 −3 projects/addon-tablebars/package.json
+1 −1 projects/cdk/constants/version.ts
+1 −1 projects/cdk/package.json
+6 −2 projects/core/components/expand/expand.component.ts
+2 −1 projects/core/components/notification/notification.style.less
+4 −2 projects/core/components/primitive-textfield/textfield/textfield.component.ts
+19 −19 projects/core/directives/dropdown/dropdown-selection.directive.ts
+6 −1 projects/core/directives/hint/hint-hover.directive.ts
+4 −4 projects/core/package.json
+2 −1 projects/core/styles/theme/appearance/outline.less
+2 −1 projects/demo-playwright/tests/demo/get-demo-paths.ts
+9 −0 projects/demo/src/modules/app/app.routes.ts
+0 −2 projects/demo/src/modules/app/home/examples/main-standalone-optional.md
+0 −2 projects/demo/src/modules/app/home/examples/main-standalone.md
+13 −0 projects/demo/src/modules/app/pages.ts
+1 −1 projects/demo/src/modules/charts/pie-chart/examples/2/index.ts
+9 −3 projects/demo/src/modules/charts/ring-chart/ring-chart.component.ts
+1 −1 projects/demo/src/modules/charts/ring-chart/ring-chart.template.html
+6 −6 projects/demo/src/modules/components/notification/examples/3/index.html
+6 −0 projects/demo/src/modules/experimental/avatar/avatar.component.ts
+4 −0 projects/demo/src/modules/experimental/avatar/avatar.module.ts
+16 −0 projects/demo/src/modules/experimental/avatar/avatar.template.html
+23 −0 projects/demo/src/modules/experimental/avatar/examples/7/index.html
+4 −0 projects/demo/src/modules/experimental/avatar/examples/7/index.less
+12 −0 projects/demo/src/modules/experimental/avatar/examples/7/index.ts
+31 −0 projects/demo/src/modules/experimental/button/examples/2/index.html
+53 −0 projects/demo/src/modules/experimental/skeleton/examples/1/index.html
+6 −0 projects/demo/src/modules/experimental/skeleton/examples/1/index.less
+14 −0 projects/demo/src/modules/experimental/skeleton/examples/1/index.ts
+16 −0 projects/demo/src/modules/experimental/skeleton/examples/2/index.html
+13 −0 projects/demo/src/modules/experimental/skeleton/examples/2/index.ts
+13 −0 projects/demo/src/modules/experimental/skeleton/examples/import/import.md
+3 −0 projects/demo/src/modules/experimental/skeleton/examples/import/insert.md
+21 −0 projects/demo/src/modules/experimental/skeleton/skeleton.component.ts
+43 −0 projects/demo/src/modules/experimental/skeleton/skeleton.module.ts
+55 −0 projects/demo/src/modules/experimental/skeleton/skeleton.template.html
+1 −1 projects/demo/src/modules/markup/shadows/shadows.template.html
+2 −0 projects/demo/src/modules/tables/table/examples/3/index.html
+5 −2 projects/experimental/components/checkbox/checkbox.component.ts
+12 −0 projects/experimental/directives/avatar-outline/avatar-outline.component.ts
+28 −0 projects/experimental/directives/avatar-outline/avatar-outline.directive.ts
+10 −0 projects/experimental/directives/avatar-outline/avatar-outline.module.ts
+36 −0 projects/experimental/directives/avatar-outline/avatar-outline.styles.less
+3 −0 projects/experimental/directives/avatar-outline/index.ts
+5 −0 projects/experimental/directives/avatar-outline/ng-package.json
+2 −0 projects/experimental/directives/index.ts
+3 −0 projects/experimental/directives/skeleton/index.ts
+5 −0 projects/experimental/directives/skeleton/ng-package.json
+12 −0 projects/experimental/directives/skeleton/skeleton.component.ts
+57 −0 projects/experimental/directives/skeleton/skeleton.directive.ts
+10 −0 projects/experimental/directives/skeleton/skeleton.module.ts
+37 −0 projects/experimental/directives/skeleton/skeleton.style.less
+5 −5 projects/experimental/package.json
+1 −1 projects/i18n/package.json
+2 −2 projects/icons/package.json
+1 −5 projects/kit/components/input-date-multi/input-date-multi.component.ts
+1 −1 projects/kit/components/input-date-multi/input-date-multi.template.html
+4 −3 projects/kit/components/input-files/input-files.component.ts
+3 −10 projects/kit/components/stepper/stepper.component.ts
+7 −2 projects/kit/components/tabs/tab/tab.style.less
+4 −4 projects/kit/package.json
+12 −3 projects/kit/tokens/countries-masks.ts
+3 −3 projects/layout/package.json
+2 −2 projects/styles/package.json
+3 −3 projects/taiga-schematics/package.json
+2 −2 projects/testing/package.json

0 comments on commit 3634bba

Please sign in to comment.