or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

utilities.mddocs/

0

# Utilities

1

2

The utilities module provides helper functions for CSS variable support detection and event coordinate normalization. These functions handle browser compatibility issues and provide consistent behavior across different input types.

3

4

## Capabilities

5

6

### CSS Variable Support Detection

7

8

#### supportsCssVariables

9

10

Detects CSS custom property support with Safari 9 compatibility handling.

11

12

```typescript { .api }

13

/**

14

* Detect CSS custom property support with Safari 9 compatibility handling

15

* Includes workarounds for WebKit CSS variable bugs and caches results for performance

16

* @param windowObj - Window object to test CSS support on

17

* @param forceRefresh - Force re-detection instead of using cached result (default: false)

18

* @returns True if CSS custom properties are fully supported

19

*/

20

function supportsCssVariables(windowObj: typeof globalThis, forceRefresh?: boolean): boolean;

21

```

22

23

**Usage Examples:**

24

25

```typescript

26

import { supportsCssVariables } from "@material/ripple/util";

27

28

// Basic usage

29

const supportsVars = supportsCssVariables(window);

30

if (supportsVars) {

31

// Use CSS variable-based ripple

32

console.log("CSS variables supported");

33

} else {

34

// Fall back to simpler CSS-only ripple

35

console.log("CSS variables not supported");

36

}

37

38

// Force fresh detection (mainly for testing)

39

const freshResult = supportsCssVariables(window, true);

40

41

// Use in adapter implementation

42

const adapter = {

43

browserSupportsCssVars: () => supportsCssVariables(window),

44

// ... other methods

45

};

46

```

47

48

**Safari 9 Compatibility:**

49

50

This function includes special handling for Safari 9, which has partial CSS variable support that doesn't work correctly with pseudo-elements. The function detects this specific case and returns `false` for Safari 9 to ensure proper fallback behavior.

51

52

```typescript

53

// The function tests both:

54

// 1. CSS.supports('--css-vars', 'yes') - basic CSS variable support

55

// 2. CSS.supports('color', '#00000000') - 8-digit hex color support

56

//

57

// Safari 10+ supports both, Safari 9 only supports the first

58

// This allows detection of Safari 9's broken CSS variable implementation

59

```

60

61

### Event Coordinate Normalization

62

63

#### getNormalizedEventCoords

64

65

Normalizes touch and mouse event coordinates relative to the target element.

66

67

```typescript { .api }

68

/**

69

* Get normalized event coordinates relative to target element

70

* Handles both touch and mouse events with proper coordinate calculation

71

* @param evt - Touch or mouse event (undefined returns {x: 0, y: 0})

72

* @param pageOffset - Current window scroll offset

73

* @param clientRect - Target element's bounding rectangle

74

* @returns Normalized coordinates relative to element

75

*/

76

function getNormalizedEventCoords(

77

evt: Event | undefined,

78

pageOffset: MDCRipplePoint,

79

clientRect: DOMRect

80

): MDCRipplePoint;

81

```

82

83

**Usage Examples:**

84

85

```typescript

86

import { getNormalizedEventCoords } from "@material/ripple/util";

87

88

// In an event handler

89

function handleActivation(evt: Event) {

90

const pageOffset = { x: window.pageXOffset, y: window.pageYOffset };

91

const clientRect = element.getBoundingClientRect();

92

93

const coords = getNormalizedEventCoords(evt, pageOffset, clientRect);

94

95

console.log(`Ripple origin: ${coords.x}, ${coords.y}`);

96

97

// Use coordinates for ripple positioning

98

}

99

100

// Touch event handling

101

element.addEventListener('touchstart', (evt: TouchEvent) => {

102

const coords = getNormalizedEventCoords(

103

evt,

104

{ x: window.pageXOffset, y: window.pageYOffset },

105

element.getBoundingClientRect()

106

);

107

// coords.x and coords.y are relative to element's top-left corner

108

});

109

110

// Mouse event handling

111

element.addEventListener('mousedown', (evt: MouseEvent) => {

112

const coords = getNormalizedEventCoords(

113

evt,

114

{ x: window.pageXOffset, y: window.pageYOffset },

115

element.getBoundingClientRect()

116

);

117

// Same interface for both touch and mouse events

118

});

119

120

// Handle undefined events (programmatic activation)

121

const defaultCoords = getNormalizedEventCoords(undefined, pageOffset, clientRect);

122

console.log(defaultCoords); // { x: 0, y: 0 }

123

```

