mithril-materialized 3.0.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/README.md +14 -20
- package/dist/components.css +3 -0
- package/dist/core.css +154 -14
- package/dist/dropdown.d.ts +8 -5
- package/dist/forms.css +154 -0
- package/dist/index.css +162 -14
- package/dist/index.esm.js +623 -391
- package/dist/index.js +623 -390
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +623 -390
- package/dist/input-options.d.ts +12 -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/_input-fields.scss +20 -0
- 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,20 +2947,57 @@ 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
|
-
|
|
2950
|
+
// const lengthUpdateHandler = () => {
|
|
2951
|
+
// const length = state.inputElement?.value.length;
|
|
2952
|
+
// if (length) {
|
|
2953
|
+
// state.currentLength = length;
|
|
2954
|
+
// state.hasInteracted = length > 0;
|
|
2955
|
+
// }
|
|
2956
|
+
// };
|
|
2957
|
+
const clearInput = (oninput, onchange) => {
|
|
2958
|
+
if (state.inputElement) {
|
|
2959
|
+
state.inputElement.value = '';
|
|
2960
|
+
state.inputElement.focus();
|
|
2961
|
+
state.active = false;
|
|
2962
|
+
// state.currentLength = 0;
|
|
2963
|
+
// state.hasInteracted = false;
|
|
2964
|
+
// Trigger oninput and onchange callbacks
|
|
2965
|
+
const value = getValue(state.inputElement);
|
|
2966
|
+
if (oninput) {
|
|
2967
|
+
oninput(value);
|
|
2968
|
+
}
|
|
2969
|
+
if (onchange) {
|
|
2970
|
+
onchange(value);
|
|
2971
|
+
}
|
|
2972
|
+
// Update validation state
|
|
2973
|
+
state.inputElement.classList.remove('valid', 'invalid');
|
|
2974
|
+
state.isValid = true;
|
|
2975
|
+
m.redraw();
|
|
2922
2976
|
}
|
|
2923
2977
|
};
|
|
2924
2978
|
// Range slider helper functions
|
|
2925
2979
|
// Range slider rendering functions are now in separate module
|
|
2926
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
|
+
},
|
|
2927
2998
|
view: ({ attrs }) => {
|
|
2928
|
-
var _a;
|
|
2929
|
-
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id,
|
|
2999
|
+
var _a, _b;
|
|
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"]);
|
|
2930
3001
|
// const attributes = toAttrs(params);
|
|
2931
3002
|
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
2932
3003
|
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
@@ -2943,10 +3014,14 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2943
3014
|
isMandatory,
|
|
2944
3015
|
helperText }));
|
|
2945
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;
|
|
2946
3021
|
return m('.input-field', { className: cn, style }, [
|
|
2947
3022
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2948
3023
|
m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
|
|
2949
|
-
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
|
|
2950
3025
|
? {
|
|
2951
3026
|
height: attrs.height || '200px',
|
|
2952
3027
|
width: '6px',
|
|
@@ -2960,25 +3035,9 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2960
3035
|
if (focus(attrs)) {
|
|
2961
3036
|
input.focus();
|
|
2962
3037
|
}
|
|
2963
|
-
//
|
|
2964
|
-
if (
|
|
2965
|
-
input.value = String(
|
|
2966
|
-
}
|
|
2967
|
-
// Update character count state for counter component
|
|
2968
|
-
if (maxLength) {
|
|
2969
|
-
state.currentLength = input.value.length; // Initial count
|
|
2970
|
-
}
|
|
2971
|
-
// Range input functionality
|
|
2972
|
-
if (type === 'range' && !attrs.minmax) {
|
|
2973
|
-
const updateThumb = () => {
|
|
2974
|
-
const value = input.value;
|
|
2975
|
-
const min = input.min || '0';
|
|
2976
|
-
const max = input.max || '100';
|
|
2977
|
-
const percentage = ((parseFloat(value) - parseFloat(min)) / (parseFloat(max) - parseFloat(min))) * 100;
|
|
2978
|
-
input.style.setProperty('--range-progress', `${percentage}%`);
|
|
2979
|
-
};
|
|
2980
|
-
input.addEventListener('input', updateThumb);
|
|
2981
|
-
updateThumb(); // Initial position
|
|
3038
|
+
// For uncontrolled mode, set initial value only
|
|
3039
|
+
if (!controlled && attrs.defaultValue !== undefined) {
|
|
3040
|
+
input.value = String(attrs.defaultValue);
|
|
2982
3041
|
}
|
|
2983
3042
|
}, onkeyup: onkeyup
|
|
2984
3043
|
? (ev) => {
|
|
@@ -2992,21 +3051,25 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2992
3051
|
? (ev) => {
|
|
2993
3052
|
onkeypress(ev, getValue(ev.target));
|
|
2994
3053
|
}
|
|
2995
|
-
: undefined, onupdate: validate
|
|
2996
|
-
? ({ dom }) => {
|
|
2997
|
-
const target = dom;
|
|
2998
|
-
setValidity(target, validate(getValue(target), target));
|
|
2999
|
-
}
|
|
3000
3054
|
: undefined, oninput: (e) => {
|
|
3001
3055
|
state.active = true;
|
|
3056
|
+
state.hasInteracted = false;
|
|
3002
3057
|
const target = e.target;
|
|
3003
3058
|
// Handle original oninput logic
|
|
3004
|
-
const
|
|
3059
|
+
const inputValue = getValue(target);
|
|
3060
|
+
// Update internal state for uncontrolled mode
|
|
3061
|
+
if (!controlled) {
|
|
3062
|
+
state.internalValue = inputValue;
|
|
3063
|
+
}
|
|
3005
3064
|
if (oninput) {
|
|
3006
|
-
oninput(
|
|
3065
|
+
oninput(inputValue);
|
|
3007
3066
|
}
|
|
3008
|
-
if (
|
|
3009
|
-
|
|
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}%`);
|
|
3010
3073
|
}
|
|
3011
3074
|
// Don't validate on input, only clear error states if user is typing
|
|
3012
3075
|
if (validate && target.classList.contains('invalid') && target.value.length > 0) {
|
|
@@ -3022,6 +3085,14 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3022
3085
|
state.isValid = true;
|
|
3023
3086
|
}
|
|
3024
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
|
+
}
|
|
3025
3096
|
}, onfocus: () => {
|
|
3026
3097
|
state.active = true;
|
|
3027
3098
|
}, onblur: (e) => {
|
|
@@ -3058,6 +3129,48 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3058
3129
|
state.isValid = true;
|
|
3059
3130
|
}
|
|
3060
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
|
+
}
|
|
3061
3174
|
// Also call the original onblur handler if provided
|
|
3062
3175
|
if (attrs.onblur) {
|
|
3063
3176
|
attrs.onblur(e);
|
|
@@ -3066,23 +3179,35 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3066
3179
|
onchange(getValue(state.inputElement));
|
|
3067
3180
|
}
|
|
3068
3181
|
} })),
|
|
3182
|
+
// Clear button - only for text inputs with canClear enabled and has content
|
|
3183
|
+
canClear && type === 'text' && ((_b = state.inputElement) === null || _b === void 0 ? void 0 : _b.value)
|
|
3184
|
+
? m(MaterialIcon, {
|
|
3185
|
+
name: 'close',
|
|
3186
|
+
className: 'input-clear-btn',
|
|
3187
|
+
onclick: (e) => {
|
|
3188
|
+
e.preventDefault();
|
|
3189
|
+
e.stopPropagation();
|
|
3190
|
+
clearInput(oninput, onchange);
|
|
3191
|
+
},
|
|
3192
|
+
})
|
|
3193
|
+
: undefined,
|
|
3069
3194
|
m(Label, {
|
|
3070
3195
|
label,
|
|
3071
3196
|
id,
|
|
3072
3197
|
isMandatory,
|
|
3073
3198
|
isActive,
|
|
3074
|
-
initialValue:
|
|
3199
|
+
initialValue: value !== undefined && value !== '',
|
|
3075
3200
|
}),
|
|
3076
3201
|
m(HelperText, {
|
|
3077
3202
|
helperText,
|
|
3078
3203
|
dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
|
|
3079
3204
|
dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
|
|
3080
3205
|
}),
|
|
3081
|
-
maxLength
|
|
3206
|
+
maxLength && typeof value === 'string'
|
|
3082
3207
|
? m(CharacterCounter, {
|
|
3083
|
-
currentLength:
|
|
3208
|
+
currentLength: value.length,
|
|
3084
3209
|
maxLength,
|
|
3085
|
-
show:
|
|
3210
|
+
show: value.length > 0,
|
|
3086
3211
|
})
|
|
3087
3212
|
: undefined,
|
|
3088
3213
|
]);
|
|
@@ -3109,7 +3234,7 @@ const FileInput = () => {
|
|
|
3109
3234
|
let i;
|
|
3110
3235
|
return {
|
|
3111
3236
|
view: ({ attrs }) => {
|
|
3112
|
-
const { multiple, disabled,
|
|
3237
|
+
const { multiple, disabled, value, placeholder, onchange, className = 'col s12', accept: acceptedFiles, label = 'File', } = attrs;
|
|
3113
3238
|
const accept = acceptedFiles
|
|
3114
3239
|
? acceptedFiles instanceof Array
|
|
3115
3240
|
? acceptedFiles.join(', ')
|
|
@@ -3140,8 +3265,8 @@ const FileInput = () => {
|
|
|
3140
3265
|
placeholder,
|
|
3141
3266
|
oncreate: ({ dom }) => {
|
|
3142
3267
|
i = dom;
|
|
3143
|
-
if (
|
|
3144
|
-
i.value =
|
|
3268
|
+
if (value)
|
|
3269
|
+
i.value = value;
|
|
3145
3270
|
},
|
|
3146
3271
|
})),
|
|
3147
3272
|
(canClear || (i === null || i === void 0 ? void 0 : i.value)) &&
|
|
@@ -3168,11 +3293,14 @@ const FileInput = () => {
|
|
|
3168
3293
|
|
|
3169
3294
|
/** Component to show a check box */
|
|
3170
3295
|
const InputCheckbox = () => {
|
|
3296
|
+
let checkboxId;
|
|
3171
3297
|
return {
|
|
3172
3298
|
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3173
|
-
|
|
3299
|
+
if (!checkboxId)
|
|
3300
|
+
checkboxId = inputId || uniqueId();
|
|
3174
3301
|
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3175
3302
|
m('input[type=checkbox][tabindex=0]', {
|
|
3303
|
+
className: disabled ? 'disabled' : undefined,
|
|
3176
3304
|
id: checkboxId,
|
|
3177
3305
|
checked,
|
|
3178
3306
|
disabled,
|
|
@@ -3189,78 +3317,79 @@ const InputCheckbox = () => {
|
|
|
3189
3317
|
},
|
|
3190
3318
|
};
|
|
3191
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
|
+
};
|
|
3192
3329
|
/** A list of checkboxes */
|
|
3193
3330
|
const Options = () => {
|
|
3194
|
-
const state = {
|
|
3195
|
-
|
|
3196
|
-
|
|
3331
|
+
const state = {
|
|
3332
|
+
componentId: '',
|
|
3333
|
+
};
|
|
3334
|
+
const selectAll = (options, onchange) => {
|
|
3197
3335
|
const allIds = options.map((option) => option.id);
|
|
3198
|
-
|
|
3199
|
-
if (callback)
|
|
3200
|
-
callback(allIds);
|
|
3336
|
+
onchange && onchange(allIds);
|
|
3201
3337
|
};
|
|
3202
|
-
const selectNone = (
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
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);
|
|
3206
3347
|
};
|
|
3207
3348
|
return {
|
|
3208
|
-
oninit: ({ attrs
|
|
3209
|
-
|
|
3210
|
-
state.checkedId = checkedId;
|
|
3211
|
-
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3212
|
-
state.componentId = id || uniqueId();
|
|
3349
|
+
oninit: ({ attrs }) => {
|
|
3350
|
+
state.componentId = attrs.id || uniqueId();
|
|
3213
3351
|
},
|
|
3214
|
-
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false,
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
if (checked) {
|
|
3219
|
-
checkedIds.push(propId);
|
|
3220
|
-
}
|
|
3221
|
-
state.checkedIds = checkedIds;
|
|
3222
|
-
callback(checkedIds);
|
|
3223
|
-
}
|
|
3224
|
-
: 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);
|
|
3225
3356
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
3226
|
-
const
|
|
3227
|
-
|
|
3357
|
+
const optionItems = options.map((option) => ({
|
|
3358
|
+
component: InputCheckbox,
|
|
3359
|
+
props: {
|
|
3228
3360
|
disabled: disabled || option.disabled,
|
|
3229
3361
|
label: option.label,
|
|
3230
|
-
onchange: onchange ? (v) =>
|
|
3362
|
+
onchange: onchange ? (v) => handleChange(option.id, v, checkedIds, onchange) : undefined,
|
|
3231
3363
|
className: option.className || checkboxClass,
|
|
3232
3364
|
checked: isChecked(option.id),
|
|
3233
3365
|
description: option.description,
|
|
3234
3366
|
inputId: `${state.componentId}-${option.id}`,
|
|
3235
|
-
}
|
|
3236
|
-
:
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
description: option.description,
|
|
3243
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3244
|
-
}));
|
|
3367
|
+
},
|
|
3368
|
+
key: option.id,
|
|
3369
|
+
}));
|
|
3370
|
+
const optionsContent = m(OptionsList, {
|
|
3371
|
+
options: optionItems,
|
|
3372
|
+
layout,
|
|
3373
|
+
});
|
|
3245
3374
|
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3246
3375
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3247
3376
|
showSelectAll &&
|
|
3248
|
-
m('div.select-all-controls', { style:
|
|
3377
|
+
m('div.select-all-controls', { style: { marginBottom: '10px' } }, [
|
|
3249
3378
|
m('a', {
|
|
3250
3379
|
href: '#',
|
|
3251
3380
|
onclick: (e) => {
|
|
3252
3381
|
e.preventDefault();
|
|
3253
|
-
selectAll(options,
|
|
3382
|
+
selectAll(options, onchange);
|
|
3254
3383
|
},
|
|
3255
|
-
style:
|
|
3256
|
-
},
|
|
3384
|
+
style: { marginRight: '15px' },
|
|
3385
|
+
}, selectAllText),
|
|
3257
3386
|
m('a', {
|
|
3258
3387
|
href: '#',
|
|
3259
3388
|
onclick: (e) => {
|
|
3260
3389
|
e.preventDefault();
|
|
3261
|
-
selectNone(
|
|
3390
|
+
selectNone(onchange);
|
|
3262
3391
|
},
|
|
3263
|
-
},
|
|
3392
|
+
}, selectNoneText),
|
|
3264
3393
|
]),
|
|
3265
3394
|
description && m(HelperText, { helperText: description }),
|
|
3266
3395
|
m('form', { action: '#' }, optionsContent),
|
|
@@ -3855,11 +3984,19 @@ const DataTable = () => {
|
|
|
3855
3984
|
const Dropdown = () => {
|
|
3856
3985
|
const state = {
|
|
3857
3986
|
isOpen: false,
|
|
3858
|
-
initialValue: undefined,
|
|
3859
3987
|
id: '',
|
|
3860
3988
|
focusedIndex: -1,
|
|
3861
3989
|
inputRef: null,
|
|
3862
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
|
+
}
|
|
3863
4000
|
};
|
|
3864
4001
|
const handleKeyDown = (e, items, onchange) => {
|
|
3865
4002
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3868,7 +4005,7 @@ const Dropdown = () => {
|
|
|
3868
4005
|
e.preventDefault();
|
|
3869
4006
|
if (!state.isOpen) {
|
|
3870
4007
|
state.isOpen = true;
|
|
3871
|
-
state.focusedIndex = 0;
|
|
4008
|
+
state.focusedIndex = availableItems.length > 0 ? 0 : -1;
|
|
3872
4009
|
}
|
|
3873
4010
|
else {
|
|
3874
4011
|
state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
|
|
@@ -3886,15 +4023,13 @@ const Dropdown = () => {
|
|
|
3886
4023
|
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
3887
4024
|
const selectedItem = availableItems[state.focusedIndex];
|
|
3888
4025
|
const value = (selectedItem.id || selectedItem.label);
|
|
3889
|
-
state.initialValue = value;
|
|
3890
4026
|
state.isOpen = false;
|
|
3891
4027
|
state.focusedIndex = -1;
|
|
3892
|
-
|
|
3893
|
-
onchange(value);
|
|
4028
|
+
return value; // Return value to be handled in view
|
|
3894
4029
|
}
|
|
3895
4030
|
else if (!state.isOpen) {
|
|
3896
4031
|
state.isOpen = true;
|
|
3897
|
-
state.focusedIndex = 0;
|
|
4032
|
+
state.focusedIndex = availableItems.length > 0 ? 0 : -1;
|
|
3898
4033
|
}
|
|
3899
4034
|
break;
|
|
3900
4035
|
case 'Escape':
|
|
@@ -3905,15 +4040,36 @@ const Dropdown = () => {
|
|
|
3905
4040
|
}
|
|
3906
4041
|
};
|
|
3907
4042
|
return {
|
|
3908
|
-
oninit: ({ attrs
|
|
3909
|
-
|
|
3910
|
-
state.
|
|
3911
|
-
//
|
|
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);
|
|
4052
|
+
},
|
|
4053
|
+
onremove: () => {
|
|
4054
|
+
// Cleanup global listener
|
|
4055
|
+
document.removeEventListener('click', closeDropdown);
|
|
3912
4056
|
},
|
|
3913
|
-
view: ({ attrs
|
|
3914
|
-
const {
|
|
3915
|
-
const
|
|
3916
|
-
|
|
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()
|
|
3917
4073
|
: undefined;
|
|
3918
4074
|
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
3919
4075
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3921,13 +4077,12 @@ const Dropdown = () => {
|
|
|
3921
4077
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3922
4078
|
m(HelperText, { helperText }),
|
|
3923
4079
|
m('.select-wrapper', {
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
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
|
+
},
|
|
3931
4086
|
tabindex: disabled ? -1 : 0,
|
|
3932
4087
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
3933
4088
|
'aria-haspopup': 'listbox',
|
|
@@ -3944,7 +4099,8 @@ const Dropdown = () => {
|
|
|
3944
4099
|
e.stopPropagation();
|
|
3945
4100
|
if (!disabled) {
|
|
3946
4101
|
state.isOpen = !state.isOpen;
|
|
3947
|
-
|
|
4102
|
+
// Reset focus index when opening/closing
|
|
4103
|
+
state.focusedIndex = -1;
|
|
3948
4104
|
}
|
|
3949
4105
|
},
|
|
3950
4106
|
}),
|
|
@@ -3960,36 +4116,31 @@ const Dropdown = () => {
|
|
|
3960
4116
|
onremove: () => {
|
|
3961
4117
|
state.dropdownRef = null;
|
|
3962
4118
|
},
|
|
3963
|
-
style: getDropdownStyles(state.inputRef, true, items
|
|
3964
|
-
|
|
3965
|
-
group: undefined }))), true),
|
|
3966
|
-
}, items.map((item, index) => {
|
|
4119
|
+
style: getDropdownStyles(state.inputRef, true, items, true),
|
|
4120
|
+
}, items.map((item) => {
|
|
3967
4121
|
if (item.divider) {
|
|
3968
|
-
return m('li.divider'
|
|
3969
|
-
key: `divider-${index}`,
|
|
3970
|
-
});
|
|
4122
|
+
return m('li.divider');
|
|
3971
4123
|
}
|
|
3972
4124
|
const itemIndex = availableItems.indexOf(item);
|
|
3973
4125
|
const isFocused = itemIndex === state.focusedIndex;
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
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
|
+
: () => {
|
|
3985
4138
|
const value = (item.id || item.label);
|
|
3986
|
-
state.initialValue = value;
|
|
3987
4139
|
state.isOpen = false;
|
|
3988
4140
|
state.focusedIndex = -1;
|
|
3989
|
-
|
|
3990
|
-
onchange(value);
|
|
4141
|
+
handleSelection(value);
|
|
3991
4142
|
},
|
|
3992
|
-
|
|
4143
|
+
}, m('span', {
|
|
3993
4144
|
style: {
|
|
3994
4145
|
display: 'flex',
|
|
3995
4146
|
alignItems: 'center',
|
|
@@ -5126,9 +5277,9 @@ const TimePicker = () => {
|
|
|
5126
5277
|
dx: 0,
|
|
5127
5278
|
dy: 0,
|
|
5128
5279
|
};
|
|
5129
|
-
// Handle
|
|
5130
|
-
if (attrs.
|
|
5131
|
-
updateTimeFromInput(attrs.
|
|
5280
|
+
// Handle value after options are set
|
|
5281
|
+
if (attrs.defaultValue) {
|
|
5282
|
+
updateTimeFromInput(attrs.defaultValue);
|
|
5132
5283
|
}
|
|
5133
5284
|
},
|
|
5134
5285
|
onremove: () => {
|
|
@@ -5410,32 +5561,47 @@ const RadioButton = () => ({
|
|
|
5410
5561
|
},
|
|
5411
5562
|
});
|
|
5412
5563
|
/** Component to show a list of radio buttons, from which you can choose one. */
|
|
5413
|
-
// export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
|
|
5414
5564
|
const RadioButtons = () => {
|
|
5415
|
-
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';
|
|
5416
5571
|
return {
|
|
5417
|
-
oninit: ({ attrs
|
|
5418
|
-
state.
|
|
5419
|
-
state
|
|
5420
|
-
|
|
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
|
+
}
|
|
5421
5578
|
},
|
|
5422
|
-
view: ({ attrs
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
const
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
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);
|
|
5431
5593
|
}
|
|
5432
5594
|
};
|
|
5433
5595
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5434
|
-
const
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
:
|
|
5438
|
-
|
|
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
|
+
});
|
|
5439
5605
|
return m('div', { id: componentId, className: cn }, [
|
|
5440
5606
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
5441
5607
|
description && m('p.helper-text', m.trust(description)),
|
|
@@ -5450,30 +5616,42 @@ const Select = () => {
|
|
|
5450
5616
|
const state = {
|
|
5451
5617
|
id: '',
|
|
5452
5618
|
isOpen: false,
|
|
5453
|
-
selectedIds: [],
|
|
5454
5619
|
focusedIndex: -1,
|
|
5455
5620
|
inputRef: null,
|
|
5456
5621
|
dropdownRef: null,
|
|
5622
|
+
internalSelectedIds: [],
|
|
5457
5623
|
};
|
|
5624
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5458
5625
|
const isSelected = (id, selectedIds) => {
|
|
5459
5626
|
return selectedIds.some((selectedId) => selectedId === id);
|
|
5460
5627
|
};
|
|
5461
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;
|
|
5462
5639
|
if (multiple) {
|
|
5463
|
-
|
|
5464
|
-
?
|
|
5465
|
-
|
|
5466
|
-
: [...state.selectedIds, id];
|
|
5467
|
-
state.selectedIds = newIds;
|
|
5468
|
-
attrs.onchange(newIds);
|
|
5469
|
-
console.log(newIds);
|
|
5470
|
-
// Keep dropdown open for multiple select
|
|
5640
|
+
newIds = currentSelectedIds.includes(id)
|
|
5641
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
5642
|
+
: [...currentSelectedIds, id];
|
|
5471
5643
|
}
|
|
5472
5644
|
else {
|
|
5473
|
-
|
|
5474
|
-
// Close dropdown for single select
|
|
5475
|
-
|
|
5476
|
-
|
|
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);
|
|
5477
5655
|
}
|
|
5478
5656
|
};
|
|
5479
5657
|
const handleKeyDown = (e, attrs) => {
|
|
@@ -5525,88 +5703,22 @@ const Select = () => {
|
|
|
5525
5703
|
};
|
|
5526
5704
|
const closeDropdown = (e) => {
|
|
5527
5705
|
const target = e.target;
|
|
5528
|
-
if (!target.closest('.select-
|
|
5706
|
+
if (!target.closest('.input-field.select-space')) {
|
|
5529
5707
|
state.isOpen = false;
|
|
5530
5708
|
m.redraw();
|
|
5531
5709
|
}
|
|
5532
5710
|
};
|
|
5533
|
-
const renderGroupedOptions = (options, multiple, attrs) => {
|
|
5534
|
-
const groupedOptions = {};
|
|
5535
|
-
const ungroupedOptions = [];
|
|
5536
|
-
// Group options by their group property
|
|
5537
|
-
options.forEach((option) => {
|
|
5538
|
-
if (option.group) {
|
|
5539
|
-
if (!groupedOptions[option.group]) {
|
|
5540
|
-
groupedOptions[option.group] = [];
|
|
5541
|
-
}
|
|
5542
|
-
groupedOptions[option.group].push(option);
|
|
5543
|
-
}
|
|
5544
|
-
else {
|
|
5545
|
-
ungroupedOptions.push(option);
|
|
5546
|
-
}
|
|
5547
|
-
});
|
|
5548
|
-
const renderElements = [];
|
|
5549
|
-
// Render ungrouped options first
|
|
5550
|
-
ungroupedOptions.forEach((option) => {
|
|
5551
|
-
renderElements.push(m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === options.indexOf(option) ? 'focused' : '' }, (option.disabled
|
|
5552
|
-
? {}
|
|
5553
|
-
: {
|
|
5554
|
-
onclick: (e) => {
|
|
5555
|
-
e.stopPropagation();
|
|
5556
|
-
toggleOption(option.id, multiple, attrs);
|
|
5557
|
-
},
|
|
5558
|
-
})), m('span', multiple
|
|
5559
|
-
? m('label', { for: option.id }, m('input', {
|
|
5560
|
-
id: option.id,
|
|
5561
|
-
type: 'checkbox',
|
|
5562
|
-
checked: state.selectedIds.includes(option.id),
|
|
5563
|
-
disabled: option.disabled ? true : undefined,
|
|
5564
|
-
onclick: (e) => {
|
|
5565
|
-
e.stopPropagation();
|
|
5566
|
-
},
|
|
5567
|
-
}), m('span', option.label))
|
|
5568
|
-
: option.label)));
|
|
5569
|
-
});
|
|
5570
|
-
// Render grouped options
|
|
5571
|
-
Object.keys(groupedOptions).forEach((groupName) => {
|
|
5572
|
-
// Add group header
|
|
5573
|
-
renderElements.push(m('li.optgroup', { tabindex: 0 }, m('span', groupName)));
|
|
5574
|
-
// Add group options
|
|
5575
|
-
groupedOptions[groupName].forEach((option) => {
|
|
5576
|
-
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
|
|
5577
|
-
? {}
|
|
5578
|
-
: {
|
|
5579
|
-
onclick: (e) => {
|
|
5580
|
-
e.stopPropagation();
|
|
5581
|
-
toggleOption(option.id, multiple, attrs);
|
|
5582
|
-
},
|
|
5583
|
-
})), m('span', multiple
|
|
5584
|
-
? m('label', { for: option.id }, m('input', {
|
|
5585
|
-
id: option.id,
|
|
5586
|
-
type: 'checkbox',
|
|
5587
|
-
checked: state.selectedIds.includes(option.id),
|
|
5588
|
-
disabled: option.disabled ? true : undefined,
|
|
5589
|
-
onclick: (e) => {
|
|
5590
|
-
e.stopPropagation();
|
|
5591
|
-
},
|
|
5592
|
-
}), m('span', option.label))
|
|
5593
|
-
: option.label)));
|
|
5594
|
-
});
|
|
5595
|
-
});
|
|
5596
|
-
return renderElements;
|
|
5597
|
-
};
|
|
5598
5711
|
return {
|
|
5599
5712
|
oninit: ({ attrs }) => {
|
|
5600
|
-
|
|
5601
|
-
state
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
}
|
|
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;
|
|
5610
5722
|
}
|
|
5611
5723
|
// Add global click listener to close dropdown
|
|
5612
5724
|
document.addEventListener('click', closeDropdown);
|
|
@@ -5616,17 +5728,18 @@ const Select = () => {
|
|
|
5616
5728
|
document.removeEventListener('click', closeDropdown);
|
|
5617
5729
|
},
|
|
5618
5730
|
view: ({ attrs }) => {
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
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;
|
|
5627
5740
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
|
|
5628
5741
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5629
|
-
const selectedOptions = options.filter((opt) => isSelected(opt.id,
|
|
5742
|
+
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
5630
5743
|
return m('.input-field.select-space', {
|
|
5631
5744
|
className: finalClassName,
|
|
5632
5745
|
key,
|
|
@@ -5635,11 +5748,6 @@ const Select = () => {
|
|
|
5635
5748
|
// Icon prefix
|
|
5636
5749
|
iconName && m('i.material-icons.prefix', iconName),
|
|
5637
5750
|
m('.select-wrapper', {
|
|
5638
|
-
onclick: disabled
|
|
5639
|
-
? undefined
|
|
5640
|
-
: () => {
|
|
5641
|
-
state.isOpen = !state.isOpen;
|
|
5642
|
-
},
|
|
5643
5751
|
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
|
|
5644
5752
|
tabindex: disabled ? -1 : 0,
|
|
5645
5753
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
@@ -5655,7 +5763,9 @@ const Select = () => {
|
|
|
5655
5763
|
onclick: (e) => {
|
|
5656
5764
|
e.preventDefault();
|
|
5657
5765
|
e.stopPropagation();
|
|
5658
|
-
|
|
5766
|
+
if (!disabled) {
|
|
5767
|
+
state.isOpen = !state.isOpen;
|
|
5768
|
+
}
|
|
5659
5769
|
},
|
|
5660
5770
|
}),
|
|
5661
5771
|
// Dropdown Menu
|
|
@@ -5671,7 +5781,63 @@ const Select = () => {
|
|
|
5671
5781
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5672
5782
|
}, [
|
|
5673
5783
|
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5674
|
-
|
|
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), []),
|
|
5675
5841
|
]),
|
|
5676
5842
|
m(MaterialIcon, {
|
|
5677
5843
|
name: 'caret',
|
|
@@ -5702,25 +5868,22 @@ const Switch = () => {
|
|
|
5702
5868
|
},
|
|
5703
5869
|
view: ({ attrs }) => {
|
|
5704
5870
|
const id = attrs.id || state.id;
|
|
5705
|
-
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"]);
|
|
5706
5872
|
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5707
5873
|
return m('div', {
|
|
5708
5874
|
className: cn,
|
|
5709
5875
|
onclick: (e) => {
|
|
5710
|
-
|
|
5711
|
-
onchange && onchange(state.checked);
|
|
5876
|
+
onchange && onchange(!checked);
|
|
5712
5877
|
e.preventDefault();
|
|
5713
5878
|
},
|
|
5714
5879
|
}, [
|
|
5715
5880
|
label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
|
|
5716
|
-
m('.switch', params, m('label',
|
|
5717
|
-
style: { cursor: 'pointer' },
|
|
5718
|
-
}, [
|
|
5881
|
+
m('.switch', params, m('label', [
|
|
5719
5882
|
m('span', left || 'Off'),
|
|
5720
5883
|
m('input[type=checkbox]', {
|
|
5721
5884
|
id,
|
|
5722
5885
|
disabled,
|
|
5723
|
-
checked
|
|
5886
|
+
checked,
|
|
5724
5887
|
}),
|
|
5725
5888
|
m('span.lever'),
|
|
5726
5889
|
m('span', right || 'On'),
|
|
@@ -5912,22 +6075,62 @@ const Tabs = () => {
|
|
|
5912
6075
|
};
|
|
5913
6076
|
};
|
|
5914
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
|
+
};
|
|
5915
6119
|
/**
|
|
5916
6120
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
5917
6121
|
*/
|
|
5918
6122
|
const SearchSelect = () => {
|
|
5919
|
-
// (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
|
|
5920
6123
|
// State initialization
|
|
5921
6124
|
const state = {
|
|
6125
|
+
id: '',
|
|
5922
6126
|
isOpen: false,
|
|
5923
|
-
selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
|
|
5924
6127
|
searchTerm: '',
|
|
5925
|
-
options: [],
|
|
5926
6128
|
inputRef: null,
|
|
5927
6129
|
dropdownRef: null,
|
|
5928
6130
|
focusedIndex: -1,
|
|
5929
|
-
|
|
6131
|
+
internalSelectedIds: [],
|
|
5930
6132
|
};
|
|
6133
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5931
6134
|
const componentId = uniqueId();
|
|
5932
6135
|
const searchInputId = `${componentId}-search`;
|
|
5933
6136
|
// Handle click outside
|
|
@@ -5968,9 +6171,7 @@ const SearchSelect = () => {
|
|
|
5968
6171
|
// Handle add new option
|
|
5969
6172
|
return 'addNew';
|
|
5970
6173
|
}
|
|
5971
|
-
else if (state.focusedIndex < filteredOptions.length)
|
|
5972
|
-
toggleOption(filteredOptions[state.focusedIndex]);
|
|
5973
|
-
}
|
|
6174
|
+
else if (state.focusedIndex < filteredOptions.length) ;
|
|
5974
6175
|
}
|
|
5975
6176
|
break;
|
|
5976
6177
|
case 'Escape':
|
|
@@ -5982,26 +6183,65 @@ const SearchSelect = () => {
|
|
|
5982
6183
|
return null;
|
|
5983
6184
|
};
|
|
5984
6185
|
// Toggle option selection
|
|
5985
|
-
const toggleOption = (option) => {
|
|
6186
|
+
const toggleOption = (option, attrs) => {
|
|
5986
6187
|
if (option.disabled)
|
|
5987
6188
|
return;
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
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
|
+
}
|
|
5991
6205
|
state.searchTerm = '';
|
|
5992
6206
|
state.focusedIndex = -1;
|
|
5993
|
-
|
|
6207
|
+
// Call onchange if provided
|
|
6208
|
+
if (attrs.onchange) {
|
|
6209
|
+
attrs.onchange(newIds);
|
|
6210
|
+
}
|
|
5994
6211
|
};
|
|
5995
6212
|
// Remove a selected option
|
|
5996
|
-
const removeOption = (
|
|
5997
|
-
|
|
5998
|
-
|
|
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
|
+
}
|
|
5999
6232
|
};
|
|
6000
6233
|
return {
|
|
6001
|
-
oninit: ({ attrs
|
|
6002
|
-
state.
|
|
6003
|
-
state
|
|
6004
|
-
|
|
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
|
+
}
|
|
6005
6245
|
},
|
|
6006
6246
|
oncreate() {
|
|
6007
6247
|
document.addEventListener('click', handleClickOutside);
|
|
@@ -6009,14 +6249,27 @@ const SearchSelect = () => {
|
|
|
6009
6249
|
onremove() {
|
|
6010
6250
|
document.removeEventListener('click', handleClickOutside);
|
|
6011
6251
|
},
|
|
6012
|
-
view({ attrs
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
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));
|
|
6017
6270
|
// Safely filter options
|
|
6018
|
-
const filteredOptions =
|
|
6019
|
-
!
|
|
6271
|
+
const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
6272
|
+
!selectedIds.includes(option.id));
|
|
6020
6273
|
// Check if we should show the "add new option" element
|
|
6021
6274
|
const showAddNew = oncreateNewOption &&
|
|
6022
6275
|
state.searchTerm &&
|
|
@@ -6034,6 +6287,7 @@ const SearchSelect = () => {
|
|
|
6034
6287
|
state.isOpen = !state.isOpen;
|
|
6035
6288
|
// console.log('SearchSelect state changed to', state.isOpen); // Debug log
|
|
6036
6289
|
},
|
|
6290
|
+
class: 'chips chips-container',
|
|
6037
6291
|
style: {
|
|
6038
6292
|
display: 'flex',
|
|
6039
6293
|
alignItems: 'end',
|
|
@@ -6046,25 +6300,20 @@ const SearchSelect = () => {
|
|
|
6046
6300
|
// Hidden input for label association and accessibility
|
|
6047
6301
|
m('input', {
|
|
6048
6302
|
type: 'text',
|
|
6049
|
-
id:
|
|
6050
|
-
value:
|
|
6303
|
+
id: state.id,
|
|
6304
|
+
value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
|
|
6051
6305
|
readonly: true,
|
|
6306
|
+
class: 'sr-only',
|
|
6052
6307
|
style: { position: 'absolute', left: '-9999px', opacity: 0 },
|
|
6053
6308
|
}),
|
|
6054
6309
|
// Selected Options (chips)
|
|
6055
|
-
...
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
onclick: (e) => {
|
|
6061
|
-
e.stopPropagation();
|
|
6062
|
-
removeOption(option);
|
|
6063
|
-
},
|
|
6064
|
-
}),
|
|
6065
|
-
])),
|
|
6310
|
+
...selectedOptions.map((option) => m(SelectedChip, {
|
|
6311
|
+
// key: option.id,
|
|
6312
|
+
option,
|
|
6313
|
+
onRemove: (id) => removeOption(id, attrs),
|
|
6314
|
+
})),
|
|
6066
6315
|
// Placeholder when no options selected
|
|
6067
|
-
|
|
6316
|
+
selectedOptions.length === 0 &&
|
|
6068
6317
|
placeholder &&
|
|
6069
6318
|
m('span.placeholder', {
|
|
6070
6319
|
style: {
|
|
@@ -6085,8 +6334,8 @@ const SearchSelect = () => {
|
|
|
6085
6334
|
// Label
|
|
6086
6335
|
label &&
|
|
6087
6336
|
m('label', {
|
|
6088
|
-
for:
|
|
6089
|
-
class: placeholder ||
|
|
6337
|
+
for: state.id,
|
|
6338
|
+
class: placeholder || selectedOptions.length > 0 ? 'active' : '',
|
|
6090
6339
|
}, label),
|
|
6091
6340
|
// Dropdown Menu
|
|
6092
6341
|
state.isOpen &&
|
|
@@ -6102,7 +6351,6 @@ const SearchSelect = () => {
|
|
|
6102
6351
|
m('li', // Search Input
|
|
6103
6352
|
{
|
|
6104
6353
|
class: 'search-wrapper',
|
|
6105
|
-
style: { padding: '0 16px', position: 'relative' },
|
|
6106
6354
|
}, [
|
|
6107
6355
|
m('input', {
|
|
6108
6356
|
type: 'text',
|
|
@@ -6121,29 +6369,21 @@ const SearchSelect = () => {
|
|
|
6121
6369
|
const result = handleKeyDown(e, filteredOptions, !!showAddNew);
|
|
6122
6370
|
if (result === 'addNew' && oncreateNewOption) {
|
|
6123
6371
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6124
|
-
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);
|
|
6125
6378
|
}
|
|
6126
6379
|
},
|
|
6127
|
-
|
|
6128
|
-
width: '100%',
|
|
6129
|
-
outline: 'none',
|
|
6130
|
-
fontSize: '0.875rem',
|
|
6131
|
-
border: 'none',
|
|
6132
|
-
padding: '8px 0',
|
|
6133
|
-
borderBottom: '1px solid var(--mm-input-border, #9e9e9e)',
|
|
6134
|
-
backgroundColor: 'transparent',
|
|
6135
|
-
color: 'var(--mm-text-primary, inherit)',
|
|
6136
|
-
},
|
|
6380
|
+
class: 'search-select-input',
|
|
6137
6381
|
}),
|
|
6138
6382
|
]),
|
|
6139
6383
|
// No options found message or list of options
|
|
6140
6384
|
...(filteredOptions.length === 0 && !showAddNew
|
|
6141
6385
|
? [
|
|
6142
|
-
m('li',
|
|
6143
|
-
// {
|
|
6144
|
-
// style: getNoOptionsStyles(),
|
|
6145
|
-
// },
|
|
6146
|
-
noOptionsFound),
|
|
6386
|
+
m('li.search-select-no-options', texts.noOptionsFound),
|
|
6147
6387
|
]
|
|
6148
6388
|
: []),
|
|
6149
6389
|
// Add new option item
|
|
@@ -6152,35 +6392,27 @@ const SearchSelect = () => {
|
|
|
6152
6392
|
m('li', {
|
|
6153
6393
|
onclick: async () => {
|
|
6154
6394
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6155
|
-
toggleOption(option);
|
|
6395
|
+
toggleOption(option, attrs);
|
|
6156
6396
|
},
|
|
6157
6397
|
class: state.focusedIndex === filteredOptions.length ? 'active' : '',
|
|
6158
6398
|
onmouseover: () => {
|
|
6159
6399
|
state.focusedIndex = filteredOptions.length;
|
|
6160
6400
|
},
|
|
6161
|
-
}, [m('span',
|
|
6401
|
+
}, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
|
|
6162
6402
|
]
|
|
6163
6403
|
: []),
|
|
6164
6404
|
// List of filtered options
|
|
6165
|
-
...filteredOptions.map((option, index) => m(
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
state.focusedIndex = index;
|
|
6175
|
-
}
|
|
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;
|
|
6176
6414
|
},
|
|
6177
|
-
},
|
|
6178
|
-
m('input', {
|
|
6179
|
-
type: 'checkbox',
|
|
6180
|
-
checked: state.selectedOptions.some((selected) => selected.id === option.id),
|
|
6181
|
-
}),
|
|
6182
|
-
option.label || option.id.toString(),
|
|
6183
|
-
]))),
|
|
6415
|
+
})),
|
|
6184
6416
|
]),
|
|
6185
6417
|
]);
|
|
6186
6418
|
},
|
|
@@ -8263,4 +8495,4 @@ const isValidationError = (result) => !isValidationSuccess(result);
|
|
|
8263
8495
|
// ============================================================================
|
|
8264
8496
|
// All types are already exported via individual export declarations above
|
|
8265
8497
|
|
|
8266
|
-
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 };
|