angular / angular/components

feat(FlexibleConnectedPositionStrategy): Handle custom containers for flexible connected position strategy

Aperta
#27,644 0 commenti 3 reazioni 0 assegnatari Vedi su GitHub
area: cdk/overlay feature P5
Lingua principale
TypeScript
Stelle
25k
Fork
6.8k
Merge medio
1g 8h
PR unite (30g)
91

Descrizione

### Feature Description

I would like to be able to provide a custom viewport to calculate `FlexibleConnectedTo` positions. Now a custom overlay container can be provided by the user, but in tandem with the `FlexibleConnectedTo` it does not work correctly, because under the hood `_getOriginRect` works with real viewport and pushing element into the custom container frame is calculated relative to the real `document.clientWidth` and `document.clientHeight`

For example, lets imagine we have `div.custom-overlay-container` with `500x200px` dimensions and tooltip directive built with CDK. How I solve this problem now to achieve desired behavior:

**Tooltip.directive.ts**
```
import { FlexibleConnectedPositionStrategy, OriginConnectionPosition, Overlay, OverlayConnectionPosition, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import {
ComponentRef,
Directive,
ElementRef,
HostBinding,
HostListener,
Input,
OnInit,
TemplateRef,
} from '@angular/core';

const FIXED_WIDTH = 200;
const FIXED_HEIGHT = 500;

@Directive({
selector: '[cdkTooltip]',
standalone: true,
})
export class TooltipDirective implements OnInit {
@Input('cdkTooltip') content: string | TemplateRef | null = '';

@Input() position: 'top' | 'right' | 'bottom' | 'left' = 'bottom';

private overlayRef: Maybe;

private tooltipHovered = false;

@HostListener('mouseenter')
show(): void {
if (!this.content) {
return;
}

this.overlayRef?.detach();

const tooltipPortal = new ComponentPortal(TooltipComponent);
if (this.overlayRef) {
const tooltipRef: ComponentRef = this.overlayRef.attach(tooltipPortal);
const tooltipHostElement = tooltipRef.location.nativeElement as HTMLElement;
tooltipRef.setInput(typeof this.content === 'string' ? 'text' : 'template', this.content);
}
}

@HostListener('mouseleave')
hide(): void {
this.overlayRef?.detach();
}

constructor(
private readonly overlay: Overlay,
private readonly elementRef: ElementRef,
) {}

ngOnInit(): void {
this.createOverlay();
}

private createOverlay(): void {
const strategy = this.overlay.position().flexibleConnectedTo(this.elementRef).withPush(true);

// Fix for correct tooltip position calculations:
// the problem is in the `_getOriginRect` function, it calculates the `DomRect` object
// for tooltip host relative to the real viewport, but the application content area is limited
// by the size of the `div.custom-overlay-container`, so we need to override it for correct calculations
// CDK source where problem method defined -
(strategy as any)._getOriginRect = () => {
const customViewportContainer = document.querySelector('.custom-overlay-container')!;
return this.getRelativeBoundingClientRect(
this.elementRef.nativeElement,
customViewportContainer,
);
};

// Override position's `_document.documentElement` for correct position adjustment,
// which is used for pushing element inside viewport (`div.custom-overlay-container` in our case)
// https://github.com/angular/components/blob/b13c6aa194cf560a304213961ae28725f8d0a4e2/src/cdk/overlay/position/flexible-connected-position-strategy.ts#L1036
Object.defineProperty(strategy, '_document', {
writable: true,
value: {
documentElement: {
clientWidth: FIXED_WIDTH,
clientHeight: FIXED_HEIGHT,
},
},
});

this.overlayRef = this.overlay.create({
positionStrategy: strategy,
});

this.setOverlayPosition(this.overlayRef);
}

private setOverlayPosition(overlayRef: OverlayRef): void {
const pos = overlayRef.getConfig().positionStrategy as FlexibleConnectedPositionStrategy;
pos.withPositions([
{
...this.getOriginPosition(),
...this.getOverlayPosition(),
panelClass: this.position,
},
]);
}

private getOverlayPosition(): OverlayConnectionPosition {
const position = this.position;

if (position === 'top') {
return { overlayX: 'center', overlayY: 'bottom' };
}

if (position === 'bottom') {
return { overlayX: 'center', overlayY: 'top' };
}

if (position === 'right') {
return { overlayX: 'start', overlayY: 'center' };
}

return { overlayX: 'end', overlayY: 'center' };
}

private getOriginPosition(): OriginConnectionPosition {
const position = this.position;

if (position === 'bottom' || position === 'top') {
return {
originX: 'center',
originY: position === 'top' ? 'top' : 'bottom',
};
}

if (position === 'right') {
return { originX: 'start', originY: 'center' };
}

return { originX: 'end', originY: 'center' };
}

private getRelativeBoundingClientRect(element: HTMLElement, relativeTo: HTMLElement): DOMRect {
const elemRect = element.getBoundingClientRect();
const containerRect = relativeTo.getBoundingClientRect();

return {
top: elemRect.top - containerRect.top,
bottom: elemRect.bottom - containerRect.top,
left: elemRect.left - containerRect.left,
right: elemRect.right - containerRect.left,
width: elemRect.width,
height: elemRect.height,
x: elemRect.left - containerRect.left,
y: elemRect.top - containerRect.top,
} as DOMRect;
}
}

```

### Use Case

the problem that I propose to solve is to add the ability `FlexibleConnectedTo` position to be able to work strictly within the custom container, that is, when `withPush` is enabled - the elements should not go beyond the custom container

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Inizia da src/cdk/overlay/position/flexible-connected-position-strategy.ts, in particolare da _getOriginRect e dall’aggiustamento del viewport con withPush citato dall’issue. Traccia il modo in cui FlexibleConnectedPositionStrategy ottiene le dimensioni del viewport e i limiti dell’origine, quindi definisci il supporto per un contenitore di overlay personalizzato. Il lavoro è completato quando withPush mantiene gli overlay all’interno di quel contenitore invece che nel viewport reale del documento, con test per il caso descritto di contenitore personalizzato.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
angular, typescript
Ambito
frontend
Tipo di issue
Funzionalità
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.