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