Skip to content

Commit

Permalink
feat(ripple): add semantic and imperative attaching
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 535409389
  • Loading branch information
asyncLiz authored and copybara-github committed May 25, 2023
1 parent fbd680a commit d65327d
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 40 deletions.
3 changes: 1 addition & 2 deletions ripple/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import './index.js';
import './material-collection.js';

import {KnobTypesToKnobs, MaterialCollection, materialInitsToStoryInits, setUpDemo} from './material-collection.js';
import {boolInput, colorPicker, cssCustomProperty, Knob, numberInput} from './index.js';
import {colorPicker, cssCustomProperty, Knob, numberInput} from './index.js';

import {stories, StoryKnobs} from './stories.js';

Expand All @@ -24,7 +24,6 @@ function cssCustomPropertyAsNumber(

const collection =
new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>('Ripple', [
new Knob('disabled', {ui: boolInput(), defaultValue: false}),
new Knob(
'--md-ripple-pressed-color',
{ui: colorPicker(), wiring: cssCustomProperty}),
Expand Down
18 changes: 6 additions & 12 deletions ripple/demo/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@
import '@material/web/ripple/ripple.js';

import {MaterialStoryInit} from './material-collection.js';
import {ripple} from '@material/web/ripple/directive.js';
import {MdRipple} from '@material/web/ripple/ripple.js';
import {css, html} from 'lit';
import {createRef, ref} from 'lit/directives/ref.js';

/** Knob types for ripple stories. */
export interface StoryKnobs {
disabled: boolean;
'--md-ripple-pressed-color': string;
'--md-ripple-pressed-opacity': number;
'--md-ripple-hover-color': string;
Expand All @@ -32,11 +28,10 @@ const bounded: MaterialStoryInit<StoryKnobs> = {
width: 64px;
}
`,
render({disabled}) {
const rippleRef = createRef<MdRipple>();
render() {
return html`
<div class="container" ${ripple(() => rippleRef.value || null)}>
<md-ripple ?disabled=${disabled} ${ref(rippleRef)}></md-ripple>
<div class="container">
<md-ripple></md-ripple>
</div>
`;
}
Expand Down Expand Up @@ -76,12 +71,11 @@ const unbounded: MaterialStoryInit<StoryKnobs> = {
width: 40px;
}
`,
render({disabled}) {
const rippleRef = createRef<MdRipple>();
render() {
return html`
<div class="container" ${ripple(() => rippleRef.value || null)}>
<div id="touch" class="container">
<div class="icon anchor">
<md-ripple ?disabled=${disabled} ${ref(rippleRef)}></md-ripple>
<md-ripple for="touch"></md-ripple>
</div>
<div class="icon"></div>
</div>
Expand Down
27 changes: 2 additions & 25 deletions ripple/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,8 @@ class RippleDirective extends Directive {
if (!ripple) {
return;
}
switch (event.type) {
case 'click':
ripple.handleClick();
break;
case 'contextmenu':
ripple.handleContextmenu();
break;
case 'pointercancel':
ripple.handlePointercancel(event as PointerEvent);
break;
case 'pointerdown':
await ripple.handlePointerdown(event as PointerEvent);
break;
case 'pointerenter':
ripple.handlePointerenter(event as PointerEvent);
break;
case 'pointerleave':
ripple.handlePointerleave(event as PointerEvent);
break;
case 'pointerup':
ripple.handlePointerup(event as PointerEvent);
break;
default:
break;
}

await ripple.handleEvent(event);
}

override update(part: ElementPart, [ripple]: DirectiveParameters<this>) {
Expand Down
79 changes: 78 additions & 1 deletion ripple/lib/ripple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {html, LitElement, PropertyValues} from 'lit';
import {property, query, state} from 'lit/decorators.js';
import {classMap} from 'lit/directives/class-map.js';

import {Attachable, AttachableController} from '../../controller/attachable-controller.js';
import {EASING} from '../../motion/animation.js';

const PRESS_GROW_MS = 450;
Expand Down Expand Up @@ -64,6 +65,14 @@ enum State {
WAITING_FOR_CLICK
}

/**
* Events that the ripple listens to.
*/
const EVENTS = [
'click', 'contextmenu', 'pointercancel', 'pointerdown', 'pointerenter',
'pointerleave', 'pointerup'
];

/**
* Delay reacting to touch so that we do not show the ripple for a swipe or
* scroll interaction.
Expand All @@ -73,12 +82,24 @@ const TOUCH_DELAY_MS = 150;
/**
* A ripple component.
*/
export class Ripple extends LitElement {
export class Ripple extends LitElement implements Attachable {
/**
* Disables the ripple.
*/
@property({type: Boolean, reflect: true}) disabled = false;

get htmlFor() {
return this.attachableController.htmlFor;
}

set htmlFor(htmlFor: string|null) {
this.attachableController.htmlFor = htmlFor;
}

get control() {
return this.attachableController.control;
}

@state() private hovered = false;
@state() private pressed = false;

Expand All @@ -90,6 +111,21 @@ export class Ripple extends LitElement {
private state = State.INACTIVE;
private rippleStartEvent?: PointerEvent;
private checkBoundsAfterContextMenu = false;
private readonly attachableController =
new AttachableController(this, this.onControlChange.bind(this));

// TODO(b/265337232): Remove once ripple directive is removed. This is used to
// prevent two animations while migrating ripples from the directive to the
// new attachment syntax.
private lastHandledEvent?: Event;

attach(control: HTMLElement) {
this.attachableController.attach(control);
}

detach() {
this.attachableController.detach();
}

handlePointerenter(event: PointerEvent) {
if (!this.shouldReactToEvent(event)) {
Expand Down Expand Up @@ -367,4 +403,45 @@ export class Ripple extends LitElement {
private isTouch({pointerType}: PointerEvent) {
return pointerType === 'touch';
}

/** @private */
async handleEvent(event: Event) {
if (this.lastHandledEvent === event) {
return;
}

this.lastHandledEvent = event;
switch (event.type) {
case 'click':
this.handleClick();
break;
case 'contextmenu':
this.handleContextmenu();
break;
case 'pointercancel':
this.handlePointercancel(event as PointerEvent);
break;
case 'pointerdown':
await this.handlePointerdown(event as PointerEvent);
break;
case 'pointerenter':
this.handlePointerenter(event as PointerEvent);
break;
case 'pointerleave':
this.handlePointerleave(event as PointerEvent);
break;
case 'pointerup':
this.handlePointerup(event as PointerEvent);
break;
default:
break;
}
}

private onControlChange(prev: HTMLElement|null, next: HTMLElement|null) {
for (const event of EVENTS) {
prev?.removeEventListener(event, this);
next?.addEventListener(event, this);
}
}
}

0 comments on commit d65327d

Please sign in to comment.