0
# Adapter Interface
1
2
The MDCRippleAdapter interface defines the required methods for integrating ripples with different frameworks and DOM environments. It abstracts DOM operations, event handling, and CSS manipulation to enable custom implementations.
3
4
## Capabilities
5
6
### MDCRippleAdapter Interface
7
8
Integration interface for custom frameworks and components.
9
10
```typescript { .api }
11
/**
12
* Adapter interface defining required methods for ripple integration
13
* Implement this interface to integrate ripples with custom frameworks
14
*/
15
interface MDCRippleAdapter {
16
/** Check if browser supports CSS custom properties */
17
browserSupportsCssVars(): boolean;
18
19
/** Check if this ripple instance is unbounded */
20
isUnbounded(): boolean;
21
22
/** Check if the surface is currently in active state (:active pseudo-class) */
23
isSurfaceActive(): boolean;
24
25
/** Check if the surface is disabled */
26
isSurfaceDisabled(): boolean;
27
28
/** Add CSS class to the ripple surface */
29
addClass(className: string): void;
30
31
/** Remove CSS class from the ripple surface */
32
removeClass(className: string): void;
33
34
/** Check if ripple surface contains the given event target */
35
containsEventTarget(target: EventTarget | null): boolean;
36
37
/** Register event handler on the ripple surface */
38
registerInteractionHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
39
40
/** Remove event handler from the ripple surface */
41
deregisterInteractionHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
42
43
/** Register event handler on document.documentElement */
44
registerDocumentInteractionHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
45
46
/** Remove event handler from document.documentElement */
47
deregisterDocumentInteractionHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
48
49
/** Register window resize event handler */
50
registerResizeHandler(handler: SpecificEventListener<'resize'>): void;
51
52
/** Remove window resize event handler */
53
deregisterResizeHandler(handler: SpecificEventListener<'resize'>): void;
54
55
/** Update CSS custom property on the ripple surface */
56
updateCssVariable(varName: string, value: string | null): void;
57
58
/** Get bounding rectangle of the ripple surface */
59
computeBoundingRect(): DOMRect;
60
61
/** Get current window scroll offset coordinates */
62
getWindowPageOffset(): MDCRipplePoint;
63
}
64
```
65
66
### Browser Support Detection
67
68
#### browserSupportsCssVars
69
70
Detects CSS custom property support with Safari 9 workarounds.
71
72
```typescript { .api }
73
/**
74
* Check if browser supports CSS custom properties
75
* Should handle Safari 9 compatibility issues with CSS variables
76
* @returns True if CSS custom properties are supported
77
*/
78
browserSupportsCssVars(): boolean;
79
```
80
81
**Usage Example:**
82
83
```typescript
84
// Standard implementation using the provided utility
85
import { supportsCssVariables } from "@material/ripple/util";
86
87
const adapter: MDCRippleAdapter = {
88
browserSupportsCssVars: () => supportsCssVariables(window),
89
// ... other methods
90
};
91
```
92
93
### Surface State Detection
94
95
#### isUnbounded
96
97
Determines if the ripple should extend beyond element bounds.
98
99
```typescript { .api }
100
/**
101
* Check if this ripple instance is unbounded
102
* Unbounded ripples extend beyond the element's boundaries
103
* @returns True if ripple is unbounded
104
*/
105
isUnbounded(): boolean;
106
```
107
108
#### isSurfaceActive
109
110
Detects if the surface is in an active state (typically :active pseudo-class).
111
112
```typescript { .api }
113
/**
114
* Check if the surface is currently in active state
115
* Used for keyboard activation detection and proper animation timing
116
* @returns True if surface is active (e.g., :active pseudo-class)
117
*/
118
isSurfaceActive(): boolean;
119
```
120
121
#### isSurfaceDisabled
122
123
Checks if the surface should not respond to interactions.
124
125
```typescript { .api }
126
/**
127
* Check if the surface is disabled
128
* Disabled surfaces should not show ripple effects
129
* @returns True if surface is disabled
130
*/
131
isSurfaceDisabled(): boolean;
132
```
133
134
### DOM Manipulation
135
136
#### addClass / removeClass
137
138
CSS class management for ripple states.
139
140
```typescript { .api }
141
/**
142
* Add CSS class to the ripple surface
143
* @param className - CSS class name to add
144
*/
145
addClass(className: string): void;
146
147
/**
148
* Remove CSS class from the ripple surface
149
* @param className - CSS class name to remove
150
*/
151
removeClass(className: string): void;
152
```
153
154
#### containsEventTarget
155
156
Event target containment check for nested element handling.
157
158
```typescript { .api }
159
/**
160
* Check if ripple surface contains the given event target
161
* Used to determine if events should be handled by this ripple instance
162
* @param target - Event target to check
163
* @returns True if target is within the ripple surface
164
*/
165
containsEventTarget(target: EventTarget | null): boolean;
166
```
167
168
### Event Management
169
170
#### registerInteractionHandler / deregisterInteractionHandler
171
172
Surface-level event handler management.
173
174
```typescript { .api }
175
/**
176
* Register event handler on the ripple surface
177
* @param evtType - Event type (e.g., 'click', 'touchstart')
178
* @param handler - Event handler function
179
*/
180
registerInteractionHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
181
182
/**
183
* Remove event handler from the ripple surface
184
* @param evtType - Event type
185
* @param handler - Event handler function to remove
186
*/
187
deregisterInteractionHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
188
```
189
190
#### registerDocumentInteractionHandler / deregisterDocumentInteractionHandler
191
192
Document-level event handler management for deactivation events.
193
194
```typescript { .api }
195
/**
196
* Register event handler on document.documentElement
197
* Used for capturing deactivation events outside the surface
198
* @param evtType - Event type
199
* @param handler - Event handler function
200
*/
201
registerDocumentInteractionHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
202
203
/**
204
* Remove event handler from document.documentElement
205
* @param evtType - Event type
206
* @param handler - Event handler function to remove
207
*/
208
deregisterDocumentInteractionHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
209
```
210
211
#### registerResizeHandler / deregisterResizeHandler
212
213
Window resize event management for layout updates.
214
215
```typescript { .api }
216
/**
217
* Register window resize event handler
218
* Used for unbounded ripples that need layout updates on resize
219
* @param handler - Resize event handler function
220
*/
221
registerResizeHandler(handler: SpecificEventListener<'resize'>): void;
222
223
/**
224
* Remove window resize event handler
225
* @param handler - Resize event handler function to remove
226
*/
227
deregisterResizeHandler(handler: SpecificEventListener<'resize'>): void;
228
```
229
230
### CSS and Layout
231
232
#### updateCssVariable
233
234
CSS custom property management for ripple animations.
235
236
```typescript { .api }
237
/**
238
* Update CSS custom property on the ripple surface
239
* @param varName - CSS custom property name (e.g., '--mdc-ripple-fg-scale')
240
* @param value - CSS property value or null to remove
241
*/
242
updateCssVariable(varName: string, value: string | null): void;
243
```
244
245
#### computeBoundingRect
246
247
Element dimension and position calculation.
248
249
```typescript { .api }
250
/**
251
* Get bounding rectangle of the ripple surface
252
* Used for ripple size calculations and positioning
253
* @returns DOMRect with element dimensions and position
254
*/
255
computeBoundingRect(): DOMRect;
256
```
257
258
#### getWindowPageOffset
259
260
Window scroll position for coordinate calculations.
261
262
```typescript { .api }
263
/**
264
* Get current window scroll offset coordinates
265
* Used for accurate event coordinate calculations
266
* @returns Object with x and y scroll offsets
267
*/
268
getWindowPageOffset(): MDCRipplePoint;
269
```
270
271
## Implementation Examples
272
273
### Standard DOM Implementation
274
275
```typescript
276
import { MDCRippleAdapter, MDCRipplePoint } from "@material/ripple";
277
import { supportsCssVariables } from "@material/ripple/util";
278
import { applyPassive } from "@material/dom/events";
279
import { matches } from "@material/dom/ponyfill";
280
281
class StandardRippleAdapter implements MDCRippleAdapter {
282
constructor(private element: HTMLElement, private surface: { unbounded?: boolean; disabled?: boolean }) {}
283
284
browserSupportsCssVars(): boolean {
285
return supportsCssVariables(window);
286
}
287
288
isUnbounded(): boolean {
289
return Boolean(this.surface.unbounded);
290
}
291
292
isSurfaceActive(): boolean {
293
return matches(this.element, ':active');
294
}
295
296
isSurfaceDisabled(): boolean {
297
return Boolean(this.surface.disabled);
298
}
299
300
addClass(className: string): void {
301
this.element.classList.add(className);
302
}
303
304
removeClass(className: string): void {
305
this.element.classList.remove(className);
306
}
307
308
containsEventTarget(target: EventTarget | null): boolean {
309
return this.element.contains(target as Node);
310
}
311
312
registerInteractionHandler(evtType: string, handler: EventListener): void {
313
this.element.addEventListener(evtType, handler, applyPassive());
314
}
315
316
deregisterInteractionHandler(evtType: string, handler: EventListener): void {
317
this.element.removeEventListener(evtType, handler, applyPassive());
318
}
319
320
registerDocumentInteractionHandler(evtType: string, handler: EventListener): void {
321
document.documentElement.addEventListener(evtType, handler, applyPassive());
322
}
323
324
deregisterDocumentInteractionHandler(evtType: string, handler: EventListener): void {
325
document.documentElement.removeEventListener(evtType, handler, applyPassive());
326
}
327
328
registerResizeHandler(handler: EventListener): void {
329
window.addEventListener('resize', handler);
330
}
331
332
deregisterResizeHandler(handler: EventListener): void {
333
window.removeEventListener('resize', handler);
334
}
335
336
updateCssVariable(varName: string, value: string | null): void {
337
this.element.style.setProperty(varName, value);
338
}
339
340
computeBoundingRect(): DOMRect {
341
return this.element.getBoundingClientRect();
342
}
343
344
getWindowPageOffset(): MDCRipplePoint {
345
return { x: window.pageXOffset, y: window.pageYOffset };
346
}
347
}
348
```
349
350
### React Integration Example
351
352
```typescript
353
import React, { useRef, useEffect } from 'react';
354
import { MDCRippleFoundation, MDCRippleAdapter } from '@material/ripple';
355
356
interface RippleProps {
357
unbounded?: boolean;
358
disabled?: boolean;
359
children: React.ReactNode;
360
}
361
362
export const RippleComponent: React.FC<RippleProps> = ({
363
unbounded = false,
364
disabled = false,
365
children
366
}) => {
367
const elementRef = useRef<HTMLDivElement>(null);
368
const foundationRef = useRef<MDCRippleFoundation | null>(null);
369
370
useEffect(() => {
371
if (!elementRef.current) return;
372
373
const adapter: MDCRippleAdapter = {
374
browserSupportsCssVars: () => CSS.supports('--css-vars', 'yes'),
375
isUnbounded: () => unbounded,
376
isSurfaceActive: () => elementRef.current?.matches(':active') ?? false,
377
isSurfaceDisabled: () => disabled,
378
addClass: (className) => elementRef.current?.classList.add(className),
379
removeClass: (className) => elementRef.current?.classList.remove(className),
380
containsEventTarget: (target) => elementRef.current?.contains(target as Node) ?? false,
381
registerInteractionHandler: (evtType, handler) => {
382
elementRef.current?.addEventListener(evtType, handler);
383
},
384
deregisterInteractionHandler: (evtType, handler) => {
385
elementRef.current?.removeEventListener(evtType, handler);
386
},
387
registerDocumentInteractionHandler: (evtType, handler) => {
388
document.documentElement.addEventListener(evtType, handler);
389
},
390
deregisterDocumentInteractionHandler: (evtType, handler) => {
391
document.documentElement.removeEventListener(evtType, handler);
392
},
393
registerResizeHandler: (handler) => {
394
window.addEventListener('resize', handler);
395
},
396
deregisterResizeHandler: (handler) => {
397
window.removeEventListener('resize', handler);
398
},
399
updateCssVariable: (varName, value) => {
400
elementRef.current?.style.setProperty(varName, value);
401
},
402
computeBoundingRect: () => {
403
return elementRef.current?.getBoundingClientRect() ?? new DOMRect();
404
},
405
getWindowPageOffset: () => ({
406
x: window.pageXOffset,
407
y: window.pageYOffset
408
})
409
};
410
411
foundationRef.current = new MDCRippleFoundation(adapter);
412
foundationRef.current.init();
413
414
return () => {
415
foundationRef.current?.destroy();
416
};
417
}, [unbounded, disabled]);
418
419
return (
420
<div ref={elementRef} className="ripple-surface">
421
{children}
422
</div>
423
);
424
};
425
```
426
427
### Custom Framework Adapter
428
429
```typescript
430
// Example for a hypothetical framework
431
class CustomFrameworkRippleAdapter implements MDCRippleAdapter {
432
constructor(
433
private component: CustomFrameworkComponent,
434
private element: FrameworkElement
435
) {}
436
437
browserSupportsCssVars(): boolean {
438
return this.component.framework.supportsCssVars();
439
}
440
441
isUnbounded(): boolean {
442
return this.component.getProperty('unbounded');
443
}
444
445
isSurfaceActive(): boolean {
446
return this.component.hasState('active');
447
}
448
449
isSurfaceDisabled(): boolean {
450
return this.component.getProperty('disabled');
451
}
452
453
addClass(className: string): void {
454
this.element.addClass(className);
455
}
456
457
removeClass(className: string): void {
458
this.element.removeClass(className);
459
}
460
461
containsEventTarget(target: EventTarget | null): boolean {
462
return this.element.contains(target);
463
}
464
465
registerInteractionHandler(evtType: string, handler: EventListener): void {
466
this.element.on(evtType, handler);
467
}
468
469
deregisterInteractionHandler(evtType: string, handler: EventListener): void {
470
this.element.off(evtType, handler);
471
}
472
473
// ... implement remaining methods using framework APIs
474
}
475
```