diff --git a/CHANGELOG.md b/CHANGELOG.md index efb8d7e5da9..eb2594da30a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,18 +7,27 @@ All notable changes for each version of this project will be documented in this ### General - `igxCombo` - **Behavioral Change** - Change default positioning strategy from `ConnectedPositioningStrategy` to `AutoPositionStrategy`. The [`Auto`](https://www.infragistics.com/products/ignite-ui-angular/angular/components/overlay_position.html#auto) strategy will initially try to show the element like the Connected strategy does. If the element goes out of the viewport Auto will flip the starting point and the direction, i.e. if the direction is 'bottom', it will switch it to 'top' and so on. If after flipping direction the content goes out of the view, auto strategy will revert to initial start point and direction and will push the content into the view. Note after pushing the content it may hide the combo's input. + - Make `onSearchInput` event cancellable. The event args type has been changed to `IComboSearchInputEventArgs`, which have the following properties: `searchText` - holds the text typed into the search input, `owner` - holds a reference to the combo component and `cancel` - indicates whether the event should be canceled. - `IgxOverlay` - - Added new property - `closeOnEsc` - in `OverlaySettings`. The overlay can now be prevented from closing, on escape keypress, by setting the property to `false`, by default it's `true`. + - Added new property `closeOnEscape` in `OverlaySettings` that controls whether the overlay should close on escape keypress. By default `closeOnEsc` is set to `false`. + - **Behavioral Change** - `modal` overlays shown directly through the Overlay Service no longer close on Escape by default. That behavior can now be specified using the `closeOnEscape` property. - `igxDialog` - - Added `closeOnEscapeKey` - with it, the dialog can be allowed or prevented from closing when `Esc` is pressed. + - Added `closeOnEscape` - with it, the dialog can be allowed or prevented from closing when `Esc` is pressed. - `IgxNavbar`: - **Breaking Changes** - The `igx-action-icon` has been renamed to `igx-navbar-action`. It should get renamed in your components via `ng update`; - `igxGrid` - Added `onScroll` event, which is emitted when the grid is scrolled vertically or horizontally. - `igxTreeGrid` - Removed `onDataPreLoad` event as it is specific for remote virtualization implementation, which is not supported for the `igxTreeGrid`. A more generic `onScroll` event is exposed and can be used instead. +- `IgxTimePicker` + - Added a disabled style for time parts outside of the minimum and maximum range. +- `igxDatePicker` + - Added new property - `editorTabIndex`, that allows setting tabindex for the default editor. ### New Features +- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid` + - Introduced `showSummaryOnCollapse` grid property which allows you to control whether the summary row stays visible when the groupBy / parent row is collapsed. + - Added support for tooltips on data cells default template and summary cells. - `IgxGridState` directive - Added support for expansion states, column selection and row pinning. - Added support for `IgxTreeGrid` and `IgxHierarchicalGrid` (including child grids) @@ -32,6 +41,11 @@ All notable changes for each version of this project will be documented in this - An optional string parameter `message` has been added to `show()` method. - `IgxNavbar` - Added new `igx-navbar-title, igxNavbarTitle` directive that can be used to provide custom content for navbar title. It would override the value of `title` input property. +- `IgxCalendar` and `IgxMonthPicker` + - `viewDateChanged` emitted after the month/year presented in the view is changed after user interaction. + - `activeViewChanged` event emitted after the active view (DEFAULT, YEAR, DECADE) is changed after user interaction. + - `viewDate` day value is always 1. + - `activeView` setter is now available as an input property. ## 10.0.0 diff --git a/ROADMAP.md b/ROADMAP.md index c86e3f48a42..f8e81ca290d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,12 +2,29 @@ # Current Milestone -## Milestone 12 (Due by August, 2020) +## Milestone 12 (Due by August 17th, 2020) -1. Accept ISO 8601 Date-only string as input for igx-calendar and igx-datepicker [#6994](https://github.com/IgniteUI/igniteui-angular/issues/6994) +1. igx-grid improve IGridEditEventArgs [#4965](https://github.com/IgniteUI/igniteui-angular/issues/4965) 2. igxCombo has to include caseSensitive property in filter search [#7282](https://github.com/IgniteUI/igniteui-angular/issues/7282) 3. igxCombo default positioning strategy [#7225](https://github.com/IgniteUI/igniteui-angular/issues/7225) -To Be Updated +4. igxSelect Add igxHint support [#5584](https://github.com/IgniteUI/igniteui-angular/issues/5584) +5. igxGrid Hide the group area row [#5561](https://github.com/IgniteUI/igniteui-angular/issues/5561) +6. igxDateRangePickerComponent calendar should display selected range if both start and end are filled and valid [#7593](https://github.com/IgniteUI/igniteui-angular/issues/7593) +7. Add support for mixing px and % column widths [#5486](https://github.com/IgniteUI/igniteui-angular/issues/5486) +8. Do not close modal overlay on ESC key press [#7697](https://github.com/IgniteUI/igniteui-angular/issues/7697) +9. Themes: Add Dock Manager Support [#7541](https://github.com/IgniteUI/igniteui-angular/issues/7541) +10. igx-grid - pre-select rows [#6653](https://github.com/IgniteUI/igniteui-angular/issues/6653) +11. Average and Sum are shown on the Ship country level [#7334](https://github.com/IgniteUI/igniteui-angular/issues/7334) +12. Dock Manager Better Default Themes [#7578](https://github.com/IgniteUI/igniteui-angular/issues/7578) +13. Expose templates for all ESF UI parts [#7221](https://github.com/IgniteUI/igniteui-angular/issues/7221) +14. Calendar events when user changes month/year [#7039](https://github.com/IgniteUI/igniteui-angular/issues/7039) +15. igxGrid default column display templates per-type [#7224](https://github.com/IgniteUI/igniteui-angular/issues/7224) +16. Provide "Unfreeze All" option under Freeze button on Data Grid & Tree Grid +To Be Updated [#6549](https://github.com/IgniteUI/igniteui-angular/issues/6549) +17. Exposing onActiveNodeChange output in the Grid [#7601](https://github.com/IgniteUI/igniteui-angular/issues/7601) +18. Refactor commit method of IgxHierarchicalTransactionService to accept same parameters as base type [#5205](https://github.com/IgniteUI/igniteui-angular/issues/5205) +19. Tooltip for grid cell text content [#6215](https://github.com/IgniteUI/igniteui-angular/issues/6215) +20. Add tooltip to column summary [#6505](https://github.com/IgniteUI/igniteui-angular/issues/6505) ## Going down the road diff --git a/package-lock.json b/package-lock.json index f57a1701742..0099154be1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7174,6 +7174,11 @@ } } }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -20162,8 +20167,7 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { "version": "1.1.1", diff --git a/package.json b/package.json index 28a558dd4f7..8c24fb799f3 100644 --- a/package.json +++ b/package.json @@ -57,12 +57,14 @@ "@types/source-map": "0.5.2", "classlist.js": "^1.1.20150312", "core-js": "^2.6.11", + "core-js-pure": "^3.6.5", "hammerjs": "^2.0.8", "igniteui-trial-watermark": "^1.0.3", "jszip": "^3.4.0", "resize-observer-polyfill": "^1.5.1", "rxjs": "^6.5.4", "tslib": "^2.0.0", + "setimmediate": "^1.0.5", "web-animations-js": "^2.3.2", "zone.js": "~0.10.3" }, diff --git a/projects/igniteui-angular/ng-package.json b/projects/igniteui-angular/ng-package.json index 0750500bbb6..ae5d8097051 100644 --- a/projects/igniteui-angular/ng-package.json +++ b/projects/igniteui-angular/ng-package.json @@ -8,6 +8,7 @@ "whitelistedNonPeerDependencies": [ "@types/hammerjs", "@types/jszip", + "core-js-pure", "hammerjs", "jszip", "resize-observer-polyfill", diff --git a/projects/igniteui-angular/ng-package.prod.json b/projects/igniteui-angular/ng-package.prod.json index 2088cdfbb99..6b21ffae16d 100644 --- a/projects/igniteui-angular/ng-package.prod.json +++ b/projects/igniteui-angular/ng-package.prod.json @@ -10,6 +10,7 @@ "whitelistedNonPeerDependencies": [ "@types/hammerjs", "@types/jszip", + "core-js-pure", "hammerjs", "jszip", "resize-observer-polyfill", diff --git a/projects/igniteui-angular/package.json b/projects/igniteui-angular/package.json index 232b741a9c3..c958315b67a 100644 --- a/projects/igniteui-angular/package.json +++ b/projects/igniteui-angular/package.json @@ -68,6 +68,7 @@ "dependencies": { "@types/hammerjs": "^2.0.36", "@types/jszip": "^3.1.7", + "core-js-pure": "^3.6.5", "hammerjs": "^2.0.8", "jszip": "^3.3.0", "tslib": "^2.0.0", diff --git a/projects/igniteui-angular/schematics/utils/dependency-handler.ts b/projects/igniteui-angular/schematics/utils/dependency-handler.ts index 607e3467623..99a68e2e96f 100644 --- a/projects/igniteui-angular/schematics/utils/dependency-handler.ts +++ b/projects/igniteui-angular/schematics/utils/dependency-handler.ts @@ -30,6 +30,7 @@ export const DEPENDENCIES_MAP: PackageEntry[] = [ { name: '@types/hammerjs', target: PackageTarget.DEV }, { name: '@types/jszip', target: PackageTarget.DEV }, { name: 'igniteui-trial-watermark', target: PackageTarget.NONE }, + { name: 'core-js-pure', target: PackageTarget.NONE }, // peerDependencies { name: '@angular/forms', target: PackageTarget.NONE }, { name: '@angular/common', target: PackageTarget.NONE }, diff --git a/projects/igniteui-angular/src/lib/calendar/README.md b/projects/igniteui-angular/src/lib/calendar/README.md index 4a978e202fd..a2407c2df68 100644 --- a/projects/igniteui-angular/src/lib/calendar/README.md +++ b/projects/igniteui-angular/src/lib/calendar/README.md @@ -133,7 +133,7 @@ The calendar header will not be rendered when the selection is either `multi` or - `viewDate: Date` -Controls the year/month that will be presented in the default view when the calendar renders. By default it is the current year/month. +Controls the year/month that will be presented in the default view when the calendar renders. By default it is the first day of the current year/month. - `value: Date | Date[]` @@ -173,7 +173,17 @@ Controls the visibility of the dates that do not belong to the current month. - `onSelection(): Date | Date[]` Event fired when a value is selected through UI interaction. -Returns the selected value (depending on the type of selection). +Emits the selected value (depending on the type of selection). + +- `viewDateChanged(): IViewDateChangeEventArgs` + +Event fired after the month/year presented in the view is changed. +Emits an object containing the previous and current value of the `viewDate` property. + +- `activeViewChanged(): CalendarView` + +Event fired after the active view is changed. +Emits an CalendarView enum, indicating the `activeView` property value. ### Methods diff --git a/projects/igniteui-angular/src/lib/calendar/calendar-base.ts b/projects/igniteui-angular/src/lib/calendar/calendar-base.ts index a5f33a315c4..2101e31d2ba 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar-base.ts +++ b/projects/igniteui-angular/src/lib/calendar/calendar-base.ts @@ -4,6 +4,7 @@ import { ControlValueAccessor } from '@angular/forms'; import { DateRangeDescriptor } from '../core/dates'; import { Subject } from 'rxjs'; import { isDate } from '../core/utils'; +import { CalendarView } from './month-picker-base'; /** * Sets the selction type - single, multi or range. @@ -20,6 +21,11 @@ export enum ScrollMonth { NONE = 'none' } +export interface IViewDateChangeEventArgs { + previousValue: Date; + currentValue: Date; +} + /** @hidden @internal */ @Directive({ selector: '[igxCalendarBase]', @@ -168,7 +174,8 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor { * Sets the date that will be presented in the default view when the component renders. */ public set viewDate(value: Date) { - this._viewDate = this.getDateOnly(value); + const date = this.getDateOnly(value).setDate(1); + this._viewDate = new Date(date); } /** @@ -240,6 +247,34 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor { @Output() public onSelection = new EventEmitter(); + /** + * Emits an event when the month in view is changed. + * ```html + * + * ``` + * ```typescript + * public viewDateChanged(event: IViewDateChangeEventArgs) { + * let viewDate = event.currentValue; + * } + * ``` + */ + @Output() + public viewDateChanged = new EventEmitter(); + + /** + * Emits an event when the active view is changed. + * ```html + * + * ``` + * ```typescript + * public activeViewChanged(event: CalendarView) { + * let activeView = event; + * } + * ``` + */ + @Output() + public activeViewChanged = new EventEmitter(); + /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.component.html b/projects/igniteui-angular/src/lib/calendar/calendar.component.html index eb77d8c740b..73b895eac7f 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.component.html +++ b/projects/igniteui-angular/src/lib/calendar/calendar.component.html @@ -22,7 +22,7 @@

