mithril-materialized 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -27
- package/dist/advanced.css +2 -2
- package/dist/components.css +241 -3
- package/dist/core.css +5 -2
- package/dist/forms.css +5 -2
- package/dist/index.css +244 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +516 -109
- package/dist/index.js +516 -108
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +516 -108
- package/dist/rating.d.ts +65 -0
- package/dist/switch.d.ts +2 -2
- package/dist/utilities.css +2 -2
- package/package.json +5 -5
- package/sass/components/_datatable.scss +1 -1
- package/sass/components/_rating.scss +341 -0
- package/sass/components/_theme-variables.scss +9 -0
- package/sass/components/_variables.scss +2 -1
- package/sass/components/forms/_input-fields.scss +3 -0
- package/sass/materialize.scss +2 -1
package/dist/index.umd.js
CHANGED
|
@@ -2793,10 +2793,6 @@
|
|
|
2793
2793
|
return m('span.character-counter', {
|
|
2794
2794
|
style: {
|
|
2795
2795
|
color: isOverLimit ? '#F44336' : '#9e9e9e',
|
|
2796
|
-
fontSize: '12px',
|
|
2797
|
-
display: 'block',
|
|
2798
|
-
textAlign: 'right',
|
|
2799
|
-
marginTop: '8px',
|
|
2800
2796
|
},
|
|
2801
2797
|
}, `${currentLength}/${maxLength}`);
|
|
2802
2798
|
},
|
|
@@ -2813,111 +2809,197 @@
|
|
|
2813
2809
|
textarea: undefined,
|
|
2814
2810
|
internalValue: '',
|
|
2815
2811
|
};
|
|
2816
|
-
const updateHeight = (textarea) => {
|
|
2817
|
-
textarea
|
|
2818
|
-
|
|
2819
|
-
|
|
2812
|
+
const updateHeight = (textarea, hiddenDiv) => {
|
|
2813
|
+
if (!textarea || !hiddenDiv)
|
|
2814
|
+
return;
|
|
2815
|
+
// Copy font properties from textarea to hidden div
|
|
2816
|
+
const computedStyle = window.getComputedStyle(textarea);
|
|
2817
|
+
hiddenDiv.style.fontFamily = computedStyle.fontFamily;
|
|
2818
|
+
hiddenDiv.style.fontSize = computedStyle.fontSize;
|
|
2819
|
+
hiddenDiv.style.lineHeight = computedStyle.lineHeight;
|
|
2820
|
+
// Copy padding from textarea (important for accurate measurement)
|
|
2821
|
+
hiddenDiv.style.paddingTop = computedStyle.paddingTop;
|
|
2822
|
+
hiddenDiv.style.paddingRight = computedStyle.paddingRight;
|
|
2823
|
+
hiddenDiv.style.paddingBottom = computedStyle.paddingBottom;
|
|
2824
|
+
hiddenDiv.style.paddingLeft = computedStyle.paddingLeft;
|
|
2825
|
+
// Handle text wrapping
|
|
2826
|
+
if (textarea.getAttribute('wrap') === 'off') {
|
|
2827
|
+
hiddenDiv.style.overflowWrap = 'normal';
|
|
2828
|
+
hiddenDiv.style.whiteSpace = 'pre';
|
|
2829
|
+
}
|
|
2830
|
+
else {
|
|
2831
|
+
hiddenDiv.style.overflowWrap = 'break-word';
|
|
2832
|
+
hiddenDiv.style.whiteSpace = 'pre-wrap';
|
|
2833
|
+
}
|
|
2834
|
+
// Set content with extra newline for measurement
|
|
2835
|
+
hiddenDiv.textContent = textarea.value + '\n';
|
|
2836
|
+
const content = hiddenDiv.innerHTML.replace(/\n/g, '<br>');
|
|
2837
|
+
hiddenDiv.innerHTML = content;
|
|
2838
|
+
// Set width to match textarea
|
|
2839
|
+
if (textarea.offsetWidth > 0) {
|
|
2840
|
+
hiddenDiv.style.width = textarea.offsetWidth + 'px';
|
|
2841
|
+
}
|
|
2842
|
+
else {
|
|
2843
|
+
hiddenDiv.style.width = window.innerWidth / 2 + 'px';
|
|
2844
|
+
}
|
|
2845
|
+
// Get the original/natural height of the textarea
|
|
2846
|
+
const originalHeight = textarea.offsetHeight;
|
|
2847
|
+
const measuredHeight = hiddenDiv.offsetHeight;
|
|
2848
|
+
// Key logic: Only set custom height when content requires MORE space than original height
|
|
2849
|
+
// This matches the Materialize CSS reference behavior
|
|
2850
|
+
if (originalHeight <= measuredHeight) {
|
|
2851
|
+
state.height = measuredHeight + 'px';
|
|
2852
|
+
}
|
|
2853
|
+
else {
|
|
2854
|
+
// Single line content or content that fits in original height - let CSS handle it
|
|
2855
|
+
state.height = undefined;
|
|
2856
|
+
}
|
|
2820
2857
|
};
|
|
2821
|
-
const isControlled = (attrs) => attrs.value !== undefined && attrs.oninput !== undefined;
|
|
2858
|
+
const isControlled = (attrs) => attrs.value !== undefined && (attrs.oninput !== undefined || attrs.onchange !== undefined);
|
|
2822
2859
|
return {
|
|
2823
2860
|
oninit: ({ attrs }) => {
|
|
2861
|
+
const controlled = isControlled(attrs);
|
|
2862
|
+
const isNonInteractive = attrs.readonly || attrs.disabled;
|
|
2863
|
+
// Warn developer for improper controlled usage
|
|
2864
|
+
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
2865
|
+
console.warn(`TextArea received 'value' prop without 'oninput' or 'onchange' handler. ` +
|
|
2866
|
+
`Use 'defaultValue' for uncontrolled components or add an event handler for controlled components.`);
|
|
2867
|
+
}
|
|
2824
2868
|
// Initialize internal value for uncontrolled mode
|
|
2825
|
-
if (!
|
|
2869
|
+
if (!controlled) {
|
|
2826
2870
|
state.internalValue = attrs.defaultValue || '';
|
|
2827
2871
|
}
|
|
2828
2872
|
},
|
|
2829
2873
|
onremove: () => {
|
|
2830
2874
|
},
|
|
2831
2875
|
view: ({ attrs }) => {
|
|
2876
|
+
var _a, _b, _c, _d;
|
|
2832
2877
|
const { className = 'col s12', helperText, iconName, id = state.id, value, placeholder, isMandatory, label, maxLength, oninput, onchange, onkeydown, onkeypress, onkeyup, onblur, style } = attrs, params = __rest(attrs, ["className", "helperText", "iconName", "id", "value", "placeholder", "isMandatory", "label", "maxLength", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "onblur", "style"]);
|
|
2833
2878
|
const controlled = isControlled(attrs);
|
|
2834
|
-
const
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
const
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
// Update character count
|
|
2865
|
-
if (maxLength) {
|
|
2866
|
-
state.currentLength = target.value.length;
|
|
2867
|
-
state.hasInteracted = target.value.length > 0;
|
|
2868
|
-
}
|
|
2869
|
-
// Update internal state for uncontrolled mode
|
|
2870
|
-
if (!controlled) {
|
|
2871
|
-
state.internalValue = target.value;
|
|
2872
|
-
}
|
|
2873
|
-
// Call oninput handler
|
|
2874
|
-
if (oninput) {
|
|
2875
|
-
oninput(target.value);
|
|
2876
|
-
}
|
|
2877
|
-
}, onblur: (e) => {
|
|
2878
|
-
state.active = false;
|
|
2879
|
-
// const target = e.target as HTMLTextAreaElement;
|
|
2880
|
-
state.hasInteracted = true;
|
|
2881
|
-
// Call original onblur if provided
|
|
2882
|
-
if (onblur) {
|
|
2883
|
-
onblur(e);
|
|
2884
|
-
}
|
|
2885
|
-
if (onchange && state.textarea) {
|
|
2886
|
-
onchange(state.textarea.value);
|
|
2887
|
-
}
|
|
2888
|
-
}, onkeyup: onkeyup
|
|
2889
|
-
? (ev) => {
|
|
2890
|
-
onkeyup(ev, ev.target.value);
|
|
2891
|
-
}
|
|
2892
|
-
: undefined, onkeydown: onkeydown
|
|
2893
|
-
? (ev) => {
|
|
2894
|
-
onkeydown(ev, ev.target.value);
|
|
2879
|
+
const isNonInteractive = attrs.readonly || attrs.disabled;
|
|
2880
|
+
let currentValue;
|
|
2881
|
+
if (controlled) {
|
|
2882
|
+
currentValue = value || '';
|
|
2883
|
+
}
|
|
2884
|
+
else if (isNonInteractive) {
|
|
2885
|
+
// Non-interactive components: prefer defaultValue, fallback to value
|
|
2886
|
+
currentValue = (_b = (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : value) !== null && _b !== void 0 ? _b : '';
|
|
2887
|
+
}
|
|
2888
|
+
else {
|
|
2889
|
+
// Interactive uncontrolled: use internal state
|
|
2890
|
+
currentValue = (_d = (_c = state.internalValue) !== null && _c !== void 0 ? _c : attrs.defaultValue) !== null && _d !== void 0 ? _d : '';
|
|
2891
|
+
}
|
|
2892
|
+
return [
|
|
2893
|
+
// Hidden div for height measurement - positioned outside the input-field
|
|
2894
|
+
m('.hiddendiv', {
|
|
2895
|
+
style: {
|
|
2896
|
+
visibility: 'hidden',
|
|
2897
|
+
position: 'absolute',
|
|
2898
|
+
top: '0',
|
|
2899
|
+
left: '0',
|
|
2900
|
+
zIndex: '-1',
|
|
2901
|
+
whiteSpace: 'pre-wrap',
|
|
2902
|
+
wordWrap: 'break-word',
|
|
2903
|
+
overflowWrap: 'break-word',
|
|
2904
|
+
},
|
|
2905
|
+
oncreate: ({ dom }) => {
|
|
2906
|
+
const hiddenDiv = dom;
|
|
2907
|
+
if (state.textarea) {
|
|
2908
|
+
updateHeight(state.textarea, hiddenDiv);
|
|
2895
2909
|
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2910
|
+
},
|
|
2911
|
+
onupdate: ({ dom }) => {
|
|
2912
|
+
const hiddenDiv = dom;
|
|
2913
|
+
if (state.textarea) {
|
|
2914
|
+
updateHeight(state.textarea, hiddenDiv);
|
|
2899
2915
|
}
|
|
2900
|
-
|
|
2901
|
-
m(Label, {
|
|
2902
|
-
label,
|
|
2903
|
-
id,
|
|
2904
|
-
isMandatory,
|
|
2905
|
-
isActive: currentValue || placeholder || state.active,
|
|
2906
|
-
initialValue: currentValue !== '',
|
|
2907
|
-
}),
|
|
2908
|
-
m(HelperText, {
|
|
2909
|
-
helperText,
|
|
2910
|
-
dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
|
|
2911
|
-
dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
|
|
2916
|
+
},
|
|
2912
2917
|
}),
|
|
2913
|
-
|
|
2914
|
-
? m(
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2918
|
+
m('.input-field', { className, style }, [
|
|
2919
|
+
iconName ? m('i.material-icons.prefix', iconName) : '',
|
|
2920
|
+
m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, value: controlled ? currentValue : undefined, style: {
|
|
2921
|
+
height: state.height,
|
|
2922
|
+
}, oncreate: ({ dom }) => {
|
|
2923
|
+
const textarea = (state.textarea = dom);
|
|
2924
|
+
// For uncontrolled mode, set initial value only
|
|
2925
|
+
if (!controlled && attrs.defaultValue !== undefined) {
|
|
2926
|
+
textarea.value = String(attrs.defaultValue);
|
|
2927
|
+
}
|
|
2928
|
+
// Height will be calculated by hidden div
|
|
2929
|
+
// Update character count state for counter component
|
|
2930
|
+
if (maxLength) {
|
|
2931
|
+
state.currentLength = textarea.value.length;
|
|
2932
|
+
}
|
|
2933
|
+
}, onupdate: ({ dom }) => {
|
|
2934
|
+
const textarea = dom;
|
|
2935
|
+
if (state.height)
|
|
2936
|
+
textarea.style.height = state.height;
|
|
2937
|
+
// No need to manually sync in onupdate since value attribute handles it
|
|
2938
|
+
}, onfocus: () => {
|
|
2939
|
+
state.active = true;
|
|
2940
|
+
}, oninput: (e) => {
|
|
2941
|
+
state.active = true;
|
|
2942
|
+
state.hasInteracted = false;
|
|
2943
|
+
const target = e.target;
|
|
2944
|
+
// Height will be recalculated by hidden div on next update
|
|
2945
|
+
// Update character count
|
|
2946
|
+
if (maxLength) {
|
|
2947
|
+
state.currentLength = target.value.length;
|
|
2948
|
+
state.hasInteracted = target.value.length > 0;
|
|
2949
|
+
}
|
|
2950
|
+
// Update internal state for uncontrolled mode
|
|
2951
|
+
if (!controlled) {
|
|
2952
|
+
state.internalValue = target.value;
|
|
2953
|
+
}
|
|
2954
|
+
// Call oninput handler
|
|
2955
|
+
if (oninput) {
|
|
2956
|
+
oninput(target.value);
|
|
2957
|
+
}
|
|
2958
|
+
}, onblur: (e) => {
|
|
2959
|
+
state.active = false;
|
|
2960
|
+
// const target = e.target as HTMLTextAreaElement;
|
|
2961
|
+
state.hasInteracted = true;
|
|
2962
|
+
// Call original onblur if provided
|
|
2963
|
+
if (onblur) {
|
|
2964
|
+
onblur(e);
|
|
2965
|
+
}
|
|
2966
|
+
if (onchange && state.textarea) {
|
|
2967
|
+
onchange(state.textarea.value);
|
|
2968
|
+
}
|
|
2969
|
+
}, onkeyup: onkeyup
|
|
2970
|
+
? (ev) => {
|
|
2971
|
+
onkeyup(ev, ev.target.value);
|
|
2972
|
+
}
|
|
2973
|
+
: undefined, onkeydown: onkeydown
|
|
2974
|
+
? (ev) => {
|
|
2975
|
+
onkeydown(ev, ev.target.value);
|
|
2976
|
+
}
|
|
2977
|
+
: undefined, onkeypress: onkeypress
|
|
2978
|
+
? (ev) => {
|
|
2979
|
+
onkeypress(ev, ev.target.value);
|
|
2980
|
+
}
|
|
2981
|
+
: undefined })),
|
|
2982
|
+
m(Label, {
|
|
2983
|
+
label,
|
|
2984
|
+
id,
|
|
2985
|
+
isMandatory,
|
|
2986
|
+
isActive: currentValue || placeholder || state.active,
|
|
2987
|
+
initialValue: currentValue !== '',
|
|
2988
|
+
}),
|
|
2989
|
+
m(HelperText, {
|
|
2990
|
+
helperText,
|
|
2991
|
+
dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
|
|
2992
|
+
dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
|
|
2993
|
+
}),
|
|
2994
|
+
maxLength
|
|
2995
|
+
? m(CharacterCounter, {
|
|
2996
|
+
currentLength: state.currentLength,
|
|
2997
|
+
maxLength,
|
|
2998
|
+
show: state.currentLength > 0,
|
|
2999
|
+
})
|
|
3000
|
+
: undefined,
|
|
3001
|
+
]),
|
|
3002
|
+
];
|
|
2921
3003
|
},
|
|
2922
3004
|
};
|
|
2923
3005
|
};
|
|
@@ -2937,7 +3019,8 @@
|
|
|
2937
3019
|
isDragging: false,
|
|
2938
3020
|
activeThumb: null,
|
|
2939
3021
|
};
|
|
2940
|
-
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' &&
|
|
3022
|
+
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' &&
|
|
3023
|
+
(typeof attrs.oninput === 'function' || typeof attrs.onchange === 'function');
|
|
2941
3024
|
const getValue = (target) => {
|
|
2942
3025
|
const val = target.value;
|
|
2943
3026
|
return (val ? (type === 'number' || type === 'range' ? +val : val) : val);
|
|
@@ -2983,8 +3066,15 @@
|
|
|
2983
3066
|
// Range slider rendering functions are now in separate module
|
|
2984
3067
|
return {
|
|
2985
3068
|
oninit: ({ attrs }) => {
|
|
3069
|
+
const controlled = isControlled(attrs);
|
|
3070
|
+
const isNonInteractive = attrs.readonly || attrs.disabled;
|
|
3071
|
+
// Warn developer for improper controlled usage
|
|
3072
|
+
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
3073
|
+
console.warn(`${type} input received 'value' prop without 'oninput' or 'onchange' handler. ` +
|
|
3074
|
+
`Use 'defaultValue' for uncontrolled components or add an event handler for controlled components.`);
|
|
3075
|
+
}
|
|
2986
3076
|
// Initialize internal value if not in controlled mode
|
|
2987
|
-
if (!
|
|
3077
|
+
if (!controlled) {
|
|
2988
3078
|
const isNumeric = ['number', 'range'].includes(type);
|
|
2989
3079
|
if (attrs.defaultValue !== undefined) {
|
|
2990
3080
|
if (isNumeric) {
|
|
@@ -3000,7 +3090,7 @@
|
|
|
3000
3090
|
}
|
|
3001
3091
|
},
|
|
3002
3092
|
view: ({ attrs }) => {
|
|
3003
|
-
var _a, _b;
|
|
3093
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3004
3094
|
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"]);
|
|
3005
3095
|
// const attributes = toAttrs(params);
|
|
3006
3096
|
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
@@ -3020,7 +3110,19 @@
|
|
|
3020
3110
|
}
|
|
3021
3111
|
const isNumeric = ['number', 'range'].includes(type);
|
|
3022
3112
|
const controlled = isControlled(attrs);
|
|
3023
|
-
const
|
|
3113
|
+
const isNonInteractive = attrs.readonly || attrs.disabled;
|
|
3114
|
+
let value;
|
|
3115
|
+
if (controlled) {
|
|
3116
|
+
value = attrs.value;
|
|
3117
|
+
}
|
|
3118
|
+
else if (isNonInteractive) {
|
|
3119
|
+
// Non-interactive components: prefer defaultValue, fallback to value
|
|
3120
|
+
value = ((_c = (_b = attrs.defaultValue) !== null && _b !== void 0 ? _b : attrs.value) !== null && _c !== void 0 ? _c : (isNumeric ? 0 : ''));
|
|
3121
|
+
}
|
|
3122
|
+
else {
|
|
3123
|
+
// Interactive uncontrolled: use internal state
|
|
3124
|
+
value = ((_e = (_d = state.internalValue) !== null && _d !== void 0 ? _d : attrs.defaultValue) !== null && _e !== void 0 ? _e : (isNumeric ? 0 : ''));
|
|
3125
|
+
}
|
|
3024
3126
|
const rangeType = type === 'range' && !attrs.minmax;
|
|
3025
3127
|
return m('.input-field', { className: cn, style }, [
|
|
3026
3128
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
@@ -3089,7 +3191,9 @@
|
|
|
3089
3191
|
state.isValid = true;
|
|
3090
3192
|
}
|
|
3091
3193
|
}
|
|
3092
|
-
else if ((type === 'email' || type === 'url') &&
|
|
3194
|
+
else if ((type === 'email' || type === 'url') &&
|
|
3195
|
+
target.classList.contains('invalid') &&
|
|
3196
|
+
target.value.length > 0) {
|
|
3093
3197
|
// Clear native validation errors if user is typing and input becomes valid
|
|
3094
3198
|
if (target.validity.valid) {
|
|
3095
3199
|
target.classList.remove('invalid');
|
|
@@ -3184,7 +3288,7 @@
|
|
|
3184
3288
|
}
|
|
3185
3289
|
} })),
|
|
3186
3290
|
// Clear button - only for text inputs with canClear enabled and has content
|
|
3187
|
-
canClear && type === 'text' && ((
|
|
3291
|
+
canClear && type === 'text' && ((_f = state.inputElement) === null || _f === void 0 ? void 0 : _f.value)
|
|
3188
3292
|
? m(MaterialIcon, {
|
|
3189
3293
|
name: 'close',
|
|
3190
3294
|
className: 'input-clear-btn',
|
|
@@ -5581,17 +5685,35 @@
|
|
|
5581
5685
|
return {
|
|
5582
5686
|
oninit: ({ attrs }) => {
|
|
5583
5687
|
state.componentId = attrs.id || uniqueId();
|
|
5688
|
+
const controlled = isControlled(attrs);
|
|
5689
|
+
// Warn developer for improper controlled usage
|
|
5690
|
+
if (attrs.checkedId !== undefined && !controlled && !attrs.disabled) {
|
|
5691
|
+
console.warn(`RadioButtons component received 'checkedId' prop without 'onchange' handler. ` +
|
|
5692
|
+
`Use 'defaultCheckedId' for uncontrolled components or add 'onchange' for controlled components.`);
|
|
5693
|
+
}
|
|
5584
5694
|
// Initialize internal state for uncontrolled mode
|
|
5585
|
-
if (!
|
|
5695
|
+
if (!controlled) {
|
|
5586
5696
|
state.internalCheckedId = attrs.defaultCheckedId;
|
|
5587
5697
|
}
|
|
5588
5698
|
},
|
|
5589
5699
|
view: ({ attrs }) => {
|
|
5700
|
+
var _a, _b;
|
|
5590
5701
|
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange, } = attrs;
|
|
5591
5702
|
const { groupId, componentId } = state;
|
|
5592
5703
|
const controlled = isControlled(attrs);
|
|
5593
5704
|
// Get current checked ID from props or internal state
|
|
5594
|
-
|
|
5705
|
+
let currentCheckedId;
|
|
5706
|
+
if (controlled) {
|
|
5707
|
+
currentCheckedId = checkedId;
|
|
5708
|
+
}
|
|
5709
|
+
else if (disabled) {
|
|
5710
|
+
// Non-interactive components: prefer defaultCheckedId, fallback to checkedId
|
|
5711
|
+
currentCheckedId = (_a = attrs.defaultCheckedId) !== null && _a !== void 0 ? _a : checkedId;
|
|
5712
|
+
}
|
|
5713
|
+
else {
|
|
5714
|
+
// Interactive uncontrolled: use internal state
|
|
5715
|
+
currentCheckedId = (_b = state.internalCheckedId) !== null && _b !== void 0 ? _b : attrs.defaultCheckedId;
|
|
5716
|
+
}
|
|
5595
5717
|
const handleChange = (id) => {
|
|
5596
5718
|
// Update internal state for uncontrolled mode
|
|
5597
5719
|
if (!controlled) {
|
|
@@ -5721,8 +5843,14 @@
|
|
|
5721
5843
|
return {
|
|
5722
5844
|
oninit: ({ attrs }) => {
|
|
5723
5845
|
state.id = attrs.id || uniqueId();
|
|
5846
|
+
const controlled = isControlled(attrs);
|
|
5847
|
+
// Warn developer for improper controlled usage
|
|
5848
|
+
if (attrs.checkedId !== undefined && !controlled && !attrs.disabled) {
|
|
5849
|
+
console.warn(`Select component received 'checkedId' prop without 'onchange' handler. ` +
|
|
5850
|
+
`Use 'defaultCheckedId' for uncontrolled components or add 'onchange' for controlled components.`);
|
|
5851
|
+
}
|
|
5724
5852
|
// Initialize internal state for uncontrolled mode
|
|
5725
|
-
if (!
|
|
5853
|
+
if (!controlled) {
|
|
5726
5854
|
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
5727
5855
|
? Array.isArray(attrs.defaultCheckedId)
|
|
5728
5856
|
? attrs.defaultCheckedId
|
|
@@ -5738,16 +5866,32 @@
|
|
|
5738
5866
|
document.removeEventListener('click', closeDropdown);
|
|
5739
5867
|
},
|
|
5740
5868
|
view: ({ attrs }) => {
|
|
5869
|
+
var _a;
|
|
5741
5870
|
const controlled = isControlled(attrs);
|
|
5871
|
+
const { disabled } = attrs;
|
|
5742
5872
|
// Get selected IDs from props or internal state
|
|
5743
|
-
|
|
5744
|
-
|
|
5873
|
+
let selectedIds;
|
|
5874
|
+
if (controlled) {
|
|
5875
|
+
selectedIds = attrs.checkedId !== undefined
|
|
5745
5876
|
? Array.isArray(attrs.checkedId)
|
|
5746
5877
|
? attrs.checkedId
|
|
5747
5878
|
: [attrs.checkedId]
|
|
5748
|
-
: []
|
|
5749
|
-
|
|
5750
|
-
|
|
5879
|
+
: [];
|
|
5880
|
+
}
|
|
5881
|
+
else if (disabled) {
|
|
5882
|
+
// Non-interactive components: prefer defaultCheckedId, fallback to checkedId
|
|
5883
|
+
const fallbackId = (_a = attrs.defaultCheckedId) !== null && _a !== void 0 ? _a : attrs.checkedId;
|
|
5884
|
+
selectedIds = fallbackId !== undefined
|
|
5885
|
+
? Array.isArray(fallbackId)
|
|
5886
|
+
? fallbackId
|
|
5887
|
+
: [fallbackId]
|
|
5888
|
+
: [];
|
|
5889
|
+
}
|
|
5890
|
+
else {
|
|
5891
|
+
// Interactive uncontrolled: use internal state
|
|
5892
|
+
selectedIds = state.internalSelectedIds;
|
|
5893
|
+
}
|
|
5894
|
+
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
|
|
5751
5895
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5752
5896
|
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
5753
5897
|
return m('.input-field.select-space', {
|
|
@@ -8484,6 +8628,269 @@
|
|
|
8484
8628
|
};
|
|
8485
8629
|
};
|
|
8486
8630
|
|
|
8631
|
+
/** Default star icons */
|
|
8632
|
+
const DEFAULT_ICONS = {
|
|
8633
|
+
filled: '★',
|
|
8634
|
+
empty: '☆',
|
|
8635
|
+
half: '☆', // We'll handle half-fill with CSS
|
|
8636
|
+
};
|
|
8637
|
+
/** Create a Rating component */
|
|
8638
|
+
const Rating = () => {
|
|
8639
|
+
const state = {
|
|
8640
|
+
id: uniqueId(),
|
|
8641
|
+
internalValue: 0,
|
|
8642
|
+
hoverValue: null,
|
|
8643
|
+
isHovering: false,
|
|
8644
|
+
isFocused: false,
|
|
8645
|
+
};
|
|
8646
|
+
const isControlled = (attrs) => typeof attrs.value !== 'undefined' && typeof attrs.onchange === 'function';
|
|
8647
|
+
const getCurrentValue = (attrs) => {
|
|
8648
|
+
var _a, _b, _c, _d;
|
|
8649
|
+
const controlled = isControlled(attrs);
|
|
8650
|
+
const isNonInteractive = attrs.readonly || attrs.disabled;
|
|
8651
|
+
if (controlled) {
|
|
8652
|
+
return attrs.value || 0;
|
|
8653
|
+
}
|
|
8654
|
+
// Non-interactive components: prefer defaultValue, fallback to value
|
|
8655
|
+
if (isNonInteractive) {
|
|
8656
|
+
return (_b = (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value) !== null && _b !== void 0 ? _b : 0;
|
|
8657
|
+
}
|
|
8658
|
+
// Interactive uncontrolled: use internal state (user can change it)
|
|
8659
|
+
return (_d = (_c = state.internalValue) !== null && _c !== void 0 ? _c : attrs.defaultValue) !== null && _d !== void 0 ? _d : 0;
|
|
8660
|
+
};
|
|
8661
|
+
const getDisplayValue = (attrs) => state.isHovering && state.hoverValue !== null ? state.hoverValue : getCurrentValue(attrs);
|
|
8662
|
+
const getLabelText = (value, max, getLabelFn) => {
|
|
8663
|
+
if (getLabelFn) {
|
|
8664
|
+
return getLabelFn(value, max);
|
|
8665
|
+
}
|
|
8666
|
+
if (value === 0) {
|
|
8667
|
+
return `No rating`;
|
|
8668
|
+
}
|
|
8669
|
+
if (value === 1) {
|
|
8670
|
+
return `1 star out of ${max}`;
|
|
8671
|
+
}
|
|
8672
|
+
return `${value} stars out of ${max}`;
|
|
8673
|
+
};
|
|
8674
|
+
const getSizeClass = (size = 'medium') => {
|
|
8675
|
+
switch (size) {
|
|
8676
|
+
case 'small':
|
|
8677
|
+
return 'rating--small';
|
|
8678
|
+
case 'large':
|
|
8679
|
+
return 'rating--large';
|
|
8680
|
+
default:
|
|
8681
|
+
return 'rating--medium';
|
|
8682
|
+
}
|
|
8683
|
+
};
|
|
8684
|
+
const getDensityClass = (density = 'standard') => {
|
|
8685
|
+
switch (density) {
|
|
8686
|
+
case 'compact':
|
|
8687
|
+
return 'rating--compact';
|
|
8688
|
+
case 'comfortable':
|
|
8689
|
+
return 'rating--comfortable';
|
|
8690
|
+
default:
|
|
8691
|
+
return 'rating--standard';
|
|
8692
|
+
}
|
|
8693
|
+
};
|
|
8694
|
+
const handleItemClick = (attrs, clickValue) => {
|
|
8695
|
+
var _a;
|
|
8696
|
+
if (attrs.readonly || attrs.disabled)
|
|
8697
|
+
return;
|
|
8698
|
+
const currentValue = getCurrentValue(attrs);
|
|
8699
|
+
const newValue = attrs.clearable && currentValue === clickValue ? 0 : clickValue;
|
|
8700
|
+
if (!isControlled(attrs)) {
|
|
8701
|
+
state.internalValue = newValue;
|
|
8702
|
+
}
|
|
8703
|
+
(_a = attrs.onchange) === null || _a === void 0 ? void 0 : _a.call(attrs, newValue);
|
|
8704
|
+
};
|
|
8705
|
+
const handleItemHover = (attrs, hoverValue) => {
|
|
8706
|
+
var _a;
|
|
8707
|
+
if (attrs.readonly || attrs.disabled)
|
|
8708
|
+
return;
|
|
8709
|
+
state.hoverValue = hoverValue;
|
|
8710
|
+
state.isHovering = true;
|
|
8711
|
+
(_a = attrs.onmouseover) === null || _a === void 0 ? void 0 : _a.call(attrs, hoverValue);
|
|
8712
|
+
};
|
|
8713
|
+
const handleMouseLeave = (attrs) => {
|
|
8714
|
+
if (attrs.readonly || attrs.disabled)
|
|
8715
|
+
return;
|
|
8716
|
+
state.isHovering = false;
|
|
8717
|
+
state.hoverValue = null;
|
|
8718
|
+
};
|
|
8719
|
+
const handleKeyDown = (attrs, e) => {
|
|
8720
|
+
var _a;
|
|
8721
|
+
if (attrs.readonly || attrs.disabled)
|
|
8722
|
+
return;
|
|
8723
|
+
const max = attrs.max || 5;
|
|
8724
|
+
const step = attrs.step || 1;
|
|
8725
|
+
const currentValue = getCurrentValue(attrs);
|
|
8726
|
+
let newValue = currentValue;
|
|
8727
|
+
switch (e.key) {
|
|
8728
|
+
case 'ArrowRight':
|
|
8729
|
+
case 'ArrowUp':
|
|
8730
|
+
e.preventDefault();
|
|
8731
|
+
newValue = Math.min(max, currentValue + step);
|
|
8732
|
+
break;
|
|
8733
|
+
case 'ArrowLeft':
|
|
8734
|
+
case 'ArrowDown':
|
|
8735
|
+
e.preventDefault();
|
|
8736
|
+
newValue = Math.max(0, currentValue - step);
|
|
8737
|
+
break;
|
|
8738
|
+
case 'Home':
|
|
8739
|
+
e.preventDefault();
|
|
8740
|
+
newValue = attrs.clearable ? 0 : step;
|
|
8741
|
+
break;
|
|
8742
|
+
case 'End':
|
|
8743
|
+
e.preventDefault();
|
|
8744
|
+
newValue = max;
|
|
8745
|
+
break;
|
|
8746
|
+
case ' ':
|
|
8747
|
+
case 'Enter':
|
|
8748
|
+
e.preventDefault();
|
|
8749
|
+
// If focused and not hovering, increment by step
|
|
8750
|
+
if (!state.isHovering) {
|
|
8751
|
+
newValue = currentValue + step > max ? (attrs.clearable ? 0 : step) : currentValue + step;
|
|
8752
|
+
}
|
|
8753
|
+
break;
|
|
8754
|
+
case 'Escape':
|
|
8755
|
+
if (attrs.clearable) {
|
|
8756
|
+
e.preventDefault();
|
|
8757
|
+
newValue = 0;
|
|
8758
|
+
}
|
|
8759
|
+
break;
|
|
8760
|
+
default:
|
|
8761
|
+
return;
|
|
8762
|
+
}
|
|
8763
|
+
if (newValue !== currentValue) {
|
|
8764
|
+
if (!isControlled(attrs)) {
|
|
8765
|
+
state.internalValue = newValue;
|
|
8766
|
+
}
|
|
8767
|
+
(_a = attrs.onchange) === null || _a === void 0 ? void 0 : _a.call(attrs, newValue);
|
|
8768
|
+
}
|
|
8769
|
+
};
|
|
8770
|
+
const RatingItem = () => {
|
|
8771
|
+
return {
|
|
8772
|
+
view: ({ attrs }) => {
|
|
8773
|
+
const { index, displayValue, step, icons, allowHalfSteps, disabled, onclick, onmouseover } = attrs;
|
|
8774
|
+
const itemValue = (index + 1) * step;
|
|
8775
|
+
// Calculate fill state based on displayValue vs itemValue
|
|
8776
|
+
const diff = displayValue - itemValue;
|
|
8777
|
+
const fillState = diff >= 0 ? 'full' : allowHalfSteps && diff >= -step / 2 ? 'half' : 'empty';
|
|
8778
|
+
return m('.rating__item', {
|
|
8779
|
+
className: [
|
|
8780
|
+
fillState === 'full' ? 'rating__item--filled' : '',
|
|
8781
|
+
fillState === 'half' ? 'rating__item--half' : '',
|
|
8782
|
+
fillState !== 'empty' ? 'rating__item--active' : '',
|
|
8783
|
+
disabled ? 'rating__item--disabled' : '',
|
|
8784
|
+
]
|
|
8785
|
+
.filter(Boolean)
|
|
8786
|
+
.join(' '),
|
|
8787
|
+
onclick,
|
|
8788
|
+
onmouseover,
|
|
8789
|
+
}, [
|
|
8790
|
+
// Empty icon (background)
|
|
8791
|
+
m('.rating__icon.rating__icon--empty', { 'aria-hidden': 'true' }, typeof icons.empty === 'string' ? icons.empty : m(icons.empty)),
|
|
8792
|
+
// Filled icon (foreground)
|
|
8793
|
+
m('.rating__icon.rating__icon--filled', {
|
|
8794
|
+
'aria-hidden': 'true',
|
|
8795
|
+
style: {
|
|
8796
|
+
clipPath: fillState === 'half' ? 'inset(0 50% 0 0)' : undefined,
|
|
8797
|
+
},
|
|
8798
|
+
}, typeof icons.filled === 'string' ? icons.filled : m(icons.filled)),
|
|
8799
|
+
]);
|
|
8800
|
+
},
|
|
8801
|
+
};
|
|
8802
|
+
};
|
|
8803
|
+
return {
|
|
8804
|
+
oninit: ({ attrs }) => {
|
|
8805
|
+
const controlled = isControlled(attrs);
|
|
8806
|
+
const isNonInteractive = attrs.readonly || attrs.disabled;
|
|
8807
|
+
// Warn developer for improper controlled usage
|
|
8808
|
+
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
8809
|
+
console.warn(`Rating component received 'value' prop without 'onchange' handler. ` +
|
|
8810
|
+
`Use 'defaultValue' for uncontrolled components or add 'onchange' for controlled components.`);
|
|
8811
|
+
}
|
|
8812
|
+
if (!controlled) {
|
|
8813
|
+
state.internalValue = attrs.defaultValue || 0;
|
|
8814
|
+
}
|
|
8815
|
+
},
|
|
8816
|
+
view: ({ attrs }) => {
|
|
8817
|
+
const { max = 5, step = 1, size = 'medium', density = 'standard', className = '', style = {}, readonly: readonly = false, disabled = false, id = state.id, name } = attrs, ariaAttrs = __rest(attrs, ["max", "step", "size", "density", "className", "style", "readonly", "disabled", "id", "name"]);
|
|
8818
|
+
const currentValue = getCurrentValue(attrs);
|
|
8819
|
+
const displayValue = getDisplayValue(attrs);
|
|
8820
|
+
const itemCount = Math.ceil(max / step);
|
|
8821
|
+
return m('.rating', {
|
|
8822
|
+
className: [
|
|
8823
|
+
'rating',
|
|
8824
|
+
getSizeClass(size),
|
|
8825
|
+
getDensityClass(density),
|
|
8826
|
+
readonly ? 'rating--read-only' : '',
|
|
8827
|
+
disabled ? 'rating--disabled' : '',
|
|
8828
|
+
state.isFocused ? 'rating--focused' : '',
|
|
8829
|
+
className,
|
|
8830
|
+
]
|
|
8831
|
+
.filter(Boolean)
|
|
8832
|
+
.join(' '),
|
|
8833
|
+
style,
|
|
8834
|
+
id,
|
|
8835
|
+
role: readonly ? 'img' : 'slider',
|
|
8836
|
+
tabindex: readonly || disabled ? -1 : 0,
|
|
8837
|
+
'aria-valuemin': 0,
|
|
8838
|
+
'aria-valuemax': max,
|
|
8839
|
+
'aria-valuenow': currentValue,
|
|
8840
|
+
'aria-valuetext': getLabelText(currentValue, max, attrs.getLabelText),
|
|
8841
|
+
'aria-label': ariaAttrs['aria-label'] ||
|
|
8842
|
+
attrs.ariaLabel ||
|
|
8843
|
+
`Rating: ${getLabelText(currentValue, max, attrs.getLabelText)}`,
|
|
8844
|
+
'aria-labelledby': ariaAttrs['aria-labelledby'],
|
|
8845
|
+
'aria-readonly': readonly,
|
|
8846
|
+
'aria-disabled': disabled,
|
|
8847
|
+
onkeydown: (e) => handleKeyDown(attrs, e),
|
|
8848
|
+
onfocus: () => {
|
|
8849
|
+
state.isFocused = true;
|
|
8850
|
+
},
|
|
8851
|
+
onblur: () => {
|
|
8852
|
+
state.isFocused = false;
|
|
8853
|
+
handleMouseLeave(attrs);
|
|
8854
|
+
},
|
|
8855
|
+
onmouseleave: () => handleMouseLeave(attrs),
|
|
8856
|
+
}, [
|
|
8857
|
+
// Hidden input for form submission
|
|
8858
|
+
name &&
|
|
8859
|
+
m('input', {
|
|
8860
|
+
type: 'hidden',
|
|
8861
|
+
name,
|
|
8862
|
+
value: currentValue,
|
|
8863
|
+
}),
|
|
8864
|
+
// Rating items
|
|
8865
|
+
m('.rating__items', {
|
|
8866
|
+
className: 'rating__items',
|
|
8867
|
+
},
|
|
8868
|
+
// Array.from({ length: itemCount }, (_, i) => renderRatingItem(attrs, i))
|
|
8869
|
+
[...Array(itemCount)].map((_, i) => {
|
|
8870
|
+
const itemValue = (i + 1) * step;
|
|
8871
|
+
return m(RatingItem, {
|
|
8872
|
+
key: `rating-item-${i}`,
|
|
8873
|
+
index: i,
|
|
8874
|
+
displayValue: displayValue,
|
|
8875
|
+
step,
|
|
8876
|
+
icons: Object.assign(Object.assign({}, DEFAULT_ICONS), attrs.icon),
|
|
8877
|
+
allowHalfSteps: attrs.allowHalfSteps,
|
|
8878
|
+
disabled: attrs.disabled,
|
|
8879
|
+
onclick: () => handleItemClick(attrs, itemValue),
|
|
8880
|
+
onmouseover: () => handleItemHover(attrs, itemValue),
|
|
8881
|
+
});
|
|
8882
|
+
})),
|
|
8883
|
+
// Screen reader text
|
|
8884
|
+
m('.rating__sr-only', {
|
|
8885
|
+
className: 'rating__sr-only',
|
|
8886
|
+
'aria-live': 'polite',
|
|
8887
|
+
'aria-atomic': 'true',
|
|
8888
|
+
}, getLabelText(displayValue, max, attrs.getLabelText)),
|
|
8889
|
+
]);
|
|
8890
|
+
},
|
|
8891
|
+
};
|
|
8892
|
+
};
|
|
8893
|
+
|
|
8487
8894
|
/**
|
|
8488
8895
|
* @fileoverview Core TypeScript utility types for mithril-materialized library
|
|
8489
8896
|
* These types improve type safety and developer experience across all components
|
|
@@ -8552,6 +8959,7 @@
|
|
|
8552
8959
|
exports.RadioButton = RadioButton;
|
|
8553
8960
|
exports.RadioButtons = RadioButtons;
|
|
8554
8961
|
exports.RangeInput = RangeInput;
|
|
8962
|
+
exports.Rating = Rating;
|
|
8555
8963
|
exports.RoundIconButton = RoundIconButton;
|
|
8556
8964
|
exports.SearchSelect = SearchSelect;
|
|
8557
8965
|
exports.SecondaryContent = SecondaryContent;
|