mithril-materialized 3.3.6 → 3.3.8
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/dist/index.esm.js +151 -80
- package/dist/index.js +151 -80
- package/dist/index.umd.js +151 -80
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -2811,6 +2811,7 @@ const TextArea = () => {
|
|
|
2811
2811
|
height: undefined,
|
|
2812
2812
|
active: false,
|
|
2813
2813
|
textarea: undefined,
|
|
2814
|
+
hiddenDiv: undefined,
|
|
2814
2815
|
internalValue: '',
|
|
2815
2816
|
};
|
|
2816
2817
|
const updateHeight = (textarea, hiddenDiv) => {
|
|
@@ -2907,13 +2908,13 @@ const TextArea = () => {
|
|
|
2907
2908
|
overflowWrap: 'break-word',
|
|
2908
2909
|
},
|
|
2909
2910
|
oncreate: ({ dom }) => {
|
|
2910
|
-
const hiddenDiv = dom;
|
|
2911
|
+
const hiddenDiv = state.hiddenDiv = dom;
|
|
2911
2912
|
if (state.textarea) {
|
|
2912
2913
|
updateHeight(state.textarea, hiddenDiv);
|
|
2913
2914
|
}
|
|
2914
2915
|
},
|
|
2915
2916
|
onupdate: ({ dom }) => {
|
|
2916
|
-
const hiddenDiv = dom;
|
|
2917
|
+
const hiddenDiv = state.hiddenDiv = dom;
|
|
2917
2918
|
if (state.textarea) {
|
|
2918
2919
|
updateHeight(state.textarea, hiddenDiv);
|
|
2919
2920
|
}
|
|
@@ -2938,7 +2939,10 @@ const TextArea = () => {
|
|
|
2938
2939
|
const textarea = dom;
|
|
2939
2940
|
if (state.height)
|
|
2940
2941
|
textarea.style.height = state.height;
|
|
2941
|
-
//
|
|
2942
|
+
// Trigger height recalculation when value changes programmatically
|
|
2943
|
+
if (state.hiddenDiv) {
|
|
2944
|
+
updateHeight(textarea, state.hiddenDiv);
|
|
2945
|
+
}
|
|
2942
2946
|
}, onfocus: () => {
|
|
2943
2947
|
state.active = true;
|
|
2944
2948
|
}, oninput: (e) => {
|
|
@@ -3098,9 +3102,6 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3098
3102
|
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate, canClear } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate", "canClear"]);
|
|
3099
3103
|
// const attributes = toAttrs(params);
|
|
3100
3104
|
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
3101
|
-
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
3102
|
-
? true
|
|
3103
|
-
: false;
|
|
3104
3105
|
// Special rendering for minmax range sliders
|
|
3105
3106
|
if (type === 'range' && (attrs.minmax || attrs.valueDisplay)) {
|
|
3106
3107
|
return m(attrs.minmax ? DoubleRangeSlider : SingleRangeSlider, Object.assign(Object.assign({}, attrs), { state,
|
|
@@ -3121,12 +3122,15 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3121
3122
|
}
|
|
3122
3123
|
else if (isNonInteractive) {
|
|
3123
3124
|
// Non-interactive components: prefer defaultValue, fallback to value
|
|
3124
|
-
value = ((
|
|
3125
|
+
value = ((_b = (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value) !== null && _b !== void 0 ? _b : (isNumeric ? 0 : ''));
|
|
3125
3126
|
}
|
|
3126
3127
|
else {
|
|
3127
3128
|
// Interactive uncontrolled: use internal state
|
|
3128
|
-
value = ((
|
|
3129
|
+
value = ((_d = (_c = state.internalValue) !== null && _c !== void 0 ? _c : attrs.defaultValue) !== null && _d !== void 0 ? _d : (isNumeric ? 0 : ''));
|
|
3129
3130
|
}
|
|
3131
|
+
const isActive = state.active || ((_e = state.inputElement) === null || _e === void 0 ? void 0 : _e.value) || value || placeholder || type === 'color' || type === 'range'
|
|
3132
|
+
? true
|
|
3133
|
+
: false;
|
|
3130
3134
|
const rangeType = type === 'range' && !attrs.minmax;
|
|
3131
3135
|
return m('.input-field', { className: cn, style }, [
|
|
3132
3136
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
@@ -5752,11 +5756,13 @@ const RadioButtons = () => {
|
|
|
5752
5756
|
const Select = () => {
|
|
5753
5757
|
const state = {
|
|
5754
5758
|
id: '',
|
|
5759
|
+
dropdownId: '',
|
|
5755
5760
|
isOpen: false,
|
|
5756
5761
|
focusedIndex: -1,
|
|
5757
5762
|
inputRef: null,
|
|
5758
5763
|
dropdownRef: null,
|
|
5759
5764
|
internalSelectedIds: [],
|
|
5765
|
+
isInsideModal: false,
|
|
5760
5766
|
};
|
|
5761
5767
|
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5762
5768
|
const isSelected = (id, selectedIds) => {
|
|
@@ -5845,9 +5851,124 @@ const Select = () => {
|
|
|
5845
5851
|
m.redraw();
|
|
5846
5852
|
}
|
|
5847
5853
|
};
|
|
5854
|
+
const getPortalStyles = (inputRef) => {
|
|
5855
|
+
if (!inputRef)
|
|
5856
|
+
return {};
|
|
5857
|
+
const rect = inputRef.getBoundingClientRect();
|
|
5858
|
+
const viewportHeight = window.innerHeight;
|
|
5859
|
+
return {
|
|
5860
|
+
position: 'fixed',
|
|
5861
|
+
top: `${rect.bottom}px`,
|
|
5862
|
+
left: `${rect.left}px`,
|
|
5863
|
+
width: `${rect.width}px`,
|
|
5864
|
+
zIndex: 10000, // Higher than modal z-index
|
|
5865
|
+
maxHeight: `${viewportHeight - rect.bottom - 20}px`, // Leave 20px margin from bottom
|
|
5866
|
+
};
|
|
5867
|
+
};
|
|
5868
|
+
const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
|
|
5869
|
+
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5870
|
+
// Render ungrouped options first
|
|
5871
|
+
attrs.options
|
|
5872
|
+
.filter((option) => !option.group)
|
|
5873
|
+
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5874
|
+
? 'disabled'
|
|
5875
|
+
: state.focusedIndex === attrs.options.indexOf(option)
|
|
5876
|
+
? 'focused'
|
|
5877
|
+
: '' }, (option.disabled
|
|
5878
|
+
? {}
|
|
5879
|
+
: {
|
|
5880
|
+
onclick: (e) => {
|
|
5881
|
+
e.stopPropagation();
|
|
5882
|
+
toggleOption(option.id, multiple, attrs);
|
|
5883
|
+
},
|
|
5884
|
+
})), [
|
|
5885
|
+
option.img && m('img', { src: option.img, alt: option.label }),
|
|
5886
|
+
m('span', [
|
|
5887
|
+
multiple
|
|
5888
|
+
? m('label', { for: option.id }, m('input', {
|
|
5889
|
+
id: option.id,
|
|
5890
|
+
type: 'checkbox',
|
|
5891
|
+
checked: selectedIds.includes(option.id),
|
|
5892
|
+
disabled: option.disabled ? true : undefined,
|
|
5893
|
+
onclick: (e) => {
|
|
5894
|
+
e.stopPropagation();
|
|
5895
|
+
},
|
|
5896
|
+
}), m('span', option.label))
|
|
5897
|
+
: m('span', option.label),
|
|
5898
|
+
].filter(Boolean)),
|
|
5899
|
+
])),
|
|
5900
|
+
// Render grouped options
|
|
5901
|
+
Object.entries(attrs.options
|
|
5902
|
+
.filter((option) => option.group)
|
|
5903
|
+
.reduce((groups, option) => {
|
|
5904
|
+
const group = option.group;
|
|
5905
|
+
if (!groups[group])
|
|
5906
|
+
groups[group] = [];
|
|
5907
|
+
groups[group].push(option);
|
|
5908
|
+
return groups;
|
|
5909
|
+
}, {}))
|
|
5910
|
+
.map(([groupName, groupOptions]) => [
|
|
5911
|
+
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5912
|
+
...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === attrs.options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5913
|
+
? {}
|
|
5914
|
+
: {
|
|
5915
|
+
onclick: (e) => {
|
|
5916
|
+
e.stopPropagation();
|
|
5917
|
+
toggleOption(option.id, multiple, attrs);
|
|
5918
|
+
},
|
|
5919
|
+
})), [
|
|
5920
|
+
option.img && m('img', { src: option.img, alt: option.label }),
|
|
5921
|
+
m('span', [
|
|
5922
|
+
multiple
|
|
5923
|
+
? m('label', { for: option.id }, m('input', {
|
|
5924
|
+
id: option.id,
|
|
5925
|
+
type: 'checkbox',
|
|
5926
|
+
checked: selectedIds.includes(option.id),
|
|
5927
|
+
disabled: option.disabled ? true : undefined,
|
|
5928
|
+
onclick: (e) => {
|
|
5929
|
+
e.stopPropagation();
|
|
5930
|
+
},
|
|
5931
|
+
}), m('span', option.label))
|
|
5932
|
+
: m('span', option.label),
|
|
5933
|
+
].filter(Boolean)),
|
|
5934
|
+
])),
|
|
5935
|
+
])
|
|
5936
|
+
.reduce((acc, val) => acc.concat(val), []),
|
|
5937
|
+
];
|
|
5938
|
+
const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
|
|
5939
|
+
var _a;
|
|
5940
|
+
if (!state.isInsideModal)
|
|
5941
|
+
return;
|
|
5942
|
+
let portalElement = document.getElementById(state.dropdownId);
|
|
5943
|
+
if (!state.isOpen) {
|
|
5944
|
+
// Clean up portal when dropdown is closed
|
|
5945
|
+
if (portalElement) {
|
|
5946
|
+
m.render(portalElement, []);
|
|
5947
|
+
(_a = portalElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(portalElement);
|
|
5948
|
+
}
|
|
5949
|
+
return;
|
|
5950
|
+
}
|
|
5951
|
+
if (!portalElement) {
|
|
5952
|
+
portalElement = document.createElement('div');
|
|
5953
|
+
portalElement.id = state.dropdownId;
|
|
5954
|
+
document.body.appendChild(portalElement);
|
|
5955
|
+
}
|
|
5956
|
+
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
5957
|
+
tabindex: 0,
|
|
5958
|
+
style: getPortalStyles(state.inputRef),
|
|
5959
|
+
oncreate: ({ dom }) => {
|
|
5960
|
+
state.dropdownRef = dom;
|
|
5961
|
+
},
|
|
5962
|
+
onremove: () => {
|
|
5963
|
+
state.dropdownRef = null;
|
|
5964
|
+
},
|
|
5965
|
+
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
|
|
5966
|
+
m.render(portalElement, dropdownVnode);
|
|
5967
|
+
};
|
|
5848
5968
|
return {
|
|
5849
5969
|
oninit: ({ attrs }) => {
|
|
5850
5970
|
state.id = attrs.id || uniqueId();
|
|
5971
|
+
state.dropdownId = `${state.id}-dropdown`;
|
|
5851
5972
|
const controlled = isControlled(attrs);
|
|
5852
5973
|
// Warn developer for improper controlled usage
|
|
5853
5974
|
if (attrs.checkedId !== undefined && !controlled && !attrs.disabled) {
|
|
@@ -5866,9 +5987,20 @@ const Select = () => {
|
|
|
5866
5987
|
// Add global click listener to close dropdown
|
|
5867
5988
|
document.addEventListener('click', closeDropdown);
|
|
5868
5989
|
},
|
|
5990
|
+
oncreate: ({ dom }) => {
|
|
5991
|
+
// Detect if component is inside a modal
|
|
5992
|
+
state.isInsideModal = !!dom.closest('.modal');
|
|
5993
|
+
},
|
|
5869
5994
|
onremove: () => {
|
|
5870
5995
|
// Cleanup global listener
|
|
5871
5996
|
document.removeEventListener('click', closeDropdown);
|
|
5997
|
+
// Cleanup portaled dropdown if it exists
|
|
5998
|
+
if (state.isInsideModal && state.dropdownRef) {
|
|
5999
|
+
const portalElement = document.getElementById(state.dropdownId);
|
|
6000
|
+
if (portalElement && portalElement.parentNode) {
|
|
6001
|
+
portalElement.parentNode.removeChild(portalElement);
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
5872
6004
|
},
|
|
5873
6005
|
view: ({ attrs }) => {
|
|
5874
6006
|
var _a;
|
|
@@ -5877,20 +6009,13 @@ const Select = () => {
|
|
|
5877
6009
|
// Get selected IDs from props or internal state
|
|
5878
6010
|
let selectedIds;
|
|
5879
6011
|
if (controlled) {
|
|
5880
|
-
selectedIds =
|
|
5881
|
-
? Array.isArray(attrs.checkedId)
|
|
5882
|
-
? attrs.checkedId
|
|
5883
|
-
: [attrs.checkedId]
|
|
5884
|
-
: [];
|
|
6012
|
+
selectedIds =
|
|
6013
|
+
attrs.checkedId !== undefined ? (Array.isArray(attrs.checkedId) ? attrs.checkedId : [attrs.checkedId]) : [];
|
|
5885
6014
|
}
|
|
5886
6015
|
else if (disabled) {
|
|
5887
6016
|
// Non-interactive components: prefer defaultCheckedId, fallback to checkedId
|
|
5888
6017
|
const fallbackId = (_a = attrs.defaultCheckedId) !== null && _a !== void 0 ? _a : attrs.checkedId;
|
|
5889
|
-
selectedIds = fallbackId !== undefined
|
|
5890
|
-
? Array.isArray(fallbackId)
|
|
5891
|
-
? fallbackId
|
|
5892
|
-
: [fallbackId]
|
|
5893
|
-
: [];
|
|
6018
|
+
selectedIds = fallbackId !== undefined ? (Array.isArray(fallbackId) ? fallbackId : [fallbackId]) : [];
|
|
5894
6019
|
}
|
|
5895
6020
|
else {
|
|
5896
6021
|
// Interactive uncontrolled: use internal state
|
|
@@ -5899,6 +6024,10 @@ const Select = () => {
|
|
|
5899
6024
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
|
|
5900
6025
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5901
6026
|
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
6027
|
+
// Update portal dropdown when inside modal
|
|
6028
|
+
if (state.isInsideModal) {
|
|
6029
|
+
updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
|
|
6030
|
+
}
|
|
5902
6031
|
return m('.input-field.select-space', {
|
|
5903
6032
|
className: finalClassName,
|
|
5904
6033
|
key,
|
|
@@ -5911,6 +6040,7 @@ const Select = () => {
|
|
|
5911
6040
|
tabindex: disabled ? -1 : 0,
|
|
5912
6041
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
5913
6042
|
'aria-haspopup': 'listbox',
|
|
6043
|
+
'aria-controls': state.dropdownId,
|
|
5914
6044
|
role: 'combobox',
|
|
5915
6045
|
}, [
|
|
5916
6046
|
m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
|
|
@@ -5927,8 +6057,8 @@ const Select = () => {
|
|
|
5927
6057
|
}
|
|
5928
6058
|
},
|
|
5929
6059
|
}),
|
|
5930
|
-
// Dropdown Menu
|
|
5931
|
-
state.isOpen &&
|
|
6060
|
+
// Dropdown Menu - render inline only when NOT inside modal
|
|
6061
|
+
state.isOpen && !state.isInsideModal &&
|
|
5932
6062
|
m('ul.dropdown-content.select-dropdown', {
|
|
5933
6063
|
tabindex: 0,
|
|
5934
6064
|
oncreate: ({ dom }) => {
|
|
@@ -5938,66 +6068,7 @@ const Select = () => {
|
|
|
5938
6068
|
state.dropdownRef = null;
|
|
5939
6069
|
},
|
|
5940
6070
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5941
|
-
},
|
|
5942
|
-
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5943
|
-
// Render ungrouped options first
|
|
5944
|
-
options
|
|
5945
|
-
.filter((option) => !option.group)
|
|
5946
|
-
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5947
|
-
? 'disabled'
|
|
5948
|
-
: state.focusedIndex === options.indexOf(option)
|
|
5949
|
-
? 'focused'
|
|
5950
|
-
: '' }, (option.disabled
|
|
5951
|
-
? {}
|
|
5952
|
-
: {
|
|
5953
|
-
onclick: (e) => {
|
|
5954
|
-
e.stopPropagation();
|
|
5955
|
-
toggleOption(option.id, multiple, attrs);
|
|
5956
|
-
},
|
|
5957
|
-
})), m('span', multiple
|
|
5958
|
-
? m('label', { for: option.id }, m('input', {
|
|
5959
|
-
id: option.id,
|
|
5960
|
-
type: 'checkbox',
|
|
5961
|
-
checked: selectedIds.includes(option.id),
|
|
5962
|
-
disabled: option.disabled ? true : undefined,
|
|
5963
|
-
onclick: (e) => {
|
|
5964
|
-
e.stopPropagation();
|
|
5965
|
-
},
|
|
5966
|
-
}), m('span', option.label))
|
|
5967
|
-
: option.label))),
|
|
5968
|
-
// Render grouped options
|
|
5969
|
-
Object.entries(options
|
|
5970
|
-
.filter((option) => option.group)
|
|
5971
|
-
.reduce((groups, option) => {
|
|
5972
|
-
const group = option.group;
|
|
5973
|
-
if (!groups[group])
|
|
5974
|
-
groups[group] = [];
|
|
5975
|
-
groups[group].push(option);
|
|
5976
|
-
return groups;
|
|
5977
|
-
}, {}))
|
|
5978
|
-
.map(([groupName, groupOptions]) => [
|
|
5979
|
-
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5980
|
-
...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5981
|
-
? {}
|
|
5982
|
-
: {
|
|
5983
|
-
onclick: (e) => {
|
|
5984
|
-
e.stopPropagation();
|
|
5985
|
-
toggleOption(option.id, multiple, attrs);
|
|
5986
|
-
},
|
|
5987
|
-
})), m('span', multiple
|
|
5988
|
-
? m('label', { for: option.id }, m('input', {
|
|
5989
|
-
id: option.id,
|
|
5990
|
-
type: 'checkbox',
|
|
5991
|
-
checked: selectedIds.includes(option.id),
|
|
5992
|
-
disabled: option.disabled ? true : undefined,
|
|
5993
|
-
onclick: (e) => {
|
|
5994
|
-
e.stopPropagation();
|
|
5995
|
-
},
|
|
5996
|
-
}), m('span', option.label))
|
|
5997
|
-
: option.label))),
|
|
5998
|
-
])
|
|
5999
|
-
.reduce((acc, val) => acc.concat(val), []),
|
|
6000
|
-
]),
|
|
6071
|
+
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
|
|
6001
6072
|
m(MaterialIcon, {
|
|
6002
6073
|
name: 'caret',
|
|
6003
6074
|
direction: 'down',
|
package/dist/index.js
CHANGED
|
@@ -2813,6 +2813,7 @@ const TextArea = () => {
|
|
|
2813
2813
|
height: undefined,
|
|
2814
2814
|
active: false,
|
|
2815
2815
|
textarea: undefined,
|
|
2816
|
+
hiddenDiv: undefined,
|
|
2816
2817
|
internalValue: '',
|
|
2817
2818
|
};
|
|
2818
2819
|
const updateHeight = (textarea, hiddenDiv) => {
|
|
@@ -2909,13 +2910,13 @@ const TextArea = () => {
|
|
|
2909
2910
|
overflowWrap: 'break-word',
|
|
2910
2911
|
},
|
|
2911
2912
|
oncreate: ({ dom }) => {
|
|
2912
|
-
const hiddenDiv = dom;
|
|
2913
|
+
const hiddenDiv = state.hiddenDiv = dom;
|
|
2913
2914
|
if (state.textarea) {
|
|
2914
2915
|
updateHeight(state.textarea, hiddenDiv);
|
|
2915
2916
|
}
|
|
2916
2917
|
},
|
|
2917
2918
|
onupdate: ({ dom }) => {
|
|
2918
|
-
const hiddenDiv = dom;
|
|
2919
|
+
const hiddenDiv = state.hiddenDiv = dom;
|
|
2919
2920
|
if (state.textarea) {
|
|
2920
2921
|
updateHeight(state.textarea, hiddenDiv);
|
|
2921
2922
|
}
|
|
@@ -2940,7 +2941,10 @@ const TextArea = () => {
|
|
|
2940
2941
|
const textarea = dom;
|
|
2941
2942
|
if (state.height)
|
|
2942
2943
|
textarea.style.height = state.height;
|
|
2943
|
-
//
|
|
2944
|
+
// Trigger height recalculation when value changes programmatically
|
|
2945
|
+
if (state.hiddenDiv) {
|
|
2946
|
+
updateHeight(textarea, state.hiddenDiv);
|
|
2947
|
+
}
|
|
2944
2948
|
}, onfocus: () => {
|
|
2945
2949
|
state.active = true;
|
|
2946
2950
|
}, oninput: (e) => {
|
|
@@ -3100,9 +3104,6 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3100
3104
|
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate, canClear } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate", "canClear"]);
|
|
3101
3105
|
// const attributes = toAttrs(params);
|
|
3102
3106
|
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
3103
|
-
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
3104
|
-
? true
|
|
3105
|
-
: false;
|
|
3106
3107
|
// Special rendering for minmax range sliders
|
|
3107
3108
|
if (type === 'range' && (attrs.minmax || attrs.valueDisplay)) {
|
|
3108
3109
|
return m(attrs.minmax ? DoubleRangeSlider : SingleRangeSlider, Object.assign(Object.assign({}, attrs), { state,
|
|
@@ -3123,12 +3124,15 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3123
3124
|
}
|
|
3124
3125
|
else if (isNonInteractive) {
|
|
3125
3126
|
// Non-interactive components: prefer defaultValue, fallback to value
|
|
3126
|
-
value = ((
|
|
3127
|
+
value = ((_b = (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value) !== null && _b !== void 0 ? _b : (isNumeric ? 0 : ''));
|
|
3127
3128
|
}
|
|
3128
3129
|
else {
|
|
3129
3130
|
// Interactive uncontrolled: use internal state
|
|
3130
|
-
value = ((
|
|
3131
|
+
value = ((_d = (_c = state.internalValue) !== null && _c !== void 0 ? _c : attrs.defaultValue) !== null && _d !== void 0 ? _d : (isNumeric ? 0 : ''));
|
|
3131
3132
|
}
|
|
3133
|
+
const isActive = state.active || ((_e = state.inputElement) === null || _e === void 0 ? void 0 : _e.value) || value || placeholder || type === 'color' || type === 'range'
|
|
3134
|
+
? true
|
|
3135
|
+
: false;
|
|
3132
3136
|
const rangeType = type === 'range' && !attrs.minmax;
|
|
3133
3137
|
return m('.input-field', { className: cn, style }, [
|
|
3134
3138
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
@@ -5754,11 +5758,13 @@ const RadioButtons = () => {
|
|
|
5754
5758
|
const Select = () => {
|
|
5755
5759
|
const state = {
|
|
5756
5760
|
id: '',
|
|
5761
|
+
dropdownId: '',
|
|
5757
5762
|
isOpen: false,
|
|
5758
5763
|
focusedIndex: -1,
|
|
5759
5764
|
inputRef: null,
|
|
5760
5765
|
dropdownRef: null,
|
|
5761
5766
|
internalSelectedIds: [],
|
|
5767
|
+
isInsideModal: false,
|
|
5762
5768
|
};
|
|
5763
5769
|
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5764
5770
|
const isSelected = (id, selectedIds) => {
|
|
@@ -5847,9 +5853,124 @@ const Select = () => {
|
|
|
5847
5853
|
m.redraw();
|
|
5848
5854
|
}
|
|
5849
5855
|
};
|
|
5856
|
+
const getPortalStyles = (inputRef) => {
|
|
5857
|
+
if (!inputRef)
|
|
5858
|
+
return {};
|
|
5859
|
+
const rect = inputRef.getBoundingClientRect();
|
|
5860
|
+
const viewportHeight = window.innerHeight;
|
|
5861
|
+
return {
|
|
5862
|
+
position: 'fixed',
|
|
5863
|
+
top: `${rect.bottom}px`,
|
|
5864
|
+
left: `${rect.left}px`,
|
|
5865
|
+
width: `${rect.width}px`,
|
|
5866
|
+
zIndex: 10000, // Higher than modal z-index
|
|
5867
|
+
maxHeight: `${viewportHeight - rect.bottom - 20}px`, // Leave 20px margin from bottom
|
|
5868
|
+
};
|
|
5869
|
+
};
|
|
5870
|
+
const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
|
|
5871
|
+
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5872
|
+
// Render ungrouped options first
|
|
5873
|
+
attrs.options
|
|
5874
|
+
.filter((option) => !option.group)
|
|
5875
|
+
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5876
|
+
? 'disabled'
|
|
5877
|
+
: state.focusedIndex === attrs.options.indexOf(option)
|
|
5878
|
+
? 'focused'
|
|
5879
|
+
: '' }, (option.disabled
|
|
5880
|
+
? {}
|
|
5881
|
+
: {
|
|
5882
|
+
onclick: (e) => {
|
|
5883
|
+
e.stopPropagation();
|
|
5884
|
+
toggleOption(option.id, multiple, attrs);
|
|
5885
|
+
},
|
|
5886
|
+
})), [
|
|
5887
|
+
option.img && m('img', { src: option.img, alt: option.label }),
|
|
5888
|
+
m('span', [
|
|
5889
|
+
multiple
|
|
5890
|
+
? m('label', { for: option.id }, m('input', {
|
|
5891
|
+
id: option.id,
|
|
5892
|
+
type: 'checkbox',
|
|
5893
|
+
checked: selectedIds.includes(option.id),
|
|
5894
|
+
disabled: option.disabled ? true : undefined,
|
|
5895
|
+
onclick: (e) => {
|
|
5896
|
+
e.stopPropagation();
|
|
5897
|
+
},
|
|
5898
|
+
}), m('span', option.label))
|
|
5899
|
+
: m('span', option.label),
|
|
5900
|
+
].filter(Boolean)),
|
|
5901
|
+
])),
|
|
5902
|
+
// Render grouped options
|
|
5903
|
+
Object.entries(attrs.options
|
|
5904
|
+
.filter((option) => option.group)
|
|
5905
|
+
.reduce((groups, option) => {
|
|
5906
|
+
const group = option.group;
|
|
5907
|
+
if (!groups[group])
|
|
5908
|
+
groups[group] = [];
|
|
5909
|
+
groups[group].push(option);
|
|
5910
|
+
return groups;
|
|
5911
|
+
}, {}))
|
|
5912
|
+
.map(([groupName, groupOptions]) => [
|
|
5913
|
+
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5914
|
+
...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === attrs.options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5915
|
+
? {}
|
|
5916
|
+
: {
|
|
5917
|
+
onclick: (e) => {
|
|
5918
|
+
e.stopPropagation();
|
|
5919
|
+
toggleOption(option.id, multiple, attrs);
|
|
5920
|
+
},
|
|
5921
|
+
})), [
|
|
5922
|
+
option.img && m('img', { src: option.img, alt: option.label }),
|
|
5923
|
+
m('span', [
|
|
5924
|
+
multiple
|
|
5925
|
+
? m('label', { for: option.id }, m('input', {
|
|
5926
|
+
id: option.id,
|
|
5927
|
+
type: 'checkbox',
|
|
5928
|
+
checked: selectedIds.includes(option.id),
|
|
5929
|
+
disabled: option.disabled ? true : undefined,
|
|
5930
|
+
onclick: (e) => {
|
|
5931
|
+
e.stopPropagation();
|
|
5932
|
+
},
|
|
5933
|
+
}), m('span', option.label))
|
|
5934
|
+
: m('span', option.label),
|
|
5935
|
+
].filter(Boolean)),
|
|
5936
|
+
])),
|
|
5937
|
+
])
|
|
5938
|
+
.reduce((acc, val) => acc.concat(val), []),
|
|
5939
|
+
];
|
|
5940
|
+
const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
|
|
5941
|
+
var _a;
|
|
5942
|
+
if (!state.isInsideModal)
|
|
5943
|
+
return;
|
|
5944
|
+
let portalElement = document.getElementById(state.dropdownId);
|
|
5945
|
+
if (!state.isOpen) {
|
|
5946
|
+
// Clean up portal when dropdown is closed
|
|
5947
|
+
if (portalElement) {
|
|
5948
|
+
m.render(portalElement, []);
|
|
5949
|
+
(_a = portalElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(portalElement);
|
|
5950
|
+
}
|
|
5951
|
+
return;
|
|
5952
|
+
}
|
|
5953
|
+
if (!portalElement) {
|
|
5954
|
+
portalElement = document.createElement('div');
|
|
5955
|
+
portalElement.id = state.dropdownId;
|
|
5956
|
+
document.body.appendChild(portalElement);
|
|
5957
|
+
}
|
|
5958
|
+
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
5959
|
+
tabindex: 0,
|
|
5960
|
+
style: getPortalStyles(state.inputRef),
|
|
5961
|
+
oncreate: ({ dom }) => {
|
|
5962
|
+
state.dropdownRef = dom;
|
|
5963
|
+
},
|
|
5964
|
+
onremove: () => {
|
|
5965
|
+
state.dropdownRef = null;
|
|
5966
|
+
},
|
|
5967
|
+
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
|
|
5968
|
+
m.render(portalElement, dropdownVnode);
|
|
5969
|
+
};
|
|
5850
5970
|
return {
|
|
5851
5971
|
oninit: ({ attrs }) => {
|
|
5852
5972
|
state.id = attrs.id || uniqueId();
|
|
5973
|
+
state.dropdownId = `${state.id}-dropdown`;
|
|
5853
5974
|
const controlled = isControlled(attrs);
|
|
5854
5975
|
// Warn developer for improper controlled usage
|
|
5855
5976
|
if (attrs.checkedId !== undefined && !controlled && !attrs.disabled) {
|
|
@@ -5868,9 +5989,20 @@ const Select = () => {
|
|
|
5868
5989
|
// Add global click listener to close dropdown
|
|
5869
5990
|
document.addEventListener('click', closeDropdown);
|
|
5870
5991
|
},
|
|
5992
|
+
oncreate: ({ dom }) => {
|
|
5993
|
+
// Detect if component is inside a modal
|
|
5994
|
+
state.isInsideModal = !!dom.closest('.modal');
|
|
5995
|
+
},
|
|
5871
5996
|
onremove: () => {
|
|
5872
5997
|
// Cleanup global listener
|
|
5873
5998
|
document.removeEventListener('click', closeDropdown);
|
|
5999
|
+
// Cleanup portaled dropdown if it exists
|
|
6000
|
+
if (state.isInsideModal && state.dropdownRef) {
|
|
6001
|
+
const portalElement = document.getElementById(state.dropdownId);
|
|
6002
|
+
if (portalElement && portalElement.parentNode) {
|
|
6003
|
+
portalElement.parentNode.removeChild(portalElement);
|
|
6004
|
+
}
|
|
6005
|
+
}
|
|
5874
6006
|
},
|
|
5875
6007
|
view: ({ attrs }) => {
|
|
5876
6008
|
var _a;
|
|
@@ -5879,20 +6011,13 @@ const Select = () => {
|
|
|
5879
6011
|
// Get selected IDs from props or internal state
|
|
5880
6012
|
let selectedIds;
|
|
5881
6013
|
if (controlled) {
|
|
5882
|
-
selectedIds =
|
|
5883
|
-
? Array.isArray(attrs.checkedId)
|
|
5884
|
-
? attrs.checkedId
|
|
5885
|
-
: [attrs.checkedId]
|
|
5886
|
-
: [];
|
|
6014
|
+
selectedIds =
|
|
6015
|
+
attrs.checkedId !== undefined ? (Array.isArray(attrs.checkedId) ? attrs.checkedId : [attrs.checkedId]) : [];
|
|
5887
6016
|
}
|
|
5888
6017
|
else if (disabled) {
|
|
5889
6018
|
// Non-interactive components: prefer defaultCheckedId, fallback to checkedId
|
|
5890
6019
|
const fallbackId = (_a = attrs.defaultCheckedId) !== null && _a !== void 0 ? _a : attrs.checkedId;
|
|
5891
|
-
selectedIds = fallbackId !== undefined
|
|
5892
|
-
? Array.isArray(fallbackId)
|
|
5893
|
-
? fallbackId
|
|
5894
|
-
: [fallbackId]
|
|
5895
|
-
: [];
|
|
6020
|
+
selectedIds = fallbackId !== undefined ? (Array.isArray(fallbackId) ? fallbackId : [fallbackId]) : [];
|
|
5896
6021
|
}
|
|
5897
6022
|
else {
|
|
5898
6023
|
// Interactive uncontrolled: use internal state
|
|
@@ -5901,6 +6026,10 @@ const Select = () => {
|
|
|
5901
6026
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
|
|
5902
6027
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5903
6028
|
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
6029
|
+
// Update portal dropdown when inside modal
|
|
6030
|
+
if (state.isInsideModal) {
|
|
6031
|
+
updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
|
|
6032
|
+
}
|
|
5904
6033
|
return m('.input-field.select-space', {
|
|
5905
6034
|
className: finalClassName,
|
|
5906
6035
|
key,
|
|
@@ -5913,6 +6042,7 @@ const Select = () => {
|
|
|
5913
6042
|
tabindex: disabled ? -1 : 0,
|
|
5914
6043
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
5915
6044
|
'aria-haspopup': 'listbox',
|
|
6045
|
+
'aria-controls': state.dropdownId,
|
|
5916
6046
|
role: 'combobox',
|
|
5917
6047
|
}, [
|
|
5918
6048
|
m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
|
|
@@ -5929,8 +6059,8 @@ const Select = () => {
|
|
|
5929
6059
|
}
|
|
5930
6060
|
},
|
|
5931
6061
|
}),
|
|
5932
|
-
// Dropdown Menu
|
|
5933
|
-
state.isOpen &&
|
|
6062
|
+
// Dropdown Menu - render inline only when NOT inside modal
|
|
6063
|
+
state.isOpen && !state.isInsideModal &&
|
|
5934
6064
|
m('ul.dropdown-content.select-dropdown', {
|
|
5935
6065
|
tabindex: 0,
|
|
5936
6066
|
oncreate: ({ dom }) => {
|
|
@@ -5940,66 +6070,7 @@ const Select = () => {
|
|
|
5940
6070
|
state.dropdownRef = null;
|
|
5941
6071
|
},
|
|
5942
6072
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5943
|
-
},
|
|
5944
|
-
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5945
|
-
// Render ungrouped options first
|
|
5946
|
-
options
|
|
5947
|
-
.filter((option) => !option.group)
|
|
5948
|
-
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5949
|
-
? 'disabled'
|
|
5950
|
-
: state.focusedIndex === options.indexOf(option)
|
|
5951
|
-
? 'focused'
|
|
5952
|
-
: '' }, (option.disabled
|
|
5953
|
-
? {}
|
|
5954
|
-
: {
|
|
5955
|
-
onclick: (e) => {
|
|
5956
|
-
e.stopPropagation();
|
|
5957
|
-
toggleOption(option.id, multiple, attrs);
|
|
5958
|
-
},
|
|
5959
|
-
})), m('span', multiple
|
|
5960
|
-
? m('label', { for: option.id }, m('input', {
|
|
5961
|
-
id: option.id,
|
|
5962
|
-
type: 'checkbox',
|
|
5963
|
-
checked: selectedIds.includes(option.id),
|
|
5964
|
-
disabled: option.disabled ? true : undefined,
|
|
5965
|
-
onclick: (e) => {
|
|
5966
|
-
e.stopPropagation();
|
|
5967
|
-
},
|
|
5968
|
-
}), m('span', option.label))
|
|
5969
|
-
: option.label))),
|
|
5970
|
-
// Render grouped options
|
|
5971
|
-
Object.entries(options
|
|
5972
|
-
.filter((option) => option.group)
|
|
5973
|
-
.reduce((groups, option) => {
|
|
5974
|
-
const group = option.group;
|
|
5975
|
-
if (!groups[group])
|
|
5976
|
-
groups[group] = [];
|
|
5977
|
-
groups[group].push(option);
|
|
5978
|
-
return groups;
|
|
5979
|
-
}, {}))
|
|
5980
|
-
.map(([groupName, groupOptions]) => [
|
|
5981
|
-
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5982
|
-
...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5983
|
-
? {}
|
|
5984
|
-
: {
|
|
5985
|
-
onclick: (e) => {
|
|
5986
|
-
e.stopPropagation();
|
|
5987
|
-
toggleOption(option.id, multiple, attrs);
|
|
5988
|
-
},
|
|
5989
|
-
})), m('span', multiple
|
|
5990
|
-
? m('label', { for: option.id }, m('input', {
|
|
5991
|
-
id: option.id,
|
|
5992
|
-
type: 'checkbox',
|
|
5993
|
-
checked: selectedIds.includes(option.id),
|
|
5994
|
-
disabled: option.disabled ? true : undefined,
|
|
5995
|
-
onclick: (e) => {
|
|
5996
|
-
e.stopPropagation();
|
|
5997
|
-
},
|
|
5998
|
-
}), m('span', option.label))
|
|
5999
|
-
: option.label))),
|
|
6000
|
-
])
|
|
6001
|
-
.reduce((acc, val) => acc.concat(val), []),
|
|
6002
|
-
]),
|
|
6073
|
+
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
|
|
6003
6074
|
m(MaterialIcon, {
|
|
6004
6075
|
name: 'caret',
|
|
6005
6076
|
direction: 'down',
|
package/dist/index.umd.js
CHANGED
|
@@ -2815,6 +2815,7 @@
|
|
|
2815
2815
|
height: undefined,
|
|
2816
2816
|
active: false,
|
|
2817
2817
|
textarea: undefined,
|
|
2818
|
+
hiddenDiv: undefined,
|
|
2818
2819
|
internalValue: '',
|
|
2819
2820
|
};
|
|
2820
2821
|
const updateHeight = (textarea, hiddenDiv) => {
|
|
@@ -2911,13 +2912,13 @@
|
|
|
2911
2912
|
overflowWrap: 'break-word',
|
|
2912
2913
|
},
|
|
2913
2914
|
oncreate: ({ dom }) => {
|
|
2914
|
-
const hiddenDiv = dom;
|
|
2915
|
+
const hiddenDiv = state.hiddenDiv = dom;
|
|
2915
2916
|
if (state.textarea) {
|
|
2916
2917
|
updateHeight(state.textarea, hiddenDiv);
|
|
2917
2918
|
}
|
|
2918
2919
|
},
|
|
2919
2920
|
onupdate: ({ dom }) => {
|
|
2920
|
-
const hiddenDiv = dom;
|
|
2921
|
+
const hiddenDiv = state.hiddenDiv = dom;
|
|
2921
2922
|
if (state.textarea) {
|
|
2922
2923
|
updateHeight(state.textarea, hiddenDiv);
|
|
2923
2924
|
}
|
|
@@ -2942,7 +2943,10 @@
|
|
|
2942
2943
|
const textarea = dom;
|
|
2943
2944
|
if (state.height)
|
|
2944
2945
|
textarea.style.height = state.height;
|
|
2945
|
-
//
|
|
2946
|
+
// Trigger height recalculation when value changes programmatically
|
|
2947
|
+
if (state.hiddenDiv) {
|
|
2948
|
+
updateHeight(textarea, state.hiddenDiv);
|
|
2949
|
+
}
|
|
2946
2950
|
}, onfocus: () => {
|
|
2947
2951
|
state.active = true;
|
|
2948
2952
|
}, oninput: (e) => {
|
|
@@ -3102,9 +3106,6 @@
|
|
|
3102
3106
|
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate, canClear } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate", "canClear"]);
|
|
3103
3107
|
// const attributes = toAttrs(params);
|
|
3104
3108
|
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
3105
|
-
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
3106
|
-
? true
|
|
3107
|
-
: false;
|
|
3108
3109
|
// Special rendering for minmax range sliders
|
|
3109
3110
|
if (type === 'range' && (attrs.minmax || attrs.valueDisplay)) {
|
|
3110
3111
|
return m(attrs.minmax ? DoubleRangeSlider : SingleRangeSlider, Object.assign(Object.assign({}, attrs), { state,
|
|
@@ -3125,12 +3126,15 @@
|
|
|
3125
3126
|
}
|
|
3126
3127
|
else if (isNonInteractive) {
|
|
3127
3128
|
// Non-interactive components: prefer defaultValue, fallback to value
|
|
3128
|
-
value = ((
|
|
3129
|
+
value = ((_b = (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value) !== null && _b !== void 0 ? _b : (isNumeric ? 0 : ''));
|
|
3129
3130
|
}
|
|
3130
3131
|
else {
|
|
3131
3132
|
// Interactive uncontrolled: use internal state
|
|
3132
|
-
value = ((
|
|
3133
|
+
value = ((_d = (_c = state.internalValue) !== null && _c !== void 0 ? _c : attrs.defaultValue) !== null && _d !== void 0 ? _d : (isNumeric ? 0 : ''));
|
|
3133
3134
|
}
|
|
3135
|
+
const isActive = state.active || ((_e = state.inputElement) === null || _e === void 0 ? void 0 : _e.value) || value || placeholder || type === 'color' || type === 'range'
|
|
3136
|
+
? true
|
|
3137
|
+
: false;
|
|
3134
3138
|
const rangeType = type === 'range' && !attrs.minmax;
|
|
3135
3139
|
return m('.input-field', { className: cn, style }, [
|
|
3136
3140
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
@@ -5756,11 +5760,13 @@
|
|
|
5756
5760
|
const Select = () => {
|
|
5757
5761
|
const state = {
|
|
5758
5762
|
id: '',
|
|
5763
|
+
dropdownId: '',
|
|
5759
5764
|
isOpen: false,
|
|
5760
5765
|
focusedIndex: -1,
|
|
5761
5766
|
inputRef: null,
|
|
5762
5767
|
dropdownRef: null,
|
|
5763
5768
|
internalSelectedIds: [],
|
|
5769
|
+
isInsideModal: false,
|
|
5764
5770
|
};
|
|
5765
5771
|
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5766
5772
|
const isSelected = (id, selectedIds) => {
|
|
@@ -5849,9 +5855,124 @@
|
|
|
5849
5855
|
m.redraw();
|
|
5850
5856
|
}
|
|
5851
5857
|
};
|
|
5858
|
+
const getPortalStyles = (inputRef) => {
|
|
5859
|
+
if (!inputRef)
|
|
5860
|
+
return {};
|
|
5861
|
+
const rect = inputRef.getBoundingClientRect();
|
|
5862
|
+
const viewportHeight = window.innerHeight;
|
|
5863
|
+
return {
|
|
5864
|
+
position: 'fixed',
|
|
5865
|
+
top: `${rect.bottom}px`,
|
|
5866
|
+
left: `${rect.left}px`,
|
|
5867
|
+
width: `${rect.width}px`,
|
|
5868
|
+
zIndex: 10000, // Higher than modal z-index
|
|
5869
|
+
maxHeight: `${viewportHeight - rect.bottom - 20}px`, // Leave 20px margin from bottom
|
|
5870
|
+
};
|
|
5871
|
+
};
|
|
5872
|
+
const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
|
|
5873
|
+
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5874
|
+
// Render ungrouped options first
|
|
5875
|
+
attrs.options
|
|
5876
|
+
.filter((option) => !option.group)
|
|
5877
|
+
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5878
|
+
? 'disabled'
|
|
5879
|
+
: state.focusedIndex === attrs.options.indexOf(option)
|
|
5880
|
+
? 'focused'
|
|
5881
|
+
: '' }, (option.disabled
|
|
5882
|
+
? {}
|
|
5883
|
+
: {
|
|
5884
|
+
onclick: (e) => {
|
|
5885
|
+
e.stopPropagation();
|
|
5886
|
+
toggleOption(option.id, multiple, attrs);
|
|
5887
|
+
},
|
|
5888
|
+
})), [
|
|
5889
|
+
option.img && m('img', { src: option.img, alt: option.label }),
|
|
5890
|
+
m('span', [
|
|
5891
|
+
multiple
|
|
5892
|
+
? m('label', { for: option.id }, m('input', {
|
|
5893
|
+
id: option.id,
|
|
5894
|
+
type: 'checkbox',
|
|
5895
|
+
checked: selectedIds.includes(option.id),
|
|
5896
|
+
disabled: option.disabled ? true : undefined,
|
|
5897
|
+
onclick: (e) => {
|
|
5898
|
+
e.stopPropagation();
|
|
5899
|
+
},
|
|
5900
|
+
}), m('span', option.label))
|
|
5901
|
+
: m('span', option.label),
|
|
5902
|
+
].filter(Boolean)),
|
|
5903
|
+
])),
|
|
5904
|
+
// Render grouped options
|
|
5905
|
+
Object.entries(attrs.options
|
|
5906
|
+
.filter((option) => option.group)
|
|
5907
|
+
.reduce((groups, option) => {
|
|
5908
|
+
const group = option.group;
|
|
5909
|
+
if (!groups[group])
|
|
5910
|
+
groups[group] = [];
|
|
5911
|
+
groups[group].push(option);
|
|
5912
|
+
return groups;
|
|
5913
|
+
}, {}))
|
|
5914
|
+
.map(([groupName, groupOptions]) => [
|
|
5915
|
+
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5916
|
+
...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === attrs.options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5917
|
+
? {}
|
|
5918
|
+
: {
|
|
5919
|
+
onclick: (e) => {
|
|
5920
|
+
e.stopPropagation();
|
|
5921
|
+
toggleOption(option.id, multiple, attrs);
|
|
5922
|
+
},
|
|
5923
|
+
})), [
|
|
5924
|
+
option.img && m('img', { src: option.img, alt: option.label }),
|
|
5925
|
+
m('span', [
|
|
5926
|
+
multiple
|
|
5927
|
+
? m('label', { for: option.id }, m('input', {
|
|
5928
|
+
id: option.id,
|
|
5929
|
+
type: 'checkbox',
|
|
5930
|
+
checked: selectedIds.includes(option.id),
|
|
5931
|
+
disabled: option.disabled ? true : undefined,
|
|
5932
|
+
onclick: (e) => {
|
|
5933
|
+
e.stopPropagation();
|
|
5934
|
+
},
|
|
5935
|
+
}), m('span', option.label))
|
|
5936
|
+
: m('span', option.label),
|
|
5937
|
+
].filter(Boolean)),
|
|
5938
|
+
])),
|
|
5939
|
+
])
|
|
5940
|
+
.reduce((acc, val) => acc.concat(val), []),
|
|
5941
|
+
];
|
|
5942
|
+
const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
|
|
5943
|
+
var _a;
|
|
5944
|
+
if (!state.isInsideModal)
|
|
5945
|
+
return;
|
|
5946
|
+
let portalElement = document.getElementById(state.dropdownId);
|
|
5947
|
+
if (!state.isOpen) {
|
|
5948
|
+
// Clean up portal when dropdown is closed
|
|
5949
|
+
if (portalElement) {
|
|
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);
|
|
5959
|
+
}
|
|
5960
|
+
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
5961
|
+
tabindex: 0,
|
|
5962
|
+
style: getPortalStyles(state.inputRef),
|
|
5963
|
+
oncreate: ({ dom }) => {
|
|
5964
|
+
state.dropdownRef = dom;
|
|
5965
|
+
},
|
|
5966
|
+
onremove: () => {
|
|
5967
|
+
state.dropdownRef = null;
|
|
5968
|
+
},
|
|
5969
|
+
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
|
|
5970
|
+
m.render(portalElement, dropdownVnode);
|
|
5971
|
+
};
|
|
5852
5972
|
return {
|
|
5853
5973
|
oninit: ({ attrs }) => {
|
|
5854
5974
|
state.id = attrs.id || uniqueId();
|
|
5975
|
+
state.dropdownId = `${state.id}-dropdown`;
|
|
5855
5976
|
const controlled = isControlled(attrs);
|
|
5856
5977
|
// Warn developer for improper controlled usage
|
|
5857
5978
|
if (attrs.checkedId !== undefined && !controlled && !attrs.disabled) {
|
|
@@ -5870,9 +5991,20 @@
|
|
|
5870
5991
|
// Add global click listener to close dropdown
|
|
5871
5992
|
document.addEventListener('click', closeDropdown);
|
|
5872
5993
|
},
|
|
5994
|
+
oncreate: ({ dom }) => {
|
|
5995
|
+
// Detect if component is inside a modal
|
|
5996
|
+
state.isInsideModal = !!dom.closest('.modal');
|
|
5997
|
+
},
|
|
5873
5998
|
onremove: () => {
|
|
5874
5999
|
// Cleanup global listener
|
|
5875
6000
|
document.removeEventListener('click', closeDropdown);
|
|
6001
|
+
// Cleanup portaled dropdown if it exists
|
|
6002
|
+
if (state.isInsideModal && state.dropdownRef) {
|
|
6003
|
+
const portalElement = document.getElementById(state.dropdownId);
|
|
6004
|
+
if (portalElement && portalElement.parentNode) {
|
|
6005
|
+
portalElement.parentNode.removeChild(portalElement);
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
5876
6008
|
},
|
|
5877
6009
|
view: ({ attrs }) => {
|
|
5878
6010
|
var _a;
|
|
@@ -5881,20 +6013,13 @@
|
|
|
5881
6013
|
// Get selected IDs from props or internal state
|
|
5882
6014
|
let selectedIds;
|
|
5883
6015
|
if (controlled) {
|
|
5884
|
-
selectedIds =
|
|
5885
|
-
? Array.isArray(attrs.checkedId)
|
|
5886
|
-
? attrs.checkedId
|
|
5887
|
-
: [attrs.checkedId]
|
|
5888
|
-
: [];
|
|
6016
|
+
selectedIds =
|
|
6017
|
+
attrs.checkedId !== undefined ? (Array.isArray(attrs.checkedId) ? attrs.checkedId : [attrs.checkedId]) : [];
|
|
5889
6018
|
}
|
|
5890
6019
|
else if (disabled) {
|
|
5891
6020
|
// Non-interactive components: prefer defaultCheckedId, fallback to checkedId
|
|
5892
6021
|
const fallbackId = (_a = attrs.defaultCheckedId) !== null && _a !== void 0 ? _a : attrs.checkedId;
|
|
5893
|
-
selectedIds = fallbackId !== undefined
|
|
5894
|
-
? Array.isArray(fallbackId)
|
|
5895
|
-
? fallbackId
|
|
5896
|
-
: [fallbackId]
|
|
5897
|
-
: [];
|
|
6022
|
+
selectedIds = fallbackId !== undefined ? (Array.isArray(fallbackId) ? fallbackId : [fallbackId]) : [];
|
|
5898
6023
|
}
|
|
5899
6024
|
else {
|
|
5900
6025
|
// Interactive uncontrolled: use internal state
|
|
@@ -5903,6 +6028,10 @@
|
|
|
5903
6028
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
|
|
5904
6029
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5905
6030
|
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
6031
|
+
// Update portal dropdown when inside modal
|
|
6032
|
+
if (state.isInsideModal) {
|
|
6033
|
+
updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
|
|
6034
|
+
}
|
|
5906
6035
|
return m('.input-field.select-space', {
|
|
5907
6036
|
className: finalClassName,
|
|
5908
6037
|
key,
|
|
@@ -5915,6 +6044,7 @@
|
|
|
5915
6044
|
tabindex: disabled ? -1 : 0,
|
|
5916
6045
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
5917
6046
|
'aria-haspopup': 'listbox',
|
|
6047
|
+
'aria-controls': state.dropdownId,
|
|
5918
6048
|
role: 'combobox',
|
|
5919
6049
|
}, [
|
|
5920
6050
|
m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
|
|
@@ -5931,8 +6061,8 @@
|
|
|
5931
6061
|
}
|
|
5932
6062
|
},
|
|
5933
6063
|
}),
|
|
5934
|
-
// Dropdown Menu
|
|
5935
|
-
state.isOpen &&
|
|
6064
|
+
// Dropdown Menu - render inline only when NOT inside modal
|
|
6065
|
+
state.isOpen && !state.isInsideModal &&
|
|
5936
6066
|
m('ul.dropdown-content.select-dropdown', {
|
|
5937
6067
|
tabindex: 0,
|
|
5938
6068
|
oncreate: ({ dom }) => {
|
|
@@ -5942,66 +6072,7 @@
|
|
|
5942
6072
|
state.dropdownRef = null;
|
|
5943
6073
|
},
|
|
5944
6074
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5945
|
-
},
|
|
5946
|
-
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5947
|
-
// Render ungrouped options first
|
|
5948
|
-
options
|
|
5949
|
-
.filter((option) => !option.group)
|
|
5950
|
-
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5951
|
-
? 'disabled'
|
|
5952
|
-
: state.focusedIndex === options.indexOf(option)
|
|
5953
|
-
? 'focused'
|
|
5954
|
-
: '' }, (option.disabled
|
|
5955
|
-
? {}
|
|
5956
|
-
: {
|
|
5957
|
-
onclick: (e) => {
|
|
5958
|
-
e.stopPropagation();
|
|
5959
|
-
toggleOption(option.id, multiple, attrs);
|
|
5960
|
-
},
|
|
5961
|
-
})), m('span', multiple
|
|
5962
|
-
? m('label', { for: option.id }, m('input', {
|
|
5963
|
-
id: option.id,
|
|
5964
|
-
type: 'checkbox',
|
|
5965
|
-
checked: selectedIds.includes(option.id),
|
|
5966
|
-
disabled: option.disabled ? true : undefined,
|
|
5967
|
-
onclick: (e) => {
|
|
5968
|
-
e.stopPropagation();
|
|
5969
|
-
},
|
|
5970
|
-
}), m('span', option.label))
|
|
5971
|
-
: option.label))),
|
|
5972
|
-
// Render grouped options
|
|
5973
|
-
Object.entries(options
|
|
5974
|
-
.filter((option) => option.group)
|
|
5975
|
-
.reduce((groups, option) => {
|
|
5976
|
-
const group = option.group;
|
|
5977
|
-
if (!groups[group])
|
|
5978
|
-
groups[group] = [];
|
|
5979
|
-
groups[group].push(option);
|
|
5980
|
-
return groups;
|
|
5981
|
-
}, {}))
|
|
5982
|
-
.map(([groupName, groupOptions]) => [
|
|
5983
|
-
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5984
|
-
...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5985
|
-
? {}
|
|
5986
|
-
: {
|
|
5987
|
-
onclick: (e) => {
|
|
5988
|
-
e.stopPropagation();
|
|
5989
|
-
toggleOption(option.id, multiple, attrs);
|
|
5990
|
-
},
|
|
5991
|
-
})), m('span', multiple
|
|
5992
|
-
? m('label', { for: option.id }, m('input', {
|
|
5993
|
-
id: option.id,
|
|
5994
|
-
type: 'checkbox',
|
|
5995
|
-
checked: selectedIds.includes(option.id),
|
|
5996
|
-
disabled: option.disabled ? true : undefined,
|
|
5997
|
-
onclick: (e) => {
|
|
5998
|
-
e.stopPropagation();
|
|
5999
|
-
},
|
|
6000
|
-
}), m('span', option.label))
|
|
6001
|
-
: option.label))),
|
|
6002
|
-
])
|
|
6003
|
-
.reduce((acc, val) => acc.concat(val), []),
|
|
6004
|
-
]),
|
|
6075
|
+
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
|
|
6005
6076
|
m(MaterialIcon, {
|
|
6006
6077
|
name: 'caret',
|
|
6007
6078
|
direction: 'down',
|