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