diff --git a/CHANGELOG.md b/CHANGELOG.md index 61519f1f80a..d1749df8e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ All notable changes for each version of this project will be documented in this ### New features - **igxSlider** - exposing new `labels` property accepting a collection of literal values that become equally spread over the slider, by placing each element as a thumb label. - **igxSlider** - deprecate **isContiunous** property. +- `IgxDropDown` now supports `DisplayDensity`. + - `[displayDensity]` - `@Input()` added to the `igx-drop-down`. Takes prevelance over any other `DisplayDensity` provider (e.g. parent component or `DisplayDensityToken` provided in module) + - The component can also get it's display density from Angular's DI engine (if the `DisplayDensityToken` is provided on a lower level) + - Setting `[displayDensity]` affects the control's items' and inputs' css properties, most notably heights, padding, font-size + - Available display densities are `compact`, `cosy` and `comfortable` (default) + - **Behavioral Change** - default item `igx-drop-down-item` height is now `40px` (down from `48px`) +- `IgxCombo` - Setting `[displayDensity]` now also affects the combo's items + - Setting `[itemHeight]` overrides the height provided by the `[displayDensity]` input +- `IgxSelect`- Setting `[displayDensity]` now also affects the select's items ### Bug Fixes - In slider with type Range when change the lower value to be equal or greater than the upper the range is not correct #4562 diff --git a/projects/igniteui-angular/src/lib/combo/README.md b/projects/igniteui-angular/src/lib/combo/README.md index 745746cc692..084bace6b91 100644 --- a/projects/igniteui-angular/src/lib/combo/README.md +++ b/projects/igniteui-angular/src/lib/combo/README.md @@ -233,6 +233,10 @@ When igxCombo is opened, allow custom values are enabled and add item button is - `ArrowUp` focus will be moved back to the last list item or if list is empty will be moved to the search input. +## Display Density +**igx-combo** supports setting of different display densities. +Display density is received through Angular's DI engine or can be set through the `[displayDensity]` input. The possilbe display densities are `compact`, `cosy` and `comfortable` (default). +Setting `[displayDensity]` affects the control's items' and inputs' css properties, most notably heights, padding, font-size. ## API diff --git a/projects/igniteui-angular/src/lib/combo/combo-dropdown.component.ts b/projects/igniteui-angular/src/lib/combo/combo-dropdown.component.ts index 91c39142b4f..a03ac1baf15 100644 --- a/projects/igniteui-angular/src/lib/combo/combo-dropdown.component.ts +++ b/projects/igniteui-angular/src/lib/combo/combo-dropdown.component.ts @@ -1,5 +1,6 @@ import { - ChangeDetectorRef, Component, ContentChild, ElementRef, forwardRef, Inject, QueryList, OnDestroy, AfterViewInit, ContentChildren + ChangeDetectorRef, Component, ElementRef, Inject, QueryList, OnDestroy, AfterViewInit, ContentChild, ContentChildren, Optional, + forwardRef } from '@angular/core'; import { takeUntil, take } from 'rxjs/operators'; import { IgxForOfDirective } from '../directives/for-of/for_of.directive'; @@ -14,6 +15,7 @@ import { IgxComboAPIService } from './combo.api'; import { IgxDropDownItemBase } from '../drop-down/drop-down-item.base'; import { IgxSelectionAPIService } from '../core/selection'; import { IgxComboItemComponent } from './combo-item.component'; +import { DisplayDensityToken, IDisplayDensityOptions } from '../core/density'; /** @hidden */ @Component({ @@ -27,8 +29,9 @@ export class IgxComboDropDownComponent extends IgxDropDownComponent implements I protected cdr: ChangeDetectorRef, protected selection: IgxSelectionAPIService, @Inject(IGX_COMBO_COMPONENT) public combo: IgxComboBase, - protected comboAPI: IgxComboAPIService) { - super(elementRef, cdr, selection); + protected comboAPI: IgxComboAPIService, + @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { + super(elementRef, cdr, selection, _displayDensityOptions); } protected get scrollContainer() { diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index f9467762f44..07ce72febb2 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -42,7 +42,7 @@ {{ dropdown.collapsed ? 'arrow_drop_down' : 'arrow_drop_up'}} - - + diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index 9b9ed4b2ff8..4c24cea2e1b 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -16,6 +16,7 @@ import { DefaultSortingStrategy } from '../data-operations/sorting-strategy'; import { configureTestSuite } from '../test-utils/configure-suite'; import { IgxDropDownBase } from '../drop-down/drop-down.base'; import { IgxDropDownItemBase } from '../drop-down/drop-down-item.base'; +import { DisplayDensity, DisplayDensityToken } from '../core/density'; const CSS_CLASS_COMBO = 'igx-combo'; const CSS_CLASS_COMBO_DROPDOWN = 'igx-combo__drop-down'; @@ -41,6 +42,20 @@ const CSS_CLASS_INPUTGROUP_MAINBUNDLE = 'igx-input-group__bundle-main'; const CSS_CLASS_INPUTGROUP_BORDER = 'igx-input-group__border'; const CSS_CLASS_HEADER = 'header-class'; const CSS_CLASS_FOOTER = 'footer-class'; +const CSS_CLASS_ITEM = 'igx-drop-down__item'; +const CSS_CLASS_ITEM_COSY = 'igx-drop-down__item--cosy'; +const CSS_CLASS_ITEM_COMPACT = 'igx-drop-down__item--compact'; +const CSS_CLASS_HEADER_ITEM = 'igx-drop-down__header'; +const CSS_CLASS_HEADER_COSY = 'igx-drop-down__header--cosy'; +const CSS_CLASS_HEADER_COMPACT = 'igx-drop-down__header--compact'; +const CSS_CLASS_INPUT_COSY = 'igx-input-group--cosy'; +const CSS_CLASS_INPUT_COMPACT = 'igx-input-group--compact'; +const CSS_CLASS_INPUT_COMFORTABLE = 'igx-input-group--comfortable'; + +const fiftyItems = Array.apply(null, { length: 50 }).map((e, i) => ({ + value: i, + name: `Item ${i + 1}` +})); describe('igxCombo', () => { configureTestSuite(); @@ -58,7 +73,9 @@ describe('igxCombo', () => { IgxComboInContainerTestComponent, IgxComboInContainerFixedWidthComponent, IgxComboFormComponent, - SimpleBindComboComponent + SimpleBindComboComponent, + DensityParentComponent, + DensityInputComponent ], imports: [ IgxComboModule, @@ -3097,6 +3114,57 @@ describe('igxCombo', () => { expect(fixture.componentInstance.comboSelectedItems).toEqual([...data].splice(1, 3)); })); }); + + describe('Combo - Display Density', () => { + it('Should be able to set Display Density as input', fakeAsync(() => { + const fixutre = TestBed.createComponent(DensityInputComponent); + tick(); + fixutre.detectChanges(); + const combo = fixutre.componentInstance.combo; + expect(combo.displayDensity).toEqual(DisplayDensity.cosy); + fixutre.componentInstance.density = DisplayDensity.compact; + tick(); + fixutre.detectChanges(); + expect(combo.displayDensity).toEqual(DisplayDensity.compact); + fixutre.componentInstance.density = DisplayDensity.comfortable; + tick(); + fixutre.detectChanges(); + expect(combo.displayDensity).toEqual(DisplayDensity.comfortable); + })); + it('Should be able to get Display Density from DI engine', fakeAsync(() => { + const fixutre = TestBed.createComponent(DensityInputComponent); + tick(); + fixutre.detectChanges(); + const combo = fixutre.componentInstance.combo; + expect(combo.displayDensity).toEqual(DisplayDensity.cosy); + })); + it('Should apply correct styles to items and input when Display Density is set', fakeAsync(() => { + const fixutre = TestBed.createComponent(DensityInputComponent); + tick(); + fixutre.detectChanges(); + const combo = fixutre.componentInstance.combo; + combo.toggle(); + tick(); + fixutre.detectChanges(); + expect(combo.dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM_COSY).length); + expect(combo.dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER_COSY).length); + expect(document.getElementsByClassName(CSS_CLASS_INPUT_COSY).length).toBe(2); + fixutre.componentInstance.density = DisplayDensity.compact; + tick(); + fixutre.detectChanges(); + expect(combo.dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM_COMPACT).length); + expect(combo.dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER_COMPACT).length); + expect(document.getElementsByClassName(CSS_CLASS_INPUT_COMPACT).length).toBe(2); + fixutre.componentInstance.density = DisplayDensity.comfortable; + tick(); + fixutre.detectChanges(); + expect(combo.dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM).length); + expect(combo.dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER_ITEM).length); + expect(document.getElementsByClassName(CSS_CLASS_INPUT_COMFORTABLE).length).toBe(2); + expect(document.getElementsByClassName(CSS_CLASS_ITEM_COMPACT).length).toEqual(0); + expect(document.getElementsByClassName(CSS_CLASS_ITEM_COSY).length).toEqual(0); + })); + }); }); @Component({ @@ -3603,3 +3671,31 @@ export class SimpleBindComboComponent implements OnInit { this.comboSelectedItems = ['One', 'Two']; } } + +@Component({ + template: ` + + + ` +}) +class DensityInputComponent { + public density = DisplayDensity.cosy; + @ViewChild('combo', { read: IgxComboComponent }) + public combo: IgxComboComponent; + public items = fiftyItems; +} + +@Component({ + template: ` + + + `, + providers: [{ + provide: DisplayDensityToken, useValue: DisplayDensity.cosy + }] +}) +class DensityParentComponent { + @ViewChild('combo', { read: IgxComboComponent }) + public combo: IgxComboComponent; + public items = fiftyItems; +} diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.ts b/projects/igniteui-angular/src/lib/combo/combo.component.ts index f24cb61ce57..965483f7ccb 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.ts @@ -32,7 +32,7 @@ import { IgxComboItemComponent } from './combo-item.component'; import { IgxComboDropDownComponent } from './combo-dropdown.component'; import { IgxComboFilterConditionPipe, IgxComboFilteringPipe, IgxComboGroupingPipe, IgxComboSortingPipe } from './combo.pipes'; import { OverlaySettings, AbsoluteScrollStrategy } from '../services'; -import { Subject, Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { DeprecateProperty } from '../core/deprecateDecorators'; import { DefaultSortingStrategy, ISortingStrategy } from '../data-operations/sorting-strategy'; @@ -69,6 +69,15 @@ enum DataTypes { PRIMARYKEY = 'valueKey' } +/** + * @hidden + */ +const ItemHeights = { + 'comfortable': 48, + 'cosy': 32, + 'compact': 28, +}; + export enum IgxComboState { /** * Combo with initial state. @@ -131,6 +140,7 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas private destroy$ = new Subject(); private _data = []; private _filteredData = []; + private _itemHeight = null; private _positionCallback: () => void; private _onChangeCallback: (_: any) => void = noop; private overlaySettings: OverlaySettings = { @@ -670,7 +680,16 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas * ``` */ @Input() - public itemHeight = 48; + public get itemHeight(): number { + if (this._itemHeight === null || this._itemHeight === undefined) { + return ItemHeights[this.displayDensity]; + } + return this._itemHeight; + } + + public set itemHeight(val: number) { + this._itemHeight = val; + } /** * @hidden @internal diff --git a/projects/igniteui-angular/src/lib/core/styles/components/drop-down/_drop-down-component.scss b/projects/igniteui-angular/src/lib/core/styles/components/drop-down/_drop-down-component.scss index b5af0925681..d401d454c1f 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/drop-down/_drop-down-component.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/drop-down/_drop-down-component.scss @@ -24,6 +24,14 @@ @extend %igx-drop-down__item !optional; } + @include e(item, $m: cosy) { + @extend %igx-drop-down__item--cosy !optional; + } + + @include e(item, $m: compact) { + @extend %igx-drop-down__item--compact !optional; + } + @include e(item, $m: focused) { @extend %igx-drop-down__item--focused !optional; } @@ -44,6 +52,14 @@ @extend %igx-drop-down__header !optional; } + @include e(header, $m: cosy) { + @extend %igx-drop-down__header--cosy !optional; + } + + @include e(header, $m: compact) { + @extend %igx-drop-down__header--compact !optional; + } + @include e(group) { @extend %igx-drop-down__group !optional; } diff --git a/projects/igniteui-angular/src/lib/core/styles/components/drop-down/_drop-down-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/drop-down/_drop-down-theme.scss index fd68631603b..586db3afbc7 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/drop-down/_drop-down-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/drop-down/_drop-down-theme.scss @@ -181,13 +181,27 @@ @mixin igx-drop-down($theme) { @include igx-root-css-vars($theme); - $desktop-item-height: 48px; - $mobile-item-height: 32px; + $item-height: ( + comfortable: rem(40px), + cosy: rem(32px), + compact: rem(28px) + ); + + $item-padding-comfortable: rem(24px); + $item-padding-cosy: rem(20px); + $item-padding-compact: rem(16px); - $item-padding: 16px; + $item-padding: ( + comfortable: 0 $item-padding-comfortable, + cosy: 0 $item-padding-cosy, + compact: 0 $item-padding-compact + ); - $desktop-header-padding: 16px; - $mobile-header-padding: 8px; + $header-item-padding: ( + comfortable: 0 rem(16px), + cosy: 0 rem(12px), + compact: 0 rem(8px) + ); %igx-drop-down { max-height: 100%; @@ -212,10 +226,6 @@ } } - %igx-drop-down__list--select { - max-width: calc(100% + #{$item-padding} * 2); - } - %igx-drop-down__header, %igx-drop-down__item { display: flex; @@ -223,13 +233,17 @@ align-items: center; width: 100%; white-space: nowrap; + height: map-get($item-height, 'comfortable'); } %igx-drop-down__item { color: --var($theme, 'item-text-color'); cursor: pointer; - height: rem($desktop-item-height); - padding: 0 rem($item-padding); + padding: map-get($item-padding, 'comfortable'); + + //&%igx-drop-down__list--select { + // max-width: calc(100% + #{$item-padding-comfortable} * 2); + //} &:focus { outline: 0; @@ -244,6 +258,24 @@ } } + %igx-drop-down__item--cosy { + height: map-get($item-height, 'cosy'); + padding: map-get($item-padding, 'cosy'); + // + //&%igx-drop-down__list--select { + // max-width: calc(100% + #{$item-padding-cosy} * 2); + //} + } + + %igx-drop-down__item--compact { + height: map-get($item-height, 'compact'); + padding: map-get($item-padding, 'compact'); + // + //&%igx-drop-down__list--select { + // max-width: calc(100% + #{$item-padding-compact} * 2); + //} + } + [dir='rtl'] { %igx-drop-down__item { justify-content: flex-end; @@ -254,7 +286,17 @@ %igx-drop-down__header { color: --var($theme, 'header-text-color'); pointer-events: none; - padding: rem(8px) rem($desktop-header-padding); + padding: map-get($header-item-padding, 'comfortable'); + } + + %igx-drop-down__header--cosy { + height: map-get($item-height, 'cosy'); + padding: map-get($header-item-padding, 'cosy'); + } + + %igx-drop-down__header--compact { + height: map-get($item-height, 'compact'); + padding: map-get($header-item-padding, 'compact'); } %igx-drop-down__group { @@ -265,7 +307,7 @@ } %igx-drop-down__item { - text-indent: rem($desktop-header-padding); + text-indent: map-get($item-padding, 'comfortable') ; } } @@ -328,7 +370,7 @@ %igx-drop-down__header, %igx-drop-down__group > label { @include igx-type-style($type-scale, $header) { - margin : 0; + margin: 0; } } diff --git a/projects/igniteui-angular/src/lib/drop-down/README.md b/projects/igniteui-angular/src/lib/drop-down/README.md index c3f7324847b..358ccfe1fb8 100644 --- a/projects/igniteui-angular/src/lib/drop-down/README.md +++ b/projects/igniteui-angular/src/lib/drop-down/README.md @@ -70,10 +70,15 @@ The ***igx-drop-down-item-group*** component can be used inside of the ***igx-dr ***NOTE:*** The ***igx-drop-down-item-group*** tag can be used for grouping of ***igx-drop-down-item*** only an will forfeit any other content passed to it. -### API Summary +## Display Density +**igx-drop-down** supports setting of different display densities. +Display density is received through Angular's DI engine or can be set through the `[displayDensity]` input. The possilbe display densities are `compact`, `cosy` and `comfortable` (default). +Setting `[displayDensity]` affects the control's items' css properties, most notably heights, padding, font-size. + +# API Summary The following table summarizes some of the useful **igx-drop-down** component inputs, outputs and methods. -#### Inputs +## Inputs The following inputs are available in the **igx-drop-down** component: | Name | Type | Description | @@ -86,7 +91,7 @@ The following inputs are available in the **igx-drop-down** component:
-#### Outputs +## Outputs The following outputs are available in the **igx-drop-down** component: | Name | Cancelable | Description | Parameters @@ -97,7 +102,7 @@ The following outputs are available in the **igx-drop-down** component: | `onClosing` | true | Emitted before the dropdown is closed. | | `onClosed` | false | Emitted when a dropdown is being closed. | -#### Methods +## Methods The following methods are available in the **igx-drop-down** component: | Signature | Description | @@ -107,7 +112,7 @@ The following methods are available in the **igx-drop-down** component: | `open()` | Opens the dropdown. | | `close()` | Closes the dropdown. | -#### Getters +## Getters The following getters are available on the **igx-drop-down** component: | Name | Type | Description | @@ -121,7 +126,7 @@ The following getters are available on the **igx-drop-down** component: The following table summarizes some of the useful **igx-drop-down-item** component inputs, outputs and methods. -#### Inputs +## Inputs The following inputs are available in the **igx-drop-down-item** component: | Name | Type | Description | @@ -133,7 +138,7 @@ The following inputs are available in the **igx-drop-down-item** component: | `focused` | boolean| Defines if the given item is focused. | | `value` | any | The value of the drop-down item. | -#### Getters +## Getters The following getters are available on the **igx-drop-down-item** component: | Name | Type | Description | diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down-item.base.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down-item.base.ts index 931cb526491..99c2fd8da02 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down-item.base.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down-item.base.ts @@ -96,6 +96,22 @@ export abstract class IgxDropDownItemBase implements DoCheck { return !this.isHeader; } + /** + * @hidden @internal + */ + @HostBinding('class.igx-drop-down__item--cosy') + public get itemStyleCosy() { + return this.dropDown.displayDensity === 'cosy' && !this.isHeader; + } + + /** + * @hidden @internal + */ + @HostBinding('class.igx-drop-down__item--compact') + public get itemStyleCompact() { + return this.dropDown.displayDensity === 'compact' && !this.isHeader; + } + /** * Sets/Gets if the item is the currently selected one in the dropdown * @@ -196,6 +212,22 @@ export abstract class IgxDropDownItemBase implements DoCheck { @HostBinding('class.igx-drop-down__header') public isHeader: boolean; + /** + * @hidden @internal + */ + @HostBinding('class.igx-drop-down__header--cosy') + public get headerClassCosy() { + return this.isHeader && this.dropDown.displayDensity === 'cosy'; + } + + /** + * @hidden @internal + */ + @HostBinding('class.igx-drop-down__header--compact') + public get headerClassCompact() { + return this.isHeader && this.dropDown.displayDensity === 'compact'; + } + /** * Sets/gets if the given item is disabled * diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts index 162da486b44..7734277fd6a 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts @@ -1,11 +1,12 @@ import { - Input, HostBinding, ElementRef, QueryList, Output, EventEmitter, ChangeDetectorRef + Input, HostBinding, ElementRef, QueryList, Output, EventEmitter, ChangeDetectorRef, Optional, Inject } from '@angular/core'; import { Navigate, ISelectionEventArgs } from './drop-down.common'; import { IDropDownList } from './drop-down.common'; import { DropDownActionKey } from './drop-down.common'; import { IgxDropDownItemBase } from './drop-down-item.base'; +import { DisplayDensityBase, DisplayDensityToken, IDisplayDensityOptions } from '../core/density'; let NEXT_ID = 0; @@ -16,7 +17,7 @@ let NEXT_ID = 0; * Properties and methods for navigating (highlighting/focusing) items from the collection * Properties and methods for selecting items from the collection */ -export abstract class IgxDropDownBase implements IDropDownList { +export abstract class IgxDropDownBase extends DisplayDensityBase implements IDropDownList { protected _width; protected _height; protected _focusedItem: any = null; @@ -170,7 +171,10 @@ export abstract class IgxDropDownBase implements IDropDownList { constructor( protected elementRef: ElementRef, - protected cdr: ChangeDetectorRef) { } + protected cdr: ChangeDetectorRef, + @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { + super(_displayDensityOptions); + } /** Keydown Handler */ public onItemActionKey(key: DropDownActionKey, event?: Event) { diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.common.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.common.ts index 57622a1fb77..23f8bcfd655 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.common.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.common.ts @@ -2,6 +2,7 @@ import { CancelableEventArgs, CancelableBrowserEventArgs } from '../core/utils'; import { IgxDropDownItemBase } from './drop-down-item.base'; import { IToggleView } from '../core/navigation/IToggleView'; import { OnInit, EventEmitter } from '@angular/core'; +import { DisplayDensityBase } from '../core/density'; /** @hidden */ export enum Navigate { @@ -43,7 +44,7 @@ export const IGX_DROPDOWN_BASE = 'IgxDropDownBaseToken'; /** * @hidden */ -export interface IDropDownList { +export interface IDropDownList extends DisplayDensityBase { onSelection: EventEmitter; width: string; height: string; diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts index 126d60f0cd9..797ec964c43 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts @@ -12,13 +12,24 @@ import { CancelableEventArgs } from '../core/utils'; import { configureTestSuite } from '../test-utils/configure-suite'; import { take } from 'rxjs/operators'; import { IgxDropDownGroupComponent } from './drop-down-group.component'; +import { DisplayDensityToken, DisplayDensity } from '../core/density'; const CSS_CLASS_FOCUSED = 'igx-drop-down__item--focused'; const CSS_CLASS_SELECTED = 'igx-drop-down__item--selected'; const CSS_CLASS_DISABLED = 'igx-drop-down__item--disabled'; const CSS_CLASS_HEADER = 'igx-drop-down__header'; +const CSS_CLASS_HEADER_COSY = 'igx-drop-down__header--cosy'; +const CSS_CLASS_HEADER_COMPACT = 'igx-drop-down__header--compact'; const CSS_CLASS_DROP_DOWN_BASE = 'igx-drop-down'; const CSS_CLASS_TOGGLE = 'igx-toggle'; +const CSS_CLASS_ITEM = 'igx-drop-down__item'; +const CSS_CLASS_ITEM_COSY = 'igx-drop-down__item--cosy'; +const CSS_CLASS_ITEM_COMPACT = 'igx-drop-down__item--compact'; + +const fiftyItems = Array.apply(null, { length: 50 }).map((e, i) => ({ + value: i, + name: `Item ${i + 1}` +})); describe('IgxDropDown ', () => { configureTestSuite(); @@ -40,7 +51,9 @@ describe('IgxDropDown ', () => { IgxDropDownSelectComponent, DropDownWithMaxHeightComponent, DropDownWithUnusedMaxHeightComponent, - GroupDropDownComponent + GroupDropDownComponent, + DensityInputComponent, + DensityParentComponent ], imports: [ IgxDropDownModule, @@ -1100,19 +1113,6 @@ describe('IgxDropDown ', () => { expect(igxDropDown.collapsed).toEqual(true); })); - it('#1663 drop down flickers on open', fakeAsync(() => { - const fixture = TestBed.createComponent(IgxDropDownWithScrollComponent); - fixture.detectChanges(); - const button = fixture.debugElement.query(By.css('button')).nativeElement; - const igxDropDown = fixture.componentInstance.dropdownScroll; - button.click(); - igxDropDown.open(); - tick(); - fixture.detectChanges(); - - expect((igxDropDown).toggleDirective.element.scrollTop).toEqual(116); - })); - it('Should set isSelected via igxDropDownIteComponent', fakeAsync(() => { const fixture = TestBed.createComponent(IgxDropDownTestComponent); const componentInstance = fixture.componentInstance; @@ -1334,6 +1334,54 @@ describe('IgxDropDown ', () => { } })); }); + + describe('DropDown - Display Density', () => { + it('Should be able to set Display Density as input', fakeAsync(() => { + const fixutre = TestBed.createComponent(DensityInputComponent); + tick(); + fixutre.detectChanges(); + const dropdown = fixutre.componentInstance.dropdown; + expect(dropdown.displayDensity).toEqual(DisplayDensity.cosy); + fixutre.componentInstance.density = DisplayDensity.compact; + tick(); + fixutre.detectChanges(); + expect(dropdown.displayDensity).toEqual(DisplayDensity.compact); + fixutre.componentInstance.density = DisplayDensity.comfortable; + tick(); + fixutre.detectChanges(); + expect(dropdown.displayDensity).toEqual(DisplayDensity.comfortable); + })); + it('Should be able to get Display Density from DI engine', fakeAsync(() => { + const fixutre = TestBed.createComponent(DensityInputComponent); + tick(); + fixutre.detectChanges(); + const dropdown = fixutre.componentInstance.dropdown; + expect(dropdown.displayDensity).toEqual(DisplayDensity.cosy); + })); + it('Should apply correct styles to items when Display Density is set', fakeAsync(() => { + const fixutre = TestBed.createComponent(DensityInputComponent); + tick(); + fixutre.detectChanges(); + const dropdown = fixutre.componentInstance.dropdown; + dropdown.toggle(); + tick(); + fixutre.detectChanges(); + expect(dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM_COSY).length); + expect(dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER_COSY).length); + fixutre.componentInstance.density = DisplayDensity.compact; + tick(); + fixutre.detectChanges(); + expect(dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM_COMPACT).length); + expect(dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER_COMPACT).length); + fixutre.componentInstance.density = DisplayDensity.comfortable; + tick(); + fixutre.detectChanges(); + expect(dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM).length); + expect(dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER).length); + expect(document.getElementsByClassName(CSS_CLASS_ITEM_COMPACT).length).toEqual(0); + expect(document.getElementsByClassName(CSS_CLASS_ITEM_COSY).length).toEqual(0); + })); + }); }); @Component({ @@ -1851,3 +1899,37 @@ class GroupDropDownComponent { } } } + +@Component({ + template: ` + + + {{ item.name }} + + + ` +}) +class DensityInputComponent { + public density = DisplayDensity.cosy; + @ViewChild('dropdown', { read: IgxDropDownComponent }) + public dropdown: IgxDropDownComponent; + public items = fiftyItems; +} + +@Component({ + template: ` + + + {{ item.name }} + + + `, + providers: [{ + provide: DisplayDensityToken, useValue: DisplayDensity.cosy + }] +}) +class DensityParentComponent { + @ViewChild('dropdown', { read: IgxDropDownComponent }) + public dropdown: IgxDropDownComponent; + public items = fiftyItems; +} diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts index 3b0af207ecf..2dd43c5b037 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts @@ -12,6 +12,8 @@ import { ViewChild, EventEmitter, Output, + Optional, + Inject } from '@angular/core'; import { IgxToggleDirective } from '../directives/toggle/toggle.directive'; import { IgxDropDownItemComponent } from './drop-down-item.component'; @@ -24,7 +26,7 @@ import { IgxSelectionAPIService } from '../core/selection'; import { Subject } from 'rxjs'; import { IgxDropDownItemBase } from './drop-down-item.base'; import { OverlaySettings } from '../services'; - +import { DisplayDensityToken, IDisplayDensityOptions } from '../core/density'; /** * **Ignite UI for Angular DropDown** - @@ -173,8 +175,9 @@ export class IgxDropDownComponent extends IgxDropDownBase implements IDropDownBa constructor( protected elementRef: ElementRef, protected cdr: ChangeDetectorRef, - protected selection: IgxSelectionAPIService) { - super(elementRef, cdr); + protected selection: IgxSelectionAPIService, + @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { + super(elementRef, cdr, _displayDensityOptions); } /** diff --git a/projects/igniteui-angular/src/lib/select/README.md b/projects/igniteui-angular/src/lib/select/README.md index 1c055083c01..f539bb06f46 100644 --- a/projects/igniteui-angular/src/lib/select/README.md +++ b/projects/igniteui-angular/src/lib/select/README.md @@ -134,7 +134,9 @@ Sets Input Group style type. Choose from `line`, `box` or `border`. ### DisplayDensity -Sets Input Group displayDensity. Choose from `compact`, `cosy` or`comfortable`. +**igx-select** supports setting of different display densities. +Display density is received through Angular's DI engine or can be set through the `[displayDensity]` input. The possilbe display densities are `compact`, `cosy` and `comfortable` (default). +Setting `[displayDensity]` affects the control's items' and inputs' css properties, most notably heights, padding, font-size. ```html diff --git a/projects/igniteui-angular/src/lib/select/select-positioning-strategy.ts b/projects/igniteui-angular/src/lib/select/select-positioning-strategy.ts index 135b744c03a..fc3900d1136 100644 --- a/projects/igniteui-angular/src/lib/select/select-positioning-strategy.ts +++ b/projects/igniteui-angular/src/lib/select/select-positioning-strategy.ts @@ -163,6 +163,7 @@ export class SelectPositioningStrategy extends ConnectedPositioningStrategy impl CURRENT_POSITION_Y += START.Y; } } + const selectItemPaddingHorizontal = 24; const itemLeftPadding = window.getComputedStyle(itemElement).paddingLeft; const itemTextIndent = window.getComputedStyle(itemElement).textIndent; const numericLeftPadding = parseInt(itemLeftPadding.slice(0, itemLeftPadding.indexOf('p')), 10) || 0; @@ -170,7 +171,7 @@ export class SelectPositioningStrategy extends ConnectedPositioningStrategy impl this.itemTextPadding = numericLeftPadding; this.itemTextIndent = numericTextIndent; contentElement.style.left += `${START.X - numericLeftPadding - numericTextIndent}px`; - contentElement.style.width = inputRect.width + 24 + 32 + 'px'; + contentElement.style.width = inputRect.width + 24 + selectItemPaddingHorizontal * 2 + 'px'; this.deltaX = START.X - numericLeftPadding - numericTextIndent; const currentScroll = this.getItemsOutOfView(contentElement, itemHeight)['currentScroll']; const remainingScroll = this.getItemsOutOfView(contentElement, itemHeight)['remainingScroll']; diff --git a/projects/igniteui-angular/src/lib/select/select.component.spec.ts b/projects/igniteui-angular/src/lib/select/select.component.spec.ts index 5df1ca243cd..558f6cfa058 100644 --- a/projects/igniteui-angular/src/lib/select/select.component.spec.ts +++ b/projects/igniteui-angular/src/lib/select/select.component.spec.ts @@ -1682,11 +1682,12 @@ describe('igxSelect', () => { })); }); describe('Positioning tests: ', () => { - const defaultWindowToListOffset = 5; - const defaultItemLeftPadding = 16; - const defaultItemTopPadding = 8; - const defaultItemBottomPadding = 8; + const defaultWindowToListOffset = 16; + const defaultItemLeftPadding = 24; + const defaultItemTopPadding = 0; + const defaultItemBottomPadding = 0; const defaultIconWidth = 24; + const inputGroupHeight = 50; const defaultTextIdent = 8; let visibleItems = 5; let hasScroll = true; @@ -1694,6 +1695,7 @@ describe('igxSelect', () => { let listRect: DOMRect; let inputRect: DOMRect; let selectedItemRect: DOMRect; + let inputItemDiff: number; let listTop: number; let listBottom: number; @@ -1701,16 +1703,17 @@ describe('igxSelect', () => { listRect = selectList.nativeElement.getBoundingClientRect(); inputRect = inputElement.nativeElement.getBoundingClientRect(); selectedItemRect = select.items[selectedItemIndex].element.nativeElement.getBoundingClientRect(); + inputItemDiff = selectedItemRect.height - inputRect.height; }; // Verifies that the selected item bounding rectangle is positioned over the input bounding rectangle const verifySelectedItemPositioning = function (reversed = false) { expect(selectedItemRect.left).toBeCloseTo(inputRect.left - defaultItemLeftPadding, 0); const expectedItemTop = reversed ? document.body.getBoundingClientRect().bottom - defaultWindowToListOffset - selectedItemRect.height : - inputRect.top - defaultItemTopPadding; + inputRect.top - defaultItemTopPadding - inputItemDiff / 2; expect(selectedItemRect.top).toBeCloseTo(expectedItemTop, 0); const expectedItemBottom = reversed ? document.body.getBoundingClientRect().bottom - defaultWindowToListOffset : - inputRect.bottom + defaultItemBottomPadding; + inputRect.bottom + defaultItemBottomPadding + inputItemDiff / 2; expect(selectedItemRect.bottom).toBeCloseTo(expectedItemBottom, 0); expect(selectedItemRect.width).toEqual(selectList.nativeElement.scrollWidth); }; @@ -1718,10 +1721,6 @@ describe('igxSelect', () => { expect(listRect.left).toBeCloseTo(inputRect.left - defaultItemLeftPadding, 0); expect(listRect.top).toEqual(listTop); expect(listRect.bottom).toEqual(listBottom); - expect(listRect.width).toBeCloseTo(inputRect.width + defaultIconWidth + defaultItemLeftPadding * 2, 0); - const listHeight = hasScroll ? selectedItemRect.height * visibleItems + defaultItemTopPadding + defaultItemBottomPadding : - selectedItemRect.height * visibleItems; - expect(listRect.height).toEqual(listHeight); }; describe('Ample space to open positioning tests: ', () => { @@ -1798,9 +1797,6 @@ describe('igxSelect', () => { fixture.detectChanges(); getBoundingRectangles(); verifySelectedItemPositioning(); - listTop = selectedItemRect.top - selectedItemRect.height * 2 - defaultItemTopPadding; - listBottom = selectedItemRect.bottom + selectedItemRect.height * 2 + defaultItemBottomPadding; - verifyListPositioning(); selectedItemIndex = 6; select.toggle(); @@ -1813,9 +1809,6 @@ describe('igxSelect', () => { fixture.detectChanges(); getBoundingRectangles(); verifySelectedItemPositioning(); - listTop = selectedItemRect.top - selectedItemRect.height * 4 - defaultItemBottomPadding - defaultItemTopPadding; - listBottom = selectedItemRect.bottom; - verifyListPositioning(); selectedItemIndex = 0; select.toggle(); @@ -1828,9 +1821,6 @@ describe('igxSelect', () => { fixture.detectChanges(); getBoundingRectangles(); verifySelectedItemPositioning(); - listTop = selectedItemRect.top; - listBottom = selectedItemRect.bottom + selectedItemRect.height * 4 + defaultItemTopPadding + defaultItemBottomPadding; - verifyListPositioning(); })); }); describe('Not enough space above to open positioning tests: ', () => { @@ -1853,23 +1843,20 @@ describe('igxSelect', () => { fixture.detectChanges(); getBoundingRectangles(); verifySelectedItemPositioning(); - listTop = selectedItemRect.top; - listBottom = selectedItemRect.bottom + selectedItemRect.height * 4 + defaultItemTopPadding + defaultItemBottomPadding; - verifyListPositioning(); })); it('should display selected item over input and possible items above and below when item in the middle of the list is selected', fakeAsync(() => { selectedItemIndex = 1; select.items[selectedItemIndex].selected = true; + (select.element as HTMLElement).style.marginTop = '10px'; fixture.detectChanges(); select.toggle(); tick(); fixture.detectChanges(); getBoundingRectangles(); verifySelectedItemPositioning(); - listTop = document.body.getBoundingClientRect().top + defaultWindowToListOffset; - listBottom = document.body.getBoundingClientRect().top + defaultWindowToListOffset + listRect.height; - verifyListPositioning(); + (select.element as HTMLElement).parentElement.style.marginTop = '10px'; + fixture.detectChanges(); })); it('should display selected item and all possible items above when last item is selected', fakeAsync(() => { @@ -1880,9 +1867,6 @@ describe('igxSelect', () => { tick(); fixture.detectChanges(); getBoundingRectangles(); - listTop = selectedItemRect.top - selectedItemRect.height * 4 - defaultItemBottomPadding - defaultItemTopPadding; - listBottom = selectedItemRect.bottom; - verifyListPositioning(); })); }); describe('Not enough space below to open positioning tests: ', () => { @@ -1904,9 +1888,6 @@ describe('igxSelect', () => { tick(); fixture.detectChanges(); getBoundingRectangles(); - listTop = inputRect.bottom - selectedItemRect.height * 5 - defaultItemTopPadding - defaultItemBottomPadding; - listBottom = inputRect.bottom; - verifyListPositioning(); })); it('should display selected item and all possible items above and below when item in the middle of the list is selected', fakeAsync(() => { @@ -1917,10 +1898,6 @@ describe('igxSelect', () => { tick(); fixture.detectChanges(); getBoundingRectangles(); - listTop = document.body.getBoundingClientRect().bottom - defaultWindowToListOffset - - selectedItemRect.height * 5 - defaultItemBottomPadding - defaultItemTopPadding; - listBottom = document.body.getBoundingClientRect().bottom - defaultWindowToListOffset; - verifyListPositioning(); })); // tslint:disable-next-line:max-line-length it('should display list with selected item and all items before it and position selected item over input when last item is selected', diff --git a/projects/igniteui-angular/src/lib/select/select.component.ts b/projects/igniteui-angular/src/lib/select/select.component.ts index 91f6d5c6a7f..6c9baf04e10 100644 --- a/projects/igniteui-angular/src/lib/select/select.component.ts +++ b/projects/igniteui-angular/src/lib/select/select.component.ts @@ -1,7 +1,7 @@ import { IgxInputDirective } from './../directives/input/input.directive'; import { Component, ContentChildren, forwardRef, QueryList, ViewChild, Input, ContentChild, - AfterContentInit, HostBinding, Directive, TemplateRef, ElementRef, ChangeDetectorRef + AfterContentInit, HostBinding, Directive, TemplateRef, ElementRef, ChangeDetectorRef, Optional, Inject } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @@ -19,6 +19,7 @@ import { IgxLabelDirective } from '../directives/label/label.directive'; import { IgxSelectBase } from './select.common'; import { EditorProvider } from '../core/edit-provider'; import { IgxSelectionAPIService } from '../core/selection'; +import { DisplayDensityToken, IDisplayDensityOptions } from '../core/density'; /** @hidden @internal */ @Directive({ @@ -198,8 +199,9 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec constructor( protected elementRef: ElementRef, protected cdr: ChangeDetectorRef, - protected selection: IgxSelectionAPIService) { - super(elementRef, cdr, selection); + protected selection: IgxSelectionAPIService, + @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { + super(elementRef, cdr, selection, _displayDensityOptions); } /** @hidden @internal */ diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5169f7351c6..5910e6c759a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -108,6 +108,11 @@ export class AppComponent implements OnInit { icon: 'view_list', name: 'DropDown' }, + { + link: '/dropDown-density', + icon: 'horizontal_split', + name: 'DropDown - Density' + }, { link: '/expansionPanel', icon: 'expand_more', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ac595cd16e2..dd9bca23aba 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -65,6 +65,7 @@ import { GridColumnGroupsSampleComponent } from './grid-column-groups/grid-colum import { GridCellStylingSampleComponent } from './gird-cell-styling/grid-cell-styling.sample'; import { GridGroupBySampleComponent } from './grid-groupby/grid-groupby.sample'; import { DropDownSampleComponent } from './drop-down/drop-down.sample'; +import { DisplayDensityDropDownComponent } from './drop-down/display-density/display-density.sample'; import { ComboSampleComponent } from './combo/combo.sample'; import { OverlaySampleComponent } from './overlay/overlay.sample'; import { OverlayAnimationSampleComponent } from './overlay/overlay-animation.sample'; @@ -108,6 +109,7 @@ const components = [ DialogSampleComponent, DatePickerSampleComponent, DropDownSampleComponent, + DisplayDensityDropDownComponent, DragDropSampleComponent, ComboSampleComponent, IconSampleComponent, diff --git a/src/app/combo/combo.sample.html b/src/app/combo/combo.sample.html index 2d6590051b8..e89d19ec6f2 100644 --- a/src/app/combo/combo.sample.html +++ b/src/app/combo/combo.sample.html @@ -16,7 +16,7 @@
- Search Input
+ + diff --git a/src/app/drop-down/display-density/display-density.sample.html b/src/app/drop-down/display-density/display-density.sample.html new file mode 100644 index 00000000000..779a71c7d95 --- /dev/null +++ b/src/app/drop-down/display-density/display-density.sample.html @@ -0,0 +1,63 @@ +
+

