Skip to content

feat(select): select search cosmetic and behavior fixes #136

Merged
merged 11 commits into from
Jun 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('OverlayKeyboardDispatcher', () => {
expect(overlayTwoSpy).toHaveBeenCalled();
});

it('should dispatch keyboard events when propagation is stopped', () => {
it('should not dispatch keyboard events when propagation is stopped', () => {
const overlayRef = overlay.create();
const spy = jasmine.createSpy('keyboard event spy');
const button = document.createElement('button');
Expand All @@ -84,7 +84,7 @@ describe('OverlayKeyboardDispatcher', () => {
keyboardDispatcher.add(overlayRef);
dispatchKeyboardEvent(button, 'keydown', ESCAPE);

expect(spy).toHaveBeenCalled();
expect(spy).not.toHaveBeenCalled();

button.parentNode!.removeChild(button); //tslint:disable-line
});
Expand Down Expand Up @@ -140,10 +140,10 @@ describe('OverlayKeyboardDispatcher', () => {
spyOn(body, 'removeEventListener');

keyboardDispatcher.add(overlayRef);
expect(body.addEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function), true);
expect(body.addEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function));

overlayRef.dispose();
expect(body.removeEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function), true);
expect(body.removeEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function));
});

});
Expand Down
4 changes: 2 additions & 2 deletions packages/cdk/overlay/keyboard/overlay-keyboard-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class OverlayKeyboardDispatcher implements OnDestroy {
add(overlayRef: OverlayRef): void {
// Lazily start dispatcher once first overlay is added
if (!this._isAttached) {
this._document.body.addEventListener('keydown', this._keydownListener, true);
this._document.body.addEventListener('keydown', this._keydownListener);
this._isAttached = true;
}

Expand All @@ -61,7 +61,7 @@ export class OverlayKeyboardDispatcher implements OnDestroy {
/** Detaches the global keyboard event listener. */
private _detach() {
if (this._isAttached) {
this._document.body.removeEventListener('keydown', this._keydownListener, true);
this._document.body.removeEventListener('keydown', this._keydownListener);
this._isAttached = false;
}
}
Expand Down
3 changes: 1 addition & 2 deletions packages/mosaic-dev/select/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ export class DemoComponent implements OnInit {
multipleSearchCtrl: FormControl = new FormControl();
filteredMultipleOptions: Observable<string[]>;

allOptions = OPTIONS;
optionCounter = 0;

private options: string[] = OPTIONS;
options: string[] = OPTIONS.sort();

ngOnInit(): void {
this.filteredOptions = merge(
Expand Down
4 changes: 1 addition & 3 deletions packages/mosaic-dev/select/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,13 @@
<mc-cleaner></mc-cleaner>
</mc-form-field>

<mc-option value="StickedOption">Sticked Option</mc-option>
<mc-option *ngFor="let option of filteredOptions | async" [value]="option">{{ option }}</mc-option>
</mc-select>
</mc-form-field>

<br><br>

<button mc-button (click)="optionCounter = optionCounter + 1;allOptions.push(optionCounter.toString())">add option</button>
<button mc-button (click)="optionCounter = optionCounter + 1;options.push(optionCounter.toString())">add option</button>

<br><br>

Expand All @@ -60,7 +59,6 @@
<mc-cleaner></mc-cleaner>
</mc-form-field>

<mc-option value="StickedOption">Sticked Option</mc-option>
<mc-option *ngFor="let option of filteredMultipleOptions | async" [value]="option">{{ option }}</mc-option>
</mc-select>
</mc-form-field>
Expand Down
4 changes: 3 additions & 1 deletion packages/mosaic/form-field/form-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export class McFormField extends _McFormFieldMixinBase implements

hovered: boolean = false;

canCleanerClearByEsc: boolean = true;

constructor(public _elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef) {
super(_elementRef);
}
Expand Down Expand Up @@ -155,7 +157,7 @@ export class McFormField extends _McFormFieldMixinBase implements

onKeyDown(event: KeyboardEvent): void {
// tslint:disable-next-line:deprecation
if (event.keyCode === ESCAPE && this._control.focused && this.hasCleaner) {
if (this.canCleanerClearByEsc && event.keyCode === ESCAPE && this._control.focused && this.hasCleaner) {
if (this._control && this._control.ngControl) {
this._control.ngControl.reset();
}
Expand Down
4 changes: 4 additions & 0 deletions packages/mosaic/select/_select-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
background-color: if($is-dark, map-get($second, 700), map-get($background, background));
}

.mc-select__no-options-message {
color: mc-color($second, 400);
}

.mc-select__search-container {
border-bottom: {
width: 1px;
Expand Down
39 changes: 37 additions & 2 deletions packages/mosaic/select/select.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
SPACE,
TAB,
UP_ARROW,
A
A, ESCAPE
} from '@ptsecurity/cdk/keycodes';
import { OverlayContainer } from '@ptsecurity/cdk/overlay';
import { Platform } from '@ptsecurity/cdk/platform';
Expand Down Expand Up @@ -235,7 +235,7 @@ class SelectWithChangeEvent {
selector: 'select-with-search',
template: `
<mc-form-field>
<mc-select [(value)]="singleSelectedWithSearch">
<mc-select #select [(value)]="singleSelectedWithSearch">
<mc-form-field mcSelectSearch>
<input
mcInput
Expand All @@ -249,6 +249,8 @@ class SelectWithChangeEvent {
`
})
class SelectWithSearch {
@ViewChild(McSelect, {static: false}) select: McSelect;

singleSelectedWithSearch = 'Moscow';

searchCtrl: FormControl = new FormControl();
Expand Down Expand Up @@ -2294,6 +2296,39 @@ describe('McSelect', () => {

expect(optionsTexts).toEqual(['Kaluga', 'Luga']);
}));

it('should clear search by esc', (() => {
trigger.click();
fixture.detectChanges();

const inputElementDebug = fixture.debugElement.query(By.css('input'));

inputElementDebug.nativeElement.value = 'lu';

inputElementDebug.triggerEventHandler('input', { target: inputElementDebug.nativeElement });
fixture.detectChanges();

dispatchKeyboardEvent(inputElementDebug.nativeElement, 'keydown', ESCAPE);

fixture.detectChanges();

expect(inputElementDebug.nativeElement.value).toBe('');
}));

it('should close list by esc if input is empty', () => {
trigger.click();
fixture.detectChanges();

const inputElementDebug = fixture.debugElement.query(By.css('input'));

dispatchKeyboardEvent(inputElementDebug.nativeElement, 'keydown', ESCAPE);

fixture.detectChanges();

const selectInstance = fixture.componentInstance.select;

expect(selectInstance.panelOpen).toBe(false);
});
});

describe('with a selectionChange event handler', () => {
Expand Down
50 changes: 42 additions & 8 deletions packages/mosaic/select/select.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* tslint:disable:no-empty */

import {
AfterContentInit, AfterViewInit,
AfterContentInit,
AfterViewInit,
Attribute,
ChangeDetectionStrategy,
ChangeDetectorRef,
Expand All @@ -21,10 +22,12 @@ import {
OnInit,
Optional,
Output,
QueryList, Renderer2,
QueryList,
Renderer2,
Self,
SimpleChanges,
ViewChild, ViewChildren,
ViewChild,
ViewChildren,
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, FormGroupDirective, NgControl, NgForm } from '@angular/forms';
Expand All @@ -42,6 +45,7 @@ import {
SPACE,
UP_ARROW,
A,
ESCAPE,
PAGE_UP,
PAGE_DOWN
} from '@ptsecurity/cdk/keycodes';
Expand Down Expand Up @@ -118,7 +122,10 @@ const McSelectMixinBase: CanDisableCtor & HasTabIndexCtor & CanUpdateErrorStateC

@Directive({
selector: '[mcSelectSearch]',
exportAs: 'mcSelectSearch'
exportAs: 'mcSelectSearch',
host: {
'(keydown)': 'handleKeydown($event)'
}
})
export class McSelectSearch implements AfterContentInit, OnDestroy {
@ContentChild(McInput, {static: false}) input: McInput;
Expand All @@ -127,6 +134,10 @@ export class McSelectSearch implements AfterContentInit, OnDestroy {

isSearchChanged: boolean = false;

constructor(formField: McFormField) {
formField.canCleanerClearByEsc = false;
}

focus(): void {
this.input.focus();
}
Expand All @@ -137,7 +148,7 @@ export class McSelectSearch implements AfterContentInit, OnDestroy {

ngAfterContentInit(): void {
if (!this.input) {
throw Error('McSelectSearch does not work without input');
throw Error('McSelectSearch does not work without mcInput');
}

if (!this.input.ngControl) {
Expand All @@ -152,8 +163,17 @@ export class McSelectSearch implements AfterContentInit, OnDestroy {
ngOnDestroy(): void {
this.searchChangesSubscription.unsubscribe();
}
}

handleKeydown(event: KeyboardEvent) {
// tslint:disable-next-line:deprecation
if (event.keyCode === ESCAPE) {
if (this.input.value) {
this.reset();
event.stopPropagation();
}
}
}
}


@Directive({ selector: 'mc-select-trigger' })
Expand Down Expand Up @@ -197,7 +217,9 @@ export class McSelect extends McSelectMixinBase implements
controlType = 'mc-select';

hiddenItems: number = 0;
// todo localization
oneMoreText: string = '...ещё';
noOptionsText: string = 'Ничего не найдено';

/** The last measured value for the trigger's client bounding rect. */
triggerRect: ClientRect;
Expand Down Expand Up @@ -435,6 +457,12 @@ export class McSelect extends McSelectMixinBase implements
return this._panelOpen;
}

get isEmptySearchResult(): boolean {
return this.search &&
this.options.length === 0 &&
!!this.search.input.value;
}

private _panelOpen = false;

/** The scroll position of the overlay panel, calculated to center the selected option. */
Expand Down Expand Up @@ -549,6 +577,12 @@ export class McSelect extends McSelectMixinBase implements
resetSearch(): void {
if (this.search) {
this.search.reset();
/*
todo the incorrect behaviour of keyManager is possible here
to avoid first item selection (to provide correct options flipping on closed select)
we should process options update like it is the first options appearance
*/
this.search.isSearchChanged = false;
}
}

Expand Down Expand Up @@ -584,13 +618,13 @@ export class McSelect extends McSelectMixinBase implements
this.overlayDir.overlayRef.overlayElement.style.fontSize = `${this.triggerFontSize}px`;
}
});

this.resetSearch();
}

/** Closes the overlay panel and focuses the host element. */
close(): void {
if (this._panelOpen) {
// the order of calls is important
this.resetSearch();
this._panelOpen = false;
this.keyManager.withHorizontalOrientation(this.isRtl() ? 'rtl' : 'ltr');

Expand Down
1 change: 1 addition & 0 deletions packages/mosaic/select/select.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
[@fadeInContent]="'showing'"
(@fadeInContent.done)="onFadeInDone()">

<div *ngIf="isEmptySearchResult" class="mc-select__no-options-message">{{noOptionsText}}</div>
<ng-content select="mc-option,mc-optgroup"></ng-content>
</div>
</div>
Expand Down
18 changes: 17 additions & 1 deletion packages/mosaic/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ $mc-select-placeholder-arrow-space: 2 * ($mc-select-arrow-size + $mc-select-arro
}
}

.mc-select__no-options-message {
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
position: relative;
max-width: 100%;
height: 32px;
cursor: default;
outline: none;
padding: 0 16px;
}

.mc-select__trigger {
display: flex;
box-sizing: border-box;
Expand All @@ -58,6 +71,9 @@ $mc-select-placeholder-arrow-space: 2 * ($mc-select-arrow-size + $mc-select-arro
overflow: hidden;
text-overflow: ellipsis;

// compensate borders
margin-top: -2px;

white-space: nowrap;

& > span {
Expand All @@ -71,7 +87,7 @@ $mc-select-placeholder-arrow-space: 2 * ($mc-select-arrow-size + $mc-select-arro

overflow: hidden;

max-height: 24px;
max-height: 26px;

margin: 0;
padding-left: 0;
Expand Down