mithril-materialized 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components.css +3 -0
- package/dist/core.css +136 -14
- package/dist/dropdown.d.ts +8 -5
- package/dist/forms.css +136 -0
- package/dist/index.css +144 -14
- package/dist/index.esm.js +604 -399
- package/dist/index.js +604 -398
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +604 -398
- package/dist/input-options.d.ts +10 -2
- package/dist/input.d.ts +1 -1
- package/dist/option.d.ts +16 -3
- package/dist/radio.d.ts +11 -5
- package/dist/search-select.d.ts +14 -20
- package/dist/select.d.ts +10 -6
- package/dist/switch.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +5 -4
- package/sass/components/_dropdown.scss +3 -0
- package/sass/components/forms/_checkboxes.scss +10 -6
- package/sass/components/forms/_forms.scss +0 -15
- package/sass/components/forms/_select.scss +164 -0
- package/sass/components/forms/_switches.scss +1 -0
package/dist/index.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
|
-
|
|
3237
|
-
|
|
3340
|
+
onchange && onchange(allIds);
|
|
3341
|
+
};
|
|
3342
|
+
const selectNone = (onchange) => {
|
|
3343
|
+
onchange && onchange([]);
|
|
3238
3344
|
};
|
|
3239
|
-
const
|
|
3240
|
-
|
|
3241
|
-
if (
|
|
3242
|
-
|
|
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,65 +3988,96 @@
|
|
|
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
|
-
const handleKeyDown = (e, items
|
|
4005
|
+
const handleKeyDown = (e, items) => {
|
|
3902
4006
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
3903
4007
|
switch (e.key) {
|
|
3904
4008
|
case 'ArrowDown':
|
|
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
|
-
state.focusedIndex =
|
|
4015
|
+
state.focusedIndex = (state.focusedIndex + 1) % availableItems.length;
|
|
3912
4016
|
}
|
|
3913
|
-
|
|
4017
|
+
return undefined;
|
|
3914
4018
|
case 'ArrowUp':
|
|
3915
4019
|
e.preventDefault();
|
|
3916
4020
|
if (state.isOpen) {
|
|
3917
|
-
state.focusedIndex =
|
|
4021
|
+
state.focusedIndex = state.focusedIndex <= 0 ? availableItems.length - 1 : state.focusedIndex - 1;
|
|
3918
4022
|
}
|
|
3919
|
-
|
|
4023
|
+
return undefined;
|
|
3920
4024
|
case 'Enter':
|
|
3921
4025
|
case ' ':
|
|
3922
4026
|
e.preventDefault();
|
|
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;
|
|
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
|
+
return undefined;
|
|
3937
4039
|
case 'Escape':
|
|
3938
4040
|
e.preventDefault();
|
|
3939
4041
|
state.isOpen = false;
|
|
3940
4042
|
state.focusedIndex = -1;
|
|
3941
|
-
|
|
4043
|
+
return undefined;
|
|
4044
|
+
default:
|
|
4045
|
+
return undefined;
|
|
3942
4046
|
}
|
|
3943
4047
|
};
|
|
3944
4048
|
return {
|
|
3945
|
-
oninit: ({ attrs
|
|
3946
|
-
|
|
3947
|
-
state.
|
|
3948
|
-
//
|
|
4049
|
+
oninit: ({ attrs }) => {
|
|
4050
|
+
var _a;
|
|
4051
|
+
state.id = ((_a = attrs.id) === null || _a === void 0 ? void 0 : _a.toString()) || uniqueId();
|
|
4052
|
+
// Initialize internal state for uncontrolled mode
|
|
4053
|
+
if (!isControlled(attrs)) {
|
|
4054
|
+
state.internalCheckedId = attrs.defaultCheckedId;
|
|
4055
|
+
}
|
|
4056
|
+
// Add global click listener to close dropdown
|
|
4057
|
+
document.addEventListener('click', closeDropdown);
|
|
3949
4058
|
},
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
4059
|
+
onremove: () => {
|
|
4060
|
+
// Cleanup global listener
|
|
4061
|
+
document.removeEventListener('click', closeDropdown);
|
|
4062
|
+
},
|
|
4063
|
+
view: ({ attrs }) => {
|
|
4064
|
+
const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
|
|
4065
|
+
const controlled = isControlled(attrs);
|
|
4066
|
+
const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
|
|
4067
|
+
const handleSelection = (value) => {
|
|
4068
|
+
// Update internal state for uncontrolled mode
|
|
4069
|
+
if (!controlled) {
|
|
4070
|
+
state.internalCheckedId = value;
|
|
4071
|
+
}
|
|
4072
|
+
// Call onchange if provided
|
|
4073
|
+
if (onchange) {
|
|
4074
|
+
onchange(value);
|
|
4075
|
+
}
|
|
4076
|
+
};
|
|
4077
|
+
const selectedItem = currentCheckedId
|
|
4078
|
+
? items
|
|
4079
|
+
.filter((i) => (i.id ? i.id === currentCheckedId : i.label === currentCheckedId))
|
|
4080
|
+
.shift()
|
|
3954
4081
|
: undefined;
|
|
3955
4082
|
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
3956
4083
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3958,13 +4085,14 @@
|
|
|
3958
4085
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3959
4086
|
m(HelperText, { helperText }),
|
|
3960
4087
|
m('.select-wrapper', {
|
|
3961
|
-
|
|
4088
|
+
onkeydown: disabled
|
|
3962
4089
|
? undefined
|
|
3963
|
-
: () => {
|
|
3964
|
-
|
|
3965
|
-
|
|
4090
|
+
: (e) => {
|
|
4091
|
+
const selectedValue = handleKeyDown(e, items);
|
|
4092
|
+
if (selectedValue) {
|
|
4093
|
+
handleSelection(selectedValue);
|
|
4094
|
+
}
|
|
3966
4095
|
},
|
|
3967
|
-
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
|
|
3968
4096
|
tabindex: disabled ? -1 : 0,
|
|
3969
4097
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
3970
4098
|
'aria-haspopup': 'listbox',
|
|
@@ -3981,7 +4109,8 @@
|
|
|
3981
4109
|
e.stopPropagation();
|
|
3982
4110
|
if (!disabled) {
|
|
3983
4111
|
state.isOpen = !state.isOpen;
|
|
3984
|
-
|
|
4112
|
+
// Reset focus index when opening/closing
|
|
4113
|
+
state.focusedIndex = -1;
|
|
3985
4114
|
}
|
|
3986
4115
|
},
|
|
3987
4116
|
}),
|
|
@@ -3997,36 +4126,31 @@
|
|
|
3997
4126
|
onremove: () => {
|
|
3998
4127
|
state.dropdownRef = null;
|
|
3999
4128
|
},
|
|
4000
|
-
style: getDropdownStyles(state.inputRef, true, items
|
|
4001
|
-
|
|
4002
|
-
group: undefined }))), true),
|
|
4003
|
-
}, items.map((item, index) => {
|
|
4129
|
+
style: getDropdownStyles(state.inputRef, true, items, true),
|
|
4130
|
+
}, items.map((item) => {
|
|
4004
4131
|
if (item.divider) {
|
|
4005
|
-
return m('li.divider'
|
|
4006
|
-
key: `divider-${index}`,
|
|
4007
|
-
});
|
|
4132
|
+
return m('li.divider');
|
|
4008
4133
|
}
|
|
4009
4134
|
const itemIndex = availableItems.indexOf(item);
|
|
4010
4135
|
const isFocused = itemIndex === state.focusedIndex;
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4136
|
+
const className = [
|
|
4137
|
+
item.disabled ? 'disabled' : '',
|
|
4138
|
+
isFocused ? 'focused' : '',
|
|
4139
|
+
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
4140
|
+
]
|
|
4141
|
+
.filter(Boolean)
|
|
4142
|
+
.join(' ') || undefined;
|
|
4143
|
+
return m('li', {
|
|
4144
|
+
className,
|
|
4145
|
+
onclick: item.disabled
|
|
4146
|
+
? undefined
|
|
4147
|
+
: () => {
|
|
4022
4148
|
const value = (item.id || item.label);
|
|
4023
|
-
state.initialValue = value;
|
|
4024
4149
|
state.isOpen = false;
|
|
4025
4150
|
state.focusedIndex = -1;
|
|
4026
|
-
|
|
4027
|
-
onchange(value);
|
|
4151
|
+
handleSelection(value);
|
|
4028
4152
|
},
|
|
4029
|
-
|
|
4153
|
+
}, m('span', {
|
|
4030
4154
|
style: {
|
|
4031
4155
|
display: 'flex',
|
|
4032
4156
|
alignItems: 'center',
|
|
@@ -5163,9 +5287,9 @@
|
|
|
5163
5287
|
dx: 0,
|
|
5164
5288
|
dy: 0,
|
|
5165
5289
|
};
|
|
5166
|
-
// Handle
|
|
5167
|
-
if (attrs.
|
|
5168
|
-
updateTimeFromInput(attrs.
|
|
5290
|
+
// Handle value after options are set
|
|
5291
|
+
if (attrs.defaultValue) {
|
|
5292
|
+
updateTimeFromInput(attrs.defaultValue);
|
|
5169
5293
|
}
|
|
5170
5294
|
},
|
|
5171
5295
|
onremove: () => {
|
|
@@ -5447,32 +5571,47 @@
|
|
|
5447
5571
|
},
|
|
5448
5572
|
});
|
|
5449
5573
|
/** Component to show a list of radio buttons, from which you can choose one. */
|
|
5450
|
-
// export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
|
|
5451
5574
|
const RadioButtons = () => {
|
|
5452
|
-
const state = {
|
|
5575
|
+
const state = {
|
|
5576
|
+
groupId: uniqueId(),
|
|
5577
|
+
componentId: '',
|
|
5578
|
+
internalCheckedId: undefined,
|
|
5579
|
+
};
|
|
5580
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5453
5581
|
return {
|
|
5454
|
-
oninit: ({ attrs
|
|
5455
|
-
state.
|
|
5456
|
-
state
|
|
5457
|
-
|
|
5582
|
+
oninit: ({ attrs }) => {
|
|
5583
|
+
state.componentId = attrs.id || uniqueId();
|
|
5584
|
+
// Initialize internal state for uncontrolled mode
|
|
5585
|
+
if (!isControlled(attrs)) {
|
|
5586
|
+
state.internalCheckedId = attrs.defaultCheckedId;
|
|
5587
|
+
}
|
|
5458
5588
|
},
|
|
5459
|
-
view: ({ attrs
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
const
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5589
|
+
view: ({ attrs }) => {
|
|
5590
|
+
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange, } = attrs;
|
|
5591
|
+
const { groupId, componentId } = state;
|
|
5592
|
+
const controlled = isControlled(attrs);
|
|
5593
|
+
// Get current checked ID from props or internal state
|
|
5594
|
+
const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
|
|
5595
|
+
const handleChange = (id) => {
|
|
5596
|
+
// Update internal state for uncontrolled mode
|
|
5597
|
+
if (!controlled) {
|
|
5598
|
+
state.internalCheckedId = id;
|
|
5599
|
+
}
|
|
5600
|
+
// Call onchange if provided
|
|
5601
|
+
if (onchange) {
|
|
5602
|
+
onchange(id);
|
|
5468
5603
|
}
|
|
5469
5604
|
};
|
|
5470
5605
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5471
|
-
const
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
:
|
|
5475
|
-
|
|
5606
|
+
const radioItems = options.map((r) => ({
|
|
5607
|
+
component: (RadioButton),
|
|
5608
|
+
props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}` }),
|
|
5609
|
+
key: r.id,
|
|
5610
|
+
}));
|
|
5611
|
+
const optionsContent = m(OptionsList, {
|
|
5612
|
+
options: radioItems,
|
|
5613
|
+
layout,
|
|
5614
|
+
});
|
|
5476
5615
|
return m('div', { id: componentId, className: cn }, [
|
|
5477
5616
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
5478
5617
|
description && m('p.helper-text', m.trust(description)),
|
|
@@ -5487,30 +5626,42 @@
|
|
|
5487
5626
|
const state = {
|
|
5488
5627
|
id: '',
|
|
5489
5628
|
isOpen: false,
|
|
5490
|
-
selectedIds: [],
|
|
5491
5629
|
focusedIndex: -1,
|
|
5492
5630
|
inputRef: null,
|
|
5493
5631
|
dropdownRef: null,
|
|
5632
|
+
internalSelectedIds: [],
|
|
5494
5633
|
};
|
|
5634
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5495
5635
|
const isSelected = (id, selectedIds) => {
|
|
5496
5636
|
return selectedIds.some((selectedId) => selectedId === id);
|
|
5497
5637
|
};
|
|
5498
5638
|
const toggleOption = (id, multiple, attrs) => {
|
|
5639
|
+
const controlled = isControlled(attrs);
|
|
5640
|
+
// Get current selected IDs from props or internal state
|
|
5641
|
+
const currentSelectedIds = controlled
|
|
5642
|
+
? attrs.checkedId !== undefined
|
|
5643
|
+
? Array.isArray(attrs.checkedId)
|
|
5644
|
+
? attrs.checkedId
|
|
5645
|
+
: [attrs.checkedId]
|
|
5646
|
+
: []
|
|
5647
|
+
: state.internalSelectedIds;
|
|
5648
|
+
let newIds;
|
|
5499
5649
|
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
|
|
5650
|
+
newIds = currentSelectedIds.includes(id)
|
|
5651
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
5652
|
+
: [...currentSelectedIds, id];
|
|
5508
5653
|
}
|
|
5509
5654
|
else {
|
|
5510
|
-
|
|
5511
|
-
// Close dropdown for single select
|
|
5512
|
-
|
|
5513
|
-
|
|
5655
|
+
newIds = [id];
|
|
5656
|
+
state.isOpen = false; // Close dropdown for single select
|
|
5657
|
+
}
|
|
5658
|
+
// Update internal state for uncontrolled mode
|
|
5659
|
+
if (!controlled) {
|
|
5660
|
+
state.internalSelectedIds = newIds;
|
|
5661
|
+
}
|
|
5662
|
+
// Call onchange if provided
|
|
5663
|
+
if (attrs.onchange) {
|
|
5664
|
+
attrs.onchange(newIds);
|
|
5514
5665
|
}
|
|
5515
5666
|
};
|
|
5516
5667
|
const handleKeyDown = (e, attrs) => {
|
|
@@ -5562,88 +5713,22 @@
|
|
|
5562
5713
|
};
|
|
5563
5714
|
const closeDropdown = (e) => {
|
|
5564
5715
|
const target = e.target;
|
|
5565
|
-
if (!target.closest('.select-
|
|
5716
|
+
if (!target.closest('.input-field.select-space')) {
|
|
5566
5717
|
state.isOpen = false;
|
|
5567
5718
|
m.redraw();
|
|
5568
5719
|
}
|
|
5569
5720
|
};
|
|
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
5721
|
return {
|
|
5636
5722
|
oninit: ({ attrs }) => {
|
|
5637
|
-
|
|
5638
|
-
state
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
}
|
|
5723
|
+
state.id = attrs.id || uniqueId();
|
|
5724
|
+
// Initialize internal state for uncontrolled mode
|
|
5725
|
+
if (!isControlled(attrs)) {
|
|
5726
|
+
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
5727
|
+
? Array.isArray(attrs.defaultCheckedId)
|
|
5728
|
+
? attrs.defaultCheckedId
|
|
5729
|
+
: [attrs.defaultCheckedId]
|
|
5730
|
+
: [];
|
|
5731
|
+
state.internalSelectedIds = defaultIds;
|
|
5647
5732
|
}
|
|
5648
5733
|
// Add global click listener to close dropdown
|
|
5649
5734
|
document.addEventListener('click', closeDropdown);
|
|
@@ -5653,17 +5738,18 @@
|
|
|
5653
5738
|
document.removeEventListener('click', closeDropdown);
|
|
5654
5739
|
},
|
|
5655
5740
|
view: ({ attrs }) => {
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5741
|
+
const controlled = isControlled(attrs);
|
|
5742
|
+
// Get selected IDs from props or internal state
|
|
5743
|
+
const selectedIds = controlled
|
|
5744
|
+
? attrs.checkedId !== undefined
|
|
5745
|
+
? Array.isArray(attrs.checkedId)
|
|
5746
|
+
? attrs.checkedId
|
|
5747
|
+
: [attrs.checkedId]
|
|
5748
|
+
: []
|
|
5749
|
+
: state.internalSelectedIds;
|
|
5664
5750
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
|
|
5665
5751
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5666
|
-
const selectedOptions = options.filter((opt) => isSelected(opt.id,
|
|
5752
|
+
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
5667
5753
|
return m('.input-field.select-space', {
|
|
5668
5754
|
className: finalClassName,
|
|
5669
5755
|
key,
|
|
@@ -5672,11 +5758,6 @@
|
|
|
5672
5758
|
// Icon prefix
|
|
5673
5759
|
iconName && m('i.material-icons.prefix', iconName),
|
|
5674
5760
|
m('.select-wrapper', {
|
|
5675
|
-
onclick: disabled
|
|
5676
|
-
? undefined
|
|
5677
|
-
: () => {
|
|
5678
|
-
state.isOpen = !state.isOpen;
|
|
5679
|
-
},
|
|
5680
5761
|
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
|
|
5681
5762
|
tabindex: disabled ? -1 : 0,
|
|
5682
5763
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
@@ -5692,7 +5773,9 @@
|
|
|
5692
5773
|
onclick: (e) => {
|
|
5693
5774
|
e.preventDefault();
|
|
5694
5775
|
e.stopPropagation();
|
|
5695
|
-
|
|
5776
|
+
if (!disabled) {
|
|
5777
|
+
state.isOpen = !state.isOpen;
|
|
5778
|
+
}
|
|
5696
5779
|
},
|
|
5697
5780
|
}),
|
|
5698
5781
|
// Dropdown Menu
|
|
@@ -5708,7 +5791,63 @@
|
|
|
5708
5791
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5709
5792
|
}, [
|
|
5710
5793
|
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5711
|
-
|
|
5794
|
+
// Render ungrouped options first
|
|
5795
|
+
options
|
|
5796
|
+
.filter((option) => !option.group)
|
|
5797
|
+
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5798
|
+
? 'disabled'
|
|
5799
|
+
: state.focusedIndex === options.indexOf(option)
|
|
5800
|
+
? 'focused'
|
|
5801
|
+
: '' }, (option.disabled
|
|
5802
|
+
? {}
|
|
5803
|
+
: {
|
|
5804
|
+
onclick: (e) => {
|
|
5805
|
+
e.stopPropagation();
|
|
5806
|
+
toggleOption(option.id, multiple, attrs);
|
|
5807
|
+
},
|
|
5808
|
+
})), m('span', multiple
|
|
5809
|
+
? m('label', { for: option.id }, m('input', {
|
|
5810
|
+
id: option.id,
|
|
5811
|
+
type: 'checkbox',
|
|
5812
|
+
checked: selectedIds.includes(option.id),
|
|
5813
|
+
disabled: option.disabled ? true : undefined,
|
|
5814
|
+
onclick: (e) => {
|
|
5815
|
+
e.stopPropagation();
|
|
5816
|
+
},
|
|
5817
|
+
}), m('span', option.label))
|
|
5818
|
+
: option.label))),
|
|
5819
|
+
// Render grouped options
|
|
5820
|
+
Object.entries(options
|
|
5821
|
+
.filter((option) => option.group)
|
|
5822
|
+
.reduce((groups, option) => {
|
|
5823
|
+
const group = option.group;
|
|
5824
|
+
if (!groups[group])
|
|
5825
|
+
groups[group] = [];
|
|
5826
|
+
groups[group].push(option);
|
|
5827
|
+
return groups;
|
|
5828
|
+
}, {}))
|
|
5829
|
+
.map(([groupName, groupOptions]) => [
|
|
5830
|
+
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5831
|
+
...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
|
|
5832
|
+
? {}
|
|
5833
|
+
: {
|
|
5834
|
+
onclick: (e) => {
|
|
5835
|
+
e.stopPropagation();
|
|
5836
|
+
toggleOption(option.id, multiple, attrs);
|
|
5837
|
+
},
|
|
5838
|
+
})), m('span', multiple
|
|
5839
|
+
? m('label', { for: option.id }, m('input', {
|
|
5840
|
+
id: option.id,
|
|
5841
|
+
type: 'checkbox',
|
|
5842
|
+
checked: selectedIds.includes(option.id),
|
|
5843
|
+
disabled: option.disabled ? true : undefined,
|
|
5844
|
+
onclick: (e) => {
|
|
5845
|
+
e.stopPropagation();
|
|
5846
|
+
},
|
|
5847
|
+
}), m('span', option.label))
|
|
5848
|
+
: option.label))),
|
|
5849
|
+
])
|
|
5850
|
+
.reduce((acc, val) => acc.concat(val), []),
|
|
5712
5851
|
]),
|
|
5713
5852
|
m(MaterialIcon, {
|
|
5714
5853
|
name: 'caret',
|
|
@@ -5739,25 +5878,22 @@
|
|
|
5739
5878
|
},
|
|
5740
5879
|
view: ({ attrs }) => {
|
|
5741
5880
|
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"]);
|
|
5881
|
+
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
5882
|
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5744
5883
|
return m('div', {
|
|
5745
5884
|
className: cn,
|
|
5746
5885
|
onclick: (e) => {
|
|
5747
|
-
|
|
5748
|
-
onchange && onchange(state.checked);
|
|
5886
|
+
onchange && onchange(!checked);
|
|
5749
5887
|
e.preventDefault();
|
|
5750
5888
|
},
|
|
5751
5889
|
}, [
|
|
5752
5890
|
label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
|
|
5753
|
-
m('.switch', params, m('label',
|
|
5754
|
-
style: { cursor: 'pointer' },
|
|
5755
|
-
}, [
|
|
5891
|
+
m('.switch', params, m('label', [
|
|
5756
5892
|
m('span', left || 'Off'),
|
|
5757
5893
|
m('input[type=checkbox]', {
|
|
5758
5894
|
id,
|
|
5759
5895
|
disabled,
|
|
5760
|
-
checked
|
|
5896
|
+
checked,
|
|
5761
5897
|
}),
|
|
5762
5898
|
m('span.lever'),
|
|
5763
5899
|
m('span', right || 'On'),
|
|
@@ -5949,22 +6085,62 @@
|
|
|
5949
6085
|
};
|
|
5950
6086
|
};
|
|
5951
6087
|
|
|
6088
|
+
// Proper components to avoid anonymous closures
|
|
6089
|
+
const SelectedChip = {
|
|
6090
|
+
view: ({ attrs: { option, onRemove } }) => m('.chip', [
|
|
6091
|
+
option.label || option.id.toString(),
|
|
6092
|
+
m(MaterialIcon, {
|
|
6093
|
+
name: 'close',
|
|
6094
|
+
className: 'close',
|
|
6095
|
+
onclick: (e) => {
|
|
6096
|
+
e.stopPropagation();
|
|
6097
|
+
onRemove(option.id);
|
|
6098
|
+
},
|
|
6099
|
+
}),
|
|
6100
|
+
]),
|
|
6101
|
+
};
|
|
6102
|
+
const DropdownOption = {
|
|
6103
|
+
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
|
|
6104
|
+
const checkboxId = `search-select-option-${option.id}`;
|
|
6105
|
+
const optionLabel = option.label || option.id.toString();
|
|
6106
|
+
return m('li', {
|
|
6107
|
+
key: option.id,
|
|
6108
|
+
onclick: (e) => {
|
|
6109
|
+
e.preventDefault();
|
|
6110
|
+
e.stopPropagation();
|
|
6111
|
+
onToggle(option);
|
|
6112
|
+
},
|
|
6113
|
+
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
6114
|
+
onmouseover: () => {
|
|
6115
|
+
if (!option.disabled) {
|
|
6116
|
+
onMouseOver(index);
|
|
6117
|
+
}
|
|
6118
|
+
},
|
|
6119
|
+
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
6120
|
+
m('input', {
|
|
6121
|
+
type: 'checkbox',
|
|
6122
|
+
id: checkboxId,
|
|
6123
|
+
checked: selectedIds.includes(option.id),
|
|
6124
|
+
}),
|
|
6125
|
+
m('span', optionLabel),
|
|
6126
|
+
]));
|
|
6127
|
+
},
|
|
6128
|
+
};
|
|
5952
6129
|
/**
|
|
5953
6130
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
5954
6131
|
*/
|
|
5955
6132
|
const SearchSelect = () => {
|
|
5956
|
-
// (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
|
|
5957
6133
|
// State initialization
|
|
5958
6134
|
const state = {
|
|
6135
|
+
id: '',
|
|
5959
6136
|
isOpen: false,
|
|
5960
|
-
selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
|
|
5961
6137
|
searchTerm: '',
|
|
5962
|
-
options: [],
|
|
5963
6138
|
inputRef: null,
|
|
5964
6139
|
dropdownRef: null,
|
|
5965
6140
|
focusedIndex: -1,
|
|
5966
|
-
|
|
6141
|
+
internalSelectedIds: [],
|
|
5967
6142
|
};
|
|
6143
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5968
6144
|
const componentId = uniqueId();
|
|
5969
6145
|
const searchInputId = `${componentId}-search`;
|
|
5970
6146
|
// Handle click outside
|
|
@@ -6005,9 +6181,7 @@
|
|
|
6005
6181
|
// Handle add new option
|
|
6006
6182
|
return 'addNew';
|
|
6007
6183
|
}
|
|
6008
|
-
else if (state.focusedIndex < filteredOptions.length)
|
|
6009
|
-
toggleOption(filteredOptions[state.focusedIndex]);
|
|
6010
|
-
}
|
|
6184
|
+
else if (state.focusedIndex < filteredOptions.length) ;
|
|
6011
6185
|
}
|
|
6012
6186
|
break;
|
|
6013
6187
|
case 'Escape':
|
|
@@ -6019,26 +6193,65 @@
|
|
|
6019
6193
|
return null;
|
|
6020
6194
|
};
|
|
6021
6195
|
// Toggle option selection
|
|
6022
|
-
const toggleOption = (option) => {
|
|
6196
|
+
const toggleOption = (option, attrs) => {
|
|
6023
6197
|
if (option.disabled)
|
|
6024
6198
|
return;
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6199
|
+
const controlled = isControlled(attrs);
|
|
6200
|
+
// Get current selected IDs from props or internal state
|
|
6201
|
+
const currentSelectedIds = controlled
|
|
6202
|
+
? attrs.checkedId !== undefined
|
|
6203
|
+
? Array.isArray(attrs.checkedId)
|
|
6204
|
+
? attrs.checkedId
|
|
6205
|
+
: [attrs.checkedId]
|
|
6206
|
+
: []
|
|
6207
|
+
: state.internalSelectedIds;
|
|
6208
|
+
const newIds = currentSelectedIds.includes(option.id)
|
|
6209
|
+
? currentSelectedIds.filter((id) => id !== option.id)
|
|
6210
|
+
: [...currentSelectedIds, option.id];
|
|
6211
|
+
// Update internal state for uncontrolled mode
|
|
6212
|
+
if (!controlled) {
|
|
6213
|
+
state.internalSelectedIds = newIds;
|
|
6214
|
+
}
|
|
6028
6215
|
state.searchTerm = '';
|
|
6029
6216
|
state.focusedIndex = -1;
|
|
6030
|
-
|
|
6217
|
+
// Call onchange if provided
|
|
6218
|
+
if (attrs.onchange) {
|
|
6219
|
+
attrs.onchange(newIds);
|
|
6220
|
+
}
|
|
6031
6221
|
};
|
|
6032
6222
|
// Remove a selected option
|
|
6033
|
-
const removeOption = (
|
|
6034
|
-
|
|
6035
|
-
|
|
6223
|
+
const removeOption = (optionId, attrs) => {
|
|
6224
|
+
const controlled = isControlled(attrs);
|
|
6225
|
+
// Get current selected IDs from props or internal state
|
|
6226
|
+
const currentSelectedIds = controlled
|
|
6227
|
+
? attrs.checkedId !== undefined
|
|
6228
|
+
? Array.isArray(attrs.checkedId)
|
|
6229
|
+
? attrs.checkedId
|
|
6230
|
+
: [attrs.checkedId]
|
|
6231
|
+
: []
|
|
6232
|
+
: state.internalSelectedIds;
|
|
6233
|
+
const newIds = currentSelectedIds.filter((id) => id !== optionId);
|
|
6234
|
+
// Update internal state for uncontrolled mode
|
|
6235
|
+
if (!controlled) {
|
|
6236
|
+
state.internalSelectedIds = newIds;
|
|
6237
|
+
}
|
|
6238
|
+
// Call onchange if provided
|
|
6239
|
+
if (attrs.onchange) {
|
|
6240
|
+
attrs.onchange(newIds);
|
|
6241
|
+
}
|
|
6036
6242
|
};
|
|
6037
6243
|
return {
|
|
6038
|
-
oninit: ({ attrs
|
|
6039
|
-
state.
|
|
6040
|
-
state
|
|
6041
|
-
|
|
6244
|
+
oninit: ({ attrs }) => {
|
|
6245
|
+
state.id = attrs.id || uniqueId();
|
|
6246
|
+
// Initialize internal state for uncontrolled mode
|
|
6247
|
+
if (!isControlled(attrs)) {
|
|
6248
|
+
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
6249
|
+
? Array.isArray(attrs.defaultCheckedId)
|
|
6250
|
+
? attrs.defaultCheckedId
|
|
6251
|
+
: [attrs.defaultCheckedId]
|
|
6252
|
+
: [];
|
|
6253
|
+
state.internalSelectedIds = defaultIds;
|
|
6254
|
+
}
|
|
6042
6255
|
},
|
|
6043
6256
|
oncreate() {
|
|
6044
6257
|
document.addEventListener('click', handleClickOutside);
|
|
@@ -6046,14 +6259,27 @@
|
|
|
6046
6259
|
onremove() {
|
|
6047
6260
|
document.removeEventListener('click', handleClickOutside);
|
|
6048
6261
|
},
|
|
6049
|
-
view({ attrs
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6262
|
+
view({ attrs }) {
|
|
6263
|
+
const controlled = isControlled(attrs);
|
|
6264
|
+
// Get selected IDs from props or internal state
|
|
6265
|
+
const selectedIds = controlled
|
|
6266
|
+
? attrs.checkedId !== undefined
|
|
6267
|
+
? Array.isArray(attrs.checkedId)
|
|
6268
|
+
? attrs.checkedId
|
|
6269
|
+
: [attrs.checkedId]
|
|
6270
|
+
: []
|
|
6271
|
+
: state.internalSelectedIds;
|
|
6272
|
+
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
|
|
6273
|
+
// Use i18n values if provided, otherwise use defaults
|
|
6274
|
+
const texts = {
|
|
6275
|
+
noOptionsFound: i18n.noOptionsFound || noOptionsFound,
|
|
6276
|
+
addNewPrefix: i18n.addNewPrefix || '+',
|
|
6277
|
+
};
|
|
6278
|
+
// Get selected options for display
|
|
6279
|
+
const selectedOptions = options.filter((opt) => selectedIds.includes(opt.id));
|
|
6054
6280
|
// Safely filter options
|
|
6055
|
-
const filteredOptions =
|
|
6056
|
-
!
|
|
6281
|
+
const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
6282
|
+
!selectedIds.includes(option.id));
|
|
6057
6283
|
// Check if we should show the "add new option" element
|
|
6058
6284
|
const showAddNew = oncreateNewOption &&
|
|
6059
6285
|
state.searchTerm &&
|
|
@@ -6071,6 +6297,7 @@
|
|
|
6071
6297
|
state.isOpen = !state.isOpen;
|
|
6072
6298
|
// console.log('SearchSelect state changed to', state.isOpen); // Debug log
|
|
6073
6299
|
},
|
|
6300
|
+
class: 'chips chips-container',
|
|
6074
6301
|
style: {
|
|
6075
6302
|
display: 'flex',
|
|
6076
6303
|
alignItems: 'end',
|
|
@@ -6083,25 +6310,20 @@
|
|
|
6083
6310
|
// Hidden input for label association and accessibility
|
|
6084
6311
|
m('input', {
|
|
6085
6312
|
type: 'text',
|
|
6086
|
-
id:
|
|
6087
|
-
value:
|
|
6313
|
+
id: state.id,
|
|
6314
|
+
value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
|
|
6088
6315
|
readonly: true,
|
|
6316
|
+
class: 'sr-only',
|
|
6089
6317
|
style: { position: 'absolute', left: '-9999px', opacity: 0 },
|
|
6090
6318
|
}),
|
|
6091
6319
|
// Selected Options (chips)
|
|
6092
|
-
...
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
onclick: (e) => {
|
|
6098
|
-
e.stopPropagation();
|
|
6099
|
-
removeOption(option);
|
|
6100
|
-
},
|
|
6101
|
-
}),
|
|
6102
|
-
])),
|
|
6320
|
+
...selectedOptions.map((option) => m(SelectedChip, {
|
|
6321
|
+
// key: option.id,
|
|
6322
|
+
option,
|
|
6323
|
+
onRemove: (id) => removeOption(id, attrs),
|
|
6324
|
+
})),
|
|
6103
6325
|
// Placeholder when no options selected
|
|
6104
|
-
|
|
6326
|
+
selectedOptions.length === 0 &&
|
|
6105
6327
|
placeholder &&
|
|
6106
6328
|
m('span.placeholder', {
|
|
6107
6329
|
style: {
|
|
@@ -6122,8 +6344,8 @@
|
|
|
6122
6344
|
// Label
|
|
6123
6345
|
label &&
|
|
6124
6346
|
m('label', {
|
|
6125
|
-
for:
|
|
6126
|
-
class: placeholder ||
|
|
6347
|
+
for: state.id,
|
|
6348
|
+
class: placeholder || selectedOptions.length > 0 ? 'active' : '',
|
|
6127
6349
|
}, label),
|
|
6128
6350
|
// Dropdown Menu
|
|
6129
6351
|
state.isOpen &&
|
|
@@ -6139,7 +6361,6 @@
|
|
|
6139
6361
|
m('li', // Search Input
|
|
6140
6362
|
{
|
|
6141
6363
|
class: 'search-wrapper',
|
|
6142
|
-
style: { padding: '0 16px', position: 'relative' },
|
|
6143
6364
|
}, [
|
|
6144
6365
|
m('input', {
|
|
6145
6366
|
type: 'text',
|
|
@@ -6158,29 +6379,21 @@
|
|
|
6158
6379
|
const result = handleKeyDown(e, filteredOptions, !!showAddNew);
|
|
6159
6380
|
if (result === 'addNew' && oncreateNewOption) {
|
|
6160
6381
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6161
|
-
toggleOption(option);
|
|
6382
|
+
toggleOption(option, attrs);
|
|
6383
|
+
}
|
|
6384
|
+
else if (e.key === 'Enter' &&
|
|
6385
|
+
state.focusedIndex >= 0 &&
|
|
6386
|
+
state.focusedIndex < filteredOptions.length) {
|
|
6387
|
+
toggleOption(filteredOptions[state.focusedIndex], attrs);
|
|
6162
6388
|
}
|
|
6163
6389
|
},
|
|
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
|
-
},
|
|
6390
|
+
class: 'search-select-input',
|
|
6174
6391
|
}),
|
|
6175
6392
|
]),
|
|
6176
6393
|
// No options found message or list of options
|
|
6177
6394
|
...(filteredOptions.length === 0 && !showAddNew
|
|
6178
6395
|
? [
|
|
6179
|
-
m('li',
|
|
6180
|
-
// {
|
|
6181
|
-
// style: getNoOptionsStyles(),
|
|
6182
|
-
// },
|
|
6183
|
-
noOptionsFound),
|
|
6396
|
+
m('li.search-select-no-options', texts.noOptionsFound),
|
|
6184
6397
|
]
|
|
6185
6398
|
: []),
|
|
6186
6399
|
// Add new option item
|
|
@@ -6189,35 +6402,27 @@
|
|
|
6189
6402
|
m('li', {
|
|
6190
6403
|
onclick: async () => {
|
|
6191
6404
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6192
|
-
toggleOption(option);
|
|
6405
|
+
toggleOption(option, attrs);
|
|
6193
6406
|
},
|
|
6194
6407
|
class: state.focusedIndex === filteredOptions.length ? 'active' : '',
|
|
6195
6408
|
onmouseover: () => {
|
|
6196
6409
|
state.focusedIndex = filteredOptions.length;
|
|
6197
6410
|
},
|
|
6198
|
-
}, [m('span',
|
|
6411
|
+
}, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
|
|
6199
6412
|
]
|
|
6200
6413
|
: []),
|
|
6201
6414
|
// List of filtered options
|
|
6202
|
-
...filteredOptions.map((option, index) => m(
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
state.focusedIndex = index;
|
|
6212
|
-
}
|
|
6415
|
+
...filteredOptions.map((option, index) => m(DropdownOption, {
|
|
6416
|
+
// key: option.id,
|
|
6417
|
+
option,
|
|
6418
|
+
index,
|
|
6419
|
+
selectedIds,
|
|
6420
|
+
isFocused: state.focusedIndex === index,
|
|
6421
|
+
onToggle: (opt) => toggleOption(opt, attrs),
|
|
6422
|
+
onMouseOver: (idx) => {
|
|
6423
|
+
state.focusedIndex = idx;
|
|
6213
6424
|
},
|
|
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
|
-
]))),
|
|
6425
|
+
})),
|
|
6221
6426
|
]),
|
|
6222
6427
|
]);
|
|
6223
6428
|
},
|
|
@@ -8337,6 +8542,7 @@
|
|
|
8337
8542
|
exports.ModalPanel = ModalPanel;
|
|
8338
8543
|
exports.NumberInput = NumberInput;
|
|
8339
8544
|
exports.Options = Options;
|
|
8545
|
+
exports.OptionsList = OptionsList;
|
|
8340
8546
|
exports.Pagination = Pagination;
|
|
8341
8547
|
exports.PaginationControls = PaginationControls;
|
|
8342
8548
|
exports.Parallax = Parallax;
|