-
- (onSelection)="changeMonth($event)"> - { @@ -252,8 +254,8 @@ describe('IgxCalendar - ', () => { expect(calendar.formatOptions).toEqual(jasmine.objectContaining(defaultOptions)); expect(calendar.formatViews).toEqual(jasmine.objectContaining(defaultViews)); expect(headerYear.nativeElement.textContent.trim()).toMatch('2018'); - expect(headerWeekday.nativeElement.textContent.trim()).toMatch('Mon'); - expect(headerDate.nativeElement.textContent.trim()).toMatch('Sep 17'); + expect(headerWeekday.nativeElement.textContent.trim()).toMatch('Sat'); + expect(headerDate.nativeElement.textContent.trim()).toMatch('Sep 1'); expect(bodyYear.nativeElement.textContent.trim()).toMatch('2018'); expect(bodyMonth.nativeElement.textContent.trim()).toMatch('Sep'); @@ -267,8 +269,8 @@ describe('IgxCalendar - ', () => { expect(calendar.formatOptions).toEqual(jasmine.objectContaining(Object.assign(defaultOptions, formatOptions))); expect(calendar.formatViews).toEqual(jasmine.objectContaining(Object.assign(defaultViews, formatViews))); expect(headerYear.nativeElement.textContent.trim()).toMatch('18'); - expect(headerWeekday.nativeElement.textContent.trim()).toMatch('Mon'); - expect(headerDate.nativeElement.textContent.trim()).toMatch('September 17'); + expect(headerWeekday.nativeElement.textContent.trim()).toMatch('Sat'); + expect(headerDate.nativeElement.textContent.trim()).toMatch('September 1'); expect(bodyYear.nativeElement.textContent.trim()).toMatch('18'); expect(bodyMonth.nativeElement.textContent.trim()).toMatch('September'); @@ -283,8 +285,8 @@ describe('IgxCalendar - ', () => { expect(calendar.formatOptions).toEqual(jasmine.objectContaining(Object.assign(defaultOptions, formatOptions))); expect(calendar.formatViews).toEqual(jasmine.objectContaining(Object.assign(defaultViews, formatViews))); expect(headerYear.nativeElement.textContent.trim()).toMatch('2018'); - expect(headerWeekday.nativeElement.textContent.trim()).toMatch('Mon'); - expect(headerDate.nativeElement.textContent.trim()).toMatch('September 17'); + expect(headerWeekday.nativeElement.textContent.trim()).toMatch('Sat'); + expect(headerDate.nativeElement.textContent.trim()).toMatch('September 1'); expect(bodyYear.nativeElement.textContent.trim()).toMatch('2018'); expect(bodyMonth.nativeElement.textContent.trim()).toMatch('8'); }); @@ -305,8 +307,8 @@ describe('IgxCalendar - ', () => { fixture.detectChanges(); expect(headerYear.nativeElement.textContent.trim()).toMatch('2018'); - expect(headerWeekday.nativeElement.textContent.trim()).toMatch('Mon'); - expect(headerDate.nativeElement.textContent.trim()).toMatch('Sep 17'); + expect(headerWeekday.nativeElement.textContent.trim()).toMatch('Sat'); + expect(headerDate.nativeElement.textContent.trim()).toMatch('Sep 1'); expect(bodyYear.nativeElement.textContent.trim()).toMatch('2018'); expect(bodyMonth.nativeElement.textContent.trim()).toMatch('Sep'); expect(bodyWeekday.nativeElement.textContent.trim()).toMatch('Sun'); @@ -319,8 +321,8 @@ describe('IgxCalendar - ', () => { bodyWeekday = dom.query(By.css(HelperTestFunctions.WEEKSTART_LABEL_CSSCLASS)); expect(calendar.locale).toEqual(locale); expect(headerYear.nativeElement.textContent.trim()).toMatch('18'); - expect(headerWeekday.nativeElement.textContent.trim()).toMatch('lun.,'); - expect(headerDate.nativeElement.textContent.trim()).toMatch('17 sept.'); + expect(headerWeekday.nativeElement.textContent.trim()).toMatch('sam.,'); + expect(headerDate.nativeElement.textContent.trim()).toMatch('1 sept.'); expect(bodyYear.nativeElement.textContent.trim()).toMatch('18'); expect(bodyMonth.nativeElement.textContent.trim()).toMatch('sept.'); expect(bodyWeekday.nativeElement.textContent.trim()).toMatch('Dim.'); @@ -1559,36 +1561,41 @@ describe('IgxCalendar - ', () => { prev.nativeElement.focus(); expect(prev.nativeElement).toBe(document.activeElement); - UIInteractions.triggerKeyDownEvtUponElem('Enter', prev.nativeElement); tick(100); fixture.detectChanges(); expect(calendar.viewDate.getMonth()).toEqual(4); - const next = dom.queryAll(By.css(HelperTestFunctions.CALENDAR_NEXT_BUTTON_CSSCLASS))[0]; next.nativeElement.focus(); expect(next.nativeElement).toBe(document.activeElement); UIInteractions.triggerKeyDownEvtUponElem('Enter', next.nativeElement); - tick(100); - fixture.detectChanges(); + fixture.detectChanges(); + tick(100); UIInteractions.triggerKeyDownEvtUponElem('Enter', next.nativeElement); tick(100); fixture.detectChanges(); + expect(calendar.viewDate.getMonth()).toEqual(6); })); - it('Should open years view, navigate through and select an year via KB.', () => { + it('Should open years view, navigate through and select an year via KB.', fakeAsync(() => { const year = dom.queryAll(By.css(HelperTestFunctions.CALENDAR_DATE_CSSCLASS))[1]; year.nativeElement.focus(); expect(year.nativeElement).toBe(document.activeElement); + spyOn(calendar.activeViewChanged, 'emit').and.callThrough(); + UIInteractions.triggerKeyDownEvtUponElem('Enter', document.activeElement); fixture.detectChanges(); + tick(); + + expect(calendar.activeViewChanged.emit).toHaveBeenCalledTimes(1); + expect(calendar.activeViewChanged.emit).toHaveBeenCalledWith(CalendarView.DECADE); const years = dom.queryAll(By.css(HelperTestFunctions.YEAR_CSSCLASS)); let currentYear = dom.query(By.css(HelperTestFunctions.CURRENT_YEAR_CSSCLASS)); @@ -1609,20 +1616,33 @@ describe('IgxCalendar - ', () => { currentYear = dom.query(By.css(HelperTestFunctions.CURRENT_YEAR_CSSCLASS)); expect(currentYear.nativeElement.textContent.trim()).toMatch('2016'); + const previousValue = fixture.componentInstance.calendar.viewDate; + spyOn(calendar.viewDateChanged, 'emit').and.callThrough(); + UIInteractions.triggerKeyDownEvtUponElem('Enter', currentYear.nativeElement); + fixture.detectChanges(); + tick(); + const eventArgs: IViewDateChangeEventArgs = { previousValue, currentValue: fixture.componentInstance.calendar.viewDate }; + expect(calendar.viewDateChanged.emit).toHaveBeenCalledTimes(1); + expect(calendar.viewDateChanged.emit).toHaveBeenCalledWith(eventArgs); expect(calendar.viewDate.getFullYear()).toEqual(2016); - }); + })); - it('Should open months view, navigate through and select a month via KB.', () => { + it('Should open months view, navigate through and select a month via KB.', fakeAsync(() => { const month = dom.queryAll(By.css(HelperTestFunctions.CALENDAR_DATE_CSSCLASS))[0]; month.nativeElement.focus(); + spyOn(calendar.activeViewChanged, 'emit').and.callThrough(); expect(month.nativeElement).toBe(document.activeElement); UIInteractions.triggerKeyDownEvtUponElem('Enter', document.activeElement); fixture.detectChanges(); + tick(); + + expect(calendar.activeViewChanged.emit).toHaveBeenCalledTimes(1); + expect(calendar.activeViewChanged.emit).toHaveBeenCalledWith(CalendarView.YEAR); const months = dom.queryAll(By.css(HelperTestFunctions.MONTH_CSSCLASS)); const currentMonth = dom.query(By.css(HelperTestFunctions.CURRENT_MONTH_CSSCLASS)); @@ -1651,11 +1671,18 @@ describe('IgxCalendar - ', () => { expect(document.activeElement.textContent.trim()).toMatch('Sep'); + const previousValue = fixture.componentInstance.calendar.viewDate; + spyOn(calendar.viewDateChanged, 'emit').and.callThrough(); + UIInteractions.triggerKeyDownEvtUponElem('Enter', document.activeElement); fixture.detectChanges(); + tick(); + const eventArgs: IViewDateChangeEventArgs = { previousValue, currentValue: fixture.componentInstance.calendar.viewDate }; + expect(calendar.viewDateChanged.emit).toHaveBeenCalledTimes(1); + expect(calendar.viewDateChanged.emit).toHaveBeenCalledWith(eventArgs); expect(calendar.viewDate.getMonth()).toEqual(8); - }); + })); it('Should navigate to the first enabled date from the previous month when using "arrow up" key.', fakeAsync(() => { const dateRangeDescriptors: DateRangeDescriptor[] = []; diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.component.ts b/projects/igniteui-angular/src/lib/calendar/calendar.component.ts index 68b25a9c35d..723f951b2e9 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/calendar.component.ts @@ -466,6 +466,7 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements * @internal */ public previousMonth(isKeydownTrigger = false) { + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.getPrevMonth(this.viewDate); this.animationAction = ScrollMonth.PREV; this.isKeydownTrigger = isKeydownTrigger; @@ -478,6 +479,7 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements * @internal */ public nextMonth(isKeydownTrigger = false) { + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.getNextMonth(this.viewDate); this.animationAction = ScrollMonth.NEXT; this.isKeydownTrigger = isKeydownTrigger; @@ -610,6 +612,7 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements this.daysView.daysNavService.focusNextDate(day.nativeElement, args.key, true); } }; + this.previousViewDate = this.viewDate; this.viewDate = this.nextDate; } @@ -618,6 +621,7 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements * @intenal */ public changeMonth(event: Date) { + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.getFirstViewDate(event, 'month', this.activeViewIdx); this.activeView = CalendarView.DEFAULT; @@ -711,6 +715,11 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements * @internal */ public animationDone(event) { + if ((event.fromState === ScrollMonth.NONE && (event.toState === ScrollMonth.PREV || event.toState === ScrollMonth.NEXT)) || + (event.fromState === 'void' && event.toState === ScrollMonth.NONE)) { + this.viewDateChanged.emit({ previousValue: this.previousViewDate, currentValue: this.viewDate }); + } + if (this.monthScrollDirection !== ScrollMonth.NONE) { this.scrollMonth$.next(); } @@ -736,6 +745,16 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements this.animationAction = ScrollMonth.NONE; } + /** + * @hidden + * @internal + */ + public viewRendered(event) { + if (event.fromState !== 'void') { + this.activeViewChanged.emit(this.activeView); + } + } + /** * Keyboard navigation of the calendar * @hidden @@ -813,6 +832,7 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements const isPageDown = event.key === 'PageDown'; const step = isPageDown ? 1 : -1; + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.timedelta(this.viewDate, 'year', step); this.animationAction = isPageDown ? ScrollMonth.NEXT : ScrollMonth.PREV; diff --git a/projects/igniteui-angular/src/lib/calendar/month-picker-base.ts b/projects/igniteui-angular/src/lib/calendar/month-picker-base.ts index 55aa507770e..24150ce16f0 100644 --- a/projects/igniteui-angular/src/lib/calendar/month-picker-base.ts +++ b/projects/igniteui-angular/src/lib/calendar/month-picker-base.ts @@ -1,5 +1,5 @@ import { IgxCalendarBaseDirective } from './calendar-base'; -import { HostBinding, Directive, ViewChildren, ElementRef, QueryList } from '@angular/core'; +import { HostBinding, Directive, ViewChildren, ElementRef, QueryList, Input } from '@angular/core'; import { KEYS } from '../core/utils'; /** @@ -35,9 +35,18 @@ export class IgxMonthPickerBaseDirective extends IgxCalendarBaseDirective { @ViewChildren('yearsBtn') public yearsBtns: QueryList; + /** + * @hidden @internal + */ + public previousViewDate: Date; + + @Input() /** * Gets the current active view. + * ```typescript + * this.activeView = calendar.activeView; + * ``` */ public get activeView(): CalendarView { return this._activeView; @@ -45,6 +54,12 @@ export class IgxMonthPickerBaseDirective extends IgxCalendarBaseDirective { /** * Sets the current active view. + * ```html + * + * ``` + * ```typescript + * calendar.activeView = CalendarView.YEAR; + * ``` */ public set activeView(val: CalendarView) { this._activeView = val; @@ -73,6 +88,7 @@ export class IgxMonthPickerBaseDirective extends IgxCalendarBaseDirective { * @hidden */ public changeYear(event: Date) { + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.getFirstViewDate(event, 'month', this.activeViewIdx); this.activeView = CalendarView.DEFAULT; @@ -87,7 +103,7 @@ export class IgxMonthPickerBaseDirective extends IgxCalendarBaseDirective { * @hidden */ public activeViewDecade(activeViewIdx = 0): void { - this._activeView = CalendarView.DECADE; + this.activeView = CalendarView.DECADE; this.activeViewIdx = activeViewIdx; } diff --git a/projects/igniteui-angular/src/lib/calendar/month-picker/README.md b/projects/igniteui-angular/src/lib/calendar/month-picker/README.md index afc9b262096..277084ec8af 100644 --- a/projects/igniteui-angular/src/lib/calendar/month-picker/README.md +++ b/projects/igniteui-angular/src/lib/calendar/month-picker/README.md @@ -88,7 +88,7 @@ The default value is `en`. - `viewDate: Date` -Controls the year/month that will be presented in the default view when the month picker renders. By default it is the current year/month. +Controls the year/month that will be presented in the default view when the month picker renders. By default it is the first day of the current year/month. - `value: Date` @@ -121,3 +121,13 @@ The default values are listed below. Event fired when a value is selected through UI interaction. Returns the selected value (depending on the type of selection). + +- `viewDateChanged(): IViewDateChangeEventArgs` + +Event fired after the month/year presented in the view is changed. +Emits an object containing the previous and current value of the `viewDate` property. + +- `activeViewChanged(): CalendarView` + +Event fired after the active view is changed. +Emits an CalendarView enum, indicating the `activeView` property value. diff --git a/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.html b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.html index 11c40c1c4a7..1ef703e88e5 100644 --- a/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.html +++ b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.html @@ -1,4 +1,4 @@ -
+
- { }; expect(monthPicker.value).toBeUndefined(); - expect(monthPicker.viewDate.getDate()).toEqual(instance.viewDate.getDate()); + expect(monthPicker.viewDate.getDate()).toEqual(1); expect(monthPicker.locale).toEqual('en'); const today = new Date(Date.now()); @@ -89,7 +89,7 @@ describe('IgxMonthPicker', () => { expect(monthPicker.locale).toEqual('fr'); expect(monthPicker.formatOptions.year).toEqual('2-digit'); expect(monthPicker.value.getDate()).toEqual(today.getDate()); - expect(monthPicker.viewDate.getDate()).toEqual(today.getDate()); + expect(monthPicker.viewDate.getDate()).toEqual(1); }); it('should properly set formatOptions and formatViews', () => { @@ -173,7 +173,7 @@ describe('IgxMonthPicker', () => { expect(monthPicker.onSelection.emit).toHaveBeenCalled(); expect(currentMonth.nativeElement.textContent.trim()).toEqual('Mar'); - const nextDay = new Date(2019, 2, 7); + const nextDay = new Date(2019, 2, 1); expect(fixture.componentInstance.model.getDate()).toEqual(nextDay.getDate()); }); diff --git a/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.ts b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.ts index fc85c9fe830..ff6d92e7a34 100644 --- a/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.ts @@ -14,6 +14,7 @@ import { IgxMonthsViewComponent } from '../months-view/months-view.component'; import { IgxMonthPickerBaseDirective, CalendarView } from '../month-picker-base'; import { IgxYearsViewComponent } from '../years-view/years-view.component'; import { IgxDaysViewComponent } from '../days-view/days-view.component'; +import { ScrollMonth } from '../calendar-base'; let NEXT_ID = 0; @Component({ @@ -99,10 +100,23 @@ export class IgxMonthPickerComponent extends IgxMonthPickerBaseDirective { /** * @hidden */ - public animationDone() { + public animationDone(event) { + if ((event.fromState === 'void' && event.toState === '') || + (event.fromState === '' && (event.toState === ScrollMonth.PREV || event.toState === ScrollMonth.NEXT))) { + this.viewDateChanged.emit({ previousValue: this.previousViewDate, currentValue: this.viewDate }); + } this.yearAction = ''; } + /** + * @hidden + */ + public viewRendered(event) { + if (event.fromState !== 'void') { + this.activeViewChanged.emit(this.activeView); + } + } + /** * @hidden */ @@ -140,6 +154,7 @@ export class IgxMonthPickerComponent extends IgxMonthPickerBaseDirective { */ public nextYear() { this.yearAction = 'next'; + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.getNextYear(this.viewDate); this.selectDate(this.viewDate); @@ -163,6 +178,7 @@ export class IgxMonthPickerComponent extends IgxMonthPickerBaseDirective { */ public previousYear() { this.yearAction = 'prev'; + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.getPrevYear(this.viewDate); this.selectDate(this.viewDate); @@ -185,6 +201,7 @@ export class IgxMonthPickerComponent extends IgxMonthPickerBaseDirective { * @hidden */ public selectYear(event: Date) { + this.previousViewDate = this.viewDate; this.viewDate = new Date(event.getFullYear(), event.getMonth(), event.getDate()); this.activeView = CalendarView.DEFAULT; @@ -207,7 +224,7 @@ export class IgxMonthPickerComponent extends IgxMonthPickerBaseDirective { /** * Selects a date. * ```typescript - * this.monPicker.selectDate(new Date(`2018-06-12`)); + * this.monthPicker.selectDate(new Date(`2018-06-12`)); * ``` */ public selectDate(value: Date) { @@ -238,6 +255,7 @@ export class IgxMonthPickerComponent extends IgxMonthPickerBaseDirective { public onKeydownPageUp(event: KeyboardEvent) { event.preventDefault(); this.yearAction = 'prev'; + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.getPrevYear(this.viewDate); } @@ -248,6 +266,7 @@ export class IgxMonthPickerComponent extends IgxMonthPickerBaseDirective { public onKeydownPageDown(event: KeyboardEvent) { event.preventDefault(); this.yearAction = 'next'; + this.previousViewDate = this.viewDate; this.viewDate = this.calendarModel.getNextYear(this.viewDate); } diff --git a/projects/igniteui-angular/src/lib/calendar/navigation.service.ts b/projects/igniteui-angular/src/lib/calendar/navigation.service.ts deleted file mode 100644 index 432955121b3..00000000000 --- a/projects/igniteui-angular/src/lib/calendar/navigation.service.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { Injectable } from '@angular/core'; -import { IgxDayItemComponent } from './days-view/day-item.component'; -import { IgxDaysViewComponent } from './days-view/days-view.component'; -import { ScrollMonth } from './calendar-base'; - -enum Direction { - Up = 'ArrowUp', - Down = 'ArrowDown', - Left = 'ArrowLeft', - Right = 'ArrowRight', -} - -const ARROW = 'Arrow'; - -/** - * @hidden - * This service should be decoupled from the days View Component and it should become global for the whole calendar navigation. - */ -@Injectable() -export class IgxDaysViewNavigationService { - public monthView: IgxDaysViewComponent; - /** - * Implements kb navigation in all MoveDirections. nextDate and nextMonthView naming convention is used for both previous/next - * @hidden - */ - public focusNextDate(target: HTMLElement, key: string, nextView = false) { - if (target.childElementCount === 0) { target = target.parentElement; } - if (key.indexOf('Arrow') === -1) { key = ARROW.concat(key); } - const monthView = this.monthView; - const node = monthView.dates.find((date) => date.nativeElement === target); - let dates = monthView.dates.toArray(), - day: IgxDayItemComponent, step, i, nextDate: Date; - const index = dates.indexOf(node); - - if (!node) { return; } - - // focus item in current month - switch (key) { - case Direction.Left: { - step = -1; - nextDate = this.timedelta(node.date.date, step); - for (i = index; i > 0; i--) { - day = nextView ? dates[i] : dates[i - 1]; - nextDate = day.date.date; - if (day.date.isPrevMonth) { - break; - } - if (day && day.isFocusable) { - day.nativeElement.focus(); - return; - } - } - break; - } - case Direction.Right: { - step = 1; - nextDate = this.timedelta(node.date.date, step); - for (i = index; i < dates.length - 1; i++) { - day = nextView ? dates[i] : dates[i + 1]; - nextDate = day.date.date; - if (day.date.isNextMonth) { - break; - } - if (day && day.isFocusable) { - day.nativeElement.focus(); - return; - } - } - break; - } - case Direction.Up: { - step = -7; - nextDate = this.timedelta(node.date.date, step); - for (i = index; i - 7 > -1; i -= 7) { - day = nextView ? dates[i] : dates[i - 7]; - nextDate = day.date.date; - if (day.date.isPrevMonth) { - break; - } - if (day && day.isFocusable) { - day.nativeElement.focus(); - return; - } - } - break; - } - case Direction.Down: { - step = 7; - nextDate = this.timedelta(node.date.date, step); - for (i = index; i + 7 < 42; i += 7) { - day = nextView ? dates[i] : dates[i + 7]; - nextDate = day.date.date; - if (day.date.isNextMonth) { - break; - } - if (day && day.isFocusable) { - day.nativeElement.focus(); - return; - } - } - break; - } - } - - // focus item in prev/next visible month - const nextMonthView = step > 0 ? monthView.nextMonthView : monthView.prevMonthView; - if (nextMonthView) { - dates = nextMonthView.dates.toArray(); - day = dates.find((item) => item.date.date.getTime() === nextDate.getTime()); - - if (day && day.isFocusable) { - day.nativeElement.focus(); - return; - } - nextMonthView.daysNavService.focusNextDate(day.nativeElement, key); - } - - // if iterating in the visible prev/next moths above found a day that is not focusable, ie is disabled, hidden, etc - // then it is needed to recalculate the next day, which is going to be part of the prev/next months - if (day && !day.isFocusable) { - day = dates[i + step]; - if (!day) { - nextDate = this.timedelta(node.date.date, step + i - index); - } - } - - // focus item in prev/next month, which is currently out of view - let dayIsNextMonth: boolean; // determine what we need to check for next date - if it belongs to prev or next month - if (day) { dayIsNextMonth = step > 0 ? day.date.isNextMonth : day.date.isPrevMonth; } - if (monthView.changeDaysView && !nextMonthView && ((day && dayIsNextMonth) || !day)) { - const monthAction = step > 0 ? ScrollMonth.NEXT : ScrollMonth.PREV; - monthView.onViewChanging.emit({monthAction: monthAction, key: key, nextDate: nextDate}); - } - } - - /** - * Focuses first focusable day in the month. Will go to next visible month, if no day in the first month is focusable - * @hidden - */ - public focusHomeDate() { - let monthView = this.monthView; - while (!this.focusFirstDay(monthView) && monthView.nextMonthView) { - monthView = monthView.nextMonthView; - } - } - - /** - * Focuses last focusable day in the month. Will go to previous visible month, if no day in the first month is focusable - * @hidden - */ - public focusEndDate() { - let monthView = this.monthView; - while (!this.focusLastDay(monthView) && monthView.prevMonthView) { - monthView = monthView.prevMonthView; - } - } - - private timedelta(date: Date, units: number): Date { - const ret = new Date(date); - ret.setDate(ret.getDate() + units); - return ret; - } - - private focusFirstDay(monthView: IgxDaysViewComponent): boolean { - const dates = monthView.dates.filter(d => d.isCurrentMonth); - for (let i = 0; i < dates.length; i++) { - if (dates[i].isFocusable) { - dates[i].nativeElement.focus(); - return true; - } - } - return false; - } - - private focusLastDay(monthView: IgxDaysViewComponent): boolean { - const dates = monthView.dates.filter(d => d.isCurrentMonth); - for (let i = dates.length - 1; i >= 0; i--) { - if (dates[i].isFocusable) { - dates[i].nativeElement.focus(); - return true; - } - } - return false; - } -} 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 8e1f89a67aa..2e74e945738 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -18,6 +18,7 @@ import { configureTestSuite } from '../test-utils/configure-suite'; import { DisplayDensity } from '../core/density'; import { AbsoluteScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api'; import { IgxSelectionAPIService } from '../core/selection'; +import { CancelableEventArgs } from '../core/utils'; const CSS_CLASS_COMBO = 'igx-combo'; const CSS_CLASS_COMBO_DROPDOWN = 'igx-combo__drop-down'; @@ -530,21 +531,42 @@ describe('igxCombo', () => { expect(matchSpy).toHaveBeenCalledTimes(1); expect(combo.onSearchInput.emit).toHaveBeenCalledTimes(0); + const args = { + searchText: 'Fake', + owner: combo, + cancel: false + }; combo.handleInputChange('Fake'); expect(matchSpy).toHaveBeenCalledTimes(2); expect(combo.onSearchInput.emit).toHaveBeenCalledTimes(1); - expect(combo.onSearchInput.emit).toHaveBeenCalledWith('Fake'); + expect(combo.onSearchInput.emit).toHaveBeenCalledWith(args); + args.searchText = ''; combo.handleInputChange(''); expect(matchSpy).toHaveBeenCalledTimes(3); expect(combo.onSearchInput.emit).toHaveBeenCalledTimes(2); - expect(combo.onSearchInput.emit).toHaveBeenCalledWith(''); + expect(combo.onSearchInput.emit).toHaveBeenCalledWith(args); combo.filterable = false; combo.handleInputChange(); expect(matchSpy).toHaveBeenCalledTimes(4); expect(combo.onSearchInput.emit).toHaveBeenCalledTimes(2); }); + it('should be able to cancel onSearchInput', () => { + combo = new IgxComboComponent({ nativeElement: null }, mockCdr, mockSelection as any, mockComboService, null, mockInjector); + combo.ngOnInit(); + combo.data = data; + combo.filterable = true; + combo.onSearchInput.subscribe((e) => { + e.cancel = true; + }); + const matchSpy = spyOn(combo, 'checkMatch').and.callThrough(); + spyOn(combo.onSearchInput, 'emit').and.callThrough(); + + combo.handleInputChange('Item1'); + expect(combo.onSearchInput.emit).toHaveBeenCalledTimes(1); + expect(matchSpy).toHaveBeenCalledTimes(0); + }); }); describe('Initialization and rendering tests: ', () => { configureTestSuite(); diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.ts b/projects/igniteui-angular/src/lib/combo/combo.component.ts index da944fee344..8366a569f9a 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.ts @@ -96,6 +96,12 @@ export interface IComboSelectionChangeEventArgs extends CancelableEventArgs, IBa event?: Event; } +/** Event emitted when the igx-combo's search input changes */ +export interface IComboSearchInputEventArgs extends CancelableEventArgs, IBaseEventArgs { + /** The text that has been typed into the search input */ + searchText: string; +} + export interface IComboItemAdditionEvent extends IBaseEventArgs { oldCollection: any[]; addedItem: any; @@ -483,7 +489,7 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas * ``` */ @Output() - public onSearchInput = new EventEmitter(); + public onSearchInput = new EventEmitter(); /** * Emitted when new chunk of data is loaded from the virtualization @@ -981,7 +987,16 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas */ public handleInputChange(event?: string) { if (event !== undefined) { - this.onSearchInput.emit(event); + const args: IComboSearchInputEventArgs = { + searchText: event, + owner: this, + cancel: false + }; + this.onSearchInput.emit(args); + if (args.cancel) { + this.searchValue = null; + return; + } } this.checkMatch(); } @@ -1436,8 +1451,8 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas /** Returns a string that should be populated in the combo's text box */ private concatDisplayText(selection: any[]): string { const value = this.displayKey !== null && this.displayKey !== undefined ? - this.convertKeysToItems(selection).map(entry => entry[this.displayKey]).join(', ') : - selection.join(', '); + this.convertKeysToItems(selection).map(entry => entry[this.displayKey]).join(', ') : + selection.join(', '); return value; } diff --git a/projects/igniteui-angular/src/lib/core/styles/components/charts/_category-chart-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/charts/_category-chart-theme.scss index e49234b6ac5..e9d0ec2bd95 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/charts/_category-chart-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/charts/_category-chart-theme.scss @@ -282,8 +282,8 @@ $title-styles: igx-type-scale-category($type-scale, $title); $subtitle-styles: igx-type-scale-category($type-scale, $subtitle); - $x-axis-label-styles: igx-type-scale-category($type-scale, $x-axis-label); - $x-axis-title-styles: igx-type-scale-category($type-scale, $x-axis-title); + $x-axis-label-styles: igx-type-scale-category($type-scale, $x-axis-label-text-style); + $x-axis-title-styles: igx-type-scale-category($type-scale, $x-axis-title-text-style); $y-axis-label-styles: igx-type-scale-category($type-scale, $y-axis-label-text-style); $y-axis-title-styles: igx-type-scale-category($type-scale, $y-axis-title-text-style); diff --git a/projects/igniteui-angular/src/lib/core/styles/components/dock-manager/_dock-manager-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/dock-manager/_dock-manager-theme.scss new file mode 100644 index 00000000000..76c8d3b8c77 --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/components/dock-manager/_dock-manager-theme.scss @@ -0,0 +1,86 @@ +//// +/// @group themes +/// @access public +/// @author Simeon Simeonoff +/// @author Marin Popov +//// + +/// If only background color is specified, text/icon color will be assigned automatically to a contrasting color. +/// @param {Map} $palette [$default-palette] - The palette used as basis for styling the component. +/// @param {Map} $schema [$light-schema] - The schema used as basis for styling the component. +/// +/// @param {Color} $accent-color [null] - Sets the pinned header background color, the joystick background and border colors, as well as the context menu background color. +/// @param {Color} $active-color [null] - Sets the active text and border colors for tabs, panes, and menus. +/// @param {Color} $border-color [null] - Sets the global border color in the dock manager. Also sets the pane content background and the context menu active background colors. +/// @param {Color} $button-text [null] - Sets the button text color. +/// @param {Color} $context-menu-background [null] - Sets the background color for context menus. +/// @param {Color} $context-menu-background-active [null] - Sets the background color for active context menus. +/// @param {Color} $context-menu-color [null] - Sets the text color for context menus. +/// @param {Color} $context-menu-color-active [null] - Sets the text color for active context menus. +/// @param {Color} $dock-background [null] - Sets the background color of the dock manager. +/// @param {Color} $dock-text [null] - Sets the text color of the dock manager. +/// @param {Color} $floating-pane-border-color [null] - Sets the border color for floating panes. +/// @param {Color} $flyout-shadow-color [null] - Sets the flyout shadow color. +/// @param {Color} $joystick-background [null] - Sets the background color of the joystick. +/// @param {Color} $joystick-background-active [null] - Sets the background color of the joysticks. +/// @param {Color} $joystick-border-color [null] - Sets the border color of the joystick. +/// @param {Color} $joystick-icon-color [null] - Sets the color for the joystick icons. +/// @param {Color} $joystick-icon-color-active [null] - Sets the color of the active joystick icons. +/// @param {Color} $pane-content-background [null] - Sets the background color of the content panes. +/// @param {Color} $pane-content-text [null] - Sets the text color of the content panes. +/// @param {Color} $pane-header-background [null] - Sets the background color for pane headers. +/// @param {Color} $pane-header-text [null] - Sets the text color for pane headers. +/// @param {Color} $pinned-header-background [null] - Sets the background colors of pinned headers. +/// @param {Color} $pinned-header-text [null] - Sets the text colors of pinned headers. +/// @param {Color} $background-color [null] - Sets the base dock manager color as well as the pane headers and tabs background colors. +/// @param {Color} $splitter-background [null] - Sets the background color for the splitters. +/// @param {Color} $splitter-handle [null] - Sets the background color for the splitter handles. +/// @param {Color} $tab-background [null] - Sets the background color for tabs. +/// @param {Color} $tab-background-active [null] - Sets the background color for active tabs. +/// @param {Color} $tab-border-color [null] - Sets the border color for tabs. +/// @param {Color} $tab-border-color-active [null] - Sets the border color for active tabs. +/// @param {Color} $tab-text [null] - Sets the text color for tabs. +/// @param {Color} $tab-text-active [null] - Sets the text color for active tabs. +/// @param {Color} $text-color [null] - Sets the text color for most elements in the dock manager. Used as the default joystick icon color. +/// @requires $default-palette +/// @requires $light-schema +/// @requires apply-palette +/// @requires text-contrast +/// @requires extend +/// +/// @example scss Change the background and icon colors in icon dock-managers +/// $my-dock-manager-theme: igc-dock-manager-theme(); +/// // Pass the theme to the igc-dock-manager component mixin +/// @include igx-css-vars($my-dock-manager-theme); +@function igc-dock-manager-theme( + $palette: $default-palette, + $schema: $light-schema, + $rest... +) { + $name: 'igc-dock-manager'; + $dock-manager-schema: (); + + @if map-has-key($schema, $name) { + $dock-manager-schema: map-get($schema, $name); + } @else { + $dock-manager-schema: $schema; + } + + $theme: apply-palette($dock-manager-schema, $palette); + + @return extend($theme, (name: 'igc'), keywords($rest)); +} + + +/// Adds typography styles for the dock manager component. +/// @access private +/// @group typography +/// @requires {mixin} igx-type-style +@mixin igx-dock-manager-typography() { + $scope: if(is-root(), ':root', '&'); + + #{$scope} { + @include css-vars-from-theme((font-family: inherit), 'igc'); + } +} + diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid-summary/_grid-summary-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid-summary/_grid-summary-theme.scss index 8fdec7829ba..a012be8ae56 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid-summary/_grid-summary-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid-summary/_grid-summary-theme.scss @@ -123,7 +123,6 @@ &::after { position: absolute; - content: ''; top: 0; bottom: 0; left: 0; diff --git a/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss index b568cbef288..2e6218d6bff 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss @@ -961,12 +961,22 @@ } %form-group-textarea { - min-height: rem(82px, map-get($base-scale-size, 'comfortable')); /* 3 lines * 22px + 8px bottom padding + 8px top padding */ - margin-#{$right}: -#{rem(16px, map-get($base-scale-size, 'comfortable'))}; /* this fixes resizing in chrome !?!? */ - line-height: normal !important; /* resets typography styles */ + // 3 lines * 22px + 8px bottom padding + 8px top padding + min-height: rem(82px, map-get($base-scale-size, 'comfortable')); + + // this fixes resizing in chrome !?!? + margin-#{$right}: -#{rem(16px, map-get($base-scale-size, 'comfortable'))}; + height: auto; resize: vertical; overflow: hidden; + + // resets typography styles + line-height: normal !important; + + &:not([type='*']) { + line-height: normal !important; /* resets typography styles */ + } } %form-group-textarea--cosy { diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/_index.scss b/projects/igniteui-angular/src/lib/core/styles/themes/_index.scss index 7991f398f49..cb7cdc5508a 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/_index.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/_index.scss @@ -36,6 +36,7 @@ @import '../components/date-range-picker/date-range-picker-theme'; @import '../components/dialog/dialog-theme'; @import '../components/divider/divider-theme'; +@import '../components/dock-manager/dock-manager-theme'; @import '../components/drop-down/drop-down-theme'; @import '../components/expansion-panel/expansion-panel-theme'; @import '../components/grid/grid-theme'; @@ -360,6 +361,10 @@ @include igx-divider(igx-divider-theme($palette, $schema)); } + @if not(index($exclude, 'igc-dock-manager')) { + @include igx-css-vars(igc-dock-manager-theme($palette, $schema)); + } + @if not(index($exclude, 'igx-drop-down')) { @include igx-drop-down(igx-drop-down-theme( $palette, diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_dock-manager.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_dock-manager.scss new file mode 100644 index 00000000000..0e781709581 --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_dock-manager.scss @@ -0,0 +1,244 @@ +@import '../light/dock-manager'; +//// +/// @group schemas +/// @access private +/// @author Simeon Simeonoff +//// + +/// Generates a dark dock-manager schema. +/// @type {Map} +/// @requires {function} extend +/// @prop {Color} accent-color [igx-color: ('grays', 400), hexrgba: #000] - Sets the pinned header background color, the joystick background and border colors, as well as the context menu background color. +/// @prop {Color} background-color [igx-color: ('grays', 200), hexrgba: #000] - Sets the base dock manager color as well as the pane headers and tabs background colors. +/// @prop {Color} border-color [igx-color: 'surface'] - Sets the global border color in the dock manager. Also sets the pane content background and the context menu active background colors. +/// @prop {Color} button-text [igx-contrast-color: 'surface'] - Sets the button text color. +/// @prop {Color} floating-pane-border-color [igx-color: ('grays', 100), hexrgba: #000] - Sets the border color for floating panes. +/// @prop {Color} flyout-shadow-color [rgba(0, 0, 0, .38)] - Sets the border color for floating panes. +/// @prop {Color} joystick-background-active [igx-color: 'surface'] - Sets the background color of the joysticks. +/// @prop {Color} joystick-border-color [igx-color: ('grays', 400), hexrgba: #000] - Sets the border color of the joystick. +/// @prop {Color} joystick-icon-color [igx-contrast-color: 'surface'] - Sets the color for the joystick icons. +/// @prop {Color} pane-content-background [igx-color: 'surface'] - Sets the background color of the content panes. +/// @prop {Color} splitter-background [igx-clor: ('grays', 100), hexrgba: #000] - Sets the background color for the splitters. +/// @prop {Color} tab-background-active [igx-color: 'surface'] - Sets the background color for active tabs. +/// @requires $_light-dock-manager +$_dark-dock-manager: extend( + $_light-dock-manager, ( + accent-color: ( + igx-color: ('grays', 400), + hexrgba: #000, + ), + background-color: ( + igx-color: ('grays', 200), + hexrgba: #000, + ), + border-color: ( + igx-color: 'surface', + ), + button-text: ( + igx-contrast-color: 'surface', + ), + floating-pane-border-color: ( + igx-color: ('grays', 100), + hexrgba: #000, + ), + flyout-shadow-color: rgba(0, 0, 0, .38), + joystick-background-active: ( + igx-color: 'surface', + ), + joystick-border-color: ( + igx-color: ('grays', 400), + hexrgba: #000, + ), + joystick-icon-color: ( + igx-contrast-color: 'surface', + ), + pane-content-background: ( + igx-color: 'surface', + ), + splitter-background: ( + igx-color: ('grays', 100), + hexrgba: #000, + ), + tab-background-active: ( + igx-color: 'surface', + ), + ) +); + +/// Generates a dark fluent dock-manager schema. +/// @type {Map} +/// @prop {Color} accent-color [igx-color: 'surface', rgba: .5, hexrgba: #000] - Sets the pinned header background color, the joystick background and border colors, as well as the context menu background color. +/// @prop {Color} background-color [igx-color: 'surface', rgba: .85, hexrgba: #000] - Sets the base dock manager color as well as the pane headers and tabs background colors. +/// @prop {Color} button-text [igx-contrast-color: 'surface'] - Sets the button text color. +/// @prop {Color} context-menu-background-active [igx-color: 'surface', rgba: .5, hexrgba: #000] - Sets the background color for active context menus. +/// @prop {Color} context-menu-color-active [igx-contrast-color: 'surface'] - Sets the text color for active context menus. +/// @prop {Color} context-menu-background [igx-color: ('grays', 400), hexrgba: #000] - Sets the background color for context menus. +/// @prop {Color} context-menu-color [igx-contrast-color: 'surface'] - Sets the text color for context menus. +/// @prop {Color} dock-background [igx-color: 'surface', rgba: .85, hexrgba: #000] - Sets the background color of the dock manager. +/// @prop {Color} floating-pane-border-color [transparent] - Sets the border color for floating panes. +/// @prop {Color} joystick-background-active [igx-color: 'surface', rgba: .5, hexrgba: #000] - Sets the background color of the joysticks. +/// @prop {Color} joystick-background [igx-color: ('grays', 400), hexrgba: #000] - Sets the background color of the joystick. +/// @prop {Color} joystick-border-color [transparent] - Sets the border color of the joystick. +/// @prop {Color} joystick-icon-color [igx-contrast-color: 'surface'] - Sets the color for the joystick icons. +/// @prop {Color} joystick-icon-color-active [igx-contrast-color: 'surface'] - Sets the color of the active joystick icons. +/// @prop {Color} pane-header-background [igx-color: 'surface', rgba: .33, hexrgba: #000] - Sets the background color for pane headers. +/// @prop {Color} pinned-header-background [igx-color: 'surface', rgba: .85, hexrgba: #000] - Sets the background colors of pinned headers. +/// @prop {Color} splitter-background [igx-color: 'surface', rgba: .7, hexrgba: #000] - Sets the background color for the splitters. +/// @prop {Color} splitter-handle [igx-color: 'surface', rgba: .2, hexrgba: #000] - Sets the background color for the splitter handles. +/// @prop {Color} tab-text [igx-contrast-color: 'surface'] - Sets the text color for tabs. +/// @prop {Color} text-color [igx-contrast-color: 'surface'] - Sets the text color for most elements in the dock manager. Used as the default joystick icon color. +/// @requires {function} extend +/// @requires $_fluent-dock-manager +$_dark-fluent-dock-manager: extend( + $_fluent-dock-manager, ( + accent-color: ( + igx-color: 'surface', + rgba: .5, + hexrgba: #000, + ), + background-color: ( + igx-color: 'surface', + rgba: .85, + hexrgba: #000, + ), + button-text: ( + igx-contrast-color: 'surface', + ), + context-menu-background-active: ( + igx-color: 'surface', + rgba: .5, + hexrgba: #000, + ), + context-menu-color-active: ( + igx-contrast-color: 'surface', + ), + context-menu-background: ( + igx-color: ('grays', 400), + hexrgba: #000, + ), + context-menu-color: ( + igx-contrast-color: 'surface', + ), + dock-background: ( + igx-color: 'surface', + rgba: .85, + hexrgba: #000, + ), + floating-pane-border-color: transparent, + joystick-background-active: ( + igx-color: 'surface', + rgba: .5, + hexrgba: #000, + ), + joystick-background: ( + igx-color: ('grays', 400), + hexrgba: #000, + ), + joystick-border-color: transparent, + joystick-icon-color: ( + igx-contrast-color: 'surface', + ), + joystick-icon-color-active: ( + igx-contrast-color: 'surface', + ), + pane-header-backround: ( + igx-color: 'surface', + rgba: .85, + hexrgba: #000, + ), + pinned-header-background: ( + igx-color: 'surface', + rgba: .85, + hexrgba: #000, + ), + splitter-background: ( + igx-color: 'surface', + rgba: .7, + hexrgba: #000, + ), + splitter-handle: ( + igx-color: 'surface', + rgba: .2, + hexrgba: #000, + ), + tab-text: ( + igx-contrast-color: 'surface', + ), + text-color: ( + igx-contrast-color: 'surface', + ), + ) +); + +/// Generates a dark bootstrap dock-manager schema. +/// @type {Map} +/// @prop {Color} accent-color [igx-color: 'surface', rgba: .33, hexrgba: ()] - Sets the pinned header background color, the joystick background and border colors, as well as the context menu background color. +/// @prop {Color} background-color [igx-color: 'surface', rgba: .33, hexrgba: ()] - Sets the base dock manager color as well as the pane headers and tabs background colors. +/// @prop {Color} context-menu-background [igx-color: 'surface', rgba: .66, hexrgba: ()] - Sets the background color for context menus. +/// @prop {Color} context-menu-color-active [igx-context-color: 'surface'] - Sets the text color for active context menus. +/// @prop {Color} dock-background [igx-color: 'surface', rgba: .33, hexrgba: ()] - Sets the background color of the dock manager. +/// @prop {Color} joystick-background [igx-color: 'surface', rgba: .66, hexrgba: ()] - Sets the background color of the joystick. +/// @prop {Color} joystick-icon-color-active [igx-context-color: 'surface'] - Sets the color of the active joystick icons. +/// @prop {Color} pane-header-background [igx-color: 'surface', rgba: .33, hexrgba: ()] - Sets the background color for pane headers. +/// @prop {Color} pinned-header-background [igx-color: 'surface', rgba: .33, hexrgba: ()] - Sets the background colors of pinned headers. +/// @prop {Color} splitter-background [igx-color: ('grays', 300), hexrgba: ()] - Sets the background color for the splitters. +/// @prop {Color} splitter-handle [igx-color: ('grays', 50), hexrgba: ()] - Sets the background color for the splitter handles. +/// @prop {Color} text-color [igx-contrast-color: 'surface'] - Sets the text color for most elements in the dock manager. Used as the default joystick icon color. +/// @requires {function} extend +/// @requires $_bootstrap-dock-manager +$_dark-bootstrap-dock-manager: extend( + $_bootstrap-dock-manager, ( + accent-color: ( + igx-color: 'surface', + rgba: .33, + hexrgba: #000, + ), + background-color: ( + igx-color: 'surface', + rgba: .33, + hexrgba: #000, + ), + context-menu-background: ( + igx-color: 'surface', + rgba: .66, + hexrgba: #000, + ), + context-menu-color-active: ( + igx-contrast-color: 'surface', + ), + dock-background: ( + igx-color: 'surface', + rgba: .33, + hexrgba: #000, + ), + joystick-background: ( + igx-color: 'surface', + rgba: .66, + hexrgba: #000, + ), + joystick-icon-color-active: ( + igx-contrast-color: 'surface', + ), + pane-header-background: ( + igx-color: 'surface', + rgba: .33, + hexrgba: #000, + ), + pinned-header-background: ( + igx-color: 'surface', + rgba: .33, + hexrgba: #000, + ), + splitter-background: ( + igx-color: ('grays', 300), + hexrgba: #000, + ), + splitter-handle: ( + igx-color: ('grays', 50), + hexrgba: #000, + ), + text-color: ( + igx-contrast-color: 'surface', + ), + ) +); diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_index.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_index.scss index 28a09a2b512..559dcf2c75b 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_index.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_index.scss @@ -23,6 +23,7 @@ @import './doughnut-chart'; @import './date-range-picker'; @import './divider'; +@import './dock-manager'; @import './drop-down'; @import './expansion-panel'; @import './gauge'; @@ -80,6 +81,7 @@ /// @property {Map} doughnut-chart [$_dark-doughnut-chart] /// @property {Map} igx-date-range-picker [$_dark-date-range-picker] /// @property {Map} igx-divider [$_dark-divider] +/// @property {Map} igc-dock-manager [$_dark-dock-manager] /// @property {Map} igx-drop-down [$_dark-drop-down] /// @property {Map} igx-expansion-panel [$_dark-expansion-panel] /// @property {Map} linear-gauge [$_dark-linear-gauge] @@ -137,6 +139,7 @@ $dark-schema: ( doughnut-chart: $_dark-doughnut-chart, igx-date-range: $_dark-date-range-picker, igx-divider: $_dark-divider, + igc-dock-manager: $_dark-dock-manager, igx-drop-down: $_dark-drop-down, igx-expansion-panel: $_dark-expansion-panel, linear-gauge: $_dark-linear-gauge, @@ -197,6 +200,7 @@ $dark-schema: ( /// @property {Map} doughnut-chart [$_dark-fluent-doughnut-chart] /// @property {map} igx-date-range-picker [$_dark-fluent-date-range-picker], /// @property {map} igx-divider [$_dark-fluent-divider], +/// @property {map} igc-dock-manager [$_dark-fluent-dock-manager], /// @property {map} igx-drop-down [$_dark-fluent-drop-down], /// @property {map} igx-expansion-panel [$_dark-fluent-expansion-panel], /// @property {Map} linear-gauge [$_dark-fluent-linear-gauge] @@ -254,6 +258,7 @@ $dark-fluent-schema: ( doughnut-chart: $_dark-fluent-doughnut-chart, igx-date-range: $_dark-fluent-date-range-picker, igx-divider: $_dark-fluent-divider, + igc-dock-manager: $_dark-fluent-dock-manager, igx-drop-down: $_dark-fluent-drop-down, igx-expansion-panel: $_dark-fluent-expansion-panel, linear-gauge: $_dark-fluent-linear-gauge, @@ -314,6 +319,7 @@ $dark-fluent-schema: ( /// @property {Map} doughnut-chart [$_dark-bootstrap-doughnut-chart] /// @property {map} igx-date-range-picker [$_dark-bootstrap-date-range-picker], /// @property {map} igx-divider [$_dark-bootstrap-divider], +/// @property {map} igc-dock-manager [$_dark-bootstrap-dock-manager], /// @property {map} igx-drop-down [$_dark-bootstrap-drop-down], /// @property {map} igx-expansion-panel [$_dark-bootstrap-expansion-panel], /// @property {Map} linear-gauge [$_dark-bootstrap-linear-gauge] @@ -371,6 +377,7 @@ $dark-bootstrap-schema: ( doughnut-chart: $_dark-bootstrap-doughnut-chart, igx-date-range: $_dark-bootstrap-date-range-picker, igx-divider: $_dark-bootstrap-divider, + igc-dock-manager: $_dark-bootstrap-dock-manager, igx-drop-down: $_dark-bootstrap-drop-down, igx-expansion-panel: $_dark-bootstrap-expansion-panel, linear-gauge: $_dark-bootstrap-linear-gauge, diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_dock-manager.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_dock-manager.scss new file mode 100644 index 00000000000..73316e659a0 --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_dock-manager.scss @@ -0,0 +1,316 @@ +//// +/// @group schemas +/// @access private +/// @author Simeon Simeonoff +//// + +/// Generates a light dock-manager schema. +/// @type {Map} +/// @prop {Color} accent-color [null] - Sets the pinned header background color, the joystick background and border colors, as well as the context menu background color. +/// @prop {Color} active-color [igx-color: 'primary'] - Sets the active text and border colors for tabs, panes, and menus. +/// @prop {Color} background-color [null] - Sets the base dock manager color as well as the pane headers and tabs background colors. +/// @prop {Color} border-color [igx-color: ('primary', 100), rgba: .1, hexrgba: ()] - Sets the global border color in the dock manager. Also sets the pane content background and the context menu active background colors. +/// @prop {Color} button-text [igx-color: ('grays', 800)] - Sets the button text color. +/// @prop {Color} context-menu-background-active [null] - Sets the background color for active context menus. +/// @prop {Color} context-menu-background [null] - Sets the background color for context menus. +/// @prop {Color} context-menu-color-active [igx-contrast-color: 'surface'] - Sets the text color for active context menus. +/// @prop {Color} context-menu-color [null] - Sets the text color for context menus. +/// @prop {Color} dock-background [null] - Sets the background color of the dock manager. +/// @prop {Color} dock-text [null] - Sets the text color of the dock manager. +/// @prop {Color} floating-pane-border-color [igx-color: 'surface'] - Sets the border color for floating panes. +/// @prop {Color} flyout-shadow-color [rgba(0, 0, 0, .08)] - Sets the flyout shadow color. +/// @prop {Color} joystick-background [null] - Sets the background color of the joystick. +/// @prop {Color} joystick-background-active [null] - Sets the background color of the joysticks. +/// @prop {Color} joystick-border-color [igx-color: ('grays', 300)] - Sets the border color of the joystick. +/// @prop {Color} joystick-icon-color [igx-color: ('grays', 600)] - Sets the color for the joystick icons. +/// @prop {Color} joystick-icon-color-active [igx-contrast-color: 'surface'] - Sets the color of the active joystick icons. +/// @prop {Color} pane-content-background [igx-color: ('primary', 100), rgba: .1, hexrgba: ()] - Sets the background color of the content panes. +/// @prop {Color} pane-content-text [null] - Sets the text color of the content panes. +/// @prop {Color} pane-header-background [null] - Sets the background color for pane headers. +/// @prop {Color} pane-header-text [null] - Sets the text color for pane headers. +/// @prop {Color} pinned-header-background [null] - Sets the background colors of pinned headers. +/// @prop {Color} pinned-header-text [null] - Sets the text colors of pinned headers. +/// @prop {Color} splitter-background [igx-color: ('grays', 300)] - Sets the background color for the splitters. +/// @prop {Color} splitter-handle [null] - Sets the background color for the splitter handles. +/// @prop {Color} tab-background-active [igx-color: ('primary', 100), rgba: .1, hexrgba: ()] - Sets the background color for active tabs. +/// @prop {Color} tab-background [null] - Sets the background color for tabs. +/// @prop {Color} tab-border-color [null] - Sets the border color for tabs. +/// @prop {Color} tab-border-color-active [null] - Sets the border color for active tabs. +/// @prop {Color} tab-text [null] - Sets the text color for tabs. +/// @prop {Color} tab-text-active [null] - Sets the text color for active tabs. +/// @prop {Color} text-color [igx-color: ('grays', 800)] - Sets the text color for most elements in the dock manager. Used as the default joystick icon color. +$_light-dock-manager: ( + accent-color: null, + active-color: ( + igx-color: primary, + ), + background-color: null, + border-color: ( + igx-color: ('primary', 100), + rgba: .1, + hexrgba: (), + ), + button-text: ( + igx-color: ('grays', 800), + ), + context-menu-background-active: null, + context-menu-background: null, + context-menu-color-active: ( + igx-contrast-color: 'surface', + ), + context-menu-color: null, + dock-background: null, + dock-text: null, + floating-pane-border-color: ( + igx-color: 'surface', + ), + flyout-shadow-color: rgba(0, 0, 0, .08), + joystick-background-active: null, + joystick-background: null, + joystick-border-color: ( + igx-color: ('grays', 300), + ), + joystick-icon-color: ( + igx-color: ('grays', 600), + ), + joystick-icon-color-active: ( + igx-contrast-color: 'surface', + ), + pane-content-background: ( + igx-color: ('primary', 100), + rgba: .1, + hexrgba: (), + ), + pane-content-text: null, + pane-header-background: null, + pane-header-text: null, + pinned-header-background: null, + pinned-header-text: null, + splitter-background: ( + igx-color: ('grays', 300), + ), + splitter-handle: null, + tab-background-active: ( + igx-color: ('primary', 100), + rgba: .1, + hexrgba: (), + ), + tab-background: null, + tab-border-color-active: null, + tab-border-color: null, + tab-text-active: null, + tab-text: null, + text-color: ( + igx-color: ('grays', 800), + ), +); + +/// Generates a fluent dock-manager schema. +/// @type {Map} +/// @prop {Color} background-color [igx-color: ('grays', 50), hexrgba: ()] - Sets the base dock manager color as well as the pane headers and tabs background colors. +/// @prop {Color} border-color [igx-color: 'surface'] - Sets the global border color in the dock manager. Also sets the pane content background and the context menu active background colors. +/// @prop {Color} context-menu-background-active [igx-color: ('grays', 200), hexrgba: ()] - Sets the background color for active context menus. +/// @prop {Color} context-menu-color-active [igx-color: ('grays', 800)] - Sets the text color for active context menus. +/// @prop {Color} context-menu-color [igx-color: ('grays', 800)] - Sets the text color for active context menus. +/// @prop {Color} dock-background [igx-color: ('grays', 50), hexrgba: ()] - Sets the background color of the dock manager. +/// @prop {Color} floating-pane-border-color [igx-color: ('grays', 400), hexrgba: ()] - Sets the border color for floating panes. +/// @prop {Color} joystick-background-active [igx-color: ('grays', 200), hexrgba: ()] - Sets the background color of the joysticks. +/// @prop {Color} joystick-border-color [igx-color: ('grays', 400), hexrgba: ()] - Sets the border color of the joystick. +/// @prop {Color} joystick-icon-color [igx-color: ('grays', 800)] - Sets the color for the joystick icons. +/// @prop {Color} joystick-icon-color-active [igx-color: ('grays', 800)] - Sets the color of the active joystick icons. +/// @prop {Color} pane-content-background [igx-color: 'surface'] - Sets the background color of the content panes. +/// @prop {Color} pane-header-background [igx-color: ('grays', 50), hexrgba: ()] - Sets the background color for pane headers. +/// @prop {Color} splitter-background [igx-color: ('grays', 100), hexrgba: ()] - Sets the background color for the splitters. +/// @prop {Color} splitter-handle [igx-color: ('grays', 400), rgba: .2, hexrgba: ()] - Sets the background color for the splitter handles. +/// @prop {Color} tab-background-active [igx-color: 'surface'] - Sets the background color for active tabs. +/// @prop {Color} tab-background [igx-color: 'surface'] - Sets the background color for tabs. +/// @prop {Color} tab-border-color [igx-color: 'surface'] - Sets the border color for tabs. +/// @prop {Color} tab-border-color-active [igx-color: 'surface'] - Sets the border color for active tabs. +/// @prop {Color} tab-text-active [igx-color: 'surface'] - Sets the text color for active tabs. +/// @prop {Color} tab-text [igx-color: ('grays', 700)] - Sets the text color for tabs. +/// @requires {function} extend +/// @requires $_light-dock-manager +$_fluent-dock-manager: extend( + $_light-dock-manager, + ( + background-color: ( + igx-color: ('grays', 50), + hexrgba: (), + ), + border-color: ( + igx-color: 'surface', + ), + context-menu-background-active: ( + igx-color: ('grays', 200), + hexrgba: (), + ), + context-menu-color-active: ( + igx-color: ('grays', 800), + ), + context-menu-color: ( + igx-color: ('grays', 800), + ), + dock-background: ( + igx-color: ('grays', 50), + hexrgba: (), + ), + floating-pane-border-color: ( + igx-color: ('grays', 400), + hexrgba: (), + ), + joystick-background-active: ( + igx-color: ('grays', 200), + hexrgba: (), + ), + joystick-border-color: ( + igx-color: ('grays', 400), + hexrgba: (), + ), + joystick-icon-color: ( + igx-color: ('grays', 800), + ), + joystick-icon-color-active: ( + igx-color: ('grays', 800), + ), + pane-content-background: ( + igx-color: 'surface', + ), + pane-header-background: ( + igx-color: ('grays', 50), + hexrgba: (), + ), + pinned-header-background: ( + igx-color: ('grays', 50), + hexrgba: (), + ), + splitter-background: ( + igx-color: ('grays', 100), + hexrgba: (), + ), + splitter-handle: ( + igx-color: ('grays', 400), + rgba: .2, + hexrgba: (), + ), + tab-background-active: ( + igx-color: 'surface', + ), + tab-background: ( + igx-color: 'surface', + ), + tab-border-color-active: ( + igx-color: 'surface', + ), + tab-border-color: ( + igx-color: 'surface', + ), + tab-text-active: ( + igx-color: 'primary', + ), + tab-text: ( + igx-color: ('grays', 700) + ), + ) +); + +/// Generates a bootstrap dock-manager schema. +/// @type {Map} +/// @prop {Color} background-color [igx-color: ('grays', 100), hexrgba: ()] - Sets the base dock manager color as well as the pane headers and tabs background colors. +/// @prop {Color} border-color [igx-color: 'surface'] - Sets the global border color in the dock manager. Also sets the pane content background and the context menu active background colors. +/// @prop {Color} button-text [igx-color: 'primary'] - Sets the button text color. +/// @prop {Color} context-menu-background-active [igx-color: 'primary'] - Sets the background color for active context menus. +/// @prop {Color} context-menu-color-active [igx-color: 'surface'] - Sets the text color for active context menus. +/// @prop {Color} context-menu-color [igx-color: 'primary'] - Sets the text color for context menus. +/// @prop {Color} dock-background [igx-color: ('grays', 100), hexrgba: ()] - Sets the background color of the dock manager. +/// @prop {Color} floating-pane-border-color [igx-color: ('grays', 300)] - Sets the border color for floating panes. +/// @prop {Color} joystick-background-active [igx-color: 'primary'] - Sets the background color of the joysticks. +/// @prop {Color} joystick-icon-color [igx-color: 'primary'] - Sets the color for the joystick icons. +/// @prop {Color} pane-content-background [igx-color: 'surface'] - Sets the background color of the content panes. +/// @prop {Color} pane-header-background [igx-color: ('grays', 100), hexrgba: ()] - Sets the background color for pane headers. +/// @prop {Color} pinned-header-background [igx-color: ('grays', 100), hexrgba: ()] - Sets the background colors of pinned headers. +/// @prop {Color} splitter-background [igx-color: ('grays', 200), hexrgba: ()] - Sets the background color for the splitters. +/// @prop {Color} splitter-handle [igx-color: ('grays', 400), rgba: .2, hexrgba: ()] - Sets the background color for the splitter handles. +/// @prop {Color} tab-background-active [igx-color: 'surface'] - Sets the background color for active tabs. +/// @prop {Color} tab-background [igx-color: 'surface'] - Sets the background color for tabs. +/// @prop {Color} tab-border-color-active [igx-color: ('grays', 300)] - Sets the border color for active tabs. +/// @prop {Color} tab-border-color [igx-color: 'surface'] - Sets the border color for tabs. +/// @prop {Color} tab-text-active [igx-color: ('grays', 800)] - Sets the text color for active tabs. +/// @prop {Color} tab-text [igx-color: 'primary'] - Sets the text color for tabs. +/// @requires {function} extend +/// @requires $_light-dock-manager +$_bootstrap-dock-manager: extend( + $_light-dock-manager, + ( + background-color: ( + igx-color: ('grays', 100), + hexrgba: (), + ), + border-color: ( + igx-color: 'surface', + ), + button-text: ( + igx-color: 'primary', + ), + context-menu-background-active: ( + igx-color: 'primary', + ), + context-menu-color-active: ( + igx-color: 'surface', + ), + context-menu-color: ( + igx-color: 'primary', + ), + dock-background: ( + igx-color: ('grays', 100), + hexrgba: (), + ), + floating-pane-border-color: ( + igx-color: ('grays', 300), + ), + joystick-background-active: ( + igx-color: 'primary', + ), + joystick-icon-color: ( + igx-color: 'primary', + ), + pane-content-background: ( + igx-color: 'surface', + ), + pane-header-background: ( + igx-color: ('grays', 100), + hexrgba: (), + ), + pinned-header-background: ( + igx-color: ('grays', 100), + hexrgba: (), + ), + splitter-background: ( + igx-color: ('grays', 200), + hexrgba: (), + ), + splitter-handle: ( + igx-color: ('grays', 400), + rgba: .2, + hexrgba: (), + ), + tab-background-active: ( + igx-color: 'surface', + ), + tab-background: ( + igx-color: 'surface', + ), + tab-border-color-active: ( + igx-color: ('grays', 300) + ), + tab-border-color: ( + igx-color: 'surface', + ), + tab-text-active: ( + igx-color: ('grays', 800) + ), + tab-text: ( + igx-color: 'primary', + ), + ) +); + diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_index.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_index.scss index 3d854322316..d23e39a4db7 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_index.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_index.scss @@ -25,6 +25,7 @@ @import './funnel-chart'; @import './date-range-picker'; @import './divider'; +@import './dock-manager'; @import './drop-down'; @import './expansion-panel'; @import './gauge'; @@ -83,6 +84,7 @@ /// @property {Map} igx-dialog [$_light-dialog] /// @property {Map} igx-date-range-picker [$_light-date-range-picker ] /// @property {Map} igx-divider [$_light-divider] +/// @property {Map} igc-dock-manager [$_light-dock-manager] /// @property {Map} igx-drop-down [$_light-drop-down] /// @property {Map} igx-expansion-panel [$_light-expansion-panel] /// @property {Map} linear-gauge [$_light-linear-gauge] @@ -140,6 +142,7 @@ $light-schema: ( igx-dialog: $_light-dialog, igx-date-range: $_light-date-range-picker, igx-divider: $_light-divider, + igc-dock-manager: $_light-dock-manager, igx-drop-down: $_light-drop-down, igx-expansion-panel: $_light-expansion-panel, linear-gauge: $_light-linear-gauge, @@ -200,6 +203,7 @@ $light-fluent-schema: ( funnel-chart: $_fluent-funnel-chart, igx-date-range: $_fluent-date-range-picker, igx-divider: $_fluent-divider, + igc-dock-manager: $_fluent-dock-manager, igx-drop-down: $_fluent-drop-down, igx-expansion-panel: $_fluent-expansion-panel, linear-gauge: $_fluent-linear-gauge, @@ -260,6 +264,7 @@ $light-bootstrap-schema: ( funnel-chart: $_bootstrap-funnel-chart, igx-date-range: $_bootstrap-date-range-picker, igx-divider: $_bootstrap-divider, + igc-dock-manager: $_bootstrap-dock-manager, igx-drop-down: $_bootstrap-drop-down, igx-expansion-panel: $_bootstrap-expansion-panel, linear-gauge: $_bootstrap-linear-gauge, diff --git a/projects/igniteui-angular/src/lib/core/styles/typography/_typography.scss b/projects/igniteui-angular/src/lib/core/styles/typography/_typography.scss index 1288c68c6c5..b81d1e95c52 100644 --- a/projects/igniteui-angular/src/lib/core/styles/typography/_typography.scss +++ b/projects/igniteui-angular/src/lib/core/styles/typography/_typography.scss @@ -9,6 +9,7 @@ @import '../components/button/button-theme'; @import '../components/calendar/calendar-theme'; @import '../components/card/card-theme'; +@import '../components/charts/category-chart-theme'; @import '../components/charts/data-chart-theme'; @import '../components/charts/financial-chart-theme'; @import '../components/charts/funnel-chart-theme'; @@ -20,6 +21,7 @@ @import '../components/column-hiding/column-hiding-theme'; @import '../components/date-range-picker/date-range-picker-theme'; @import '../components/dialog/dialog-theme'; +@import '../components/dock-manager/dock-manager-theme'; @import '../components/drop-down/drop-down-theme'; @import '../components/expansion-panel/expansion-panel-theme'; @import '../components/grid/excel-filtering-theme'; @@ -45,48 +47,6 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - $this: bem--selector-to-string(&); - $selector: bem--extract-first-selector($this); - - // Maps type scale typographic categories - // to native elements. - $category-element-map: ( - h1: 'h1', - h2: 'h2', - h3: 'h3', - h4: 'h4', - h5: 'h5', - h6: 'h6', - body-1: 'p' - ); - - @each $category, $type-style in $type-scale { - // Get the native element that uses typographic styles directly - // as mapped in the $category-element-map - $e: map-get($category-element-map, $category); - - // Create a placeholder selector with styles for each - // typographic style to be able to easily extend it - // elsewhere. - %#{$category} { - @include igx-type-style($type-scale, $category); - } - - // Add native element typographic styles. - @if $e != null { - #{$e} { - @extend %#{$category}; - } - } - - // Add class selector typographic styles. - @if ($selector == '.igx-typography') { - @include e(#{$category}) { - @extend %#{$category}; - } - } - } - // Call the individual component styles with the type scale @include _excel-filtering-typography($type-scale); @include igx-banner-typography($type-scale); @@ -94,12 +54,14 @@ @include igx-button-typography($type-scale); @include igx-calendar-typography($type-scale); @include igx-card-typography($type-scale); + @include igx-category-chart-typography($type-scale); @include igx-checkbox-typography($type-scale); @include igx-chip-typography($type-scale); @include igx-column-hiding-typography($type-scale); @include igx-data-chart-typography($type-scale); @include igx-date-range-typography($type-scale); @include igx-dialog-typography($type-scale); + @include igx-dock-manager-typography(); @include igx-drop-down-typography($type-scale); @include igx-expansion-panel-typography($type-scale); @include igx-financial-chart-typography($type-scale); @@ -138,6 +100,44 @@ // and all typographic elements. @include b(igx-typography) { @include _igx-typography-styles($font-family, $type-scale); + + // Maps type scale typographic categories + // to native elements. + $category-element-map: ( + h1: 'h1', + h2: 'h2', + h3: 'h3', + h4: 'h4', + h5: 'h5', + h6: 'h6', + body-1: 'p' + ); + + @each $category, $type-style in $type-scale { + // Get the native element that uses typographic styles directly + // as mapped in the $category-element-map + $e: map-get($category-element-map, $category); + + // Create a placeholder selector with styles for each + // typographic style to be able to easily extend it + // elsewhere. + %#{$category} { + @include igx-type-style($type-scale, $category); + } + + // Add native element typographic styles. + @if $e != null { + // stylelint-disable-next-line max-nesting-depth + #{$e} { + @extend %#{$category}; + } + } + + // Add class selector typographic styles. + @include e(#{$category}) { + @extend %#{$category}; + } + } } } @else { @include _igx-typography-styles($font-family, $type-scale); diff --git a/projects/igniteui-angular/src/lib/core/utils.ts b/projects/igniteui-angular/src/lib/core/utils.ts index 9166378170e..8e8d6389784 100644 --- a/projects/igniteui-angular/src/lib/core/utils.ts +++ b/projects/igniteui-angular/src/lib/core/utils.ts @@ -2,6 +2,7 @@ import { Injectable, PLATFORM_ID, Inject } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { Observable } from 'rxjs'; import ResizeObserver from 'resize-observer-polyfill'; +import { setImmediate } from 'core-js-pure'; /** * @hidden @@ -383,3 +384,19 @@ export function compareMaps(map1: Map, map2: Map): boolean { } return match; } + +export function yieldingLoop(count: number, chunkSize: number, callback: (index: number) => void, done: () => void) { + let i = 0; + const chunk = () => { + const end = Math.min(i + chunkSize, count); + for ( ; i < end; ++i) { + callback(i); + } + if (i < count) { + setImmediate(chunk); + } else { + done(); + } + }; + chunk(); +} diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html index 673dc7b3c62..26c3e4f51e2 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html @@ -10,6 +10,7 @@ [value]="displayData || ''" [disabled]="disabled" (blur)="onBlur($event)" + [tabindex]='editorTabIndex' readonly /> @@ -36,6 +37,7 @@ (wheel)="onWheel($event)" (input)="onInput($event)" (focus)="onFocus()" + [tabindex]='editorTabIndex' /> clear diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts index ab724154379..45955cebf4e 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts @@ -277,6 +277,13 @@ describe('IgxDatePicker', () => { expect(input).toEqual(document.activeElement); })); + it('should allow setting editorTabIndex', () => { + fixture.componentInstance.datePicker.editorTabIndex = 3; + fixture.detectChanges(); + const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement; + expect(input.tabIndex).toBe(3); + }); + }); describe('DatePicker with passed date', () => { diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts index 6a0fa20e661..b2b950d5366 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts @@ -177,6 +177,15 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor */ @Input() public locale: 'en'; + /** + * Gets/Sets the default template editor's tabindex. + * @example + * ```html + * + * ``` + */ + @Input() public editorTabIndex: number; + /** * Gets/Sets on which day the week starts. * @example @@ -783,8 +792,8 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor this._modalOverlaySettings = { closeOnOutsideClick: true, - closeOnEsc: true, modal: true, + closeOnEscape: true, outlet: this.outlet }; diff --git a/projects/igniteui-angular/src/lib/dialog/dialog.component.ts b/projects/igniteui-angular/src/lib/dialog/dialog.component.ts index 2e295ca9ce7..0082152dd6b 100644 --- a/projects/igniteui-angular/src/lib/dialog/dialog.component.ts +++ b/projects/igniteui-angular/src/lib/dialog/dialog.component.ts @@ -73,23 +73,36 @@ export class IgxDialogComponent implements IToggleView, OnInit, OnDestroy, After @Input() public id = `igx-dialog-${DIALOG_ID++}`; + /** + * Controls whether the dialog should be shown as modal. Defaults to `true` + * ```html + * + * ``` + */ @Input() - get isModal() { + public get isModal() { return this._isModal; } - set isModal(val: boolean) { + public set isModal(val: boolean) { this._overlayDefaultSettings.modal = val; this._isModal = val; } - get closeOnEscapeKey() { - return this._closeOnEscapeKey; + /** + * Controls whether the dialog should close when `Esc` key is pressed. Defaults to `true` + * ```html + * + * ``` + */ + @Input() + public get closeOnEscape() { + return this._closeOnEscape; } - set closeOnEscapeKey(val: boolean) { - this._overlayDefaultSettings.closeOnEsc = val; - this._closeOnEscapeKey = val; + public set closeOnEscape(val: boolean) { + this._overlayDefaultSettings.closeOnEscape = val; + this._closeOnEscape = val; } /** @@ -310,7 +323,7 @@ export class IgxDialogComponent implements IToggleView, OnInit, OnDestroy, After private _overlayDefaultSettings: OverlaySettings; private _closeOnOutsideSelect = false; - private _closeOnEscapeKey = true; + private _closeOnEscape = true; private _isModal = true; protected destroy$ = new Subject(); @@ -414,7 +427,7 @@ export class IgxDialogComponent implements IToggleView, OnInit, OnDestroy, After positionStrategy: new GlobalPositionStrategy(this._positionSettings), scrollStrategy: new NoOpScrollStrategy(), modal: this.isModal, - closeOnEsc: this._closeOnEscapeKey, + closeOnEscape: this._closeOnEscape, closeOnOutsideClick: this.closeOnOutsideSelect }; } diff --git a/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.spec.ts index 35365c58662..33744066b07 100644 --- a/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.spec.ts @@ -475,7 +475,6 @@ describe('IgxToggle', () => { positionStrategy: jasmine.any(ConnectedPositioningStrategy) as any, closeOnOutsideClick: true, modal: false, - closeOnEsc: true, scrollStrategy: jasmine.any(AbsoluteScrollStrategy) as any, excludePositionTarget: true }; @@ -499,7 +498,6 @@ describe('IgxToggle', () => { positionStrategy: jasmine.any(ConnectedPositioningStrategy) as any, closeOnOutsideClick: true, modal: false, - closeOnEsc: true, scrollStrategy: jasmine.any(AbsoluteScrollStrategy) as any, excludePositionTarget: true }; @@ -535,7 +533,6 @@ describe('IgxToggle', () => { positionStrategy: jasmine.any(ConnectedPositioningStrategy) as any, closeOnOutsideClick: true, modal: false, - closeOnEsc: true, scrollStrategy: jasmine.any(AbsoluteScrollStrategy) as any, excludePositionTarget: true }; @@ -594,7 +591,6 @@ describe('IgxToggle', () => { positionStrategy: jasmine.any(ConnectedPositioningStrategy) as any, closeOnOutsideClick: true, modal: false, - closeOnEsc: true, scrollStrategy: jasmine.any(AbsoluteScrollStrategy) as any, outlet: jasmine.any(IgxOverlayOutletDirective) as any, excludePositionTarget: true diff --git a/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts b/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts index 9ae74bfd93b..1db48870e9e 100644 --- a/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts @@ -429,7 +429,6 @@ export class IgxToggleActionDirective implements OnInit { scrollStrategy: new AbsoluteScrollStrategy(), closeOnOutsideClick: true, modal: false, - closeOnEsc: true, excludePositionTarget: true }; } diff --git a/projects/igniteui-angular/src/lib/grids/cell.component.html b/projects/igniteui-angular/src/lib/grids/cell.component.html index 0aa8c34c8c0..e9a52a57075 100644 --- a/projects/igniteui-angular/src/lib/grids/cell.component.html +++ b/projects/igniteui-angular/src/lib/grids/cell.component.html @@ -6,7 +6,9 @@ [value]="formatter ? formatter(value) : column.dataType === 'number' ? (value | igxdecimal: grid.locale) : column.dataType === 'date' ? (value | igxdate: grid.locale) : value" [row]="rowData" [column]="this.column.field" [containerClass]="'igx-grid__td-text'" [metadata]="searchMetadata" class="igx-grid__td-text">{{ formatter ? formatter(value) : column.dataType === 'number' ? (value | igxdecimal: - grid.locale) : column.dataType === 'date' ? (value | igxdate: grid.locale) : value }}
+ grid.locale) : column.dataType === 'date' ? (value | igxdate: grid.locale) : column.dataType === 'boolean' ? "" : value }} + {{value ? 'check' : 'close'}} +
diff --git a/projects/igniteui-angular/src/lib/grids/cell.component.ts b/projects/igniteui-angular/src/lib/grids/cell.component.ts index 39704bdec32..703ac67f3d1 100644 --- a/projects/igniteui-angular/src/lib/grids/cell.component.ts +++ b/projects/igniteui-angular/src/lib/grids/cell.component.ts @@ -285,6 +285,11 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { return `${this.row.gridID}_${this.rowIndex}_${ this.visibleColumnIndex}`; } + @HostBinding('attr.title') + public get title() { + return this.editMode || this.cellTemplate ? '' : this.value; + } + /** * Returns a reference to the nativeElement of the cell. * ```typescript diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 9d15266e02f..f456aaff4bb 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -1026,6 +1026,26 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements } } + /** + * Controls whether the summary row is visible when groupBy/parent row is collapsed. + * @example + * ```html + * + * ``` + * @remarks + * By default showSummaryOnCollapse is set to 'false' which means that the summary row is not visible + * when the groupBy/parent row is collapsed. + */ + @Input() + get showSummaryOnCollapse() { + return this._showSummaryOnCollapse; + } + + set showSummaryOnCollapse(value: boolean) { + this._showSummaryOnCollapse = value; + this.notifyChanges(); + } + /** * Gets/Sets the filtering strategy of the grid. * @example @@ -2625,6 +2645,7 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements private _summaryPosition = GridSummaryPosition.bottom; private _summaryCalculationMode = GridSummaryCalculationMode.rootAndChildLevels; + private _showSummaryOnCollapse = false; private _cellSelectionMode = GridSelectionMode.multiple; private _rowSelectionMode = GridSelectionMode.none; private _columnSelectionMode = GridSelectionMode.none; diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index e100c6ffea0..d60f23b85e6 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -90,6 +90,7 @@ class="igx-grid__td igx-grid__td--fw" [class.igx-grid__td--pinned]="col.pinned" [class.igx-grid__td--number]="col.dataType === 'number'" + [class.igx-grid__td--bool]="col.dataType === 'boolean'" [ngClass]="col.cellClasses | igxCellStyleClasses:rowData[col.field]:rowData:col.field:viewIndex" [ngStyle]="col.cellStyles | igxCellStyles:rowData[col.field]:rowData:col.field:viewIndex" [editMode]="col.editable && crudService.isInEditMode(index, col.index)" diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-summary.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-summary.spec.ts index 8fb65735aa6..8bcb989a43b 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-summary.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-summary.spec.ts @@ -1759,6 +1759,112 @@ describe('IgxGrid - Summaries #grid', () => { expect(GridSummaryFunctions.getAllVisibleSummariesLength(fix)).toEqual(5); }); + it('should show summaries when group row is collapsed', () => { + expect(grid.showSummaryOnCollapse).toBe(false); + expect(GridSummaryFunctions.getAllVisibleSummariesLength(fix)).toEqual(3); + const groupRows = grid.groupsRowList.toArray(); + grid.showSummaryOnCollapse = true; + fix.detectChanges(); + + groupRows[0].toggle(); + fix.detectChanges(); + + expect(groupRows[0].expanded).toBe(false); + expect(GridSummaryFunctions.getAllVisibleSummariesLength(fix)).toEqual(4); + let summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 1); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + + groupRows[0].toggle(); + fix.detectChanges(); + + expect(groupRows[0].expanded).toBe(true); + expect(GridSummaryFunctions.getAllVisibleSummariesLength(fix)).toEqual(3); + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 3); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + }); + + it('should be able to change showSummaryOnCollapse run time', () => { + expect(grid.showSummaryOnCollapse).toBe(false); + expect(GridSummaryFunctions.getAllVisibleSummariesLength(fix)).toEqual(3); + const groupRows = grid.groupsRowList.toArray(); + fix.detectChanges(); + + groupRows[0].toggle(); + fix.detectChanges(); + + expect(groupRows[0].expanded).toBe(false); + expect(GridSummaryFunctions.getAllVisibleSummariesLength(fix)).toEqual(3); + + grid.showSummaryOnCollapse = true; + fix.detectChanges(); + + expect(grid.showSummaryOnCollapse).toBe(true); + expect(GridSummaryFunctions.getAllVisibleSummariesLength(fix)).toEqual(4); + const summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 1); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + }); + + + it('should correctly position summary row when group row is collapsed', () => { + grid.showSummaryOnCollapse = true; + fix.detectChanges(); + grid.groupBy({ fieldName: 'OnPTO', dir: SortingDirection.Asc, ignoreCase: false }); + fix.detectChanges(); + + let summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 4); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 5); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + + const groupRows = grid.groupsRowList.toArray(); + groupRows[1].toggle(); + fix.detectChanges(); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 2); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 3); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + + grid.summaryPosition = 'top'; + fix.detectChanges(); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 1); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 3); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + + }); + it('should be able to enable/disable summaries at runtime', () => { grid.getColumnByName('Age').hasSummary = false; grid.getColumnByName('ParentID').hasSummary = false; diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html index 8fd95ac088a..cbd941da732 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -138,7 +138,7 @@ | gridSort:sortingExpressions:sortStrategy:id:pipeTrigger | gridGroupBy:groupingExpressions:groupingExpansionState:groupsExpanded:id:groupsRecords:pipeTrigger | gridPaging:page:perPage:id:pipeTrigger - | gridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:pipeTrigger:summaryPipeTrigger + | gridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:showSummaryOnCollapse:pipeTrigger:summaryPipeTrigger | gridDetails:hasDetails:expansionStates:pipeTrigger | gridRowPinning:id:false:pipeTrigger" let-rowIndex="index" [igxForScrollOrientation]="'vertical'" [igxForScrollContainer]='verticalScroll' diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.summary.pipe.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.summary.pipe.ts index 28d76e1c484..7e231436b6b 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.summary.pipe.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.summary.pipe.ts @@ -30,16 +30,16 @@ export class IgxGridSummaryPipe implements PipeTransform { hasSummary: boolean, summaryCalculationMode: GridSummaryCalculationMode, summaryPosition: GridSummaryPosition, - id: string, pipeTrigger: number, summaryPipeTrigger: number): any[] { + id: string, showSummary, pipeTrigger: number, summaryPipeTrigger: number): any[] { if (!collection.data || !hasSummary || summaryCalculationMode === GridSummaryCalculationMode.rootLevelOnly) { return collection.data; } - return this.addSummaryRows(id, collection, summaryPosition); + return this.addSummaryRows(id, collection, summaryPosition, showSummary); } - private addSummaryRows(gridId: string, collection: IGroupByResult, summaryPosition: GridSummaryPosition): any[] { + private addSummaryRows(gridId: string, collection: IGroupByResult, summaryPosition: GridSummaryPosition, showSummary): any[] { const recordsWithSummary = []; const lastChildMap = new Map(); const grid: IgxGridComponent = this.gridAPI.grid; @@ -73,6 +73,15 @@ export class IgxGridSummaryPipe implements PipeTransform { recordsWithSummary.push(record); } + if (summaryPosition === GridSummaryPosition.bottom && showSummary && (groupByRecord && !grid.isExpandedGroup(groupByRecord))) { + const records = this.removeDeletedRecord(grid, groupByRecord.records.slice()); + const summaries = grid.summaryService.calculateSummaries(recordId, records); + const summaryRecord: ISummaryRecord = { + summaries: summaries, + max: maxSummaryHeight + }; + recordsWithSummary.push(summaryRecord); + } if (summaryPosition === GridSummaryPosition.bottom && lastChildMap.has(recordId)) { const groupRecords = lastChildMap.get(recordId); @@ -89,7 +98,8 @@ export class IgxGridSummaryPipe implements PipeTransform { } } - if (groupByRecord === null || !grid.isExpandedGroup(groupByRecord)) { + const showSummaries = showSummary ? false : (groupByRecord && !grid.isExpandedGroup(groupByRecord)); + if (groupByRecord === null || showSummaries) { continue; } @@ -123,7 +133,6 @@ export class IgxGridSummaryPipe implements PipeTransform { groupRecords.unshift(groupByRecord); } } - return recordsWithSummary; } diff --git a/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.html b/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.html index 2f3ad576a5b..fa4d4f18150 100644 --- a/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.html +++ b/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.html @@ -10,8 +10,8 @@
- {{ translateSummary(summary) }} - + {{ translateSummary(summary) }} + {{ columnDatatype === 'number' ? (summary.summaryResult | igxdecimal: grid.locale) : columnDatatype === 'date' ? (summary.summaryResult | igxdate: grid.locale) : (summary.summaryResult) }}
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.html index 5b66eb7b820..cb96a001d9c 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.html @@ -6,7 +6,8 @@ [value]="formatter ? formatter(value) : column.dataType === 'number' ? (value | igxdecimal: grid.locale) : column.dataType === 'date' ? (value | igxdate: grid.locale) : value" [row]="rowData" [column]="this.column.field" [containerClass]="'igx-grid__td-text'" [metadata]="searchMetadata" class="igx-grid__td-text">{{ formatter ? formatter(value) : column.dataType === 'number' ? (value | igxdecimal: - grid.locale) : column.dataType === 'date' ? (value | igxdate: grid.locale) : value }} + grid.locale) : column.dataType === 'date' ? (value | igxdate: grid.locale) : column.dataType === 'boolean' ? "" : value }} + {{value ? 'check' : 'close'}} diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-summaries.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-summaries.spec.ts index 2ff11762564..5321c23ef1a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-summaries.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-summaries.spec.ts @@ -163,6 +163,99 @@ describe('IgxTreeGrid - Summaries #tGrid', () => { expect(GridSummaryFunctions.getAllVisibleSummariesRowIndexes(fix)).toEqual([6, 7, rootSummaryIndex]); }); + it('should be able to show/hide summaries for collapsed parent rows runtime', () => { + treeGrid.summaryCalculationMode = 'childLevelsOnly'; + fix.detectChanges(); + + let summaries = GridSummaryFunctions.getAllVisibleSummaries(fix); + expect(summaries.length).toBe(0); + + treeGrid.showSummaryOnCollapse = true; + fix.detectChanges(); + + summaries = GridSummaryFunctions.getAllVisibleSummaries(fix); + expect(summaries.length).toBe(4); + + treeGrid.showSummaryOnCollapse = false; + fix.detectChanges(); + + summaries = GridSummaryFunctions.getAllVisibleSummaries(fix); + expect(summaries.length).toBe(0); + }); + + it('should position correctly summary row for collapsed rows -- bottom position', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + treeGrid.summaryCalculationMode = 'childLevelsOnly'; + fix.detectChanges(); + + let summaries = GridSummaryFunctions.getAllVisibleSummaries(fix); + expect(summaries.length).toBe(4); + + treeGrid.showSummaryOnCollapse = true; + fix.detectChanges(); + + treeGrid.toggleRow(treeGrid.getRowByIndex(3).rowID); + fix.detectChanges(); + + summaries = GridSummaryFunctions.getAllVisibleSummaries(fix); + expect(summaries.length).toBe(4); + + let summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 4); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, + ['Count', 'Earliest', 'Latest'], ['2', 'Nov 11, 2009', 'Oct 17, 2015']); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 5); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, + ['Count', 'Earliest', 'Latest'], ['3', 'Jul 19, 2009', 'Sep 18, 2014']); + + treeGrid.summaryPosition = 'top'; + fix.detectChanges(); + + summaries = GridSummaryFunctions.getAllVisibleSummaries(fix); + expect(summaries.length).toBe(4); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 1); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, + ['Count', 'Earliest', 'Latest'], ['3', 'Jul 19, 2009', 'Sep 18, 2014']); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 5); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, + ['Count', 'Earliest', 'Latest'], ['2', 'Nov 11, 2009', 'Oct 17, 2015']); + }); + + it('should position correctly summary row for collapsed rows -- top position', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + treeGrid.summaryCalculationMode = 'childLevelsOnly'; + fix.detectChanges(); + + treeGrid.showSummaryOnCollapse = true; + fix.detectChanges(); + + let summaries = GridSummaryFunctions.getAllVisibleSummaries(fix); + expect(summaries.length).toBe(4); + + treeGrid.toggleRow(treeGrid.getRowByIndex(3).rowID); + fix.detectChanges(); + + treeGrid.summaryPosition = 'top'; + fix.detectChanges(); + + summaries = GridSummaryFunctions.getAllVisibleSummaries(fix); + expect(summaries.length).toBe(4); + + let summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 1); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, + ['Count', 'Earliest', 'Latest'], ['3', 'Jul 19, 2009', 'Sep 18, 2014']); + + summaryRow = GridSummaryFunctions.getSummaryRowByDataRowIndex(fix, 5); + GridSummaryFunctions.verifyColumnSummaries(summaryRow, 2, + ['Count', 'Earliest', 'Latest'], ['2', 'Nov 11, 2009', 'Oct 17, 2015']); + }); + it('should be able to enable/disable summaries at runtime', () => { treeGrid.expandAll(); fix.detectChanges(); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html index d093d3d920a..b28206ae718 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html @@ -104,7 +104,7 @@ | treeGridSorting:sortingExpressions:sortStrategy:id:pipeTrigger | treeGridFlattening:id:expansionDepth:expansionStates:pipeTrigger | treeGridPaging:page:perPage:id:pipeTrigger - | treeGridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:pipeTrigger:summaryPipeTrigger + | treeGridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:showSummaryOnCollapse:id:pipeTrigger:summaryPipeTrigger | gridRowPinning:id:false:pipeTrigger" let-rowIndex="index" [igxForScrollOrientation]="'vertical'" [igxForScrollContainer]='verticalScroll' [igxForContainerSize]='calcHeight' [igxForItemSize]="renderedRowHeight" #verticalScrollContainer> diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.summary.pipe.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.summary.pipe.ts index 07d76682b49..12f0ecdace7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.summary.pipe.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.summary.pipe.ts @@ -23,7 +23,7 @@ export class IgxTreeGridSummaryPipe implements PipeTransform { public transform(flatData: ITreeGridRecord[], hasSummary: boolean, summaryCalculationMode: GridSummaryCalculationMode, - summaryPosition: GridSummaryPosition, + summaryPosition: GridSummaryPosition, showSummaryOnCollapse: boolean, id: string, pipeTrigger: number, summaryPipeTrigger: number): any[] { const grid: IgxTreeGridComponent = this.gridAPI.grid; @@ -31,10 +31,11 @@ export class IgxTreeGridSummaryPipe implements PipeTransform { return flatData; } - return this.addSummaryRows(grid, flatData, summaryPosition); + return this.addSummaryRows(grid, flatData, summaryPosition, showSummaryOnCollapse); } - private addSummaryRows(grid: IgxTreeGridComponent, collection: ITreeGridRecord[], summaryPosition: GridSummaryPosition): any[] { + private addSummaryRows(grid: IgxTreeGridComponent, collection: ITreeGridRecord[], + summaryPosition: GridSummaryPosition, showSummaryOnCollapse: boolean): any[] { const recordsWithSummary = []; const maxSummaryHeight = grid.summaryService.calcMaxSummaryHeight(); @@ -42,8 +43,19 @@ export class IgxTreeGridSummaryPipe implements PipeTransform { const record = collection[i]; recordsWithSummary.push(record); + const isCollapsed = !record.expanded && record.children && record.children.length > 0 && showSummaryOnCollapse; + if (isCollapsed) { + let childData = record.children.filter(r => !r.isFilteredOutParent).map(r => r.data); + childData = this.removeDeletedRecord(grid, record.rowID, childData); + const summaries = grid.summaryService.calculateSummaries(record.rowID, childData); + const summaryRecord: ISummaryRecord = { + summaries: summaries, + max: maxSummaryHeight, + cellIndentation: record.level + 1 + }; + recordsWithSummary.push(summaryRecord); + } const isExpanded = record.children && record.children.length > 0 && record.expanded; - if (summaryPosition === GridSummaryPosition.bottom && !isExpanded) { let childRecord = record; let parent = record.parent; diff --git a/projects/igniteui-angular/src/lib/services/csv/char-separated-value-data.ts b/projects/igniteui-angular/src/lib/services/csv/char-separated-value-data.ts index 1e23bd7aa35..aa137c1e56b 100644 --- a/projects/igniteui-angular/src/lib/services/csv/char-separated-value-data.ts +++ b/projects/igniteui-angular/src/lib/services/csv/char-separated-value-data.ts @@ -1,4 +1,5 @@ import { ExportUtilities } from '../exporter-common/export-utilities'; +import { yieldingLoop } from '../../core/utils'; /** * @hidden @@ -36,6 +37,26 @@ export class CharSeparatedValueData { return this._headerRecord + this._dataRecords; } + public prepareDataAsync(done: (result: string) => void) { + if (!this._data || this._data.length === 0) { + done(''); + } + + const keys = ExportUtilities.getKeysFromData(this._data); + + if (keys.length === 0) { + done(''); + } + + this._isSpecialData = ExportUtilities.isSpecialData(this._data); + this._escapeCharacters.push(this._delimiter); + + this._headerRecord = this.processHeaderRecord(keys, this._escapeCharacters); + this.processDataRecordsAsync(this._data, keys, this._escapeCharacters, (dr) => { + done(this._headerRecord + dr); + }); + } + private processField(value, escapeChars): string { let safeValue = ExportUtilities.hasValue(value) ? String(value) : ''; if (escapeChars.some((v) => safeValue.includes(v))) { @@ -54,23 +75,37 @@ export class CharSeparatedValueData { } private processRecord(record, keys, escapeChars): string { - let recordData = ''; - for (const keyName of keys) { - - const value = (record[keyName] !== undefined) ? record[keyName] : this._isSpecialData ? record : ''; - recordData += this.processField(value, this._escapeCharacters); + const recordData = new Array(keys.length); + for (let index = 0; index < keys.length; index++) { + const value = (record[keys[index]] !== undefined) ? record[keys[index]] : this._isSpecialData ? record : ''; + recordData[index] = this.processField(value, this._escapeCharacters); } - return recordData.slice(0, -this._delimiterLength) + this._eor; + return recordData.join('').slice(0, -this._delimiterLength) + this._eor; } private processDataRecords(currentData, keys, escapeChars) { - let dataRecords = ''; - for (const row of currentData) { - dataRecords += this.processRecord(row, keys, escapeChars); + const dataRecords = new Array(currentData.length); + + for (let i = 0; i < currentData.length; i++) { + const row = currentData[i]; + dataRecords[i] = this.processRecord(row, keys, escapeChars); } - return dataRecords; + return dataRecords.join(''); + } + + private processDataRecordsAsync(currentData, keys, escapeChars, done: (result: string) => void) { + const dataRecords = new Array(currentData.length); + + yieldingLoop(currentData.length, 1000, + (i) => { + const row = currentData[i]; + dataRecords[i] = this.processRecord(row, keys, escapeChars); + }, + () => { + done(dataRecords.join('')); + }); } private setDelimiter(value) { diff --git a/projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts b/projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts index 740f293a2bc..b15e55e0eb7 100644 --- a/projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts +++ b/projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts @@ -50,10 +50,11 @@ export class IgxCsvExporterService extends IgxBaseExporter { protected exportDataImplementation(data: any[], options: IgxCsvExporterOptions) { data = data.map((item) => item.rowData); const csvData = new CharSeparatedValueData(data, options.valueDelimiter); - this._stringData = csvData.prepareData(); - - this.saveFile(options); - this.onExportEnded.emit({ csvData: this._stringData }); + csvData.prepareDataAsync((r) => { + this._stringData = r; + this.saveFile(options); + this.onExportEnded.emit({ csvData: this._stringData }); + }); } private saveFile(options: IgxCsvExporterOptions) { diff --git a/projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts b/projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts index 9e9b0465c55..fe1364d60ed 100644 --- a/projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts +++ b/projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts @@ -266,14 +266,13 @@ describe('Excel Exporter', () => { const grid = fix.componentInstance.grid; grid.columns[1].hidden = true; grid.columns[2].hidden = true; - const columnWidths = [100, 200, 0, undefined, null]; + const columnWidths = [100, 200, 0, null]; fix.detectChanges(); await setColWidthAndExport(grid, options, fix, columnWidths[0]); await setColWidthAndExport(grid, options, fix, columnWidths[1]); await setColWidthAndExport(grid, options, fix, columnWidths[2]); await setColWidthAndExport(grid, options, fix, columnWidths[3]); - await setColWidthAndExport(grid, options, fix, columnWidths[4]); }); it('should export all rows with the height specified in options.', async () => { diff --git a/projects/igniteui-angular/src/lib/services/excel/excel-exporter-options.ts b/projects/igniteui-angular/src/lib/services/excel/excel-exporter-options.ts index 28c1bb4f559..c4b86d4477c 100644 --- a/projects/igniteui-angular/src/lib/services/excel/excel-exporter-options.ts +++ b/projects/igniteui-angular/src/lib/services/excel/excel-exporter-options.ts @@ -44,8 +44,8 @@ export class IgxExcelExporterOptions extends IgxExporterOptionsBase { } /** - * Sets the width of the columns in the exported excel file. If left unspecified or 0, - * the width of the largest string in the column will be used. + * Sets the width of the columns in the exported excel file. If left unspecified, + * the width of the column or the default width of the excel columns will be used. * ```typescript * this.exportOptions.columnWidth = 55; * ``` diff --git a/projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts b/projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts index dd9d9419fd1..077af5aa3f5 100644 --- a/projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts +++ b/projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts @@ -9,6 +9,7 @@ import { IgxBaseExporter } from '../exporter-common/base-export-service'; import { ExportUtilities } from '../exporter-common/export-utilities'; import { WorksheetData } from './worksheet-data'; import { IBaseEventArgs } from '../../core/utils'; +import { WorksheetFile } from './excel-files'; export interface IExcelExportEndedEventArgs extends IBaseEventArgs { xlsx: JSZip; @@ -53,16 +54,20 @@ export class IgxExcelExporterService extends IgxBaseExporter { @Output() public onExportEnded = new EventEmitter(); - private static populateFolder(folder: IExcelFolder, zip: JSZip, worksheetData: WorksheetData): any { + private static async populateFolderAsync(folder: IExcelFolder, zip: JSZip, worksheetData: WorksheetData) { for (const childFolder of folder.childFolders(worksheetData)) { - const folderIntance = ExcelElementsFactory.getExcelFolder(childFolder); - const zipFolder = zip.folder(folderIntance.folderName); - IgxExcelExporterService.populateFolder(folderIntance, zipFolder, worksheetData); + const folderInstance = ExcelElementsFactory.getExcelFolder(childFolder); + const zipFolder = zip.folder(folderInstance.folderName); + await IgxExcelExporterService.populateFolderAsync(folderInstance, zipFolder, worksheetData); } for (const childFile of folder.childFiles(worksheetData)) { const fileInstance = ExcelElementsFactory.getExcelFile(childFile); - fileInstance.writeElement(zip, worksheetData); + if (fileInstance instanceof WorksheetFile) { + await (fileInstance as WorksheetFile).writeElementAsync(zip, worksheetData); + } else { + fileInstance.writeElement(zip, worksheetData); + } } } @@ -77,16 +82,19 @@ export class IgxExcelExporterService extends IgxBaseExporter { } } - const worksheetData = new WorksheetData(data, options, this._indexOfLastPinnedColumn, this._sort, this._isTreeGrid); + const worksheetData = + new WorksheetData(data, this.columnWidthList, options, this._indexOfLastPinnedColumn, this._sort, this._isTreeGrid); + this._xlsx = new JSZip(); const rootFolder = ExcelElementsFactory.getExcelFolder(ExcelFolderTypes.RootExcelFolder); - IgxExcelExporterService.populateFolder(rootFolder, this._xlsx, worksheetData); - this._xlsx.generateAsync(IgxExcelExporterService.ZIP_OPTIONS).then((result) => { - this.saveFile(result, options.fileName); - - this.onExportEnded.emit({ xlsx: this._xlsx }); + IgxExcelExporterService.populateFolderAsync(rootFolder, this._xlsx, worksheetData) + .then(() => { + this._xlsx.generateAsync(IgxExcelExporterService.ZIP_OPTIONS).then((result) => { + this.saveFile(result, options.fileName); + this.onExportEnded.emit({ xlsx: this._xlsx }); + }); }); } diff --git a/projects/igniteui-angular/src/lib/services/excel/excel-files.ts b/projects/igniteui-angular/src/lib/services/excel/excel-files.ts index 561733c0f6e..19f367be866 100644 --- a/projects/igniteui-angular/src/lib/services/excel/excel-files.ts +++ b/projects/igniteui-angular/src/lib/services/excel/excel-files.ts @@ -3,6 +3,7 @@ import { ExcelStrings } from './excel-strings'; import { WorksheetData } from './worksheet-data'; import * as JSZip from 'jszip'; +import { yieldingLoop } from '../../core/utils'; /** * @hidden @@ -54,7 +55,11 @@ export class ThemeFile implements IExcelFile { * @hidden */ export class WorksheetFile implements IExcelFile { - private static MIN_WIDTH = 8.34; + private static MIN_WIDTH = 8.43; + private maxOutlineLevel = 0; + private dimension = ''; + private freezePane = ''; + private rowHeight = ''; public writeElement(folder: JSZip, worksheetData: WorksheetData) { const sheetData = []; @@ -107,9 +112,12 @@ export class WorksheetFile implements IExcelFile { for (let i = 0; i < worksheetData.columnCount; i++) { const width = dictionary.columnWidths[i]; // Use the width provided in the options if it exists - const widthInTwips = worksheetData.options.columnWidth ? - worksheetData.options.columnWidth : - Math.max(((width / 96) * 14.4), WorksheetFile.MIN_WIDTH); + let widthInTwips = worksheetData.options.columnWidth !== undefined ? + worksheetData.options.columnWidth : + Math.max(((width / 96) * 14.4), WorksheetFile.MIN_WIDTH); + if (!(widthInTwips > 0)) { + widthInTwips = WorksheetFile.MIN_WIDTH; + } cols.push(``); } @@ -131,6 +139,110 @@ export class WorksheetFile implements IExcelFile { worksheetData.isTreeGridData, maxOutlineLevel)); } + public async writeElementAsync(folder: JSZip, worksheetData: WorksheetData) { + return new Promise(resolve => { + this.prepareDataAsync(worksheetData, (cols, rows) => { + const hasTable = !worksheetData.isEmpty && worksheetData.options.exportAsTable; + folder.file('sheet1.xml', ExcelStrings.getSheetXML( + this.dimension, this.freezePane, cols, rows, hasTable, worksheetData.isTreeGridData, this.maxOutlineLevel)); + resolve(); + }); + }); + } + + private prepareDataAsync(worksheetData: WorksheetData, done: (cols: string, sheetData: string) => void) { + let sheetData = ''; + let cols = ''; + const dictionary = worksheetData.dataDictionary; + + if (worksheetData.isEmpty) { + sheetData += ''; + this.dimension = 'A1'; + done('', sheetData); + } else { + sheetData += ''; + const height = worksheetData.options.rowHeight; + this.rowHeight = height ? ' ht="' + height + '" customHeight="1"' : ''; + sheetData += ``; + + for (let i = 0; i < worksheetData.columnCount; i++) { + const column = ExcelStrings.getExcelColumn(i) + 1; + const value = dictionary.saveValue(worksheetData.keys[i], i, true); + sheetData += `${value}`; + } + sheetData += ''; + + this.dimension = 'A1:' + ExcelStrings.getExcelColumn(worksheetData.columnCount - 1) + worksheetData.rowCount; + cols += ''; + + for (let i = 0; i < worksheetData.columnCount; i++) { + const width = dictionary.columnWidths[i]; + // Use the width provided in the options if it exists + let widthInTwips = worksheetData.options.columnWidth !== undefined ? + worksheetData.options.columnWidth : + Math.max(((width / 96) * 14.4), WorksheetFile.MIN_WIDTH); + if (!(widthInTwips > 0)) { + widthInTwips = WorksheetFile.MIN_WIDTH; + } + + cols += ``; + } + + cols += ''; + + if (worksheetData.indexOfLastPinnedColumn !== -1 && + !worksheetData.options.ignorePinning && + !worksheetData.options.ignoreColumnsOrder) { + const frozenColumnCount = worksheetData.indexOfLastPinnedColumn + 1; + const firstCell = ExcelStrings.getExcelColumn(frozenColumnCount) + '1'; + this.freezePane = ``; + } + + this.processDataRecordsAsync(worksheetData, (rows) => { + sheetData += rows; + sheetData += ''; + done(cols, sheetData); + }); + } + } + + private processDataRecordsAsync(worksheetData: WorksheetData, done: (rows: string) => void) { + const rowDataArr = new Array(worksheetData.rowCount - 1); + const height = worksheetData.options.rowHeight; + this.rowHeight = height ? ' ht="' + height + '" customHeight="1"' : ''; + + yieldingLoop(worksheetData.rowCount - 1, 1000, + (i) => { + rowDataArr[i] = this.processRow(worksheetData, i + 1); + }, + () => { + done(rowDataArr.join('')); + }); + } + + private processRow(worksheetData: WorksheetData, i: number) { + const rowData = new Array(worksheetData.columnCount + 2); + if (!worksheetData.isTreeGridData) { + rowData[0] = ``; + } else { + const originalData = worksheetData.data[i - 1].originalRowData; + const sCollapsed = (!originalData.expanded) ? '' : (originalData.expanded === true) ? '' : ` collapsed="1"`; + const sHidden = (originalData.parent && this.hasCollapsedParent(originalData)) ? ` hidden="1"` : ''; + const rowOutlineLevel = originalData.level ? originalData.level : 0; + const sOutlineLevel = rowOutlineLevel > 0 ? ` outlineLevel="${rowOutlineLevel}"` : ''; + this.maxOutlineLevel = this.maxOutlineLevel < rowOutlineLevel ? rowOutlineLevel : this.maxOutlineLevel; + rowData[0] = ``; + } + + for (let j = 0; j < worksheetData.columnCount; j++) { + const cellData = WorksheetFile.getCellData(worksheetData, i, j); + rowData[j + 1] = cellData; + } + rowData[worksheetData.columnCount + 1] = ''; + + return rowData.join(''); + } + private hasCollapsedParent(rowData) { let result = !rowData.parent.expanded; while (rowData.parent) { diff --git a/projects/igniteui-angular/src/lib/services/excel/test-data.service.spec.ts b/projects/igniteui-angular/src/lib/services/excel/test-data.service.spec.ts index 206f175c032..bd8ce2a9fd5 100644 --- a/projects/igniteui-angular/src/lib/services/excel/test-data.service.spec.ts +++ b/projects/igniteui-angular/src/lib/services/excel/test-data.service.spec.ts @@ -346,7 +346,7 @@ export class FileContentData { ``; this._worksheetData = `` + - `01` + `23`; @@ -611,11 +611,10 @@ export class FileContentData { wsDataColSettings = ``; break; - case undefined: case null: case 0: wsDataColSettings = - ``; + ``; break; } diff --git a/projects/igniteui-angular/src/lib/services/excel/worksheet-data-dictionary.ts b/projects/igniteui-angular/src/lib/services/excel/worksheet-data-dictionary.ts index 7b9ec3d8cca..c533f1cd049 100644 --- a/projects/igniteui-angular/src/lib/services/excel/worksheet-data-dictionary.ts +++ b/projects/igniteui-angular/src/lib/services/excel/worksheet-data-dictionary.ts @@ -15,7 +15,6 @@ export class WorksheetDataDictionary { private _keysAreValid: boolean; private _counter: number; - private _calculateColumnWidth: boolean; private _columnWidths: number[]; private _context: any; @@ -24,18 +23,19 @@ export class WorksheetDataDictionary { public stringsCount: number; - constructor(columnCount: number, columnWidth: number) { + constructor(columnCount: number, columnWidth: number, columnWidthsList: number[]) { this._dictionary = {}; this._widthsDictionary = {}; this._counter = 0; this.dirtyKeyCollections(); - this._calculateColumnWidth = !columnWidth; this._columnWidths = new Array(columnCount); this._columnTypeInfo = new Array(columnCount); - if (!this._calculateColumnWidth) { + if (columnWidth) { this._columnWidths.fill(columnWidth); + } else { + this._columnWidths = columnWidthsList; } this.stringsCount = 0; @@ -68,12 +68,6 @@ export class WorksheetDataDictionary { this.hasNonStringValues = true; } - if (this._calculateColumnWidth) { - const width = this.getTextWidth(value); - const maxWidth = Math.max(this._columnWidths[column] || 0, width); - this._columnWidths[column] = maxWidth; - } - return isSavedAsString ? this.getSanitizedValue(sanitizedValue) : -1; } diff --git a/projects/igniteui-angular/src/lib/services/excel/worksheet-data.ts b/projects/igniteui-angular/src/lib/services/excel/worksheet-data.ts index 4496edf0012..fa944268059 100644 --- a/projects/igniteui-angular/src/lib/services/excel/worksheet-data.ts +++ b/projects/igniteui-angular/src/lib/services/excel/worksheet-data.ts @@ -10,8 +10,8 @@ export class WorksheetData { private _keys: string[]; private _isSpecialData: boolean; - constructor(private _data: any[], public options: IgxExcelExporterOptions, public indexOfLastPinnedColumn, - public sort: any, public isTreeGridData = false) { + constructor(private _data: any[], private _columnWidths: number[], public options: IgxExcelExporterOptions, + public indexOfLastPinnedColumn, public sort: any, public isTreeGridData = false) { this.initializeData(); } @@ -60,6 +60,6 @@ export class WorksheetData { this._columnCount = this._keys.length; this._rowCount = this._data.length + 1; - this._dataDictionary = new WorksheetDataDictionary(this._columnCount, this.options.columnWidth); + this._dataDictionary = new WorksheetDataDictionary(this._columnCount, this.options.columnWidth, this._columnWidths); } } diff --git a/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts b/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts index 14a3066e92b..a8a0eca3f22 100644 --- a/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts +++ b/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts @@ -1,6 +1,6 @@ import { EventEmitter } from '@angular/core'; -import { cloneValue, IBaseEventArgs } from '../../core/utils'; +import { cloneValue, IBaseEventArgs, yieldingLoop } from '../../core/utils'; import { DataUtil } from '../../data-operations/data-util'; import { ExportUtilities } from './export-utilities'; @@ -66,9 +66,12 @@ export interface IColumnExportingEventArgs extends IBaseEventArgs { skipFormatter: boolean; } +const DEFAULT_COLUMN_WIDTH = 8.43; + export abstract class IgxBaseExporter { private _columnList: any[]; private flatRecords = []; + private _columnWidthList: number[]; protected _isTreeGrid = false; protected _indexOfLastPinnedColumn = -1; @@ -96,6 +99,10 @@ export abstract class IgxBaseExporter { */ public onColumnExport = new EventEmitter(); + public get columnWidthList() { + return this._columnWidthList; + } + /** * Method for exporting IgxGrid component's data. * ```typescript @@ -110,6 +117,7 @@ export abstract class IgxBaseExporter { const columns = grid.columnList.toArray(); this._columnList = new Array(columns.length); + this._columnWidthList = new Array(columns.filter(c => !c.hidden).length); const hiddenColumns = []; let lastVisbleColumnIndex = -1; @@ -118,6 +126,7 @@ export abstract class IgxBaseExporter { const columnHeader = column.header !== '' ? column.header : column.field; const exportColumn = !column.hidden || options.ignoreColumnsVisibility; const index = options.ignoreColumnsOrder ? column.index : column.visibleIndex; + const columnWidth = Number(column.width.slice(0, -2)); const columnInfo = { header: columnHeader, @@ -129,6 +138,7 @@ export abstract class IgxBaseExporter { if (index !== -1) { this._columnList[index] = columnInfo; + this._columnWidthList[index] = columnWidth; lastVisbleColumnIndex = Math.max(lastVisbleColumnIndex, index); } else { hiddenColumns.push(columnInfo); @@ -163,6 +173,7 @@ export abstract class IgxBaseExporter { if (!this._columnList || this._columnList.length === 0) { const keys = ExportUtilities.getKeysFromData(data); this._columnList = keys.map((k) => ({ header: k, field: k, skip: false })); + this._columnWidthList = new Array(keys.length).fill(DEFAULT_COLUMN_WIDTH); } let skippedPinnedColumnsCount = 0; @@ -202,12 +213,13 @@ export abstract class IgxBaseExporter { const dataToExport = new Array(); const isSpecialData = ExportUtilities.isSpecialData(data); - data.forEach((row, index) => { - this.exportRow(dataToExport, row, index, isSpecialData); + yieldingLoop(data.length, 1000, (i) => { + const row = data[i]; + this.exportRow(dataToExport, row, i, isSpecialData); + }, () => { + this.exportDataImplementation(dataToExport, options); + this.resetDefaults(); }); - - this.exportDataImplementation(dataToExport, options); - this.resetDefaults(); } protected abstract exportDataImplementation(data: any[], options: IgxExporterOptionsBase): void; diff --git a/projects/igniteui-angular/src/lib/services/overlay/overlay.spec.ts b/projects/igniteui-angular/src/lib/services/overlay/overlay.spec.ts index 98cc3cd1985..889c533d818 100644 --- a/projects/igniteui-angular/src/lib/services/overlay/overlay.spec.ts +++ b/projects/igniteui-angular/src/lib/services/overlay/overlay.spec.ts @@ -9,7 +9,7 @@ import { ComponentRef } from '@angular/core'; import { TestBed, fakeAsync, tick, async, inject } from '@angular/core/testing'; -import { BrowserModule } from '@angular/platform-browser'; +import { BrowserModule, By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxOverlayService } from './overlay'; import { IgxToggleDirective, IgxToggleModule, IgxOverlayOutletDirective } from './../../directives/toggle/toggle.directive'; @@ -216,8 +216,7 @@ describe('igxOverlay', () => { overlay.show(overlay.attach(SimpleDynamicComponent), { outlet: button, - modal: false, - closeOnEsc: false + modal: false }); tick(); let wrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER)[0]; @@ -225,7 +224,7 @@ describe('igxOverlay', () => { expect(wrapper.parentNode).toBe(button.nativeElement); overlay.hideAll(); - overlay.show(overlay.attach(SimpleDynamicComponent), { modal: false, closeOnEsc: false }); + overlay.show(overlay.attach(SimpleDynamicComponent), { modal: false }); tick(); wrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER)[0]; expect(wrapper).toBeDefined(); @@ -237,7 +236,6 @@ describe('igxOverlay', () => { fixture.debugElement.nativeElement.appendChild(outlet); overlay.show(overlay.attach(SimpleDynamicComponent), { modal: false, - closeOnEsc: false, outlet: new IgxOverlayOutletDirective(new ElementRef(outlet)) }); tick(); @@ -688,7 +686,6 @@ describe('igxOverlay', () => { tick(); fix.componentInstance.overlaySettings.outlet = fix.componentInstance.elementRef; - fix.componentInstance.overlaySettings.closeOnEsc = false; const buttonElement: HTMLElement = fix.componentInstance.buttonElement.nativeElement; buttonElement.click(); @@ -1078,7 +1075,6 @@ describe('igxOverlay', () => { positionStrategy: new GlobalPositionStrategy(), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const positionSettings: PositionSettings = { @@ -1305,7 +1301,6 @@ describe('igxOverlay', () => { positionStrategy: new GlobalPositionStrategy(), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const positionSettings: PositionSettings = { @@ -1332,7 +1327,6 @@ describe('igxOverlay', () => { positionStrategy: new GlobalPositionStrategy(), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; overlaySettings.positionStrategy = new ConnectedPositioningStrategy(); @@ -1460,7 +1454,6 @@ describe('igxOverlay', () => { positionStrategy: new GlobalPositionStrategy(), scrollStrategy: scrollStrat, modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const overlay = fixture.componentInstance.overlay; @@ -1490,7 +1483,6 @@ describe('igxOverlay', () => { positionStrategy: new ConnectedPositioningStrategy(), scrollStrategy: scrollStrat, modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const buttonElement = fixture.componentInstance.buttonElement.nativeElement; @@ -1668,7 +1660,6 @@ describe('igxOverlay', () => { positionStrategy: new AutoPositionStrategy(), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; @@ -1697,7 +1688,6 @@ describe('igxOverlay', () => { positionStrategy: new GlobalPositionStrategy(), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const positionSettings: PositionSettings = { @@ -1943,7 +1933,6 @@ describe('igxOverlay', () => { positionStrategy: new AutoPositionStrategy(positionSettings), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const hAlignmentArray = Object.keys(HorizontalAlignment).filter(key => !isNaN(Number(HorizontalAlignment[key]))); @@ -2000,7 +1989,6 @@ describe('igxOverlay', () => { positionStrategy: new AutoPositionStrategy(positionSettings), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); @@ -2043,7 +2031,6 @@ describe('igxOverlay', () => { positionStrategy: new AutoPositionStrategy(positionSettings), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); @@ -2145,7 +2132,6 @@ describe('igxOverlay', () => { positionStrategy: new ElasticPositionStrategy(), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; @@ -2174,7 +2160,6 @@ describe('igxOverlay', () => { positionStrategy: new GlobalPositionStrategy(), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const positionSettings: PositionSettings = { @@ -2438,7 +2423,6 @@ describe('igxOverlay', () => { positionStrategy: new ElasticPositionStrategy(positionSettings), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const hAlignmentArray = Object.keys(HorizontalAlignment).filter(key => !isNaN(Number(HorizontalAlignment[key]))); @@ -2495,7 +2479,6 @@ describe('igxOverlay', () => { positionStrategy: new ElasticPositionStrategy(positionSettings), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); @@ -2539,7 +2522,6 @@ describe('igxOverlay', () => { positionStrategy: new ElasticPositionStrategy(positionSettings), scrollStrategy: new NoOpScrollStrategy(), modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); @@ -2736,100 +2718,117 @@ describe('igxOverlay', () => { const fixture = TestBed.createComponent(EmptyPageComponent); const overlay = fixture.componentInstance.overlay; const overlaySettings: OverlaySettings = { - modal: true, - closeOnEsc: true, - positionStrategy: new GlobalPositionStrategy() + closeOnEscape: true, }; - const targetButton = 'Escape'; - const escEvent = new KeyboardEvent('keydown', { - key: targetButton - }); - overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); tick(); let overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; - overlayWrapper.addEventListener('keydown', (event: KeyboardEvent) => { - if (event.key === targetButton) { - overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; - } - }); - tick(); expect(overlayWrapper).toBeTruthy(); - overlayWrapper.dispatchEvent(escEvent); + + UIInteractions.triggerKeyDownEvtUponElem('Escape', document); tick(); + + overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; + expect(overlayWrapper).toBeFalsy(); })); it('Should not close the component when esc key is pressed and closeOnEsc is false', fakeAsync(() => { const fixture = TestBed.createComponent(EmptyPageComponent); const overlay = fixture.componentInstance.overlay; const overlaySettings: OverlaySettings = { - modal: true, - closeOnEsc: false, - positionStrategy: new GlobalPositionStrategy() + closeOnEscape: false }; - const targetButton = 'Escape'; - const escEvent = new KeyboardEvent('keydown', { - key: targetButton - }); overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); tick(); let overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; - overlayWrapper.addEventListener('keydown', (event: KeyboardEvent) => { - if (event.key === targetButton) { - overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; - } - }); - overlayWrapper.dispatchEvent(escEvent); + expect(overlayWrapper).toBeTruthy(); + + UIInteractions.triggerKeyDownEvtUponElem('Escape', document); tick(); - fixture.detectChanges(); + overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; expect(overlayWrapper).toBeTruthy(); })); + it('Should close the opened overlays consecutively on esc keypress', fakeAsync(() => { + const fixture = TestBed.createComponent(EmptyPageComponent); + const overlay = fixture.componentInstance.overlay; + overlay.show(overlay.attach(SimpleDynamicComponent), { closeOnEscape: true }); + tick(); + overlay.show(overlay.attach(SimpleDynamicComponent), { closeOnEscape: true }); + tick(); + + const overlayDiv = document.getElementsByClassName(CLASS_OVERLAY_MAIN)[0]; + expect(overlayDiv.children.length).toBe(2); + + UIInteractions.triggerKeyDownEvtUponElem('Escape', document); + tick(); + expect(overlayDiv.children.length).toBe(1); + + UIInteractions.triggerKeyDownEvtUponElem('Escape', document); + tick(); + expect(overlayDiv.children.length).toBe(0); + })); + + it('Should not close the opened overlays consecutively on esc keypress', fakeAsync(() => { + const fixture = TestBed.createComponent(EmptyPageComponent); + const overlay = fixture.componentInstance.overlay; + overlay.show(overlay.attach(SimpleDynamicComponent), { closeOnEscape: true }); + tick(); + overlay.show(overlay.attach(SimpleDynamicComponent), { closeOnEscape: false }); + tick(); + overlay.show(overlay.attach(SimpleDynamicComponent), { closeOnEscape: true }); + tick(); + + const overlayDiv = document.getElementsByClassName(CLASS_OVERLAY_MAIN)[0]; + expect(overlayDiv.children.length).toBe(3); + + UIInteractions.triggerKeyDownEvtUponElem('Escape', document); + tick(); + expect(overlayDiv.children.length).toBe(2); + + UIInteractions.triggerKeyDownEvtUponElem('Escape', document); + tick(); + expect(overlayDiv.children.length).toBe(2); + })); + // Test #1883 #1820 it('It should close the component when esc key is pressed and there were other keys pressed prior to esc.', fakeAsync(() => { const fixture = TestBed.createComponent(EmptyPageComponent); const overlay = fixture.componentInstance.overlay; const overlaySettings: OverlaySettings = { - modal: true, - closeOnEsc: true, - positionStrategy: new GlobalPositionStrategy() + closeOnEscape: true, }; - const escEvent = new KeyboardEvent('keydown', { - key: 'Escape' - }); - const enterEvent = new KeyboardEvent('keydown', { - key: 'Enter' - }); - const arrowUpEvent = new KeyboardEvent('keydown', { - key: 'ArrowUp' - }); - const aEvent = new KeyboardEvent('keydown', { - key: 'a' - }); - overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); tick(); let overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; - overlayWrapper.addEventListener('keydown', (event: KeyboardEvent) => { - if (event.key === 'Escape') { - overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; - expect(overlayWrapper).toBeFalsy(); - } - }); + expect(overlayWrapper).toBeTruthy(); + + UIInteractions.triggerKeyDownEvtUponElem('Enter', document); tick(); + overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; expect(overlayWrapper).toBeTruthy(); - overlayWrapper.dispatchEvent(enterEvent); - overlayWrapper.dispatchEvent(aEvent); - overlayWrapper.dispatchEvent(arrowUpEvent); - overlayWrapper.dispatchEvent(escEvent); + UIInteractions.triggerKeyDownEvtUponElem('a', document); + tick(); + overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; + expect(overlayWrapper).toBeTruthy(); + + UIInteractions.triggerKeyDownEvtUponElem('ArrowUp', document); + tick(); + overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; + expect(overlayWrapper).toBeTruthy(); + + UIInteractions.triggerKeyDownEvtUponElem('Escape', document); + tick(); + overlayWrapper = document.getElementsByClassName(CLASS_OVERLAY_WRAPPER_MODAL)[0]; + expect(overlayWrapper).toBeFalsy(); })); // 3.2 Non - Modal @@ -2837,8 +2836,7 @@ describe('igxOverlay', () => { const fixture = TestBed.createComponent(EmptyPageComponent); const overlay = fixture.componentInstance.overlay; const overlaySettings: OverlaySettings = { - modal: false, - closeOnEsc: false + modal: false }; overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); @@ -2867,7 +2865,6 @@ describe('igxOverlay', () => { const overlay = fixture.componentInstance.overlay; const overlaySettings: OverlaySettings = { modal: false, - closeOnEsc: false, positionStrategy: new GlobalPositionStrategy() }; const targetEvent = 'keydown'; @@ -2986,7 +2983,6 @@ describe('igxOverlay', () => { positionStrategy: new ConnectedPositioningStrategy(), scrollStrategy: scrollStrat, modal: false, - closeOnEsc: false, closeOnOutsideClick: false }; const overlay = fixture.componentInstance.overlay; @@ -3405,7 +3401,6 @@ describe('igxOverlay', () => { const overlaySettings: OverlaySettings = { modal: false, - closeOnEsc: false }; const overlay = fixture.componentInstance.overlay; @@ -3448,7 +3443,6 @@ describe('igxOverlay', () => { positionStrategy: new GlobalPositionStrategy(), scrollStrategy: scrollStrategy, modal: false, - closeOnEsc: false }; overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); @@ -3489,7 +3483,6 @@ describe('igxOverlay', () => { scrollStrategy: scrollStrategy, closeOnOutsideClick: false, modal: false, - closeOnEsc: false }; overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); @@ -3524,7 +3517,6 @@ describe('igxOverlay', () => { positionStrategy: new GlobalPositionStrategy(), scrollStrategy: scrollStrategy, modal: false, - closeOnEsc: false }; overlay.show(overlay.attach(SimpleDynamicComponent), overlaySettings); @@ -3562,7 +3554,6 @@ describe('igxOverlay', () => { const overlay = fixture.componentInstance.overlay; const overlaySettings: OverlaySettings = { modal: false, - closeOnEsc: false, scrollStrategy: scrollStrategy, positionStrategy: new GlobalPositionStrategy() }; @@ -3604,7 +3595,6 @@ describe('igxOverlay', () => { const overlaySettings: OverlaySettings = { closeOnOutsideClick: false, modal: false, - closeOnEsc: false, positionStrategy: new ConnectedPositioningStrategy(), scrollStrategy: scrollStrategy }; @@ -3879,16 +3869,16 @@ export class TopLeftOffsetComponent { ` }) export class TwoButtonsComponent { - private _setting: OverlaySettings = { modal: false, closeOnEsc: false }; + public settings: OverlaySettings = { modal: false }; constructor(@Inject(IgxOverlayService) public overlay: IgxOverlayService) { } clickOne() { - this.overlay.show(this.overlay.attach(SimpleDynamicComponent), this._setting); + this.overlay.show(this.overlay.attach(SimpleDynamicComponent), this.settings); } clickTwo() { - this.overlay.show(this.overlay.attach(SimpleDynamicComponent), this._setting); + this.overlay.show(this.overlay.attach(SimpleDynamicComponent), this.settings); } divClick(ev: Event) { diff --git a/projects/igniteui-angular/src/lib/services/overlay/overlay.ts b/projects/igniteui-angular/src/lib/services/overlay/overlay.ts index e6f04bd9636..ce7549cd62f 100644 --- a/projects/igniteui-angular/src/lib/services/overlay/overlay.ts +++ b/projects/igniteui-angular/src/lib/services/overlay/overlay.ts @@ -1,15 +1,5 @@ +import { AnimationAnimateRefMetadata, AnimationBuilder, AnimationMetadataType, AnimationReferenceMetadata } from '@angular/animations'; import { DOCUMENT } from '@angular/common'; -import { GlobalPositionStrategy } from './position/global-position-strategy'; -import { NoOpScrollStrategy } from './scroll/NoOpScrollStrategy'; -import { - OverlaySettings, - OverlayEventArgs, - OverlayInfo, - OverlayAnimationEventArgs, - OverlayCancelableEventArgs, - OverlayClosingEventArgs -} from './utilities'; - import { ApplicationRef, ComponentFactory, @@ -20,16 +10,22 @@ import { Inject, Injectable, Injector, - Type, - OnDestroy, NgModuleRef, - NgZone + NgZone, OnDestroy, Type } from '@angular/core'; -import { AnimationBuilder, AnimationReferenceMetadata, AnimationMetadataType, AnimationAnimateRefMetadata } from '@angular/animations'; -import { fromEvent, Subject } from 'rxjs'; +import { fromEvent, Subject, Subscription } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; import { IAnimationParams } from '../../animations/main'; import { showMessage } from '../../core/deprecateDecorators'; +import { GlobalPositionStrategy } from './position/global-position-strategy'; +import { NoOpScrollStrategy } from './scroll/NoOpScrollStrategy'; +import { + OverlayAnimationEventArgs, + OverlayCancelableEventArgs, + OverlayClosingEventArgs, OverlayEventArgs, + OverlayInfo, OverlaySettings +} from './utilities'; + let warningShown = false; @@ -43,6 +39,7 @@ export class IgxOverlayService implements OnDestroy { private _overlayInfos: OverlayInfo[] = []; private _overlayElement: HTMLElement; private _document: Document; + private _keyPressEventListener: Subscription; private destroy$ = new Subject(); private _defaultSettings: OverlaySettings = { @@ -50,7 +47,7 @@ export class IgxOverlayService implements OnDestroy { scrollStrategy: new NoOpScrollStrategy(), modal: true, closeOnOutsideClick: true, - closeOnEsc: true + closeOnEscape: false }; /** @@ -321,7 +318,8 @@ export class IgxOverlayService implements OnDestroy { } this.addOutsideClickListener(info); - this.addResizeHandler(info.id); + this.addResizeHandler(); + this.addCloseOnEscapeListener(info); if (info.settings.modal) { const wrapperElement = info.elementRef.nativeElement.parentElement.parentElement; @@ -330,9 +328,6 @@ export class IgxOverlayService implements OnDestroy { wrapperElement.classList.add('igx-overlay__wrapper--modal'); } - if (info.settings.closeOnEsc) { - this.setUpCloseOnEscape(info); - } if (info.settings.positionStrategy.settings.openAnimation) { this.playOpenAnimation(info); @@ -360,7 +355,7 @@ export class IgxOverlayService implements OnDestroy { // TODO: synchronize where these are added/attached and where removed/detached info.settings.scrollStrategy.detach(); this.removeOutsideClickListener(info); - this.removeResizeHandler(info.id); + this.removeResizeHandler(); const child: HTMLElement = info.elementRef.nativeElement; if (info.settings.modal) { @@ -475,14 +470,6 @@ export class IgxOverlayService implements OnDestroy { } } - private setUpCloseOnEscape(info: OverlayInfo) { - const wrapperElement = info.elementRef.nativeElement.parentElement.parentElement; - fromEvent(wrapperElement, 'keydown').pipe( - filter((ev: KeyboardEvent) => ev.key === 'Escape' || ev.key === 'Esc'), - takeUntil(this.destroy$) - ).subscribe(() => this.hide(info.id)); - } - private onCloseDone(info: OverlayInfo) { this.cleanUp(info); this.onClosed.emit({ id: info.id, componentRef: info.componentRef }); @@ -514,6 +501,7 @@ export class IgxOverlayService implements OnDestroy { if (this._overlayInfos.length === 0 && this._overlayElement && this._overlayElement.parentElement) { this._overlayElement.parentElement.removeChild(this._overlayElement); this._overlayElement = null; + this.removeCloseOnEscapeListener(); } } @@ -715,7 +703,7 @@ export class IgxOverlayService implements OnDestroy { } } - private addResizeHandler(id: string) { + private addResizeHandler() { const closingOverlaysCount = this._overlayInfos .filter(o => o.closeAnimationPlayer && o.closeAnimationPlayer.hasStarted()) @@ -725,7 +713,7 @@ export class IgxOverlayService implements OnDestroy { } } - private removeResizeHandler(id: string) { + private removeResizeHandler() { const closingOverlaysCount = this._overlayInfos .filter(o => o.closeAnimationPlayer && o.closeAnimationPlayer.hasStarted()) @@ -735,6 +723,26 @@ export class IgxOverlayService implements OnDestroy { } } + private addCloseOnEscapeListener(info: OverlayInfo) { + if (info.settings.closeOnEscape && !this._keyPressEventListener) { + this._keyPressEventListener = fromEvent(this._document, 'keydown').pipe( + filter((ev: KeyboardEvent) => ev.key === 'Escape' || ev.key === 'Esc') + ).subscribe(() => { + const targetOverlay = this._overlayInfos[this._overlayInfos.length - 1]; + if (targetOverlay.settings.closeOnEscape) { + this.hide(targetOverlay.id); + } + }); + } + } + + private removeCloseOnEscapeListener() { + if (this._keyPressEventListener) { + this._keyPressEventListener.unsubscribe(); + this._keyPressEventListener = null; + } + } + /** @hidden */ public repositionAll = () => { for (let i = this._overlayInfos.length; i--;) { diff --git a/projects/igniteui-angular/src/lib/services/overlay/utilities.ts b/projects/igniteui-angular/src/lib/services/overlay/utilities.ts index a78f9dd16a9..78674fc04ae 100644 --- a/projects/igniteui-angular/src/lib/services/overlay/utilities.ts +++ b/projects/igniteui-angular/src/lib/services/overlay/utilities.ts @@ -59,7 +59,7 @@ export interface OverlaySettings { /** Set if the overlay should close on outside click */ closeOnOutsideClick?: boolean; /** Set if the overlay should close when `Esc` key is pressed */ - closeOnEsc?: boolean; + closeOnEscape?: boolean; /** Set the outlet container to attach the overlay to */ outlet?: IgxOverlayOutletDirective | ElementRef; /** diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts index d92f5da0596..ee53c77345c 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts @@ -4,6 +4,14 @@ import { InteractionMode } from '../core/enums'; /** @hidden */ export const IGX_TIME_PICKER_COMPONENT = 'IgxTimePickerComponentToken'; +/** @hidden */ +export enum TimeParts { + Hour = 'hour', + Minute = 'minute', + Seconds = 'seconds', + amPM = 'ampm' +} + /** @hidden */ export interface IgxTimePickerBase { hourList: ElementRef; diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html index e411d1f35e9..7cdb5951497 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html @@ -4,7 +4,7 @@ access_time -
- {{ hour }} + {{ hour }}
- {{ minute }} + {{ minute }}
- {{ seconds }} + {{ seconds }}
- {{ ampm }} + {{ ampm }}
{ @@ -1667,6 +1668,53 @@ describe('IgxTimePicker', () => { expect(input).not.toEqual(document.activeElement); expect(dummyInput).toEqual(document.activeElement); })); + + it('should apply disabled style for time outside the min and max values', fakeAsync(() => { + timePicker = new IgxTimePickerComponent(null, null); + fixture.detectChanges(); + timePicker.format = 'hh:mm:ss tt'; + const date = new Date(2018, 10, 27, 9, 50, 58); + timePicker.value = date; + + timePicker.minValue = '09:15:10 AM'; + timePicker.maxValue = '11:15:10 AM'; + + timePicker.selectedHour = '06'; + timePicker.selectedMinute = '25'; + timePicker.selectedSeconds = '00'; + timePicker.selectedAmPm = 'AM'; + + fixture.detectChanges(); + + // The selected time is 06:25:00 AM + // Testing 09:25:00 AM + expect(timePicker.applyDisabledStyleForItem('hour', '9')).toBe(false); + + timePicker.selectedHour = '9'; // The selected time is 09:25:00 AM + + // Testing 10:25:00 AM + expect(timePicker.applyDisabledStyleForItem('hour', '10')).toBe(false); + + timePicker.selectedHour = '10'; + timePicker.selectedMinute = '10'; // The selected time is 10:10:00 AM + + // Testing 11:10:00 AM + expect(timePicker.applyDisabledStyleForItem('hour', '11')).toBe(false); + + timePicker.selectedHour = '11'; // The selected time is 11:10:00 AM + + // Testing 12:11:00 AM + expect(timePicker.applyDisabledStyleForItem('hour', '12')).toBe(true); + // Testing 11:28:00 AM + expect(timePicker.applyDisabledStyleForItem('minute', '28')).toBe(true); + // Testing 11:10:28 AM + expect(timePicker.applyDisabledStyleForItem('seconds', '28')).toBe(false); + + timePicker.selectedAmPm = 'PM'; // The selected time is 11:10:00 PM + + // Testing 11:10:00 AM + expect(timePicker.applyDisabledStyleForItem('ampm', 'AM')).toBe(false); + })); }); describe('Timepicker with outlet', () => { @@ -2248,7 +2296,7 @@ export class IgxTimePickerRetemplatedComponent { } ` }) export class IgxTimePickerDropDownComponent { - itemsDelta = { hours: 1, minutes: 5 }; + itemsDelta = { hours: 1, minutes: 5, seconds: 1 }; format = 'hh:mm tt'; isSpinLoop = true; isVertical = true; diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 116436f9b8a..39926a16cc1 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -37,7 +37,7 @@ import { } from './time-picker.directives'; import { Subject, fromEvent, interval, animationFrameScheduler, Subscription } from 'rxjs'; import { EditorProvider } from '../core/edit-provider'; -import { IgxTimePickerBase, IGX_TIME_PICKER_COMPONENT } from './time-picker.common'; +import { IgxTimePickerBase, IGX_TIME_PICKER_COMPONENT, TimeParts } from './time-picker.common'; import { AbsoluteScrollStrategy } from '../services/overlay/scroll'; import { AutoPositionStrategy } from '../services/overlay/position'; import { OverlaySettings } from '../services/overlay/utilities'; @@ -98,6 +98,7 @@ const noop = () => { }; }` ] }) + export class IgxTimePickerComponent implements IgxTimePickerBase, ControlValueAccessor, @@ -152,6 +153,10 @@ export class IgxTimePickerComponent implements this.onValidationFailed.emit(args); } } + /** + * @hidden @internal + */ + timeParts: any = Object.assign({}, TimeParts); /** * An accessor that returns the value of `igx-time-picker` component. @@ -507,10 +512,10 @@ export class IgxTimePickerComponent implements @ViewChild(IgxInputDirective, { read: ElementRef }) private _inputElementRef: ElementRef; - @ViewChild(IgxInputDirective, { read: IgxInputDirective}) + @ViewChild(IgxInputDirective, { read: IgxInputDirective }) private _inputDirective: IgxInputDirective; - @ContentChild(IgxInputDirective, { read: IgxInputDirective}) + @ContentChild(IgxInputDirective, { read: IgxInputDirective }) private _inputDirectiveUserTemplate: IgxInputDirective; @ViewChild(IgxInputGroupComponent, { read: IgxInputGroupComponent }) @@ -635,6 +640,46 @@ export class IgxTimePickerComponent implements } } + /** @hidden @internal */ + applyDisabledStyleForItem(period: string, value: string) { + if (!this.minValue || !this.maxValue) { + return false; + } + const minValueDate: Date = this.convertMinMaxValue(this.minValue); + const maxValueDate: Date = this.convertMinMaxValue(this.maxValue); + let hour: number = parseInt(this.selectedHour, 10); + let minute: number = parseInt(this.selectedMinute, 10); + let seconds: number = parseInt(this.selectedSeconds, 10); + let amPM: string = this.selectedAmPm; + const date = new Date(minValueDate); + switch (period) { + case TimeParts.Hour: + hour = parseInt(value, 10); + break; + + case TimeParts.Minute: + minute = parseInt(value, 10); + break; + + case TimeParts.Seconds: + seconds = parseInt(value, 10); + break; + + case TimeParts.amPM: + amPM = value; + break; + } + + if (amPM === 'PM') { + hour += 12; + } + date.setHours(hour); + date.setMinutes(minute); + date.setSeconds(seconds); + return date < minValueDate || date > maxValueDate; + + } + /** @hidden @internal */ public registerOnChange(fn: (_: Date) => void) { this._onChangeCallback = fn; } @@ -1267,7 +1312,11 @@ export class IgxTimePickerComponent implements return date; } - private _convertMinMaxValue(value: string): Date { + /** @hidden @internal */ + public convertMinMaxValue(value: string): Date { + if (!value) { + return; + } const date = this.value ? new Date(this.value) : this._dateFromModel ? new Date(this._dateFromModel) : new Date(); const sections = value.split(/[\s:]+/); let hour, minutes, seconds, amPM; @@ -1310,9 +1359,9 @@ export class IgxTimePickerComponent implements } private _isValueValid(value: Date): boolean { - if (this.maxValue && value > this._convertMinMaxValue(this.maxValue)) { + if (this.maxValue && value > this.convertMinMaxValue(this.maxValue)) { return false; - } else if (this.minValue && value < this._convertMinMaxValue(this.minValue)) { + } else if (this.minValue && value < this.convertMinMaxValue(this.minValue)) { return false; } else { return true; @@ -1484,7 +1533,7 @@ export class IgxTimePickerComponent implements private _onDropDownClosed(): void { const oldValue = this.value; - const newVal = this._convertMinMaxValue(this.displayValue); + const newVal = this.convertMinMaxValue(this.displayValue); if (this.displayValue === this.parseMask(false)) { return; @@ -1922,7 +1971,7 @@ export class IgxTimePickerComponent implements // timepicker own value property if it is a valid Date if (val.indexOf(this.promptChar) === -1) { if (this._isEntryValid(val)) { - const newVal = this._convertMinMaxValue(val); + const newVal = this.convertMinMaxValue(val); if (oldVal.getTime() !== newVal.getTime()) { this.value = newVal; } @@ -1970,7 +2019,7 @@ export class IgxTimePickerComponent implements if (value && value !== this.parseMask()) { if (this._isEntryValid(value)) { - const newVal = this._convertMinMaxValue(value); + const newVal = this.convertMinMaxValue(value); if (!this.value || this.value.getTime() !== newVal.getTime()) { this.value = newVal; } @@ -2007,8 +2056,8 @@ export class IgxTimePickerComponent implements let sign: number; let displayVal: string; const currentVal = new Date(this.value); - const min = this.minValue ? this._convertMinMaxValue(this.minValue) : this._convertMinMaxValue('00:00'); - const max = this.maxValue ? this._convertMinMaxValue(this.maxValue) : this._convertMinMaxValue('24:00'); + const min = this.minValue ? this.convertMinMaxValue(this.minValue) : this.convertMinMaxValue('00:00'); + const max = this.maxValue ? this.convertMinMaxValue(this.maxValue) : this.convertMinMaxValue('24:00'); const cursor = this._getCursorPosition(); diff --git a/src/app/calendar-views/calendar-views.sample.html b/src/app/calendar-views/calendar-views.sample.html index 611bb6d48b0..aa4deb60ea3 100644 --- a/src/app/calendar-views/calendar-views.sample.html +++ b/src/app/calendar-views/calendar-views.sample.html @@ -10,9 +10,11 @@

Month Picker

[formatViews]="formatViews" [formatOptions]="formatOptions" [locale]="localeDe" - (onSelection)="onSelection($event)"> + (onSelection)="onSelection($event)" + (viewDateChanged)="viewDateChanged($event)" + (activeViewChanged)="activeViewChanged($event)" > -
+ {{ date1 }} diff --git a/src/app/calendar-views/calendar-views.sample.ts b/src/app/calendar-views/calendar-views.sample.ts index d771d262c96..8904ded8cc3 100644 --- a/src/app/calendar-views/calendar-views.sample.ts +++ b/src/app/calendar-views/calendar-views.sample.ts @@ -3,7 +3,8 @@ import { IgxCalendarComponent, DateRangeType, IgxDaysViewComponent, - IgxMonthPickerComponent + IgxMonthPickerComponent, + CalendarView } from 'igniteui-angular'; @Component({ @@ -99,4 +100,12 @@ export class CalendarViewsSampleComponent implements OnInit { this.daysView.deselectDate(new Date(2019, 1, 13)); // this.daysView.deselectDate([new Date(2019, 1, 13), new Date(2019, 1, 14)]); } + + public viewDateChanged(event: Date) { + const date = event; + console.log(date); + } + + public activeViewChanged(event: CalendarView) { + } } diff --git a/src/app/calendar/calendar.sample.html b/src/app/calendar/calendar.sample.html index f61ed677203..6b7a4eb67db 100644 --- a/src/app/calendar/calendar.sample.html +++ b/src/app/calendar/calendar.sample.html @@ -24,7 +24,7 @@

Default Calendar

- + diff --git a/src/app/calendar/calendar.sample.ts b/src/app/calendar/calendar.sample.ts index 937ad84d53c..7cf35edadfa 100644 --- a/src/app/calendar/calendar.sample.ts +++ b/src/app/calendar/calendar.sample.ts @@ -1,5 +1,6 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { IgxCalendarComponent, DateRangeDescriptor, DateRangeType } from 'igniteui-angular'; +import { IgxCalendarComponent, DateRangeType, CalendarView } from 'igniteui-angular'; +import { IViewDateChangeEventArgs } from '../../../projects/igniteui-angular/src/lib/calendar/calendar-base'; @Component({ selector: 'app-calendar-sample', @@ -36,6 +37,17 @@ export class CalendarSampleComponent implements OnInit { this.calendar.hideOutsideDays = !this.calendar.hideOutsideDays; } + public onSelection(event: Date) { + const date = event; + } + + public viewDateChanged(event: IViewDateChangeEventArgs) { + console.log(event); + } + + public activeViewChanged(event: CalendarView) { + } + public setSelection(args: string) { this.calendar.selection = args; } diff --git a/src/app/grid-groupby/grid-groupby.sample.html b/src/app/grid-groupby/grid-groupby.sample.html index 4c95bd88be2..c90588152a2 100644 --- a/src/app/grid-groupby/grid-groupby.sample.html +++ b/src/app/grid-groupby/grid-groupby.sample.html @@ -1,11 +1,15 @@
Toggle Hiding Of Grouped Columns +
+ Toggle Summary Position +
+ - Show summary on collapse
+ [height]="'700px'" [(groupingExpansionState)]='expState' [rowSelection]='selectionMode' [summaryCalculationMode]="summaryMode" [summaryPosition]='position'> diff --git a/src/app/grid-groupby/grid-groupby.sample.ts b/src/app/grid-groupby/grid-groupby.sample.ts index 1e1b292b604..941943e7a26 100644 --- a/src/app/grid-groupby/grid-groupby.sample.ts +++ b/src/app/grid-groupby/grid-groupby.sample.ts @@ -2,7 +2,7 @@ import { Component, ViewChild, OnInit, Inject } from '@angular/core'; import { IgxGridComponent, SortingDirection, ISortingExpression, - DefaultSortingStrategy, DisplayDensity, IDisplayDensityOptions, DisplayDensityToken, GridSelectionMode + DefaultSortingStrategy, DisplayDensity, IDisplayDensityOptions, DisplayDensityToken, GridSelectionMode, GridSummaryPosition } from 'igniteui-angular'; @Component({ @@ -21,6 +21,7 @@ export class GridGroupBySampleComponent implements OnInit { public summaryMode = 'rootLevelOnly'; public summaryModes = []; public selectionMode; + public position = GridSummaryPosition.top; constructor(@Inject(DisplayDensityToken) public displayDensityOptions: IDisplayDensityOptions) {} public ngOnInit(): void { this.columns = [ @@ -97,6 +98,10 @@ export class GridGroupBySampleComponent implements OnInit { toggleGroupedVisibility(event){ this.grid1.hideGroupedColumns = !event.checked; } + + toggleSummaryPosition($event) { + this.position = this.position === GridSummaryPosition.top ? GridSummaryPosition.bottom : GridSummaryPosition.top; + } toggleDensity() { switch (this.displayDensityOptions.displayDensity ) { case DisplayDensity.comfortable: this.displayDensityOptions.displayDensity = DisplayDensity.compact; break; diff --git a/src/app/input/input.sample.html b/src/app/input/input.sample.html index e86596d0ebb..b97236aecd0 100644 --- a/src/app/input/input.sample.html +++ b/src/app/input/input.sample.html @@ -19,6 +19,11 @@

Personal Info

+ + + + + diff --git a/src/app/input/input.sample.ts b/src/app/input/input.sample.ts index ae88b988b15..5164963a5b2 100644 --- a/src/app/input/input.sample.ts +++ b/src/app/input/input.sample.ts @@ -15,7 +15,8 @@ export class InputSampleComponent { lastName: 'Doe', password: '1337s3cr3t', registered: false, - subscribed: false + subscribed: false, + dateOfBirth: new Date('07 July, 1987') }; user2 = { @@ -25,9 +26,11 @@ export class InputSampleComponent { lastName: 'Doe', password: '1337s3cr3t', registered: true, - subscribed: false + subscribed: false, + dateOfBirth: new Date('01 July, 1954') }; + settings = [{ name: 'WiFi', icon: 'wifi', diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index 875109c1a8b..8b75d6f6ab4 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -7,7 +7,7 @@

Time Picker with Dropdown

{{showDate(date)}}
- diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index 25e8aa4e1f1..fc71d87cc31 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -11,13 +11,13 @@ export class TimePickerSampleComponent implements AfterViewInit { min = '09:00'; itemsDelta = { hours: 1, minutes: 5 }; - format = 'hh:mm tt'; + format = 'hh:mm:ss tt'; isSpinLoop = true; isVertical = true; mode = InteractionMode.DropDown; date1 = new Date(2018, 10, 27, 17, 45, 0, 0); - date = new Date(2018, 10, 27, 21, 45, 0, 0); + date = new Date(2018, 10, 27, 9, 45, 0, 0); val = new Date(0, 0, 0, 19, 35, 30, 0); today = new Date(Date.now()); diff --git a/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.html b/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.html index 539a94fe943..6e0c217d5d0 100644 --- a/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.html +++ b/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.html @@ -11,6 +11,7 @@
+