Skip to content

Commit

Permalink
feat(chips): Expose "action" component
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 363786852
  • Loading branch information
patrickrodee authored and copybara-github committed Mar 19, 2021
1 parent f524626 commit 03d34bb
Show file tree
Hide file tree
Showing 14 changed files with 1,581 additions and 0 deletions.
45 changes: 45 additions & 0 deletions packages/mdc-chips/action/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @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 {Attributes, Events} 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 MDCChipActionAdapter {
emitEvent<D extends object>(eventName: Events, eventDetail: D): void;

focus(): void;

getAttribute(attr: Attributes): string|null;

getElementID(): string;

removeAttribute(attr: Attributes): void;

setAttribute(attr: Attributes, value: string): void;
}
53 changes: 53 additions & 0 deletions packages/mdc-chips/action/component-ripple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @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.
*/

/**
* Computes the ripple client rect for the primary action given the raw client
* rect and the selected width graphic style property.
*/
export function computePrimaryActionRippleClientRect(
clientRect: ClientRect, graphicSelectedWidthStyleValue: string): ClientRect {
// parseInt is banned so we need to manually format and parse the string.
const graphicWidth = Number(graphicSelectedWidthStyleValue.replace('px', ''));
if (Number.isNaN(graphicWidth)) {
return clientRect;
}
// Can't use the spread operator because it has internal problems
return {
width: clientRect.width + graphicWidth,
height: clientRect.height,
top: clientRect.top,
right: clientRect.right,
bottom: clientRect.bottom,
left: clientRect.left,
};
}

/**
* Provides the CSS custom property whose value is read by
* computePrimaryRippleClientRect. The CSS custom property provides the width
* of the chip graphic when selected. It is only set for the unselected chip
* variant without a leadinc icon. In all other cases, it will have no value.
*/
export const GRAPHIC_SELECTED_WIDTH_STYLE_PROP =
'--mdc-chip-graphic-selected-width';
173 changes: 173 additions & 0 deletions packages/mdc-chips/action/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/**
* @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 {closest} from '@material/dom/ponyfill';
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 {MDCChipActionAdapter} from './adapter';
import {computePrimaryActionRippleClientRect, GRAPHIC_SELECTED_WIDTH_STYLE_PROP} from './component-ripple';
import {ActionType, CssClasses, FocusBehavior} from './constants';
import {MDCChipActionFoundation} from './foundation';
import {MDCChipPrimaryActionFoundation} from './primary-foundation';
import {MDCChipTrailingActionFoundation} from './trailing-foundation';

/**
* MDCChipActionFactory is used by the parent MDCChip component to initialize
* chip actions.
*/
export type MDCChipActionFactory =
(el: Element, foundation?: MDCChipActionFoundation) => MDCChipAction;

