Skip to content

Commit

Permalink
feat(chips): Create trailing action business logic
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 299930892
  • Loading branch information
patrickrodee authored and copybara-github committed Mar 9, 2020
1 parent 2e0daf3 commit 9ebee4c
Show file tree
Hide file tree
Showing 8 changed files with 726 additions and 0 deletions.
43 changes: 43 additions & 0 deletions packages/mdc-chips/trailingaction/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @license
* Copyright 2020 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

import {InteractionTrigger} from './constants';

/**
* Defines the shape of the adapter expected by the foundation.
* Implement this adapter for your framework of choice to delegate updates to
* the component in your framework of choice. See architecture documentation
* for more details.
* https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md
*/
export interface MDCChipTrailingActionAdapter {
focus(): void;

getAttribute(attr: string): string|null;

notifyInteraction(trigger: InteractionTrigger): void;

notifyNavigation(key: string): void;

setAttribute(attr: string, value: string): void;
}
119 changes: 119 additions & 0 deletions packages/mdc-chips/trailingaction/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @license
* Copyright 2020 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

import {MDCComponent} from '@material/base/component';
import {SpecificEventListener} from '@material/base/types';
import {MDCRippleAdapter} from '@material/ripple/adapter';
import {MDCRipple, MDCRippleFactory} from '@material/ripple/component';
import {MDCRippleFoundation} from '@material/ripple/foundation';
import {MDCRippleCapableSurface} from '@material/ripple/types';
import {MDCChipTrailingActionAdapter} from './adapter';
import {strings} from './constants';
import {MDCChipTrailingActionFoundation} from './foundation';
import {MDCChipTrailingActionInteractionEventDetail, MDCChipTrailingActionNavigationEventDetail} from './types';

export class MDCChipTrailingAction extends
MDCComponent<MDCChipTrailingActionFoundation> implements
MDCRippleCapableSurface {
get ripple(): MDCRipple {
return this.ripple_;
}

static attachTo(root: Element) {
return new MDCChipTrailingAction(root);
}

// Public visibility for this property is required by MDCRippleCapableSurface.
root_!: HTMLElement; // assigned in MDCComponent constructor

private ripple_!: MDCRipple; // assigned in initialize()
private handleClick_!:
SpecificEventListener<'click'>; // assigned in initialSyncWithDOM()
private handleKeydown_!:
SpecificEventListener<'keydown'>; // assigned in initialSyncWithDOM()

initialize(
rippleFactory: MDCRippleFactory = (el, foundation) =>
new MDCRipple(el, foundation)) {
// DO NOT INLINE this variable. For backward compatibility, foundations take
// a Partial<MDCFooAdapter>. To ensure we don't accidentally omit any
// methods, we need a separate, strongly typed adapter variable.
const rippleAdapter: MDCRippleAdapter = MDCRipple.createAdapter(this);
this.ripple_ =
rippleFactory(this.root_, new MDCRippleFoundation(rippleAdapter));
}

initialSyncWithDOM() {
this.handleClick_ = (evt: MouseEvent) => {
this.foundation_.handleClick(evt);
};
this.handleKeydown_ = (evt: KeyboardEvent) => {
this.foundation_.handleKeydown(evt);
};

this.listen('click', this.handleClick_);
this.listen('keydown', this.handleKeydown_);
}

destroy() {
this.ripple_.destroy();
this.unlisten('click', this.handleClick_);
this.unlisten('keydown', this.handleKeydown_);
super.destroy();
}

getDefaultFoundation() {
// DO NOT INLINE this variable. For backward compatibility, foundations take
// a Partial<MDCFooAdapter>. To ensure we don't accidentally omit any
// methods, we need a separate, strongly typed adapter variable.
const adapter: MDCChipTrailingActionAdapter = {
focus: () => {
this.root_.focus();
},
getAttribute: (attr) => this.root_.getAttribute(attr),
notifyInteraction: (trigger) =>
this.emit<MDCChipTrailingActionInteractionEventDetail>(
strings.INTERACTION_EVENT, {trigger}, true /* shouldBubble */),
notifyNavigation: (key) => {
this.emit<MDCChipTrailingActionNavigationEventDetail>(
strings.NAVIGATION_EVENT, {key}, true /* shouldBubble */);
},
setAttribute: (attr, value) => {
this.root_.setAttribute(attr, value);
},
};
return new MDCChipTrailingActionFoundation(adapter);
}

isNavigable() {
return this.foundation_.isNavigable();
}

focus() {
this.foundation_.focus();
}

removeFocus() {
this.foundation_.removeFocus();
}
}
38 changes: 38 additions & 0 deletions packages/mdc-chips/trailingaction/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

