mtrl-addons 0.5.6 → 0.7.0
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.
- package/README.md +567 -7
- package/dist/components/form/features/fields.d.ts +16 -9
- package/dist/components/form/features/index.d.ts +1 -1
- package/dist/components/index.d.ts +7 -3
- package/dist/components/index.js +9345 -0
- package/dist/components/index.mjs +9313 -0
- package/dist/components/vlist/types.d.ts +7 -0
- package/dist/core/gestures/index.js +508 -0
- package/dist/core/gestures/index.mjs +476 -0
- package/dist/core/layout/index.js +752 -0
- package/dist/core/layout/index.mjs +720 -0
- package/dist/core/viewport/index.js +3715 -0
- package/dist/core/viewport/index.mjs +3683 -0
- package/dist/index.js +890 -906
- package/dist/index.mjs +890 -906
- package/package.json +34 -3
package/README.md
CHANGED
|
@@ -1,32 +1,592 @@
|
|
|
1
1
|
# mtrl-addons
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Extended components and utilities for the [mtrl](https://github.com/nicholasgriffintn/mtrl) Material Design 3 component library
|
|
4
|
+
|
|
5
|
+
mtrl-addons provides high-performance, specialized components and core systems that extend mtrl's capabilities for building modern web applications. Built with the same functional composition philosophy and zero external dependencies (except mtrl as a peer dependency).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🚀 **Virtual Scrolling** - Handle millions of items with smooth 60fps scrolling
|
|
10
|
+
- 📝 **Form Builder** - Declarative form creation with validation and state management
|
|
11
|
+
- 🎨 **Color Picker** - Full-featured HSV picker with swatches, pipette, and variants
|
|
12
|
+
- 📐 **Layout System** - Flexible array-based layout schemas with JSX support
|
|
13
|
+
- 👆 **Gesture Recognition** - Touch and mouse gesture detection (tap, swipe, pinch, etc.)
|
|
14
|
+
- 🎯 **Viewport System** - Composable virtual scrolling foundation
|
|
15
|
+
- 🌳 **Tree-Shaking Optimized** - Import only what you need
|
|
4
16
|
|
|
5
17
|
## Installation
|
|
6
18
|
|
|
7
19
|
```bash
|
|
8
|
-
npm
|
|
20
|
+
# npm
|
|
21
|
+
npm install mtrl-addons mtrl
|
|
22
|
+
|
|
23
|
+
# yarn
|
|
24
|
+
yarn add mtrl-addons mtrl
|
|
25
|
+
|
|
26
|
+
# bun
|
|
27
|
+
bun add mtrl-addons mtrl
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
import { createVList, createForm, createColorPicker } from 'mtrl-addons';
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Tree-Shaking Optimized Imports
|
|
37
|
+
|
|
38
|
+
mtrl-addons is optimized for tree-shaking. Constants are exported separately from component creators to minimize bundle size.
|
|
39
|
+
|
|
40
|
+
### Import Patterns
|
|
41
|
+
|
|
42
|
+
| Import Type | Path |
|
|
43
|
+
|-------------|------|
|
|
44
|
+
| Component creators | `import { createColorPicker } from 'mtrl-addons'` |
|
|
45
|
+
| ColorPicker constants | `import { COLORPICKER_EVENTS } from 'mtrl-addons/components/colorpicker/constants'` |
|
|
46
|
+
| Form constants | `import { FORM_EVENTS, DATA_STATE } from 'mtrl-addons/components/form/constants'` |
|
|
47
|
+
| VList constants | `import { VLIST_CLASSES } from 'mtrl-addons/components/vlist/constants'` |
|
|
48
|
+
| Color utilities | `import { hsvToRgb, rgbToHex } from 'mtrl-addons'` |
|
|
49
|
+
| Layout system | `import { createLayout } from 'mtrl-addons/layout'` |
|
|
50
|
+
| Viewport system | `import { createViewport } from 'mtrl-addons/viewport'` |
|
|
51
|
+
| Gestures | `import { createGestureManager } from 'mtrl-addons/gestures'` |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Components
|
|
56
|
+
|
|
57
|
+
### VList (Virtual List)
|
|
58
|
+
|
|
59
|
+
High-performance virtual scrolling list for large datasets. Renders only visible items while maintaining smooth scrolling with millions of rows.
|
|
60
|
+
|
|
61
|
+
#### Features
|
|
62
|
+
- 📊 Handles 100,000+ items efficiently
|
|
63
|
+
- 🔍 Built-in search with debouncing
|
|
64
|
+
- 🎛️ Filter panel integration
|
|
65
|
+
- ⌨️ Keyboard navigation
|
|
66
|
+
- 📍 Scroll position restoration
|
|
67
|
+
- 🎯 Item selection (single/multi)
|
|
68
|
+
- 📈 Stats tracking (render time, visible items)
|
|
69
|
+
- ⚡ Velocity-based scroll optimization
|
|
70
|
+
|
|
71
|
+
#### Basic Usage
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import { createVList } from 'mtrl-addons';
|
|
75
|
+
|
|
76
|
+
const vlist = createVList({
|
|
77
|
+
container: '#my-list',
|
|
78
|
+
template: (item) => ({
|
|
79
|
+
class: 'list-item',
|
|
80
|
+
children: [
|
|
81
|
+
{ tag: 'span', class: 'name', text: item.name },
|
|
82
|
+
{ tag: 'span', class: 'email', text: item.email }
|
|
83
|
+
]
|
|
84
|
+
}),
|
|
85
|
+
collection: {
|
|
86
|
+
adapter: {
|
|
87
|
+
read: async ({ page, limit }) => {
|
|
88
|
+
const response = await fetch(`/api/users?page=${page}&limit=${limit}`);
|
|
89
|
+
return response.json();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### With Layout, Search & Filters
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
import { createVList } from 'mtrl-addons';
|
|
100
|
+
import { createIconButton, createSearch, createSelect } from 'mtrl';
|
|
101
|
+
|
|
102
|
+
const vlist = createVList({
|
|
103
|
+
container: '#users-list',
|
|
104
|
+
class: 'users',
|
|
105
|
+
|
|
106
|
+
// Layout schema - declarative UI structure
|
|
107
|
+
layout: [
|
|
108
|
+
['head', { class: 'list-head' },
|
|
109
|
+
[createIconButton, 'searchBtn', { icon: iconSearch, toggle: true }],
|
|
110
|
+
[createIconButton, 'filterBtn', { icon: iconFilter, toggle: true }]
|
|
111
|
+
],
|
|
112
|
+
['filter-panel', { class: 'filter-panel' },
|
|
113
|
+
[createSelect, 'country', { label: 'Country', options: countries }],
|
|
114
|
+
[createSelect, 'role', { label: 'Role', options: roles }]
|
|
115
|
+
],
|
|
116
|
+
[createSearch, 'searchBar', { placeholder: 'Search users...' }],
|
|
117
|
+
['viewport'], // Virtual scrolling area
|
|
118
|
+
['foot', { class: 'list-foot' },
|
|
119
|
+
['stats', { text: 'Loading...' }]
|
|
120
|
+
]
|
|
121
|
+
],
|
|
122
|
+
|
|
123
|
+
// Search configuration
|
|
124
|
+
search: {
|
|
125
|
+
toggleButton: 'searchBtn',
|
|
126
|
+
searchBar: 'searchBar',
|
|
127
|
+
debounce: 300
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// Filter configuration
|
|
131
|
+
filter: {
|
|
132
|
+
toggleButton: 'filterBtn',
|
|
133
|
+
panel: 'filter-panel',
|
|
134
|
+
controls: {
|
|
135
|
+
country: 'country',
|
|
136
|
+
role: 'role'
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// Stats display
|
|
141
|
+
stats: {
|
|
142
|
+
element: 'stats',
|
|
143
|
+
format: ({ total, rendered }) => `${rendered} of ${total} users`
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
template: userTemplate,
|
|
147
|
+
collection: {
|
|
148
|
+
adapter: {
|
|
149
|
+
read: async ({ page, limit, search, filters }) => {
|
|
150
|
+
// search and filters are automatically populated
|
|
151
|
+
return fetchUsers({ page, limit, search, ...filters });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Events
|
|
158
|
+
vlist.on('search:change', ({ query }) => console.log('Search:', query));
|
|
159
|
+
vlist.on('filter:change', ({ filters }) => console.log('Filters:', filters));
|
|
160
|
+
vlist.on('item:select', ({ item }) => console.log('Selected:', item));
|
|
161
|
+
|
|
162
|
+
// API
|
|
163
|
+
vlist.search('john');
|
|
164
|
+
vlist.setFilter('country', 'US');
|
|
165
|
+
vlist.scrollToIndex(50);
|
|
166
|
+
vlist.refresh();
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### Form
|
|
172
|
+
|
|
173
|
+
Functional form builder with declarative field configuration, validation, and state management.
|
|
174
|
+
|
|
175
|
+
#### Features
|
|
176
|
+
- 📋 Declarative field definitions
|
|
177
|
+
- ✅ Built-in validation rules
|
|
178
|
+
- 🔄 Dirty/pristine state tracking
|
|
179
|
+
- 🛡️ Unsaved changes protection
|
|
180
|
+
- 📤 Submit handling with loading states
|
|
181
|
+
- 🎯 Field-level error handling
|
|
182
|
+
|
|
183
|
+
#### Basic Usage
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
import { createForm } from 'mtrl-addons';
|
|
187
|
+
import { FORM_EVENTS, DATA_STATE } from 'mtrl-addons/components/form/constants';
|
|
188
|
+
|
|
189
|
+
const form = createForm({
|
|
190
|
+
fields: [
|
|
191
|
+
{ name: 'email', type: 'email', label: 'Email', required: true },
|
|
192
|
+
{ name: 'password', type: 'password', label: 'Password', required: true },
|
|
193
|
+
{ name: 'remember', type: 'checkbox', label: 'Remember me' }
|
|
194
|
+
],
|
|
195
|
+
|
|
196
|
+
// Protect unsaved changes
|
|
197
|
+
protectChanges: {
|
|
198
|
+
beforeUnload: true, // Warn on page close
|
|
199
|
+
onDataOverwrite: true // Emit event when setData() called with changes
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
onSubmit: async (data) => {
|
|
203
|
+
await api.login(data);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Mount to container
|
|
208
|
+
document.getElementById('login-form').appendChild(form.element);
|
|
209
|
+
|
|
210
|
+
// Events
|
|
211
|
+
form.on(FORM_EVENTS.CHANGE, ({ field, value }) => {
|
|
212
|
+
console.log(`${field} changed to:`, value);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
form.on(FORM_EVENTS.STATE_CHANGE, ({ state }) => {
|
|
216
|
+
if (state === DATA_STATE.DIRTY) {
|
|
217
|
+
console.log('Form has unsaved changes');
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
form.on(FORM_EVENTS.SUBMIT, (data) => {
|
|
222
|
+
console.log('Form submitted:', data);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
form.on(FORM_EVENTS.DATA_CONFLICT, ({ currentData, newData, cancel, proceed }) => {
|
|
226
|
+
if (confirm('Discard unsaved changes?')) {
|
|
227
|
+
proceed();
|
|
228
|
+
} else {
|
|
229
|
+
cancel();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// API
|
|
234
|
+
form.setData({ email: 'user@example.com' });
|
|
235
|
+
const data = form.getData();
|
|
236
|
+
const isValid = form.validate();
|
|
237
|
+
form.reset();
|
|
238
|
+
form.disable();
|
|
239
|
+
form.enable();
|
|
9
240
|
```
|
|
10
241
|
|
|
11
|
-
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### ColorPicker
|
|
245
|
+
|
|
246
|
+
Full-featured color picker with HSV color area, hue slider, swatches, and multiple display variants.
|
|
247
|
+
|
|
248
|
+
#### Features
|
|
249
|
+
- 🎨 HSV color area with saturation/brightness
|
|
250
|
+
- 📊 Hue slider
|
|
251
|
+
- 🔲 Opacity/alpha slider (optional)
|
|
252
|
+
- 💎 Color swatches with add/remove
|
|
253
|
+
- 💉 Pipette/eyedropper (native API or canvas sampling)
|
|
254
|
+
- 📝 Hex input field
|
|
255
|
+
- 🖼️ Multiple variants: inline, dropdown, dialog
|
|
256
|
+
- 📱 Compact density mode
|
|
257
|
+
|
|
258
|
+
#### Basic Usage
|
|
12
259
|
|
|
13
260
|
```javascript
|
|
14
|
-
import {
|
|
261
|
+
import { createColorPicker } from 'mtrl-addons';
|
|
262
|
+
import { COLORPICKER_EVENTS, COLORPICKER_VARIANTS } from 'mtrl-addons/components/colorpicker/constants';
|
|
263
|
+
|
|
264
|
+
// Inline picker (always visible)
|
|
265
|
+
const picker = createColorPicker({
|
|
266
|
+
value: '#6200ee',
|
|
267
|
+
showSwatches: true,
|
|
268
|
+
swatches: ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff']
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
document.getElementById('color-picker').appendChild(picker.element);
|
|
272
|
+
|
|
273
|
+
// Events
|
|
274
|
+
picker.on(COLORPICKER_EVENTS.CHANGE, ({ value }) => {
|
|
275
|
+
console.log('Color selected:', value);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
picker.on(COLORPICKER_EVENTS.INPUT, ({ value }) => {
|
|
279
|
+
// Live preview during drag
|
|
280
|
+
document.body.style.backgroundColor = value;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// API
|
|
284
|
+
picker.setValue('#ff5722');
|
|
285
|
+
const hex = picker.getValue(); // '#ff5722'
|
|
286
|
+
const hsv = picker.getHSV(); // { h: 14, s: 86, v: 100 }
|
|
287
|
+
const rgb = picker.getRGB(); // { r: 255, g: 87, b: 34 }
|
|
288
|
+
|
|
289
|
+
picker.addSwatch('#9c27b0', 'Purple');
|
|
290
|
+
picker.setOpacity(0.5);
|
|
15
291
|
```
|
|
16
292
|
|
|
293
|
+
#### Dropdown Variant
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
const trigger = document.getElementById('color-button');
|
|
297
|
+
|
|
298
|
+
const picker = createColorPicker({
|
|
299
|
+
value: '#6200ee',
|
|
300
|
+
variant: COLORPICKER_VARIANTS.DROPDOWN,
|
|
301
|
+
trigger: trigger,
|
|
302
|
+
closeOnSelect: true
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Toggle programmatically
|
|
306
|
+
picker.open();
|
|
307
|
+
picker.close();
|
|
308
|
+
picker.toggle();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### With Pipette (Color Sampling)
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
const picker = createColorPicker({
|
|
315
|
+
value: '#ffffff',
|
|
316
|
+
showPipette: true,
|
|
317
|
+
// Optional: provide image for canvas-based sampling
|
|
318
|
+
imageSource: document.getElementById('my-image'),
|
|
319
|
+
|
|
320
|
+
onPipetteStart: () => console.log('Sampling started'),
|
|
321
|
+
onPipetteEnd: (color) => console.log('Picked:', color)
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Trigger programmatically
|
|
325
|
+
const pickedColor = await picker.pickColor();
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Core Systems
|
|
331
|
+
|
|
332
|
+
### Layout System
|
|
333
|
+
|
|
334
|
+
Flexible array-based layout schemas for declarative UI construction. Supports mtrl components, HTML elements, and nested structures.
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
import { createLayout } from 'mtrl-addons/layout';
|
|
338
|
+
import { createButton, createTextField } from 'mtrl';
|
|
339
|
+
|
|
340
|
+
// Array-based schema
|
|
341
|
+
const layout = createLayout([
|
|
342
|
+
'div', { class: 'form-container' },
|
|
343
|
+
[
|
|
344
|
+
['header', { class: 'form-header' },
|
|
345
|
+
['h2', { text: 'Contact Form' }]
|
|
346
|
+
],
|
|
347
|
+
['main', { class: 'form-body' },
|
|
348
|
+
[createTextField, 'name', { label: 'Name' }],
|
|
349
|
+
[createTextField, 'email', { label: 'Email', type: 'email' }],
|
|
350
|
+
[createTextField, 'message', { label: 'Message', multiline: true }]
|
|
351
|
+
],
|
|
352
|
+
['footer', { class: 'form-footer' },
|
|
353
|
+
[createButton, 'submit', { text: 'Send', variant: 'filled' }],
|
|
354
|
+
[createButton, 'cancel', { text: 'Cancel', variant: 'outlined' }]
|
|
355
|
+
]
|
|
356
|
+
]
|
|
357
|
+
]);
|
|
358
|
+
|
|
359
|
+
// Access named components
|
|
360
|
+
layout.name.setValue('John Doe');
|
|
361
|
+
layout.submit.on('click', handleSubmit);
|
|
362
|
+
|
|
363
|
+
// Append to DOM
|
|
364
|
+
document.body.appendChild(layout.element);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### Convenience Functions
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
import { layout, row, stack, grid } from 'mtrl-addons/layout';
|
|
371
|
+
|
|
372
|
+
// Vertical stack
|
|
373
|
+
const stackLayout = stack({ gap: '1rem' });
|
|
374
|
+
|
|
375
|
+
// Horizontal row (mobile-stacks automatically)
|
|
376
|
+
const rowLayout = row({ gap: '1rem', mobileStack: true });
|
|
377
|
+
|
|
378
|
+
// Responsive grid
|
|
379
|
+
const gridLayout = grid('auto-fit', { gap: '1rem' });
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
### Viewport System
|
|
385
|
+
|
|
386
|
+
Low-level composable virtual scrolling foundation. Used internally by VList but available for custom implementations.
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
import { createViewport } from 'mtrl-addons/viewport';
|
|
390
|
+
|
|
391
|
+
const viewport = createViewport({
|
|
392
|
+
container: document.getElementById('scroll-container'),
|
|
393
|
+
itemCount: 100000,
|
|
394
|
+
estimatedItemSize: 48,
|
|
395
|
+
overscan: 5, // Extra items to render outside viewport
|
|
396
|
+
|
|
397
|
+
renderItem: (index) => {
|
|
398
|
+
const div = document.createElement('div');
|
|
399
|
+
div.textContent = `Item ${index}`;
|
|
400
|
+
return div;
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
onRangeChange: ({ start, end }) => {
|
|
404
|
+
console.log(`Rendering items ${start} to ${end}`);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// API
|
|
409
|
+
viewport.scrollToIndex(500);
|
|
410
|
+
viewport.refresh();
|
|
411
|
+
viewport.destroy();
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
#### Feature Composition
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
import {
|
|
418
|
+
withBase,
|
|
419
|
+
withVirtual,
|
|
420
|
+
withScrolling,
|
|
421
|
+
withRendering
|
|
422
|
+
} from 'mtrl-addons/viewport';
|
|
423
|
+
|
|
424
|
+
// Build custom viewport with specific features
|
|
425
|
+
const customViewport = pipe(
|
|
426
|
+
withBase,
|
|
427
|
+
withVirtual,
|
|
428
|
+
withScrolling,
|
|
429
|
+
withRendering
|
|
430
|
+
)(config);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
### Gesture System
|
|
436
|
+
|
|
437
|
+
Touch and mouse gesture recognition with support for tap, swipe, long-press, pinch, rotate, and pan.
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
import { createGestureManager } from 'mtrl-addons/gestures';
|
|
441
|
+
|
|
442
|
+
const gestures = createGestureManager(element, {
|
|
443
|
+
// Tap detection
|
|
444
|
+
onTap: ({ x, y, target }) => {
|
|
445
|
+
console.log('Tapped at', x, y);
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
// Swipe detection
|
|
449
|
+
onSwipe: ({ direction, velocity, distance }) => {
|
|
450
|
+
console.log(`Swiped ${direction} with velocity ${velocity}`);
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
// Long press
|
|
454
|
+
onLongPress: ({ x, y, duration }) => {
|
|
455
|
+
console.log('Long pressed for', duration, 'ms');
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
// Pinch (zoom)
|
|
459
|
+
onPinch: ({ scale, center }) => {
|
|
460
|
+
element.style.transform = `scale(${scale})`;
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
// Rotation
|
|
464
|
+
onRotate: ({ angle, center }) => {
|
|
465
|
+
element.style.transform = `rotate(${angle}deg)`;
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
// Pan (drag)
|
|
469
|
+
onPan: ({ deltaX, deltaY, state }) => {
|
|
470
|
+
if (state === 'move') {
|
|
471
|
+
element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Configuration
|
|
477
|
+
const gestures = createGestureManager(element, {
|
|
478
|
+
tap: { maxDuration: 300 },
|
|
479
|
+
swipe: { minDistance: 50, minVelocity: 0.3 },
|
|
480
|
+
longPress: { duration: 500 },
|
|
481
|
+
pinch: { minScale: 0.5, maxScale: 3 }
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Cleanup
|
|
485
|
+
gestures.destroy();
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### Individual Gesture Detectors
|
|
489
|
+
|
|
490
|
+
```javascript
|
|
491
|
+
import { detectTap, detectSwipe, detectPinch } from 'mtrl-addons/gestures';
|
|
492
|
+
|
|
493
|
+
// Use specific detectors for lighter bundles
|
|
494
|
+
const tapDetector = detectTap(element, { onTap: handleTap });
|
|
495
|
+
const swipeDetector = detectSwipe(element, { onSwipe: handleSwipe });
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Color Utilities
|
|
501
|
+
|
|
502
|
+
Pure functions for color conversion (tree-shakeable, included in main bundle).
|
|
503
|
+
|
|
504
|
+
```javascript
|
|
505
|
+
import {
|
|
506
|
+
hsvToRgb,
|
|
507
|
+
rgbToHsv,
|
|
508
|
+
hsvToHex,
|
|
509
|
+
hexToHsv,
|
|
510
|
+
rgbToHex,
|
|
511
|
+
hexToRgb,
|
|
512
|
+
isValidHex,
|
|
513
|
+
normalizeHex,
|
|
514
|
+
getContrastColor
|
|
515
|
+
} from 'mtrl-addons';
|
|
516
|
+
|
|
517
|
+
// HSV ↔ RGB
|
|
518
|
+
const rgb = hsvToRgb({ h: 200, s: 80, v: 90 }); // { r: 46, g: 184, b: 230 }
|
|
519
|
+
const hsv = rgbToHsv({ r: 255, g: 128, b: 0 }); // { h: 30, s: 100, v: 100 }
|
|
520
|
+
|
|
521
|
+
// Hex conversions
|
|
522
|
+
const hex = rgbToHex({ r: 255, g: 0, b: 128 }); // '#ff0080'
|
|
523
|
+
const rgb2 = hexToRgb('#ff0080'); // { r: 255, g: 0, b: 128 }
|
|
524
|
+
|
|
525
|
+
// Validation & normalization
|
|
526
|
+
isValidHex('#ff0080'); // true
|
|
527
|
+
isValidHex('ff0080'); // true
|
|
528
|
+
normalizeHex('f00'); // '#ff0000'
|
|
529
|
+
normalizeHex('#F00'); // '#ff0000'
|
|
530
|
+
|
|
531
|
+
// Contrast color (for text on colored backgrounds)
|
|
532
|
+
getContrastColor('#ffffff'); // '#000000'
|
|
533
|
+
getContrastColor('#000000'); // '#ffffff'
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
17
538
|
## Development
|
|
18
539
|
|
|
19
540
|
```bash
|
|
20
541
|
# Install dependencies
|
|
21
542
|
bun install
|
|
22
543
|
|
|
23
|
-
# Build
|
|
544
|
+
# Build package (for distribution)
|
|
24
545
|
bun run build
|
|
25
546
|
|
|
26
|
-
#
|
|
547
|
+
# Build for mtrl-app integration
|
|
548
|
+
bun run build:app
|
|
549
|
+
|
|
550
|
+
# Run tests
|
|
27
551
|
bun test
|
|
552
|
+
|
|
553
|
+
# Watch mode
|
|
554
|
+
bun run dev
|
|
555
|
+
|
|
556
|
+
# Link mtrl for local development
|
|
557
|
+
bun run link:mtrl
|
|
28
558
|
```
|
|
29
559
|
|
|
560
|
+
## Browser Support
|
|
561
|
+
|
|
562
|
+
mtrl-addons supports modern browsers:
|
|
563
|
+
|
|
564
|
+
- Chrome (latest)
|
|
565
|
+
- Firefox (latest)
|
|
566
|
+
- Safari (latest)
|
|
567
|
+
- Edge (latest)
|
|
568
|
+
|
|
569
|
+
## Peer Dependencies
|
|
570
|
+
|
|
571
|
+
- `mtrl` ^0.6.2 - Core Material Design 3 component library
|
|
572
|
+
|
|
573
|
+
## Related Packages
|
|
574
|
+
|
|
575
|
+
- [mtrl](https://github.com/floor/mtrl) - Core Material Design 3 component library
|
|
576
|
+
- [mtrl-app](https://github.com/floor/mtrl-app) - Documentation and showcase application
|
|
577
|
+
|
|
30
578
|
## License
|
|
31
579
|
|
|
32
|
-
MIT
|
|
580
|
+
MIT
|
|
581
|
+
|
|
582
|
+
## Contributing
|
|
583
|
+
|
|
584
|
+
Contributions are welcome! Please follow the existing code style and include tests for new features.
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
# Run tests before submitting
|
|
588
|
+
bun test
|
|
589
|
+
|
|
590
|
+
# Build to verify no errors
|
|
591
|
+
bun run build
|
|
592
|
+
```
|
|
@@ -24,20 +24,27 @@ export declare const getFieldValue: (field: FormField) => FieldValue;
|
|
|
24
24
|
*/
|
|
25
25
|
export declare const setFieldValue: (field: FormField, value: FieldValue, silent?: boolean) => void;
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* Binds change and input events to fields
|
|
28
|
+
* Calls the provided callback when any field changes
|
|
29
|
+
*
|
|
30
|
+
* Listens to both 'input' (for immediate feedback while typing)
|
|
31
|
+
* and 'change' (for components that only emit on blur/selection)
|
|
32
|
+
*
|
|
33
|
+
* Deduplicates events to prevent double-firing when both input and change
|
|
34
|
+
* emit for the same value (e.g., textfield input followed by blur)
|
|
29
35
|
*/
|
|
30
|
-
export declare const updateTrackedFieldValue: (name: string, value: FieldValue) => void;
|
|
31
36
|
/**
|
|
32
|
-
* Updates
|
|
33
|
-
* Called after silent setData to sync deduplication state
|
|
37
|
+
* Updates the tracked value for a field in the given tracker
|
|
34
38
|
*/
|
|
35
|
-
export declare const
|
|
39
|
+
export declare const updateTrackedFieldValue: (name: string, value: FieldValue, tracker?: Map<string, FieldValue>) => void;
|
|
36
40
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
41
|
+
* Updates all tracked field values from a fields registry
|
|
42
|
+
* Called after silent setData to sync deduplication state
|
|
43
|
+
*
|
|
44
|
+
* @param fields - The form's field registry
|
|
45
|
+
* @param tracker - The per-form tracker map (stored on the component as _fieldValueTracker)
|
|
39
46
|
*/
|
|
40
|
-
export declare const
|
|
47
|
+
export declare const syncTrackedFieldValues: (fields: FormFieldRegistry, tracker?: Map<string, FieldValue>) => void;
|
|
41
48
|
/**
|
|
42
49
|
* withFields feature
|
|
43
50
|
* Adds field extraction and registry management to the form
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Each feature adds specific capabilities to the form component
|
|
4
4
|
*/
|
|
5
5
|
export { withLayout } from "./layout";
|
|
6
|
-
export { withFields, getFieldValue, setFieldValue, updateTrackedFieldValue, syncTrackedFieldValues,
|
|
6
|
+
export { withFields, getFieldValue, setFieldValue, updateTrackedFieldValue, syncTrackedFieldValues, } from "./fields";
|
|
7
7
|
export { withData, flatToNested, getNestedValue, setNestedValue } from "./data";
|
|
8
8
|
export { withController } from "./controller";
|
|
9
9
|
export { withSubmit, validateData, performRequest } from "./submit";
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Components Module Exports
|
|
3
3
|
*
|
|
4
|
-
* Central export point for all components
|
|
4
|
+
* Central export point for all components.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: Constants are NOT exported here to enable tree-shaking.
|
|
7
|
+
* Import constants directly from the component's constants file:
|
|
8
|
+
* import { COLORPICKER_EVENTS } from 'mtrl-addons/components/colorpicker/constants'
|
|
9
|
+
* import { FORM_EVENTS } from 'mtrl-addons/components/form/constants'
|
|
10
|
+
* import { VLIST_CLASSES } from 'mtrl-addons/components/vlist/constants'
|
|
5
11
|
*/
|
|
6
12
|
export { createVList } from "./vlist";
|
|
7
13
|
export type { VListConfig, VListComponent } from "./vlist/types";
|
|
8
14
|
export { createForm } from "./form";
|
|
9
15
|
export type { FormConfig, FormComponent, FormField, FormData, DataState, FormState, FormValidationRule, FormValidationResult, FormSubmitOptions, FormEventHandlers, FieldValue, SubmitHandler, CancelHandler, } from "./form/types";
|
|
10
|
-
export { DATA_STATE, FORM_EVENTS, FORM_CLASSES, FORM_DEFAULTS, } from "./form/constants";
|
|
11
16
|
export { createColorPicker } from "./colorpicker";
|
|
12
17
|
export type { ColorPickerConfig, ColorPickerComponent, HSVColor, RGBColor, ColorSwatch, } from "./colorpicker/types";
|
|
13
|
-
export { COLORPICKER_EVENTS, COLORPICKER_SIZES, COLORPICKER_VARIANTS, COLORPICKER_CLASSES, COLORPICKER_DEFAULTS, SWATCH_SIZES, PALETTE_SWATCH_ORDER, } from "./colorpicker/constants";
|
|
14
18
|
export { hsvToRgb, rgbToHsv, hsvToHex, hexToHsv, rgbToHex, hexToRgb, isValidHex, normalizeHex, getContrastColor, } from "./colorpicker/utils";
|