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