mithril-materialized 3.3.8 → 3.4.1
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 +1 -1
- package/dist/button.d.ts +1 -0
- package/dist/components.css +22 -0
- package/dist/core.css +0 -3
- package/dist/forms.css +8 -3
- package/dist/index.css +23 -3
- package/dist/index.esm.js +342 -101
- package/dist/index.js +342 -100
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +342 -100
- package/dist/utilities.css +1 -0
- package/dist/waves.d.ts +16 -0
- package/package.json +1 -1
- package/sass/components/_buttons.scss +21 -0
- package/sass/components/_chips.scss +1 -0
- package/sass/components/_theme-switcher.scss +10 -0
- package/sass/components/forms/_select.scss +3 -3
package/dist/index.umd.js
CHANGED
|
@@ -232,8 +232,6 @@
|
|
|
232
232
|
if (attrs.onAutocomplete) {
|
|
233
233
|
attrs.onAutocomplete(suggestion.key);
|
|
234
234
|
}
|
|
235
|
-
// Force redraw to update label state
|
|
236
|
-
m.redraw();
|
|
237
235
|
};
|
|
238
236
|
const handleKeydown = (e, attrs) => {
|
|
239
237
|
if (!state.isOpen)
|
|
@@ -312,7 +310,7 @@
|
|
|
312
310
|
const id = attrs.id || state.id;
|
|
313
311
|
const { label, helperText, onchange, newRow, className = 'col s12', style, iconName, isMandatory, data = {}, limit = Infinity, minLength = 1 } = attrs, params = __rest(attrs, ["label", "helperText", "onchange", "newRow", "className", "style", "iconName", "isMandatory", "data", "limit", "minLength"]);
|
|
314
312
|
const controlled = isControlled(attrs);
|
|
315
|
-
const currentValue = controlled ?
|
|
313
|
+
const currentValue = controlled ? attrs.value || '' : state.internalValue;
|
|
316
314
|
const cn = newRow ? className + ' clear' : className;
|
|
317
315
|
// Update suggestions when input changes
|
|
318
316
|
state.suggestions = filterSuggestions(currentValue, data, limit, minLength);
|
|
@@ -327,7 +325,7 @@
|
|
|
327
325
|
style,
|
|
328
326
|
}, [
|
|
329
327
|
iconName ? m('i.material-icons.prefix', iconName) : '',
|
|
330
|
-
m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value:
|
|
328
|
+
m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: currentValue, oncreate: (vnode) => {
|
|
331
329
|
state.inputElement = vnode.dom;
|
|
332
330
|
// Set initial value for uncontrolled mode
|
|
333
331
|
if (!controlled && attrs.defaultValue) {
|
|
@@ -363,14 +361,10 @@
|
|
|
363
361
|
}
|
|
364
362
|
}, onblur: (e) => {
|
|
365
363
|
state.isActive = false;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
state.selectedIndex = -1;
|
|
371
|
-
m.redraw();
|
|
372
|
-
}
|
|
373
|
-
}, 150);
|
|
364
|
+
if (!e.relatedTarget || !e.relatedTarget.closest('.autocomplete-content')) {
|
|
365
|
+
state.isOpen = false;
|
|
366
|
+
state.selectedIndex = -1;
|
|
367
|
+
}
|
|
374
368
|
} })),
|
|
375
369
|
// Autocomplete dropdown
|
|
376
370
|
state.isOpen &&
|
|
@@ -386,7 +380,6 @@
|
|
|
386
380
|
},
|
|
387
381
|
onmouseover: () => {
|
|
388
382
|
state.selectedIndex = index;
|
|
389
|
-
m.redraw();
|
|
390
383
|
},
|
|
391
384
|
}, [
|
|
392
385
|
// Check if value contains image URL or icon
|
|
@@ -436,6 +429,103 @@
|
|
|
436
429
|
},
|
|
437
430
|
});
|
|
438
431
|
|
|
432
|
+
/*!
|
|
433
|
+
* Waves Effect for Mithril Materialized
|
|
434
|
+
* Based on Waves v0.6.4 by Alfiana E. Sibuea
|
|
435
|
+
* Adapted for TypeScript and Mithril integration
|
|
436
|
+
*/
|
|
437
|
+
class WavesEffect {
|
|
438
|
+
static offset(elem) {
|
|
439
|
+
const rect = elem.getBoundingClientRect();
|
|
440
|
+
return {
|
|
441
|
+
top: rect.top + window.pageYOffset,
|
|
442
|
+
left: rect.left + window.pageXOffset
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
static createRipple(e, element) {
|
|
446
|
+
// Disable right click
|
|
447
|
+
if (e.button === 2) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
// Create ripple element
|
|
451
|
+
const ripple = document.createElement('div');
|
|
452
|
+
ripple.className = 'waves-ripple';
|
|
453
|
+
// Get click position relative to element
|
|
454
|
+
const pos = this.offset(element);
|
|
455
|
+
const relativeY = e.pageY - pos.top;
|
|
456
|
+
const relativeX = e.pageX - pos.left;
|
|
457
|
+
// Calculate scale based on element size
|
|
458
|
+
const scale = (element.clientWidth / 100) * 10;
|
|
459
|
+
// Set initial ripple position and style
|
|
460
|
+
ripple.style.cssText = `
|
|
461
|
+
top: ${relativeY}px;
|
|
462
|
+
left: ${relativeX}px;
|
|
463
|
+
transform: scale(0);
|
|
464
|
+
opacity: 1;
|
|
465
|
+
`;
|
|
466
|
+
// Add ripple to element
|
|
467
|
+
element.appendChild(ripple);
|
|
468
|
+
// Force reflow and animate
|
|
469
|
+
ripple.offsetHeight;
|
|
470
|
+
ripple.style.transform = `scale(${scale})`;
|
|
471
|
+
ripple.style.opacity = '1';
|
|
472
|
+
// Store reference for cleanup
|
|
473
|
+
ripple.setAttribute('data-created', Date.now().toString());
|
|
474
|
+
}
|
|
475
|
+
static removeRipples(element) {
|
|
476
|
+
const ripples = element.querySelectorAll('.waves-ripple');
|
|
477
|
+
ripples.forEach((ripple) => {
|
|
478
|
+
const created = parseInt(ripple.getAttribute('data-created') || '0');
|
|
479
|
+
const age = Date.now() - created;
|
|
480
|
+
const fadeOut = () => {
|
|
481
|
+
ripple.style.opacity = '0';
|
|
482
|
+
setTimeout(() => {
|
|
483
|
+
if (ripple.parentNode) {
|
|
484
|
+
ripple.parentNode.removeChild(ripple);
|
|
485
|
+
}
|
|
486
|
+
}, this.duration);
|
|
487
|
+
};
|
|
488
|
+
if (age >= 350) {
|
|
489
|
+
fadeOut();
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
setTimeout(fadeOut, 350 - age);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
WavesEffect.duration = 750;
|
|
498
|
+
WavesEffect.onMouseDown = (e) => {
|
|
499
|
+
const element = e.currentTarget;
|
|
500
|
+
if (element && element.classList.contains('waves-effect')) {
|
|
501
|
+
WavesEffect.createRipple(e, element);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
WavesEffect.onMouseUp = (e) => {
|
|
505
|
+
const element = e.currentTarget;
|
|
506
|
+
if (element && element.classList.contains('waves-effect')) {
|
|
507
|
+
WavesEffect.removeRipples(element);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
WavesEffect.onMouseLeave = (e) => {
|
|
511
|
+
const element = e.currentTarget;
|
|
512
|
+
if (element && element.classList.contains('waves-effect')) {
|
|
513
|
+
WavesEffect.removeRipples(element);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
WavesEffect.onTouchStart = (e) => {
|
|
517
|
+
const element = e.currentTarget;
|
|
518
|
+
if (element && element.classList.contains('waves-effect')) {
|
|
519
|
+
WavesEffect.createRipple(e, element);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
WavesEffect.onTouchEnd = (e) => {
|
|
523
|
+
const element = e.currentTarget;
|
|
524
|
+
if (element && element.classList.contains('waves-effect')) {
|
|
525
|
+
WavesEffect.removeRipples(element);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
439
529
|
/**
|
|
440
530
|
* A factory to create new buttons.
|
|
441
531
|
*
|
|
@@ -449,13 +539,18 @@
|
|
|
449
539
|
iconName, iconClass, label, className, variant } = attrs, params = __rest(attrs, ["tooltip", "tooltipPosition", "tooltipPostion", "iconName", "iconClass", "label", "className", "variant"]);
|
|
450
540
|
// Use variant or fallback to factory type
|
|
451
541
|
const buttonType = variant || type || 'button';
|
|
452
|
-
const cn = [tooltip ? 'tooltipped' : '', defaultClassNames, className]
|
|
453
|
-
.filter(Boolean)
|
|
454
|
-
.join(' ')
|
|
455
|
-
.trim();
|
|
542
|
+
const cn = [tooltip ? 'tooltipped' : '', defaultClassNames, className].filter(Boolean).join(' ').trim();
|
|
456
543
|
// Use tooltipPosition if available, fallback to legacy tooltipPostion
|
|
457
544
|
const position = tooltipPosition || tooltipPostion || 'top';
|
|
458
|
-
|
|
545
|
+
// Add waves effect event handlers if waves-effect class is present
|
|
546
|
+
const wavesHandlers = cn.includes('waves-effect') ? {
|
|
547
|
+
onmousedown: WavesEffect.onMouseDown,
|
|
548
|
+
onmouseup: WavesEffect.onMouseUp,
|
|
549
|
+
onmouseleave: WavesEffect.onMouseLeave,
|
|
550
|
+
ontouchstart: WavesEffect.onTouchStart,
|
|
551
|
+
ontouchend: WavesEffect.onTouchEnd
|
|
552
|
+
} : {};
|
|
553
|
+
return m(element, Object.assign(Object.assign(Object.assign({}, params), wavesHandlers), { className: cn, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined, type: buttonType }), iconName ? m(Icon, { iconName, className: iconClass || 'left' }) : undefined, label ? label : undefined);
|
|
459
554
|
},
|
|
460
555
|
};
|
|
461
556
|
};
|
|
@@ -464,6 +559,7 @@
|
|
|
464
559
|
const LargeButton = ButtonFactory('a', 'waves-effect waves-light btn-large', 'button');
|
|
465
560
|
const SmallButton = ButtonFactory('a', 'waves-effect waves-light btn-small', 'button');
|
|
466
561
|
const FlatButton = ButtonFactory('a', 'waves-effect waves-teal btn-flat', 'button');
|
|
562
|
+
const IconButton = ButtonFactory('button', 'btn-flat btn-icon waves-effect waves-teal', 'button');
|
|
467
563
|
const RoundIconButton = ButtonFactory('button', 'btn-floating btn-large waves-effect waves-light', 'button');
|
|
468
564
|
const SubmitButton = ButtonFactory('button', 'btn waves-effect waves-light', 'submit');
|
|
469
565
|
|
|
@@ -900,7 +996,7 @@
|
|
|
900
996
|
};
|
|
901
997
|
const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
|
|
902
998
|
const transform = rotation ? `rotate(${rotation}deg)` : undefined;
|
|
903
|
-
return m('svg', Object.assign(Object.assign({}, props), { style: Object.assign({ transform }, style), height: '
|
|
999
|
+
return m('svg', Object.assign(Object.assign({}, props), { style: Object.assign({ transform }, style), height: '24px', width: '24px', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }), iconPaths[name].map((d) => m('path', {
|
|
904
1000
|
d,
|
|
905
1001
|
fill: d.includes('M0 0h24v24H0z') ? 'none' : 'currentColor',
|
|
906
1002
|
})));
|
|
@@ -2384,36 +2480,52 @@
|
|
|
2384
2480
|
return null;
|
|
2385
2481
|
}
|
|
2386
2482
|
};
|
|
2483
|
+
const isControlled = (attrs) => {
|
|
2484
|
+
return attrs.value !== undefined && typeof attrs.oninput === 'function';
|
|
2485
|
+
};
|
|
2486
|
+
const isRangeControlled = (attrs) => {
|
|
2487
|
+
return (attrs.minValue !== undefined || attrs.maxValue !== undefined) && typeof attrs.oninput === 'function';
|
|
2488
|
+
};
|
|
2387
2489
|
const initRangeState = (state, attrs) => {
|
|
2388
|
-
const { min = 0, max = 100, value, minValue, maxValue } = attrs;
|
|
2490
|
+
const { min = 0, max = 100, value, minValue, maxValue, defaultValue } = attrs;
|
|
2389
2491
|
// Initialize single range value
|
|
2390
|
-
if (
|
|
2391
|
-
|
|
2492
|
+
if (isControlled(attrs)) {
|
|
2493
|
+
// Always use value from props in controlled mode
|
|
2494
|
+
state.singleValue = value !== undefined ? value : min;
|
|
2495
|
+
}
|
|
2496
|
+
else {
|
|
2497
|
+
// Use internal state for uncontrolled mode
|
|
2392
2498
|
if (state.singleValue === undefined) {
|
|
2393
|
-
state.singleValue =
|
|
2499
|
+
state.singleValue = defaultValue !== undefined ? defaultValue : value !== undefined ? value : min;
|
|
2394
2500
|
}
|
|
2395
|
-
|
|
2501
|
+
// Only update internal state if props changed and user hasn't interacted
|
|
2502
|
+
if (state.lastValue !== value && !state.hasUserInteracted && value !== undefined) {
|
|
2396
2503
|
state.singleValue = value;
|
|
2397
2504
|
state.lastValue = value;
|
|
2398
2505
|
}
|
|
2399
2506
|
}
|
|
2400
|
-
else if (state.singleValue === undefined) {
|
|
2401
|
-
state.singleValue = min;
|
|
2402
|
-
}
|
|
2403
2507
|
// Initialize range values
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
state.
|
|
2408
|
-
state.rangeMaxValue = currentMaxValue;
|
|
2508
|
+
if (isRangeControlled(attrs)) {
|
|
2509
|
+
// Always use values from props in controlled mode
|
|
2510
|
+
state.rangeMinValue = minValue !== undefined ? minValue : min;
|
|
2511
|
+
state.rangeMaxValue = maxValue !== undefined ? maxValue : max;
|
|
2409
2512
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
state.rangeMaxValue
|
|
2415
|
-
|
|
2416
|
-
|
|
2513
|
+
else {
|
|
2514
|
+
// Use internal state for uncontrolled mode
|
|
2515
|
+
const currentMinValue = minValue !== undefined ? minValue : min;
|
|
2516
|
+
const currentMaxValue = maxValue !== undefined ? maxValue : max;
|
|
2517
|
+
if (state.rangeMinValue === undefined || state.rangeMaxValue === undefined) {
|
|
2518
|
+
state.rangeMinValue = currentMinValue;
|
|
2519
|
+
state.rangeMaxValue = currentMaxValue;
|
|
2520
|
+
}
|
|
2521
|
+
if (!state.hasUserInteracted &&
|
|
2522
|
+
((minValue !== undefined && state.lastMinValue !== minValue) ||
|
|
2523
|
+
(maxValue !== undefined && state.lastMaxValue !== maxValue))) {
|
|
2524
|
+
state.rangeMinValue = currentMinValue;
|
|
2525
|
+
state.rangeMaxValue = currentMaxValue;
|
|
2526
|
+
state.lastMinValue = minValue;
|
|
2527
|
+
state.lastMaxValue = maxValue;
|
|
2528
|
+
}
|
|
2417
2529
|
}
|
|
2418
2530
|
// Initialize active thumb if not set
|
|
2419
2531
|
if (state.activeThumb === null) {
|
|
@@ -2430,15 +2542,18 @@
|
|
|
2430
2542
|
minValue = maxValue;
|
|
2431
2543
|
if (maxValue < minValue)
|
|
2432
2544
|
maxValue = minValue;
|
|
2433
|
-
state
|
|
2434
|
-
|
|
2545
|
+
// Only update internal state for uncontrolled mode
|
|
2546
|
+
if (!isRangeControlled(attrs)) {
|
|
2547
|
+
state.rangeMinValue = minValue;
|
|
2548
|
+
state.rangeMaxValue = maxValue;
|
|
2549
|
+
}
|
|
2435
2550
|
state.hasUserInteracted = true;
|
|
2436
|
-
// Call
|
|
2551
|
+
// Call appropriate handler based on interaction type, not control mode
|
|
2437
2552
|
if (immediate && attrs.oninput) {
|
|
2438
|
-
attrs.oninput(minValue, maxValue);
|
|
2553
|
+
attrs.oninput(minValue, maxValue); // Immediate feedback during drag
|
|
2439
2554
|
}
|
|
2440
|
-
|
|
2441
|
-
attrs.onchange(minValue, maxValue);
|
|
2555
|
+
if (!immediate && attrs.onchange) {
|
|
2556
|
+
attrs.onchange(minValue, maxValue); // Final value on interaction end (blur/mouseup)
|
|
2442
2557
|
}
|
|
2443
2558
|
};
|
|
2444
2559
|
// Single Range Slider Component
|
|
@@ -2472,19 +2587,24 @@
|
|
|
2472
2587
|
: tooltipPos
|
|
2473
2588
|
: tooltipPos;
|
|
2474
2589
|
const updateSingleValue = (newValue, immediate = false) => {
|
|
2475
|
-
state
|
|
2590
|
+
// Only update internal state for uncontrolled mode
|
|
2591
|
+
if (!isControlled(attrs)) {
|
|
2592
|
+
state.singleValue = newValue;
|
|
2593
|
+
}
|
|
2476
2594
|
state.hasUserInteracted = true;
|
|
2595
|
+
// Call appropriate handler based on interaction type, not control mode
|
|
2477
2596
|
if (immediate && oninput) {
|
|
2478
|
-
oninput(newValue);
|
|
2597
|
+
oninput(newValue); // Immediate feedback during drag
|
|
2479
2598
|
}
|
|
2480
|
-
|
|
2481
|
-
onchange(newValue);
|
|
2599
|
+
if (!immediate && onchange) {
|
|
2600
|
+
onchange(newValue); // Final value on interaction end (blur/mouseup)
|
|
2482
2601
|
}
|
|
2483
2602
|
};
|
|
2484
2603
|
const handleMouseDown = (e) => {
|
|
2485
2604
|
if (disabled)
|
|
2486
2605
|
return;
|
|
2487
2606
|
e.preventDefault();
|
|
2607
|
+
e.stopPropagation();
|
|
2488
2608
|
state.isDragging = true;
|
|
2489
2609
|
if (finalValueDisplay === 'auto') {
|
|
2490
2610
|
m.redraw();
|
|
@@ -2559,6 +2679,11 @@
|
|
|
2559
2679
|
updateSingleValue(newValue, false);
|
|
2560
2680
|
}
|
|
2561
2681
|
},
|
|
2682
|
+
onblur: () => {
|
|
2683
|
+
if (disabled || !onchange)
|
|
2684
|
+
return;
|
|
2685
|
+
onchange(state.singleValue);
|
|
2686
|
+
},
|
|
2562
2687
|
}, [
|
|
2563
2688
|
m(`.track.${orientation}`),
|
|
2564
2689
|
m(`.range-progress.${orientation}`, { style: progressStyle }),
|
|
@@ -2617,6 +2742,7 @@
|
|
|
2617
2742
|
if (disabled)
|
|
2618
2743
|
return;
|
|
2619
2744
|
e.preventDefault();
|
|
2745
|
+
e.stopPropagation();
|
|
2620
2746
|
state.isDragging = true;
|
|
2621
2747
|
state.activeThumb = thumb;
|
|
2622
2748
|
if (finalValueDisplay === 'auto') {
|
|
@@ -2707,6 +2833,11 @@
|
|
|
2707
2833
|
maxThumb.focus();
|
|
2708
2834
|
}
|
|
2709
2835
|
},
|
|
2836
|
+
onblur: () => {
|
|
2837
|
+
if (disabled || !attrs.onchange)
|
|
2838
|
+
return;
|
|
2839
|
+
attrs.onchange(state.rangeMinValue, state.rangeMaxValue);
|
|
2840
|
+
},
|
|
2710
2841
|
}, [
|
|
2711
2842
|
m(`.track.${orientation}`),
|
|
2712
2843
|
m(`.range.${orientation}`, { style: rangeStyle }),
|
|
@@ -2912,13 +3043,13 @@
|
|
|
2912
3043
|
overflowWrap: 'break-word',
|
|
2913
3044
|
},
|
|
2914
3045
|
oncreate: ({ dom }) => {
|
|
2915
|
-
const hiddenDiv = state.hiddenDiv = dom;
|
|
3046
|
+
const hiddenDiv = (state.hiddenDiv = dom);
|
|
2916
3047
|
if (state.textarea) {
|
|
2917
3048
|
updateHeight(state.textarea, hiddenDiv);
|
|
2918
3049
|
}
|
|
2919
3050
|
},
|
|
2920
3051
|
onupdate: ({ dom }) => {
|
|
2921
|
-
const hiddenDiv = state.hiddenDiv = dom;
|
|
3052
|
+
const hiddenDiv = (state.hiddenDiv = dom);
|
|
2922
3053
|
if (state.textarea) {
|
|
2923
3054
|
updateHeight(state.textarea, hiddenDiv);
|
|
2924
3055
|
}
|
|
@@ -3031,8 +3162,7 @@
|
|
|
3031
3162
|
isDragging: false,
|
|
3032
3163
|
activeThumb: null,
|
|
3033
3164
|
};
|
|
3034
|
-
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' &&
|
|
3035
|
-
(typeof attrs.oninput === 'function' || typeof attrs.onchange === 'function');
|
|
3165
|
+
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' && typeof attrs.oninput === 'function';
|
|
3036
3166
|
const getValue = (target) => {
|
|
3037
3167
|
const val = target.value;
|
|
3038
3168
|
return (val ? (type === 'number' || type === 'range' ? +val : val) : val);
|
|
@@ -3082,7 +3212,7 @@
|
|
|
3082
3212
|
const isNonInteractive = attrs.readonly || attrs.disabled;
|
|
3083
3213
|
// Warn developer for improper controlled usage
|
|
3084
3214
|
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
3085
|
-
console.warn(`${type} input received 'value' prop without 'oninput'
|
|
3215
|
+
console.warn(`${type} input with label '${attrs.label}' received 'value' prop without 'oninput' handler. ` +
|
|
3086
3216
|
`Use 'defaultValue' for uncontrolled components or add an event handler for controlled components.`);
|
|
3087
3217
|
}
|
|
3088
3218
|
// Initialize internal value if not in controlled mode
|
|
@@ -4109,14 +4239,12 @@
|
|
|
4109
4239
|
inputRef: null,
|
|
4110
4240
|
dropdownRef: null,
|
|
4111
4241
|
internalCheckedId: undefined,
|
|
4242
|
+
isInsideModal: false,
|
|
4112
4243
|
};
|
|
4113
4244
|
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
4114
|
-
const closeDropdown = (
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
state.isOpen = false;
|
|
4118
|
-
m.redraw();
|
|
4119
|
-
}
|
|
4245
|
+
const closeDropdown = () => {
|
|
4246
|
+
state.isOpen = false;
|
|
4247
|
+
m.redraw(); // Needed to remove the dropdown options list (potentially added to document root)
|
|
4120
4248
|
};
|
|
4121
4249
|
const handleKeyDown = (e, items) => {
|
|
4122
4250
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -4161,6 +4289,83 @@
|
|
|
4161
4289
|
return undefined;
|
|
4162
4290
|
}
|
|
4163
4291
|
};
|
|
4292
|
+
const getPortalStyles = (inputRef) => {
|
|
4293
|
+
if (!inputRef)
|
|
4294
|
+
return {};
|
|
4295
|
+
const rect = inputRef.getBoundingClientRect();
|
|
4296
|
+
const viewportHeight = window.innerHeight;
|
|
4297
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
4298
|
+
const spaceAbove = rect.top;
|
|
4299
|
+
// Choose whether to show above or below based on available space
|
|
4300
|
+
const showAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
|
|
4301
|
+
return {
|
|
4302
|
+
position: 'fixed',
|
|
4303
|
+
top: showAbove ? 'auto' : `${rect.bottom}px`,
|
|
4304
|
+
bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
|
|
4305
|
+
left: `${rect.left}px`,
|
|
4306
|
+
width: `${rect.width}px`,
|
|
4307
|
+
zIndex: 10000,
|
|
4308
|
+
maxHeight: showAbove ? `${spaceAbove - 20}px` : `${spaceBelow - 20}px`,
|
|
4309
|
+
overflow: 'auto',
|
|
4310
|
+
display: 'block',
|
|
4311
|
+
opacity: 1,
|
|
4312
|
+
};
|
|
4313
|
+
};
|
|
4314
|
+
const updatePortalDropdown = (items, selectedLabel, onSelectItem) => {
|
|
4315
|
+
if (!state.isInsideModal)
|
|
4316
|
+
return;
|
|
4317
|
+
// Clean up existing portal
|
|
4318
|
+
const existingPortal = document.getElementById(`${state.id}-dropdown`);
|
|
4319
|
+
if (existingPortal) {
|
|
4320
|
+
existingPortal.remove();
|
|
4321
|
+
}
|
|
4322
|
+
if (!state.isOpen || !state.inputRef)
|
|
4323
|
+
return;
|
|
4324
|
+
// Create portal element
|
|
4325
|
+
const portalElement = document.createElement('div');
|
|
4326
|
+
portalElement.id = `${state.id}-dropdown`;
|
|
4327
|
+
document.body.appendChild(portalElement);
|
|
4328
|
+
// Create dropdown content
|
|
4329
|
+
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
4330
|
+
const dropdownContent = items.map((item) => {
|
|
4331
|
+
if (item.divider) {
|
|
4332
|
+
return m('li.divider');
|
|
4333
|
+
}
|
|
4334
|
+
const itemIndex = availableItems.indexOf(item);
|
|
4335
|
+
const isSelected = selectedLabel === item.label;
|
|
4336
|
+
const isFocused = state.focusedIndex === itemIndex;
|
|
4337
|
+
return m('li', {
|
|
4338
|
+
class: `${isSelected ? 'selected' : ''} ${isFocused ? 'focused' : ''}${item.disabled ? ' disabled' : ''}`,
|
|
4339
|
+
onclick: item.disabled ? undefined : () => onSelectItem(item),
|
|
4340
|
+
}, m('span', {
|
|
4341
|
+
style: {
|
|
4342
|
+
display: 'flex',
|
|
4343
|
+
alignItems: 'center',
|
|
4344
|
+
padding: '14px 16px',
|
|
4345
|
+
},
|
|
4346
|
+
}, [
|
|
4347
|
+
item.iconName
|
|
4348
|
+
? m('i.material-icons', {
|
|
4349
|
+
style: { marginRight: '32px' },
|
|
4350
|
+
}, item.iconName)
|
|
4351
|
+
: undefined,
|
|
4352
|
+
item.label,
|
|
4353
|
+
]));
|
|
4354
|
+
});
|
|
4355
|
+
// Create dropdown with proper positioning
|
|
4356
|
+
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
4357
|
+
tabindex: 0,
|
|
4358
|
+
style: getPortalStyles(state.inputRef),
|
|
4359
|
+
oncreate: ({ dom }) => {
|
|
4360
|
+
state.dropdownRef = dom;
|
|
4361
|
+
},
|
|
4362
|
+
onremove: () => {
|
|
4363
|
+
state.dropdownRef = null;
|
|
4364
|
+
},
|
|
4365
|
+
}, dropdownContent);
|
|
4366
|
+
// Render to portal
|
|
4367
|
+
m.render(portalElement, dropdownVnode);
|
|
4368
|
+
};
|
|
4164
4369
|
return {
|
|
4165
4370
|
oninit: ({ attrs }) => {
|
|
4166
4371
|
var _a;
|
|
@@ -4172,9 +4377,18 @@
|
|
|
4172
4377
|
// Add global click listener to close dropdown
|
|
4173
4378
|
document.addEventListener('click', closeDropdown);
|
|
4174
4379
|
},
|
|
4380
|
+
oncreate: ({ dom }) => {
|
|
4381
|
+
// Detect if component is inside a modal
|
|
4382
|
+
state.isInsideModal = !!dom.closest('.modal');
|
|
4383
|
+
},
|
|
4175
4384
|
onremove: () => {
|
|
4176
4385
|
// Cleanup global listener
|
|
4177
4386
|
document.removeEventListener('click', closeDropdown);
|
|
4387
|
+
// Cleanup portal
|
|
4388
|
+
const portalElement = document.getElementById(`${state.id}-dropdown`);
|
|
4389
|
+
if (portalElement) {
|
|
4390
|
+
portalElement.remove();
|
|
4391
|
+
}
|
|
4178
4392
|
},
|
|
4179
4393
|
view: ({ attrs }) => {
|
|
4180
4394
|
const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
|
|
@@ -4197,6 +4411,16 @@
|
|
|
4197
4411
|
: undefined;
|
|
4198
4412
|
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
4199
4413
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
4414
|
+
// Update portal dropdown when inside modal
|
|
4415
|
+
if (state.isInsideModal) {
|
|
4416
|
+
updatePortalDropdown(items, title, (item) => {
|
|
4417
|
+
if (item.id) {
|
|
4418
|
+
state.isOpen = false;
|
|
4419
|
+
state.focusedIndex = -1;
|
|
4420
|
+
handleSelection(item.id);
|
|
4421
|
+
}
|
|
4422
|
+
});
|
|
4423
|
+
}
|
|
4200
4424
|
return m('.dropdown-wrapper.input-field', { className, key, style }, [
|
|
4201
4425
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
4202
4426
|
m(HelperText, { helperText }),
|
|
@@ -4230,8 +4454,9 @@
|
|
|
4230
4454
|
}
|
|
4231
4455
|
},
|
|
4232
4456
|
}),
|
|
4233
|
-
// Dropdown Menu
|
|
4457
|
+
// Dropdown Menu - render inline only when NOT inside modal
|
|
4234
4458
|
state.isOpen &&
|
|
4459
|
+
!state.isInsideModal &&
|
|
4235
4460
|
m('ul.dropdown-content.select-dropdown', {
|
|
4236
4461
|
tabindex: 0,
|
|
4237
4462
|
role: 'listbox',
|
|
@@ -4348,12 +4573,17 @@
|
|
|
4348
4573
|
}
|
|
4349
4574
|
: undefined,
|
|
4350
4575
|
}, [
|
|
4351
|
-
m('a.btn-floating.btn-large', {
|
|
4576
|
+
m('a.btn-floating.btn-large.waves-effect.waves-light', {
|
|
4352
4577
|
className,
|
|
4578
|
+
onmousedown: WavesEffect.onMouseDown,
|
|
4579
|
+
onmouseup: WavesEffect.onMouseUp,
|
|
4580
|
+
onmouseleave: WavesEffect.onMouseLeave,
|
|
4581
|
+
ontouchstart: WavesEffect.onTouchStart,
|
|
4582
|
+
ontouchend: WavesEffect.onTouchEnd,
|
|
4353
4583
|
}, m('i.material-icons', { className: iconClass }, iconName)),
|
|
4354
4584
|
buttons &&
|
|
4355
4585
|
buttons.length > 0 &&
|
|
4356
|
-
m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
|
|
4586
|
+
m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.waves-effect.waves-light.${button.className || 'red'}`, {
|
|
4357
4587
|
style: {
|
|
4358
4588
|
opacity: state.isOpen ? '1' : '0',
|
|
4359
4589
|
transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
|
|
@@ -4364,6 +4594,11 @@
|
|
|
4364
4594
|
if (button.onclick)
|
|
4365
4595
|
button.onclick(e);
|
|
4366
4596
|
},
|
|
4597
|
+
onmousedown: WavesEffect.onMouseDown,
|
|
4598
|
+
onmouseup: WavesEffect.onMouseUp,
|
|
4599
|
+
onmouseleave: WavesEffect.onMouseLeave,
|
|
4600
|
+
ontouchstart: WavesEffect.onTouchStart,
|
|
4601
|
+
ontouchend: WavesEffect.onTouchEnd,
|
|
4367
4602
|
}, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
|
|
4368
4603
|
]));
|
|
4369
4604
|
},
|
|
@@ -4671,7 +4906,7 @@
|
|
|
4671
4906
|
maxWidth: '75%',
|
|
4672
4907
|
borderRadius: '4px',
|
|
4673
4908
|
})), { backgroundColor: 'var(--mm-modal-background, #fff)', maxHeight: '85%', overflow: 'auto', zIndex: '1003', padding: '0', flexDirection: 'column', boxShadow: '0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,0,0.20)' }),
|
|
4674
|
-
onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
|
|
4909
|
+
// onclick: (e: Event) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
|
|
4675
4910
|
}, [
|
|
4676
4911
|
// Close button
|
|
4677
4912
|
showCloseButton &&
|
|
@@ -5767,6 +6002,7 @@
|
|
|
5767
6002
|
dropdownRef: null,
|
|
5768
6003
|
internalSelectedIds: [],
|
|
5769
6004
|
isInsideModal: false,
|
|
6005
|
+
isMultiple: false,
|
|
5770
6006
|
};
|
|
5771
6007
|
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5772
6008
|
const isSelected = (id, selectedIds) => {
|
|
@@ -5849,10 +6085,18 @@
|
|
|
5849
6085
|
}
|
|
5850
6086
|
};
|
|
5851
6087
|
const closeDropdown = (e) => {
|
|
6088
|
+
console.log('select closeDropdown called');
|
|
6089
|
+
if (!state.isMultiple) {
|
|
6090
|
+
state.isOpen = false;
|
|
6091
|
+
return;
|
|
6092
|
+
}
|
|
5852
6093
|
const target = e.target;
|
|
5853
|
-
|
|
6094
|
+
// When inside modal, check both the select component AND the portaled dropdown
|
|
6095
|
+
const isClickInsideSelect = target.closest('.input-field.select-space');
|
|
6096
|
+
const isClickInsidePortalDropdown = state.isInsideModal && state.dropdownRef && (state.dropdownRef.contains(target) || target === state.dropdownRef);
|
|
6097
|
+
if (!isClickInsideSelect && !isClickInsidePortalDropdown) {
|
|
6098
|
+
console.log('select closeDropdown called: set state');
|
|
5854
6099
|
state.isOpen = false;
|
|
5855
|
-
m.redraw();
|
|
5856
6100
|
}
|
|
5857
6101
|
};
|
|
5858
6102
|
const getPortalStyles = (inputRef) => {
|
|
@@ -5860,13 +6104,21 @@
|
|
|
5860
6104
|
return {};
|
|
5861
6105
|
const rect = inputRef.getBoundingClientRect();
|
|
5862
6106
|
const viewportHeight = window.innerHeight;
|
|
6107
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
6108
|
+
const spaceAbove = rect.top;
|
|
6109
|
+
// Choose whether to show above or below based on available space
|
|
6110
|
+
const showAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
|
|
5863
6111
|
return {
|
|
5864
6112
|
position: 'fixed',
|
|
5865
|
-
top: `${rect.bottom}px`,
|
|
6113
|
+
top: showAbove ? 'auto' : `${rect.bottom}px`,
|
|
6114
|
+
bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
|
|
5866
6115
|
left: `${rect.left}px`,
|
|
5867
6116
|
width: `${rect.width}px`,
|
|
5868
6117
|
zIndex: 10000, // Higher than modal z-index
|
|
5869
|
-
maxHeight: `${
|
|
6118
|
+
maxHeight: showAbove ? `${spaceAbove - 20}px` : `${spaceBelow - 20}px`, // Leave 20px margin
|
|
6119
|
+
overflow: 'auto',
|
|
6120
|
+
display: 'block',
|
|
6121
|
+
opacity: 1,
|
|
5870
6122
|
};
|
|
5871
6123
|
};
|
|
5872
6124
|
const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
|
|
@@ -5874,15 +6126,10 @@
|
|
|
5874
6126
|
// Render ungrouped options first
|
|
5875
6127
|
attrs.options
|
|
5876
6128
|
.filter((option) => !option.group)
|
|
5877
|
-
.map((option) => m('li', Object.assign({
|
|
5878
|
-
? 'disabled'
|
|
5879
|
-
: state.focusedIndex === attrs.options.indexOf(option)
|
|
5880
|
-
? 'focused'
|
|
5881
|
-
: '' }, (option.disabled
|
|
6129
|
+
.map((option) => m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === attrs.options.indexOf(option) ? 'focused' : '' }, (option.disabled
|
|
5882
6130
|
? {}
|
|
5883
6131
|
: {
|
|
5884
|
-
onclick: (
|
|
5885
|
-
e.stopPropagation();
|
|
6132
|
+
onclick: () => {
|
|
5886
6133
|
toggleOption(option.id, multiple, attrs);
|
|
5887
6134
|
},
|
|
5888
6135
|
})), [
|
|
@@ -5912,8 +6159,8 @@
|
|
|
5912
6159
|
return groups;
|
|
5913
6160
|
}, {}))
|
|
5914
6161
|
.map(([groupName, groupOptions]) => [
|
|
5915
|
-
m('li.optgroup', {
|
|
5916
|
-
...groupOptions.map((option) => m('li', Object.assign({
|
|
6162
|
+
m('li.optgroup', { tabindex: 0 }, m('span', groupName)),
|
|
6163
|
+
...groupOptions.map((option) => m('li', Object.assign({ class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === attrs.options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5917
6164
|
? {}
|
|
5918
6165
|
: {
|
|
5919
6166
|
onclick: (e) => {
|
|
@@ -5940,23 +6187,20 @@
|
|
|
5940
6187
|
.reduce((acc, val) => acc.concat(val), []),
|
|
5941
6188
|
];
|
|
5942
6189
|
const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
|
|
5943
|
-
var _a;
|
|
5944
6190
|
if (!state.isInsideModal)
|
|
5945
6191
|
return;
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
m.render(portalElement, []);
|
|
5951
|
-
(_a = portalElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(portalElement);
|
|
5952
|
-
}
|
|
5953
|
-
return;
|
|
5954
|
-
}
|
|
5955
|
-
if (!portalElement) {
|
|
5956
|
-
portalElement = document.createElement('div');
|
|
5957
|
-
portalElement.id = state.dropdownId;
|
|
5958
|
-
document.body.appendChild(portalElement);
|
|
6192
|
+
// Clean up existing portal
|
|
6193
|
+
const existingPortal = document.getElementById(state.dropdownId);
|
|
6194
|
+
if (existingPortal) {
|
|
6195
|
+
existingPortal.remove();
|
|
5959
6196
|
}
|
|
6197
|
+
if (!state.isOpen || !state.inputRef)
|
|
6198
|
+
return;
|
|
6199
|
+
// Create portal element
|
|
6200
|
+
const portalElement = document.createElement('div');
|
|
6201
|
+
portalElement.id = state.dropdownId;
|
|
6202
|
+
document.body.appendChild(portalElement);
|
|
6203
|
+
// Create dropdown with proper positioning
|
|
5960
6204
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
5961
6205
|
tabindex: 0,
|
|
5962
6206
|
style: getPortalStyles(state.inputRef),
|
|
@@ -5967,6 +6211,7 @@
|
|
|
5967
6211
|
state.dropdownRef = null;
|
|
5968
6212
|
},
|
|
5969
6213
|
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
|
|
6214
|
+
// Render to portal
|
|
5970
6215
|
m.render(portalElement, dropdownVnode);
|
|
5971
6216
|
};
|
|
5972
6217
|
return {
|
|
@@ -6009,7 +6254,8 @@
|
|
|
6009
6254
|
view: ({ attrs }) => {
|
|
6010
6255
|
var _a;
|
|
6011
6256
|
const controlled = isControlled(attrs);
|
|
6012
|
-
const { disabled } = attrs;
|
|
6257
|
+
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, disabled, } = attrs;
|
|
6258
|
+
state.isMultiple = multiple;
|
|
6013
6259
|
// Get selected IDs from props or internal state
|
|
6014
6260
|
let selectedIds;
|
|
6015
6261
|
if (controlled) {
|
|
@@ -6025,7 +6271,6 @@
|
|
|
6025
6271
|
// Interactive uncontrolled: use internal state
|
|
6026
6272
|
selectedIds = state.internalSelectedIds;
|
|
6027
6273
|
}
|
|
6028
|
-
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
|
|
6029
6274
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
6030
6275
|
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
6031
6276
|
// Update portal dropdown when inside modal
|
|
@@ -6062,7 +6307,8 @@
|
|
|
6062
6307
|
},
|
|
6063
6308
|
}),
|
|
6064
6309
|
// Dropdown Menu - render inline only when NOT inside modal
|
|
6065
|
-
state.isOpen &&
|
|
6310
|
+
state.isOpen &&
|
|
6311
|
+
!state.isInsideModal &&
|
|
6066
6312
|
m('ul.dropdown-content.select-dropdown', {
|
|
6067
6313
|
tabindex: 0,
|
|
6068
6314
|
oncreate: ({ dom }) => {
|
|
@@ -6220,7 +6466,6 @@
|
|
|
6220
6466
|
}
|
|
6221
6467
|
state.isDragging = false;
|
|
6222
6468
|
state.translateX = 0;
|
|
6223
|
-
// m.redraw();
|
|
6224
6469
|
};
|
|
6225
6470
|
/** Initialize active tab - selectedTabId takes precedence, next active property or first available tab */
|
|
6226
6471
|
const setActiveTabId = (anchoredTabs, selectedTabId) => {
|
|
@@ -6247,7 +6492,6 @@
|
|
|
6247
6492
|
},
|
|
6248
6493
|
oncreate: () => {
|
|
6249
6494
|
updateIndicator();
|
|
6250
|
-
m.redraw();
|
|
6251
6495
|
},
|
|
6252
6496
|
view: ({ attrs }) => {
|
|
6253
6497
|
const { tabWidth, tabs, className, style, swipeable = false } = attrs;
|
|
@@ -6381,7 +6625,6 @@
|
|
|
6381
6625
|
else {
|
|
6382
6626
|
// Click outside, close dropdown
|
|
6383
6627
|
state.isOpen = false;
|
|
6384
|
-
m.redraw();
|
|
6385
6628
|
}
|
|
6386
6629
|
};
|
|
6387
6630
|
// Handle keyboard navigation
|
|
@@ -6616,9 +6859,7 @@
|
|
|
6616
6859
|
]),
|
|
6617
6860
|
// No options found message or list of options
|
|
6618
6861
|
...(filteredOptions.length === 0 && !showAddNew
|
|
6619
|
-
? [
|
|
6620
|
-
m('li.search-select-no-options', texts.noOptionsFound),
|
|
6621
|
-
]
|
|
6862
|
+
? [m('li.search-select-no-options', texts.noOptionsFound)]
|
|
6622
6863
|
: []),
|
|
6623
6864
|
// Add new option item
|
|
6624
6865
|
...(showAddNew
|
|
@@ -9017,6 +9258,7 @@
|
|
|
9017
9258
|
exports.FloatingActionButton = FloatingActionButton;
|
|
9018
9259
|
exports.HelperText = HelperText;
|
|
9019
9260
|
exports.Icon = Icon;
|
|
9261
|
+
exports.IconButton = IconButton;
|
|
9020
9262
|
exports.ImageList = ImageList;
|
|
9021
9263
|
exports.InputCheckbox = InputCheckbox;
|
|
9022
9264
|
exports.Label = Label;
|