Skip to content

build: add most components to kitchen-sink #4836

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/lib/chips/chip-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ describe('MdChipList', () => {
manager = chipListInstance._keyManager;
});

it('focuses the first chip on focus', () => {
it('should focus the first chip on focus', () => {
chipListInstance.focus();
fixture.detectChanges();

expect(manager.activeItemIndex).toBe(0);
});

it('watches for chip focus', () => {
it('should watch for chip focus', () => {
let array = chips.toArray();
let lastIndex = array.length - 1;
let lastItem = array[lastIndex];
Expand All @@ -81,13 +81,13 @@ describe('MdChipList', () => {
expect(manager.activeItemIndex).toEqual(2);
});

it('focuses the previous item', () => {
it('should focus the previous item', () => {
let array = chips.toArray();
let lastIndex = array.length - 1;
let lastItem = array[lastIndex];

// Focus the last item
lastItem.focus();
// Focus the last item by fake updating the _hasFocus state for unit tests.
lastItem._hasFocus = true;

// Destroy the last item
testComponent.remove = lastIndex;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/chips/chip-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export class MdChipList implements AfterContentInit, OnDestroy {
chip.destroy.subscribe(() => {
let chipIndex: number = this.chips.toArray().indexOf(chip);

if (this._isValidIndex(chipIndex)) {
if (this._isValidIndex(chipIndex) && chip._hasFocus) {
// Check whether the chip is the last item
if (chipIndex < this.chips.length - 1) {
this._keyManager.setActiveItem(chipIndex);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/chips/chip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('Chips', () => {

chipDebugElement = fixture.debugElement.query(By.directive(MdChip));
chipNativeElement = chipDebugElement.nativeElement;
chipInstance = chipDebugElement.componentInstance;
chipInstance = chipDebugElement.injector.get(MdChip);

document.body.appendChild(chipNativeElement);
});
Expand All @@ -56,7 +56,7 @@ describe('Chips', () => {
chipDebugElement = fixture.debugElement.query(By.directive(MdChip));
chipListNativeElement = fixture.debugElement.query(By.directive(MdChipList)).nativeElement;
chipNativeElement = chipDebugElement.nativeElement;
chipInstance = chipDebugElement.componentInstance;
chipInstance = chipDebugElement.injector.get(MdChip);
testComponent = fixture.debugElement.componentInstance;

document.body.appendChild(chipNativeElement);
Expand Down
98 changes: 36 additions & 62 deletions src/lib/chips/chip.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {
Component,
Directive,
ElementRef,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
Renderer2,
} from '@angular/core';
Expand All @@ -24,34 +23,52 @@ export class MdChipBase {
export const _MdChipMixinBase = mixinColor(MdChipBase, 'primary');


/**
* Dummy directive to add CSS class to basic chips.
* @docs-private
*/
@Directive({
selector: `md-basic-chip, [md-basic-chip], mat-basic-chip, [mat-basic-chip]`,
host: {'class': 'mat-basic-chip'}
})
export class MdBasicChip { }

/**
* Material design styled Chip component. Used inside the MdChipList component.
*/
@Component({
@Directive({
selector: `md-basic-chip, [md-basic-chip], md-chip, [md-chip],
mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]`,
template: `<ng-content></ng-content>`,
inputs: ['color'],
host: {
'[class.mat-chip]': 'true',
'class': 'mat-chip',
'tabindex': '-1',
'role': 'option',

'[class.mat-chip-selected]': 'selected',
'[attr.disabled]': 'disabled',
'[attr.aria-disabled]': '_isAriaDisabled',

'(click)': '_handleClick($event)'
'[attr.disabled]': 'disabled || null',
'[attr.aria-disabled]': '_isAriaDisabled()',
'(click)': '_handleClick($event)',
'(focus)': '_hasFocus = true',
'(blur)': '_hasFocus = false',
}
})
export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDestroy, CanColor {

/** Whether or not the chip is disabled. Disabled chips cannot be focused. */
export class MdChip extends _MdChipMixinBase implements Focusable, OnDestroy, CanColor {
/** Whether or not the chip is disabled. */
@Input() get disabled(): boolean { return this._disabled; }
set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value); }
protected _disabled: boolean = null;

/** Whether or not the chip is selected. */
/** Whether the chip is selected. */
@Input() get selected(): boolean { return this._selected; }
set selected(value: boolean) {
this._selected = coerceBooleanProperty(value);
(this.selected ? this.select : this.deselect).emit({chip: this});
}
protected _selected: boolean = false;

/** Whether the chip has focus. */
_hasFocus: boolean = false;

/** Emitted when the chip is focused. */
onFocus = new EventEmitter<MdChipEvent>();

Expand All @@ -68,44 +85,10 @@ export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDes
super(renderer, elementRef);
}

ngOnInit(): void {
this._addDefaultCSSClass();
}

ngOnDestroy(): void {
this.destroy.emit({chip: this});
}

/** Whether or not the chip is disabled. */
@Input() get disabled(): boolean {
return this._disabled;
}

/** Sets the disabled state of the chip. */
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value) ? true : null;
}

/** A String representation of the current disabled state. */
get _isAriaDisabled(): string {
return String(coerceBooleanProperty(this.disabled));
}

/** Whether or not this chip is selected. */
@Input() get selected(): boolean {
return this._selected;
}

set selected(value: boolean) {
this._selected = coerceBooleanProperty(value);

if (this._selected) {
this.select.emit({chip: this});
} else {
this.deselect.emit({chip: this});
}
}

/**
* Toggles the current selected state of this chip.
* @return Whether the chip is selected.
Expand All @@ -121,6 +104,11 @@ export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDes
this.onFocus.emit({chip: this});
}

/** The aria-disabled state for the chip */
_isAriaDisabled(): string {
return String(this.disabled);
}

/** Ensures events fire properly upon click. */
_handleClick(event: Event) {
// Check disabled
Expand All @@ -131,18 +119,4 @@ export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDes
this.focus();
}
}

/** Initializes the appropriate CSS classes based on the chip type (basic or standard). */
private _addDefaultCSSClass() {
let el: HTMLElement = this._elementRef.nativeElement;

// Always add the `mat-chip` class
this._renderer.addClass(el, 'mat-chip');

// If we are a basic chip, also add the `mat-basic-chip` class for :not() targeting
if (el.nodeName.toLowerCase() == 'mat-basic-chip' || el.hasAttribute('mat-basic-chip') ||
el.nodeName.toLowerCase() == 'md-basic-chip' || el.hasAttribute('md-basic-chip')) {
this._renderer.addClass(el, 'mat-basic-chip');
}
}
}
6 changes: 3 additions & 3 deletions src/lib/chips/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {NgModule} from '@angular/core';
import {MdChipList} from './chip-list';
import {MdChip} from './chip';
import {MdChip, MdBasicChip} from './chip';


@NgModule({
imports: [],
exports: [MdChipList, MdChip],
declarations: [MdChipList, MdChip]
exports: [MdChipList, MdChip, MdBasicChip],
declarations: [MdChipList, MdChip, MdBasicChip]
})
export class MdChipsModule {}

Expand Down
15 changes: 13 additions & 2 deletions src/lib/core/a11y/focus-trap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {
Injectable,
} from '@angular/core';
import {InteractivityChecker} from './interactivity-checker';
import {Platform} from '../platform/platform';
import {coerceBooleanProperty} from '../coercion/boolean-property';

import 'rxjs/add/operator/first';


/**
* Class that allows for trapping focus within a DOM element.
*
Expand All @@ -37,6 +39,7 @@ export class FocusTrap {

constructor(
private _element: HTMLElement,
private _platform: Platform,
private _checker: InteractivityChecker,
private _ngZone: NgZone,
deferAnchors = false) {
Expand Down Expand Up @@ -64,6 +67,11 @@ export class FocusTrap {
* in the constructor, but can be deferred for cases like directives with `*ngIf`.
*/
attachAnchors(): void {
// If we're not on the browser, there can be no focus to trap.
if (!this._platform.isBrowser) {
return;
}

if (!this._startAnchor) {
this._startAnchor = this._createAnchor();
}
Expand Down Expand Up @@ -223,10 +231,13 @@ export class FocusTrap {
/** Factory that allows easy instantiation of focus traps. */
@Injectable()
export class FocusTrapFactory {
constructor(private _checker: InteractivityChecker, private _ngZone: NgZone) { }
constructor(
private _checker: InteractivityChecker,
private _platform: Platform,
private _ngZone: NgZone) { }

create(element: HTMLElement, deferAnchors = false): FocusTrap {
return new FocusTrap(element, this._checker, this._ngZone, deferAnchors);
return new FocusTrap(element, this._platform, this._checker, this._ngZone, deferAnchors);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/lib/core/a11y/interactivity-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export class InteractivityChecker {
* @returns Whether the element is tabbable.
*/
isTabbable(element: HTMLElement): boolean {
// Nothing is tabbable on the the server 😎
Copy link
Member

@devversion devversion May 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a lint rule for that! 😄 (joke)

if (!this._platform.isBrowser) {
return false;
}

let frameElement = getWindow(element).frameElement as HTMLElement;

Expand Down
82 changes: 49 additions & 33 deletions src/lib/core/platform/features.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,56 @@
/** Cached result Set of input types support by the current browser. */
let supportedInputTypes: Set<string>;

/** Types of <input> that *might* be supported. */
const candidateInputTypes = [
// `color` must come first. Chrome 56 shows a warning if we change the type to `color` after
// first changing it to something else:
// The specified value "" does not conform to the required format.
// The format is "#rrggbb" where rr, gg, bb are two-digit hexadecimal numbers.
'color',
'button',
'checkbox',
'date',
'datetime-local',
'email',
'file',
'hidden',
'image',
'month',
'number',
'password',
'radio',
'range',
'reset',
'search',
'submit',
'tel',
'text',
'time',
'url',
'week',
];

/** @returns The input types supported by this browser. */
export function getSupportedInputTypes(): Set<string> {
if (!supportedInputTypes) {
let featureTestInput = document.createElement('input');
supportedInputTypes = new Set([
// `color` must come first. Chrome 56 shows a warning if we change the type to `color` after
// first changing it to something else:
// The specified value "" does not conform to the required format.
// The format is "#rrggbb" where rr, gg, bb are two-digit hexadecimal numbers.
'color',
'button',
'checkbox',
'date',
'datetime-local',
'email',
'file',
'hidden',
'image',
'month',
'number',
'password',
'radio',
'range',
'reset',
'search',
'submit',
'tel',
'text',
'time',
'url',
'week',
].filter(value => {
featureTestInput.setAttribute('type', value);
return featureTestInput.type === value;
}));
// Result is cached.
if (supportedInputTypes) {
return supportedInputTypes;
}

// We can't check if an input type is not supported until we're on the browser, so say that
// everything is supported when not on the browser. We don't use `Platform` here since it's
// just a helper function and can't inject it.
if (typeof document !== 'object' || !document) {
supportedInputTypes = new Set(candidateInputTypes);
return supportedInputTypes;
}

let featureTestInput = document.createElement('input');
supportedInputTypes = new Set(candidateInputTypes.filter(value => {
featureTestInput.setAttribute('type', value);
return featureTestInput.type === value;
}));

return supportedInputTypes;
}
4 changes: 2 additions & 2 deletions src/lib/datepicker/datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export class MdDatepicker<D> implements OnDestroy {
config.viewContainerRef = this._viewContainerRef;

this._dialogRef = this._dialog.open(MdDatepickerContent, config);
this._dialogRef.afterClosed().first().subscribe(() => this.close());
this._dialogRef.afterClosed().subscribe(() => this.close());
this._dialogRef.componentInstance.datepicker = this;
}

Expand All @@ -257,7 +257,7 @@ export class MdDatepicker<D> implements OnDestroy {
this._ngZone.onStable.first().subscribe(() => this._popupRef.updatePosition());
}

this._popupRef.backdropClick().first().subscribe(() => this.close());
this._popupRef.backdropClick().subscribe(() => this.close());
}

/** Create the popup. */
Expand Down
Loading