or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

adapter.mdcomponent.mdfoundation.mdindex.mdsass.mdutilities.md

adapter.mddocs/

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

```