mtrl 0.2.2 → 0.2.3

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 (92) 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/index.ts +9 -1
  89. package/src/styles/abstract/_variables.scss +24 -12
  90. package/tsconfig.json +22 -0
  91. package/typedoc.json +28 -0
  92. package/typedoc.simple.json +14 -0
@@ -0,0 +1,388 @@
1
+ // src/components/carousel/features/drag.ts
2
+ import { CAROUSEL_EVENTS, CAROUSEL_LAYOUTS, CAROUSEL_SCROLL_BEHAVIORS } from '../constants';
3
+ import { CarouselConfig } from '../types';
4
+
5
+ /**
6
+ * Adds drag functionality to the carousel component with Material Design 3 scroll behavior support
7
+ *
8
+ * @param {CarouselConfig} config - Carousel configuration
9
+ * @returns {Function} Higher-order function that adds drag navigation feature
10
+ */
11
+ export const withDrag = (config: CarouselConfig) => (component) => {
12
+ let startX: number;
13
+ let startScrollLeft: number;
14
+ let currentX: number;
15
+ let isDragging = false;
16
+ let velocity = 0;
17
+ let lastTimestamp = 0;
18
+ let animationFrame: number;
19
+
20
+ // Scroll behavior from config
21
+ const scrollBehavior = config.scrollBehavior || 'default';
22
+
23
+ // Handle pointer events
24
+ const handlePointerDown = (event: PointerEvent) => {
25
+ // Only handle primary button (usually left click)
26
+ if (event.button !== 0) return;
27
+
28
+ // Cancel any ongoing scroll animations
29
+ cancelAnimationFrame(animationFrame);
30
+
31
+ startX = event.clientX;
32
+ startScrollLeft = component.slidesContainer.scrollLeft;
33
+ currentX = startX;
34
+ isDragging = true;
35
+ lastTimestamp = Date.now();
36
+ velocity = 0;
37
+
38
+ // Set dragging state via data attribute
39
+ component.slidesContainer.dataset.touchAction = 'none';
40
+ component.element.dataset.dragging = 'true';
41
+
42
+ // Prevent default behaviors that might cause resistance
43
+ component.slidesContainer.style.scrollBehavior = 'auto';
44
+
45
+ // Capture pointer to receive events outside element
46
+ component.element.setPointerCapture(event.pointerId);
47
+
48
+ // Stop click events during drag
49
+ event.preventDefault();
50
+ };
51
+
52
+ const handlePointerMove = (event: PointerEvent) => {
53
+ if (!isDragging) return;
54
+
55
+ // Calculate how far the pointer has moved
56
+ const dx = event.clientX - startX;
57
+
58
+ // Directly set scroll position without any easing or calculations
59
+ component.slidesContainer.scrollLeft = startScrollLeft - dx;
60
+
61
+ // Calculate velocity for momentum scrolling
62
+ const now = Date.now();
63
+ const dt = now - lastTimestamp;
64
+
65
+ if (dt > 0) {
66
+ const dx = event.clientX - currentX;
67
+ velocity = dx / dt; // Pixels per millisecond
68
+ }
69
+
70
+ currentX = event.clientX;
71
+ lastTimestamp = now;
72
+
73
+ event.preventDefault();
74
+ };
75
+
76
+ /**
77
+ * Finds the nearest snap position based on current scroll position
78
+ * Used for snap scrolling behavior
79
+ */
80
+ const findSnapPosition = () => {
81
+ const slideElements = component.slides.getElements();
82
+ const containerWidth = component.slidesContainer.clientWidth;
83
+ const scrollLeft = component.slidesContainer.scrollLeft;
84
+
85
+ // For center-aligned hero layout
86
+ const isCentered = config.layout === 'hero' && config.centered;
87
+
88
+ let closestPosition = 0;
89
+ let closestDistance = Infinity;
90
+
91
+ // Find the closest slide position for snapping
92
+ slideElements.forEach((slide) => {
93
+ let targetPosition;
94
+
95
+ if (isCentered) {
96
+ // For centered layouts, snap to center-aligned position
97
+ const slideCenter = slide.offsetLeft + (slide.offsetWidth / 2);
98
+ targetPosition = slideCenter - (containerWidth / 2);
99
+ } else {
100
+ // For standard layouts, snap to start-aligned position
101
+ targetPosition = slide.offsetLeft;
102
+ }
103
+
104
+ const distance = Math.abs(targetPosition - scrollLeft);
105
+
106
+ if (distance < closestDistance) {
107
+ closestDistance = distance;
108
+ closestPosition = targetPosition;
109
+ }
110
+ });
111
+
112
+ return closestPosition;
113
+ };
114
+
115
+ /**
116
+ * Animates scroll with momentum and snapping
117
+ * @param {number} startPosition - Starting scroll position
118
+ * @param {number} endPosition - Target scroll position
119
+ * @param {number} startVelocity - Initial velocity in pixels/ms
120
+ */
121
+ const animateScroll = (startPosition: number, endPosition: number, startVelocity: number) => {
122
+ const startTime = Date.now();
123
+ const distance = endPosition - startPosition;
124
+
125
+ // Base animation duration on distance and velocity
126
+ // Faster flicks = shorter duration
127
+ let duration = 500; // Base duration in ms
128
+
129
+ if (Math.abs(startVelocity) > 0.5) {
130
+ // For faster flicks, reduce duration
131
+ duration = Math.min(duration, 300);
132
+ }
133
+
134
+ const animateStep = () => {
135
+ const elapsed = Date.now() - startTime;
136
+ let progress = Math.min(elapsed / duration, 1);
137
+
138
+ // Ease out cubic function for smooth deceleration
139
+ progress = 1 - Math.pow(1 - progress, 3);
140
+
141
+ const currentPosition = startPosition + (distance * progress);
142
+ component.slidesContainer.scrollLeft = currentPosition;
143
+
144
+ if (progress < 1) {
145
+ animationFrame = requestAnimationFrame(animateStep);
146
+ }
147
+ };
148
+
149
+ animationFrame = requestAnimationFrame(animateStep);
150
+ };
151
+
152
+ const handlePointerUp = (event: PointerEvent) => {
153
+ if (!isDragging) return;
154
+
155
+ isDragging = false;
156
+ delete component.element.dataset.dragging;
157
+
158
+ // Release pointer capture
159
+ component.element.releasePointerCapture(event.pointerId);
160
+
161
+ // Handle scroll behavior based on config
162
+ if (scrollBehavior === 'snap') {
163
+ // Find nearest snap position
164
+ const snapPosition = findSnapPosition();
165
+
166
+ // Use velocity for a more natural feel
167
+ animateScroll(component.slidesContainer.scrollLeft, snapPosition, velocity);
168
+ } else if (Math.abs(velocity) > 0.5) {
169
+ // For standard scrolling, add momentum effect for fast flicks
170
+ const momentumDistance = velocity * 100; // Arbitrary multiplier for momentum
171
+ const targetPosition = component.slidesContainer.scrollLeft - momentumDistance;
172
+
173
+ animateScroll(component.slidesContainer.scrollLeft, targetPosition, velocity);
174
+ }
175
+
176
+ // Reset velocity
177
+ velocity = 0;
178
+
179
+ // Restore scrolling behavior
180
+ setTimeout(() => {
181
+ component.slidesContainer.style.scrollBehavior = '';
182
+ }, 0);
183
+ };
184
+
185
+ const handlePointerCancel = (event: PointerEvent) => {
186
+ if (isDragging) {
187
+ isDragging = false;
188
+ delete component.element.dataset.dragging;
189
+ component.element.releasePointerCapture(event.pointerId);
190
+
191
+ // Restore scrolling behavior
192
+ component.slidesContainer.style.scrollBehavior = '';
193
+ }
194
+ };
195
+
196
+ /**
197
+ * Handle wheel events for horizontal scrolling
198
+ * @param {WheelEvent} event - Wheel event
199
+ */
200
+ const handleWheel = (event: WheelEvent) => {
201
+ // For full-screen layout, allow vertical scrolling
202
+ if (config.layout === 'full-screen') {
203
+ // Let the default scroll behavior happen
204
+ return;
205
+ }
206
+
207
+ // For other layouts, transform vertical scrolling to horizontal
208
+ event.preventDefault();
209
+
210
+ // Determine the scroll delta
211
+ const delta = event.deltaY || event.deltaX;
212
+
213
+ // Calculate scroll increment based on wheel delta
214
+ const scrollIncrement = delta * 0.5;
215
+
216
+ // For snap scrolling, we want to snap after wheel events
217
+ if (scrollBehavior === 'snap') {
218
+ // First scroll normally
219
+ component.slidesContainer.scrollLeft += scrollIncrement;
220
+
221
+ // Clear previous snap timeout
222
+ clearTimeout(component['wheelSnapTimeout']);
223
+
224
+ // Set timeout to snap after wheel motion stops
225
+ component['wheelSnapTimeout'] = setTimeout(() => {
226
+ const snapPosition = findSnapPosition();
227
+
228
+ // Animate to snap position
229
+ animateScroll(component.slidesContainer.scrollLeft, snapPosition, 0);
230
+ }, 150); // Short delay to detect end of wheel motion
231
+ } else {
232
+ // Standard scrolling - just scroll directly
233
+ component.slidesContainer.scrollLeft += scrollIncrement;
234
+ }
235
+ };
236
+
237
+ /**
238
+ * Apply parallax effect to slides during scroll
239
+ * Used for multi-browse layout with different sized items
240
+ */
241
+ const applyParallaxEffect = () => {
242
+ // Only apply for multi-browse layout
243
+ if (config.layout !== 'multi-browse' || !component.element.dataset.enableParallax) {
244
+ return;
245
+ }
246
+
247
+ const slides = component.slides.getElements();
248
+ const containerWidth = component.slidesContainer.clientWidth;
249
+ const scrollLeft = component.slidesContainer.scrollLeft;
250
+
251
+ slides.forEach((slide) => {
252
+ const slideLeft = slide.offsetLeft;
253
+ const slideWidth = slide.offsetWidth;
254
+
255
+ // Calculate how centered the slide is in the container
256
+ const slideCenterX = slideLeft + (slideWidth / 2);
257
+ const containerCenterX = scrollLeft + (containerWidth / 2);
258
+ const distanceFromCenter = slideCenterX - containerCenterX;
259
+
260
+ // Normalize to a -1 to 1 range
261
+ const normalizedDistance = Math.max(-1, Math.min(1, distanceFromCenter / (containerWidth / 2)));
262
+
263
+ // Apply a subtle parallax effect
264
+ // Move images in the opposite direction of the scroll by a small amount
265
+ const slideImage = slide.querySelector(`.${component.getClass('carousel')}-slide-image img`);
266
+ if (slideImage) {
267
+ (slideImage as HTMLElement).style.transform = `translateX(${normalizedDistance * -5}%)`;
268
+ }
269
+ });
270
+ };
271
+
272
+ // Handle scroll events for parallax and active slide detection
273
+ const handleScroll = () => {
274
+ // Apply parallax effect if enabled
275
+ applyParallaxEffect();
276
+ };
277
+
278
+ // Add parallax data attribute if it's multi-browse
279
+ if (config.layout === 'multi-browse') {
280
+ component.element.dataset.enableParallax = 'true';
281
+ }
282
+
283
+ // Add data attribute for draggable UI
284
+ component.element.dataset.swipe = 'true';
285
+
286
+ // Add pointer event listeners
287
+ component.slidesContainer.addEventListener('pointerdown', handlePointerDown);
288
+ window.addEventListener('pointermove', handlePointerMove);
289
+ window.addEventListener('pointerup', handlePointerUp);
290
+ window.addEventListener('pointercancel', handlePointerCancel);
291
+
292
+ // Add wheel event listener with appropriate passive setting
293
+ component.slidesContainer.addEventListener('wheel', handleWheel, { passive: false });
294
+
295
+ // Add scroll event listener for parallax effect
296
+ component.slidesContainer.addEventListener('scroll', handleScroll, { passive: true });
297
+
298
+ // Add keyboard navigation
299
+ const handleKeyDown = (event: KeyboardEvent) => {
300
+ // Only handle keyboard navigation when the carousel has focus
301
+ if (component.element !== document.activeElement) {
302
+ return;
303
+ }
304
+
305
+ switch (event.key) {
306
+ case 'ArrowLeft':
307
+ if (component.prev) {
308
+ component.prev();
309
+ } else {
310
+ // Fallback if prev method not available
311
+ component.slidesContainer.scrollBy({
312
+ left: -200,
313
+ behavior: scrollBehavior === 'snap' ? 'smooth' : 'auto'
314
+ });
315
+ }
316
+ event.preventDefault();
317
+ break;
318
+
319
+ case 'ArrowRight':
320
+ if (component.next) {
321
+ component.next();
322
+ } else {
323
+ // Fallback if next method not available
324
+ component.slidesContainer.scrollBy({
325
+ left: 200,
326
+ behavior: scrollBehavior === 'snap' ? 'smooth' : 'auto'
327
+ });
328
+ }
329
+ event.preventDefault();
330
+ break;
331
+
332
+ case 'Home':
333
+ // Go to first slide
334
+ if (component.goTo) {
335
+ component.goTo(0);
336
+ }
337
+ event.preventDefault();
338
+ break;
339
+
340
+ case 'End':
341
+ // Go to last slide
342
+ if (component.goTo && component.slides.getCount) {
343
+ component.goTo(component.slides.getCount() - 1);
344
+ }
345
+ event.preventDefault();
346
+ break;
347
+ }
348
+ };
349
+
350
+ // Make the carousel focusable for keyboard navigation
351
+ component.element.setAttribute('tabindex', '0');
352
+ component.element.addEventListener('keydown', handleKeyDown);
353
+
354
+ // Add aria attributes for accessibility
355
+ component.element.setAttribute('aria-label', 'Carousel');
356
+
357
+ // Return the component with additional cleanup
358
+ return {
359
+ ...component,
360
+
361
+ // Add drag cleanup to lifecycle
362
+ lifecycle: {
363
+ ...component.lifecycle,
364
+ destroy: () => {
365
+ // Cancel any animations
366
+ cancelAnimationFrame(animationFrame);
367
+
368
+ // Remove pointer event listeners
369
+ component.slidesContainer.removeEventListener('pointerdown', handlePointerDown);
370
+ window.removeEventListener('pointermove', handlePointerMove);
371
+ window.removeEventListener('pointerup', handlePointerUp);
372
+ window.removeEventListener('pointercancel', handlePointerCancel);
373
+
374
+ // Remove wheel and scroll event listeners
375
+ component.slidesContainer.removeEventListener('wheel', handleWheel);
376
+ component.slidesContainer.removeEventListener('scroll', handleScroll);
377
+
378
+ // Remove keyboard event listener
379
+ component.element.removeEventListener('keydown', handleKeyDown);
380
+
381
+ // Call original destroy if it exists
382
+ if (component.lifecycle && component.lifecycle.destroy) {
383
+ component.lifecycle.destroy();
384
+ }
385
+ }
386
+ }
387
+ };
388
+ };
@@ -0,0 +1,8 @@
1
+ // src/components/carousel/features/index.ts
2
+
3
+ /**
4
+ * Entry point for Carousel component features
5
+ */
6
+
7
+ export { withSlides } from './slides';
8
+ export { withDrag } from './drag';