Skip to content

Commit

Permalink
feat(select, autocomplete): ability to override options width (#2874)
Browse files Browse the repository at this point in the history
  • Loading branch information
evtkhvch committed Nov 8, 2021
1 parent c62d390 commit e30e00c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<nb-option-list *nbPortal
[size]="size"
[position]="overlayPosition"
[style.width.px]="hostWidth"
[style.width.px]="optionsWidth"
role="listbox"
[id]="id"
[class.empty]="!options?.length"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ let lastAutocompleteId: number = 0;
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NbAutocompleteComponent<T> implements AfterContentInit, OnDestroy {

protected destroy$: Subject<void> = new Subject<void>();

/**
Expand All @@ -55,7 +54,6 @@ export class NbAutocompleteComponent<T> implements AfterContentInit, OnDestroy {
* */
id: string = `nb-autocomplete-${lastAutocompleteId++}`;


/**
* @docs-private
* Current overlay position because of we have to toggle overlayPosition
Expand Down Expand Up @@ -83,7 +81,7 @@ export class NbAutocompleteComponent<T> implements AfterContentInit, OnDestroy {
/**
* Function passed as input to process each string option value before render.
* */
@Input() handleDisplayFn: ((value: any) => string);
@Input() handleDisplayFn: (value: any) => string;

/**
* Autocomplete size, available sizes:
Expand All @@ -106,14 +104,26 @@ export class NbAutocompleteComponent<T> implements AfterContentInit, OnDestroy {
* */
@Input() optionsPanelClass: string | string[];

/**
* Specifies width (in pixels) to be set on `nb-option`s container (`nb-option-list`)
* */
@Input()
get optionsWidth(): number {
return this._optionsWidth ?? this.hostWidth;
}
set optionsWidth(value: number) {
this._optionsWidth = value;
}
protected _optionsWidth: number | undefined;

/**
* Will be emitted when selected value changes.
* */
@Output() selectedChange: EventEmitter<T> = new EventEmitter();

/**
* List of `NbOptionComponent`'s components passed as content.
* */
* List of `NbOptionComponent`'s components passed as content.
* */
@ContentChildren(NbOptionComponent, { descendants: true }) options: QueryList<NbOptionComponent<T>>;

/**
Expand All @@ -124,9 +134,7 @@ export class NbAutocompleteComponent<T> implements AfterContentInit, OnDestroy {
constructor(protected cd: ChangeDetectorRef) {}

ngAfterContentInit() {
this.options.changes
.pipe(takeUntil(this.destroy$))
.subscribe(() => this.cd.detectChanges());
this.options.changes.pipe(takeUntil(this.destroy$)).subscribe(() => this.cd.detectChanges());
}

ngOnDestroy() {
Expand Down Expand Up @@ -169,5 +177,4 @@ export class NbAutocompleteComponent<T> implements AfterContentInit, OnDestroy {
get giant(): boolean {
return this.size === 'giant';
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
</nb-icon>
</button>

<nb-option-list *nbPortal [size]="size" [position]="overlayPosition" [style.width.px]="hostWidth" [ngClass]="optionsListClass">
<nb-option-list *nbPortal [size]="size" [position]="overlayPosition" [style.width.px]="optionsWidth" [ngClass]="optionsListClass">
<ng-content select="nb-option, nb-option-group"></ng-content>
</nb-option-list>
124 changes: 68 additions & 56 deletions src/framework/theme/components/select/select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ export type NbSelectAppearance = 'outline' | 'filled' | 'hero';
selector: 'nb-select-label',
template: '<ng-content></ng-content>',
})
export class NbSelectLabelComponent {
}
export class NbSelectLabelComponent {}

export function nbSelectFormFieldControlConfigFactory() {
const config = new NbFormFieldControlConfig();
Expand Down Expand Up @@ -519,9 +518,9 @@ export function nbSelectFormFieldControlConfigFactory() {
{ provide: NbFormFieldControlConfig, useFactory: nbSelectFormFieldControlConfigFactory },
],
})
export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContentInit, OnDestroy,
ControlValueAccessor, NbFormFieldControl {

export class NbSelectComponent
implements OnChanges, AfterViewInit, AfterContentInit, OnDestroy, ControlValueAccessor, NbFormFieldControl
{
/**
* Select size, available sizes:
* `tiny`, `small`, `medium` (default), `large`, `giant`
Expand Down Expand Up @@ -554,6 +553,18 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
* */
@Input() optionsPanelClass: string | string[];

/**
* Specifies width (in pixels) to be set on `nb-option`s container (`nb-option-list`)
* */
@Input()
get optionsWidth(): number {
return this._optionsWidth ?? this.hostWidth;
}
set optionsWidth(value: number) {
this._optionsWidth = value;
}
protected _optionsWidth: number | undefined;

/**
* Adds `outline` styles
*/
Expand Down Expand Up @@ -660,9 +671,7 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
this.writeValue(value);
}
get selected() {
return this.multiple
? this.selectionModel.map(o => o.value)
: this.selectionModel[0].value;
return this.multiple ? this.selectionModel.map((o) => o.value) : this.selectionModel[0].value;
}

/**
Expand Down Expand Up @@ -788,18 +797,19 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
**/
fullWidth$ = new BehaviorSubject<boolean>(this.fullWidth);

constructor(@Inject(NB_DOCUMENT) protected document,
protected overlay: NbOverlayService,
protected hostRef: ElementRef<HTMLElement>,
protected positionBuilder: NbPositionBuilderService,
protected triggerStrategyBuilder: NbTriggerStrategyBuilderService,
protected cd: ChangeDetectorRef,
protected focusKeyManagerFactoryService: NbFocusKeyManagerFactoryService<NbOptionComponent>,
protected focusMonitor: NbFocusMonitor,
protected renderer: Renderer2,
protected zone: NgZone,
protected statusService: NbStatusService) {
}
constructor(
@Inject(NB_DOCUMENT) protected document,
protected overlay: NbOverlayService,
protected hostRef: ElementRef<HTMLElement>,
protected positionBuilder: NbPositionBuilderService,
protected triggerStrategyBuilder: NbTriggerStrategyBuilderService,
protected cd: ChangeDetectorRef,
protected focusKeyManagerFactoryService: NbFocusKeyManagerFactoryService<NbOptionComponent>,
protected focusMonitor: NbFocusMonitor,
protected renderer: Renderer2,
protected zone: NgZone,
protected statusService: NbStatusService,
) {}

/**
* Determines is select hidden.
Expand Down Expand Up @@ -880,9 +890,11 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
this.subscribeOnOptionClick();

// TODO: #2254
this.zone.runOutsideAngular(() => setTimeout(() => {
this.renderer.addClass(this.hostRef.nativeElement, 'nb-transition');
}));
this.zone.runOutsideAngular(() =>
setTimeout(() => {
this.renderer.addClass(this.hostRef.nativeElement, 'nb-transition');
}),
);
}

ngOnDestroy() {
Expand Down Expand Up @@ -1001,7 +1013,7 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
* */
protected handleMultipleSelect(option: NbOptionComponent) {
if (option.selected) {
this.selectionModel = this.selectionModel.filter(s => !this._compareWith(s.value, option.value));
this.selectionModel = this.selectionModel.filter((s) => !this._compareWith(s.value, option.value));
option.deselect();
} else {
this.selectionModel.push(option);
Expand Down Expand Up @@ -1066,23 +1078,19 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent

protected subscribeOnTriggers() {
this.triggerStrategy.show$.subscribe(() => this.show());
this.triggerStrategy.hide$
.pipe(filter(() => this.isOpen))
.subscribe(($event: Event) => {
this.hide();
if (!this.isClickedWithinComponent($event)) {
this.onTouched();
}
});
this.triggerStrategy.hide$.pipe(filter(() => this.isOpen)).subscribe(($event: Event) => {
this.hide();
if (!this.isClickedWithinComponent($event)) {
this.onTouched();
}
});
}

protected subscribeOnPositionChange() {
this.positionStrategy.positionChange
.pipe(takeUntil(this.destroy$))
.subscribe((position: NbPosition) => {
this.overlayPosition = position;
this.cd.detectChanges();
});
this.positionStrategy.positionChange.pipe(takeUntil(this.destroy$)).subscribe((position: NbPosition) => {
this.overlayPosition = position;
this.cd.detectChanges();
});
}

protected subscribeOnOptionClick() {
Expand All @@ -1095,15 +1103,16 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
.pipe(
startWith(this.options),
switchMap((options: QueryList<NbOptionComponent>) => {
return merge(...options.map(option => option.click));
return merge(...options.map((option) => option.click));
}),
takeUntil(this.destroy$),
)
.subscribe((clickedOption: NbOptionComponent) => this.handleOptionClick(clickedOption));
}

protected subscribeOnOverlayKeys(): void {
this.ref.keydownEvents()
this.ref
.keydownEvents()
.pipe(
filter(() => this.isOpen),
takeUntil(this.destroy$),
Expand All @@ -1117,30 +1126,33 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
}
});

this.keyManager.tabOut
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.hide();
this.onTouched();
});
this.keyManager.tabOut.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.hide();
this.onTouched();
});
}

