mtrl 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.typedocignore +11 -0
  2. package/DOCS.md +153 -0
  3. package/index.ts +18 -3
  4. package/package.json +7 -2
  5. package/src/components/badge/_styles.scss +174 -0
  6. package/src/components/badge/api.ts +292 -0
  7. package/src/components/badge/badge.ts +52 -0
  8. package/src/components/badge/config.ts +68 -0
  9. package/src/components/badge/constants.ts +30 -0
  10. package/src/components/badge/features.ts +185 -0
  11. package/src/components/badge/index.ts +4 -0
  12. package/src/components/badge/types.ts +105 -0
  13. package/src/components/button/types.ts +174 -29
  14. package/src/components/carousel/_styles.scss +645 -0
  15. package/src/components/carousel/api.ts +147 -0
  16. package/src/components/carousel/carousel.ts +178 -0
  17. package/src/components/carousel/config.ts +91 -0
  18. package/src/components/carousel/constants.ts +95 -0
  19. package/src/components/carousel/features/drag.ts +388 -0
  20. package/src/components/carousel/features/index.ts +8 -0
  21. package/src/components/carousel/features/slides.ts +682 -0
  22. package/src/components/carousel/index.ts +38 -0
  23. package/src/components/carousel/types.ts +327 -0
  24. package/src/components/dialog/_styles.scss +213 -0
  25. package/src/components/dialog/api.ts +283 -0
  26. package/src/components/dialog/config.ts +113 -0
  27. package/src/components/dialog/constants.ts +32 -0
  28. package/src/components/dialog/dialog.ts +56 -0
  29. package/src/components/dialog/features.ts +713 -0
  30. package/src/components/dialog/index.ts +15 -0
  31. package/src/components/dialog/types.ts +221 -0
  32. package/src/components/progress/_styles.scss +13 -1
  33. package/src/components/progress/api.ts +2 -2
  34. package/src/components/progress/progress.ts +2 -2
  35. package/src/components/progress/types.ts +3 -0
  36. package/src/components/radios/_styles.scss +232 -0
  37. package/src/components/radios/api.ts +100 -0
  38. package/src/components/radios/config.ts +60 -0
  39. package/src/components/radios/constants.ts +28 -0
  40. package/src/components/radios/index.ts +4 -0
  41. package/src/components/radios/radio.ts +269 -0
  42. package/src/components/radios/radios.ts +42 -0
  43. package/src/components/radios/types.ts +232 -0
  44. package/src/components/sheet/_styles.scss +236 -0
  45. package/src/components/sheet/api.ts +96 -0
  46. package/src/components/sheet/config.ts +66 -0
  47. package/src/components/sheet/constants.ts +20 -0
  48. package/src/components/sheet/features/content.ts +51 -0
  49. package/src/components/sheet/features/gestures.ts +177 -0
  50. package/src/components/sheet/features/index.ts +6 -0
  51. package/src/components/sheet/features/position.ts +42 -0
  52. package/src/components/sheet/features/state.ts +116 -0
  53. package/src/components/sheet/features/title.ts +86 -0
  54. package/src/components/sheet/index.ts +4 -0
  55. package/src/components/sheet/sheet.ts +57 -0
  56. package/src/components/sheet/types.ts +266 -0
  57. package/src/components/slider/_styles.scss +518 -0
  58. package/src/components/slider/api.ts +336 -0
  59. package/src/components/slider/config.ts +145 -0
  60. package/src/components/slider/constants.ts +28 -0
  61. package/src/components/slider/features/appearance.ts +140 -0
  62. package/src/components/slider/features/disabled.ts +43 -0
  63. package/src/components/slider/features/events.ts +164 -0
  64. package/src/components/slider/features/index.ts +5 -0
  65. package/src/components/slider/features/interactions.ts +256 -0
  66. package/src/components/slider/features/keyboard.ts +114 -0
  67. package/src/components/slider/features/slider.ts +336 -0
  68. package/src/components/slider/features/structure.ts +264 -0
  69. package/src/components/slider/features/ui.ts +518 -0
  70. package/src/components/slider/index.ts +9 -0
  71. package/src/components/slider/slider.ts +58 -0
  72. package/src/components/slider/types.ts +166 -0
  73. package/src/components/tabs/_styles.scss +224 -0
  74. package/src/components/tabs/api.ts +443 -0
  75. package/src/components/tabs/config.ts +80 -0
  76. package/src/components/tabs/constants.ts +12 -0
  77. package/src/components/tabs/index.ts +4 -0
  78. package/src/components/tabs/tabs.ts +52 -0
  79. package/src/components/tabs/types.ts +247 -0
  80. package/src/components/textfield/_styles.scss +97 -4
  81. package/src/components/tooltip/_styles.scss +241 -0
  82. package/src/components/tooltip/api.ts +411 -0
  83. package/src/components/tooltip/config.ts +78 -0
  84. package/src/components/tooltip/constants.ts +27 -0
  85. package/src/components/tooltip/index.ts +4 -0
  86. package/src/components/tooltip/tooltip.ts +60 -0
  87. package/src/components/tooltip/types.ts +178 -0
  88. package/src/core/build/_ripple.scss +79 -0
  89. package/src/core/build/constants.ts +48 -0
  90. package/src/core/build/icon.ts +137 -0
  91. package/src/core/build/ripple.ts +216 -0
  92. package/src/core/build/text.ts +91 -0
  93. package/src/index.ts +9 -1
  94. package/src/styles/abstract/_variables.scss +24 -12
  95. package/tsconfig.json +22 -0
  96. package/typedoc.json +28 -0
  97. package/typedoc.simple.json +14 -0
