mithril-materialized 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +604 -399
- package/dist/index.js +604 -398
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +604 -398
- 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
|
-
|
|
3233
|
-
|
|
3336
|
+
onchange && onchange(allIds);
|
|
3337
|
+
};
|
|
3338
|
+
const selectNone = (onchange) => {
|
|
3339
|
+
onchange && onchange([]);
|
|
3234
3340
|
};
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
if (
|
|
3238
|
-
|
|
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,65 +3984,96 @@ 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
|
-
const handleKeyDown = (e, items
|
|
4001
|
+
const handleKeyDown = (e, items) => {
|
|
3898
4002
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
3899
4003
|
switch (e.key) {
|
|
3900
4004
|
case 'ArrowDown':
|
|
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
|
-
state.focusedIndex =
|
|
4011
|
+
state.focusedIndex = (state.focusedIndex + 1) % availableItems.length;
|
|
3908
4012
|
}
|
|
3909
|
-
|
|
4013
|
+
return undefined;
|
|
3910
4014
|
case 'ArrowUp':
|
|
3911
4015
|
e.preventDefault();
|
|
3912
4016
|
if (state.isOpen) {
|
|
3913
|
-
state.focusedIndex =
|
|
4017
|
+
state.focusedIndex = state.focusedIndex <= 0 ? availableItems.length - 1 : state.focusedIndex - 1;
|
|
3914
4018
|
}
|
|
3915
|
-
|
|
4019
|
+
return undefined;
|
|
3916
4020
|
case 'Enter':
|
|
3917
4021
|
case ' ':
|
|
3918
4022
|
e.preventDefault();
|
|
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;
|
|
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
|
+
return undefined;
|
|
3933
4035
|
case 'Escape':
|
|
3934
4036
|
e.preventDefault();
|
|
3935
4037
|
state.isOpen = false;
|
|
3936
4038
|
state.focusedIndex = -1;
|
|
3937
|
-
|
|
4039
|
+
return undefined;
|
|
4040
|
+
default:
|
|
4041
|
+
return undefined;
|
|
3938
4042
|
}
|
|
3939
4043
|
};
|
|
3940
4044
|
return {
|
|
3941
|
-
oninit: ({ attrs
|
|
3942
|
-
|
|
3943
|
-
state.
|
|
3944
|
-
//
|
|
4045
|
+
oninit: ({ attrs }) => {
|
|
4046
|
+
var _a;
|
|
4047
|
+
state.id = ((_a = attrs.id) === null || _a === void 0 ? void 0 : _a.toString()) || uniqueId();
|
|
4048
|
+
// Initialize internal state for uncontrolled mode
|
|
4049
|
+
if (!isControlled(attrs)) {
|
|
4050
|
+
state.internalCheckedId = attrs.defaultCheckedId;
|
|
4051
|
+
}
|
|
4052
|
+
// Add global click listener to close dropdown
|
|
4053
|
+
document.addEventListener('click', closeDropdown);
|
|
3945
4054
|
},
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
4055
|
+
onremove: () => {
|
|
4056
|
+
// Cleanup global listener
|
|
4057
|
+
document.removeEventListener('click', closeDropdown);
|
|
4058
|
+
},
|
|
4059
|
+
view: ({ attrs }) => {
|
|
4060
|
+
const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
|
|
4061
|
+
const controlled = isControlled(attrs);
|
|
4062
|
+
const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
|
|
4063
|
+
const handleSelection = (value) => {
|
|
4064
|
+
// Update internal state for uncontrolled mode
|
|
4065
|
+
if (!controlled) {
|
|
4066
|
+
state.internalCheckedId = value;
|
|
4067
|
+
}
|
|
4068
|
+
// Call onchange if provided
|
|
4069
|
+
if (onchange) {
|
|
4070
|
+
onchange(value);
|
|
4071
|
+
}
|
|
4072
|
+
};
|
|
4073
|
+
const selectedItem = currentCheckedId
|
|
4074
|
+
? items
|
|
4075
|
+
.filter((i) => (i.id ? i.id === currentCheckedId : i.label === currentCheckedId))
|
|
4076
|
+
.shift()
|
|
3950
4077
|
: undefined;
|
|
3951
4078
|
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
3952
4079
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3954,13 +4081,14 @@ const Dropdown = () => {
|
|
|
3954
4081
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3955
4082
|
m(HelperText, { helperText }),
|
|
3956
4083
|
m('.select-wrapper', {
|
|
3957
|
-
|
|
4084
|
+
onkeydown: disabled
|
|
3958
4085
|
? undefined
|
|
3959
|
-
: () => {
|
|
3960
|
-
|
|
3961
|
-
|
|
4086
|
+
: (e) => {
|
|
4087
|
+
const selectedValue = handleKeyDown(e, items);
|
|
4088
|
+
if (selectedValue) {
|
|
4089
|
+
handleSelection(selectedValue);
|
|
4090
|
+
}
|
|
3962
4091
|
},
|
|
3963
|
-
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
|
|
3964
4092
|
tabindex: disabled ? -1 : 0,
|
|
3965
4093
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
3966
4094
|
'aria-haspopup': 'listbox',
|
|
@@ -3977,7 +4105,8 @@ const Dropdown = () => {
|
|
|
3977
4105
|
e.stopPropagation();
|
|
3978
4106
|
if (!disabled) {
|
|
3979
4107
|
state.isOpen = !state.isOpen;
|
|
3980
|
-
|
|
4108
|
+
// Reset focus index when opening/closing
|
|
4109
|
+
state.focusedIndex = -1;
|
|
3981
4110
|
}
|
|
3982
4111
|
},
|
|
3983
4112
|
}),
|
|
@@ -3993,36 +4122,31 @@ const Dropdown = () => {
|
|
|
3993
4122
|
onremove: () => {
|
|
3994
4123
|
state.dropdownRef = null;
|
|
3995
4124
|
},
|
|
3996
|
-
style: getDropdownStyles(state.inputRef, true, items
|
|
3997
|
-
|
|
3998
|
-
group: undefined }))), true),
|
|
3999
|
-
}, items.map((item, index) => {
|
|
4125
|
+
style: getDropdownStyles(state.inputRef, true, items, true),
|
|
4126
|
+
}, items.map((item) => {
|
|
4000
4127
|
if (item.divider) {
|
|
4001
|
-
return m('li.divider'
|
|
4002
|
-
key: `divider-${index}`,
|
|
4003
|
-
});
|
|
4128
|
+
return m('li.divider');
|
|
4004
4129
|
}
|
|
4005
4130
|
const itemIndex = availableItems.indexOf(item);
|
|
4006
4131
|
const isFocused = itemIndex === state.focusedIndex;
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4132
|
+
const className = [
|
|
4133
|
+
item.disabled ? 'disabled' : '',
|
|
4134
|
+
isFocused ? 'focused' : '',
|
|
4135
|
+
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
4136
|
+
]
|
|
4137
|
+
.filter(Boolean)
|
|
4138
|
+
.join(' ') || undefined;
|
|
4139
|
+
return m('li', {
|
|
4140
|
+
className,
|
|
4141
|
+
onclick: item.disabled
|
|
4142
|
+
? undefined
|
|
4143
|
+
: () => {
|
|
4018
4144
|
const value = (item.id || item.label);
|
|
4019
|
-
state.initialValue = value;
|
|
4020
4145
|
state.isOpen = false;
|
|
4021
4146
|
state.focusedIndex = -1;
|
|
4022
|
-
|
|
4023
|
-
onchange(value);
|
|
4147
|
+
handleSelection(value);
|
|
4024
4148
|
},
|
|
4025
|
-
|
|
4149
|
+
}, m('span', {
|
|
4026
4150
|
style: {
|
|
4027
4151
|
display: 'flex',
|
|
4028
4152
|
alignItems: 'center',
|
|
@@ -5159,9 +5283,9 @@ const TimePicker = () => {
|
|
|
5159
5283
|
dx: 0,
|
|
5160
5284
|
dy: 0,
|
|
5161
5285
|
};
|
|
5162
|
-
// Handle
|
|
5163
|
-
if (attrs.
|
|
5164
|
-
updateTimeFromInput(attrs.
|
|
5286
|
+
// Handle value after options are set
|
|
5287
|
+
if (attrs.defaultValue) {
|
|
5288
|
+
updateTimeFromInput(attrs.defaultValue);
|
|
5165
5289
|
}
|
|
5166
5290
|
},
|
|
5167
5291
|
onremove: () => {
|
|
@@ -5443,32 +5567,47 @@ const RadioButton = () => ({
|
|
|
5443
5567
|
},
|
|
5444
5568
|
});
|
|
5445
5569
|
/** Component to show a list of radio buttons, from which you can choose one. */
|
|
5446
|
-
// export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
|
|
5447
5570
|
const RadioButtons = () => {
|
|
5448
|
-
const state = {
|
|
5571
|
+
const state = {
|
|
5572
|
+
groupId: uniqueId(),
|
|
5573
|
+
componentId: '',
|
|
5574
|
+
internalCheckedId: undefined,
|
|
5575
|
+
};
|
|
5576
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5449
5577
|
return {
|
|
5450
|
-
oninit: ({ attrs
|
|
5451
|
-
state.
|
|
5452
|
-
state
|
|
5453
|
-
|
|
5578
|
+
oninit: ({ attrs }) => {
|
|
5579
|
+
state.componentId = attrs.id || uniqueId();
|
|
5580
|
+
// Initialize internal state for uncontrolled mode
|
|
5581
|
+
if (!isControlled(attrs)) {
|
|
5582
|
+
state.internalCheckedId = attrs.defaultCheckedId;
|
|
5583
|
+
}
|
|
5454
5584
|
},
|
|
5455
|
-
view: ({ attrs
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
const
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5585
|
+
view: ({ attrs }) => {
|
|
5586
|
+
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange, } = attrs;
|
|
5587
|
+
const { groupId, componentId } = state;
|
|
5588
|
+
const controlled = isControlled(attrs);
|
|
5589
|
+
// Get current checked ID from props or internal state
|
|
5590
|
+
const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
|
|
5591
|
+
const handleChange = (id) => {
|
|
5592
|
+
// Update internal state for uncontrolled mode
|
|
5593
|
+
if (!controlled) {
|
|
5594
|
+
state.internalCheckedId = id;
|
|
5595
|
+
}
|
|
5596
|
+
// Call onchange if provided
|
|
5597
|
+
if (onchange) {
|
|
5598
|
+
onchange(id);
|
|
5464
5599
|
}
|
|
5465
5600
|
};
|
|
5466
5601
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5467
|
-
const
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
:
|
|
5471
|
-
|
|
5602
|
+
const radioItems = options.map((r) => ({
|
|
5603
|
+
component: (RadioButton),
|
|
5604
|
+
props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}` }),
|
|
5605
|
+
key: r.id,
|
|
5606
|
+
}));
|
|
5607
|
+
const optionsContent = m(OptionsList, {
|
|
5608
|
+
options: radioItems,
|
|
5609
|
+
layout,
|
|
5610
|
+
});
|
|
5472
5611
|
return m('div', { id: componentId, className: cn }, [
|
|
5473
5612
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
5474
5613
|
description && m('p.helper-text', m.trust(description)),
|
|
@@ -5483,30 +5622,42 @@ const Select = () => {
|
|
|
5483
5622
|
const state = {
|
|
5484
5623
|
id: '',
|
|
5485
5624
|
isOpen: false,
|
|
5486
|
-
selectedIds: [],
|
|
5487
5625
|
focusedIndex: -1,
|
|
5488
5626
|
inputRef: null,
|
|
5489
5627
|
dropdownRef: null,
|
|
5628
|
+
internalSelectedIds: [],
|
|
5490
5629
|
};
|
|
5630
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5491
5631
|
const isSelected = (id, selectedIds) => {
|
|
5492
5632
|
return selectedIds.some((selectedId) => selectedId === id);
|
|
5493
5633
|
};
|
|
5494
5634
|
const toggleOption = (id, multiple, attrs) => {
|
|
5635
|
+
const controlled = isControlled(attrs);
|
|
5636
|
+
// Get current selected IDs from props or internal state
|
|
5637
|
+
const currentSelectedIds = controlled
|
|
5638
|
+
? attrs.checkedId !== undefined
|
|
5639
|
+
? Array.isArray(attrs.checkedId)
|
|
5640
|
+
? attrs.checkedId
|
|
5641
|
+
: [attrs.checkedId]
|
|
5642
|
+
: []
|
|
5643
|
+
: state.internalSelectedIds;
|
|
5644
|
+
let newIds;
|
|
5495
5645
|
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
|
|
5646
|
+
newIds = currentSelectedIds.includes(id)
|
|
5647
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
5648
|
+
: [...currentSelectedIds, id];
|
|
5504
5649
|
}
|
|
5505
5650
|
else {
|
|
5506
|
-
|
|
5507
|
-
// Close dropdown for single select
|
|
5508
|
-
|
|
5509
|
-
|
|
5651
|
+
newIds = [id];
|
|
5652
|
+
state.isOpen = false; // Close dropdown for single select
|
|
5653
|
+
}
|
|
5654
|
+
// Update internal state for uncontrolled mode
|
|
5655
|
+
if (!controlled) {
|
|
5656
|
+
state.internalSelectedIds = newIds;
|
|
5657
|
+
}
|
|
5658
|
+
// Call onchange if provided
|
|
5659
|
+
if (attrs.onchange) {
|
|
5660
|
+
attrs.onchange(newIds);
|
|
5510
5661
|
}
|
|
5511
5662
|
};
|
|
5512
5663
|
const handleKeyDown = (e, attrs) => {
|
|
@@ -5558,88 +5709,22 @@ const Select = () => {
|
|
|
5558
5709
|
};
|
|
5559
5710
|
const closeDropdown = (e) => {
|
|
5560
5711
|
const target = e.target;
|
|
5561
|
-
if (!target.closest('.select-
|
|
5712
|
+
if (!target.closest('.input-field.select-space')) {
|
|
5562
5713
|
state.isOpen = false;
|
|
5563
5714
|
m.redraw();
|
|
5564
5715
|
}
|
|
5565
5716
|
};
|
|
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
5717
|
return {
|
|
5632
5718
|
oninit: ({ attrs }) => {
|
|
5633
|
-
|
|
5634
|
-
state
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
}
|
|
5719
|
+
state.id = attrs.id || uniqueId();
|
|
5720
|
+
// Initialize internal state for uncontrolled mode
|
|
5721
|
+
if (!isControlled(attrs)) {
|
|
5722
|
+
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
5723
|
+
? Array.isArray(attrs.defaultCheckedId)
|
|
5724
|
+
? attrs.defaultCheckedId
|
|
5725
|
+
: [attrs.defaultCheckedId]
|
|
5726
|
+
: [];
|
|
5727
|
+
state.internalSelectedIds = defaultIds;
|
|
5643
5728
|
}
|
|
5644
5729
|
// Add global click listener to close dropdown
|
|
5645
5730
|
document.addEventListener('click', closeDropdown);
|
|
@@ -5649,17 +5734,18 @@ const Select = () => {
|
|
|
5649
5734
|
document.removeEventListener('click', closeDropdown);
|
|
5650
5735
|
},
|
|
5651
5736
|
view: ({ attrs }) => {
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5737
|
+
const controlled = isControlled(attrs);
|
|
5738
|
+
// Get selected IDs from props or internal state
|
|
5739
|
+
const selectedIds = controlled
|
|
5740
|
+
? attrs.checkedId !== undefined
|
|
5741
|
+
? Array.isArray(attrs.checkedId)
|
|
5742
|
+
? attrs.checkedId
|
|
5743
|
+
: [attrs.checkedId]
|
|
5744
|
+
: []
|
|
5745
|
+
: state.internalSelectedIds;
|
|
5660
5746
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
|
|
5661
5747
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5662
|
-
const selectedOptions = options.filter((opt) => isSelected(opt.id,
|
|
5748
|
+
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
5663
5749
|
return m('.input-field.select-space', {
|
|
5664
5750
|
className: finalClassName,
|
|
5665
5751
|
key,
|
|
@@ -5668,11 +5754,6 @@ const Select = () => {
|
|
|
5668
5754
|
// Icon prefix
|
|
5669
5755
|
iconName && m('i.material-icons.prefix', iconName),
|
|
5670
5756
|
m('.select-wrapper', {
|
|
5671
|
-
onclick: disabled
|
|
5672
|
-
? undefined
|
|
5673
|
-
: () => {
|
|
5674
|
-
state.isOpen = !state.isOpen;
|
|
5675
|
-
},
|
|
5676
5757
|
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
|
|
5677
5758
|
tabindex: disabled ? -1 : 0,
|
|
5678
5759
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
@@ -5688,7 +5769,9 @@ const Select = () => {
|
|
|
5688
5769
|
onclick: (e) => {
|
|
5689
5770
|
e.preventDefault();
|
|
5690
5771
|
e.stopPropagation();
|
|
5691
|
-
|
|
5772
|
+
if (!disabled) {
|
|
5773
|
+
state.isOpen = !state.isOpen;
|
|
5774
|
+
}
|
|
5692
5775
|
},
|
|
5693
5776
|
}),
|
|
5694
5777
|
// Dropdown Menu
|
|
@@ -5704,7 +5787,63 @@ const Select = () => {
|
|
|
5704
5787
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5705
5788
|
}, [
|
|
5706
5789
|
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5707
|
-
|
|
5790
|
+
// Render ungrouped options first
|
|
5791
|
+
options
|
|
5792
|
+
.filter((option) => !option.group)
|
|
5793
|
+
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5794
|
+
? 'disabled'
|
|
5795
|
+
: state.focusedIndex === options.indexOf(option)
|
|
5796
|
+
? 'focused'
|
|
5797
|
+
: '' }, (option.disabled
|
|
5798
|
+
? {}
|
|
5799
|
+
: {
|
|
5800
|
+
onclick: (e) => {
|
|
5801
|
+
e.stopPropagation();
|
|
5802
|
+
toggleOption(option.id, multiple, attrs);
|
|
5803
|
+
},
|
|
5804
|
+
})), m('span', multiple
|
|
5805
|
+
? m('label', { for: option.id }, m('input', {
|
|
5806
|
+
id: option.id,
|
|
5807
|
+
type: 'checkbox',
|
|
5808
|
+
checked: selectedIds.includes(option.id),
|
|
5809
|
+
disabled: option.disabled ? true : undefined,
|
|
5810
|
+
onclick: (e) => {
|
|
5811
|
+
e.stopPropagation();
|
|
5812
|
+
},
|
|
5813
|
+
}), m('span', option.label))
|
|
5814
|
+
: option.label))),
|
|
5815
|
+
// Render grouped options
|
|
5816
|
+
Object.entries(options
|
|
5817
|
+
.filter((option) => option.group)
|
|
5818
|
+
.reduce((groups, option) => {
|
|
5819
|
+
const group = option.group;
|
|
5820
|
+
if (!groups[group])
|
|
5821
|
+
groups[group] = [];
|
|
5822
|
+
groups[group].push(option);
|
|
5823
|
+
return groups;
|
|
5824
|
+
}, {}))
|
|
5825
|
+
.map(([groupName, groupOptions]) => [
|
|
5826
|
+
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5827
|
+
...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
|
|
5828
|
+
? {}
|
|
5829
|
+
: {
|
|
5830
|
+
onclick: (e) => {
|
|
5831
|
+
e.stopPropagation();
|
|
5832
|
+
toggleOption(option.id, multiple, attrs);
|
|
5833
|
+
},
|
|
5834
|
+
})), m('span', multiple
|
|
5835
|
+
? m('label', { for: option.id }, m('input', {
|
|
5836
|
+
id: option.id,
|
|
5837
|
+
type: 'checkbox',
|
|
5838
|
+
checked: selectedIds.includes(option.id),
|
|
5839
|
+
disabled: option.disabled ? true : undefined,
|
|
5840
|
+
onclick: (e) => {
|
|
5841
|
+
e.stopPropagation();
|
|
5842
|
+
},
|
|
5843
|
+
}), m('span', option.label))
|
|
5844
|
+
: option.label))),
|
|
5845
|
+
])
|
|
5846
|
+
.reduce((acc, val) => acc.concat(val), []),
|
|
5708
5847
|
]),
|
|
5709
5848
|
m(MaterialIcon, {
|
|
5710
5849
|
name: 'caret',
|
|
@@ -5735,25 +5874,22 @@ const Switch = () => {
|
|
|
5735
5874
|
},
|
|
5736
5875
|
view: ({ attrs }) => {
|
|
5737
5876
|
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"]);
|
|
5877
|
+
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
5878
|
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5740
5879
|
return m('div', {
|
|
5741
5880
|
className: cn,
|
|
5742
5881
|
onclick: (e) => {
|
|
5743
|
-
|
|
5744
|
-
onchange && onchange(state.checked);
|
|
5882
|
+
onchange && onchange(!checked);
|
|
5745
5883
|
e.preventDefault();
|
|
5746
5884
|
},
|
|
5747
5885
|
}, [
|
|
5748
5886
|
label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
|
|
5749
|
-
m('.switch', params, m('label',
|
|
5750
|
-
style: { cursor: 'pointer' },
|
|
5751
|
-
}, [
|
|
5887
|
+
m('.switch', params, m('label', [
|
|
5752
5888
|
m('span', left || 'Off'),
|
|
5753
5889
|
m('input[type=checkbox]', {
|
|
5754
5890
|
id,
|
|
5755
5891
|
disabled,
|
|
5756
|
-
checked
|
|
5892
|
+
checked,
|
|
5757
5893
|
}),
|
|
5758
5894
|
m('span.lever'),
|
|
5759
5895
|
m('span', right || 'On'),
|
|
@@ -5945,22 +6081,62 @@ const Tabs = () => {
|
|
|
5945
6081
|
};
|
|
5946
6082
|
};
|
|
5947
6083
|
|
|
6084
|
+
// Proper components to avoid anonymous closures
|
|
6085
|
+
const SelectedChip = {
|
|
6086
|
+
view: ({ attrs: { option, onRemove } }) => m('.chip', [
|
|
6087
|
+
option.label || option.id.toString(),
|
|
6088
|
+
m(MaterialIcon, {
|
|
6089
|
+
name: 'close',
|
|
6090
|
+
className: 'close',
|
|
6091
|
+
onclick: (e) => {
|
|
6092
|
+
e.stopPropagation();
|
|
6093
|
+
onRemove(option.id);
|
|
6094
|
+
},
|
|
6095
|
+
}),
|
|
6096
|
+
]),
|
|
6097
|
+
};
|
|
6098
|
+
const DropdownOption = {
|
|
6099
|
+
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
|
|
6100
|
+
const checkboxId = `search-select-option-${option.id}`;
|
|
6101
|
+
const optionLabel = option.label || option.id.toString();
|
|
6102
|
+
return m('li', {
|
|
6103
|
+
key: option.id,
|
|
6104
|
+
onclick: (e) => {
|
|
6105
|
+
e.preventDefault();
|
|
6106
|
+
e.stopPropagation();
|
|
6107
|
+
onToggle(option);
|
|
6108
|
+
},
|
|
6109
|
+
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
6110
|
+
onmouseover: () => {
|
|
6111
|
+
if (!option.disabled) {
|
|
6112
|
+
onMouseOver(index);
|
|
6113
|
+
}
|
|
6114
|
+
},
|
|
6115
|
+
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
6116
|
+
m('input', {
|
|
6117
|
+
type: 'checkbox',
|
|
6118
|
+
id: checkboxId,
|
|
6119
|
+
checked: selectedIds.includes(option.id),
|
|
6120
|
+
}),
|
|
6121
|
+
m('span', optionLabel),
|
|
6122
|
+
]));
|
|
6123
|
+
},
|
|
6124
|
+
};
|
|
5948
6125
|
/**
|
|
5949
6126
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
5950
6127
|
*/
|
|
5951
6128
|
const SearchSelect = () => {
|
|
5952
|
-
// (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
|
|
5953
6129
|
// State initialization
|
|
5954
6130
|
const state = {
|
|
6131
|
+
id: '',
|
|
5955
6132
|
isOpen: false,
|
|
5956
|
-
selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
|
|
5957
6133
|
searchTerm: '',
|
|
5958
|
-
options: [],
|
|
5959
6134
|
inputRef: null,
|
|
5960
6135
|
dropdownRef: null,
|
|
5961
6136
|
focusedIndex: -1,
|
|
5962
|
-
|
|
6137
|
+
internalSelectedIds: [],
|
|
5963
6138
|
};
|
|
6139
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5964
6140
|
const componentId = uniqueId();
|
|
5965
6141
|
const searchInputId = `${componentId}-search`;
|
|
5966
6142
|
// Handle click outside
|
|
@@ -6001,9 +6177,7 @@ const SearchSelect = () => {
|
|
|
6001
6177
|
// Handle add new option
|
|
6002
6178
|
return 'addNew';
|
|
6003
6179
|
}
|
|
6004
|
-
else if (state.focusedIndex < filteredOptions.length)
|
|
6005
|
-
toggleOption(filteredOptions[state.focusedIndex]);
|
|
6006
|
-
}
|
|
6180
|
+
else if (state.focusedIndex < filteredOptions.length) ;
|
|
6007
6181
|
}
|
|
6008
6182
|
break;
|
|
6009
6183
|
case 'Escape':
|
|
@@ -6015,26 +6189,65 @@ const SearchSelect = () => {
|
|
|
6015
6189
|
return null;
|
|
6016
6190
|
};
|
|
6017
6191
|
// Toggle option selection
|
|
6018
|
-
const toggleOption = (option) => {
|
|
6192
|
+
const toggleOption = (option, attrs) => {
|
|
6019
6193
|
if (option.disabled)
|
|
6020
6194
|
return;
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6195
|
+
const controlled = isControlled(attrs);
|
|
6196
|
+
// Get current selected IDs from props or internal state
|
|
6197
|
+
const currentSelectedIds = controlled
|
|
6198
|
+
? attrs.checkedId !== undefined
|
|
6199
|
+
? Array.isArray(attrs.checkedId)
|
|
6200
|
+
? attrs.checkedId
|
|
6201
|
+
: [attrs.checkedId]
|
|
6202
|
+
: []
|
|
6203
|
+
: state.internalSelectedIds;
|
|
6204
|
+
const newIds = currentSelectedIds.includes(option.id)
|
|
6205
|
+
? currentSelectedIds.filter((id) => id !== option.id)
|
|
6206
|
+
: [...currentSelectedIds, option.id];
|
|
6207
|
+
// Update internal state for uncontrolled mode
|
|
6208
|
+
if (!controlled) {
|
|
6209
|
+
state.internalSelectedIds = newIds;
|
|
6210
|
+
}
|
|
6024
6211
|
state.searchTerm = '';
|
|
6025
6212
|
state.focusedIndex = -1;
|
|
6026
|
-
|
|
6213
|
+
// Call onchange if provided
|
|
6214
|
+
if (attrs.onchange) {
|
|
6215
|
+
attrs.onchange(newIds);
|
|
6216
|
+
}
|
|
6027
6217
|
};
|
|
6028
6218
|
// Remove a selected option
|
|
6029
|
-
const removeOption = (
|
|
6030
|
-
|
|
6031
|
-
|
|
6219
|
+
const removeOption = (optionId, attrs) => {
|
|
6220
|
+
const controlled = isControlled(attrs);
|
|
6221
|
+
// Get current selected IDs from props or internal state
|
|
6222
|
+
const currentSelectedIds = controlled
|
|
6223
|
+
? attrs.checkedId !== undefined
|
|
6224
|
+
? Array.isArray(attrs.checkedId)
|
|
6225
|
+
? attrs.checkedId
|
|
6226
|
+
: [attrs.checkedId]
|
|
6227
|
+
: []
|
|
6228
|
+
: state.internalSelectedIds;
|
|
6229
|
+
const newIds = currentSelectedIds.filter((id) => id !== optionId);
|
|
6230
|
+
// Update internal state for uncontrolled mode
|
|
6231
|
+
if (!controlled) {
|
|
6232
|
+
state.internalSelectedIds = newIds;
|
|
6233
|
+
}
|
|
6234
|
+
// Call onchange if provided
|
|
6235
|
+
if (attrs.onchange) {
|
|
6236
|
+
attrs.onchange(newIds);
|
|
6237
|
+
}
|
|
6032
6238
|
};
|
|
6033
6239
|
return {
|
|
6034
|
-
oninit: ({ attrs
|
|
6035
|
-
state.
|
|
6036
|
-
state
|
|
6037
|
-
|
|
6240
|
+
oninit: ({ attrs }) => {
|
|
6241
|
+
state.id = attrs.id || uniqueId();
|
|
6242
|
+
// Initialize internal state for uncontrolled mode
|
|
6243
|
+
if (!isControlled(attrs)) {
|
|
6244
|
+
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
6245
|
+
? Array.isArray(attrs.defaultCheckedId)
|
|
6246
|
+
? attrs.defaultCheckedId
|
|
6247
|
+
: [attrs.defaultCheckedId]
|
|
6248
|
+
: [];
|
|
6249
|
+
state.internalSelectedIds = defaultIds;
|
|
6250
|
+
}
|
|
6038
6251
|
},
|
|
6039
6252
|
oncreate() {
|
|
6040
6253
|
document.addEventListener('click', handleClickOutside);
|
|
@@ -6042,14 +6255,27 @@ const SearchSelect = () => {
|
|
|
6042
6255
|
onremove() {
|
|
6043
6256
|
document.removeEventListener('click', handleClickOutside);
|
|
6044
6257
|
},
|
|
6045
|
-
view({ attrs
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6258
|
+
view({ attrs }) {
|
|
6259
|
+
const controlled = isControlled(attrs);
|
|
6260
|
+
// Get selected IDs from props or internal state
|
|
6261
|
+
const selectedIds = controlled
|
|
6262
|
+
? attrs.checkedId !== undefined
|
|
6263
|
+
? Array.isArray(attrs.checkedId)
|
|
6264
|
+
? attrs.checkedId
|
|
6265
|
+
: [attrs.checkedId]
|
|
6266
|
+
: []
|
|
6267
|
+
: state.internalSelectedIds;
|
|
6268
|
+
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
|
|
6269
|
+
// Use i18n values if provided, otherwise use defaults
|
|
6270
|
+
const texts = {
|
|
6271
|
+
noOptionsFound: i18n.noOptionsFound || noOptionsFound,
|
|
6272
|
+
addNewPrefix: i18n.addNewPrefix || '+',
|
|
6273
|
+
};
|
|
6274
|
+
// Get selected options for display
|
|
6275
|
+
const selectedOptions = options.filter((opt) => selectedIds.includes(opt.id));
|
|
6050
6276
|
// Safely filter options
|
|
6051
|
-
const filteredOptions =
|
|
6052
|
-
!
|
|
6277
|
+
const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
6278
|
+
!selectedIds.includes(option.id));
|
|
6053
6279
|
// Check if we should show the "add new option" element
|
|
6054
6280
|
const showAddNew = oncreateNewOption &&
|
|
6055
6281
|
state.searchTerm &&
|
|
@@ -6067,6 +6293,7 @@ const SearchSelect = () => {
|
|
|
6067
6293
|
state.isOpen = !state.isOpen;
|
|
6068
6294
|
// console.log('SearchSelect state changed to', state.isOpen); // Debug log
|
|
6069
6295
|
},
|
|
6296
|
+
class: 'chips chips-container',
|
|
6070
6297
|
style: {
|
|
6071
6298
|
display: 'flex',
|
|
6072
6299
|
alignItems: 'end',
|
|
@@ -6079,25 +6306,20 @@ const SearchSelect = () => {
|
|
|
6079
6306
|
// Hidden input for label association and accessibility
|
|
6080
6307
|
m('input', {
|
|
6081
6308
|
type: 'text',
|
|
6082
|
-
id:
|
|
6083
|
-
value:
|
|
6309
|
+
id: state.id,
|
|
6310
|
+
value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
|
|
6084
6311
|
readonly: true,
|
|
6312
|
+
class: 'sr-only',
|
|
6085
6313
|
style: { position: 'absolute', left: '-9999px', opacity: 0 },
|
|
6086
6314
|
}),
|
|
6087
6315
|
// Selected Options (chips)
|
|
6088
|
-
...
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
onclick: (e) => {
|
|
6094
|
-
e.stopPropagation();
|
|
6095
|
-
removeOption(option);
|
|
6096
|
-
},
|
|
6097
|
-
}),
|
|
6098
|
-
])),
|
|
6316
|
+
...selectedOptions.map((option) => m(SelectedChip, {
|
|
6317
|
+
// key: option.id,
|
|
6318
|
+
option,
|
|
6319
|
+
onRemove: (id) => removeOption(id, attrs),
|
|
6320
|
+
})),
|
|
6099
6321
|
// Placeholder when no options selected
|
|
6100
|
-
|
|
6322
|
+
selectedOptions.length === 0 &&
|
|
6101
6323
|
placeholder &&
|
|
6102
6324
|
m('span.placeholder', {
|
|
6103
6325
|
style: {
|
|
@@ -6118,8 +6340,8 @@ const SearchSelect = () => {
|
|
|
6118
6340
|
// Label
|
|
6119
6341
|
label &&
|
|
6120
6342
|
m('label', {
|
|
6121
|
-
for:
|
|
6122
|
-
class: placeholder ||
|
|
6343
|
+
for: state.id,
|
|
6344
|
+
class: placeholder || selectedOptions.length > 0 ? 'active' : '',
|
|
6123
6345
|
}, label),
|
|
6124
6346
|
// Dropdown Menu
|
|
6125
6347
|
state.isOpen &&
|
|
@@ -6135,7 +6357,6 @@ const SearchSelect = () => {
|
|
|
6135
6357
|
m('li', // Search Input
|
|
6136
6358
|
{
|
|
6137
6359
|
class: 'search-wrapper',
|
|
6138
|
-
style: { padding: '0 16px', position: 'relative' },
|
|
6139
6360
|
}, [
|
|
6140
6361
|
m('input', {
|
|
6141
6362
|
type: 'text',
|
|
@@ -6154,29 +6375,21 @@ const SearchSelect = () => {
|
|
|
6154
6375
|
const result = handleKeyDown(e, filteredOptions, !!showAddNew);
|
|
6155
6376
|
if (result === 'addNew' && oncreateNewOption) {
|
|
6156
6377
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6157
|
-
toggleOption(option);
|
|
6378
|
+
toggleOption(option, attrs);
|
|
6379
|
+
}
|
|
6380
|
+
else if (e.key === 'Enter' &&
|
|
6381
|
+
state.focusedIndex >= 0 &&
|
|
6382
|
+
state.focusedIndex < filteredOptions.length) {
|
|
6383
|
+
toggleOption(filteredOptions[state.focusedIndex], attrs);
|
|
6158
6384
|
}
|
|
6159
6385
|
},
|
|
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
|
-
},
|
|
6386
|
+
class: 'search-select-input',
|
|
6170
6387
|
}),
|
|
6171
6388
|
]),
|
|
6172
6389
|
// No options found message or list of options
|
|
6173
6390
|
...(filteredOptions.length === 0 && !showAddNew
|
|
6174
6391
|
? [
|
|
6175
|
-
m('li',
|
|
6176
|
-
// {
|
|
6177
|
-
// style: getNoOptionsStyles(),
|
|
6178
|
-
// },
|
|
6179
|
-
noOptionsFound),
|
|
6392
|
+
m('li.search-select-no-options', texts.noOptionsFound),
|
|
6180
6393
|
]
|
|
6181
6394
|
: []),
|
|
6182
6395
|
// Add new option item
|
|
@@ -6185,35 +6398,27 @@ const SearchSelect = () => {
|
|
|
6185
6398
|
m('li', {
|
|
6186
6399
|
onclick: async () => {
|
|
6187
6400
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6188
|
-
toggleOption(option);
|
|
6401
|
+
toggleOption(option, attrs);
|
|
6189
6402
|
},
|
|
6190
6403
|
class: state.focusedIndex === filteredOptions.length ? 'active' : '',
|
|
6191
6404
|
onmouseover: () => {
|
|
6192
6405
|
state.focusedIndex = filteredOptions.length;
|
|
6193
6406
|
},
|
|
6194
|
-
}, [m('span',
|
|
6407
|
+
}, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
|
|
6195
6408
|
]
|
|
6196
6409
|
: []),
|
|
6197
6410
|
// List of filtered options
|
|
6198
|
-
...filteredOptions.map((option, index) => m(
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
state.focusedIndex = index;
|
|
6208
|
-
}
|
|
6411
|
+
...filteredOptions.map((option, index) => m(DropdownOption, {
|
|
6412
|
+
// key: option.id,
|
|
6413
|
+
option,
|
|
6414
|
+
index,
|
|
6415
|
+
selectedIds,
|
|
6416
|
+
isFocused: state.focusedIndex === index,
|
|
6417
|
+
onToggle: (opt) => toggleOption(opt, attrs),
|
|
6418
|
+
onMouseOver: (idx) => {
|
|
6419
|
+
state.focusedIndex = idx;
|
|
6209
6420
|
},
|
|
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
|
-
]))),
|
|
6421
|
+
})),
|
|
6217
6422
|
]),
|
|
6218
6423
|
]);
|
|
6219
6424
|
},
|
|
@@ -8296,4 +8501,4 @@ const isValidationError = (result) => !isValidationSuccess(result);
|
|
|
8296
8501
|
// ============================================================================
|
|
8297
8502
|
// All types are already exported via individual export declarations above
|
|
8298
8503
|
|
|
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 };
|
|
8504
|
+
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 };
|