protected subscribeOnButtonFocus() {
this.focusMonitor.monitor(this.button)
this.focusMonitor
.monitor(this.button)
.pipe(
map(origin => !!origin),
map((origin) => !!origin),
finalize(() => this.focusMonitor.stopMonitoring(this.button)),
takeUntil(this.destroy$),
)
.subscribe(this.focused$);
}

protected getContainer() {
return this.ref && this.ref.hasAttached() && <ComponentRef<any>> {
location: {
nativeElement: this.ref.overlayElement,
},
};
return (
this.ref &&
this.ref.hasAttached() &&
<ComponentRef<any>>{
location: {
nativeElement: this.ref.overlayElement,
},
}
);
}

/**
Expand All @@ -1165,17 +1177,17 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
const isArray: boolean = Array.isArray(safeValue);

if (this.multiple && !isArray && !isResetValue) {
throw new Error('Can\'t assign single value if select is marked as multiple');
throw new Error("Can't assign single value if select is marked as multiple");
}
if (!this.multiple && isArray) {
throw new Error('Can\'t assign array if select is not marked as multiple');
throw new Error("Can't assign array if select is not marked as multiple");
}

const previouslySelectedOptions = this.selectionModel;
this.selectionModel = [];

if (this.multiple) {
safeValue.forEach(option => this.selectValue(option));
safeValue.forEach((option) => this.selectValue(option));
} else {
this.selectValue(safeValue);
}
Expand Down

0 comments on commit e30e00c

Please sign in to comment.