/**
* MDCChipAction provides component encapsulation of the different foundation
* implementations.
*/
export class MDCChipAction extends
MDCComponent<MDCChipActionFoundation> implements MDCRippleCapableSurface {
static attachTo(root: Element): MDCChipAction {
return new MDCChipAction(root);
}

private readonly rootHTML = this.root as HTMLElement;

// Assigned in #initialize()
private rippleInstance!: MDCRipple;
// Assigned in #initialSyncWithDOM()
private handleClick!: SpecificEventListener<'click'>;
private handleKeydown!: SpecificEventListener<'keydown'>;

get ripple(): MDCRipple {
return this.rippleInstance;
}

initialize(
rippleFactory: MDCRippleFactory = (el, foundation) =>
new MDCRipple(el, foundation)) {
const rippleAdapter: MDCRippleAdapter = {
...MDCRipple.createAdapter(this),
computeBoundingRect: () => this.computeRippleClientRect(),
};
this.rippleInstance =
rippleFactory(this.root, new MDCRippleFoundation(rippleAdapter));
}

initialSyncWithDOM() {
this.handleClick = () => {
this.foundation.handleClick();
};

this.handleKeydown = (event: KeyboardEvent) => {
this.foundation.handleKeydown(event);
};

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: MDCChipActionAdapter = {
emitEvent: (eventName, eventDetail) => {
this.emit(eventName, eventDetail, true /* shouldBubble */);
},
focus: () => {
this.rootHTML.focus();
},
getAttribute: (attrName) => this.root.getAttribute(attrName),
getElementID: () => this.root.id,
removeAttribute: (name) => {
this.root.removeAttribute(name);
},
setAttribute: (name, value) => {
this.root.setAttribute(name, value);
},
};

if (this.root.classList.contains(CssClasses.TRAILING_ACTION)) {
return new MDCChipTrailingActionFoundation(adapter);
}

// Default to the primary foundation
return new MDCChipPrimaryActionFoundation(adapter);
}

setDisabled(isDisabled: boolean) {
this.foundation.setDisabled(isDisabled);
}

isDisabled(): boolean {
return this.foundation.isDisabled();
}

setFocus(behavior: FocusBehavior) {
this.foundation.setFocus(behavior);
}

isFocusable() {
return this.foundation.isFocusable();
}

setSelected(isSelected: boolean) {
this.foundation.setSelected(isSelected);
}

isSelected(): boolean {
return this.foundation.isSelected();
}

isSelectable(): boolean {
return this.foundation.isSelectable();
}

actionType(): ActionType {
return this.foundation.actionType();
}

private computeRippleClientRect(): ClientRect {
if (this.root.classList.contains(CssClasses.PRIMARY_ACTION)) {
const chipRoot = closest(this.root, `.${CssClasses.CHIP_ROOT}`);
// Return the root client rect since it's better than nothing
if (!chipRoot) return this.root.getBoundingClientRect();
const graphicWidth = window.getComputedStyle(chipRoot).getPropertyValue(
GRAPHIC_SELECTED_WIDTH_STYLE_PROP);
return computePrimaryActionRippleClientRect(
chipRoot.getBoundingClientRect(), graphicWidth);
}

return this.root.getBoundingClientRect();
}
}
83 changes: 83 additions & 0 deletions packages/mdc-chips/action/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @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.
*/

/**
* CssClasses provides the classes to be queried and manipulated on the root.
*/
export enum CssClasses {
PRIMARY_ACTION = 'mdc-evolution-chip__action--primary',
TRAILING_ACTION = 'mdc-evolution-chip__action--trailing',
CHIP_ROOT = 'mdc-evolution-chip',
}

/**
* InteractionTrigger provides detail of the different triggers for action
* interactions.
*/
export enum InteractionTrigger {
UNSPECIFIED, // Default type
CLICK,
BACKSPACE_KEY,
DELETE_KEY,
SPACEBAR_KEY,
ENTER_KEY,
}

/**
* ActionType provides the different types of available actions.
*/
export enum ActionType {
UNSPECIFIED, // Default type
PRIMARY,
TRAILING,
}

/**
* Events provides the different events emitted by the action.
*/
export enum Events {
INTERACTION = 'MDCChipAction:interaction',
NAVIGATION = 'MDCChipAction:navigation',
}

/**
* FocusBehavior provides configurations for focusing or unfocusing an action.
*/
export enum FocusBehavior {
FOCUSABLE,
FOCUSABLE_AND_FOCUSED,
NOT_FOCUSABLE,
}

/**
* Attributes provides the HTML attributes used by the foundation.
*/
export enum Attributes {
ARIA_DISABLED = 'aria-disabled',
ARIA_HIDDEN = 'aria-hidden',
ARIA_SELECTED = 'aria-selected',
DATA_DELETABLE = 'data-mdc-deletable',
DISABLED = 'disabled',
ROLE = 'role',
TAB_INDEX = 'tabindex',
}
Loading

0 comments on commit 03d34bb

Please sign in to comment.