@@ -0,0 +1,178 @@
1
+ // src/components/tooltip/types.ts
2
+ import { TOOLTIP_POSITIONS, TOOLTIP_VARIANTS } from './constants';
3
+
4
+ /**
5
+ * Configuration interface for the Tooltip component
6
+ * @category Components
7
+ */
8
+ export interface TooltipConfig {
9
+ /**
10
+ * Tooltip content text
11
+ * @example 'Delete item'
12
+ */
13
+ text?: string;
14
+
15
+ /**
16
+ * HTML element that triggers the tooltip
17
+ * @example document.querySelector('#my-button')
18
+ */
19
+ target?: HTMLElement;
20
+
21
+ /**
22
+ * Tooltip position relative to the target
23
+ * @default 'bottom'
24
+ */
25
+ position?: keyof typeof TOOLTIP_POSITIONS | string;
26
+
27
+ /**
28
+ * Tooltip variant that determines visual styling
29
+ * @default 'default'
30
+ */
31
+ variant?: keyof typeof TOOLTIP_VARIANTS | string;
32
+
33
+ /**
34
+ * Whether the tooltip is initially visible
35
+ * @default false
36
+ */
37
+ visible?: boolean;
38
+
39
+ /**
40
+ * Show delay in milliseconds
41
+ * @default 300
42
+ */
43
+ showDelay?: number;
44
+
45
+ /**
46
+ * Hide delay in milliseconds
47
+ * @default 100
48
+ */
49
+ hideDelay?: number;
50
+
51
+ /**
52
+ * Whether to show the tooltip on focus
53
+ * @default true
54
+ */
55
+ showOnFocus?: boolean;
56
+
57
+ /**
58
+ * Whether to show the tooltip on hover
59
+ * @default true
60
+ */
61
+ showOnHover?: boolean;
62
+
63
+ /**
64
+ * Additional CSS classes to add to the tooltip
65
+ */
66
+ class?: string;
67
+
68
+ /**
69
+ * Component prefix for class names
70
+ * @default 'mtrl'
71
+ */
72
+ prefix?: string;
73
+
74
+ /**
75
+ * Component name used in class generation
76
+ */
77
+ componentName?: string;
78
+
79
+ /**
80
+ * Optional z-index for the tooltip
81
+ */
82
+ zIndex?: number;
83
+
84
+ /**
85
+ * Whether to enable rich (HTML) content
86
+ * @default false
87
+ */
88
+ rich?: boolean;
89
+ }
90
+
91
+ /**
92
+ * Tooltip component interface
93
+ * @category Components
94
+ */
95
+ export interface TooltipComponent {
96
+ /** The tooltip's DOM element */
97
+ element: HTMLElement;
98
+
99
+ /** The tooltip's target element */
100
+ target: HTMLElement | null;
101
+
102
+ /** API for managing component lifecycle */
103
+ lifecycle: {
104
+ /** Destroys the component and cleans up resources */
105
+ destroy: () => void;
106
+ };
107
+
108
+ /**
109
+ * Gets a class name with the component's prefix
110
+ * @param name - Base class name
111
+ * @returns Prefixed class name
112
+ */
113
+ getClass: (name: string) => string;
114
+
115
+ /**
116
+ * Sets the tooltip text content
117
+ * @param text - New text content
118
+ * @returns The tooltip component for chaining
119
+ */
120
+ setText: (text: string) => TooltipComponent;
121
+
122
+ /**
123
+ * Gets the tooltip text content
124
+ * @returns Tooltip text content
125
+ */
126
+ getText: () => string;
127
+
128
+ /**
129
+ * Sets the tooltip position
130
+ * @param position - Position value
131
+ * @returns The tooltip component for chaining
132
+ */
133
+ setPosition: (position: keyof typeof TOOLTIP_POSITIONS | string) => TooltipComponent;
134
+
135
+ /**
136
+ * Gets the current tooltip position
137
+ * @returns Current position
138
+ */
139
+ getPosition: () => string;
140
+
141
+ /**
142
+ * Sets the tooltip target element
143
+ * @param target - Element to attach tooltip to
144
+ * @returns The tooltip component for chaining
145
+ */
146
+ setTarget: (target: HTMLElement) => TooltipComponent;
147
+
148
+ /**
149
+ * Shows the tooltip
150
+ * @param immediate - Whether to show immediately (bypassing delay)
151
+ * @returns The tooltip component for chaining
152
+ */
153
+ show: (immediate?: boolean) => TooltipComponent;
154
+
155
+ /**
156
+ * Hides the tooltip
157
+ * @param immediate - Whether to hide immediately (bypassing delay)
158
+ * @returns The tooltip component for chaining
159
+ */
160
+ hide: (immediate?: boolean) => TooltipComponent;
161
+
162
+ /**
163
+ * Checks if the tooltip is currently visible
164
+ * @returns Whether the tooltip is visible
165
+ */
166
+ isVisible: () => boolean;
167
+
168
+ /**
169
+ * Updates the tooltip's position relative to its target
170
+ * @returns The tooltip component for chaining
171
+ */
172
+ updatePosition: () => TooltipComponent;
173
+
174
+ /**
175
+ * Destroys the tooltip component and cleans up resources
176
+ */
177
+ destroy: () => void;
178
+ }
@@ -0,0 +1,79 @@
1
+ // src/components/ripple/_ripple.scss
2
+ @use '../../styles/abstract/base' as base;
3
+ @use '../../styles/abstract/variables' as v;
4
+ @use '../../styles/abstract/functions' as f;
5
+ @use '../../styles/abstract/mixins' as m;
6
+ @use '../../styles/abstract/theme' as t;
7
+
8
+ $component: '#{base.$prefix}-ripple';
9
+
10
+ .#{$component} {
11
+ // Ripple container
12
+ position: absolute;
13
+ top: 0;
14
+ left: 0;
15
+ right: 0;
16
+ bottom: 0;
17
+ overflow: hidden;
18
+ border-radius: inherit;
19
+ pointer-events: none;
20
+ z-index: 0;
21
+
22
+ // Ripple element
23
+ &-wave {
24
+ position: absolute;
25
+ border-radius: 50%;
26
+ background-color: currentColor;
27
+ transform: scale(0);
28
+ opacity: 0;
29
+ pointer-events: none;
30
+ will-change: transform, opacity;
31
+
32
+ // Animation
33
+ transition-property: transform, opacity;
34
+ transition-duration: v.motion('duration-short4');
35
+ transition-timing-function: v.motion('easing-standard');
36
+
37
+ // Active ripple
38
+ &.active {
39
+ transform: scale(1);
40
+ opacity: v.state('hover-state-layer-opacity');
41
+ }
42
+
43
+ &.fade-out {
44
+ opacity: 0;
45
+ }
46
+ }
47
+ }
48
+
49
+ // Standalone utility for adding ripple to any element
50
+ [data-ripple] {
51
+ position: relative;
52
+ overflow: hidden;
53
+
54
+ &::after {
55
+ content: '';
56
+ position: absolute;
57
+ top: 0;
58
+ left: 0;
59
+ right: 0;
60
+ bottom: 0;
61
+ z-index: 0;
62
+ pointer-events: none;
63
+ }
64
+
65
+ // Handle ripple color based on data attribute
66
+ &[data-ripple="light"]::after {
67
+ background-color: rgba(255, 255, 255, 0.3);
68
+ }
69
+
70
+ &[data-ripple="dark"]::after {
71
+ background-color: rgba(0, 0, 0, 0.1);
72
+ }
73
+
74
+ // Make content appear above ripple
75
+ > * {
76
+ position: relative;
77
+ z-index: 1;
78
+ }
79
+ }
@@ -0,0 +1,48 @@
1
+ // src/core/build/constants.ts
2
+
3
+ /**
4
+ * Animation timing functions for ripple effect
5
+ */
6
+ export enum RIPPLE_TIMING {
7
+ LINEAR = 'linear',
8
+ EASE = 'ease',
9
+ EASE_IN = 'ease-in',
10
+ EASE_OUT = 'ease-out',
11
+ EASE_IN_OUT = 'ease-in-out',
12
+ MATERIAL = 'cubic-bezier(0.4, 0.0, 0.2, 1)'
13
+ }
14
+
15
+ /**
16
+ * Default configuration for ripple effect
17
+ */
18
+ export const RIPPLE_CONFIG = {
19
+ duration: 375,
20
+ timing: RIPPLE_TIMING.LINEAR,
21
+ opacity: ['1', '0.3'] as [string, string]
22
+ };
23
+
24
+ /**
25
+ * Validation schema for ripple configuration
26
+ */
27
+ export const RIPPLE_SCHEMA = {
28
+ duration: {
29
+ type: 'number',
30
+ minimum: 0,
31
+ default: RIPPLE_CONFIG.duration
32
+ },
33
+ timing: {
34
+ type: 'string',
35
+ enum: Object.values(RIPPLE_TIMING),
36
+ default: RIPPLE_CONFIG.timing
37
+ },
38
+ opacity: {
39
+ type: 'array',
40
+ items: {
41
+ type: 'string',
42
+ pattern: '^[0-1](\\.\\d+)?$'
43
+ },
44
+ minItems: 2,
45
+ maxItems: 2,
46
+ default: RIPPLE_CONFIG.opacity
47
+ }
48
+ };
@@ -0,0 +1,137 @@
1
+ // src/core/build/icon.ts
2
+ /**
3
+ * @module core/build
4
+ */
5
+
6
+ /**
7
+ * Options for creating an icon element
8
+ */
9
+ export interface IconElementOptions {
10
+ /**
11
+ * CSS class prefix
12
+ */
13
+ prefix?: string;
14
+
15
+ /**
16
+ * Additional CSS class
17
+ */
18
+ class?: string;
19
+
20
+ /**
21
+ * Icon size variant
22
+ */
23
+ size?: string;
24
+ }
25
+
26
+ /**
27
+ * Configuration for icon manager
28
+ */
29
+ export interface IconConfig {
30
+ /**
31
+ * CSS class prefix
32
+ */
33
+ prefix?: string;
34
+
35
+ /**
36
+ * Component type
37
+ */
38
+ type?: string;
39
+
40
+ /**
41
+ * Icon position ('start' or 'end')
42
+ */
43
+ position?: 'start' | 'end';
44
+
45
+ /**
46
+ * Icon size
47
+ */
48
+ iconSize?: string;
49
+ }
50
+
51
+ /**
52
+ * Icon manager interface
53
+ */
54
+ export interface IconManager {
55
+ /**
56
+ * Sets icon HTML content
57
+ * @param html - Icon HTML content
58
+ * @returns IconManager instance for chaining
59
+ */
60
+ setIcon: (html: string) => IconManager;
61
+
62
+ /**
63
+ * Gets current icon HTML content
64
+ * @returns Current icon HTML
65
+ */
66
+ getIcon: () => string;
67
+
68
+ /**
69
+ * Gets icon element
70
+ * @returns Icon element or null if not created
71
+ */
72
+ getElement: () => HTMLElement | null;
73
+ }
74
+
75
+ /**
76
+ * Creates an icon DOM element
77
+ *
78
+ * @param html - Icon HTML content
79
+ * @param options - Icon options
80
+ * @returns Icon element
81
+ * @private
82
+ */
83
+ const createIconElement = (html: string, options: IconElementOptions = {}): HTMLElement => {
84
+ const PREFIX = options.prefix || 'mtrl';
85
+ const element = document.createElement('span');
86
+ element.className = `${PREFIX}-icon`;
87
+
88
+ if (options.class) {
89
+ element.classList.add(options.class);
90
+ }
91
+ if (options.size) {
92
+ element.classList.add(`${PREFIX}-icon--${options.size}`);
93
+ }
94
+
95
+ element.innerHTML = html;
96
+ return element;
97
+ };
98
+
99
+ /**
100
+ * Creates an icon manager for a component
101
+ *
102
+ * @param element - Parent element
103
+ * @param config - Icon configuration
104
+ * @returns Icon manager interface
105
+ */
106
+ export const createIcon = (element: HTMLElement, config: IconConfig = {}): IconManager => {
107
+ let iconElement: HTMLElement | null = null;
108
+ const PREFIX = config.prefix || 'mtrl';
109
+
110
+ return {
111
+ setIcon(html: string): IconManager {
112
+ if (!iconElement && html) {
113
+ iconElement = createIconElement(html, {
114
+ prefix: PREFIX,
115
+ class: `${PREFIX}-${config.type || 'component'}-icon`,
116
+ size: config.iconSize
117
+ });
118
+ if (config.position === 'end') {
119
+ element.appendChild(iconElement);
120
+ } else {
121
+ element.insertBefore(iconElement, element.firstChild);
122
+ }
123
+ } else if (iconElement && html) {
124
+ iconElement.innerHTML = html;
125
+ }
126
+ return this;
127
+ },
128
+
129
+ getIcon(): string {
130
+ return iconElement ? iconElement.innerHTML : '';
131
+ },
132
+
133
+ getElement(): HTMLElement | null {
134
+ return iconElement;
135
+ }
136
+ };
137
+ };
@@ -0,0 +1,216 @@
1
+ // src/core/build/ripple.ts
2
+
3
+ import { RIPPLE_CONFIG, RIPPLE_TIMING } from './constants';
4
+
5
+ /**
6
+ * Ripple animation configuration
7
+ */
8
+ export interface RippleConfig {
9
+ /**
10
+ * Animation duration in milliseconds
11
+ */
12
+ duration?: number;
13
+
14
+ /**
15
+ * Animation timing function
16
+ */
17
+ timing?: string;
18
+
19
+ /**
20
+ * Opacity start and end values
21
+ */
22
+ opacity?: [string, string];
23
+ }
24
+
25
+ /**
26
+ * End coordinates for ripple animation
27
+ */
28
+ interface EndCoordinates {
29
+ size: string;
30
+ top: string;
31
+ left: string;
32
+ }
33
+
34
+ /**
35
+ * Document event listener
36
+ */
37
+ interface DocumentListener {
38
+ event: string;
39
+ handler: EventListener;
40
+ }
41
+
42
+ /**
43
+ * Ripple controller interface
44
+ */
45
+ export interface RippleController {
46
+ /**
47
+ * Attaches ripple effect to an element
48
+ * @param element - Target element
49
+ */
50
+ mount: (element: HTMLElement) => void;
51
+
52
+ /**
53
+ * Removes ripple effect from an element
54
+ * @param element - Target element
55
+ */
56
+ unmount: (element: HTMLElement) => void;
57
+ }
58
+
59
+ /**
60
+ * Creates a ripple effect instance
61
+ *
62
+ * @param config - Ripple configuration
63
+ * @returns Ripple controller instance
64
+ */
65
+ export const createRipple = (config: RippleConfig = {}): RippleController => {
66
+ // Make sure we fully merge the config options
67
+ const options = {
68
+ ...RIPPLE_CONFIG,
69
+ ...config,
70
+ // Handle nested objects like opacity array
71
+ opacity: config.opacity || RIPPLE_CONFIG.opacity
72
+ };
73
+
74
+ const getEndCoordinates = (bounds: DOMRect): EndCoordinates => {
75
+ const size = Math.max(bounds.width, bounds.height);
76
+ const top = bounds.height > bounds.width
77
+ ? -bounds.height / 2
78
+ : -(bounds.width - bounds.height / 2);
79
+
80
+ return {
81
+ size: `${size * 2}px`,
82
+ top: `${top}px`,
83
+ left: `${size / -2}px`
84
+ };
85
+ };
86
+
87
+ const createRippleElement = (): HTMLDivElement => {
88
+ const ripple = document.createElement('div');
89
+ ripple.className = 'ripple';
90
+ // Initial styles already set in CSS
91
+ ripple.style.transition = `all ${options.duration}ms ${options.timing}`;
92
+ return ripple;
93
+ };
94
+
95
+ // Store document event listeners for cleanup
96
+ let documentListeners: DocumentListener[] = [];
97
+
98
+ // Safe document event handling
99
+ const addDocumentListener = (event: string, handler: EventListener): void => {
100
+ if (typeof document.addEventListener === 'function') {
101
+ document.addEventListener(event, handler);
102
+ documentListeners.push({ event, handler });
103
+ }
104
+ };
105
+
106
+ const removeDocumentListener = (event: string, handler: EventListener): void => {
107
+ if (typeof document.removeEventListener === 'function') {
108
+ document.removeEventListener(event, handler);
109
+ documentListeners = documentListeners.filter(
110
+ listener => !(listener.event === event && listener.handler === handler)
111
+ );
112
+ }
113
+ };
114
+
115
+ const animate = (event: MouseEvent, container: HTMLElement): void => {
116
+ if (!container) return;
117
+
118
+ const bounds = container.getBoundingClientRect();
119
+ const ripple = createRippleElement();
120
+
121
+ // Set initial position and state
122
+ Object.assign(ripple.style, {
123
+ left: `${event.offsetX || bounds.width / 2}px`,
124
+ top: `${event.offsetY || bounds.height / 2}px`,
125
+ transform: 'scale(0)',
126
+ opacity: options.opacity[0]
127
+ });
128
+
129
+ container.appendChild(ripple);
130
+
131
+ // Force reflow
132
+ // eslint-disable-next-line no-unused-expressions
133
+ ripple.offsetHeight;
134
+
135
+ // Animate to end position
136
+ const end = getEndCoordinates(bounds);
137
+ Object.assign(ripple.style, {
138
+ ...end,
139
+ transform: 'scale(1)',
140
+ opacity: options.opacity[1]
141
+ });
142
+
143
+ const cleanup = () => {
144
+ ripple.style.opacity = '0';
145
+
146
+ // Use setTimeout to remove element after animation
147
+ setTimeout(() => {
148
+ if (ripple.parentNode) {
149
+ ripple.parentNode.removeChild(ripple);
150
+ }
151
+ }, options.duration);
152
+
153
+ removeDocumentListener('mouseup', cleanup);
154
+ removeDocumentListener('mouseleave', cleanup);
155
+ };
156
+
157
+ addDocumentListener('mouseup', cleanup);
158
+ addDocumentListener('mouseleave', cleanup);
159
+ };
160
+
161
+ return {
162
+ mount: (element: HTMLElement): void => {
163
+ if (!element) return;
164
+
165
+ // Ensure proper positioning context
166
+ const currentPosition = window.getComputedStyle(element).position;
167
+ if (currentPosition === 'static') {
168
+ element.style.position = 'relative';
169
+ }
170
+ element.style.overflow = 'hidden';
171
+
172
+ // Store the mousedown handler to be able to remove it later
173
+ const mousedownHandler = (e: MouseEvent) => animate(e, element);
174
+
175
+ // Store handler reference on the element
176
+ if (!element.__rippleHandlers) {
177
+ element.__rippleHandlers = [];
178
+ }
179
+ element.__rippleHandlers.push(mousedownHandler);
180
+
181
+ element.addEventListener('mousedown', mousedownHandler);
182
+ },
183
+
184
+ unmount: (element: HTMLElement): void => {
185
+ if (!element) return;
186
+
187
+ // Clear document event listeners
188
+ documentListeners.forEach(({ event, handler }) => {
189
+ removeDocumentListener(event, handler);
190
+ });
191
+ documentListeners = [];
192
+
193
+ // Remove event listeners
194
+ if (element.__rippleHandlers) {
195
+ element.__rippleHandlers.forEach(handler => {
196
+ element.removeEventListener('mousedown', handler);
197
+ });
198
+ element.__rippleHandlers = [];
199
+ }
200
+
201
+ // Remove all ripple elements
202
+ const ripples = element.querySelectorAll('.ripple');
203
+ ripples.forEach(ripple => {
204
+ // Call remove directly to match the test expectation
205
+ ripple.remove();
206
+ });
207
+ }
208
+ };
209
+ };
210
+
211
+ // Extend the HTMLElement interface to add rippleHandlers property
212
+ declare global {
213
+ interface HTMLElement {
214
+ __rippleHandlers?: Array<(e: MouseEvent) => void>;
215
+ }
216
+ }