mithril-materialized 3.4.0 → 3.4.2
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/core.css +0 -3
- package/dist/forms.css +0 -3
- package/dist/index.css +1 -3
- package/dist/index.esm.js +218 -92
- package/dist/index.js +218 -92
- package/dist/index.min.css +1 -1
- package/dist/index.umd.js +218 -92
- package/dist/utilities.css +1 -0
- package/package.json +1 -1
- package/sass/components/_chips.scss +1 -0
- package/sass/components/forms/_select.scss +3 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A Mithril.js component library inspired by [materialize-css](https://materializecss.com) design principles, [available on npm](https://www.npmjs.com/package/mithril-materialized). This library provides you with ready-to-use Mithril components that follow Material Design guidelines, with **no external JavaScript dependencies**.
|
|
4
4
|
|
|
5
|
-
## 🚀 v3.
|
|
5
|
+
## 🚀 v3.4 - Latest Release
|
|
6
6
|
|
|
7
7
|
The current stable release that provides a complete Mithril.js Material Design component library with no external JavaScript dependencies.
|
|
8
8
|
|
package/dist/core.css
CHANGED
|
@@ -3167,9 +3167,6 @@ body.keyboard-focused .select-dropdown.dropdown-content li:focus {
|
|
|
3167
3167
|
.select-dropdown.dropdown-content li:hover {
|
|
3168
3168
|
background-color: var(--mm-dropdown-hover, rgba(0, 0, 0, 0.08));
|
|
3169
3169
|
}
|
|
3170
|
-
.select-dropdown.dropdown-content li.selected {
|
|
3171
|
-
background-color: var(--mm-dropdown-selected, rgba(0, 0, 0, 0.03));
|
|
3172
|
-
}
|
|
3173
3170
|
|
|
3174
3171
|
.prefix ~ .select-wrapper {
|
|
3175
3172
|
margin-left: 3rem;
|
package/dist/forms.css
CHANGED
|
@@ -1412,9 +1412,6 @@ body.keyboard-focused .select-dropdown.dropdown-content li:focus {
|
|
|
1412
1412
|
.select-dropdown.dropdown-content li:hover {
|
|
1413
1413
|
background-color: var(--mm-dropdown-hover, rgba(0, 0, 0, 0.08));
|
|
1414
1414
|
}
|
|
1415
|
-
.select-dropdown.dropdown-content li.selected {
|
|
1416
|
-
background-color: var(--mm-dropdown-selected, rgba(0, 0, 0, 0.03));
|
|
1417
|
-
}
|
|
1418
1415
|
|
|
1419
1416
|
.prefix ~ .select-wrapper {
|
|
1420
1417
|
margin-left: 3rem;
|
package/dist/index.css
CHANGED
|
@@ -5632,6 +5632,7 @@ body.keyboard-focused .dropdown-content li:focus {
|
|
|
5632
5632
|
font-size: 16px;
|
|
5633
5633
|
line-height: 32px;
|
|
5634
5634
|
padding-left: 8px;
|
|
5635
|
+
min-height: 1lh;
|
|
5635
5636
|
}
|
|
5636
5637
|
|
|
5637
5638
|
.chips {
|
|
@@ -6846,9 +6847,6 @@ body.keyboard-focused .select-dropdown.dropdown-content li:focus {
|
|
|
6846
6847
|
.select-dropdown.dropdown-content li:hover {
|
|
6847
6848
|
background-color: var(--mm-dropdown-hover, rgba(0, 0, 0, 0.08));
|
|
6848
6849
|
}
|
|
6849
|
-
.select-dropdown.dropdown-content li.selected {
|
|
6850
|
-
background-color: var(--mm-dropdown-selected, rgba(0, 0, 0, 0.03));
|
|
6851
|
-
}
|
|
6852
6850
|
|
|
6853
6851
|
.prefix ~ .select-wrapper {
|
|
6854
6852
|
margin-left: 3rem;
|
package/dist/index.esm.js
CHANGED
|
@@ -228,8 +228,6 @@ const Autocomplete = () => {
|
|
|
228
228
|
if (attrs.onAutocomplete) {
|
|
229
229
|
attrs.onAutocomplete(suggestion.key);
|
|
230
230
|
}
|
|
231
|
-
// Force redraw to update label state
|
|
232
|
-
m.redraw();
|
|
233
231
|
};
|
|
234
232
|
const handleKeydown = (e, attrs) => {
|
|
235
233
|
if (!state.isOpen)
|
|
@@ -308,7 +306,7 @@ const Autocomplete = () => {
|
|
|
308
306
|
const id = attrs.id || state.id;
|
|
309
307
|
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"]);
|
|
310
308
|
const controlled = isControlled(attrs);
|
|
311
|
-
const currentValue = controlled ?
|
|
309
|
+
const currentValue = controlled ? attrs.value || '' : state.internalValue;
|
|
312
310
|
const cn = newRow ? className + ' clear' : className;
|
|
313
311
|
// Update suggestions when input changes
|
|
314
312
|
state.suggestions = filterSuggestions(currentValue, data, limit, minLength);
|
|
@@ -323,7 +321,7 @@ const Autocomplete = () => {
|
|
|
323
321
|
style,
|
|
324
322
|
}, [
|
|
325
323
|
iconName ? m('i.material-icons.prefix', iconName) : '',
|
|
326
|
-
m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value:
|
|
324
|
+
m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: currentValue, oncreate: (vnode) => {
|
|
327
325
|
state.inputElement = vnode.dom;
|
|
328
326
|
// Set initial value for uncontrolled mode
|
|
329
327
|
if (!controlled && attrs.defaultValue) {
|
|
@@ -359,14 +357,10 @@ const Autocomplete = () => {
|
|
|
359
357
|
}
|
|
360
358
|
}, onblur: (e) => {
|
|
361
359
|
state.isActive = false;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
state.selectedIndex = -1;
|
|
367
|
-
m.redraw();
|
|
368
|
-
}
|
|
369
|
-
}, 150);
|
|
360
|
+
if (!e.relatedTarget || !e.relatedTarget.closest('.autocomplete-content')) {
|
|
361
|
+
state.isOpen = false;
|
|
362
|
+
state.selectedIndex = -1;
|
|
363
|
+
}
|
|
370
364
|
} })),
|
|
371
365
|
// Autocomplete dropdown
|
|
372
366
|
state.isOpen &&
|
|
@@ -382,7 +376,6 @@ const Autocomplete = () => {
|
|
|
382
376
|
},
|
|
383
377
|
onmouseover: () => {
|
|
384
378
|
state.selectedIndex = index;
|
|
385
|
-
m.redraw();
|
|
386
379
|
},
|
|
387
380
|
}, [
|
|
388
381
|
// Check if value contains image URL or icon
|
|
@@ -2483,36 +2476,52 @@ const handleKeyboardNavigation = (key, currentValue, min, max, step) => {
|
|
|
2483
2476
|
return null;
|
|
2484
2477
|
}
|
|
2485
2478
|
};
|
|
2479
|
+
const isControlled = (attrs) => {
|
|
2480
|
+
return attrs.value !== undefined && typeof attrs.oninput === 'function';
|
|
2481
|
+
};
|
|
2482
|
+
const isRangeControlled = (attrs) => {
|
|
2483
|
+
return (attrs.minValue !== undefined || attrs.maxValue !== undefined) && typeof attrs.oninput === 'function';
|
|
2484
|
+
};
|
|
2486
2485
|
const initRangeState = (state, attrs) => {
|
|
2487
|
-
const { min = 0, max = 100, value, minValue, maxValue } = attrs;
|
|
2486
|
+
const { min = 0, max = 100, value, minValue, maxValue, defaultValue } = attrs;
|
|
2488
2487
|
// Initialize single range value
|
|
2489
|
-
if (
|
|
2490
|
-
|
|
2488
|
+
if (isControlled(attrs)) {
|
|
2489
|
+
// Always use value from props in controlled mode
|
|
2490
|
+
state.singleValue = value !== undefined ? value : min;
|
|
2491
|
+
}
|
|
2492
|
+
else {
|
|
2493
|
+
// Use internal state for uncontrolled mode
|
|
2491
2494
|
if (state.singleValue === undefined) {
|
|
2492
|
-
state.singleValue =
|
|
2495
|
+
state.singleValue = defaultValue !== undefined ? defaultValue : value !== undefined ? value : min;
|
|
2493
2496
|
}
|
|
2494
|
-
|
|
2497
|
+
// Only update internal state if props changed and user hasn't interacted
|
|
2498
|
+
if (state.lastValue !== value && !state.hasUserInteracted && value !== undefined) {
|
|
2495
2499
|
state.singleValue = value;
|
|
2496
2500
|
state.lastValue = value;
|
|
2497
2501
|
}
|
|
2498
2502
|
}
|
|
2499
|
-
else if (state.singleValue === undefined) {
|
|
2500
|
-
state.singleValue = min;
|
|
2501
|
-
}
|
|
2502
2503
|
// Initialize range values
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
state.
|
|
2507
|
-
state.rangeMaxValue = currentMaxValue;
|
|
2504
|
+
if (isRangeControlled(attrs)) {
|
|
2505
|
+
// Always use values from props in controlled mode
|
|
2506
|
+
state.rangeMinValue = minValue !== undefined ? minValue : min;
|
|
2507
|
+
state.rangeMaxValue = maxValue !== undefined ? maxValue : max;
|
|
2508
2508
|
}
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
state.rangeMaxValue
|
|
2514
|
-
|
|
2515
|
-
|
|
2509
|
+
else {
|
|
2510
|
+
// Use internal state for uncontrolled mode
|
|
2511
|
+
const currentMinValue = minValue !== undefined ? minValue : min;
|
|
2512
|
+
const currentMaxValue = maxValue !== undefined ? maxValue : max;
|
|
2513
|
+
if (state.rangeMinValue === undefined || state.rangeMaxValue === undefined) {
|
|
2514
|
+
state.rangeMinValue = currentMinValue;
|
|
2515
|
+
state.rangeMaxValue = currentMaxValue;
|
|
2516
|
+
}
|
|
2517
|
+
if (!state.hasUserInteracted &&
|
|
2518
|
+
((minValue !== undefined && state.lastMinValue !== minValue) ||
|
|
2519
|
+
(maxValue !== undefined && state.lastMaxValue !== maxValue))) {
|
|
2520
|
+
state.rangeMinValue = currentMinValue;
|
|
2521
|
+
state.rangeMaxValue = currentMaxValue;
|
|
2522
|
+
state.lastMinValue = minValue;
|
|
2523
|
+
state.lastMaxValue = maxValue;
|
|
2524
|
+
}
|
|
2516
2525
|
}
|
|
2517
2526
|
// Initialize active thumb if not set
|
|
2518
2527
|
if (state.activeThumb === null) {
|
|
@@ -2529,15 +2538,18 @@ const updateRangeValues = (minValue, maxValue, attrs, state, immediate) => {
|
|
|
2529
2538
|
minValue = maxValue;
|
|
2530
2539
|
if (maxValue < minValue)
|
|
2531
2540
|
maxValue = minValue;
|
|
2532
|
-
state
|
|
2533
|
-
|
|
2541
|
+
// Only update internal state for uncontrolled mode
|
|
2542
|
+
if (!isRangeControlled(attrs)) {
|
|
2543
|
+
state.rangeMinValue = minValue;
|
|
2544
|
+
state.rangeMaxValue = maxValue;
|
|
2545
|
+
}
|
|
2534
2546
|
state.hasUserInteracted = true;
|
|
2535
|
-
// Call
|
|
2547
|
+
// Call appropriate handler based on interaction type, not control mode
|
|
2536
2548
|
if (immediate && attrs.oninput) {
|
|
2537
|
-
attrs.oninput(minValue, maxValue);
|
|
2549
|
+
attrs.oninput(minValue, maxValue); // Immediate feedback during drag
|
|
2538
2550
|
}
|
|
2539
|
-
|
|
2540
|
-
attrs.onchange(minValue, maxValue);
|
|
2551
|
+
if (!immediate && attrs.onchange) {
|
|
2552
|
+
attrs.onchange(minValue, maxValue); // Final value on interaction end (blur/mouseup)
|
|
2541
2553
|
}
|
|
2542
2554
|
};
|
|
2543
2555
|
// Single Range Slider Component
|
|
@@ -2571,19 +2583,24 @@ const SingleRangeSlider = {
|
|
|
2571
2583
|
: tooltipPos
|
|
2572
2584
|
: tooltipPos;
|
|
2573
2585
|
const updateSingleValue = (newValue, immediate = false) => {
|
|
2574
|
-
state
|
|
2586
|
+
// Only update internal state for uncontrolled mode
|
|
2587
|
+
if (!isControlled(attrs)) {
|
|
2588
|
+
state.singleValue = newValue;
|
|
2589
|
+
}
|
|
2575
2590
|
state.hasUserInteracted = true;
|
|
2591
|
+
// Call appropriate handler based on interaction type, not control mode
|
|
2576
2592
|
if (immediate && oninput) {
|
|
2577
|
-
oninput(newValue);
|
|
2593
|
+
oninput(newValue); // Immediate feedback during drag
|
|
2578
2594
|
}
|
|
2579
|
-
|
|
2580
|
-
onchange(newValue);
|
|
2595
|
+
if (!immediate && onchange) {
|
|
2596
|
+
onchange(newValue); // Final value on interaction end (blur/mouseup)
|
|
2581
2597
|
}
|
|
2582
2598
|
};
|
|
2583
2599
|
const handleMouseDown = (e) => {
|
|
2584
2600
|
if (disabled)
|
|
2585
2601
|
return;
|
|
2586
2602
|
e.preventDefault();
|
|
2603
|
+
e.stopPropagation();
|
|
2587
2604
|
state.isDragging = true;
|
|
2588
2605
|
if (finalValueDisplay === 'auto') {
|
|
2589
2606
|
m.redraw();
|
|
@@ -2658,6 +2675,11 @@ const SingleRangeSlider = {
|
|
|
2658
2675
|
updateSingleValue(newValue, false);
|
|
2659
2676
|
}
|
|
2660
2677
|
},
|
|
2678
|
+
onblur: () => {
|
|
2679
|
+
if (disabled || !onchange)
|
|
2680
|
+
return;
|
|
2681
|
+
onchange(state.singleValue);
|
|
2682
|
+
},
|
|
2661
2683
|
}, [
|
|
2662
2684
|
m(`.track.${orientation}`),
|
|
2663
2685
|
m(`.range-progress.${orientation}`, { style: progressStyle }),
|
|
@@ -2716,6 +2738,7 @@ const DoubleRangeSlider = {
|
|
|
2716
2738
|
if (disabled)
|
|
2717
2739
|
return;
|
|
2718
2740
|
e.preventDefault();
|
|
2741
|
+
e.stopPropagation();
|
|
2719
2742
|
state.isDragging = true;
|
|
2720
2743
|
state.activeThumb = thumb;
|
|
2721
2744
|
if (finalValueDisplay === 'auto') {
|
|
@@ -2806,6 +2829,11 @@ const DoubleRangeSlider = {
|
|
|
2806
2829
|
maxThumb.focus();
|
|
2807
2830
|
}
|
|
2808
2831
|
},
|
|
2832
|
+
onblur: () => {
|
|
2833
|
+
if (disabled || !attrs.onchange)
|
|
2834
|
+
return;
|
|
2835
|
+
attrs.onchange(state.rangeMinValue, state.rangeMaxValue);
|
|
2836
|
+
},
|
|
2809
2837
|
}, [
|
|
2810
2838
|
m(`.track.${orientation}`),
|
|
2811
2839
|
m(`.range.${orientation}`, { style: rangeStyle }),
|
|
@@ -3011,13 +3039,13 @@ const TextArea = () => {
|
|
|
3011
3039
|
overflowWrap: 'break-word',
|
|
3012
3040
|
},
|
|
3013
3041
|
oncreate: ({ dom }) => {
|
|
3014
|
-
const hiddenDiv = state.hiddenDiv = dom;
|
|
3042
|
+
const hiddenDiv = (state.hiddenDiv = dom);
|
|
3015
3043
|
if (state.textarea) {
|
|
3016
3044
|
updateHeight(state.textarea, hiddenDiv);
|
|
3017
3045
|
}
|
|
3018
3046
|
},
|
|
3019
3047
|
onupdate: ({ dom }) => {
|
|
3020
|
-
const hiddenDiv = state.hiddenDiv = dom;
|
|
3048
|
+
const hiddenDiv = (state.hiddenDiv = dom);
|
|
3021
3049
|
if (state.textarea) {
|
|
3022
3050
|
updateHeight(state.textarea, hiddenDiv);
|
|
3023
3051
|
}
|
|
@@ -3130,8 +3158,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3130
3158
|
isDragging: false,
|
|
3131
3159
|
activeThumb: null,
|
|
3132
3160
|
};
|
|
3133
|
-
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' &&
|
|
3134
|
-
(typeof attrs.oninput === 'function' || typeof attrs.onchange === 'function');
|
|
3161
|
+
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' && typeof attrs.oninput === 'function';
|
|
3135
3162
|
const getValue = (target) => {
|
|
3136
3163
|
const val = target.value;
|
|
3137
3164
|
return (val ? (type === 'number' || type === 'range' ? +val : val) : val);
|
|
@@ -3181,7 +3208,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3181
3208
|
const isNonInteractive = attrs.readonly || attrs.disabled;
|
|
3182
3209
|
// Warn developer for improper controlled usage
|
|
3183
3210
|
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
3184
|
-
console.warn(`${type} input received 'value' prop without 'oninput'
|
|
3211
|
+
console.warn(`${type} input with label '${attrs.label}' received 'value' prop without 'oninput' handler. ` +
|
|
3185
3212
|
`Use 'defaultValue' for uncontrolled components or add an event handler for controlled components.`);
|
|
3186
3213
|
}
|
|
3187
3214
|
// Initialize internal value if not in controlled mode
|
|
@@ -4208,14 +4235,12 @@ const Dropdown = () => {
|
|
|
4208
4235
|
inputRef: null,
|
|
4209
4236
|
dropdownRef: null,
|
|
4210
4237
|
internalCheckedId: undefined,
|
|
4238
|
+
isInsideModal: false,
|
|
4211
4239
|
};
|
|
4212
4240
|
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
4213
|
-
const closeDropdown = (
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
state.isOpen = false;
|
|
4217
|
-
m.redraw();
|
|
4218
|
-
}
|
|
4241
|
+
const closeDropdown = () => {
|
|
4242
|
+
state.isOpen = false;
|
|
4243
|
+
m.redraw(); // Needed to remove the dropdown options list (potentially added to document root)
|
|
4219
4244
|
};
|
|
4220
4245
|
const handleKeyDown = (e, items) => {
|
|
4221
4246
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -4260,6 +4285,83 @@ const Dropdown = () => {
|
|
|
4260
4285
|
return undefined;
|
|
4261
4286
|
}
|
|
4262
4287
|
};
|
|
4288
|
+
const getPortalStyles = (inputRef) => {
|
|
4289
|
+
if (!inputRef)
|
|
4290
|
+
return {};
|
|
4291
|
+
const rect = inputRef.getBoundingClientRect();
|
|
4292
|
+
const viewportHeight = window.innerHeight;
|
|
4293
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
4294
|
+
const spaceAbove = rect.top;
|
|
4295
|
+
// Choose whether to show above or below based on available space
|
|
4296
|
+
const showAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
|
|
4297
|
+
return {
|
|
4298
|
+
position: 'fixed',
|
|
4299
|
+
top: showAbove ? 'auto' : `${rect.bottom}px`,
|
|
4300
|
+
bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
|
|
4301
|
+
left: `${rect.left}px`,
|
|
4302
|
+
width: `${rect.width}px`,
|
|
4303
|
+
zIndex: 10000,
|
|
4304
|
+
maxHeight: showAbove ? `${spaceAbove - 20}px` : `${spaceBelow - 20}px`,
|
|
4305
|
+
overflow: 'auto',
|
|
4306
|
+
display: 'block',
|
|
4307
|
+
opacity: 1,
|
|
4308
|
+
};
|
|
4309
|
+
};
|
|
4310
|
+
const updatePortalDropdown = (items, selectedLabel, onSelectItem) => {
|
|
4311
|
+
if (!state.isInsideModal)
|
|
4312
|
+
return;
|
|
4313
|
+
// Clean up existing portal
|
|
4314
|
+
const existingPortal = document.getElementById(`${state.id}-dropdown`);
|
|
4315
|
+
if (existingPortal) {
|
|
4316
|
+
existingPortal.remove();
|
|
4317
|
+
}
|
|
4318
|
+
if (!state.isOpen || !state.inputRef)
|
|
4319
|
+
return;
|
|
4320
|
+
// Create portal element
|
|
4321
|
+
const portalElement = document.createElement('div');
|
|
4322
|
+
portalElement.id = `${state.id}-dropdown`;
|
|
4323
|
+
document.body.appendChild(portalElement);
|
|
4324
|
+
// Create dropdown content
|
|
4325
|
+
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
4326
|
+
const dropdownContent = items.map((item) => {
|
|
4327
|
+
if (item.divider) {
|
|
4328
|
+
return m('li.divider');
|
|
4329
|
+
}
|
|
4330
|
+
const itemIndex = availableItems.indexOf(item);
|
|
4331
|
+
const isSelected = selectedLabel === item.label;
|
|
4332
|
+
const isFocused = state.focusedIndex === itemIndex;
|
|
4333
|
+
return m('li', {
|
|
4334
|
+
class: `${isSelected ? 'selected' : ''} ${isFocused ? 'focused' : ''}${item.disabled ? ' disabled' : ''}`,
|
|
4335
|
+
onclick: item.disabled ? undefined : () => onSelectItem(item),
|
|
4336
|
+
}, m('span', {
|
|
4337
|
+
style: {
|
|
4338
|
+
display: 'flex',
|
|
4339
|
+
alignItems: 'center',
|
|
4340
|
+
padding: '14px 16px',
|
|
4341
|
+
},
|
|
4342
|
+
}, [
|
|
4343
|
+
item.iconName
|
|
4344
|
+
? m('i.material-icons', {
|
|
4345
|
+
style: { marginRight: '32px' },
|
|
4346
|
+
}, item.iconName)
|
|
4347
|
+
: undefined,
|
|
4348
|
+
item.label,
|
|
4349
|
+
]));
|
|
4350
|
+
});
|
|
4351
|
+
// Create dropdown with proper positioning
|
|
4352
|
+
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
4353
|
+
tabindex: 0,
|
|
4354
|
+
style: getPortalStyles(state.inputRef),
|
|
4355
|
+
oncreate: ({ dom }) => {
|
|
4356
|
+
state.dropdownRef = dom;
|
|
4357
|
+
},
|
|
4358
|
+
onremove: () => {
|
|
4359
|
+
state.dropdownRef = null;
|
|
4360
|
+
},
|
|
4361
|
+
}, dropdownContent);
|
|
4362
|
+
// Render to portal
|
|
4363
|
+
m.render(portalElement, dropdownVnode);
|
|
4364
|
+
};
|
|
4263
4365
|
return {
|
|
4264
4366
|
oninit: ({ attrs }) => {
|
|
4265
4367
|
var _a;
|
|
@@ -4271,9 +4373,18 @@ const Dropdown = () => {
|
|
|
4271
4373
|
// Add global click listener to close dropdown
|
|
4272
4374
|
document.addEventListener('click', closeDropdown);
|
|
4273
4375
|
},
|
|
4376
|
+
oncreate: ({ dom }) => {
|
|
4377
|
+
// Detect if component is inside a modal
|
|
4378
|
+
state.isInsideModal = !!dom.closest('.modal');
|
|
4379
|
+
},
|
|
4274
4380
|
onremove: () => {
|
|
4275
4381
|
// Cleanup global listener
|
|
4276
4382
|
document.removeEventListener('click', closeDropdown);
|
|
4383
|
+
// Cleanup portal
|
|
4384
|
+
const portalElement = document.getElementById(`${state.id}-dropdown`);
|
|
4385
|
+
if (portalElement) {
|
|
4386
|
+
portalElement.remove();
|
|
4387
|
+
}
|
|
4277
4388
|
},
|
|
4278
4389
|
view: ({ attrs }) => {
|
|
4279
4390
|
const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
|
|
@@ -4296,6 +4407,16 @@ const Dropdown = () => {
|
|
|
4296
4407
|
: undefined;
|
|
4297
4408
|
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
4298
4409
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
4410
|
+
// Update portal dropdown when inside modal
|
|
4411
|
+
if (state.isInsideModal) {
|
|
4412
|
+
updatePortalDropdown(items, title, (item) => {
|
|
4413
|
+
if (item.id) {
|
|
4414
|
+
state.isOpen = false;
|
|
4415
|
+
state.focusedIndex = -1;
|
|
4416
|
+
handleSelection(item.id);
|
|
4417
|
+
}
|
|
4418
|
+
});
|
|
4419
|
+
}
|
|
4299
4420
|
return m('.dropdown-wrapper.input-field', { className, key, style }, [
|
|
4300
4421
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
4301
4422
|
m(HelperText, { helperText }),
|
|
@@ -4329,8 +4450,9 @@ const Dropdown = () => {
|
|
|
4329
4450
|
}
|
|
4330
4451
|
},
|
|
4331
4452
|
}),
|
|
4332
|
-
// Dropdown Menu
|
|
4453
|
+
// Dropdown Menu - render inline only when NOT inside modal
|
|
4333
4454
|
state.isOpen &&
|
|
4455
|
+
!state.isInsideModal &&
|
|
4334
4456
|
m('ul.dropdown-content.select-dropdown', {
|
|
4335
4457
|
tabindex: 0,
|
|
4336
4458
|
role: 'listbox',
|
|
@@ -4780,7 +4902,7 @@ const ModalPanel = () => {
|
|
|
4780
4902
|
maxWidth: '75%',
|
|
4781
4903
|
borderRadius: '4px',
|
|
4782
4904
|
})), { 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)' }),
|
|
4783
|
-
onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
|
|
4905
|
+
// onclick: (e: Event) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
|
|
4784
4906
|
}, [
|
|
4785
4907
|
// Close button
|
|
4786
4908
|
showCloseButton &&
|
|
@@ -5876,6 +5998,7 @@ const Select = () => {
|
|
|
5876
5998
|
dropdownRef: null,
|
|
5877
5999
|
internalSelectedIds: [],
|
|
5878
6000
|
isInsideModal: false,
|
|
6001
|
+
isMultiple: false,
|
|
5879
6002
|
};
|
|
5880
6003
|
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5881
6004
|
const isSelected = (id, selectedIds) => {
|
|
@@ -5958,10 +6081,16 @@ const Select = () => {
|
|
|
5958
6081
|
}
|
|
5959
6082
|
};
|
|
5960
6083
|
const closeDropdown = (e) => {
|
|
6084
|
+
if (!state.isMultiple) {
|
|
6085
|
+
state.isOpen = false;
|
|
6086
|
+
return;
|
|
6087
|
+
}
|
|
5961
6088
|
const target = e.target;
|
|
5962
|
-
|
|
6089
|
+
// When inside modal, check both the select component AND the portaled dropdown
|
|
6090
|
+
const isClickInsideSelect = target.closest('.input-field.select-space');
|
|
6091
|
+
const isClickInsidePortalDropdown = state.isInsideModal && state.dropdownRef && (state.dropdownRef.contains(target) || target === state.dropdownRef);
|
|
6092
|
+
if (!isClickInsideSelect && !isClickInsidePortalDropdown) {
|
|
5963
6093
|
state.isOpen = false;
|
|
5964
|
-
m.redraw();
|
|
5965
6094
|
}
|
|
5966
6095
|
};
|
|
5967
6096
|
const getPortalStyles = (inputRef) => {
|
|
@@ -5969,13 +6098,21 @@ const Select = () => {
|
|
|
5969
6098
|
return {};
|
|
5970
6099
|
const rect = inputRef.getBoundingClientRect();
|
|
5971
6100
|
const viewportHeight = window.innerHeight;
|
|
6101
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
6102
|
+
const spaceAbove = rect.top;
|
|
6103
|
+
// Choose whether to show above or below based on available space
|
|
6104
|
+
const showAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
|
|
5972
6105
|
return {
|
|
5973
6106
|
position: 'fixed',
|
|
5974
|
-
top: `${rect.bottom}px`,
|
|
6107
|
+
top: showAbove ? 'auto' : `${rect.bottom}px`,
|
|
6108
|
+
bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
|
|
5975
6109
|
left: `${rect.left}px`,
|
|
5976
6110
|
width: `${rect.width}px`,
|
|
5977
6111
|
zIndex: 10000, // Higher than modal z-index
|
|
5978
|
-
maxHeight: `${
|
|
6112
|
+
maxHeight: showAbove ? `${spaceAbove - 20}px` : `${spaceBelow - 20}px`, // Leave 20px margin
|
|
6113
|
+
overflow: 'auto',
|
|
6114
|
+
display: 'block',
|
|
6115
|
+
opacity: 1,
|
|
5979
6116
|
};
|
|
5980
6117
|
};
|
|
5981
6118
|
const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
|
|
@@ -5983,15 +6120,10 @@ const Select = () => {
|
|
|
5983
6120
|
// Render ungrouped options first
|
|
5984
6121
|
attrs.options
|
|
5985
6122
|
.filter((option) => !option.group)
|
|
5986
|
-
.map((option) => m('li', Object.assign({
|
|
5987
|
-
? 'disabled'
|
|
5988
|
-
: state.focusedIndex === attrs.options.indexOf(option)
|
|
5989
|
-
? 'focused'
|
|
5990
|
-
: '' }, (option.disabled
|
|
6123
|
+
.map((option) => m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === attrs.options.indexOf(option) ? 'focused' : '' }, (option.disabled
|
|
5991
6124
|
? {}
|
|
5992
6125
|
: {
|
|
5993
|
-
onclick: (
|
|
5994
|
-
e.stopPropagation();
|
|
6126
|
+
onclick: () => {
|
|
5995
6127
|
toggleOption(option.id, multiple, attrs);
|
|
5996
6128
|
},
|
|
5997
6129
|
})), [
|
|
@@ -6021,8 +6153,8 @@ const Select = () => {
|
|
|
6021
6153
|
return groups;
|
|
6022
6154
|
}, {}))
|
|
6023
6155
|
.map(([groupName, groupOptions]) => [
|
|
6024
|
-
m('li.optgroup', {
|
|
6025
|
-
...groupOptions.map((option) => m('li', Object.assign({
|
|
6156
|
+
m('li.optgroup', { tabindex: 0 }, m('span', groupName)),
|
|
6157
|
+
...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
|
|
6026
6158
|
? {}
|
|
6027
6159
|
: {
|
|
6028
6160
|
onclick: (e) => {
|
|
@@ -6049,23 +6181,20 @@ const Select = () => {
|
|
|
6049
6181
|
.reduce((acc, val) => acc.concat(val), []),
|
|
6050
6182
|
];
|
|
6051
6183
|
const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
|
|
6052
|
-
var _a;
|
|
6053
6184
|
if (!state.isInsideModal)
|
|
6054
6185
|
return;
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
m.render(portalElement, []);
|
|
6060
|
-
(_a = portalElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(portalElement);
|
|
6061
|
-
}
|
|
6062
|
-
return;
|
|
6063
|
-
}
|
|
6064
|
-
if (!portalElement) {
|
|
6065
|
-
portalElement = document.createElement('div');
|
|
6066
|
-
portalElement.id = state.dropdownId;
|
|
6067
|
-
document.body.appendChild(portalElement);
|
|
6186
|
+
// Clean up existing portal
|
|
6187
|
+
const existingPortal = document.getElementById(state.dropdownId);
|
|
6188
|
+
if (existingPortal) {
|
|
6189
|
+
existingPortal.remove();
|
|
6068
6190
|
}
|
|
6191
|
+
if (!state.isOpen || !state.inputRef)
|
|
6192
|
+
return;
|
|
6193
|
+
// Create portal element
|
|
6194
|
+
const portalElement = document.createElement('div');
|
|
6195
|
+
portalElement.id = state.dropdownId;
|
|
6196
|
+
document.body.appendChild(portalElement);
|
|
6197
|
+
// Create dropdown with proper positioning
|
|
6069
6198
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
6070
6199
|
tabindex: 0,
|
|
6071
6200
|
style: getPortalStyles(state.inputRef),
|
|
@@ -6076,6 +6205,7 @@ const Select = () => {
|
|
|
6076
6205
|
state.dropdownRef = null;
|
|
6077
6206
|
},
|
|
6078
6207
|
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
|
|
6208
|
+
// Render to portal
|
|
6079
6209
|
m.render(portalElement, dropdownVnode);
|
|
6080
6210
|
};
|
|
6081
6211
|
return {
|
|
@@ -6118,7 +6248,8 @@ const Select = () => {
|
|
|
6118
6248
|
view: ({ attrs }) => {
|
|
6119
6249
|
var _a;
|
|
6120
6250
|
const controlled = isControlled(attrs);
|
|
6121
|
-
const { disabled } = attrs;
|
|
6251
|
+
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, disabled, } = attrs;
|
|
6252
|
+
state.isMultiple = multiple;
|
|
6122
6253
|
// Get selected IDs from props or internal state
|
|
6123
6254
|
let selectedIds;
|
|
6124
6255
|
if (controlled) {
|
|
@@ -6134,7 +6265,6 @@ const Select = () => {
|
|
|
6134
6265
|
// Interactive uncontrolled: use internal state
|
|
6135
6266
|
selectedIds = state.internalSelectedIds;
|
|
6136
6267
|
}
|
|
6137
|
-
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
|
|
6138
6268
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
6139
6269
|
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
6140
6270
|
// Update portal dropdown when inside modal
|
|
@@ -6171,7 +6301,8 @@ const Select = () => {
|
|
|
6171
6301
|
},
|
|
6172
6302
|
}),
|
|
6173
6303
|
// Dropdown Menu - render inline only when NOT inside modal
|
|
6174
|
-
state.isOpen &&
|
|
6304
|
+
state.isOpen &&
|
|
6305
|
+
!state.isInsideModal &&
|
|
6175
6306
|
m('ul.dropdown-content.select-dropdown', {
|
|
6176
6307
|
tabindex: 0,
|
|
6177
6308
|
oncreate: ({ dom }) => {
|
|
@@ -6329,7 +6460,6 @@ const Tabs = () => {
|
|
|
6329
6460
|
}
|
|
6330
6461
|
state.isDragging = false;
|
|
6331
6462
|
state.translateX = 0;
|
|
6332
|
-
// m.redraw();
|
|
6333
6463
|
};
|
|
6334
6464
|
/** Initialize active tab - selectedTabId takes precedence, next active property or first available tab */
|
|
6335
6465
|
const setActiveTabId = (anchoredTabs, selectedTabId) => {
|
|
@@ -6356,7 +6486,6 @@ const Tabs = () => {
|
|
|
6356
6486
|
},
|
|
6357
6487
|
oncreate: () => {
|
|
6358
6488
|
updateIndicator();
|
|
6359
|
-
m.redraw();
|
|
6360
6489
|
},
|
|
6361
6490
|
view: ({ attrs }) => {
|
|
6362
6491
|
const { tabWidth, tabs, className, style, swipeable = false } = attrs;
|
|
@@ -6490,7 +6619,6 @@ const SearchSelect = () => {
|
|
|
6490
6619
|
else {
|
|
6491
6620
|
// Click outside, close dropdown
|
|
6492
6621
|
state.isOpen = false;
|
|
6493
|
-
m.redraw();
|
|
6494
6622
|
}
|
|
6495
6623
|
};
|
|
6496
6624
|
// Handle keyboard navigation
|
|
@@ -6725,9 +6853,7 @@ const SearchSelect = () => {
|
|
|
6725
6853
|
]),
|
|
6726
6854
|
// No options found message or list of options
|
|
6727
6855
|
...(filteredOptions.length === 0 && !showAddNew
|
|
6728
|
-
? [
|
|
6729
|
-
m('li.search-select-no-options', texts.noOptionsFound),
|
|
6730
|
-
]
|
|
6856
|
+
? [m('li.search-select-no-options', texts.noOptionsFound)]
|
|
6731
6857
|
: []),
|
|
6732
6858
|
// Add new option item
|
|
6733
6859
|
...(showAddNew
|