Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
fix(filter-field, autocomplete): Fixes an issue that the panels did n…
Browse files Browse the repository at this point in the history
…ot react to viewport boundaries correctly.

Closes #1747.
  • Loading branch information
ffriedl89 committed Oct 28, 2020
1 parent c713b27 commit d225055
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 45 deletions.
54 changes: 39 additions & 15 deletions libs/barista-components/autocomplete/src/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ import {
DtViewportResizer,
isDefined,
stringify,
ViewportBoundaries,
mixinViewportBoundaries,
Constructor,
} from '@dynatrace/barista-components/core';
import { DtFormField } from '@dynatrace/barista-components/form-field';

Expand Down Expand Up @@ -136,6 +139,14 @@ export function calculateOptionHeight(
};
}

// Boilerplate for applying mixins to DtAutocompleteTrigger.
export class DtAutocompleteTriggerBase {
constructor(public _viewportResizer: DtViewportResizer) {}
}
export const _DtAutocompleteTriggerMixinBase = mixinViewportBoundaries<
Constructor<DtAutocompleteTriggerBase>
>(DtAutocompleteTriggerBase);

@Directive({
selector: `input[dtAutocomplete], textarea[dtAutocomplete]`,
exportAs: 'dtAutocompleteTrigger',
Expand All @@ -157,6 +168,7 @@ export function calculateOptionHeight(
providers: [DT_AUTOCOMPLETE_VALUE_ACCESSOR],
})
export class DtAutocompleteTrigger<T>
extends _DtAutocompleteTriggerMixinBase
implements ControlValueAccessor, OnDestroy {
private _optionHeight: number;
private _maxPanelHeight: number;
Expand Down Expand Up @@ -291,13 +303,14 @@ export class DtAutocompleteTrigger<T>
/** Old value of the native input. Used to work around issues with the `input` event on IE. */
private _previousValue: string | number | null;

private _destroy$ = new Subject<void>();
/** The stream that holds the viewport boundaries in order to be able to limit where an overlay can be positioned */
private _viewportBoundaries: ViewportBoundaries = { left: 0, top: 0 };

constructor(
private _element: ElementRef<HTMLInputElement>,
private _overlay: Overlay,
private _changeDetectorRef: ChangeDetectorRef,
private _viewportResizer: DtViewportResizer,
public _viewportResizer: DtViewportResizer,
private _zone: NgZone,
private _viewportRuler: ViewportRuler,
private _platform: Platform,
Expand All @@ -312,6 +325,7 @@ export class DtAutocompleteTrigger<T>
@Inject(DT_OPTION_CONFIG)
optionConfig?: DtOptionConfiguration,
) {
super(_viewportResizer);
// tslint:disable-next-line:strict-type-predicates
if (typeof window !== 'undefined') {
_zone.runOutsideAngular(() => {
Expand All @@ -328,16 +342,23 @@ export class DtAutocompleteTrigger<T>
});
}

if (this._viewportResizer) {
this._viewportResizer
.change()
.pipe(takeUntil(this._destroy$))
.subscribe(() => {
if (this.panelOpen && this._overlayRef) {
this._overlayRef.updateSize({ maxWidth: this._getPanelWidth() });
}
});
}
this._viewportResizer
.change()
.pipe(takeUntil(this._destroy$))
.subscribe(() => {
if (this.panelOpen && this._overlayRef) {
this._overlayRef.updateSize({ maxWidth: this._getPanelWidth() });
}
});

this._viewportBoundaries$.subscribe((boundaries) => {
this._viewportBoundaries = boundaries;
if (this.panelOpen) {
// attachOverlay updates the position strategy of an already existing
// overlay
this._attachOverlay();
}
});

const heightConfig = calculateOptionHeight(optionConfig?.height ?? 0);

Expand Down Expand Up @@ -495,7 +516,9 @@ export class DtAutocompleteTrigger<T>
} else {
// Update the panel width and position in case anything has changed.
this._overlayRef.updateSize({ maxWidth: this._getPanelWidth() });
this._overlayRef.updatePosition();
this._overlayRef.updatePositionStrategy(
this._getOverlayPositionStrategy(),
);
}

if (this._overlayRef && !this._overlayRef.hasAttached()) {
Expand Down Expand Up @@ -555,13 +578,13 @@ export class DtAutocompleteTrigger<T>
// TODO: reconsider if this config should be providable
private _getOverlayConfig(): OverlayConfig {
return new OverlayConfig({
positionStrategy: this._getOverlayPosition(),
positionStrategy: this._getOverlayPositionStrategy(),
scrollStrategy: this._overlay.scrollStrategies.reposition(),
maxWidth: this._getPanelWidth(),
});
}

private _getOverlayPosition(): PositionStrategy {
private _getOverlayPositionStrategy(): PositionStrategy {
const originalPositionStrategy = new DtFlexibleConnectedPositionStrategy(
this._getConnectedElement(),
this._viewportRuler,
Expand All @@ -573,6 +596,7 @@ export class DtAutocompleteTrigger<T>
this._positionStrategy = originalPositionStrategy
.withFlexibleDimensions(false)
.withPush(false)
.withViewportBoundaries(this._viewportBoundaries)
.withPositions([
{
originX: 'start',
Expand Down
37 changes: 18 additions & 19 deletions libs/barista-components/chart/src/selection-area/selection-area.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import {
_readKeyCode,
_removeCssClass,
ViewportBoundaries,
mixinViewportBoundaries,
Constructor,
} from '@dynatrace/barista-components/core';
import {
animationFrameScheduler,
Expand All @@ -58,8 +60,6 @@ import {
fromEvent,
merge,
Observable,
of,
Subject,
} from 'rxjs';
import {
concatMapTo,
Expand All @@ -68,7 +68,6 @@ import {
map,
mapTo,
share,
shareReplay,
startWith,
switchMap,
take,
Expand Down Expand Up @@ -115,6 +114,14 @@ import {
getTouchStream,
} from './streams';

// Boilerplate for applying mixins to DtChartSelectionArea.
export class DtChartSelectionAreaBase {
constructor(public _viewportResizer: DtViewportResizer) {}
}
export const _DtChartSelectionAreaMixinBase = mixinViewportBoundaries<
Constructor<DtChartSelectionAreaBase>
>(DtChartSelectionAreaBase);

@Component({
selector: 'dt-chart-selection-area',
templateUrl: 'selection-area.html',
Expand All @@ -128,7 +135,9 @@ import {
'[attr.tabindex]': '0',
},
})
export class DtChartSelectionArea implements AfterContentInit, OnDestroy {
export class DtChartSelectionArea
extends _DtChartSelectionAreaMixinBase
implements AfterContentInit, OnDestroy {
/** @internal The timestamp that follows the mouse */
@ViewChild('hairline', { static: true })
_hairline: ElementRef<HTMLDivElement>;
Expand Down Expand Up @@ -172,11 +181,6 @@ export class DtChartSelectionArea implements AfterContentInit, OnDestroy {
/** Stream that holds the Bounding Client Rect of the selection area. set after Highcharts render */
private _selectionAreaBcr$: Observable<ClientRect> = EMPTY;

/** Subject to unsubscribe from every subscription */
private _destroy$ = new Subject<void>();

private _viewportBoundaries$: Observable<ViewportBoundaries> = EMPTY;

constructor(
@SkipSelf() private _chart: DtChart,
private _elementRef: ElementRef<HTMLElement>,
Expand All @@ -189,18 +193,13 @@ export class DtChartSelectionArea implements AfterContentInit, OnDestroy {
private _changeDetectorRef: ChangeDetectorRef,
// tslint:disable-next-line: no-any
@Inject(DOCUMENT) private _document: any,
@Optional() private _viewportResizer: DtViewportResizer,
) {}
// @breaking-change Will be made mandatory with 9.0.0
@Optional() public _viewportResizer: DtViewportResizer,
) {
super(_viewportResizer);
}

ngAfterContentInit(): void {
this._viewportBoundaries$ = this._viewportResizer
? this._viewportResizer.offset$.pipe(
startWith({ top: 0, left: 0 }),
distinctUntilChanged(),
shareReplay(),
)
: of({ left: 0, top: 0 });

this._plotBackground$ = this._chart._afterRender.asObservable().pipe(
concatMapTo(this._chart._plotBackground$),
// plot background can be null as well
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from './progress';
export * from './tabindex';
export * from './dom-exit';
export * from './id';
export * from './viewport-boundaries';
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @license
* Copyright 2020 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ViewportRuler } from '@angular/cdk/overlay';
import { Platform } from '@angular/cdk/platform';
import { NgZone } from '@angular/core';
import { MockNgZone } from '@dynatrace/testing/browser';
import { DtDefaultViewportResizer, DtViewportResizer } from '../viewport';
import { mixinViewportBoundaries } from './viewport-boundaries';

describe('MixinViewportBoundaries', () => {
it('should augment an existing class with a _viewportBoundaries$ property', () => {
const classWithDisabled = mixinViewportBoundaries(TestClass);
const instance = new classWithDisabled();

// Expected the mixed-into class to have a _viewportBoundaries property
expect(instance).toHaveProperty('_viewportBoundaries$');
});

it('should augment an existing class with a _destroy$ property', () => {
const classWithDisabled = mixinViewportBoundaries(TestClass);
const instance = new classWithDisabled();

// Expected the mixed-into class to have a _viewportBoundaries property
expect(instance).toHaveProperty('_destroy$');
});
});

class TestClass {
_platform = new Platform('testid');
_zone: NgZone = new MockNgZone();
_viewportRuler = new ViewportRuler(this._platform, this._zone);
/** Fake instance of an DtViewportResizer. */
_viewportResizer: DtViewportResizer = new DtDefaultViewportResizer(
this._viewportRuler,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @license
* Copyright 2020 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { EMPTY, Observable, of, Subject } from 'rxjs';
import { distinctUntilChanged, startWith, takeUntil } from 'rxjs/operators';
import { ViewportBoundaries } from '../overlay/flexible-connected-position-strategy';
import { DtViewportResizer } from '../viewport';
import { Constructor } from './constructor';

export interface HasViewportBoundaries {
/** The current viewport boundaries */
_viewportBoundaries$: Observable<ViewportBoundaries>;
}

export interface HasDestroySubject {
_destroy$: Subject<void>;
}

export interface HasDtViewportResizer {
_viewportResizer: DtViewportResizer;
}

/** Mixin to augment a directive with a `viewportBoundaries$` property. */
export function mixinViewportBoundaries<
T extends Constructor<HasDtViewportResizer>
>(
base: T,
): Constructor<HasViewportBoundaries> & Constructor<HasDestroySubject> & T {
return class extends base {
_viewportBoundaries$: Observable<ViewportBoundaries> = EMPTY;

_destroy$ = new Subject<void>();

// tslint:disable-next-line:no-any
constructor(...args: any[]) {
super(...args);

this._viewportBoundaries$ = this._viewportResizer
? this._viewportResizer.offset$.pipe(
startWith({ top: 0, left: 0 }),
distinctUntilChanged(),
takeUntil(this._destroy$),
)
: of({ left: 0, top: 0 });
}
};
}
Loading

0 comments on commit d225055

Please sign in to comment.