Comfortable

+ + + + + + + {{ entry.valueKey }} + + + + + + + {{ entry.textKey }} + + +
+
+

Cosy

+ + + + + + + {{ entry.valueKey }} + + + + + + + {{ entry.textKey }} + + +
+
+

Compact

+ + + + + + + {{ entry.valueKey }} + + + + + + + {{ entry.textKey }} + + +
\ No newline at end of file diff --git a/src/app/drop-down/display-density/display-density.sample.scss b/src/app/drop-down/display-density/display-density.sample.scss new file mode 100644 index 00000000000..94d98b328fa --- /dev/null +++ b/src/app/drop-down/display-density/display-density.sample.scss @@ -0,0 +1,23 @@ +:host { + display: flex; + flex-flow: row; + justify-content: space-around; + align-items: center; +} + +.wrapping-div { + width: 100%; + height: 400px; + overflow-y: auto; +} + + +section { + width: 280px; + height: 600px; + border: 1px solid black; + display: flex; + flex-flow: column; + justify-content: space-around; + align-items: center; +} \ No newline at end of file diff --git a/src/app/drop-down/display-density/display-density.sample.ts b/src/app/drop-down/display-density/display-density.sample.ts new file mode 100644 index 00000000000..aaec6e8183f --- /dev/null +++ b/src/app/drop-down/display-density/display-density.sample.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; +import { DisplayDensity } from 'igniteui-angular'; + +const data = Array.apply(null, { length: 57 }).map((e, i) => { + return { + valueKey: i, + textKey: `Option ${i + 1}`, + isHeader: i % 7 === 0 + }; +}); +@Component({ + selector: 'app-display-density', + templateUrl: 'display-density.sample.html', + styleUrls: ['display-density.sample.scss'] +}) +export class DisplayDensityDropDownComponent { + public data = data; + public displayDensity = DisplayDensity; +} diff --git a/src/app/drop-down/drop-down.sample.html b/src/app/drop-down/drop-down.sample.html index 4e4aa2c66af..3456952d5d5 100644 --- a/src/app/drop-down/drop-down.sample.html +++ b/src/app/drop-down/drop-down.sample.html @@ -61,4 +61,24 @@
+
+ + + + {{ item.field }} + + + + + + {{ item.field }} + + + + + + {{ item.field }} + + +
\ No newline at end of file diff --git a/src/app/routing.ts b/src/app/routing.ts index 620968dfe33..30606cd891a 100644 --- a/src/app/routing.ts +++ b/src/app/routing.ts @@ -47,6 +47,7 @@ import { GridVirtualizationSampleComponent } from './grid-remote-virtualization/ import { ButtonGroupSampleComponent } from './buttonGroup/buttonGroup.sample'; import { GridColumnGroupsSampleComponent } from './grid-column-groups/grid-column-groups.sample'; import { DropDownSampleComponent } from './drop-down/drop-down.sample'; +import { DisplayDensityDropDownComponent } from './drop-down/display-density/display-density.sample'; import { ComboSampleComponent } from './combo/combo.sample'; import { OverlaySampleComponent } from './overlay/overlay.sample'; import { OverlayAnimationSampleComponent } from './overlay/overlay-animation.sample'; @@ -137,6 +138,10 @@ const appRoutes = [ path: 'dropDown', component: DropDownSampleComponent }, + { + path: 'dropDown-density', + component: DisplayDensityDropDownComponent + }, { path: 'drag-drop', component: DragDropSampleComponent