mithril-materialized 3.1.0 → 3.2.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/dist/components.css +3 -0
- package/dist/core.css +136 -14
- package/dist/dropdown.d.ts +8 -5
- package/dist/forms.css +136 -0
- package/dist/index.css +144 -14
- package/dist/index.esm.js +593 -394
- package/dist/index.js +593 -393
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +593 -393
- package/dist/input-options.d.ts +10 -2
- package/dist/input.d.ts +1 -1
- package/dist/option.d.ts +16 -3
- package/dist/radio.d.ts +11 -5
- package/dist/search-select.d.ts +14 -20
- package/dist/select.d.ts +10 -6
- package/dist/switch.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +5 -4
- package/sass/components/_dropdown.scss +3 -0
- package/sass/components/forms/_checkboxes.scss +10 -6
- package/sass/components/forms/_forms.scss +0 -15
- package/sass/components/forms/_select.scss +164 -0
- package/sass/components/forms/_switches.scss +1 -0
package/dist/index.esm.js
CHANGED
|
@@ -194,12 +194,13 @@ const Autocomplete = () => {
|
|
|
194
194
|
const state = {
|
|
195
195
|
id: uniqueId(),
|
|
196
196
|
isActive: false,
|
|
197
|
-
|
|
197
|
+
internalValue: '',
|
|
198
198
|
isOpen: false,
|
|
199
199
|
suggestions: [],
|
|
200
200
|
selectedIndex: -1,
|
|
201
201
|
inputElement: null,
|
|
202
202
|
};
|
|
203
|
+
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' && typeof attrs.oninput === 'function';
|
|
203
204
|
const filterSuggestions = (input, data, limit, minLength) => {
|
|
204
205
|
if (!input || input.length < minLength) {
|
|
205
206
|
return [];
|
|
@@ -211,9 +212,16 @@ const Autocomplete = () => {
|
|
|
211
212
|
return filtered;
|
|
212
213
|
};
|
|
213
214
|
const selectSuggestion = (suggestion, attrs) => {
|
|
214
|
-
|
|
215
|
+
const controlled = isControlled(attrs);
|
|
216
|
+
// Update internal state for uncontrolled mode
|
|
217
|
+
if (!controlled) {
|
|
218
|
+
state.internalValue = suggestion.key;
|
|
219
|
+
}
|
|
215
220
|
state.isOpen = false;
|
|
216
221
|
state.selectedIndex = -1;
|
|
222
|
+
if (attrs.oninput) {
|
|
223
|
+
attrs.oninput(suggestion.key);
|
|
224
|
+
}
|
|
217
225
|
if (attrs.onchange) {
|
|
218
226
|
attrs.onchange(suggestion.key);
|
|
219
227
|
}
|
|
@@ -287,7 +295,10 @@ const Autocomplete = () => {
|
|
|
287
295
|
};
|
|
288
296
|
return {
|
|
289
297
|
oninit: ({ attrs }) => {
|
|
290
|
-
|
|
298
|
+
// Initialize internal value for uncontrolled mode
|
|
299
|
+
if (!isControlled(attrs)) {
|
|
300
|
+
state.internalValue = attrs.defaultValue || '';
|
|
301
|
+
}
|
|
291
302
|
document.addEventListener('click', closeDropdown);
|
|
292
303
|
},
|
|
293
304
|
onremove: () => {
|
|
@@ -296,40 +307,54 @@ const Autocomplete = () => {
|
|
|
296
307
|
view: ({ attrs }) => {
|
|
297
308
|
const id = attrs.id || state.id;
|
|
298
309
|
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
|
+
const controlled = isControlled(attrs);
|
|
311
|
+
const currentValue = controlled ? (attrs.value || '') : state.internalValue;
|
|
299
312
|
const cn = newRow ? className + ' clear' : className;
|
|
300
313
|
// Update suggestions when input changes
|
|
301
|
-
state.suggestions = filterSuggestions(
|
|
314
|
+
state.suggestions = filterSuggestions(currentValue, data, limit, minLength);
|
|
302
315
|
// Check if there's a perfect match (exact key match, case-insensitive)
|
|
303
|
-
const hasExactMatch =
|
|
304
|
-
Object.keys(data).some((key) => key.toLowerCase() ===
|
|
316
|
+
const hasExactMatch = currentValue.length >= minLength &&
|
|
317
|
+
Object.keys(data).some((key) => key.toLowerCase() === currentValue.toLowerCase());
|
|
305
318
|
// Only open dropdown if there are suggestions and no perfect match
|
|
306
|
-
state.isOpen = state.suggestions.length > 0 &&
|
|
307
|
-
const replacer = new RegExp(`(${
|
|
319
|
+
state.isOpen = state.suggestions.length > 0 && currentValue.length >= minLength && !hasExactMatch;
|
|
320
|
+
const replacer = new RegExp(`(${currentValue})`, 'i');
|
|
308
321
|
return m('.input-field.autocomplete-wrapper', {
|
|
309
322
|
className: cn,
|
|
310
323
|
style,
|
|
311
324
|
}, [
|
|
312
325
|
iconName ? m('i.material-icons.prefix', iconName) : '',
|
|
313
|
-
m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value:
|
|
326
|
+
m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: controlled ? currentValue : undefined, oncreate: (vnode) => {
|
|
314
327
|
state.inputElement = vnode.dom;
|
|
328
|
+
// Set initial value for uncontrolled mode
|
|
329
|
+
if (!controlled && attrs.defaultValue) {
|
|
330
|
+
vnode.dom.value = attrs.defaultValue;
|
|
331
|
+
}
|
|
315
332
|
}, oninput: (e) => {
|
|
316
333
|
const target = e.target;
|
|
317
|
-
|
|
334
|
+
const inputValue = target.value;
|
|
318
335
|
state.selectedIndex = -1;
|
|
336
|
+
// Update internal state for uncontrolled mode
|
|
337
|
+
if (!controlled) {
|
|
338
|
+
state.internalValue = inputValue;
|
|
339
|
+
}
|
|
340
|
+
// Call oninput and onchange if provided
|
|
341
|
+
if (attrs.oninput) {
|
|
342
|
+
attrs.oninput(inputValue);
|
|
343
|
+
}
|
|
319
344
|
if (onchange) {
|
|
320
|
-
onchange(
|
|
345
|
+
onchange(inputValue);
|
|
321
346
|
}
|
|
322
347
|
}, onkeydown: (e) => {
|
|
323
348
|
handleKeydown(e, attrs);
|
|
324
349
|
// Call original onkeydown if provided
|
|
325
350
|
if (attrs.onkeydown) {
|
|
326
|
-
attrs.onkeydown(e,
|
|
351
|
+
attrs.onkeydown(e, currentValue);
|
|
327
352
|
}
|
|
328
353
|
}, onfocus: () => {
|
|
329
354
|
state.isActive = true;
|
|
330
|
-
if (
|
|
355
|
+
if (currentValue.length >= minLength) {
|
|
331
356
|
// Check for perfect match on focus too
|
|
332
|
-
const hasExactMatch = Object.keys(data).some((key) => key.toLowerCase() ===
|
|
357
|
+
const hasExactMatch = Object.keys(data).some((key) => key.toLowerCase() === currentValue.toLowerCase());
|
|
333
358
|
state.isOpen = state.suggestions.length > 0 && !hasExactMatch;
|
|
334
359
|
}
|
|
335
360
|
}, onblur: (e) => {
|
|
@@ -386,7 +411,7 @@ const Autocomplete = () => {
|
|
|
386
411
|
label,
|
|
387
412
|
id,
|
|
388
413
|
isMandatory,
|
|
389
|
-
isActive: state.isActive ||
|
|
414
|
+
isActive: state.isActive || currentValue.length > 0 || !!attrs.placeholder || !!attrs.value,
|
|
390
415
|
}),
|
|
391
416
|
m(HelperText, { helperText }),
|
|
392
417
|
]);
|
|
@@ -2075,11 +2100,11 @@ const DatePicker = () => {
|
|
|
2075
2100
|
else {
|
|
2076
2101
|
// Single date initialization (original behavior)
|
|
2077
2102
|
let defaultDate = attrs.defaultDate;
|
|
2078
|
-
if (!defaultDate && attrs.
|
|
2079
|
-
defaultDate = new Date(attrs.
|
|
2103
|
+
if (!defaultDate && attrs.defaultValue) {
|
|
2104
|
+
defaultDate = new Date(attrs.defaultValue);
|
|
2080
2105
|
}
|
|
2081
2106
|
if (isDate(defaultDate)) {
|
|
2082
|
-
// Always set the date if we have
|
|
2107
|
+
// Always set the date if we have value or defaultDate
|
|
2083
2108
|
setDate(defaultDate, true, options);
|
|
2084
2109
|
}
|
|
2085
2110
|
else {
|
|
@@ -2348,16 +2373,16 @@ const handleKeyboardNavigation = (key, currentValue, min, max, step) => {
|
|
|
2348
2373
|
}
|
|
2349
2374
|
};
|
|
2350
2375
|
const initRangeState = (state, attrs) => {
|
|
2351
|
-
const { min = 0, max = 100,
|
|
2376
|
+
const { min = 0, max = 100, value, minValue, maxValue } = attrs;
|
|
2352
2377
|
// Initialize single range value
|
|
2353
|
-
if (
|
|
2354
|
-
const currentValue =
|
|
2378
|
+
if (value !== undefined) {
|
|
2379
|
+
const currentValue = value;
|
|
2355
2380
|
if (state.singleValue === undefined) {
|
|
2356
2381
|
state.singleValue = currentValue;
|
|
2357
2382
|
}
|
|
2358
|
-
if (state.
|
|
2359
|
-
state.singleValue =
|
|
2360
|
-
state.
|
|
2383
|
+
if (state.lastValue !== value && !state.hasUserInteracted) {
|
|
2384
|
+
state.singleValue = value;
|
|
2385
|
+
state.lastValue = value;
|
|
2361
2386
|
}
|
|
2362
2387
|
}
|
|
2363
2388
|
else if (state.singleValue === undefined) {
|
|
@@ -2782,41 +2807,48 @@ const TextArea = () => {
|
|
|
2782
2807
|
height: undefined,
|
|
2783
2808
|
active: false,
|
|
2784
2809
|
textarea: undefined,
|
|
2810
|
+
internalValue: '',
|
|
2785
2811
|
};
|
|
2786
2812
|
const updateHeight = (textarea) => {
|
|
2787
2813
|
textarea.style.height = 'auto';
|
|
2788
2814
|
const newHeight = textarea.scrollHeight + 'px';
|
|
2789
2815
|
state.height = textarea.value.length === 0 ? undefined : newHeight;
|
|
2790
2816
|
};
|
|
2817
|
+
const isControlled = (attrs) => attrs.value !== undefined && attrs.oninput !== undefined;
|
|
2791
2818
|
return {
|
|
2819
|
+
oninit: ({ attrs }) => {
|
|
2820
|
+
// Initialize internal value for uncontrolled mode
|
|
2821
|
+
if (!isControlled(attrs)) {
|
|
2822
|
+
state.internalValue = attrs.defaultValue || '';
|
|
2823
|
+
}
|
|
2824
|
+
},
|
|
2792
2825
|
onremove: () => {
|
|
2793
2826
|
},
|
|
2794
2827
|
view: ({ attrs }) => {
|
|
2795
|
-
|
|
2796
|
-
const
|
|
2797
|
-
|
|
2828
|
+
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"]);
|
|
2829
|
+
const controlled = isControlled(attrs);
|
|
2830
|
+
const currentValue = controlled ? value || '' : state.internalValue;
|
|
2798
2831
|
return m('.input-field', { className, style }, [
|
|
2799
2832
|
iconName ? m('i.material-icons.prefix', iconName) : '',
|
|
2800
|
-
m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, style: {
|
|
2833
|
+
m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, value: controlled ? currentValue : undefined, style: {
|
|
2801
2834
|
height: state.height,
|
|
2802
2835
|
}, oncreate: ({ dom }) => {
|
|
2803
2836
|
const textarea = (state.textarea = dom);
|
|
2804
|
-
//
|
|
2805
|
-
if (
|
|
2806
|
-
textarea.value = String(
|
|
2807
|
-
updateHeight(textarea);
|
|
2808
|
-
// } else {
|
|
2809
|
-
// updateHeight(textarea);
|
|
2837
|
+
// For uncontrolled mode, set initial value only
|
|
2838
|
+
if (!controlled && attrs.defaultValue !== undefined) {
|
|
2839
|
+
textarea.value = String(attrs.defaultValue);
|
|
2810
2840
|
}
|
|
2841
|
+
updateHeight(textarea);
|
|
2811
2842
|
// Update character count state for counter component
|
|
2812
2843
|
if (maxLength) {
|
|
2813
|
-
state.currentLength = textarea.value.length;
|
|
2844
|
+
state.currentLength = textarea.value.length;
|
|
2814
2845
|
m.redraw();
|
|
2815
2846
|
}
|
|
2816
2847
|
}, onupdate: ({ dom }) => {
|
|
2817
2848
|
const textarea = dom;
|
|
2818
2849
|
if (state.height)
|
|
2819
2850
|
textarea.style.height = state.height;
|
|
2851
|
+
// No need to manually sync in onupdate since value attribute handles it
|
|
2820
2852
|
}, onfocus: () => {
|
|
2821
2853
|
state.active = true;
|
|
2822
2854
|
}, oninput: (e) => {
|
|
@@ -2830,7 +2862,11 @@ const TextArea = () => {
|
|
|
2830
2862
|
state.currentLength = target.value.length;
|
|
2831
2863
|
state.hasInteracted = target.value.length > 0;
|
|
2832
2864
|
}
|
|
2833
|
-
//
|
|
2865
|
+
// Update internal state for uncontrolled mode
|
|
2866
|
+
if (!controlled) {
|
|
2867
|
+
state.internalValue = target.value;
|
|
2868
|
+
}
|
|
2869
|
+
// Call oninput handler
|
|
2834
2870
|
if (oninput) {
|
|
2835
2871
|
oninput(target.value);
|
|
2836
2872
|
}
|
|
@@ -2862,8 +2898,8 @@ const TextArea = () => {
|
|
|
2862
2898
|
label,
|
|
2863
2899
|
id,
|
|
2864
2900
|
isMandatory,
|
|
2865
|
-
isActive:
|
|
2866
|
-
initialValue:
|
|
2901
|
+
isActive: currentValue || placeholder || state.active,
|
|
2902
|
+
initialValue: currentValue !== '',
|
|
2867
2903
|
}),
|
|
2868
2904
|
m(HelperText, {
|
|
2869
2905
|
helperText,
|
|
@@ -2885,7 +2921,7 @@ const TextArea = () => {
|
|
|
2885
2921
|
const InputField = (type, defaultClass = '') => () => {
|
|
2886
2922
|
const state = {
|
|
2887
2923
|
id: uniqueId(),
|
|
2888
|
-
|
|
2924
|
+
internalValue: undefined,
|
|
2889
2925
|
hasInteracted: false,
|
|
2890
2926
|
isValid: true,
|
|
2891
2927
|
active: false,
|
|
@@ -2897,9 +2933,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2897
2933
|
isDragging: false,
|
|
2898
2934
|
activeThumb: null,
|
|
2899
2935
|
};
|
|
2900
|
-
|
|
2901
|
-
// let lengthUpdateHandler: (() => void) | null = null;
|
|
2902
|
-
// let inputElement: HTMLInputElement | null = null;
|
|
2936
|
+
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' && typeof attrs.oninput === 'function';
|
|
2903
2937
|
const getValue = (target) => {
|
|
2904
2938
|
const val = target.value;
|
|
2905
2939
|
return (val ? (type === 'number' || type === 'range' ? +val : val) : val);
|
|
@@ -2913,21 +2947,20 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2913
2947
|
}
|
|
2914
2948
|
};
|
|
2915
2949
|
const focus = ({ autofocus }) => autofocus ? (typeof autofocus === 'boolean' ? autofocus : autofocus()) : false;
|
|
2916
|
-
const lengthUpdateHandler = () => {
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
};
|
|
2950
|
+
// const lengthUpdateHandler = () => {
|
|
2951
|
+
// const length = state.inputElement?.value.length;
|
|
2952
|
+
// if (length) {
|
|
2953
|
+
// state.currentLength = length;
|
|
2954
|
+
// state.hasInteracted = length > 0;
|
|
2955
|
+
// }
|
|
2956
|
+
// };
|
|
2924
2957
|
const clearInput = (oninput, onchange) => {
|
|
2925
2958
|
if (state.inputElement) {
|
|
2926
2959
|
state.inputElement.value = '';
|
|
2927
2960
|
state.inputElement.focus();
|
|
2928
2961
|
state.active = false;
|
|
2929
|
-
state.currentLength = 0;
|
|
2930
|
-
state.hasInteracted = false;
|
|
2962
|
+
// state.currentLength = 0;
|
|
2963
|
+
// state.hasInteracted = false;
|
|
2931
2964
|
// Trigger oninput and onchange callbacks
|
|
2932
2965
|
const value = getValue(state.inputElement);
|
|
2933
2966
|
if (oninput) {
|
|
@@ -2945,9 +2978,26 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2945
2978
|
// Range slider helper functions
|
|
2946
2979
|
// Range slider rendering functions are now in separate module
|
|
2947
2980
|
return {
|
|
2981
|
+
oninit: ({ attrs }) => {
|
|
2982
|
+
// Initialize internal value if not in controlled mode
|
|
2983
|
+
if (!isControlled(attrs)) {
|
|
2984
|
+
const isNumeric = ['number', 'range'].includes(type);
|
|
2985
|
+
if (attrs.defaultValue !== undefined) {
|
|
2986
|
+
if (isNumeric) {
|
|
2987
|
+
state.internalValue = attrs.defaultValue;
|
|
2988
|
+
}
|
|
2989
|
+
else {
|
|
2990
|
+
state.internalValue = String(attrs.defaultValue);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
else {
|
|
2994
|
+
state.internalValue = (type === 'color' ? '#ff0000' : isNumeric ? undefined : '');
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
},
|
|
2948
2998
|
view: ({ attrs }) => {
|
|
2949
2999
|
var _a, _b;
|
|
2950
|
-
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id,
|
|
3000
|
+
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"]);
|
|
2951
3001
|
// const attributes = toAttrs(params);
|
|
2952
3002
|
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
2953
3003
|
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
@@ -2964,10 +3014,14 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2964
3014
|
isMandatory,
|
|
2965
3015
|
helperText }));
|
|
2966
3016
|
}
|
|
3017
|
+
const isNumeric = ['number', 'range'].includes(type);
|
|
3018
|
+
const controlled = isControlled(attrs);
|
|
3019
|
+
const value = (controlled ? attrs.value : state.internalValue);
|
|
3020
|
+
const rangeType = type === 'range' && !attrs.minmax;
|
|
2967
3021
|
return m('.input-field', { className: cn, style }, [
|
|
2968
3022
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2969
3023
|
m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
|
|
2970
|
-
placeholder, class: type === 'range' && attrs.vertical ? 'range-slider vertical' : undefined, style: type === 'range' && attrs.vertical
|
|
3024
|
+
placeholder, value: controlled ? value : undefined, class: type === 'range' && attrs.vertical ? 'range-slider vertical' : undefined, style: type === 'range' && attrs.vertical
|
|
2971
3025
|
? {
|
|
2972
3026
|
height: attrs.height || '200px',
|
|
2973
3027
|
width: '6px',
|
|
@@ -2981,25 +3035,9 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2981
3035
|
if (focus(attrs)) {
|
|
2982
3036
|
input.focus();
|
|
2983
3037
|
}
|
|
2984
|
-
//
|
|
2985
|
-
if (
|
|
2986
|
-
input.value = String(
|
|
2987
|
-
}
|
|
2988
|
-
// Update character count state for counter component
|
|
2989
|
-
if (maxLength) {
|
|
2990
|
-
state.currentLength = input.value.length; // Initial count
|
|
2991
|
-
}
|
|
2992
|
-
// Range input functionality
|
|
2993
|
-
if (type === 'range' && !attrs.minmax) {
|
|
2994
|
-
const updateThumb = () => {
|
|
2995
|
-
const value = input.value;
|
|
2996
|
-
const min = input.min || '0';
|
|
2997
|
-
const max = input.max || '100';
|
|
2998
|
-
const percentage = ((parseFloat(value) - parseFloat(min)) / (parseFloat(max) - parseFloat(min))) * 100;
|
|
2999
|
-
input.style.setProperty('--range-progress', `${percentage}%`);
|
|
3000
|
-
};
|
|
3001
|
-
input.addEventListener('input', updateThumb);
|
|
3002
|
-
updateThumb(); // Initial position
|
|
3038
|
+
// For uncontrolled mode, set initial value only
|
|
3039
|
+
if (!controlled && attrs.defaultValue !== undefined) {
|
|
3040
|
+
input.value = String(attrs.defaultValue);
|
|
3003
3041
|
}
|
|
3004
3042
|
}, onkeyup: onkeyup
|
|
3005
3043
|
? (ev) => {
|
|
@@ -3013,21 +3051,25 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3013
3051
|
? (ev) => {
|
|
3014
3052
|
onkeypress(ev, getValue(ev.target));
|
|
3015
3053
|
}
|
|
3016
|
-
: undefined, onupdate: validate
|
|
3017
|
-
? ({ dom }) => {
|
|
3018
|
-
const target = dom;
|
|
3019
|
-
setValidity(target, validate(getValue(target), target));
|
|
3020
|
-
}
|
|
3021
3054
|
: undefined, oninput: (e) => {
|
|
3022
3055
|
state.active = true;
|
|
3056
|
+
state.hasInteracted = false;
|
|
3023
3057
|
const target = e.target;
|
|
3024
3058
|
// Handle original oninput logic
|
|
3025
|
-
const
|
|
3059
|
+
const inputValue = getValue(target);
|
|
3060
|
+
// Update internal state for uncontrolled mode
|
|
3061
|
+
if (!controlled) {
|
|
3062
|
+
state.internalValue = inputValue;
|
|
3063
|
+
}
|
|
3026
3064
|
if (oninput) {
|
|
3027
|
-
oninput(
|
|
3065
|
+
oninput(inputValue);
|
|
3028
3066
|
}
|
|
3029
|
-
if (
|
|
3030
|
-
|
|
3067
|
+
if (rangeType) {
|
|
3068
|
+
const value = target.value;
|
|
3069
|
+
const min = parseFloat(target.min || '0');
|
|
3070
|
+
const max = parseFloat(target.max || '100');
|
|
3071
|
+
const percentage = Math.round((100 * (parseFloat(value) - min)) / (max - min));
|
|
3072
|
+
target.style.setProperty('--range-progress', `${percentage}%`);
|
|
3031
3073
|
}
|
|
3032
3074
|
// Don't validate on input, only clear error states if user is typing
|
|
3033
3075
|
if (validate && target.classList.contains('invalid') && target.value.length > 0) {
|
|
@@ -3043,6 +3085,14 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3043
3085
|
state.isValid = true;
|
|
3044
3086
|
}
|
|
3045
3087
|
}
|
|
3088
|
+
else if ((type === 'email' || type === 'url') && target.classList.contains('invalid') && target.value.length > 0) {
|
|
3089
|
+
// Clear native validation errors if user is typing and input becomes valid
|
|
3090
|
+
if (target.validity.valid) {
|
|
3091
|
+
target.classList.remove('invalid');
|
|
3092
|
+
target.classList.add('valid');
|
|
3093
|
+
state.isValid = true;
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3046
3096
|
}, onfocus: () => {
|
|
3047
3097
|
state.active = true;
|
|
3048
3098
|
}, onblur: (e) => {
|
|
@@ -3079,6 +3129,48 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3079
3129
|
state.isValid = true;
|
|
3080
3130
|
}
|
|
3081
3131
|
}
|
|
3132
|
+
else if (type === 'email' || type === 'url') {
|
|
3133
|
+
// Use browser's native HTML5 validation for email and url types
|
|
3134
|
+
const value = getValue(target);
|
|
3135
|
+
if (value && String(value).length > 0) {
|
|
3136
|
+
state.isValid = target.validity.valid;
|
|
3137
|
+
target.setCustomValidity(''); // Clear any custom validation message
|
|
3138
|
+
if (state.isValid) {
|
|
3139
|
+
target.classList.remove('invalid');
|
|
3140
|
+
target.classList.add('valid');
|
|
3141
|
+
}
|
|
3142
|
+
else {
|
|
3143
|
+
target.classList.remove('valid');
|
|
3144
|
+
target.classList.add('invalid');
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
else {
|
|
3148
|
+
// Clear validation state if no text
|
|
3149
|
+
target.classList.remove('valid', 'invalid');
|
|
3150
|
+
state.isValid = true;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
else if (isNumeric) {
|
|
3154
|
+
// Use browser's native HTML5 validation for numeric inputs (handles min, max, step, etc.)
|
|
3155
|
+
const value = getValue(target);
|
|
3156
|
+
if (value !== undefined && value !== null && !isNaN(Number(value))) {
|
|
3157
|
+
state.isValid = target.validity.valid;
|
|
3158
|
+
target.setCustomValidity(''); // Clear any custom validation message
|
|
3159
|
+
if (state.isValid) {
|
|
3160
|
+
target.classList.remove('invalid');
|
|
3161
|
+
target.classList.add('valid');
|
|
3162
|
+
}
|
|
3163
|
+
else {
|
|
3164
|
+
target.classList.remove('valid');
|
|
3165
|
+
target.classList.add('invalid');
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
else {
|
|
3169
|
+
// Clear validation state if no valid number
|
|
3170
|
+
target.classList.remove('valid', 'invalid');
|
|
3171
|
+
state.isValid = true;
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3082
3174
|
// Also call the original onblur handler if provided
|
|
3083
3175
|
if (attrs.onblur) {
|
|
3084
3176
|
attrs.onblur(e);
|
|
@@ -3104,18 +3196,18 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3104
3196
|
id,
|
|
3105
3197
|
isMandatory,
|
|
3106
3198
|
isActive,
|
|
3107
|
-
initialValue:
|
|
3199
|
+
initialValue: value !== undefined && value !== '',
|
|
3108
3200
|
}),
|
|
3109
3201
|
m(HelperText, {
|
|
3110
3202
|
helperText,
|
|
3111
3203
|
dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
|
|
3112
3204
|
dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
|
|
3113
3205
|
}),
|
|
3114
|
-
maxLength
|
|
3206
|
+
maxLength && typeof value === 'string'
|
|
3115
3207
|
? m(CharacterCounter, {
|
|
3116
|
-
currentLength:
|
|
3208
|
+
currentLength: value.length,
|
|
3117
3209
|
maxLength,
|
|
3118
|
-
show:
|
|
3210
|
+
show: value.length > 0,
|
|
3119
3211
|
})
|
|
3120
3212
|
: undefined,
|
|
3121
3213
|
]);
|
|
@@ -3142,7 +3234,7 @@ const FileInput = () => {
|
|
|
3142
3234
|
let i;
|
|
3143
3235
|
return {
|
|
3144
3236
|
view: ({ attrs }) => {
|
|
3145
|
-
const { multiple, disabled,
|
|
3237
|
+
const { multiple, disabled, value, placeholder, onchange, className = 'col s12', accept: acceptedFiles, label = 'File', } = attrs;
|
|
3146
3238
|
const accept = acceptedFiles
|
|
3147
3239
|
? acceptedFiles instanceof Array
|
|
3148
3240
|
? acceptedFiles.join(', ')
|
|
@@ -3173,8 +3265,8 @@ const FileInput = () => {
|
|
|
3173
3265
|
placeholder,
|
|
3174
3266
|
oncreate: ({ dom }) => {
|
|
3175
3267
|
i = dom;
|
|
3176
|
-
if (
|
|
3177
|
-
i.value =
|
|
3268
|
+
if (value)
|
|
3269
|
+
i.value = value;
|
|
3178
3270
|
},
|
|
3179
3271
|
})),
|
|
3180
3272
|
(canClear || (i === null || i === void 0 ? void 0 : i.value)) &&
|
|
@@ -3201,11 +3293,14 @@ const FileInput = () => {
|
|
|
3201
3293
|
|
|
3202
3294
|
/** Component to show a check box */
|
|
3203
3295
|
const InputCheckbox = () => {
|
|
3296
|
+
let checkboxId;
|
|
3204
3297
|
return {
|
|
3205
3298
|
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3206
|
-
|
|
3299
|
+
if (!checkboxId)
|
|
3300
|
+
checkboxId = inputId || uniqueId();
|
|
3207
3301
|
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3208
3302
|
m('input[type=checkbox][tabindex=0]', {
|
|
3303
|
+
className: disabled ? 'disabled' : undefined,
|
|
3209
3304
|
id: checkboxId,
|
|
3210
3305
|
checked,
|
|
3211
3306
|
disabled,
|
|
@@ -3222,78 +3317,79 @@ const InputCheckbox = () => {
|
|
|
3222
3317
|
},
|
|
3223
3318
|
};
|
|
3224
3319
|
};
|
|
3320
|
+
/** Reusable layout component for rendering options horizontally or vertically */
|
|
3321
|
+
const OptionsList = {
|
|
3322
|
+
view: ({ attrs: { options, layout } }) => {
|
|
3323
|
+
const optionElements = options.map(({ component, props, key }) => m(component, Object.assign(Object.assign({}, props), { key })));
|
|
3324
|
+
return layout === 'horizontal'
|
|
3325
|
+
? m('div.grid-container', optionElements)
|
|
3326
|
+
: optionElements;
|
|
3327
|
+
},
|
|
3328
|
+
};
|
|
3225
3329
|
/** A list of checkboxes */
|
|
3226
3330
|
const Options = () => {
|
|
3227
|
-
const state = {
|
|
3228
|
-
|
|
3229
|
-
|
|
3331
|
+
const state = {
|
|
3332
|
+
componentId: '',
|
|
3333
|
+
};
|
|
3334
|
+
const selectAll = (options, onchange) => {
|
|
3230
3335
|
const allIds = options.map((option) => option.id);
|
|
3231
|
-
|
|
3232
|
-
if (callback)
|
|
3233
|
-
callback(allIds);
|
|
3336
|
+
onchange && onchange(allIds);
|
|
3234
3337
|
};
|
|
3235
|
-
const selectNone = (
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3338
|
+
const selectNone = (onchange) => {
|
|
3339
|
+
onchange && onchange([]);
|
|
3340
|
+
};
|
|
3341
|
+
const handleChange = (propId, checked, checkedIds, onchange) => {
|
|
3342
|
+
const newCheckedIds = checkedIds.filter((i) => i !== propId);
|
|
3343
|
+
if (checked) {
|
|
3344
|
+
newCheckedIds.push(propId);
|
|
3345
|
+
}
|
|
3346
|
+
onchange && onchange(newCheckedIds);
|
|
3239
3347
|
};
|
|
3240
3348
|
return {
|
|
3241
|
-
oninit: ({ attrs
|
|
3242
|
-
|
|
3243
|
-
state.checkedId = checkedId;
|
|
3244
|
-
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3245
|
-
state.componentId = id || uniqueId();
|
|
3349
|
+
oninit: ({ attrs }) => {
|
|
3350
|
+
state.componentId = attrs.id || uniqueId();
|
|
3246
3351
|
},
|
|
3247
|
-
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false,
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
if (checked) {
|
|
3252
|
-
checkedIds.push(propId);
|
|
3253
|
-
}
|
|
3254
|
-
state.checkedIds = checkedIds;
|
|
3255
|
-
callback(checkedIds);
|
|
3256
|
-
}
|
|
3257
|
-
: undefined;
|
|
3352
|
+
view: ({ attrs: { checkedId, label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, selectAllText = 'Select All', selectNoneText = 'Select None', onchange, }, }) => {
|
|
3353
|
+
// Derive checked IDs from props
|
|
3354
|
+
const checkedIds = checkedId !== undefined ? (Array.isArray(checkedId) ? checkedId : [checkedId]) : [];
|
|
3355
|
+
const isChecked = (id) => checkedIds.includes(id);
|
|
3258
3356
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
3259
|
-
const
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
label: option.label,
|
|
3263
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3264
|
-
className: option.className || checkboxClass,
|
|
3265
|
-
checked: isChecked(option.id),
|
|
3266
|
-
description: option.description,
|
|
3267
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3268
|
-
})))
|
|
3269
|
-
: options.map((option) => m(InputCheckbox, {
|
|
3357
|
+
const optionItems = options.map((option) => ({
|
|
3358
|
+
component: InputCheckbox,
|
|
3359
|
+
props: {
|
|
3270
3360
|
disabled: disabled || option.disabled,
|
|
3271
3361
|
label: option.label,
|
|
3272
|
-
onchange: onchange ? (v) =>
|
|
3362
|
+
onchange: onchange ? (v) => handleChange(option.id, v, checkedIds, onchange) : undefined,
|
|
3273
3363
|
className: option.className || checkboxClass,
|
|
3274
3364
|
checked: isChecked(option.id),
|
|
3275
3365
|
description: option.description,
|
|
3276
3366
|
inputId: `${state.componentId}-${option.id}`,
|
|
3277
|
-
}
|
|
3367
|
+
},
|
|
3368
|
+
key: option.id,
|
|
3369
|
+
}));
|
|
3370
|
+
const optionsContent = m(OptionsList, {
|
|
3371
|
+
options: optionItems,
|
|
3372
|
+
layout,
|
|
3373
|
+
});
|
|
3278
3374
|
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3279
3375
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3280
3376
|
showSelectAll &&
|
|
3281
|
-
m('div.select-all-controls', { style:
|
|
3377
|
+
m('div.select-all-controls', { style: { marginBottom: '10px' } }, [
|
|
3282
3378
|
m('a', {
|
|
3283
3379
|
href: '#',
|
|
3284
3380
|
onclick: (e) => {
|
|
3285
3381
|
e.preventDefault();
|
|
3286
|
-
selectAll(options,
|
|
3382
|
+
selectAll(options, onchange);
|
|
3287
3383
|
},
|
|
3288
|
-
style:
|
|
3289
|
-
},
|
|
3384
|
+
style: { marginRight: '15px' },
|
|
3385
|
+
}, selectAllText),
|
|
3290
3386
|
m('a', {
|
|
3291
3387
|
href: '#',
|
|
3292
3388
|
onclick: (e) => {
|
|
3293
3389
|
e.preventDefault();
|
|
3294
|
-
selectNone(
|
|
3390
|
+
selectNone(onchange);
|
|
3295
3391
|
},
|
|
3296
|
-
},
|
|
3392
|
+
}, selectNoneText),
|
|
3297
3393
|
]),
|
|
3298
3394
|
description && m(HelperText, { helperText: description }),
|
|
3299
3395
|
m('form', { action: '#' }, optionsContent),
|
|
@@ -3888,11 +3984,19 @@ const DataTable = () => {
|
|
|
3888
3984
|
const Dropdown = () => {
|
|
3889
3985
|
const state = {
|
|
3890
3986
|
isOpen: false,
|
|
3891
|
-
initialValue: undefined,
|
|
3892
3987
|
id: '',
|
|
3893
3988
|
focusedIndex: -1,
|
|
3894
3989
|
inputRef: null,
|
|
3895
3990
|
dropdownRef: null,
|
|
3991
|
+
internalCheckedId: undefined,
|
|
3992
|
+
};
|
|
3993
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
3994
|
+
const closeDropdown = (e) => {
|
|
3995
|
+
const target = e.target;
|
|
3996
|
+
if (!target.closest('.dropdown-wrapper.input-field')) {
|
|
3997
|
+
state.isOpen = false;
|
|
3998
|
+
m.redraw();
|
|
3999
|
+
}
|
|
3896
4000
|
};
|
|
3897
4001
|
const handleKeyDown = (e, items, onchange) => {
|
|
3898
4002
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3901,7 +4005,7 @@ const Dropdown = () => {
|
|
|
3901
4005
|
e.preventDefault();
|
|
3902
4006
|
if (!state.isOpen) {
|
|
3903
4007
|
state.isOpen = true;
|
|
3904
|
-
state.focusedIndex = 0;
|
|
4008
|
+
state.focusedIndex = availableItems.length > 0 ? 0 : -1;
|
|
3905
4009
|
}
|
|
3906
4010
|
else {
|
|
3907
4011
|
state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
|
|
@@ -3919,15 +4023,13 @@ const Dropdown = () => {
|
|
|
3919
4023
|
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
3920
4024
|
const selectedItem = availableItems[state.focusedIndex];
|
|
3921
4025
|
const value = (selectedItem.id || selectedItem.label);
|
|
3922
|
-
state.initialValue = value;
|
|
3923
4026
|
state.isOpen = false;
|
|
3924
4027
|
state.focusedIndex = -1;
|
|
3925
|
-
|
|
3926
|
-
onchange(value);
|
|
4028
|
+
return value; // Return value to be handled in view
|
|
3927
4029
|
}
|
|
3928
4030
|
else if (!state.isOpen) {
|
|
3929
4031
|
state.isOpen = true;
|
|
3930
|
-
state.focusedIndex = 0;
|
|
4032
|
+
state.focusedIndex = availableItems.length > 0 ? 0 : -1;
|
|
3931
4033
|
}
|
|
3932
4034
|
break;
|
|
3933
4035
|
case 'Escape':
|
|
@@ -3938,15 +4040,36 @@ const Dropdown = () => {
|
|
|
3938
4040
|
}
|
|
3939
4041
|
};
|
|
3940
4042
|
return {
|
|
3941
|
-
oninit: ({ attrs
|
|
3942
|
-
|
|
3943
|
-
state.
|
|
3944
|
-
//
|
|
4043
|
+
oninit: ({ attrs }) => {
|
|
4044
|
+
var _a;
|
|
4045
|
+
state.id = ((_a = attrs.id) === null || _a === void 0 ? void 0 : _a.toString()) || uniqueId();
|
|
4046
|
+
// Initialize internal state for uncontrolled mode
|
|
4047
|
+
if (!isControlled(attrs)) {
|
|
4048
|
+
state.internalCheckedId = attrs.defaultCheckedId;
|
|
4049
|
+
}
|
|
4050
|
+
// Add global click listener to close dropdown
|
|
4051
|
+
document.addEventListener('click', closeDropdown);
|
|
3945
4052
|
},
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
4053
|
+
onremove: () => {
|
|
4054
|
+
// Cleanup global listener
|
|
4055
|
+
document.removeEventListener('click', closeDropdown);
|
|
4056
|
+
},
|
|
4057
|
+
view: ({ attrs }) => {
|
|
4058
|
+
const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
|
|
4059
|
+
const controlled = isControlled(attrs);
|
|
4060
|
+
const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
|
|
4061
|
+
const handleSelection = (value) => {
|
|
4062
|
+
// Update internal state for uncontrolled mode
|
|
4063
|
+
if (!controlled) {
|
|
4064
|
+
state.internalCheckedId = value;
|
|
4065
|
+
}
|
|
4066
|
+
// Call onchange if provided
|
|
4067
|
+
if (onchange) {
|
|
4068
|
+
onchange(value);
|
|
4069
|
+
}
|
|
4070
|
+
};
|
|
4071
|
+
const selectedItem = currentCheckedId
|
|
4072
|
+
? items.filter((i) => (i.id ? i.id === currentCheckedId : i.label === currentCheckedId)).shift()
|
|
3950
4073
|
: undefined;
|
|
3951
4074
|
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
3952
4075
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3954,13 +4077,12 @@ const Dropdown = () => {
|
|
|
3954
4077
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3955
4078
|
m(HelperText, { helperText }),
|
|
3956
4079
|
m('.select-wrapper', {
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
|
|
4080
|
+
onkeydown: disabled ? undefined : (e) => {
|
|
4081
|
+
const selectedValue = handleKeyDown(e, items);
|
|
4082
|
+
if (selectedValue) {
|
|
4083
|
+
handleSelection(selectedValue);
|
|
4084
|
+
}
|
|
4085
|
+
},
|
|
3964
4086
|
tabindex: disabled ? -1 : 0,
|
|
3965
4087
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
3966
4088
|
'aria-haspopup': 'listbox',
|
|
@@ -3977,7 +4099,8 @@ const Dropdown = () => {
|
|
|
3977
4099
|
e.stopPropagation();
|
|
3978
4100
|
if (!disabled) {
|
|
3979
4101
|
state.isOpen = !state.isOpen;
|
|
3980
|
-
|
|
4102
|
+
// Reset focus index when opening/closing
|
|
4103
|
+
state.focusedIndex = -1;
|
|
3981
4104
|
}
|
|
3982
4105
|
},
|
|
3983
4106
|
}),
|
|
@@ -3993,36 +4116,31 @@ const Dropdown = () => {
|
|
|
3993
4116
|
onremove: () => {
|
|
3994
4117
|
state.dropdownRef = null;
|
|
3995
4118
|
},
|
|
3996
|
-
style: getDropdownStyles(state.inputRef, true, items
|
|
3997
|
-
|
|
3998
|
-
group: undefined }))), true),
|
|
3999
|
-
}, items.map((item, index) => {
|
|
4119
|
+
style: getDropdownStyles(state.inputRef, true, items, true),
|
|
4120
|
+
}, items.map((item) => {
|
|
4000
4121
|
if (item.divider) {
|
|
4001
|
-
return m('li.divider'
|
|
4002
|
-
key: `divider-${index}`,
|
|
4003
|
-
});
|
|
4122
|
+
return m('li.divider');
|
|
4004
4123
|
}
|
|
4005
4124
|
const itemIndex = availableItems.indexOf(item);
|
|
4006
4125
|
const isFocused = itemIndex === state.focusedIndex;
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4126
|
+
const className = [
|
|
4127
|
+
item.disabled ? 'disabled' : '',
|
|
4128
|
+
isFocused ? 'focused' : '',
|
|
4129
|
+
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
4130
|
+
]
|
|
4131
|
+
.filter(Boolean)
|
|
4132
|
+
.join(' ') || undefined;
|
|
4133
|
+
return m('li', {
|
|
4134
|
+
className,
|
|
4135
|
+
onclick: item.disabled
|
|
4136
|
+
? undefined
|
|
4137
|
+
: () => {
|
|
4018
4138
|
const value = (item.id || item.label);
|
|
4019
|
-
state.initialValue = value;
|
|
4020
4139
|
state.isOpen = false;
|
|
4021
4140
|
state.focusedIndex = -1;
|
|
4022
|
-
|
|
4023
|
-
onchange(value);
|
|
4141
|
+
handleSelection(value);
|
|
4024
4142
|
},
|
|
4025
|
-
|
|
4143
|
+
}, m('span', {
|
|
4026
4144
|
style: {
|
|
4027
4145
|
display: 'flex',
|
|
4028
4146
|
alignItems: 'center',
|
|
@@ -5159,9 +5277,9 @@ const TimePicker = () => {
|
|
|
5159
5277
|
dx: 0,
|
|
5160
5278
|
dy: 0,
|
|
5161
5279
|
};
|
|
5162
|
-
// Handle
|
|
5163
|
-
if (attrs.
|
|
5164
|
-
updateTimeFromInput(attrs.
|
|
5280
|
+
// Handle value after options are set
|
|
5281
|
+
if (attrs.defaultValue) {
|
|
5282
|
+
updateTimeFromInput(attrs.defaultValue);
|
|
5165
5283
|
}
|
|
5166
5284
|
},
|
|
5167
5285
|
onremove: () => {
|
|
@@ -5443,32 +5561,47 @@ const RadioButton = () => ({
|
|
|
5443
5561
|
},
|
|
5444
5562
|
});
|
|
5445
5563
|
/** Component to show a list of radio buttons, from which you can choose one. */
|
|
5446
|
-
// export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
|
|
5447
5564
|
const RadioButtons = () => {
|
|
5448
|
-
const state = {
|
|
5565
|
+
const state = {
|
|
5566
|
+
groupId: uniqueId(),
|
|
5567
|
+
componentId: '',
|
|
5568
|
+
internalCheckedId: undefined,
|
|
5569
|
+
};
|
|
5570
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5449
5571
|
return {
|
|
5450
|
-
oninit: ({ attrs
|
|
5451
|
-
state.
|
|
5452
|
-
state
|
|
5453
|
-
|
|
5572
|
+
oninit: ({ attrs }) => {
|
|
5573
|
+
state.componentId = attrs.id || uniqueId();
|
|
5574
|
+
// Initialize internal state for uncontrolled mode
|
|
5575
|
+
if (!isControlled(attrs)) {
|
|
5576
|
+
state.internalCheckedId = attrs.defaultCheckedId;
|
|
5577
|
+
}
|
|
5454
5578
|
},
|
|
5455
|
-
view: ({ attrs
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
const
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5579
|
+
view: ({ attrs }) => {
|
|
5580
|
+
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange, } = attrs;
|
|
5581
|
+
const { groupId, componentId } = state;
|
|
5582
|
+
const controlled = isControlled(attrs);
|
|
5583
|
+
// Get current checked ID from props or internal state
|
|
5584
|
+
const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
|
|
5585
|
+
const handleChange = (id) => {
|
|
5586
|
+
// Update internal state for uncontrolled mode
|
|
5587
|
+
if (!controlled) {
|
|
5588
|
+
state.internalCheckedId = id;
|
|
5589
|
+
}
|
|
5590
|
+
// Call onchange if provided
|
|
5591
|
+
if (onchange) {
|
|
5592
|
+
onchange(id);
|
|
5464
5593
|
}
|
|
5465
5594
|
};
|
|
5466
5595
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5467
|
-
const
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
:
|
|
5471
|
-
|
|
5596
|
+
const radioItems = options.map((r) => ({
|
|
5597
|
+
component: (RadioButton),
|
|
5598
|
+
props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}` }),
|
|
5599
|
+
key: r.id,
|
|
5600
|
+
}));
|
|
5601
|
+
const optionsContent = m(OptionsList, {
|
|
5602
|
+
options: radioItems,
|
|
5603
|
+
layout,
|
|
5604
|
+
});
|
|
5472
5605
|
return m('div', { id: componentId, className: cn }, [
|
|
5473
5606
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
5474
5607
|
description && m('p.helper-text', m.trust(description)),
|
|
@@ -5483,30 +5616,42 @@ const Select = () => {
|
|
|
5483
5616
|
const state = {
|
|
5484
5617
|
id: '',
|
|
5485
5618
|
isOpen: false,
|
|
5486
|
-
selectedIds: [],
|
|
5487
5619
|
focusedIndex: -1,
|
|
5488
5620
|
inputRef: null,
|
|
5489
5621
|
dropdownRef: null,
|
|
5622
|
+
internalSelectedIds: [],
|
|
5490
5623
|
};
|
|
5624
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5491
5625
|
const isSelected = (id, selectedIds) => {
|
|
5492
5626
|
return selectedIds.some((selectedId) => selectedId === id);
|
|
5493
5627
|
};
|
|
5494
5628
|
const toggleOption = (id, multiple, attrs) => {
|
|
5629
|
+
const controlled = isControlled(attrs);
|
|
5630
|
+
// Get current selected IDs from props or internal state
|
|
5631
|
+
const currentSelectedIds = controlled
|
|
5632
|
+
? attrs.checkedId !== undefined
|
|
5633
|
+
? Array.isArray(attrs.checkedId)
|
|
5634
|
+
? attrs.checkedId
|
|
5635
|
+
: [attrs.checkedId]
|
|
5636
|
+
: []
|
|
5637
|
+
: state.internalSelectedIds;
|
|
5638
|
+
let newIds;
|
|
5495
5639
|
if (multiple) {
|
|
5496
|
-
|
|
5497
|
-
?
|
|
5498
|
-
|
|
5499
|
-
: [...state.selectedIds, id];
|
|
5500
|
-
state.selectedIds = newIds;
|
|
5501
|
-
attrs.onchange(newIds);
|
|
5502
|
-
console.log(newIds);
|
|
5503
|
-
// Keep dropdown open for multiple select
|
|
5640
|
+
newIds = currentSelectedIds.includes(id)
|
|
5641
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
5642
|
+
: [...currentSelectedIds, id];
|
|
5504
5643
|
}
|
|
5505
5644
|
else {
|
|
5506
|
-
|
|
5507
|
-
// Close dropdown for single select
|
|
5508
|
-
|
|
5509
|
-
|
|
5645
|
+
newIds = [id];
|
|
5646
|
+
state.isOpen = false; // Close dropdown for single select
|
|
5647
|
+
}
|
|
5648
|
+
// Update internal state for uncontrolled mode
|
|
5649
|
+
if (!controlled) {
|
|
5650
|
+
state.internalSelectedIds = newIds;
|
|
5651
|
+
}
|
|
5652
|
+
// Call onchange if provided
|
|
5653
|
+
if (attrs.onchange) {
|
|
5654
|
+
attrs.onchange(newIds);
|
|
5510
5655
|
}
|
|
5511
5656
|
};
|
|
5512
5657
|
const handleKeyDown = (e, attrs) => {
|
|
@@ -5558,88 +5703,22 @@ const Select = () => {
|
|
|
5558
5703
|
};
|
|
5559
5704
|
const closeDropdown = (e) => {
|
|
5560
5705
|
const target = e.target;
|
|
5561
|
-
if (!target.closest('.select-
|
|
5706
|
+
if (!target.closest('.input-field.select-space')) {
|
|
5562
5707
|
state.isOpen = false;
|
|
5563
5708
|
m.redraw();
|
|
5564
5709
|
}
|
|
5565
5710
|
};
|
|
5566
|
-
const renderGroupedOptions = (options, multiple, attrs) => {
|
|
5567
|
-
const groupedOptions = {};
|
|
5568
|
-
const ungroupedOptions = [];
|
|
5569
|
-
// Group options by their group property
|
|
5570
|
-
options.forEach((option) => {
|
|
5571
|
-
if (option.group) {
|
|
5572
|
-
if (!groupedOptions[option.group]) {
|
|
5573
|
-
groupedOptions[option.group] = [];
|
|
5574
|
-
}
|
|
5575
|
-
groupedOptions[option.group].push(option);
|
|
5576
|
-
}
|
|
5577
|
-
else {
|
|
5578
|
-
ungroupedOptions.push(option);
|
|
5579
|
-
}
|
|
5580
|
-
});
|
|
5581
|
-
const renderElements = [];
|
|
5582
|
-
// Render ungrouped options first
|
|
5583
|
-
ungroupedOptions.forEach((option) => {
|
|
5584
|
-
renderElements.push(m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === options.indexOf(option) ? 'focused' : '' }, (option.disabled
|
|
5585
|
-
? {}
|
|
5586
|
-
: {
|
|
5587
|
-
onclick: (e) => {
|
|
5588
|
-
e.stopPropagation();
|
|
5589
|
-
toggleOption(option.id, multiple, attrs);
|
|
5590
|
-
},
|
|
5591
|
-
})), m('span', multiple
|
|
5592
|
-
? m('label', { for: option.id }, m('input', {
|
|
5593
|
-
id: option.id,
|
|
5594
|
-
type: 'checkbox',
|
|
5595
|
-
checked: state.selectedIds.includes(option.id),
|
|
5596
|
-
disabled: option.disabled ? true : undefined,
|
|
5597
|
-
onclick: (e) => {
|
|
5598
|
-
e.stopPropagation();
|
|
5599
|
-
},
|
|
5600
|
-
}), m('span', option.label))
|
|
5601
|
-
: option.label)));
|
|
5602
|
-
});
|
|
5603
|
-
// Render grouped options
|
|
5604
|
-
Object.keys(groupedOptions).forEach((groupName) => {
|
|
5605
|
-
// Add group header
|
|
5606
|
-
renderElements.push(m('li.optgroup', { tabindex: 0 }, m('span', groupName)));
|
|
5607
|
-
// Add group options
|
|
5608
|
-
groupedOptions[groupName].forEach((option) => {
|
|
5609
|
-
renderElements.push(m('li', Object.assign({ class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, state.selectedIds) ? ' selected' : ''}${state.focusedIndex === options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5610
|
-
? {}
|
|
5611
|
-
: {
|
|
5612
|
-
onclick: (e) => {
|
|
5613
|
-
e.stopPropagation();
|
|
5614
|
-
toggleOption(option.id, multiple, attrs);
|
|
5615
|
-
},
|
|
5616
|
-
})), m('span', multiple
|
|
5617
|
-
? m('label', { for: option.id }, m('input', {
|
|
5618
|
-
id: option.id,
|
|
5619
|
-
type: 'checkbox',
|
|
5620
|
-
checked: state.selectedIds.includes(option.id),
|
|
5621
|
-
disabled: option.disabled ? true : undefined,
|
|
5622
|
-
onclick: (e) => {
|
|
5623
|
-
e.stopPropagation();
|
|
5624
|
-
},
|
|
5625
|
-
}), m('span', option.label))
|
|
5626
|
-
: option.label)));
|
|
5627
|
-
});
|
|
5628
|
-
});
|
|
5629
|
-
return renderElements;
|
|
5630
|
-
};
|
|
5631
5711
|
return {
|
|
5632
5712
|
oninit: ({ attrs }) => {
|
|
5633
|
-
|
|
5634
|
-
state
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
}
|
|
5713
|
+
state.id = attrs.id || uniqueId();
|
|
5714
|
+
// Initialize internal state for uncontrolled mode
|
|
5715
|
+
if (!isControlled(attrs)) {
|
|
5716
|
+
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
5717
|
+
? Array.isArray(attrs.defaultCheckedId)
|
|
5718
|
+
? attrs.defaultCheckedId
|
|
5719
|
+
: [attrs.defaultCheckedId]
|
|
5720
|
+
: [];
|
|
5721
|
+
state.internalSelectedIds = defaultIds;
|
|
5643
5722
|
}
|
|
5644
5723
|
// Add global click listener to close dropdown
|
|
5645
5724
|
document.addEventListener('click', closeDropdown);
|
|
@@ -5649,17 +5728,18 @@ const Select = () => {
|
|
|
5649
5728
|
document.removeEventListener('click', closeDropdown);
|
|
5650
5729
|
},
|
|
5651
5730
|
view: ({ attrs }) => {
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5731
|
+
const controlled = isControlled(attrs);
|
|
5732
|
+
// Get selected IDs from props or internal state
|
|
5733
|
+
const selectedIds = controlled
|
|
5734
|
+
? attrs.checkedId !== undefined
|
|
5735
|
+
? Array.isArray(attrs.checkedId)
|
|
5736
|
+
? attrs.checkedId
|
|
5737
|
+
: [attrs.checkedId]
|
|
5738
|
+
: []
|
|
5739
|
+
: state.internalSelectedIds;
|
|
5660
5740
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
|
|
5661
5741
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5662
|
-
const selectedOptions = options.filter((opt) => isSelected(opt.id,
|
|
5742
|
+
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
5663
5743
|
return m('.input-field.select-space', {
|
|
5664
5744
|
className: finalClassName,
|
|
5665
5745
|
key,
|
|
@@ -5668,11 +5748,6 @@ const Select = () => {
|
|
|
5668
5748
|
// Icon prefix
|
|
5669
5749
|
iconName && m('i.material-icons.prefix', iconName),
|
|
5670
5750
|
m('.select-wrapper', {
|
|
5671
|
-
onclick: disabled
|
|
5672
|
-
? undefined
|
|
5673
|
-
: () => {
|
|
5674
|
-
state.isOpen = !state.isOpen;
|
|
5675
|
-
},
|
|
5676
5751
|
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
|
|
5677
5752
|
tabindex: disabled ? -1 : 0,
|
|
5678
5753
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
@@ -5688,7 +5763,9 @@ const Select = () => {
|
|
|
5688
5763
|
onclick: (e) => {
|
|
5689
5764
|
e.preventDefault();
|
|
5690
5765
|
e.stopPropagation();
|
|
5691
|
-
|
|
5766
|
+
if (!disabled) {
|
|
5767
|
+
state.isOpen = !state.isOpen;
|
|
5768
|
+
}
|
|
5692
5769
|
},
|
|
5693
5770
|
}),
|
|
5694
5771
|
// Dropdown Menu
|
|
@@ -5704,7 +5781,63 @@ const Select = () => {
|
|
|
5704
5781
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5705
5782
|
}, [
|
|
5706
5783
|
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5707
|
-
|
|
5784
|
+
// Render ungrouped options first
|
|
5785
|
+
options
|
|
5786
|
+
.filter((option) => !option.group)
|
|
5787
|
+
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5788
|
+
? 'disabled'
|
|
5789
|
+
: state.focusedIndex === options.indexOf(option)
|
|
5790
|
+
? 'focused'
|
|
5791
|
+
: '' }, (option.disabled
|
|
5792
|
+
? {}
|
|
5793
|
+
: {
|
|
5794
|
+
onclick: (e) => {
|
|
5795
|
+
e.stopPropagation();
|
|
5796
|
+
toggleOption(option.id, multiple, attrs);
|
|
5797
|
+
},
|
|
5798
|
+
})), m('span', multiple
|
|
5799
|
+
? m('label', { for: option.id }, m('input', {
|
|
5800
|
+
id: option.id,
|
|
5801
|
+
type: 'checkbox',
|
|
5802
|
+
checked: selectedIds.includes(option.id),
|
|
5803
|
+
disabled: option.disabled ? true : undefined,
|
|
5804
|
+
onclick: (e) => {
|
|
5805
|
+
e.stopPropagation();
|
|
5806
|
+
},
|
|
5807
|
+
}), m('span', option.label))
|
|
5808
|
+
: option.label))),
|
|
5809
|
+
// Render grouped options
|
|
5810
|
+
Object.entries(options
|
|
5811
|
+
.filter((option) => option.group)
|
|
5812
|
+
.reduce((groups, option) => {
|
|
5813
|
+
const group = option.group;
|
|
5814
|
+
if (!groups[group])
|
|
5815
|
+
groups[group] = [];
|
|
5816
|
+
groups[group].push(option);
|
|
5817
|
+
return groups;
|
|
5818
|
+
}, {}))
|
|
5819
|
+
.map(([groupName, groupOptions]) => [
|
|
5820
|
+
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5821
|
+
...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
|
|
5822
|
+
? {}
|
|
5823
|
+
: {
|
|
5824
|
+
onclick: (e) => {
|
|
5825
|
+
e.stopPropagation();
|
|
5826
|
+
toggleOption(option.id, multiple, attrs);
|
|
5827
|
+
},
|
|
5828
|
+
})), m('span', multiple
|
|
5829
|
+
? m('label', { for: option.id }, m('input', {
|
|
5830
|
+
id: option.id,
|
|
5831
|
+
type: 'checkbox',
|
|
5832
|
+
checked: selectedIds.includes(option.id),
|
|
5833
|
+
disabled: option.disabled ? true : undefined,
|
|
5834
|
+
onclick: (e) => {
|
|
5835
|
+
e.stopPropagation();
|
|
5836
|
+
},
|
|
5837
|
+
}), m('span', option.label))
|
|
5838
|
+
: option.label))),
|
|
5839
|
+
])
|
|
5840
|
+
.reduce((acc, val) => acc.concat(val), []),
|
|
5708
5841
|
]),
|
|
5709
5842
|
m(MaterialIcon, {
|
|
5710
5843
|
name: 'caret',
|
|
@@ -5735,25 +5868,22 @@ const Switch = () => {
|
|
|
5735
5868
|
},
|
|
5736
5869
|
view: ({ attrs }) => {
|
|
5737
5870
|
const id = attrs.id || state.id;
|
|
5738
|
-
const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
|
|
5871
|
+
const { checked, label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["checked", "label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
|
|
5739
5872
|
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5740
5873
|
return m('div', {
|
|
5741
5874
|
className: cn,
|
|
5742
5875
|
onclick: (e) => {
|
|
5743
|
-
|
|
5744
|
-
onchange && onchange(state.checked);
|
|
5876
|
+
onchange && onchange(!checked);
|
|
5745
5877
|
e.preventDefault();
|
|
5746
5878
|
},
|
|
5747
5879
|
}, [
|
|
5748
5880
|
label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
|
|
5749
|
-
m('.switch', params, m('label',
|
|
5750
|
-
style: { cursor: 'pointer' },
|
|
5751
|
-
}, [
|
|
5881
|
+
m('.switch', params, m('label', [
|
|
5752
5882
|
m('span', left || 'Off'),
|
|
5753
5883
|
m('input[type=checkbox]', {
|
|
5754
5884
|
id,
|
|
5755
5885
|
disabled,
|
|
5756
|
-
checked
|
|
5886
|
+
checked,
|
|
5757
5887
|
}),
|
|
5758
5888
|
m('span.lever'),
|
|
5759
5889
|
m('span', right || 'On'),
|
|
@@ -5945,22 +6075,62 @@ const Tabs = () => {
|
|
|
5945
6075
|
};
|
|
5946
6076
|
};
|
|
5947
6077
|
|
|
6078
|
+
// Proper components to avoid anonymous closures
|
|
6079
|
+
const SelectedChip = {
|
|
6080
|
+
view: ({ attrs: { option, onRemove } }) => m('.chip', [
|
|
6081
|
+
option.label || option.id.toString(),
|
|
6082
|
+
m(MaterialIcon, {
|
|
6083
|
+
name: 'close',
|
|
6084
|
+
className: 'close',
|
|
6085
|
+
onclick: (e) => {
|
|
6086
|
+
e.stopPropagation();
|
|
6087
|
+
onRemove(option.id);
|
|
6088
|
+
},
|
|
6089
|
+
}),
|
|
6090
|
+
]),
|
|
6091
|
+
};
|
|
6092
|
+
const DropdownOption = {
|
|
6093
|
+
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
|
|
6094
|
+
const checkboxId = `search-select-option-${option.id}`;
|
|
6095
|
+
const optionLabel = option.label || option.id.toString();
|
|
6096
|
+
return m('li', {
|
|
6097
|
+
key: option.id,
|
|
6098
|
+
onclick: (e) => {
|
|
6099
|
+
e.preventDefault();
|
|
6100
|
+
e.stopPropagation();
|
|
6101
|
+
onToggle(option);
|
|
6102
|
+
},
|
|
6103
|
+
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
6104
|
+
onmouseover: () => {
|
|
6105
|
+
if (!option.disabled) {
|
|
6106
|
+
onMouseOver(index);
|
|
6107
|
+
}
|
|
6108
|
+
},
|
|
6109
|
+
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
6110
|
+
m('input', {
|
|
6111
|
+
type: 'checkbox',
|
|
6112
|
+
id: checkboxId,
|
|
6113
|
+
checked: selectedIds.includes(option.id),
|
|
6114
|
+
}),
|
|
6115
|
+
m('span', optionLabel),
|
|
6116
|
+
]));
|
|
6117
|
+
},
|
|
6118
|
+
};
|
|
5948
6119
|
/**
|
|
5949
6120
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
5950
6121
|
*/
|
|
5951
6122
|
const SearchSelect = () => {
|
|
5952
|
-
// (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
|
|
5953
6123
|
// State initialization
|
|
5954
6124
|
const state = {
|
|
6125
|
+
id: '',
|
|
5955
6126
|
isOpen: false,
|
|
5956
|
-
selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
|
|
5957
6127
|
searchTerm: '',
|
|
5958
|
-
options: [],
|
|
5959
6128
|
inputRef: null,
|
|
5960
6129
|
dropdownRef: null,
|
|
5961
6130
|
focusedIndex: -1,
|
|
5962
|
-
|
|
6131
|
+
internalSelectedIds: [],
|
|
5963
6132
|
};
|
|
6133
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5964
6134
|
const componentId = uniqueId();
|
|
5965
6135
|
const searchInputId = `${componentId}-search`;
|
|
5966
6136
|
// Handle click outside
|
|
@@ -6001,9 +6171,7 @@ const SearchSelect = () => {
|
|
|
6001
6171
|
// Handle add new option
|
|
6002
6172
|
return 'addNew';
|
|
6003
6173
|
}
|
|
6004
|
-
else if (state.focusedIndex < filteredOptions.length)
|
|
6005
|
-
toggleOption(filteredOptions[state.focusedIndex]);
|
|
6006
|
-
}
|
|
6174
|
+
else if (state.focusedIndex < filteredOptions.length) ;
|
|
6007
6175
|
}
|
|
6008
6176
|
break;
|
|
6009
6177
|
case 'Escape':
|
|
@@ -6015,26 +6183,65 @@ const SearchSelect = () => {
|
|
|
6015
6183
|
return null;
|
|
6016
6184
|
};
|
|
6017
6185
|
// Toggle option selection
|
|
6018
|
-
const toggleOption = (option) => {
|
|
6186
|
+
const toggleOption = (option, attrs) => {
|
|
6019
6187
|
if (option.disabled)
|
|
6020
6188
|
return;
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6189
|
+
const controlled = isControlled(attrs);
|
|
6190
|
+
// Get current selected IDs from props or internal state
|
|
6191
|
+
const currentSelectedIds = controlled
|
|
6192
|
+
? attrs.checkedId !== undefined
|
|
6193
|
+
? Array.isArray(attrs.checkedId)
|
|
6194
|
+
? attrs.checkedId
|
|
6195
|
+
: [attrs.checkedId]
|
|
6196
|
+
: []
|
|
6197
|
+
: state.internalSelectedIds;
|
|
6198
|
+
const newIds = currentSelectedIds.includes(option.id)
|
|
6199
|
+
? currentSelectedIds.filter((id) => id !== option.id)
|
|
6200
|
+
: [...currentSelectedIds, option.id];
|
|
6201
|
+
// Update internal state for uncontrolled mode
|
|
6202
|
+
if (!controlled) {
|
|
6203
|
+
state.internalSelectedIds = newIds;
|
|
6204
|
+
}
|
|
6024
6205
|
state.searchTerm = '';
|
|
6025
6206
|
state.focusedIndex = -1;
|
|
6026
|
-
|
|
6207
|
+
// Call onchange if provided
|
|
6208
|
+
if (attrs.onchange) {
|
|
6209
|
+
attrs.onchange(newIds);
|
|
6210
|
+
}
|
|
6027
6211
|
};
|
|
6028
6212
|
// Remove a selected option
|
|
6029
|
-
const removeOption = (
|
|
6030
|
-
|
|
6031
|
-
|
|
6213
|
+
const removeOption = (optionId, attrs) => {
|
|
6214
|
+
const controlled = isControlled(attrs);
|
|
6215
|
+
// Get current selected IDs from props or internal state
|
|
6216
|
+
const currentSelectedIds = controlled
|
|
6217
|
+
? attrs.checkedId !== undefined
|
|
6218
|
+
? Array.isArray(attrs.checkedId)
|
|
6219
|
+
? attrs.checkedId
|
|
6220
|
+
: [attrs.checkedId]
|
|
6221
|
+
: []
|
|
6222
|
+
: state.internalSelectedIds;
|
|
6223
|
+
const newIds = currentSelectedIds.filter((id) => id !== optionId);
|
|
6224
|
+
// Update internal state for uncontrolled mode
|
|
6225
|
+
if (!controlled) {
|
|
6226
|
+
state.internalSelectedIds = newIds;
|
|
6227
|
+
}
|
|
6228
|
+
// Call onchange if provided
|
|
6229
|
+
if (attrs.onchange) {
|
|
6230
|
+
attrs.onchange(newIds);
|
|
6231
|
+
}
|
|
6032
6232
|
};
|
|
6033
6233
|
return {
|
|
6034
|
-
oninit: ({ attrs
|
|
6035
|
-
state.
|
|
6036
|
-
state
|
|
6037
|
-
|
|
6234
|
+
oninit: ({ attrs }) => {
|
|
6235
|
+
state.id = attrs.id || uniqueId();
|
|
6236
|
+
// Initialize internal state for uncontrolled mode
|
|
6237
|
+
if (!isControlled(attrs)) {
|
|
6238
|
+
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
6239
|
+
? Array.isArray(attrs.defaultCheckedId)
|
|
6240
|
+
? attrs.defaultCheckedId
|
|
6241
|
+
: [attrs.defaultCheckedId]
|
|
6242
|
+
: [];
|
|
6243
|
+
state.internalSelectedIds = defaultIds;
|
|
6244
|
+
}
|
|
6038
6245
|
},
|
|
6039
6246
|
oncreate() {
|
|
6040
6247
|
document.addEventListener('click', handleClickOutside);
|
|
@@ -6042,14 +6249,27 @@ const SearchSelect = () => {
|
|
|
6042
6249
|
onremove() {
|
|
6043
6250
|
document.removeEventListener('click', handleClickOutside);
|
|
6044
6251
|
},
|
|
6045
|
-
view({ attrs
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6252
|
+
view({ attrs }) {
|
|
6253
|
+
const controlled = isControlled(attrs);
|
|
6254
|
+
// Get selected IDs from props or internal state
|
|
6255
|
+
const selectedIds = controlled
|
|
6256
|
+
? attrs.checkedId !== undefined
|
|
6257
|
+
? Array.isArray(attrs.checkedId)
|
|
6258
|
+
? attrs.checkedId
|
|
6259
|
+
: [attrs.checkedId]
|
|
6260
|
+
: []
|
|
6261
|
+
: state.internalSelectedIds;
|
|
6262
|
+
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
|
|
6263
|
+
// Use i18n values if provided, otherwise use defaults
|
|
6264
|
+
const texts = {
|
|
6265
|
+
noOptionsFound: i18n.noOptionsFound || noOptionsFound,
|
|
6266
|
+
addNewPrefix: i18n.addNewPrefix || '+',
|
|
6267
|
+
};
|
|
6268
|
+
// Get selected options for display
|
|
6269
|
+
const selectedOptions = options.filter((opt) => selectedIds.includes(opt.id));
|
|
6050
6270
|
// Safely filter options
|
|
6051
|
-
const filteredOptions =
|
|
6052
|
-
!
|
|
6271
|
+
const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
6272
|
+
!selectedIds.includes(option.id));
|
|
6053
6273
|
// Check if we should show the "add new option" element
|
|
6054
6274
|
const showAddNew = oncreateNewOption &&
|
|
6055
6275
|
state.searchTerm &&
|
|
@@ -6067,6 +6287,7 @@ const SearchSelect = () => {
|
|
|
6067
6287
|
state.isOpen = !state.isOpen;
|
|
6068
6288
|
// console.log('SearchSelect state changed to', state.isOpen); // Debug log
|
|
6069
6289
|
},
|
|
6290
|
+
class: 'chips chips-container',
|
|
6070
6291
|
style: {
|
|
6071
6292
|
display: 'flex',
|
|
6072
6293
|
alignItems: 'end',
|
|
@@ -6079,25 +6300,20 @@ const SearchSelect = () => {
|
|
|
6079
6300
|
// Hidden input for label association and accessibility
|
|
6080
6301
|
m('input', {
|
|
6081
6302
|
type: 'text',
|
|
6082
|
-
id:
|
|
6083
|
-
value:
|
|
6303
|
+
id: state.id,
|
|
6304
|
+
value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
|
|
6084
6305
|
readonly: true,
|
|
6306
|
+
class: 'sr-only',
|
|
6085
6307
|
style: { position: 'absolute', left: '-9999px', opacity: 0 },
|
|
6086
6308
|
}),
|
|
6087
6309
|
// Selected Options (chips)
|
|
6088
|
-
...
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
onclick: (e) => {
|
|
6094
|
-
e.stopPropagation();
|
|
6095
|
-
removeOption(option);
|
|
6096
|
-
},
|
|
6097
|
-
}),
|
|
6098
|
-
])),
|
|
6310
|
+
...selectedOptions.map((option) => m(SelectedChip, {
|
|
6311
|
+
// key: option.id,
|
|
6312
|
+
option,
|
|
6313
|
+
onRemove: (id) => removeOption(id, attrs),
|
|
6314
|
+
})),
|
|
6099
6315
|
// Placeholder when no options selected
|
|
6100
|
-
|
|
6316
|
+
selectedOptions.length === 0 &&
|
|
6101
6317
|
placeholder &&
|
|
6102
6318
|
m('span.placeholder', {
|
|
6103
6319
|
style: {
|
|
@@ -6118,8 +6334,8 @@ const SearchSelect = () => {
|
|
|
6118
6334
|
// Label
|
|
6119
6335
|
label &&
|
|
6120
6336
|
m('label', {
|
|
6121
|
-
for:
|
|
6122
|
-
class: placeholder ||
|
|
6337
|
+
for: state.id,
|
|
6338
|
+
class: placeholder || selectedOptions.length > 0 ? 'active' : '',
|
|
6123
6339
|
}, label),
|
|
6124
6340
|
// Dropdown Menu
|
|
6125
6341
|
state.isOpen &&
|
|
@@ -6135,7 +6351,6 @@ const SearchSelect = () => {
|
|
|
6135
6351
|
m('li', // Search Input
|
|
6136
6352
|
{
|
|
6137
6353
|
class: 'search-wrapper',
|
|
6138
|
-
style: { padding: '0 16px', position: 'relative' },
|
|
6139
6354
|
}, [
|
|
6140
6355
|
m('input', {
|
|
6141
6356
|
type: 'text',
|
|
@@ -6154,29 +6369,21 @@ const SearchSelect = () => {
|
|
|
6154
6369
|
const result = handleKeyDown(e, filteredOptions, !!showAddNew);
|
|
6155
6370
|
if (result === 'addNew' && oncreateNewOption) {
|
|
6156
6371
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6157
|
-
toggleOption(option);
|
|
6372
|
+
toggleOption(option, attrs);
|
|
6373
|
+
}
|
|
6374
|
+
else if (e.key === 'Enter' &&
|
|
6375
|
+
state.focusedIndex >= 0 &&
|
|
6376
|
+
state.focusedIndex < filteredOptions.length) {
|
|
6377
|
+
toggleOption(filteredOptions[state.focusedIndex], attrs);
|
|
6158
6378
|
}
|
|
6159
6379
|
},
|
|
6160
|
-
|
|
6161
|
-
width: '100%',
|
|
6162
|
-
outline: 'none',
|
|
6163
|
-
fontSize: '0.875rem',
|
|
6164
|
-
border: 'none',
|
|
6165
|
-
padding: '8px 0',
|
|
6166
|
-
borderBottom: '1px solid var(--mm-input-border, #9e9e9e)',
|
|
6167
|
-
backgroundColor: 'transparent',
|
|
6168
|
-
color: 'var(--mm-text-primary, inherit)',
|
|
6169
|
-
},
|
|
6380
|
+
class: 'search-select-input',
|
|
6170
6381
|
}),
|
|
6171
6382
|
]),
|
|
6172
6383
|
// No options found message or list of options
|
|
6173
6384
|
...(filteredOptions.length === 0 && !showAddNew
|
|
6174
6385
|
? [
|
|
6175
|
-
m('li',
|
|
6176
|
-
// {
|
|
6177
|
-
// style: getNoOptionsStyles(),
|
|
6178
|
-
// },
|
|
6179
|
-
noOptionsFound),
|
|
6386
|
+
m('li.search-select-no-options', texts.noOptionsFound),
|
|
6180
6387
|
]
|
|
6181
6388
|
: []),
|
|
6182
6389
|
// Add new option item
|
|
@@ -6185,35 +6392,27 @@ const SearchSelect = () => {
|
|
|
6185
6392
|
m('li', {
|
|
6186
6393
|
onclick: async () => {
|
|
6187
6394
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6188
|
-
toggleOption(option);
|
|
6395
|
+
toggleOption(option, attrs);
|
|
6189
6396
|
},
|
|
6190
6397
|
class: state.focusedIndex === filteredOptions.length ? 'active' : '',
|
|
6191
6398
|
onmouseover: () => {
|
|
6192
6399
|
state.focusedIndex = filteredOptions.length;
|
|
6193
6400
|
},
|
|
6194
|
-
}, [m('span',
|
|
6401
|
+
}, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
|
|
6195
6402
|
]
|
|
6196
6403
|
: []),
|
|
6197
6404
|
// List of filtered options
|
|
6198
|
-
...filteredOptions.map((option, index) => m(
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
state.focusedIndex = index;
|
|
6208
|
-
}
|
|
6405
|
+
...filteredOptions.map((option, index) => m(DropdownOption, {
|
|
6406
|
+
// key: option.id,
|
|
6407
|
+
option,
|
|
6408
|
+
index,
|
|
6409
|
+
selectedIds,
|
|
6410
|
+
isFocused: state.focusedIndex === index,
|
|
6411
|
+
onToggle: (opt) => toggleOption(opt, attrs),
|
|
6412
|
+
onMouseOver: (idx) => {
|
|
6413
|
+
state.focusedIndex = idx;
|
|
6209
6414
|
},
|
|
6210
|
-
},
|
|
6211
|
-
m('input', {
|
|
6212
|
-
type: 'checkbox',
|
|
6213
|
-
checked: state.selectedOptions.some((selected) => selected.id === option.id),
|
|
6214
|
-
}),
|
|
6215
|
-
option.label || option.id.toString(),
|
|
6216
|
-
]))),
|
|
6415
|
+
})),
|
|
6217
6416
|
]),
|
|
6218
6417
|
]);
|
|
6219
6418
|
},
|
|
@@ -8296,4 +8495,4 @@ const isValidationError = (result) => !isValidationSuccess(result);
|
|
|
8296
8495
|
// ============================================================================
|
|
8297
8496
|
// All types are already exported via individual export declarations above
|
|
8298
8497
|
|
|
8299
|
-
export { AnchorItem, Autocomplete, Breadcrumb, BreadcrumbManager, Button, ButtonFactory, Carousel, CharacterCounter, Chips, CodeBlock, Collapsible, CollapsibleItem, Collection, CollectionMode, ColorInput, DataTable, DatePicker, DoubleRangeSlider, Dropdown, EmailInput, FileInput, FileUpload, FlatButton, FloatingActionButton, HelperText, Icon, ImageList, InputCheckbox, Label, LargeButton, ListItem, Mandatory, Masonry, MaterialBox, MaterialIcon, ModalPanel, NumberInput, Options, Pagination, PaginationControls, Parallax, PasswordInput, Pushpin, PushpinComponent, RadioButton, RadioButtons, RangeInput, RoundIconButton, SearchSelect, SecondaryContent, Select, Sidenav, SidenavItem, SidenavManager, SingleRangeSlider, SmallButton, Stepper, SubmitButton, Switch, Tabs, TextArea, TextInput, ThemeManager, ThemeSwitcher, ThemeToggle, TimePicker, Timeline, Toast, ToastComponent, Tooltip, TooltipComponent, TreeView, UrlInput, Wizard, createBreadcrumb, getDropdownStyles, initPushpins, initTooltips, isNumeric, isValidationError, isValidationSuccess, padLeft, range, toast, uniqueId, uuid4 };
|
|
8498
|
+
export { AnchorItem, Autocomplete, Breadcrumb, BreadcrumbManager, Button, ButtonFactory, Carousel, CharacterCounter, Chips, CodeBlock, Collapsible, CollapsibleItem, Collection, CollectionMode, ColorInput, DataTable, DatePicker, DoubleRangeSlider, Dropdown, EmailInput, FileInput, FileUpload, FlatButton, FloatingActionButton, HelperText, Icon, ImageList, InputCheckbox, Label, LargeButton, ListItem, Mandatory, Masonry, MaterialBox, MaterialIcon, ModalPanel, NumberInput, Options, OptionsList, Pagination, PaginationControls, Parallax, PasswordInput, Pushpin, PushpinComponent, RadioButton, RadioButtons, RangeInput, RoundIconButton, SearchSelect, SecondaryContent, Select, Sidenav, SidenavItem, SidenavManager, SingleRangeSlider, SmallButton, Stepper, SubmitButton, Switch, Tabs, TextArea, TextInput, ThemeManager, ThemeSwitcher, ThemeToggle, TimePicker, Timeline, Toast, ToastComponent, Tooltip, TooltipComponent, TreeView, UrlInput, Wizard, createBreadcrumb, getDropdownStyles, initPushpins, initTooltips, isNumeric, isValidationError, isValidationSuccess, padLeft, range, toast, uniqueId, uuid4 };
|