124

125

**Coordinate Calculation:**

126

127

The function performs different calculations based on event type:

128

129

```typescript

130

// For touch events: uses changedTouches[0].pageX/pageY

131

// For mouse events: uses pageX/pageY

132

// For undefined events: returns { x: 0, y: 0 }

133

134

// Final calculation:

135

// normalizedX = event.pageX - (pageOffset.x + clientRect.left)

136

// normalizedY = event.pageY - (pageOffset.y + clientRect.top)

137

```

138

139

## Advanced Usage

140

141

### Custom CSS Variable Detection

142

143

```typescript

144

import { supportsCssVariables } from "@material/ripple/util";

145

146

class CustomRippleImplementation {

147

private cssVarsSupported: boolean;

148

149

constructor(private element: HTMLElement) {

150

this.cssVarsSupported = supportsCssVariables(window);

151

152

if (this.cssVarsSupported) {

153

this.setupAdvancedRipple();

154

} else {

155

this.setupFallbackRipple();

156

}

157

}

158

159

private setupAdvancedRipple() {

160

// Use CSS variables for dynamic ripple effects

161

this.element.style.setProperty('--ripple-color', 'rgba(0, 0, 0, 0.1)');

162

console.log('Using CSS variable-based ripple');

163

}

164

165

private setupFallbackRipple() {

166

// Use static CSS classes for ripple effects

167

this.element.classList.add('ripple-fallback');

168

console.log('Using CSS-only fallback ripple');

169

}

170

}

171

```

172

173

### Event Coordinate Processing

174

175

```typescript

176

import { getNormalizedEventCoords } from "@material/ripple/util";

177

178

class RippleAnimationController {

179

constructor(private element: HTMLElement) {

180

this.setupEventHandlers();

181

}

182

183

private setupEventHandlers() {

184

// Handle multiple event types with unified coordinate processing

185

const activationEvents = ['mousedown', 'touchstart', 'pointerdown'];

186

187

activationEvents.forEach(eventType => {

188

this.element.addEventListener(eventType, (evt) => {

189

this.handleActivation(evt);

190

});

191

});

192

}

193

194

private handleActivation(evt: Event) {

195

const pageOffset = this.getPageOffset();

196

const clientRect = this.element.getBoundingClientRect();

197

198

// Get normalized coordinates for any event type

199

const coords = getNormalizedEventCoords(evt, pageOffset, clientRect);

200

201

// Calculate ripple properties based on coordinates

202

const centerX = clientRect.width / 2;

203

const centerY = clientRect.height / 2;

204

205

const distanceFromCenter = Math.sqrt(

206

Math.pow(coords.x - centerX, 2) + Math.pow(coords.y - centerY, 2)

207

);

208

209

// Use coordinates for animation positioning

210

this.animateRipple(coords, distanceFromCenter);

211

}

212

213

private getPageOffset() {

214

return {

215

x: window.pageXOffset || document.documentElement.scrollLeft,

216

y: window.pageYOffset || document.documentElement.scrollTop

217

};

218

}

219

220

private animateRipple(origin: { x: number; y: number }, distance: number) {

221

// Custom ripple animation using calculated coordinates

222

console.log(`Animating ripple from ${origin.x}, ${origin.y} with distance ${distance}`);

223

}

224

}

225

```

226

227

### Framework Integration Utilities

228