export enum InteractionTrigger {
UNSPECIFIED, // Default type
CLICK,
BACKSPACE_KEY,
DELETE_KEY,
SPACEBAR_KEY,
ENTER_KEY,
}

export const strings = {
ARIA_HIDDEN: 'aria-hidden',
INTERACTION_EVENT: 'MDCChipTrailingAction:interaction',
NAVIGATION_EVENT: 'MDCChipTrailingAction:navigation',
TAB_INDEX: 'tabindex',
};
120 changes: 120 additions & 0 deletions packages/mdc-chips/trailingaction/foundation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* @license
* Copyright 2020 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

import {MDCFoundation} from '@material/base/foundation';

import {navigationKeys, strings as chipStrings} from '../chip/constants';

import {MDCChipTrailingActionAdapter} from './adapter';
import {InteractionTrigger, strings} from './constants';

export class MDCChipTrailingActionFoundation extends
MDCFoundation<MDCChipTrailingActionAdapter> {
static get strings() {
return strings;
}

static get defaultAdapter(): MDCChipTrailingActionAdapter {
return {
focus: () => undefined,
getAttribute: () => null,
setAttribute: () => undefined,
notifyInteraction: () => undefined,
notifyNavigation: () => undefined,
};
}

constructor(adapter?: Partial<MDCChipTrailingActionAdapter>) {
super({...MDCChipTrailingActionFoundation.defaultAdapter, ...adapter});
}

handleClick(evt: MouseEvent) {
evt.stopPropagation();
this.adapter_.notifyInteraction(InteractionTrigger.CLICK);
}

handleKeydown(evt: KeyboardEvent) {
evt.stopPropagation();
if (this.shouldNotifyInteraction_(evt)) {
this.adapter_.notifyInteraction(this.getTriggerFromKeyboard_(evt));
return;
}

if (this.shouldNotifyNavigation_(evt)) {
this.adapter_.notifyNavigation(evt.key);
return;
}
}

removeFocus() {
this.adapter_.setAttribute(strings.TAB_INDEX, '-1');
}

focus() {
this.adapter_.setAttribute(strings.TAB_INDEX, '0');
this.adapter_.focus();
}

isNavigable() {
return this.adapter_.getAttribute(strings.ARIA_HIDDEN) !== 'true';
}

private shouldNotifyInteraction_(evt: KeyboardEvent): boolean {
const isFromActionKey = evt.key === chipStrings.ENTER_KEY ||
evt.key === chipStrings.SPACEBAR_KEY;
const isFromDeleteKey = evt.key === chipStrings.BACKSPACE_KEY ||
evt.key === chipStrings.DELETE_KEY ||
evt.key === chipStrings.IE_DELETE_KEY;

return isFromActionKey || isFromDeleteKey;
}

private shouldNotifyNavigation_(evt: KeyboardEvent): boolean {
return navigationKeys.has(evt.key);
}

private getTriggerFromKeyboard_(evt: KeyboardEvent): InteractionTrigger {
if (evt.key === chipStrings.SPACEBAR_KEY) {
return InteractionTrigger.SPACEBAR_KEY;
}

if (evt.key === chipStrings.ENTER_KEY) {
return InteractionTrigger.ENTER_KEY;
}

if (evt.key === chipStrings.DELETE_KEY ||
evt.key === chipStrings.IE_DELETE_KEY) {
return InteractionTrigger.DELETE_KEY;
}

if (evt.key === chipStrings.BACKSPACE_KEY) {
return InteractionTrigger.BACKSPACE_KEY;
}

// Default case, should never be returned
return InteractionTrigger.UNSPECIFIED;
}
}

// tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
export default MDCChipTrailingActionFoundation;
28 changes: 28 additions & 0 deletions packages/mdc-chips/trailingaction/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license
* Copyright 2020 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

export * from './adapter';
export * from './component';
export * from './foundation';
export * from './types';
export {strings as trailingActionStrings} from './constants';
Loading

0 comments on commit 9ebee4c

Please sign in to comment.