From 8ad4306fbb12d446fd8ccb605f9f6dc64cf305dc Mon Sep 17 00:00:00 2001 From: gjdev Date: Fri, 1 Jun 2018 10:51:17 +0200 Subject: [PATCH 1/3] feat(chips): chip and chip-sets --- .../components/chips/mdc.chip-set.adapter.ts | 8 + .../src/components/chips/mdc.chip.adapter.ts | 18 + .../chips/mdc.chip.directive.spec.ts | 425 ++++++++++++++++ .../components/chips/mdc.chip.directive.ts | 468 ++++++++++++++++++ bundle/src/material.module.ts | 4 + bundle/tools/ngbundler/index.ts | 1 + 6 files changed, 924 insertions(+) create mode 100644 bundle/src/components/chips/mdc.chip-set.adapter.ts create mode 100644 bundle/src/components/chips/mdc.chip.adapter.ts create mode 100644 bundle/src/components/chips/mdc.chip.directive.spec.ts create mode 100644 bundle/src/components/chips/mdc.chip.directive.ts diff --git a/bundle/src/components/chips/mdc.chip-set.adapter.ts b/bundle/src/components/chips/mdc.chip-set.adapter.ts new file mode 100644 index 0000000..22c70f8 --- /dev/null +++ b/bundle/src/components/chips/mdc.chip-set.adapter.ts @@ -0,0 +1,8 @@ +/** @docs-private */ +export interface MdcChipSetAdapter { + hasClass: (className: string) => boolean, + registerInteractionHandler: (evtType: string, handler: EventListener) => void, + deregisterInteractionHandler: (evtType: string, handler: EventListener) => void, + appendChip: (text: string, leadingIcon: Element, trailingIcon: Element) => Element, + removeChip: (chip: any) => void +} diff --git a/bundle/src/components/chips/mdc.chip.adapter.ts b/bundle/src/components/chips/mdc.chip.adapter.ts new file mode 100644 index 0000000..ab17569 --- /dev/null +++ b/bundle/src/components/chips/mdc.chip.adapter.ts @@ -0,0 +1,18 @@ +/** @docs-private */ +export interface MdcChipAdapter { + addClass: (className: string) => void, + removeClass: (className: string) => void, + hasClass: (className: string) => boolean, + addClassToLeadingIcon: (className: string) => void, + removeClassFromLeadingIcon: (className: string) => void, + eventTargetHasClass: (target: EventTarget, className: string) => boolean, + registerEventHandler: (evtType: string, handler: EventListener) => void, + deregisterEventHandler: (evtType: string, handler: EventListener) => void, + registerTrailingIconInteractionHandler: (evtType: string, handler: EventListener) => void, + deregisterTrailingIconInteractionHandler: (evtType: string, handler: EventListener) => void, + notifyInteraction: () => void, + notifyTrailingIconInteraction: () => void, + notifyRemoval: () => void, + getComputedStyleValue: (propertyName: string) => string, + setStyleProperty: (propertyName: string, value: string) => void +} diff --git a/bundle/src/components/chips/mdc.chip.directive.spec.ts b/bundle/src/components/chips/mdc.chip.directive.spec.ts new file mode 100644 index 0000000..052e6eb --- /dev/null +++ b/bundle/src/components/chips/mdc.chip.directive.spec.ts @@ -0,0 +1,425 @@ +import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Component, DebugElement } from '@angular/core'; +import { MdcChipSetDirective, MdcChipDirective, MdcChipIconDirective, CHIP_DIRECTIVES } from './mdc.chip.directive'; +import { booleanAttributeStyleTest, hasRipple } from '../../testutils/page.test'; + +describe('MdcChipDirective', () => { + @Component({ + template: ` +
+
+ event +
{{chip}}
+ cancel +
+
+ ` + }) + class TestComponent { + includeLeadingIcon = false; + includeTrailingIcon = false; + type = 'action'; + chips = ['chippie', 'chappie', 'choppie']; + interactions = []; + trailingIconInteractions = []; + valueChanges = []; + interact(chip: string) { + this.interactions.push(chip); + } + trailingIconInteract(chip: string) { + this.trailingIconInteractions.push(chip); + } + resetInteractions() { + this.interactions = []; + this.trailingIconInteractions = []; + } + remove(i: number) { + this.chips.splice(i, 1); + } + valueChange(chip: string, value: boolean) { + this.valueChanges.push({chip: chip, value: value}); + } + } + + function setup(testComponentType: any = TestComponent) { + const fixture = TestBed.configureTestingModule({ + declarations: [...CHIP_DIRECTIVES, testComponentType] + }).createComponent(testComponentType); + fixture.detectChanges(); + return { fixture }; + } + + it('apply correct styles for chip and chip-set', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + const chipSet: HTMLElement = fixture.nativeElement.querySelector('#set'); + expect(chipSet.classList).toContain('mdc-chip-set'); + const chips = chipSet.querySelectorAll('.mdc-chip'); + expect(chips.length).toBe(3); + for (let i = 0; i !== chips.length; ++i) { + let icons = chips.item(i).querySelectorAll('i'); + expect(icons.length).toBe(0); + } + testComponent.includeLeadingIcon = true; + testComponent.includeTrailingIcon = true; + fixture.detectChanges(); + for (let i = 0; i !== chips.length; ++i) { + let icons = chips.item(i).querySelectorAll('i'); + expect(icons.length).toBe(2); + expect(icons.item(0).classList).toContain('mdc-chip__icon'); + expect(icons.item(1).classList).toContain('mdc-chip__icon'); + expect(icons.item(0).classList).toContain('mdc-chip__icon--leading'); + expect(icons.item(1).classList).toContain('mdc-chip__icon--trailing'); + } + })); + + it('chips have ripples', fakeAsync(() => { + const { fixture } = setup(); + const chips = fixture.nativeElement.querySelectorAll('.mdc-chip'); + expect(chips.length).toBe(3); + for (let i = 0; i !== chips.length; ++i) + expect(hasRipple(chips.item(i))).toBe(true); + })); + + it('chipset type is one of choice, filter, input, or action', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + const chipSetComponent = fixture.debugElement.query(By.directive(MdcChipSetDirective)).injector.get(MdcChipSetDirective); + const chipSet: HTMLElement = fixture.nativeElement.querySelector('#set'); + + expect(chipSetComponent.mdcChipSet).toBe('action'); + expect(chipSet.getAttribute('class')).toBe('mdc-chip-set'); + + for (let type of ['choice', 'filter', 'input']) { + testComponent.type = type; + fixture.detectChanges(); + expect(chipSetComponent.mdcChipSet).toBe(type); + expect(chipSet.classList).toContain('mdc-chip-set--' + type); + } + + testComponent.type = 'invalid'; + fixture.detectChanges(); + expect(chipSetComponent.mdcChipSet).toBe('action'); + expect(chipSet.getAttribute('class')).toBe('mdc-chip-set'); + })); + + it('trailing icons get a tabindex and role=button', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.chips = ['chip']; + testComponent.includeLeadingIcon = true; + testComponent.includeTrailingIcon = true; + fixture.detectChanges(); + let icons = fixture.nativeElement.querySelectorAll('i'); + expect(icons.length).toBe(2); + expect(icons[0].tabIndex).toBe(-1); + expect(icons[0].hasAttribute('role')).toBe(false); + expect(icons[1].tabIndex).toBe(0); + expect(icons[1].getAttribute('role')).toBe('button'); + + // role/tabIndex changes must be undone when the icon is not a trailing icon anymore: + const trailingIcon = fixture.debugElement.queryAll(By.directive(MdcChipIconDirective))[1].injector.get(MdcChipIconDirective); + expect(trailingIcon._elm.nativeElement).toBe(icons[1]); + trailingIcon._trailing = false; + expect(icons[1].tabIndex).toBe(-1); + expect(icons[1].hasAttribute('role')).toBe(false); + })); + + it('unreachable code for our implementation must throw errors', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.chips = ['chip']; + fixture.detectChanges(); + const chipSetComponent = fixture.debugElement.query(By.directive(MdcChipSetDirective)).injector.get(MdcChipSetDirective); + expect(() => {chipSetComponent._adapter.appendChip(null, null, null); }).toThrowError(); + expect(() => {chipSetComponent._adapter.removeChip(null); }).toThrowError(); + })); + + it('click action chip triggers interaction event', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.chips = ['chip']; + fixture.detectChanges(); + const chip = fixture.nativeElement.querySelector('.mdc-chip'); + const trailingIcon = fixture.nativeElement.querySelector + + expect(testComponent.interactions).toEqual([]); + expect(testComponent.trailingIconInteractions).toEqual([]); + + chip.click(); + expect(testComponent.interactions).toEqual(['chip']); + expect(testComponent.trailingIconInteractions).toEqual([]); + })); + + it('trailing icon interactions trigger interaction and remove events', fakeAsync(() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.type = 'input'; + testComponent.chips = ['chip']; + testComponent.includeTrailingIcon = true; + fixture.detectChanges(); + const chip = fixture.nativeElement.querySelector('.mdc-chip'); + const trailingIcon = fixture.nativeElement.querySelector('.mdc-chip i:last-child'); + const chipComponent = fixture.debugElement.query(By.directive(MdcChipDirective)).injector.get(MdcChipDirective); + + expect(testComponent.interactions).toEqual([]); + expect(testComponent.trailingIconInteractions).toEqual([]); + + trailingIcon.click(); + expect(testComponent.interactions).toEqual([]); + expect(testComponent.trailingIconInteractions).toEqual(['chip']); + // simulate transitionend event for exit transition of chip: + (chipComponent._foundation).handleTransitionEnd_({target: chip, propertyName: 'opacity'}); + tick(20); // wait for requestAnimationFrame + (chipComponent._foundation).handleTransitionEnd_({target: chip, propertyName: 'width'}); + expect(testComponent.chips).toEqual([]); + })); + + it('filter chips get a checkmark icon on selection', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.type = 'filter'; + fixture.detectChanges(); + const chips = fixture.nativeElement.querySelectorAll('.mdc-chip'); + const svgs = fixture.nativeElement.querySelectorAll('svg'); + + expect(svgs.length).toBe(3); + expect(chips[0].classList.contains('mdc-chip--selected')).toBe(false); + expect(chips[1].classList.contains('mdc-chip--selected')).toBe(false); + chips[0].click(); + expect(chips[0].classList).toContain('mdc-chip--selected'); + expect(chips[1].classList.contains('mdc-chip--selected')).toBe(false); + chips[1].click(); + expect(chips[0].classList).toContain('mdc-chip--selected'); + expect(chips[1].classList).toContain('mdc-chip--selected'); + chips[0].click(); + expect(chips[0].classList.contains('mdc-chip--selected')).toBe(false); + expect(chips[1].classList).toContain('mdc-chip--selected'); + })); + + it('filter chips selected value changes on clicks', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.type = 'filter'; + fixture.detectChanges(); + const chips = fixture.nativeElement.querySelectorAll('.mdc-chip'); + + expect(testComponent.valueChanges).toEqual([]); + chips[0].click(); + chips[1].click(); + chips[0].click(); + expect(testComponent.valueChanges).toEqual([ + {chip: 'chippie', value: true}, + {chip: 'chappie', value: true}, + {chip: 'chippie', value: false} + ]); + })); + + it('choice chips selected value changes on clicks and clicks of other choices', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.type = 'choice'; + fixture.detectChanges(); + const chips = fixture.nativeElement.querySelectorAll('.mdc-chip'); + + expect(testComponent.valueChanges).toEqual([]); + chips[0].click(); + expect(testComponent.valueChanges).toEqual([{chip: 'chippie', value: true}]); + testComponent.valueChanges = []; + chips[1].click(); + expect(testComponent.valueChanges).toEqual([{chip: 'chippie', value: false}, {chip: 'chappie', value: true}]); + testComponent.valueChanges = []; + chips[1].click(); + expect(testComponent.valueChanges).toEqual([{chip: 'chappie', value: false}]); + })); + + it('filter/choice chips selected state can be changed', (() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.type = 'choice'; + fixture.detectChanges(); + const chips = fixture.nativeElement.querySelectorAll('.mdc-chip'); + const chipComponents = fixture.debugElement.queryAll(By.directive(MdcChipDirective)).map(de => de.injector.get(MdcChipDirective)); + + expect(chipComponents[0].selected).toBe(false); + chipComponents[0].selected = true; + expect(testComponent.valueChanges).toEqual([{chip: 'chippie', value: true}]); + testComponent.valueChanges = []; + + chipComponents[1].selected = true; + expect(testComponent.valueChanges).toEqual([{chip: 'chippie', value: false}, {chip: 'chappie', value: true}]); + expect(chipComponents[0].selected).toBe(false); + testComponent.valueChanges = []; + + chipComponents[1].selected = true; // no change shouldn't trigger change events + expect(testComponent.valueChanges).toEqual([]); + expect(chipComponents[0].selected).toBe(false); + expect(chipComponents[1].selected).toBe(true); + + chipComponents[1].selected = false; + expect(testComponent.valueChanges).toEqual([{chip: 'chappie', value: false}]); + expect(chipComponents[0].selected).toBe(false); + expect(chipComponents[1].selected).toBe(false); + })); + + it('filter chips hide their leading icon on selection (to make place for the checkmark)', fakeAsync(() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.type = 'filter'; + testComponent.chips = ['chip']; + testComponent.includeLeadingIcon = true; + fixture.detectChanges(); + const chip = fixture.nativeElement.querySelector('.mdc-chip'); + const chipComponent = fixture.debugElement.query(By.directive(MdcChipDirective)).injector.get(MdcChipDirective); + const svg = fixture.nativeElement.querySelector('svg'); + const icon = fixture.nativeElement.querySelector('i'); + + expect(icon.classList.contains('mdc-chip__icon--leading-hidden')).toBe(false); + + chip.click(); + // simulate transitionend event for hide animation of icon: + (chipComponent._foundation).handleTransitionEnd_({target: icon, propertyName: 'opacity'}); + expect(icon.classList.contains('mdc-chip__icon--leading-hidden')).toBe(true); + + chip.click(); + // simulate transitionend event for hide animation of checkmark: + (chipComponent._foundation).handleTransitionEnd_({target: svg.parentElement, propertyName: 'opacity'}); + expect(icon.classList.contains('mdc-chip__icon--leading-hidden')).toBe(false); + })); + + it('computeRippleBoundingRect returns correct values', fakeAsync(() => { + const { fixture } = setup(); + const testComponent = fixture.debugElement.injector.get(TestComponent); + testComponent.type = 'filter'; + testComponent.chips = ['chip']; + testComponent.includeLeadingIcon = true; + fixture.detectChanges(); + + let chipComponent = fixture.debugElement.query(By.directive(MdcChipDirective)).injector.get(MdcChipDirective); + let rect = chipComponent['computeRippleBoundingRect'](); + expect(rect.width).toBeGreaterThan(rect.height); + + testComponent.includeLeadingIcon = false; + fixture.detectChanges(); + chipComponent = fixture.debugElement.query(By.directive(MdcChipDirective)).injector.get(MdcChipDirective); + rect = chipComponent['computeRippleBoundingRect'](); + expect(rect.width).toBe(rect.height); + })); + + @Component({ + template: ` +
+
+ + {{icon}} + +
+ {{icon}} +
+
{{chip}}
+
+
{{chip}}
+
+ + {{icon}} + +
+ {{icon}} +
+
+
+ ` + }) + class TestIconsComponent { + contained = false; + beforeIcons = []; + afterIcons = []; + } + it('leading/trailing icons are detected properly', (() => { + const { fixture } = setup(TestIconsComponent); + const testComponent = fixture.debugElement.injector.get(TestIconsComponent); + + let icons = fixture.nativeElement.querySelectorAll('i'); + expect(icons.length).toBe(0); + + testComponent.afterIcons = ['cancel']; + fixture.detectChanges(); + icons = fixture.nativeElement.querySelectorAll('i'); + expect(icons.length).toBe(1); + expect(icons[0].classList.contains('mdc-chip__icon--leading')).toBe(false); + expect(icons[0].classList.contains('mdc-chip__icon--trailing')).toBe(true); + testComponent.contained = true; + fixture.detectChanges(); + icons = fixture.nativeElement.querySelectorAll('i'); + expect(icons.length).toBe(1); + expect(icons[0].classList.contains('mdc-chip__icon--leading')).toBe(false); + expect(icons[0].classList.contains('mdc-chip__icon--trailing')).toBe(true); + + testComponent.beforeIcons = ['event']; + testComponent.afterIcons = []; + fixture.detectChanges(); + icons = fixture.nativeElement.querySelectorAll('i'); + expect(icons.length).toBe(1); + expect(icons[0].classList.contains('mdc-chip__icon--leading')).toBe(true); + expect(icons[0].classList.contains('mdc-chip__icon--trailing')).toBe(false); + testComponent.contained = true; + fixture.detectChanges(); + icons = fixture.nativeElement.querySelectorAll('i'); + expect(icons.length).toBe(1); + expect(icons[0].classList.contains('mdc-chip__icon--leading')).toBe(true); + expect(icons[0].classList.contains('mdc-chip__icon--trailing')).toBe(false); + + testComponent.beforeIcons = ['event', 'event']; + testComponent.afterIcons = ['cancel', 'cancel']; + testComponent.contained = false; + fixture.detectChanges(); + icons = fixture.nativeElement.querySelectorAll('i'); + expect(icons.length).toBe(4); + expect(icons[0].classList.contains('mdc-chip__icon--leading')).toBe(true); + expect(icons[0].classList.contains('mdc-chip__icon--trailing')).toBe(false); + expect(icons[1].classList.contains('mdc-chip__icon--leading')).toBe(false); + expect(icons[1].classList.contains('mdc-chip__icon--trailing')).toBe(false); + expect(icons[2].classList.contains('mdc-chip__icon--leading')).toBe(false); + expect(icons[2].classList.contains('mdc-chip__icon--trailing')).toBe(false); + expect(icons[3].classList.contains('mdc-chip__icon--leading')).toBe(false); + expect(icons[3].classList.contains('mdc-chip__icon--trailing')).toBe(true); + + testComponent.contained = true; + fixture.detectChanges(); + icons = fixture.nativeElement.querySelectorAll('i'); + expect(icons.length).toBe(4); + expect(icons[0].classList.contains('mdc-chip__icon--leading')).toBe(true); + expect(icons[0].classList.contains('mdc-chip__icon--trailing')).toBe(false); + expect(icons[1].classList.contains('mdc-chip__icon--leading')).toBe(false); + expect(icons[1].classList.contains('mdc-chip__icon--trailing')).toBe(false); + expect(icons[2].classList.contains('mdc-chip__icon--leading')).toBe(false); + expect(icons[2].classList.contains('mdc-chip__icon--trailing')).toBe(false); + expect(icons[3].classList.contains('mdc-chip__icon--leading')).toBe(false); + expect(icons[3].classList.contains('mdc-chip__icon--trailing')).toBe(true); + })); + + @Component({ + template: ` +
+
+
one
+
+
+
two
+
+
+ ` + }) + class TestTabbingComponent { + } + it('chips are tabbable by default, but this can be overridden', (() => { + const { fixture } = setup(TestTabbingComponent); + let chips = fixture.nativeElement.querySelectorAll('.mdc-chip'); + expect(chips[0].tabIndex).toBe(0); + expect(chips[1].tabIndex).toBe(-1); + })); +}); diff --git a/bundle/src/components/chips/mdc.chip.directive.ts b/bundle/src/components/chips/mdc.chip.directive.ts new file mode 100644 index 0000000..ba46552 --- /dev/null +++ b/bundle/src/components/chips/mdc.chip.directive.ts @@ -0,0 +1,468 @@ +import { AfterContentInit, ContentChild, ContentChildren, Directive, ElementRef, EventEmitter, HostBinding, Input, + OnDestroy, Optional, Output, QueryList, Renderer2, Self, forwardRef, ChangeDetectorRef } from '@angular/core'; +import { MDCChipFoundation, MDCChipSetFoundation } from '@material/chips'; +import { AbstractMdcRipple } from '../ripple/abstract.mdc.ripple'; +import { MdcEventRegistry } from '../../utils/mdc.event.registry'; +import { asBoolean } from '../../utils/value.utils'; +import { MdcChipAdapter } from './mdc.chip.adapter'; +import { MdcChipSetAdapter } from './mdc.chip-set.adapter'; +import { MdcIconDirective } from '../../material.module'; +import { Subscription } from 'rxjs'; + +/** + * Directive for the (optional) leading or trailing icon of an mdcChip. + * Depenending on the position within the mdcChip the icon will determine + * whether it is a leading or trailing icon. + * Trailing icons must implement the functionality to remove the chip from the set, and + * must only be added to input chips (mdcChipSet="input"). Chips with a trailing + * icon must implement the remove event. + */ +@Directive({ + selector: '[mdcChipIcon]' +}) +export class MdcChipIconDirective { + @HostBinding('class.mdc-chip__icon') _cls = true; + @HostBinding('class.mdc-chip__icon--leading') _leading = false; + /** + * Event emitted for trailing icon user interactions. + */ + @Output() interact: EventEmitter = new EventEmitter(); + private __trailing = false; + private _oldTabIndex: number; + private _oldRole: string; + + constructor(public _elm: ElementRef, private _rndr: Renderer2, public _cdRef: ChangeDetectorRef) {} + + @HostBinding('class.mdc-chip__icon--trailing') get _trailing() { + return this.__trailing; + } + + set _trailing(val: boolean) { + if (val !== this._trailing) { + this.__trailing = val; + if (this._trailing) { + this._oldTabIndex = this._elm.nativeElement.tabIndex; + this._elm.nativeElement.tabIndex = 0; + this._oldRole = this._elm.nativeElement.getAttribute('role'); + this._rndr.setAttribute(this._elm.nativeElement, 'role', 'button'); + } else { + if (this._oldTabIndex) { + this._elm.nativeElement.tabIndex = this._oldTabIndex; + if (this._oldRole) + this._rndr.setAttribute(this._elm.nativeElement, 'role', this._oldRole); + else + this._rndr.removeAttribute(this._elm.nativeElement, 'role'); + } + this._oldTabIndex = null; + this._oldRole = null; + } + } + } +} + +/** + * Directive for the text of an mdcChip. + */ +@Directive({ + selector: '[mdcChipText]' +}) +export class MdcChipTextDirective { + @HostBinding('class.mdc-chip__text') _cls = true; + + constructor(public _elm: ElementRef) {} +} + +/** + * Directive for a chip. Chips must be child elements of an mdcChipSet. + */ +@Directive({ + selector: '[mdcChip]' +}) +export class MdcChipDirective extends AbstractMdcRipple implements AfterContentInit, OnDestroy { + @HostBinding('class.mdc-chip') _cls = true; + private initialized = false; + private selectedMem = false; + /** + * Event emitted for user interaction with the chip. + */ + @Output() interact: EventEmitter = new EventEmitter(); + /** + * Event emitted when the user has removed (by clicking the trailing icon) the chip. + * This event must be implemented when the chip has a trailing icon, and the implementation + * must remove the chip from the set. Without such implementation the directive will + * animate the chip out of vision, but will not remove the chip from the DOM. + */ + @Output() remove: EventEmitter = new EventEmitter(); + /** + * Event emitted when the chip changes from not-selected to selected state or vice versa + * (for filter and choice chips). + */ + @Output() selectedChange: EventEmitter = new EventEmitter(); + _set: MdcChipSetDirective; + private _checkmark: HTMLElement; + private _leadingIcon: MdcChipIconDirective; + private _trailingIcon: MdcChipIconDirective; + @ContentChildren(MdcChipIconDirective, {descendants: true}) _icons: QueryList; + @ContentChildren(MdcChipTextDirective, {descendants: true}) _texts: QueryList; + private _adapter: MdcChipAdapter = { + addClass: (className: string) => { + let selectedChanged = className === 'mdc-chip--selected' && !this._elm.nativeElement.classList.contains(className); + this._renderer.addClass(this._elm.nativeElement, className); + if (this.selectedChange) + this.selectedChange.emit(true); + }, + removeClass: (className: string) => { + let selectedChanged = className === 'mdc-chip--selected' && this._elm.nativeElement.classList.contains(className); + this._renderer.removeClass(this._elm.nativeElement, className); + if (this.selectedChange) + this.selectedChange.emit(false); + }, + hasClass: (className) => this._elm.nativeElement.classList.contains(className), + addClassToLeadingIcon: (className: string) => { + let icon = this._leadingIcon; + if (icon) + this._renderer.addClass(icon._elm.nativeElement, className); + }, + removeClassFromLeadingIcon: (className: string) => { + let icon = this._leadingIcon; + if (icon) + this._renderer.removeClass(icon._elm.nativeElement, className); + }, + eventTargetHasClass: (target: HTMLElement, className: string) => target.classList.contains(className), + registerEventHandler: (evt: string, handler: EventListener) => this._registry.listen(this._renderer, evt, handler, this._elm), + deregisterEventHandler: (evt: string, handler: EventListener) => this._registry.unlisten(evt, handler), + registerTrailingIconInteractionHandler: (evt: string, handler: EventListener) => { + let icon = this._trailingIcon; + if (icon) + this._registry.listen(this._renderer, evt, handler, icon._elm); + }, + deregisterTrailingIconInteractionHandler: (evt: string, handler: EventListener) => this._registry.unlisten(evt, handler), + notifyInteraction: () => this.interact.emit(), + notifyTrailingIconInteraction: () => this._trailingIcon.interact.emit(), + notifyRemoval: () => this.remove.emit(), + getComputedStyleValue: (propertyName: string) => getComputedStyle(this._elm.nativeElement).getPropertyValue(propertyName), + setStyleProperty: (style: string, value: string) => this._renderer.setStyle(this._elm.nativeElement, style, value) + }; + _foundation: { + init: Function, + destroy: Function, + isSelected: () => boolean, + setSelected: (selected: boolean) => void + } = new MDCChipFoundation(this._adapter); + + constructor(private _elm: ElementRef, rndr: Renderer2, registry: MdcEventRegistry) { + super(_elm, rndr, registry); + } + + ngAfterContentInit() { + if (!this._elm.nativeElement.hasAttribute('tabindex')) + // unless overridden, make the chip tabbable: + this._elm.nativeElement.tabIndex = 0; + this.initIcons(); + this._icons.changes.subscribe(() => { + this._reInit(); + }); + this._texts.changes.subscribe(() => { + this._reInit(); + }); + this.initRipple(); + this.initCheckMark(); + this._foundation.init(); + if (this.selectedMem) + // triggers setting the foundation selected state (and possibly for other [choice] chips too): + this.selected = this.selectedMem; + this.initialized = true; + } + + ngOnDestroy() { + this.destroyRipple(); + this._foundation.destroy(); + } + + _reInit() { + // if icons have changed, the foundation must be reinitialized, because + // trailingIconInteractionHandler might have to be removed and/or attached + // to another icon: + this._foundation.destroy(); + this._foundation = new MDCChipFoundation(this._adapter); + this.initIcons(); + this.initCheckMark(); + this._foundation.init(); + // no need to call setSelected again, as the previous state will still be available via + // the mdc-chip--selected class + } + + private initIcons() { + let newLeading = this.computeLeadingIcon(); + let newTrailing = this.computeTrailingIcon(newLeading); + if (newLeading !== this._leadingIcon || newTrailing !== this._trailingIcon) { + this._leadingIcon = newLeading; + this._trailingIcon = newTrailing; + this._icons.forEach(icon => { + let leading = icon === this._leadingIcon; + let trailing = icon === this._trailingIcon; + let changed = leading !== icon._leading || trailing !== icon._trailing; + icon._leading = icon === this._leadingIcon; + icon._trailing = icon === this._trailingIcon; + if (changed) // if we don't do this we get ExpressionChangedAfterItHasBeenCheckedError: + icon._cdRef.detectChanges(); + }); + this.removeCheckMark(); // checkmark may have changed position, will be readded later (for filter chips) + } + } + + private computeLeadingIcon() { + if (this._icons.length > 0) { + let icon = this._icons.first; + let prev = this.previousElement(icon._elm.nativeElement); + let last = icon._elm.nativeElement; + while (true) { + // if it is contained in another element, check the siblings of the container too: + if (prev == null && last != null && last.parentElement !== this._elm.nativeElement) + prev = last.parentElement; + // no more elements before, must be the leading icon: + if (prev == null) + return icon; + // comes after the text, so it's not the leading icon: + if (this._text && (prev === this._text._elm.nativeElement || prev.contains(this._text._elm.nativeElement))) + return null; + last = prev; + prev = this.previousElement(prev); + } + } + return null; + } + + private get _text() { + return this._texts.first; + } + + private computeTrailingIcon(leading: MdcChipIconDirective) { + if (this._icons.length > 0) { + let icon = this._icons.last; + if (icon === leading) + return null; + // if not the leading icon, it must be the trailing icon: + return icon; + } + return null; + } + + private previousElement(el: Element): Element { + let result = el.previousSibling; + while (result != null && !(result instanceof Element)) + result = result.previousSibling; + return result; + } + + private initCheckMark() { + if (this._set && this._set._type === 'filter') + this.addCheckMark(); + else + this.removeCheckMark(); + } + + private addCheckMark() { + if (!this._checkmark) { + let path = this._renderer.createElement('path', 'svg'); + this._renderer.addClass(path, 'mdc-chip__checkmark-path'); + this._renderer.setAttribute(path, 'fill', 'none'); + this._renderer.setAttribute(path, 'stroke', 'black'); + this._renderer.setAttribute(path, 'd', 'M1.73,12.91 8.1,19.28 22.79,4.59'); + let svg = this._renderer.createElement('svg', 'svg'); + this._renderer.appendChild(svg, path); + this._renderer.addClass(svg, 'mdc-chip__checkmark-svg'); + this._renderer.setAttribute(svg, 'viewBox', '-2 -3 30 30'); + this._checkmark = this._renderer.createElement('div'); + this._renderer.appendChild(this._checkmark, svg); + this._renderer.addClass(this._checkmark, 'mdc-chip__checkmark'); + let beforeElement = this._text ? this._text._elm.nativeElement : null; + if (beforeElement) + // checkmark should go after leading icon: + this._renderer.insertBefore(this._elm.nativeElement, this._checkmark, beforeElement); + else + this._renderer.appendChild(this._elm.nativeElement, this._checkmark); + } + } + + private removeCheckMark() { + if (this._checkmark) { + this._checkmark.parentElement.removeChild(this._checkmark); + this._checkmark = null; + } + } + + /** + * The 'selected' state of the chip. Filter and choice chips are either selected or + * not selected. Making a choice chip selected, will make all other chips in that set + * not selected. + */ + @Input() get selected(): any { + return this.initialized ? this._foundation.isSelected() : this.selectedMem; + } + + set selected(val: any) { + let value = asBoolean(val); + this.selectedMem = value; + if (this.initialized && value !== this._foundation.isSelected()) { + if (value && this._set) + // this will also trigger deselection of other values for choice chips + this._set._foundation.select(this._foundation); + else + this._foundation.setSelected(val); + } + // when not initialized the selectedChange will be emitted via the foundation after + // initialization + } + + /** @docs-private */ + protected computeRippleBoundingRect() { + if (this._checkmark && !this._leadingIcon) { + const height = this._rippleElm.nativeElement.getBoundingClientRect().height; + // https://github.com/material-components/material-components-web/blob/cb373ad34857070734a7c02bf59116e21853842a/packages/mdc-chips/chip/index.js#L60: + // the checkmark should be square, but initiallly the width is set to 0 + return { height: height, width: height }; + } + return super.computeRippleBoundingRect(); + } +} + +/** + * Directive for a chip-set (a collection of mdcChip). + */ +@Directive({ + selector: '[mdcChipSet]' +}) +export class MdcChipSetDirective implements AfterContentInit, OnDestroy { + @HostBinding('class.mdc-chip-set') _cls = true; + @ContentChildren(MdcChipDirective, {descendants: true}) _chips: QueryList; + private _interactSubscriptions: Subscription[]; + private _removeSubscriptions: Subscription[]; + private _initialized = false; + private _interactionHandler: EventListener; + _type: 'choice' | 'filter' | 'input' | 'action' = 'action'; + _adapter: MdcChipSetAdapter = { + hasClass: (className: string) => this._elm.nativeElement.classList.contains(className), + registerInteractionHandler: (evt: string, handler: EventListener) => { + if (evt === 'MDCChip:interaction') + this._interactionHandler = handler; + // 'MDCChip:removal' is not really used, we call deselect/removeChip directly on the angular eventemitter subscription + else + this._registry.listen(this._rndr, evt, handler, this._elm); + }, + deregisterInteractionHandler: (evt: string, handler: EventListener) => { + if (evt === 'MDCChip:interaction') + this._interactionHandler = null; + else + this._registry.unlisten(evt, handler); + }, + appendChip: (text: string, leadingIcon: Element, trailingIcon: Element) => { + throw new Error('this adapter method should be unreachable in the MdcChipSetDirective implementation'); + }, + removeChip: (chip: any) => { + throw new Error('this adapter method should be unreachable in the MdcChipSetDirective implementation'); + } + }; + _foundation: { + init: Function, + destroy: Function, + select: (chip: MDCChipFoundation) => void, + deselect: (chip: MDCChipFoundation) => void + } = new MDCChipSetFoundation(this._adapter); + + constructor(private _elm: ElementRef, private _rndr: Renderer2, private _registry: MdcEventRegistry) {} + + ngAfterContentInit() { + this._chips.changes.subscribe(() => { + this.initSubscriptions(); + this.initChips(); + }); + this.initSubscriptions(); + this.initChips(); + this._foundation.init(); + this._initialized = true; + } + + ngOnDestroy() { + this._foundation.destroy(); + this.destroySubscriptions(); + this._initialized = false; + } + + /** + * Chip sets by default contain 'action' chips. Set this value to choice, + * filter, input, or action to determine the kind + * of chips that are contained in the chip set. + */ + @Input() get mdcChipSet() { + return this._type; + } + + set mdcChipSet(value: any) { + if (value !== this._type) { + if (value === 'choice' || value === 'filter' || value ==='input') + this._type = value; + else + this._type = 'action'; + if (this._initialized) + this.initChips(true); + } + } + + private initChips(force = false) { + this._chips.forEach(chip => { + if (force || chip._set !== this) { + chip._set = this; + chip._reInit(); + } + }); + } + + private destroySubscriptions() { + try { + if (this._interactSubscriptions) + this._interactSubscriptions.forEach(sub => sub.unsubscribe()); + if (this._removeSubscriptions) + this._removeSubscriptions.forEach(sub => sub.unsubscribe()); + } finally { + this._interactSubscriptions = null; + this._removeSubscriptions = null; + } + } + + private initSubscriptions() { + this.destroySubscriptions(); + this._interactSubscriptions = []; + this._removeSubscriptions = []; + this._chips.forEach(chip => { + this._interactSubscriptions.push(chip.interact.subscribe(event => { + // using the interactionHandler that was registered by the foundation: + if (this._interactionHandler) + this._interactionHandler({ + detail: { chip: { foundation: chip._foundation }} + }); + })); + this._removeSubscriptions.push(chip.remove.subscribe(event => { + // directly handling, bypassing event handlers: + this._foundation.deselect(chip._foundation); + // don't call this._adapter.removeChip(chip); + })); + }); + } + + @HostBinding('class.mdc-chip-set--choice') get _choice() { + return this._type === 'choice'; + } + + @HostBinding('class.mdc-chip-set--filter') get _filter() { + return this._type === 'filter'; + } + + @HostBinding('class.mdc-chip-set--input') get _input() { + return this._type === 'input'; + } +} + +export const CHIP_DIRECTIVES = [ + MdcChipIconDirective, MdcChipTextDirective, MdcChipDirective, MdcChipSetDirective +]; diff --git a/bundle/src/material.module.ts b/bundle/src/material.module.ts index 6f133ca..ced33d1 100644 --- a/bundle/src/material.module.ts +++ b/bundle/src/material.module.ts @@ -10,6 +10,7 @@ import { MdcCardDirective, MdcCardPrimaryActionDirective } from './components/card/mdc.card.directive'; import { MdcCheckboxDirective, MdcCheckboxInputDirective } from './components/checkbox/mdc.checkbox.directive'; +import { CHIP_DIRECTIVES } from './components/chips/mdc.chip.directive'; import { DIALOG_DIRECTIVES } from './components/dialog/mdc.dialog.directive'; import { MdcDrawerDirective, MdcDrawerContainerDirective, @@ -84,6 +85,7 @@ export { MdcCardDirective, MdcCardPrimaryActionDirective } from './components/card/mdc.card.directive'; export { MdcCheckboxDirective, MdcCheckboxInputDirective } from './components/checkbox/mdc.checkbox.directive'; +export * from './components/chips/mdc.chip.directive'; export * from './components/dialog/mdc.dialog.directive'; export { MdcDrawerDirective, MdcDrawerContainerDirective, @@ -160,6 +162,7 @@ export { MdcEventRegistry } from './utils/mdc.event.registry'; MdcCardDirective, MdcCardMediaDirective, MdcCardMediaContentDirective, MdcCardActionButtonsDirective, MdcCardActionIconsDirective, MdcCardActionsDirective, MdcCardPrimaryActionDirective, MdcCheckboxDirective, MdcCheckboxInputDirective, + ...CHIP_DIRECTIVES, ...DIALOG_DIRECTIVES, MdcDrawerDirective, MdcDrawerContainerDirective, MdcDrawerToolbarSpacerDirective, MdcDrawerHeaderDirective, MdcDrawerHeaderContentDirective, MdcDrawerContentDirective, MdcElevationDirective, @@ -191,6 +194,7 @@ export { MdcEventRegistry } from './utils/mdc.event.registry'; MdcCardDirective, MdcCardMediaDirective, MdcCardMediaContentDirective, MdcCardActionButtonsDirective, MdcCardActionIconsDirective, MdcCardActionsDirective, MdcCardPrimaryActionDirective, MdcCheckboxDirective, MdcCheckboxInputDirective, + ...CHIP_DIRECTIVES, ...DIALOG_DIRECTIVES, MdcDrawerDirective, MdcDrawerContainerDirective, MdcDrawerToolbarSpacerDirective, MdcDrawerHeaderDirective, MdcDrawerHeaderContentDirective, MdcDrawerContentDirective, MdcElevationDirective, diff --git a/bundle/tools/ngbundler/index.ts b/bundle/tools/ngbundler/index.ts index 157e067..cb11772 100644 --- a/bundle/tools/ngbundler/index.ts +++ b/bundle/tools/ngbundler/index.ts @@ -9,6 +9,7 @@ const globals = { '@angular/router': 'ng.router', '@material/animation': 'mdc.animation', '@material/checkbox': 'mdc.checkbox', + '@material/chips': 'mdc.chips', '@material/dialog': 'mdc.dialog', '@material/drawer': 'mdc.drawer', '@material/drawer/slidable': 'mdc.drawer', From c8ef77800d688937e4f0744de63f75294014c604 Mon Sep 17 00:00:00 2001 From: gjdev Date: Fri, 1 Jun 2018 12:00:25 +0200 Subject: [PATCH 2/3] docs(chips): document chips and chip-sets --- README.md | 2 +- site/src/app/app.module.ts | 2 + site/src/app/app.routes.ts | 2 + .../chips.directives.component.html | 24 ++++++++++++ .../chips.directives.component.ts | 14 +++++++ .../app/components/directives.demo/index.ts | 3 ++ .../directives/chips.directives.component.ts | 14 +++++++ .../components/snippets/directives/index.ts | 4 ++ .../snippet.chips.choice.component.html | 9 +++++ .../snippet.chips.choice.component.ts | 22 +++++++++++ .../directives/snippet.chips.component.html | 20 ++++++++++ .../directives/snippet.chips.component.ts | 22 +++++++++++ .../snippet.chips.filter.component.html | 20 ++++++++++ .../snippet.chips.filter.component.ts | 22 +++++++++++ .../snippet.chips.input.component.html | 16 ++++++++ .../snippet.chips.input.component.ts | 38 +++++++++++++++++++ site/src/app/messages.json | 3 ++ site/src/style/_mdc.scss | 13 +++++++ 18 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 site/src/app/components/directives.demo/chips.directives.component.html create mode 100644 site/src/app/components/directives.demo/chips.directives.component.ts create mode 100644 site/src/app/components/snippets/directives/chips.directives.component.ts create mode 100644 site/src/app/components/snippets/directives/snippet.chips.choice.component.html create mode 100644 site/src/app/components/snippets/directives/snippet.chips.choice.component.ts create mode 100644 site/src/app/components/snippets/directives/snippet.chips.component.html create mode 100644 site/src/app/components/snippets/directives/snippet.chips.component.ts create mode 100644 site/src/app/components/snippets/directives/snippet.chips.filter.component.html create mode 100644 site/src/app/components/snippets/directives/snippet.chips.filter.component.ts create mode 100644 site/src/app/components/snippets/directives/snippet.chips.input.component.html create mode 100644 site/src/app/components/snippets/directives/snippet.chips.input.component.ts diff --git a/README.md b/README.md index 826141e..61a509b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Component | Documentation | button | [button docs & demo](https://blox.src.zone/material/directives/button) | card | [card docs & demo](https://blox.src.zone/material/directives/card) | checkbox | [checkbox docs & demo](https://blox.src.zone/material/directives/checkbox) | -chips | in tracker | +chips | [chips docs & demo](https://blox.src.zone/material/directives/chips) | dialog | [dialog docs & demo](https://blox.src.zone/material/directives/drawer) | drawer | [drawer docs & demo](https://blox.src.zone/material/directives/drawer) | elevation | [elevation docs & demo](https://blox.src.zone/material/directives/elevation) | diff --git a/site/src/app/app.module.ts b/site/src/app/app.module.ts index e66641e..8acbaad 100644 --- a/site/src/app/app.module.ts +++ b/site/src/app/app.module.ts @@ -24,6 +24,7 @@ import { SnippetButtonComponent, SnippetCardComponent, SnippetCheckboxComponent, + SnippetChipsComponent, SnippetChipsChoiceComponent, SnippetChipsFilterComponent, SnippetChipsInputComponent, SnippetDialogComponent, SnippetDrawerPermanentBelowComponent, SnippetDrawerPermanentComponent, @@ -77,6 +78,7 @@ import { ThemeService } from './services'; SnippetButtonComponent, SnippetCardComponent, SnippetCheckboxComponent, + SnippetChipsComponent, SnippetChipsChoiceComponent, SnippetChipsFilterComponent, SnippetChipsInputComponent, SnippetDialogComponent, SnippetDrawerPermanentBelowComponent, SnippetDrawerPermanentComponent, diff --git a/site/src/app/app.routes.ts b/site/src/app/app.routes.ts index 20cf37c..bb75e00 100644 --- a/site/src/app/app.routes.ts +++ b/site/src/app/app.routes.ts @@ -12,6 +12,7 @@ import { ButtonDirectivesComponent, CardDirectivesComponent, CheckboxDirectivesComponent, + ChipsDirectivesComponent, DialogDirectivesComponent, DrawerDirectivesComponent, ElevationDirectivesComponent, @@ -45,6 +46,7 @@ export const routes: Routes = [ {path: ButtonDirectivesComponent.DOC_HREF, component: ButtonDirectivesComponent}, {path: CardDirectivesComponent.DOC_HREF, component: CardDirectivesComponent}, {path: CheckboxDirectivesComponent.DOC_HREF, component: CheckboxDirectivesComponent}, + {path: ChipsDirectivesComponent.DOC_HREF, component: ChipsDirectivesComponent}, {path: DialogDirectivesComponent.DOC_HREF, component: DialogDirectivesComponent}, {path: DrawerDirectivesComponent.DOC_HREF, component: DrawerDirectivesComponent}, {path: ElevationDirectivesComponent.DOC_HREF, component: ElevationDirectivesComponent}, diff --git a/site/src/app/components/directives.demo/chips.directives.component.html b/site/src/app/components/directives.demo/chips.directives.component.html new file mode 100644 index 0000000..829d9da --- /dev/null +++ b/site/src/app/components/directives.demo/chips.directives.component.html @@ -0,0 +1,24 @@ +

Chips

+

Chips are compact elements that allow users to enter information, select a choice, filter content, or trigger an action. +

+ + Action Chips Demo + + +
+ + Choice Chips Demo + + +
+ + Filter Chips Demo + + +
+ + Input Chips Demo + + + +
${require('@blox/material/apidocs/chips.html')}
\ No newline at end of file diff --git a/site/src/app/components/directives.demo/chips.directives.component.ts b/site/src/app/components/directives.demo/chips.directives.component.ts new file mode 100644 index 0000000..2ae5581 --- /dev/null +++ b/site/src/app/components/directives.demo/chips.directives.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'blox-chips-directives', + templateUrl: './chips.directives.component.html' +}) +export class ChipsDirectivesComponent { + static DOC_SVG = require('assets/img/mdc-icons/chip.svg'); + static DOC_TYPE = 'components'; + static DOC_HREF = 'chips'; + + constructor() { + } +} diff --git a/site/src/app/components/directives.demo/index.ts b/site/src/app/components/directives.demo/index.ts index a596118..f5639dc 100644 --- a/site/src/app/components/directives.demo/index.ts +++ b/site/src/app/components/directives.demo/index.ts @@ -1,6 +1,7 @@ import { ButtonDirectivesComponent } from './button.directives.component'; import { CardDirectivesComponent } from './card.directives.component'; import { CheckboxDirectivesComponent } from './checkbox.directives.component'; +import { ChipsDirectivesComponent } from './chips.directives.component'; import { DialogDirectivesComponent } from './dialog.directives.component'; import { DrawerDirectivesComponent } from './drawer.directives.component'; import { ElevationDirectivesComponent } from './elevation.directives.component'; @@ -24,6 +25,7 @@ import { UtilityDirectivesComponent } from './utility.directives.component'; export * from './button.directives.component'; export * from './card.directives.component'; export * from './checkbox.directives.component'; +export * from './chips.directives.component'; export * from './dialog.directives.component'; export * from './drawer.directives.component'; export * from './elevation.directives.component'; @@ -48,6 +50,7 @@ export const MDC_DIRECTIVE_DOC_COMPONENTS = [ ButtonDirectivesComponent, CardDirectivesComponent, CheckboxDirectivesComponent, + ChipsDirectivesComponent, DialogDirectivesComponent, DrawerDirectivesComponent, ElevationDirectivesComponent, diff --git a/site/src/app/components/snippets/directives/chips.directives.component.ts b/site/src/app/components/snippets/directives/chips.directives.component.ts new file mode 100644 index 0000000..2ae5581 --- /dev/null +++ b/site/src/app/components/snippets/directives/chips.directives.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'blox-chips-directives', + templateUrl: './chips.directives.component.html' +}) +export class ChipsDirectivesComponent { + static DOC_SVG = require('assets/img/mdc-icons/chip.svg'); + static DOC_TYPE = 'components'; + static DOC_HREF = 'chips'; + + constructor() { + } +} diff --git a/site/src/app/components/snippets/directives/index.ts b/site/src/app/components/snippets/directives/index.ts index 733fded..6088916 100644 --- a/site/src/app/components/snippets/directives/index.ts +++ b/site/src/app/components/snippets/directives/index.ts @@ -1,6 +1,10 @@ export * from './snippet.button.component'; export * from './snippet.card.component'; export * from './snippet.checkbox.component'; +export * from './snippet.chips.component'; +export * from './snippet.chips.choice.component'; +export * from './snippet.chips.filter.component'; +export * from './snippet.chips.input.component'; export * from './snippet.dialog.component'; export * from './snippet.drawer.permanent.below.component'; export * from './snippet.drawer.permanent.component'; diff --git a/site/src/app/components/snippets/directives/snippet.chips.choice.component.html b/site/src/app/components/snippets/directives/snippet.chips.choice.component.html new file mode 100644 index 0000000..479ac78 --- /dev/null +++ b/site/src/app/components/snippets/directives/snippet.chips.choice.component.html @@ -0,0 +1,9 @@ +
+
+
Extra Small
+
Small
+
Medium
+
Large
+
Extra Large
+
+
diff --git a/site/src/app/components/snippets/directives/snippet.chips.choice.component.ts b/site/src/app/components/snippets/directives/snippet.chips.choice.component.ts new file mode 100644 index 0000000..d705def --- /dev/null +++ b/site/src/app/components/snippets/directives/snippet.chips.choice.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; +//snip:skip +import { forwardRef } from '@angular/core'; +import { AbstractSnippetComponent } from '../abstract.snippet.component'; +//snip:endskip +@Component({ + //snip:skip + providers: [{provide: AbstractSnippetComponent, useExisting: forwardRef(() => SnippetChipsChoiceComponent)}], + //snip:endskip + selector: 'blox-snippet-chips-choice', + templateUrl: './snippet.chips.choice.component.html' +}) +export class SnippetChipsChoiceComponent/*snip:skip*/extends AbstractSnippetComponent/*snip:endskip*/ { + //snip:skip + constructor() { + super({ + 'html': require('!raw-loader!./snippet.chips.choice.component.html'), + 'typescript': require('!raw-loader!./snippet.chips.choice.component.ts') + }); + } + //snip:endskip +} diff --git a/site/src/app/components/snippets/directives/snippet.chips.component.html b/site/src/app/components/snippets/directives/snippet.chips.component.html new file mode 100644 index 0000000..53c8a18 --- /dev/null +++ b/site/src/app/components/snippets/directives/snippet.chips.component.html @@ -0,0 +1,20 @@ +
+
+
+ wb_sunny +
Turn lights on
+
+
+ bookmark +
Bookmark
+
+
+ alarm +
Set alarm
+
+
+ directions +
Get directions
+
+
+
diff --git a/site/src/app/components/snippets/directives/snippet.chips.component.ts b/site/src/app/components/snippets/directives/snippet.chips.component.ts new file mode 100644 index 0000000..f1bbf8e --- /dev/null +++ b/site/src/app/components/snippets/directives/snippet.chips.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; +//snip:skip +import { forwardRef } from '@angular/core'; +import { AbstractSnippetComponent } from '../abstract.snippet.component'; +//snip:endskip +@Component({ + //snip:skip + providers: [{provide: AbstractSnippetComponent, useExisting: forwardRef(() => SnippetChipsComponent)}], + //snip:endskip + selector: 'blox-snippet-chips', + templateUrl: './snippet.chips.component.html' +}) +export class SnippetChipsComponent/*snip:skip*/extends AbstractSnippetComponent/*snip:endskip*/ { + //snip:skip + constructor() { + super({ + 'html': require('!raw-loader!./snippet.chips.component.html'), + 'typescript': require('!raw-loader!./snippet.chips.component.ts') + }); + } + //snip:endskip +} diff --git a/site/src/app/components/snippets/directives/snippet.chips.filter.component.html b/site/src/app/components/snippets/directives/snippet.chips.filter.component.html new file mode 100644 index 0000000..50e79d4 --- /dev/null +++ b/site/src/app/components/snippets/directives/snippet.chips.filter.component.html @@ -0,0 +1,20 @@ +
+
+
+ message +
Messages
+
+
+ notification_important +
Notifications
+
+
+ warning +
Warnings
+
+
+ error +
Errors
+
+
+
diff --git a/site/src/app/components/snippets/directives/snippet.chips.filter.component.ts b/site/src/app/components/snippets/directives/snippet.chips.filter.component.ts new file mode 100644 index 0000000..c97ce64 --- /dev/null +++ b/site/src/app/components/snippets/directives/snippet.chips.filter.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; +//snip:skip +import { forwardRef } from '@angular/core'; +import { AbstractSnippetComponent } from '../abstract.snippet.component'; +//snip:endskip +@Component({ + //snip:skip + providers: [{provide: AbstractSnippetComponent, useExisting: forwardRef(() => SnippetChipsFilterComponent)}], + //snip:endskip + selector: 'blox-snippet-chips-filter', + templateUrl: './snippet.chips.filter.component.html' +}) +export class SnippetChipsFilterComponent/*snip:skip*/extends AbstractSnippetComponent/*snip:endskip*/ { + //snip:skip + constructor() { + super({ + 'html': require('!raw-loader!./snippet.chips.filter.component.html'), + 'typescript': require('!raw-loader!./snippet.chips.filter.component.ts') + }); + } + //snip:endskip +} diff --git a/site/src/app/components/snippets/directives/snippet.chips.input.component.html b/site/src/app/components/snippets/directives/snippet.chips.input.component.html new file mode 100644 index 0000000..2b5238c --- /dev/null +++ b/site/src/app/components/snippets/directives/snippet.chips.input.component.html @@ -0,0 +1,16 @@ +
+
+
+ face +
{{chip}}
+ cancel +
+
+
+
+ + +
+ +
+
diff --git a/site/src/app/components/snippets/directives/snippet.chips.input.component.ts b/site/src/app/components/snippets/directives/snippet.chips.input.component.ts new file mode 100644 index 0000000..ecfc209 --- /dev/null +++ b/site/src/app/components/snippets/directives/snippet.chips.input.component.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core'; +//snip:skip +import { forwardRef } from '@angular/core'; +import { AbstractSnippetComponent } from '../abstract.snippet.component'; +//snip:endskip +@Component({ + //snip:skip + providers: [{provide: AbstractSnippetComponent, useExisting: forwardRef(() => SnippetChipsInputComponent)}], + //snip:endskip + selector: 'blox-snippet-chips-input', + templateUrl: './snippet.chips.input.component.html' +}) +export class SnippetChipsInputComponent/*snip:skip*/extends AbstractSnippetComponent/*snip:endskip*/ { + chips = [ 'claire', 'pete', 'anne' ]; + newChip: string; + + //snip:skip + constructor() { + super({ + 'html': require('!raw-loader!./snippet.chips.input.component.html'), + 'typescript': require('!raw-loader!./snippet.chips.input.component.ts') + }); + } + //snip:endskip + + addChip() { + if (this.newChip) { + let value = this.newChip.trim(); + if (value.length) + this.chips.push(value); + } + this.newChip = null; + } + + removeChip(index: number) { + this.chips.splice(index, 1); + } +} diff --git a/site/src/app/messages.json b/site/src/app/messages.json index 6a8e646..6489632 100644 --- a/site/src/app/messages.json +++ b/site/src/app/messages.json @@ -11,6 +11,9 @@ "components.checkbox.title": "Checkbox", "components.checkbox.description": "Multi selection controls", "components.checkbox.meta.description": "Checkbox (mdcCheckbox) component of ${default.meta.description}", + "components.chips.title": "Chips", + "components.chips.description": "Chips for actions, selection, and input", + "components.chips.meta.description": "Chips (mdcChip, mdcChipSet) components of ${default.meta.description}", "components.dialog.title": "Dialog", "components.dialog.description": "Modal Dialogs", "components.dialog.meta.description": "Dialog (mdcDialog) component of ${default.meta.description}", diff --git a/site/src/style/_mdc.scss b/site/src/style/_mdc.scss index 16ad329..831cf2a 100644 --- a/site/src/style/_mdc.scss +++ b/site/src/style/_mdc.scss @@ -1,20 +1,26 @@ @import "@material/button/mdc-button"; @import "@material/card/mdc-card"; @import "@material/checkbox/mdc-checkbox"; +@import "@material/chips/mdc-chips"; @import "@material/dialog/mdc-dialog"; @import "@material/drawer/mdc-drawer"; @import "@material/elevation/mdc-elevation"; @import "@material/fab/mdc-fab"; +@import "@material/floating-label/mdc-floating-label"; @import "@material/form-field/mdc-form-field"; //@import "@material/grid-list/mdc-grid-list"; @import "@material/icon-toggle/mdc-icon-toggle"; +//@import "@material/image-list/mdc-image-list"; //@import "@material/layout-grid/mdc-layout-grid"; +@import "@material/line-ripple/mdc-line-ripple"; @import "@material/linear-progress/mdc-linear-progress"; @import "@material/list/mdc-list"; @import "@material/menu/mdc-menu"; +@import "@material/notched-outline/mdc-notched-outline"; @import "@material/radio/mdc-radio"; @import "@material/ripple/mdc-ripple"; @import "@material/select/mdc-select"; +@import "@material/shape/mdc-shape"; @import "@material/slider/mdc-slider"; @import "@material/snackbar/mdc-snackbar"; @import "@material/switch/mdc-switch"; @@ -22,4 +28,11 @@ @import "@material/textfield/mdc-text-field"; @import "@material/theme/mdc-theme"; @import "@material/toolbar/mdc-toolbar"; +//@import "@material/top-app-bar/mdc-top-app-bar"; @import "@material/typography/mdc-typography"; + +.mdc-chip.mdc-chip--selected { + // our theme color defaults otherwise have almost no contrast between + // selected/unselected state: + @include mdc-theme-prop(background-color, secondary); +} From 6347f369482ffabe917bbb2dd9642e54cc6c2995 Mon Sep 17 00:00:00 2001 From: gjdev Date: Fri, 1 Jun 2018 12:44:03 +0200 Subject: [PATCH 3/3] fix: remove incorrectly copied source file --- .../directives/chips.directives.component.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 site/src/app/components/snippets/directives/chips.directives.component.ts diff --git a/site/src/app/components/snippets/directives/chips.directives.component.ts b/site/src/app/components/snippets/directives/chips.directives.component.ts deleted file mode 100644 index 2ae5581..0000000 --- a/site/src/app/components/snippets/directives/chips.directives.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'blox-chips-directives', - templateUrl: './chips.directives.component.html' -}) -export class ChipsDirectivesComponent { - static DOC_SVG = require('assets/img/mdc-icons/chip.svg'); - static DOC_TYPE = 'components'; - static DOC_HREF = 'chips'; - - constructor() { - } -}