229

```typescript

230

import { supportsCssVariables, getNormalizedEventCoords } from "@material/ripple/util";

231

232

// React hook for ripple utilities

233

function useRippleUtils() {

234

const cssVarsSupported = React.useMemo(

235

() => supportsCssVariables(window),

236

[]

237

);

238

239

const normalizeEventCoords = React.useCallback(

240

(evt: React.SyntheticEvent, element: HTMLElement) => {

241

const nativeEvent = evt.nativeEvent;

242

const pageOffset = { x: window.pageXOffset, y: window.pageYOffset };

243

const clientRect = element.getBoundingClientRect();

244

245

return getNormalizedEventCoords(nativeEvent, pageOffset, clientRect);

246

},

247

[]

248

);

249

250

return { cssVarsSupported, normalizeEventCoords };

251

}

252

253

// Vue.js composition function

254

function useRipple() {

255

const cssVarsSupported = supportsCssVariables(window);

256

257

function handleRippleEvent(evt: Event, element: HTMLElement) {

258

const coords = getNormalizedEventCoords(

259

evt,

260

{ x: window.pageXOffset, y: window.pageYOffset },

261

element.getBoundingClientRect()

262

);

263

264

return coords;

265

}

266

267

return { cssVarsSupported, handleRippleEvent };

268

}

269

```

270

271

### Testing and Development

272

273

```typescript

274

import { supportsCssVariables } from "@material/ripple/util";

275

276

// Testing CSS variable support in different environments

277

function testCssVariableSupport() {

278

// Test in current environment

279

const currentSupport = supportsCssVariables(window);

280

console.log(`Current environment supports CSS vars: ${currentSupport}`);

281

282

// Force fresh detection (useful for testing)

283

const freshSupport = supportsCssVariables(window, true);

284

console.log(`Fresh detection result: ${freshSupport}`);

285

286

// Mock window object for testing

287

const mockWindow = {

288

CSS: {

289

supports: (property: string, value: string) => {

290

if (property === '--css-vars' && value === 'yes') return true;

291

if (property === 'color' && value === '#00000000') return false; // Simulate Safari 9

292

return false;

293

}

294

}

295

};

296

297

const mockSupport = supportsCssVariables(mockWindow as any);

298

console.log(`Mock Safari 9 result: ${mockSupport}`); // Should be false

299

}

300

301

// Testing coordinate normalization

302

function testCoordinateNormalization() {

303

const mockEvent = new MouseEvent('mousedown', {

304

clientX: 100,

305

clientY: 200

306

});

307

308

// Mock page offset and client rect

309

const pageOffset = { x: 50, y: 75 };

310

const clientRect = new DOMRect(25, 30, 200, 150);

311

312

const coords = getNormalizedEventCoords(mockEvent, pageOffset, clientRect);

313

console.log(`Normalized coordinates: ${coords.x}, ${coords.y}`);

314

315

// Test with undefined event

316

const defaultCoords = getNormalizedEventCoords(undefined, pageOffset, clientRect);

317

console.log(`Default coordinates: ${defaultCoords.x}, ${defaultCoords.y}`); // 0, 0

318

}

319

```

320

321

## Browser Compatibility

322

323

### CSS Variable Support Matrix

324

325

- **Chrome 49+**: Full support

326

- **Firefox 31+**: Full support

327

- **Safari 10+**: Full support

328

- **Safari 9.1**: Partial support (detected and disabled by `supportsCssVariables`)

329

- **Edge 16+**: Full support

330

- **IE**: No support (falls back to CSS-only implementation)

331

332

### Event Handling Compatibility

333

334

- **Touch Events**: Supported in all mobile browsers and modern desktop browsers

335

- **Mouse Events**: Universal support

336

- **Coordinate Normalization**: Handles all event types consistently across browsers

337

338

The utility functions ensure consistent behavior across all supported browsers by detecting capabilities and providing appropriate fallbacks.