mithril-materialized 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -20
- package/dist/components.css +3 -0
- package/dist/core.css +154 -14
- package/dist/dropdown.d.ts +8 -5
- package/dist/forms.css +154 -0
- package/dist/index.css +162 -14
- package/dist/index.esm.js +623 -391
- package/dist/index.js +623 -390
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +623 -390
- package/dist/input-options.d.ts +12 -2
- package/dist/input.d.ts +1 -1
- package/dist/option.d.ts +16 -3
- package/dist/radio.d.ts +11 -5
- package/dist/search-select.d.ts +14 -20
- package/dist/select.d.ts +10 -6
- package/dist/switch.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +5 -4
- package/sass/components/_dropdown.scss +3 -0
- package/sass/components/forms/_checkboxes.scss +10 -6
- package/sass/components/forms/_forms.scss +0 -15
- package/sass/components/forms/_input-fields.scss +20 -0
- package/sass/components/forms/_select.scss +164 -0
- package/sass/components/forms/_switches.scss +1 -0
package/dist/index.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,20 +2951,57 @@
|
|
|
2917
2951
|
}
|
|
2918
2952
|
};
|
|
2919
2953
|
const focus = ({ autofocus }) => autofocus ? (typeof autofocus === 'boolean' ? autofocus : autofocus()) : false;
|
|
2920
|
-
const lengthUpdateHandler = () => {
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2954
|
+
// const lengthUpdateHandler = () => {
|
|
2955
|
+
// const length = state.inputElement?.value.length;
|
|
2956
|
+
// if (length) {
|
|
2957
|
+
// state.currentLength = length;
|
|
2958
|
+
// state.hasInteracted = length > 0;
|
|
2959
|
+
// }
|
|
2960
|
+
// };
|
|
2961
|
+
const clearInput = (oninput, onchange) => {
|
|
2962
|
+
if (state.inputElement) {
|
|
2963
|
+
state.inputElement.value = '';
|
|
2964
|
+
state.inputElement.focus();
|
|
2965
|
+
state.active = false;
|
|
2966
|
+
// state.currentLength = 0;
|
|
2967
|
+
// state.hasInteracted = false;
|
|
2968
|
+
// Trigger oninput and onchange callbacks
|
|
2969
|
+
const value = getValue(state.inputElement);
|
|
2970
|
+
if (oninput) {
|
|
2971
|
+
oninput(value);
|
|
2972
|
+
}
|
|
2973
|
+
if (onchange) {
|
|
2974
|
+
onchange(value);
|
|
2975
|
+
}
|
|
2976
|
+
// Update validation state
|
|
2977
|
+
state.inputElement.classList.remove('valid', 'invalid');
|
|
2978
|
+
state.isValid = true;
|
|
2979
|
+
m.redraw();
|
|
2926
2980
|
}
|
|
2927
2981
|
};
|
|
2928
2982
|
// Range slider helper functions
|
|
2929
2983
|
// Range slider rendering functions are now in separate module
|
|
2930
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
|
+
},
|
|
2931
3002
|
view: ({ attrs }) => {
|
|
2932
|
-
var _a;
|
|
2933
|
-
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id,
|
|
3003
|
+
var _a, _b;
|
|
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"]);
|
|
2934
3005
|
// const attributes = toAttrs(params);
|
|
2935
3006
|
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
2936
3007
|
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
@@ -2947,10 +3018,14 @@
|
|
|
2947
3018
|
isMandatory,
|
|
2948
3019
|
helperText }));
|
|
2949
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;
|
|
2950
3025
|
return m('.input-field', { className: cn, style }, [
|
|
2951
3026
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2952
3027
|
m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
|
|
2953
|
-
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
|
|
2954
3029
|
? {
|
|
2955
3030
|
height: attrs.height || '200px',
|
|
2956
3031
|
width: '6px',
|
|
@@ -2964,25 +3039,9 @@
|
|
|
2964
3039
|
if (focus(attrs)) {
|
|
2965
3040
|
input.focus();
|
|
2966
3041
|
}
|
|
2967
|
-
//
|
|
2968
|
-
if (
|
|
2969
|
-
input.value = String(
|
|
2970
|
-
}
|
|
2971
|
-
// Update character count state for counter component
|
|
2972
|
-
if (maxLength) {
|
|
2973
|
-
state.currentLength = input.value.length; // Initial count
|
|
2974
|
-
}
|
|
2975
|
-
// Range input functionality
|
|
2976
|
-
if (type === 'range' && !attrs.minmax) {
|
|
2977
|
-
const updateThumb = () => {
|
|
2978
|
-
const value = input.value;
|
|
2979
|
-
const min = input.min || '0';
|
|
2980
|
-
const max = input.max || '100';
|
|
2981
|
-
const percentage = ((parseFloat(value) - parseFloat(min)) / (parseFloat(max) - parseFloat(min))) * 100;
|
|
2982
|
-
input.style.setProperty('--range-progress', `${percentage}%`);
|
|
2983
|
-
};
|
|
2984
|
-
input.addEventListener('input', updateThumb);
|
|
2985
|
-
updateThumb(); // Initial position
|
|
3042
|
+
// For uncontrolled mode, set initial value only
|
|
3043
|
+
if (!controlled && attrs.defaultValue !== undefined) {
|
|
3044
|
+
input.value = String(attrs.defaultValue);
|
|
2986
3045
|
}
|
|
2987
3046
|
}, onkeyup: onkeyup
|
|
2988
3047
|
? (ev) => {
|
|
@@ -2996,21 +3055,25 @@
|
|
|
2996
3055
|
? (ev) => {
|
|
2997
3056
|
onkeypress(ev, getValue(ev.target));
|
|
2998
3057
|
}
|
|
2999
|
-
: undefined, onupdate: validate
|
|
3000
|
-
? ({ dom }) => {
|
|
3001
|
-
const target = dom;
|
|
3002
|
-
setValidity(target, validate(getValue(target), target));
|
|
3003
|
-
}
|
|
3004
3058
|
: undefined, oninput: (e) => {
|
|
3005
3059
|
state.active = true;
|
|
3060
|
+
state.hasInteracted = false;
|
|
3006
3061
|
const target = e.target;
|
|
3007
3062
|
// Handle original oninput logic
|
|
3008
|
-
const
|
|
3063
|
+
const inputValue = getValue(target);
|
|
3064
|
+
// Update internal state for uncontrolled mode
|
|
3065
|
+
if (!controlled) {
|
|
3066
|
+
state.internalValue = inputValue;
|
|
3067
|
+
}
|
|
3009
3068
|
if (oninput) {
|
|
3010
|
-
oninput(
|
|
3069
|
+
oninput(inputValue);
|
|
3011
3070
|
}
|
|
3012
|
-
if (
|
|
3013
|
-
|
|
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}%`);
|
|
3014
3077
|
}
|
|
3015
3078
|
// Don't validate on input, only clear error states if user is typing
|
|
3016
3079
|
if (validate && target.classList.contains('invalid') && target.value.length > 0) {
|
|
@@ -3026,6 +3089,14 @@
|
|
|
3026
3089
|
state.isValid = true;
|
|
3027
3090
|
}
|
|
3028
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
|
+
}
|
|
3029
3100
|
}, onfocus: () => {
|
|
3030
3101
|
state.active = true;
|
|
3031
3102
|
}, onblur: (e) => {
|
|
@@ -3062,6 +3133,48 @@
|
|
|
3062
3133
|
state.isValid = true;
|
|
3063
3134
|
}
|
|
3064
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
|
+
}
|
|
3065
3178
|
// Also call the original onblur handler if provided
|
|
3066
3179
|
if (attrs.onblur) {
|
|
3067
3180
|
attrs.onblur(e);
|
|
@@ -3070,23 +3183,35 @@
|
|
|
3070
3183
|
onchange(getValue(state.inputElement));
|
|
3071
3184
|
}
|
|
3072
3185
|
} })),
|
|
3186
|
+
// Clear button - only for text inputs with canClear enabled and has content
|
|
3187
|
+
canClear && type === 'text' && ((_b = state.inputElement) === null || _b === void 0 ? void 0 : _b.value)
|
|
3188
|
+
? m(MaterialIcon, {
|
|
3189
|
+
name: 'close',
|
|
3190
|
+
className: 'input-clear-btn',
|
|
3191
|
+
onclick: (e) => {
|
|
3192
|
+
e.preventDefault();
|
|
3193
|
+
e.stopPropagation();
|
|
3194
|
+
clearInput(oninput, onchange);
|
|
3195
|
+
},
|
|
3196
|
+
})
|
|
3197
|
+
: undefined,
|
|
3073
3198
|
m(Label, {
|
|
3074
3199
|
label,
|
|
3075
3200
|
id,
|
|
3076
3201
|
isMandatory,
|
|
3077
3202
|
isActive,
|
|
3078
|
-
initialValue:
|
|
3203
|
+
initialValue: value !== undefined && value !== '',
|
|
3079
3204
|
}),
|
|
3080
3205
|
m(HelperText, {
|
|
3081
3206
|
helperText,
|
|
3082
3207
|
dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
|
|
3083
3208
|
dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
|
|
3084
3209
|
}),
|
|
3085
|
-
maxLength
|
|
3210
|
+
maxLength && typeof value === 'string'
|
|
3086
3211
|
? m(CharacterCounter, {
|
|
3087
|
-
currentLength:
|
|
3212
|
+
currentLength: value.length,
|
|
3088
3213
|
maxLength,
|
|
3089
|
-
show:
|
|
3214
|
+
show: value.length > 0,
|
|
3090
3215
|
})
|
|
3091
3216
|
: undefined,
|
|
3092
3217
|
]);
|
|
@@ -3113,7 +3238,7 @@
|
|
|
3113
3238
|
let i;
|
|
3114
3239
|
return {
|
|
3115
3240
|
view: ({ attrs }) => {
|
|
3116
|
-
const { multiple, disabled,
|
|
3241
|
+
const { multiple, disabled, value, placeholder, onchange, className = 'col s12', accept: acceptedFiles, label = 'File', } = attrs;
|
|
3117
3242
|
const accept = acceptedFiles
|
|
3118
3243
|
? acceptedFiles instanceof Array
|
|
3119
3244
|
? acceptedFiles.join(', ')
|
|
@@ -3144,8 +3269,8 @@
|
|
|
3144
3269
|
placeholder,
|
|
3145
3270
|
oncreate: ({ dom }) => {
|
|
3146
3271
|
i = dom;
|
|
3147
|
-
if (
|
|
3148
|
-
i.value =
|
|
3272
|
+
if (value)
|
|
3273
|
+
i.value = value;
|
|
3149
3274
|
},
|
|
3150
3275
|
})),
|
|
3151
3276
|
(canClear || (i === null || i === void 0 ? void 0 : i.value)) &&
|
|
@@ -3172,11 +3297,14 @@
|
|
|
3172
3297
|
|
|
3173
3298
|
/** Component to show a check box */
|
|
3174
3299
|
const InputCheckbox = () => {
|
|
3300
|
+
let checkboxId;
|
|
3175
3301
|
return {
|
|
3176
3302
|
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3177
|
-
|
|
3303
|
+
if (!checkboxId)
|
|
3304
|
+
checkboxId = inputId || uniqueId();
|
|
3178
3305
|
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3179
3306
|
m('input[type=checkbox][tabindex=0]', {
|
|
3307
|
+
className: disabled ? 'disabled' : undefined,
|
|
3180
3308
|
id: checkboxId,
|
|
3181
3309
|
checked,
|
|
3182
3310
|
disabled,
|
|
@@ -3193,78 +3321,79 @@
|
|
|
3193
3321
|
},
|
|
3194
3322
|
};
|
|
3195
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
|
+
};
|
|
3196
3333
|
/** A list of checkboxes */
|
|
3197
3334
|
const Options = () => {
|
|
3198
|
-
const state = {
|
|
3199
|
-
|
|
3200
|
-
|
|
3335
|
+
const state = {
|
|
3336
|
+
componentId: '',
|
|
3337
|
+
};
|
|
3338
|
+
const selectAll = (options, onchange) => {
|
|
3201
3339
|
const allIds = options.map((option) => option.id);
|
|
3202
|
-
|
|
3203
|
-
if (callback)
|
|
3204
|
-
callback(allIds);
|
|
3340
|
+
onchange && onchange(allIds);
|
|
3205
3341
|
};
|
|
3206
|
-
const selectNone = (
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
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);
|
|
3210
3351
|
};
|
|
3211
3352
|
return {
|
|
3212
|
-
oninit: ({ attrs
|
|
3213
|
-
|
|
3214
|
-
state.checkedId = checkedId;
|
|
3215
|
-
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3216
|
-
state.componentId = id || uniqueId();
|
|
3353
|
+
oninit: ({ attrs }) => {
|
|
3354
|
+
state.componentId = attrs.id || uniqueId();
|
|
3217
3355
|
},
|
|
3218
|
-
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false,
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
if (checked) {
|
|
3223
|
-
checkedIds.push(propId);
|
|
3224
|
-
}
|
|
3225
|
-
state.checkedIds = checkedIds;
|
|
3226
|
-
callback(checkedIds);
|
|
3227
|
-
}
|
|
3228
|
-
: 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);
|
|
3229
3360
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
3230
|
-
const
|
|
3231
|
-
|
|
3361
|
+
const optionItems = options.map((option) => ({
|
|
3362
|
+
component: InputCheckbox,
|
|
3363
|
+
props: {
|
|
3232
3364
|
disabled: disabled || option.disabled,
|
|
3233
3365
|
label: option.label,
|
|
3234
|
-
onchange: onchange ? (v) =>
|
|
3366
|
+
onchange: onchange ? (v) => handleChange(option.id, v, checkedIds, onchange) : undefined,
|
|
3235
3367
|
className: option.className || checkboxClass,
|
|
3236
3368
|
checked: isChecked(option.id),
|
|
3237
3369
|
description: option.description,
|
|
3238
3370
|
inputId: `${state.componentId}-${option.id}`,
|
|
3239
|
-
}
|
|
3240
|
-
:
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
description: option.description,
|
|
3247
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3248
|
-
}));
|
|
3371
|
+
},
|
|
3372
|
+
key: option.id,
|
|
3373
|
+
}));
|
|
3374
|
+
const optionsContent = m(OptionsList, {
|
|
3375
|
+
options: optionItems,
|
|
3376
|
+
layout,
|
|
3377
|
+
});
|
|
3249
3378
|
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3250
3379
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3251
3380
|
showSelectAll &&
|
|
3252
|
-
m('div.select-all-controls', { style:
|
|
3381
|
+
m('div.select-all-controls', { style: { marginBottom: '10px' } }, [
|
|
3253
3382
|
m('a', {
|
|
3254
3383
|
href: '#',
|
|
3255
3384
|
onclick: (e) => {
|
|
3256
3385
|
e.preventDefault();
|
|
3257
|
-
selectAll(options,
|
|
3386
|
+
selectAll(options, onchange);
|
|
3258
3387
|
},
|
|
3259
|
-
style:
|
|
3260
|
-
},
|
|
3388
|
+
style: { marginRight: '15px' },
|
|
3389
|
+
}, selectAllText),
|
|
3261
3390
|
m('a', {
|
|
3262
3391
|
href: '#',
|
|
3263
3392
|
onclick: (e) => {
|
|
3264
3393
|
e.preventDefault();
|
|
3265
|
-
selectNone(
|
|
3394
|
+
selectNone(onchange);
|
|
3266
3395
|
},
|
|
3267
|
-
},
|
|
3396
|
+
}, selectNoneText),
|
|
3268
3397
|
]),
|
|
3269
3398
|
description && m(HelperText, { helperText: description }),
|
|
3270
3399
|
m('form', { action: '#' }, optionsContent),
|
|
@@ -3859,11 +3988,19 @@
|
|
|
3859
3988
|
const Dropdown = () => {
|
|
3860
3989
|
const state = {
|
|
3861
3990
|
isOpen: false,
|
|
3862
|
-
initialValue: undefined,
|
|
3863
3991
|
id: '',
|
|
3864
3992
|
focusedIndex: -1,
|
|
3865
3993
|
inputRef: null,
|
|
3866
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
|
+
}
|
|
3867
4004
|
};
|
|
3868
4005
|
const handleKeyDown = (e, items, onchange) => {
|
|
3869
4006
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3872,7 +4009,7 @@
|
|
|
3872
4009
|
e.preventDefault();
|
|
3873
4010
|
if (!state.isOpen) {
|
|
3874
4011
|
state.isOpen = true;
|
|
3875
|
-
state.focusedIndex = 0;
|
|
4012
|
+
state.focusedIndex = availableItems.length > 0 ? 0 : -1;
|
|
3876
4013
|
}
|
|
3877
4014
|
else {
|
|
3878
4015
|
state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
|
|
@@ -3890,15 +4027,13 @@
|
|
|
3890
4027
|
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
3891
4028
|
const selectedItem = availableItems[state.focusedIndex];
|
|
3892
4029
|
const value = (selectedItem.id || selectedItem.label);
|
|
3893
|
-
state.initialValue = value;
|
|
3894
4030
|
state.isOpen = false;
|
|
3895
4031
|
state.focusedIndex = -1;
|
|
3896
|
-
|
|
3897
|
-
onchange(value);
|
|
4032
|
+
return value; // Return value to be handled in view
|
|
3898
4033
|
}
|
|
3899
4034
|
else if (!state.isOpen) {
|
|
3900
4035
|
state.isOpen = true;
|
|
3901
|
-
state.focusedIndex = 0;
|
|
4036
|
+
state.focusedIndex = availableItems.length > 0 ? 0 : -1;
|
|
3902
4037
|
}
|
|
3903
4038
|
break;
|
|
3904
4039
|
case 'Escape':
|
|
@@ -3909,15 +4044,36 @@
|
|
|
3909
4044
|
}
|
|
3910
4045
|
};
|
|
3911
4046
|
return {
|
|
3912
|
-
oninit: ({ attrs
|
|
3913
|
-
|
|
3914
|
-
state.
|
|
3915
|
-
//
|
|
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);
|
|
4056
|
+
},
|
|
4057
|
+
onremove: () => {
|
|
4058
|
+
// Cleanup global listener
|
|
4059
|
+
document.removeEventListener('click', closeDropdown);
|
|
3916
4060
|
},
|
|
3917
|
-
view: ({ attrs
|
|
3918
|
-
const {
|
|
3919
|
-
const
|
|
3920
|
-
|
|
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()
|
|
3921
4077
|
: undefined;
|
|
3922
4078
|
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
3923
4079
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3925,13 +4081,12 @@
|
|
|
3925
4081
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3926
4082
|
m(HelperText, { helperText }),
|
|
3927
4083
|
m('.select-wrapper', {
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
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
|
+
},
|
|
3935
4090
|
tabindex: disabled ? -1 : 0,
|
|
3936
4091
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
3937
4092
|
'aria-haspopup': 'listbox',
|
|
@@ -3948,7 +4103,8 @@
|
|
|
3948
4103
|
e.stopPropagation();
|
|
3949
4104
|
if (!disabled) {
|
|
3950
4105
|
state.isOpen = !state.isOpen;
|
|
3951
|
-
|
|
4106
|
+
// Reset focus index when opening/closing
|
|
4107
|
+
state.focusedIndex = -1;
|
|
3952
4108
|
}
|
|
3953
4109
|
},
|
|
3954
4110
|
}),
|
|
@@ -3964,36 +4120,31 @@
|
|
|
3964
4120
|
onremove: () => {
|
|
3965
4121
|
state.dropdownRef = null;
|
|
3966
4122
|
},
|
|
3967
|
-
style: getDropdownStyles(state.inputRef, true, items
|
|
3968
|
-
|
|
3969
|
-
group: undefined }))), true),
|
|
3970
|
-
}, items.map((item, index) => {
|
|
4123
|
+
style: getDropdownStyles(state.inputRef, true, items, true),
|
|
4124
|
+
}, items.map((item) => {
|
|
3971
4125
|
if (item.divider) {
|
|
3972
|
-
return m('li.divider'
|
|
3973
|
-
key: `divider-${index}`,
|
|
3974
|
-
});
|
|
4126
|
+
return m('li.divider');
|
|
3975
4127
|
}
|
|
3976
4128
|
const itemIndex = availableItems.indexOf(item);
|
|
3977
4129
|
const isFocused = itemIndex === state.focusedIndex;
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
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
|
+
: () => {
|
|
3989
4142
|
const value = (item.id || item.label);
|
|
3990
|
-
state.initialValue = value;
|
|
3991
4143
|
state.isOpen = false;
|
|
3992
4144
|
state.focusedIndex = -1;
|
|
3993
|
-
|
|
3994
|
-
onchange(value);
|
|
4145
|
+
handleSelection(value);
|
|
3995
4146
|
},
|
|
3996
|
-
|
|
4147
|
+
}, m('span', {
|
|
3997
4148
|
style: {
|
|
3998
4149
|
display: 'flex',
|
|
3999
4150
|
alignItems: 'center',
|
|
@@ -5130,9 +5281,9 @@
|
|
|
5130
5281
|
dx: 0,
|
|
5131
5282
|
dy: 0,
|
|
5132
5283
|
};
|
|
5133
|
-
// Handle
|
|
5134
|
-
if (attrs.
|
|
5135
|
-
updateTimeFromInput(attrs.
|
|
5284
|
+
// Handle value after options are set
|
|
5285
|
+
if (attrs.defaultValue) {
|
|
5286
|
+
updateTimeFromInput(attrs.defaultValue);
|
|
5136
5287
|
}
|
|
5137
5288
|
},
|
|
5138
5289
|
onremove: () => {
|
|
@@ -5414,32 +5565,47 @@
|
|
|
5414
5565
|
},
|
|
5415
5566
|
});
|
|
5416
5567
|
/** Component to show a list of radio buttons, from which you can choose one. */
|
|
5417
|
-
// export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
|
|
5418
5568
|
const RadioButtons = () => {
|
|
5419
|
-
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';
|
|
5420
5575
|
return {
|
|
5421
|
-
oninit: ({ attrs
|
|
5422
|
-
state.
|
|
5423
|
-
state
|
|
5424
|
-
|
|
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
|
+
}
|
|
5425
5582
|
},
|
|
5426
|
-
view: ({ attrs
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
const
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
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);
|
|
5435
5597
|
}
|
|
5436
5598
|
};
|
|
5437
5599
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5438
|
-
const
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
:
|
|
5442
|
-
|
|
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
|
+
});
|
|
5443
5609
|
return m('div', { id: componentId, className: cn }, [
|
|
5444
5610
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
5445
5611
|
description && m('p.helper-text', m.trust(description)),
|
|
@@ -5454,30 +5620,42 @@
|
|
|
5454
5620
|
const state = {
|
|
5455
5621
|
id: '',
|
|
5456
5622
|
isOpen: false,
|
|
5457
|
-
selectedIds: [],
|
|
5458
5623
|
focusedIndex: -1,
|
|
5459
5624
|
inputRef: null,
|
|
5460
5625
|
dropdownRef: null,
|
|
5626
|
+
internalSelectedIds: [],
|
|
5461
5627
|
};
|
|
5628
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5462
5629
|
const isSelected = (id, selectedIds) => {
|
|
5463
5630
|
return selectedIds.some((selectedId) => selectedId === id);
|
|
5464
5631
|
};
|
|
5465
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;
|
|
5466
5643
|
if (multiple) {
|
|
5467
|
-
|
|
5468
|
-
?
|
|
5469
|
-
|
|
5470
|
-
: [...state.selectedIds, id];
|
|
5471
|
-
state.selectedIds = newIds;
|
|
5472
|
-
attrs.onchange(newIds);
|
|
5473
|
-
console.log(newIds);
|
|
5474
|
-
// Keep dropdown open for multiple select
|
|
5644
|
+
newIds = currentSelectedIds.includes(id)
|
|
5645
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
5646
|
+
: [...currentSelectedIds, id];
|
|
5475
5647
|
}
|
|
5476
5648
|
else {
|
|
5477
|
-
|
|
5478
|
-
// Close dropdown for single select
|
|
5479
|
-
|
|
5480
|
-
|
|
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);
|
|
5481
5659
|
}
|
|
5482
5660
|
};
|
|
5483
5661
|
const handleKeyDown = (e, attrs) => {
|
|
@@ -5529,88 +5707,22 @@
|
|
|
5529
5707
|
};
|
|
5530
5708
|
const closeDropdown = (e) => {
|
|
5531
5709
|
const target = e.target;
|
|
5532
|
-
if (!target.closest('.select-
|
|
5710
|
+
if (!target.closest('.input-field.select-space')) {
|
|
5533
5711
|
state.isOpen = false;
|
|
5534
5712
|
m.redraw();
|
|
5535
5713
|
}
|
|
5536
5714
|
};
|
|
5537
|
-
const renderGroupedOptions = (options, multiple, attrs) => {
|
|
5538
|
-
const groupedOptions = {};
|
|
5539
|
-
const ungroupedOptions = [];
|
|
5540
|
-
// Group options by their group property
|
|
5541
|
-
options.forEach((option) => {
|
|
5542
|
-
if (option.group) {
|
|
5543
|
-
if (!groupedOptions[option.group]) {
|
|
5544
|
-
groupedOptions[option.group] = [];
|
|
5545
|
-
}
|
|
5546
|
-
groupedOptions[option.group].push(option);
|
|
5547
|
-
}
|
|
5548
|
-
else {
|
|
5549
|
-
ungroupedOptions.push(option);
|
|
5550
|
-
}
|
|
5551
|
-
});
|
|
5552
|
-
const renderElements = [];
|
|
5553
|
-
// Render ungrouped options first
|
|
5554
|
-
ungroupedOptions.forEach((option) => {
|
|
5555
|
-
renderElements.push(m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === options.indexOf(option) ? 'focused' : '' }, (option.disabled
|
|
5556
|
-
? {}
|
|
5557
|
-
: {
|
|
5558
|
-
onclick: (e) => {
|
|
5559
|
-
e.stopPropagation();
|
|
5560
|
-
toggleOption(option.id, multiple, attrs);
|
|
5561
|
-
},
|
|
5562
|
-
})), m('span', multiple
|
|
5563
|
-
? m('label', { for: option.id }, m('input', {
|
|
5564
|
-
id: option.id,
|
|
5565
|
-
type: 'checkbox',
|
|
5566
|
-
checked: state.selectedIds.includes(option.id),
|
|
5567
|
-
disabled: option.disabled ? true : undefined,
|
|
5568
|
-
onclick: (e) => {
|
|
5569
|
-
e.stopPropagation();
|
|
5570
|
-
},
|
|
5571
|
-
}), m('span', option.label))
|
|
5572
|
-
: option.label)));
|
|
5573
|
-
});
|
|
5574
|
-
// Render grouped options
|
|
5575
|
-
Object.keys(groupedOptions).forEach((groupName) => {
|
|
5576
|
-
// Add group header
|
|
5577
|
-
renderElements.push(m('li.optgroup', { tabindex: 0 }, m('span', groupName)));
|
|
5578
|
-
// Add group options
|
|
5579
|
-
groupedOptions[groupName].forEach((option) => {
|
|
5580
|
-
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
|
|
5581
|
-
? {}
|
|
5582
|
-
: {
|
|
5583
|
-
onclick: (e) => {
|
|
5584
|
-
e.stopPropagation();
|
|
5585
|
-
toggleOption(option.id, multiple, attrs);
|
|
5586
|
-
},
|
|
5587
|
-
})), m('span', multiple
|
|
5588
|
-
? m('label', { for: option.id }, m('input', {
|
|
5589
|
-
id: option.id,
|
|
5590
|
-
type: 'checkbox',
|
|
5591
|
-
checked: state.selectedIds.includes(option.id),
|
|
5592
|
-
disabled: option.disabled ? true : undefined,
|
|
5593
|
-
onclick: (e) => {
|
|
5594
|
-
e.stopPropagation();
|
|
5595
|
-
},
|
|
5596
|
-
}), m('span', option.label))
|
|
5597
|
-
: option.label)));
|
|
5598
|
-
});
|
|
5599
|
-
});
|
|
5600
|
-
return renderElements;
|
|
5601
|
-
};
|
|
5602
5715
|
return {
|
|
5603
5716
|
oninit: ({ attrs }) => {
|
|
5604
|
-
|
|
5605
|
-
state
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
}
|
|
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;
|
|
5614
5726
|
}
|
|
5615
5727
|
// Add global click listener to close dropdown
|
|
5616
5728
|
document.addEventListener('click', closeDropdown);
|
|
@@ -5620,17 +5732,18 @@
|
|
|
5620
5732
|
document.removeEventListener('click', closeDropdown);
|
|
5621
5733
|
},
|
|
5622
5734
|
view: ({ attrs }) => {
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
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;
|
|
5631
5744
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
|
|
5632
5745
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5633
|
-
const selectedOptions = options.filter((opt) => isSelected(opt.id,
|
|
5746
|
+
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
5634
5747
|
return m('.input-field.select-space', {
|
|
5635
5748
|
className: finalClassName,
|
|
5636
5749
|
key,
|
|
@@ -5639,11 +5752,6 @@
|
|
|
5639
5752
|
// Icon prefix
|
|
5640
5753
|
iconName && m('i.material-icons.prefix', iconName),
|
|
5641
5754
|
m('.select-wrapper', {
|
|
5642
|
-
onclick: disabled
|
|
5643
|
-
? undefined
|
|
5644
|
-
: () => {
|
|
5645
|
-
state.isOpen = !state.isOpen;
|
|
5646
|
-
},
|
|
5647
5755
|
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
|
|
5648
5756
|
tabindex: disabled ? -1 : 0,
|
|
5649
5757
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
@@ -5659,7 +5767,9 @@
|
|
|
5659
5767
|
onclick: (e) => {
|
|
5660
5768
|
e.preventDefault();
|
|
5661
5769
|
e.stopPropagation();
|
|
5662
|
-
|
|
5770
|
+
if (!disabled) {
|
|
5771
|
+
state.isOpen = !state.isOpen;
|
|
5772
|
+
}
|
|
5663
5773
|
},
|
|
5664
5774
|
}),
|
|
5665
5775
|
// Dropdown Menu
|
|
@@ -5675,7 +5785,63 @@
|
|
|
5675
5785
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5676
5786
|
}, [
|
|
5677
5787
|
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5678
|
-
|
|
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), []),
|
|
5679
5845
|
]),
|
|
5680
5846
|
m(MaterialIcon, {
|
|
5681
5847
|
name: 'caret',
|
|
@@ -5706,25 +5872,22 @@
|
|
|
5706
5872
|
},
|
|
5707
5873
|
view: ({ attrs }) => {
|
|
5708
5874
|
const id = attrs.id || state.id;
|
|
5709
|
-
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"]);
|
|
5710
5876
|
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5711
5877
|
return m('div', {
|
|
5712
5878
|
className: cn,
|
|
5713
5879
|
onclick: (e) => {
|
|
5714
|
-
|
|
5715
|
-
onchange && onchange(state.checked);
|
|
5880
|
+
onchange && onchange(!checked);
|
|
5716
5881
|
e.preventDefault();
|
|
5717
5882
|
},
|
|
5718
5883
|
}, [
|
|
5719
5884
|
label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
|
|
5720
|
-
m('.switch', params, m('label',
|
|
5721
|
-
style: { cursor: 'pointer' },
|
|
5722
|
-
}, [
|
|
5885
|
+
m('.switch', params, m('label', [
|
|
5723
5886
|
m('span', left || 'Off'),
|
|
5724
5887
|
m('input[type=checkbox]', {
|
|
5725
5888
|
id,
|
|
5726
5889
|
disabled,
|
|
5727
|
-
checked
|
|
5890
|
+
checked,
|
|
5728
5891
|
}),
|
|
5729
5892
|
m('span.lever'),
|
|
5730
5893
|
m('span', right || 'On'),
|
|
@@ -5916,22 +6079,62 @@
|
|
|
5916
6079
|
};
|
|
5917
6080
|
};
|
|
5918
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
|
+
};
|
|
5919
6123
|
/**
|
|
5920
6124
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
5921
6125
|
*/
|
|
5922
6126
|
const SearchSelect = () => {
|
|
5923
|
-
// (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
|
|
5924
6127
|
// State initialization
|
|
5925
6128
|
const state = {
|
|
6129
|
+
id: '',
|
|
5926
6130
|
isOpen: false,
|
|
5927
|
-
selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
|
|
5928
6131
|
searchTerm: '',
|
|
5929
|
-
options: [],
|
|
5930
6132
|
inputRef: null,
|
|
5931
6133
|
dropdownRef: null,
|
|
5932
6134
|
focusedIndex: -1,
|
|
5933
|
-
|
|
6135
|
+
internalSelectedIds: [],
|
|
5934
6136
|
};
|
|
6137
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5935
6138
|
const componentId = uniqueId();
|
|
5936
6139
|
const searchInputId = `${componentId}-search`;
|
|
5937
6140
|
// Handle click outside
|
|
@@ -5972,9 +6175,7 @@
|
|
|
5972
6175
|
// Handle add new option
|
|
5973
6176
|
return 'addNew';
|
|
5974
6177
|
}
|
|
5975
|
-
else if (state.focusedIndex < filteredOptions.length)
|
|
5976
|
-
toggleOption(filteredOptions[state.focusedIndex]);
|
|
5977
|
-
}
|
|
6178
|
+
else if (state.focusedIndex < filteredOptions.length) ;
|
|
5978
6179
|
}
|
|
5979
6180
|
break;
|
|
5980
6181
|
case 'Escape':
|
|
@@ -5986,26 +6187,65 @@
|
|
|
5986
6187
|
return null;
|
|
5987
6188
|
};
|
|
5988
6189
|
// Toggle option selection
|
|
5989
|
-
const toggleOption = (option) => {
|
|
6190
|
+
const toggleOption = (option, attrs) => {
|
|
5990
6191
|
if (option.disabled)
|
|
5991
6192
|
return;
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
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
|
+
}
|
|
5995
6209
|
state.searchTerm = '';
|
|
5996
6210
|
state.focusedIndex = -1;
|
|
5997
|
-
|
|
6211
|
+
// Call onchange if provided
|
|
6212
|
+
if (attrs.onchange) {
|
|
6213
|
+
attrs.onchange(newIds);
|
|
6214
|
+
}
|
|
5998
6215
|
};
|
|
5999
6216
|
// Remove a selected option
|
|
6000
|
-
const removeOption = (
|
|
6001
|
-
|
|
6002
|
-
|
|
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
|
+
}
|
|
6003
6236
|
};
|
|
6004
6237
|
return {
|
|
6005
|
-
oninit: ({ attrs
|
|
6006
|
-
state.
|
|
6007
|
-
state
|
|
6008
|
-
|
|
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
|
+
}
|
|
6009
6249
|
},
|
|
6010
6250
|
oncreate() {
|
|
6011
6251
|
document.addEventListener('click', handleClickOutside);
|
|
@@ -6013,14 +6253,27 @@
|
|
|
6013
6253
|
onremove() {
|
|
6014
6254
|
document.removeEventListener('click', handleClickOutside);
|
|
6015
6255
|
},
|
|
6016
|
-
view({ attrs
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
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));
|
|
6021
6274
|
// Safely filter options
|
|
6022
|
-
const filteredOptions =
|
|
6023
|
-
!
|
|
6275
|
+
const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
6276
|
+
!selectedIds.includes(option.id));
|
|
6024
6277
|
// Check if we should show the "add new option" element
|
|
6025
6278
|
const showAddNew = oncreateNewOption &&
|
|
6026
6279
|
state.searchTerm &&
|
|
@@ -6038,6 +6291,7 @@
|
|
|
6038
6291
|
state.isOpen = !state.isOpen;
|
|
6039
6292
|
// console.log('SearchSelect state changed to', state.isOpen); // Debug log
|
|
6040
6293
|
},
|
|
6294
|
+
class: 'chips chips-container',
|
|
6041
6295
|
style: {
|
|
6042
6296
|
display: 'flex',
|
|
6043
6297
|
alignItems: 'end',
|
|
@@ -6050,25 +6304,20 @@
|
|
|
6050
6304
|
// Hidden input for label association and accessibility
|
|
6051
6305
|
m('input', {
|
|
6052
6306
|
type: 'text',
|
|
6053
|
-
id:
|
|
6054
|
-
value:
|
|
6307
|
+
id: state.id,
|
|
6308
|
+
value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
|
|
6055
6309
|
readonly: true,
|
|
6310
|
+
class: 'sr-only',
|
|
6056
6311
|
style: { position: 'absolute', left: '-9999px', opacity: 0 },
|
|
6057
6312
|
}),
|
|
6058
6313
|
// Selected Options (chips)
|
|
6059
|
-
...
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
onclick: (e) => {
|
|
6065
|
-
e.stopPropagation();
|
|
6066
|
-
removeOption(option);
|
|
6067
|
-
},
|
|
6068
|
-
}),
|
|
6069
|
-
])),
|
|
6314
|
+
...selectedOptions.map((option) => m(SelectedChip, {
|
|
6315
|
+
// key: option.id,
|
|
6316
|
+
option,
|
|
6317
|
+
onRemove: (id) => removeOption(id, attrs),
|
|
6318
|
+
})),
|
|
6070
6319
|
// Placeholder when no options selected
|
|
6071
|
-
|
|
6320
|
+
selectedOptions.length === 0 &&
|
|
6072
6321
|
placeholder &&
|
|
6073
6322
|
m('span.placeholder', {
|
|
6074
6323
|
style: {
|
|
@@ -6089,8 +6338,8 @@
|
|
|
6089
6338
|
// Label
|
|
6090
6339
|
label &&
|
|
6091
6340
|
m('label', {
|
|
6092
|
-
for:
|
|
6093
|
-
class: placeholder ||
|
|
6341
|
+
for: state.id,
|
|
6342
|
+
class: placeholder || selectedOptions.length > 0 ? 'active' : '',
|
|
6094
6343
|
}, label),
|
|
6095
6344
|
// Dropdown Menu
|
|
6096
6345
|
state.isOpen &&
|
|
@@ -6106,7 +6355,6 @@
|
|
|
6106
6355
|
m('li', // Search Input
|
|
6107
6356
|
{
|
|
6108
6357
|
class: 'search-wrapper',
|
|
6109
|
-
style: { padding: '0 16px', position: 'relative' },
|
|
6110
6358
|
}, [
|
|
6111
6359
|
m('input', {
|
|
6112
6360
|
type: 'text',
|
|
@@ -6125,29 +6373,21 @@
|
|
|
6125
6373
|
const result = handleKeyDown(e, filteredOptions, !!showAddNew);
|
|
6126
6374
|
if (result === 'addNew' && oncreateNewOption) {
|
|
6127
6375
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6128
|
-
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);
|
|
6129
6382
|
}
|
|
6130
6383
|
},
|
|
6131
|
-
|
|
6132
|
-
width: '100%',
|
|
6133
|
-
outline: 'none',
|
|
6134
|
-
fontSize: '0.875rem',
|
|
6135
|
-
border: 'none',
|
|
6136
|
-
padding: '8px 0',
|
|
6137
|
-
borderBottom: '1px solid var(--mm-input-border, #9e9e9e)',
|
|
6138
|
-
backgroundColor: 'transparent',
|
|
6139
|
-
color: 'var(--mm-text-primary, inherit)',
|
|
6140
|
-
},
|
|
6384
|
+
class: 'search-select-input',
|
|
6141
6385
|
}),
|
|
6142
6386
|
]),
|
|
6143
6387
|
// No options found message or list of options
|
|
6144
6388
|
...(filteredOptions.length === 0 && !showAddNew
|
|
6145
6389
|
? [
|
|
6146
|
-
m('li',
|
|
6147
|
-
// {
|
|
6148
|
-
// style: getNoOptionsStyles(),
|
|
6149
|
-
// },
|
|
6150
|
-
noOptionsFound),
|
|
6390
|
+
m('li.search-select-no-options', texts.noOptionsFound),
|
|
6151
6391
|
]
|
|
6152
6392
|
: []),
|
|
6153
6393
|
// Add new option item
|
|
@@ -6156,35 +6396,27 @@
|
|
|
6156
6396
|
m('li', {
|
|
6157
6397
|
onclick: async () => {
|
|
6158
6398
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6159
|
-
toggleOption(option);
|
|
6399
|
+
toggleOption(option, attrs);
|
|
6160
6400
|
},
|
|
6161
6401
|
class: state.focusedIndex === filteredOptions.length ? 'active' : '',
|
|
6162
6402
|
onmouseover: () => {
|
|
6163
6403
|
state.focusedIndex = filteredOptions.length;
|
|
6164
6404
|
},
|
|
6165
|
-
}, [m('span',
|
|
6405
|
+
}, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
|
|
6166
6406
|
]
|
|
6167
6407
|
: []),
|
|
6168
6408
|
// List of filtered options
|
|
6169
|
-
...filteredOptions.map((option, index) => m(
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
state.focusedIndex = index;
|
|
6179
|
-
}
|
|
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;
|
|
6180
6418
|
},
|
|
6181
|
-
},
|
|
6182
|
-
m('input', {
|
|
6183
|
-
type: 'checkbox',
|
|
6184
|
-
checked: state.selectedOptions.some((selected) => selected.id === option.id),
|
|
6185
|
-
}),
|
|
6186
|
-
option.label || option.id.toString(),
|
|
6187
|
-
]))),
|
|
6419
|
+
})),
|
|
6188
6420
|
]),
|
|
6189
6421
|
]);
|
|
6190
6422
|
},
|
|
@@ -8304,6 +8536,7 @@
|
|
|
8304
8536
|
exports.ModalPanel = ModalPanel;
|
|
8305
8537
|
exports.NumberInput = NumberInput;
|
|
8306
8538
|
exports.Options = Options;
|
|
8539
|
+
exports.OptionsList = OptionsList;
|
|
8307
8540
|
exports.Pagination = Pagination;
|
|
8308
8541
|
exports.PaginationControls = PaginationControls;
|
|
8309
8542
|
exports.Parallax = Parallax;
|