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.js
CHANGED
|
@@ -196,12 +196,13 @@ const Autocomplete = () => {
|
|
|
196
196
|
const state = {
|
|
197
197
|
id: uniqueId(),
|
|
198
198
|
isActive: false,
|
|
199
|
-
|
|
199
|
+
internalValue: '',
|
|
200
200
|
isOpen: false,
|
|
201
201
|
suggestions: [],
|
|
202
202
|
selectedIndex: -1,
|
|
203
203
|
inputElement: null,
|
|
204
204
|
};
|
|
205
|
+
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' && typeof attrs.oninput === 'function';
|
|
205
206
|
const filterSuggestions = (input, data, limit, minLength) => {
|
|
206
207
|
if (!input || input.length < minLength) {
|
|
207
208
|
return [];
|
|
@@ -213,9 +214,16 @@ const Autocomplete = () => {
|
|
|
213
214
|
return filtered;
|
|
214
215
|
};
|
|
215
216
|
const selectSuggestion = (suggestion, attrs) => {
|
|
216
|
-
|
|
217
|
+
const controlled = isControlled(attrs);
|
|
218
|
+
// Update internal state for uncontrolled mode
|
|
219
|
+
if (!controlled) {
|
|
220
|
+
state.internalValue = suggestion.key;
|
|
221
|
+
}
|
|
217
222
|
state.isOpen = false;
|
|
218
223
|
state.selectedIndex = -1;
|
|
224
|
+
if (attrs.oninput) {
|
|
225
|
+
attrs.oninput(suggestion.key);
|
|
226
|
+
}
|
|
219
227
|
if (attrs.onchange) {
|
|
220
228
|
attrs.onchange(suggestion.key);
|
|
221
229
|
}
|
|
@@ -289,7 +297,10 @@ const Autocomplete = () => {
|
|
|
289
297
|
};
|
|
290
298
|
return {
|
|
291
299
|
oninit: ({ attrs }) => {
|
|
292
|
-
|
|
300
|
+
// Initialize internal value for uncontrolled mode
|
|
301
|
+
if (!isControlled(attrs)) {
|
|
302
|
+
state.internalValue = attrs.defaultValue || '';
|
|
303
|
+
}
|
|
293
304
|
document.addEventListener('click', closeDropdown);
|
|
294
305
|
},
|
|
295
306
|
onremove: () => {
|
|
@@ -298,40 +309,54 @@ const Autocomplete = () => {
|
|
|
298
309
|
view: ({ attrs }) => {
|
|
299
310
|
const id = attrs.id || state.id;
|
|
300
311
|
const { label, helperText, onchange, newRow, className = 'col s12', style, iconName, isMandatory, data = {}, limit = Infinity, minLength = 1 } = attrs, params = __rest(attrs, ["label", "helperText", "onchange", "newRow", "className", "style", "iconName", "isMandatory", "data", "limit", "minLength"]);
|
|
312
|
+
const controlled = isControlled(attrs);
|
|
313
|
+
const currentValue = controlled ? (attrs.value || '') : state.internalValue;
|
|
301
314
|
const cn = newRow ? className + ' clear' : className;
|
|
302
315
|
// Update suggestions when input changes
|
|
303
|
-
state.suggestions = filterSuggestions(
|
|
316
|
+
state.suggestions = filterSuggestions(currentValue, data, limit, minLength);
|
|
304
317
|
// Check if there's a perfect match (exact key match, case-insensitive)
|
|
305
|
-
const hasExactMatch =
|
|
306
|
-
Object.keys(data).some((key) => key.toLowerCase() ===
|
|
318
|
+
const hasExactMatch = currentValue.length >= minLength &&
|
|
319
|
+
Object.keys(data).some((key) => key.toLowerCase() === currentValue.toLowerCase());
|
|
307
320
|
// Only open dropdown if there are suggestions and no perfect match
|
|
308
|
-
state.isOpen = state.suggestions.length > 0 &&
|
|
309
|
-
const replacer = new RegExp(`(${
|
|
321
|
+
state.isOpen = state.suggestions.length > 0 && currentValue.length >= minLength && !hasExactMatch;
|
|
322
|
+
const replacer = new RegExp(`(${currentValue})`, 'i');
|
|
310
323
|
return m('.input-field.autocomplete-wrapper', {
|
|
311
324
|
className: cn,
|
|
312
325
|
style,
|
|
313
326
|
}, [
|
|
314
327
|
iconName ? m('i.material-icons.prefix', iconName) : '',
|
|
315
|
-
m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value:
|
|
328
|
+
m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: controlled ? currentValue : undefined, oncreate: (vnode) => {
|
|
316
329
|
state.inputElement = vnode.dom;
|
|
330
|
+
// Set initial value for uncontrolled mode
|
|
331
|
+
if (!controlled && attrs.defaultValue) {
|
|
332
|
+
vnode.dom.value = attrs.defaultValue;
|
|
333
|
+
}
|
|
317
334
|
}, oninput: (e) => {
|
|
318
335
|
const target = e.target;
|
|
319
|
-
|
|
336
|
+
const inputValue = target.value;
|
|
320
337
|
state.selectedIndex = -1;
|
|
338
|
+
// Update internal state for uncontrolled mode
|
|
339
|
+
if (!controlled) {
|
|
340
|
+
state.internalValue = inputValue;
|
|
341
|
+
}
|
|
342
|
+
// Call oninput and onchange if provided
|
|
343
|
+
if (attrs.oninput) {
|
|
344
|
+
attrs.oninput(inputValue);
|
|
345
|
+
}
|
|
321
346
|
if (onchange) {
|
|
322
|
-
onchange(
|
|
347
|
+
onchange(inputValue);
|
|
323
348
|
}
|
|
324
349
|
}, onkeydown: (e) => {
|
|
325
350
|
handleKeydown(e, attrs);
|
|
326
351
|
// Call original onkeydown if provided
|
|
327
352
|
if (attrs.onkeydown) {
|
|
328
|
-
attrs.onkeydown(e,
|
|
353
|
+
attrs.onkeydown(e, currentValue);
|
|
329
354
|
}
|
|
330
355
|
}, onfocus: () => {
|
|
331
356
|
state.isActive = true;
|
|
332
|
-
if (
|
|
357
|
+
if (currentValue.length >= minLength) {
|
|
333
358
|
// Check for perfect match on focus too
|
|
334
|
-
const hasExactMatch = Object.keys(data).some((key) => key.toLowerCase() ===
|
|
359
|
+
const hasExactMatch = Object.keys(data).some((key) => key.toLowerCase() === currentValue.toLowerCase());
|
|
335
360
|
state.isOpen = state.suggestions.length > 0 && !hasExactMatch;
|
|
336
361
|
}
|
|
337
362
|
}, onblur: (e) => {
|
|
@@ -388,7 +413,7 @@ const Autocomplete = () => {
|
|
|
388
413
|
label,
|
|
389
414
|
id,
|
|
390
415
|
isMandatory,
|
|
391
|
-
isActive: state.isActive ||
|
|
416
|
+
isActive: state.isActive || currentValue.length > 0 || !!attrs.placeholder || !!attrs.value,
|
|
392
417
|
}),
|
|
393
418
|
m(HelperText, { helperText }),
|
|
394
419
|
]);
|
|
@@ -2077,11 +2102,11 @@ const DatePicker = () => {
|
|
|
2077
2102
|
else {
|
|
2078
2103
|
// Single date initialization (original behavior)
|
|
2079
2104
|
let defaultDate = attrs.defaultDate;
|
|
2080
|
-
if (!defaultDate && attrs.
|
|
2081
|
-
defaultDate = new Date(attrs.
|
|
2105
|
+
if (!defaultDate && attrs.defaultValue) {
|
|
2106
|
+
defaultDate = new Date(attrs.defaultValue);
|
|
2082
2107
|
}
|
|
2083
2108
|
if (isDate(defaultDate)) {
|
|
2084
|
-
// Always set the date if we have
|
|
2109
|
+
// Always set the date if we have value or defaultDate
|
|
2085
2110
|
setDate(defaultDate, true, options);
|
|
2086
2111
|
}
|
|
2087
2112
|
else {
|
|
@@ -2350,16 +2375,16 @@ const handleKeyboardNavigation = (key, currentValue, min, max, step) => {
|
|
|
2350
2375
|
}
|
|
2351
2376
|
};
|
|
2352
2377
|
const initRangeState = (state, attrs) => {
|
|
2353
|
-
const { min = 0, max = 100,
|
|
2378
|
+
const { min = 0, max = 100, value, minValue, maxValue } = attrs;
|
|
2354
2379
|
// Initialize single range value
|
|
2355
|
-
if (
|
|
2356
|
-
const currentValue =
|
|
2380
|
+
if (value !== undefined) {
|
|
2381
|
+
const currentValue = value;
|
|
2357
2382
|
if (state.singleValue === undefined) {
|
|
2358
2383
|
state.singleValue = currentValue;
|
|
2359
2384
|
}
|
|
2360
|
-
if (state.
|
|
2361
|
-
state.singleValue =
|
|
2362
|
-
state.
|
|
2385
|
+
if (state.lastValue !== value && !state.hasUserInteracted) {
|
|
2386
|
+
state.singleValue = value;
|
|
2387
|
+
state.lastValue = value;
|
|
2363
2388
|
}
|
|
2364
2389
|
}
|
|
2365
2390
|
else if (state.singleValue === undefined) {
|
|
@@ -2784,41 +2809,48 @@ const TextArea = () => {
|
|
|
2784
2809
|
height: undefined,
|
|
2785
2810
|
active: false,
|
|
2786
2811
|
textarea: undefined,
|
|
2812
|
+
internalValue: '',
|
|
2787
2813
|
};
|
|
2788
2814
|
const updateHeight = (textarea) => {
|
|
2789
2815
|
textarea.style.height = 'auto';
|
|
2790
2816
|
const newHeight = textarea.scrollHeight + 'px';
|
|
2791
2817
|
state.height = textarea.value.length === 0 ? undefined : newHeight;
|
|
2792
2818
|
};
|
|
2819
|
+
const isControlled = (attrs) => attrs.value !== undefined && attrs.oninput !== undefined;
|
|
2793
2820
|
return {
|
|
2821
|
+
oninit: ({ attrs }) => {
|
|
2822
|
+
// Initialize internal value for uncontrolled mode
|
|
2823
|
+
if (!isControlled(attrs)) {
|
|
2824
|
+
state.internalValue = attrs.defaultValue || '';
|
|
2825
|
+
}
|
|
2826
|
+
},
|
|
2794
2827
|
onremove: () => {
|
|
2795
2828
|
},
|
|
2796
2829
|
view: ({ attrs }) => {
|
|
2797
|
-
|
|
2798
|
-
const
|
|
2799
|
-
|
|
2830
|
+
const { className = 'col s12', helperText, iconName, id = state.id, value, placeholder, isMandatory, label, maxLength, oninput, onchange, onkeydown, onkeypress, onkeyup, onblur, style } = attrs, params = __rest(attrs, ["className", "helperText", "iconName", "id", "value", "placeholder", "isMandatory", "label", "maxLength", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "onblur", "style"]);
|
|
2831
|
+
const controlled = isControlled(attrs);
|
|
2832
|
+
const currentValue = controlled ? value || '' : state.internalValue;
|
|
2800
2833
|
return m('.input-field', { className, style }, [
|
|
2801
2834
|
iconName ? m('i.material-icons.prefix', iconName) : '',
|
|
2802
|
-
m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, style: {
|
|
2835
|
+
m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, value: controlled ? currentValue : undefined, style: {
|
|
2803
2836
|
height: state.height,
|
|
2804
2837
|
}, oncreate: ({ dom }) => {
|
|
2805
2838
|
const textarea = (state.textarea = dom);
|
|
2806
|
-
//
|
|
2807
|
-
if (
|
|
2808
|
-
textarea.value = String(
|
|
2809
|
-
updateHeight(textarea);
|
|
2810
|
-
// } else {
|
|
2811
|
-
// updateHeight(textarea);
|
|
2839
|
+
// For uncontrolled mode, set initial value only
|
|
2840
|
+
if (!controlled && attrs.defaultValue !== undefined) {
|
|
2841
|
+
textarea.value = String(attrs.defaultValue);
|
|
2812
2842
|
}
|
|
2843
|
+
updateHeight(textarea);
|
|
2813
2844
|
// Update character count state for counter component
|
|
2814
2845
|
if (maxLength) {
|
|
2815
|
-
state.currentLength = textarea.value.length;
|
|
2846
|
+
state.currentLength = textarea.value.length;
|
|
2816
2847
|
m.redraw();
|
|
2817
2848
|
}
|
|
2818
2849
|
}, onupdate: ({ dom }) => {
|
|
2819
2850
|
const textarea = dom;
|
|
2820
2851
|
if (state.height)
|
|
2821
2852
|
textarea.style.height = state.height;
|
|
2853
|
+
// No need to manually sync in onupdate since value attribute handles it
|
|
2822
2854
|
}, onfocus: () => {
|
|
2823
2855
|
state.active = true;
|
|
2824
2856
|
}, oninput: (e) => {
|
|
@@ -2832,7 +2864,11 @@ const TextArea = () => {
|
|
|
2832
2864
|
state.currentLength = target.value.length;
|
|
2833
2865
|
state.hasInteracted = target.value.length > 0;
|
|
2834
2866
|
}
|
|
2835
|
-
//
|
|
2867
|
+
// Update internal state for uncontrolled mode
|
|
2868
|
+
if (!controlled) {
|
|
2869
|
+
state.internalValue = target.value;
|
|
2870
|
+
}
|
|
2871
|
+
// Call oninput handler
|
|
2836
2872
|
if (oninput) {
|
|
2837
2873
|
oninput(target.value);
|
|
2838
2874
|
}
|
|
@@ -2864,8 +2900,8 @@ const TextArea = () => {
|
|
|
2864
2900
|
label,
|
|
2865
2901
|
id,
|
|
2866
2902
|
isMandatory,
|
|
2867
|
-
isActive:
|
|
2868
|
-
initialValue:
|
|
2903
|
+
isActive: currentValue || placeholder || state.active,
|
|
2904
|
+
initialValue: currentValue !== '',
|
|
2869
2905
|
}),
|
|
2870
2906
|
m(HelperText, {
|
|
2871
2907
|
helperText,
|
|
@@ -2887,7 +2923,7 @@ const TextArea = () => {
|
|
|
2887
2923
|
const InputField = (type, defaultClass = '') => () => {
|
|
2888
2924
|
const state = {
|
|
2889
2925
|
id: uniqueId(),
|
|
2890
|
-
|
|
2926
|
+
internalValue: undefined,
|
|
2891
2927
|
hasInteracted: false,
|
|
2892
2928
|
isValid: true,
|
|
2893
2929
|
active: false,
|
|
@@ -2899,9 +2935,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2899
2935
|
isDragging: false,
|
|
2900
2936
|
activeThumb: null,
|
|
2901
2937
|
};
|
|
2902
|
-
|
|
2903
|
-
// let lengthUpdateHandler: (() => void) | null = null;
|
|
2904
|
-
// let inputElement: HTMLInputElement | null = null;
|
|
2938
|
+
const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' && typeof attrs.oninput === 'function';
|
|
2905
2939
|
const getValue = (target) => {
|
|
2906
2940
|
const val = target.value;
|
|
2907
2941
|
return (val ? (type === 'number' || type === 'range' ? +val : val) : val);
|
|
@@ -2915,20 +2949,57 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2915
2949
|
}
|
|
2916
2950
|
};
|
|
2917
2951
|
const focus = ({ autofocus }) => autofocus ? (typeof autofocus === 'boolean' ? autofocus : autofocus()) : false;
|
|
2918
|
-
const lengthUpdateHandler = () => {
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2952
|
+
// const lengthUpdateHandler = () => {
|
|
2953
|
+
// const length = state.inputElement?.value.length;
|
|
2954
|
+
// if (length) {
|
|
2955
|
+
// state.currentLength = length;
|
|
2956
|
+
// state.hasInteracted = length > 0;
|
|
2957
|
+
// }
|
|
2958
|
+
// };
|
|
2959
|
+
const clearInput = (oninput, onchange) => {
|
|
2960
|
+
if (state.inputElement) {
|
|
2961
|
+
state.inputElement.value = '';
|
|
2962
|
+
state.inputElement.focus();
|
|
2963
|
+
state.active = false;
|
|
2964
|
+
// state.currentLength = 0;
|
|
2965
|
+
// state.hasInteracted = false;
|
|
2966
|
+
// Trigger oninput and onchange callbacks
|
|
2967
|
+
const value = getValue(state.inputElement);
|
|
2968
|
+
if (oninput) {
|
|
2969
|
+
oninput(value);
|
|
2970
|
+
}
|
|
2971
|
+
if (onchange) {
|
|
2972
|
+
onchange(value);
|
|
2973
|
+
}
|
|
2974
|
+
// Update validation state
|
|
2975
|
+
state.inputElement.classList.remove('valid', 'invalid');
|
|
2976
|
+
state.isValid = true;
|
|
2977
|
+
m.redraw();
|
|
2924
2978
|
}
|
|
2925
2979
|
};
|
|
2926
2980
|
// Range slider helper functions
|
|
2927
2981
|
// Range slider rendering functions are now in separate module
|
|
2928
2982
|
return {
|
|
2983
|
+
oninit: ({ attrs }) => {
|
|
2984
|
+
// Initialize internal value if not in controlled mode
|
|
2985
|
+
if (!isControlled(attrs)) {
|
|
2986
|
+
const isNumeric = ['number', 'range'].includes(type);
|
|
2987
|
+
if (attrs.defaultValue !== undefined) {
|
|
2988
|
+
if (isNumeric) {
|
|
2989
|
+
state.internalValue = attrs.defaultValue;
|
|
2990
|
+
}
|
|
2991
|
+
else {
|
|
2992
|
+
state.internalValue = String(attrs.defaultValue);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
else {
|
|
2996
|
+
state.internalValue = (type === 'color' ? '#ff0000' : isNumeric ? undefined : '');
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
},
|
|
2929
3000
|
view: ({ attrs }) => {
|
|
2930
|
-
var _a;
|
|
2931
|
-
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id,
|
|
3001
|
+
var _a, _b;
|
|
3002
|
+
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate, canClear } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate", "canClear"]);
|
|
2932
3003
|
// const attributes = toAttrs(params);
|
|
2933
3004
|
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
2934
3005
|
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
@@ -2945,10 +3016,14 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2945
3016
|
isMandatory,
|
|
2946
3017
|
helperText }));
|
|
2947
3018
|
}
|
|
3019
|
+
const isNumeric = ['number', 'range'].includes(type);
|
|
3020
|
+
const controlled = isControlled(attrs);
|
|
3021
|
+
const value = (controlled ? attrs.value : state.internalValue);
|
|
3022
|
+
const rangeType = type === 'range' && !attrs.minmax;
|
|
2948
3023
|
return m('.input-field', { className: cn, style }, [
|
|
2949
3024
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2950
3025
|
m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
|
|
2951
|
-
placeholder, class: type === 'range' && attrs.vertical ? 'range-slider vertical' : undefined, style: type === 'range' && attrs.vertical
|
|
3026
|
+
placeholder, value: controlled ? value : undefined, class: type === 'range' && attrs.vertical ? 'range-slider vertical' : undefined, style: type === 'range' && attrs.vertical
|
|
2952
3027
|
? {
|
|
2953
3028
|
height: attrs.height || '200px',
|
|
2954
3029
|
width: '6px',
|
|
@@ -2962,25 +3037,9 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2962
3037
|
if (focus(attrs)) {
|
|
2963
3038
|
input.focus();
|
|
2964
3039
|
}
|
|
2965
|
-
//
|
|
2966
|
-
if (
|
|
2967
|
-
input.value = String(
|
|
2968
|
-
}
|
|
2969
|
-
// Update character count state for counter component
|
|
2970
|
-
if (maxLength) {
|
|
2971
|
-
state.currentLength = input.value.length; // Initial count
|
|
2972
|
-
}
|
|
2973
|
-
// Range input functionality
|
|
2974
|
-
if (type === 'range' && !attrs.minmax) {
|
|
2975
|
-
const updateThumb = () => {
|
|
2976
|
-
const value = input.value;
|
|
2977
|
-
const min = input.min || '0';
|
|
2978
|
-
const max = input.max || '100';
|
|
2979
|
-
const percentage = ((parseFloat(value) - parseFloat(min)) / (parseFloat(max) - parseFloat(min))) * 100;
|
|
2980
|
-
input.style.setProperty('--range-progress', `${percentage}%`);
|
|
2981
|
-
};
|
|
2982
|
-
input.addEventListener('input', updateThumb);
|
|
2983
|
-
updateThumb(); // Initial position
|
|
3040
|
+
// For uncontrolled mode, set initial value only
|
|
3041
|
+
if (!controlled && attrs.defaultValue !== undefined) {
|
|
3042
|
+
input.value = String(attrs.defaultValue);
|
|
2984
3043
|
}
|
|
2985
3044
|
}, onkeyup: onkeyup
|
|
2986
3045
|
? (ev) => {
|
|
@@ -2994,21 +3053,25 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2994
3053
|
? (ev) => {
|
|
2995
3054
|
onkeypress(ev, getValue(ev.target));
|
|
2996
3055
|
}
|
|
2997
|
-
: undefined, onupdate: validate
|
|
2998
|
-
? ({ dom }) => {
|
|
2999
|
-
const target = dom;
|
|
3000
|
-
setValidity(target, validate(getValue(target), target));
|
|
3001
|
-
}
|
|
3002
3056
|
: undefined, oninput: (e) => {
|
|
3003
3057
|
state.active = true;
|
|
3058
|
+
state.hasInteracted = false;
|
|
3004
3059
|
const target = e.target;
|
|
3005
3060
|
// Handle original oninput logic
|
|
3006
|
-
const
|
|
3061
|
+
const inputValue = getValue(target);
|
|
3062
|
+
// Update internal state for uncontrolled mode
|
|
3063
|
+
if (!controlled) {
|
|
3064
|
+
state.internalValue = inputValue;
|
|
3065
|
+
}
|
|
3007
3066
|
if (oninput) {
|
|
3008
|
-
oninput(
|
|
3067
|
+
oninput(inputValue);
|
|
3009
3068
|
}
|
|
3010
|
-
if (
|
|
3011
|
-
|
|
3069
|
+
if (rangeType) {
|
|
3070
|
+
const value = target.value;
|
|
3071
|
+
const min = parseFloat(target.min || '0');
|
|
3072
|
+
const max = parseFloat(target.max || '100');
|
|
3073
|
+
const percentage = Math.round((100 * (parseFloat(value) - min)) / (max - min));
|
|
3074
|
+
target.style.setProperty('--range-progress', `${percentage}%`);
|
|
3012
3075
|
}
|
|
3013
3076
|
// Don't validate on input, only clear error states if user is typing
|
|
3014
3077
|
if (validate && target.classList.contains('invalid') && target.value.length > 0) {
|
|
@@ -3024,6 +3087,14 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3024
3087
|
state.isValid = true;
|
|
3025
3088
|
}
|
|
3026
3089
|
}
|
|
3090
|
+
else if ((type === 'email' || type === 'url') && target.classList.contains('invalid') && target.value.length > 0) {
|
|
3091
|
+
// Clear native validation errors if user is typing and input becomes valid
|
|
3092
|
+
if (target.validity.valid) {
|
|
3093
|
+
target.classList.remove('invalid');
|
|
3094
|
+
target.classList.add('valid');
|
|
3095
|
+
state.isValid = true;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3027
3098
|
}, onfocus: () => {
|
|
3028
3099
|
state.active = true;
|
|
3029
3100
|
}, onblur: (e) => {
|
|
@@ -3060,6 +3131,48 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3060
3131
|
state.isValid = true;
|
|
3061
3132
|
}
|
|
3062
3133
|
}
|
|
3134
|
+
else if (type === 'email' || type === 'url') {
|
|
3135
|
+
// Use browser's native HTML5 validation for email and url types
|
|
3136
|
+
const value = getValue(target);
|
|
3137
|
+
if (value && String(value).length > 0) {
|
|
3138
|
+
state.isValid = target.validity.valid;
|
|
3139
|
+
target.setCustomValidity(''); // Clear any custom validation message
|
|
3140
|
+
if (state.isValid) {
|
|
3141
|
+
target.classList.remove('invalid');
|
|
3142
|
+
target.classList.add('valid');
|
|
3143
|
+
}
|
|
3144
|
+
else {
|
|
3145
|
+
target.classList.remove('valid');
|
|
3146
|
+
target.classList.add('invalid');
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
else {
|
|
3150
|
+
// Clear validation state if no text
|
|
3151
|
+
target.classList.remove('valid', 'invalid');
|
|
3152
|
+
state.isValid = true;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
else if (isNumeric) {
|
|
3156
|
+
// Use browser's native HTML5 validation for numeric inputs (handles min, max, step, etc.)
|
|
3157
|
+
const value = getValue(target);
|
|
3158
|
+
if (value !== undefined && value !== null && !isNaN(Number(value))) {
|
|
3159
|
+
state.isValid = target.validity.valid;
|
|
3160
|
+
target.setCustomValidity(''); // Clear any custom validation message
|
|
3161
|
+
if (state.isValid) {
|
|
3162
|
+
target.classList.remove('invalid');
|
|
3163
|
+
target.classList.add('valid');
|
|
3164
|
+
}
|
|
3165
|
+
else {
|
|
3166
|
+
target.classList.remove('valid');
|
|
3167
|
+
target.classList.add('invalid');
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
else {
|
|
3171
|
+
// Clear validation state if no valid number
|
|
3172
|
+
target.classList.remove('valid', 'invalid');
|
|
3173
|
+
state.isValid = true;
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3063
3176
|
// Also call the original onblur handler if provided
|
|
3064
3177
|
if (attrs.onblur) {
|
|
3065
3178
|
attrs.onblur(e);
|
|
@@ -3068,23 +3181,35 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3068
3181
|
onchange(getValue(state.inputElement));
|
|
3069
3182
|
}
|
|
3070
3183
|
} })),
|
|
3184
|
+
// Clear button - only for text inputs with canClear enabled and has content
|
|
3185
|
+
canClear && type === 'text' && ((_b = state.inputElement) === null || _b === void 0 ? void 0 : _b.value)
|
|
3186
|
+
? m(MaterialIcon, {
|
|
3187
|
+
name: 'close',
|
|
3188
|
+
className: 'input-clear-btn',
|
|
3189
|
+
onclick: (e) => {
|
|
3190
|
+
e.preventDefault();
|
|
3191
|
+
e.stopPropagation();
|
|
3192
|
+
clearInput(oninput, onchange);
|
|
3193
|
+
},
|
|
3194
|
+
})
|
|
3195
|
+
: undefined,
|
|
3071
3196
|
m(Label, {
|
|
3072
3197
|
label,
|
|
3073
3198
|
id,
|
|
3074
3199
|
isMandatory,
|
|
3075
3200
|
isActive,
|
|
3076
|
-
initialValue:
|
|
3201
|
+
initialValue: value !== undefined && value !== '',
|
|
3077
3202
|
}),
|
|
3078
3203
|
m(HelperText, {
|
|
3079
3204
|
helperText,
|
|
3080
3205
|
dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
|
|
3081
3206
|
dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
|
|
3082
3207
|
}),
|
|
3083
|
-
maxLength
|
|
3208
|
+
maxLength && typeof value === 'string'
|
|
3084
3209
|
? m(CharacterCounter, {
|
|
3085
|
-
currentLength:
|
|
3210
|
+
currentLength: value.length,
|
|
3086
3211
|
maxLength,
|
|
3087
|
-
show:
|
|
3212
|
+
show: value.length > 0,
|
|
3088
3213
|
})
|
|
3089
3214
|
: undefined,
|
|
3090
3215
|
]);
|
|
@@ -3111,7 +3236,7 @@ const FileInput = () => {
|
|
|
3111
3236
|
let i;
|
|
3112
3237
|
return {
|
|
3113
3238
|
view: ({ attrs }) => {
|
|
3114
|
-
const { multiple, disabled,
|
|
3239
|
+
const { multiple, disabled, value, placeholder, onchange, className = 'col s12', accept: acceptedFiles, label = 'File', } = attrs;
|
|
3115
3240
|
const accept = acceptedFiles
|
|
3116
3241
|
? acceptedFiles instanceof Array
|
|
3117
3242
|
? acceptedFiles.join(', ')
|
|
@@ -3142,8 +3267,8 @@ const FileInput = () => {
|
|
|
3142
3267
|
placeholder,
|
|
3143
3268
|
oncreate: ({ dom }) => {
|
|
3144
3269
|
i = dom;
|
|
3145
|
-
if (
|
|
3146
|
-
i.value =
|
|
3270
|
+
if (value)
|
|
3271
|
+
i.value = value;
|
|
3147
3272
|
},
|
|
3148
3273
|
})),
|
|
3149
3274
|
(canClear || (i === null || i === void 0 ? void 0 : i.value)) &&
|
|
@@ -3170,11 +3295,14 @@ const FileInput = () => {
|
|
|
3170
3295
|
|
|
3171
3296
|
/** Component to show a check box */
|
|
3172
3297
|
const InputCheckbox = () => {
|
|
3298
|
+
let checkboxId;
|
|
3173
3299
|
return {
|
|
3174
3300
|
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3175
|
-
|
|
3301
|
+
if (!checkboxId)
|
|
3302
|
+
checkboxId = inputId || uniqueId();
|
|
3176
3303
|
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3177
3304
|
m('input[type=checkbox][tabindex=0]', {
|
|
3305
|
+
className: disabled ? 'disabled' : undefined,
|
|
3178
3306
|
id: checkboxId,
|
|
3179
3307
|
checked,
|
|
3180
3308
|
disabled,
|
|
@@ -3191,78 +3319,79 @@ const InputCheckbox = () => {
|
|
|
3191
3319
|
},
|
|
3192
3320
|
};
|
|
3193
3321
|
};
|
|
3322
|
+
/** Reusable layout component for rendering options horizontally or vertically */
|
|
3323
|
+
const OptionsList = {
|
|
3324
|
+
view: ({ attrs: { options, layout } }) => {
|
|
3325
|
+
const optionElements = options.map(({ component, props, key }) => m(component, Object.assign(Object.assign({}, props), { key })));
|
|
3326
|
+
return layout === 'horizontal'
|
|
3327
|
+
? m('div.grid-container', optionElements)
|
|
3328
|
+
: optionElements;
|
|
3329
|
+
},
|
|
3330
|
+
};
|
|
3194
3331
|
/** A list of checkboxes */
|
|
3195
3332
|
const Options = () => {
|
|
3196
|
-
const state = {
|
|
3197
|
-
|
|
3198
|
-
|
|
3333
|
+
const state = {
|
|
3334
|
+
componentId: '',
|
|
3335
|
+
};
|
|
3336
|
+
const selectAll = (options, onchange) => {
|
|
3199
3337
|
const allIds = options.map((option) => option.id);
|
|
3200
|
-
|
|
3201
|
-
if (callback)
|
|
3202
|
-
callback(allIds);
|
|
3338
|
+
onchange && onchange(allIds);
|
|
3203
3339
|
};
|
|
3204
|
-
const selectNone = (
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3340
|
+
const selectNone = (onchange) => {
|
|
3341
|
+
onchange && onchange([]);
|
|
3342
|
+
};
|
|
3343
|
+
const handleChange = (propId, checked, checkedIds, onchange) => {
|
|
3344
|
+
const newCheckedIds = checkedIds.filter((i) => i !== propId);
|
|
3345
|
+
if (checked) {
|
|
3346
|
+
newCheckedIds.push(propId);
|
|
3347
|
+
}
|
|
3348
|
+
onchange && onchange(newCheckedIds);
|
|
3208
3349
|
};
|
|
3209
3350
|
return {
|
|
3210
|
-
oninit: ({ attrs
|
|
3211
|
-
|
|
3212
|
-
state.checkedId = checkedId;
|
|
3213
|
-
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3214
|
-
state.componentId = id || uniqueId();
|
|
3351
|
+
oninit: ({ attrs }) => {
|
|
3352
|
+
state.componentId = attrs.id || uniqueId();
|
|
3215
3353
|
},
|
|
3216
|
-
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false,
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
if (checked) {
|
|
3221
|
-
checkedIds.push(propId);
|
|
3222
|
-
}
|
|
3223
|
-
state.checkedIds = checkedIds;
|
|
3224
|
-
callback(checkedIds);
|
|
3225
|
-
}
|
|
3226
|
-
: undefined;
|
|
3354
|
+
view: ({ attrs: { checkedId, label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, selectAllText = 'Select All', selectNoneText = 'Select None', onchange, }, }) => {
|
|
3355
|
+
// Derive checked IDs from props
|
|
3356
|
+
const checkedIds = checkedId !== undefined ? (Array.isArray(checkedId) ? checkedId : [checkedId]) : [];
|
|
3357
|
+
const isChecked = (id) => checkedIds.includes(id);
|
|
3227
3358
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
3228
|
-
const
|
|
3229
|
-
|
|
3359
|
+
const optionItems = options.map((option) => ({
|
|
3360
|
+
component: InputCheckbox,
|
|
3361
|
+
props: {
|
|
3230
3362
|
disabled: disabled || option.disabled,
|
|
3231
3363
|
label: option.label,
|
|
3232
|
-
onchange: onchange ? (v) =>
|
|
3364
|
+
onchange: onchange ? (v) => handleChange(option.id, v, checkedIds, onchange) : undefined,
|
|
3233
3365
|
className: option.className || checkboxClass,
|
|
3234
3366
|
checked: isChecked(option.id),
|
|
3235
3367
|
description: option.description,
|
|
3236
3368
|
inputId: `${state.componentId}-${option.id}`,
|
|
3237
|
-
}
|
|
3238
|
-
:
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
description: option.description,
|
|
3245
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3246
|
-
}));
|
|
3369
|
+
},
|
|
3370
|
+
key: option.id,
|
|
3371
|
+
}));
|
|
3372
|
+
const optionsContent = m(OptionsList, {
|
|
3373
|
+
options: optionItems,
|
|
3374
|
+
layout,
|
|
3375
|
+
});
|
|
3247
3376
|
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3248
3377
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3249
3378
|
showSelectAll &&
|
|
3250
|
-
m('div.select-all-controls', { style:
|
|
3379
|
+
m('div.select-all-controls', { style: { marginBottom: '10px' } }, [
|
|
3251
3380
|
m('a', {
|
|
3252
3381
|
href: '#',
|
|
3253
3382
|
onclick: (e) => {
|
|
3254
3383
|
e.preventDefault();
|
|
3255
|
-
selectAll(options,
|
|
3384
|
+
selectAll(options, onchange);
|
|
3256
3385
|
},
|
|
3257
|
-
style:
|
|
3258
|
-
},
|
|
3386
|
+
style: { marginRight: '15px' },
|
|
3387
|
+
}, selectAllText),
|
|
3259
3388
|
m('a', {
|
|
3260
3389
|
href: '#',
|
|
3261
3390
|
onclick: (e) => {
|
|
3262
3391
|
e.preventDefault();
|
|
3263
|
-
selectNone(
|
|
3392
|
+
selectNone(onchange);
|
|
3264
3393
|
},
|
|
3265
|
-
},
|
|
3394
|
+
}, selectNoneText),
|
|
3266
3395
|
]),
|
|
3267
3396
|
description && m(HelperText, { helperText: description }),
|
|
3268
3397
|
m('form', { action: '#' }, optionsContent),
|
|
@@ -3857,11 +3986,19 @@ const DataTable = () => {
|
|
|
3857
3986
|
const Dropdown = () => {
|
|
3858
3987
|
const state = {
|
|
3859
3988
|
isOpen: false,
|
|
3860
|
-
initialValue: undefined,
|
|
3861
3989
|
id: '',
|
|
3862
3990
|
focusedIndex: -1,
|
|
3863
3991
|
inputRef: null,
|
|
3864
3992
|
dropdownRef: null,
|
|
3993
|
+
internalCheckedId: undefined,
|
|
3994
|
+
};
|
|
3995
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
3996
|
+
const closeDropdown = (e) => {
|
|
3997
|
+
const target = e.target;
|
|
3998
|
+
if (!target.closest('.dropdown-wrapper.input-field')) {
|
|
3999
|
+
state.isOpen = false;
|
|
4000
|
+
m.redraw();
|
|
4001
|
+
}
|
|
3865
4002
|
};
|
|
3866
4003
|
const handleKeyDown = (e, items, onchange) => {
|
|
3867
4004
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3870,7 +4007,7 @@ const Dropdown = () => {
|
|
|
3870
4007
|
e.preventDefault();
|
|
3871
4008
|
if (!state.isOpen) {
|
|
3872
4009
|
state.isOpen = true;
|
|
3873
|
-
state.focusedIndex = 0;
|
|
4010
|
+
state.focusedIndex = availableItems.length > 0 ? 0 : -1;
|
|
3874
4011
|
}
|
|
3875
4012
|
else {
|
|
3876
4013
|
state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
|
|
@@ -3888,15 +4025,13 @@ const Dropdown = () => {
|
|
|
3888
4025
|
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
3889
4026
|
const selectedItem = availableItems[state.focusedIndex];
|
|
3890
4027
|
const value = (selectedItem.id || selectedItem.label);
|
|
3891
|
-
state.initialValue = value;
|
|
3892
4028
|
state.isOpen = false;
|
|
3893
4029
|
state.focusedIndex = -1;
|
|
3894
|
-
|
|
3895
|
-
onchange(value);
|
|
4030
|
+
return value; // Return value to be handled in view
|
|
3896
4031
|
}
|
|
3897
4032
|
else if (!state.isOpen) {
|
|
3898
4033
|
state.isOpen = true;
|
|
3899
|
-
state.focusedIndex = 0;
|
|
4034
|
+
state.focusedIndex = availableItems.length > 0 ? 0 : -1;
|
|
3900
4035
|
}
|
|
3901
4036
|
break;
|
|
3902
4037
|
case 'Escape':
|
|
@@ -3907,15 +4042,36 @@ const Dropdown = () => {
|
|
|
3907
4042
|
}
|
|
3908
4043
|
};
|
|
3909
4044
|
return {
|
|
3910
|
-
oninit: ({ attrs
|
|
3911
|
-
|
|
3912
|
-
state.
|
|
3913
|
-
//
|
|
4045
|
+
oninit: ({ attrs }) => {
|
|
4046
|
+
var _a;
|
|
4047
|
+
state.id = ((_a = attrs.id) === null || _a === void 0 ? void 0 : _a.toString()) || uniqueId();
|
|
4048
|
+
// Initialize internal state for uncontrolled mode
|
|
4049
|
+
if (!isControlled(attrs)) {
|
|
4050
|
+
state.internalCheckedId = attrs.defaultCheckedId;
|
|
4051
|
+
}
|
|
4052
|
+
// Add global click listener to close dropdown
|
|
4053
|
+
document.addEventListener('click', closeDropdown);
|
|
4054
|
+
},
|
|
4055
|
+
onremove: () => {
|
|
4056
|
+
// Cleanup global listener
|
|
4057
|
+
document.removeEventListener('click', closeDropdown);
|
|
3914
4058
|
},
|
|
3915
|
-
view: ({ attrs
|
|
3916
|
-
const {
|
|
3917
|
-
const
|
|
3918
|
-
|
|
4059
|
+
view: ({ attrs }) => {
|
|
4060
|
+
const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
|
|
4061
|
+
const controlled = isControlled(attrs);
|
|
4062
|
+
const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
|
|
4063
|
+
const handleSelection = (value) => {
|
|
4064
|
+
// Update internal state for uncontrolled mode
|
|
4065
|
+
if (!controlled) {
|
|
4066
|
+
state.internalCheckedId = value;
|
|
4067
|
+
}
|
|
4068
|
+
// Call onchange if provided
|
|
4069
|
+
if (onchange) {
|
|
4070
|
+
onchange(value);
|
|
4071
|
+
}
|
|
4072
|
+
};
|
|
4073
|
+
const selectedItem = currentCheckedId
|
|
4074
|
+
? items.filter((i) => (i.id ? i.id === currentCheckedId : i.label === currentCheckedId)).shift()
|
|
3919
4075
|
: undefined;
|
|
3920
4076
|
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
3921
4077
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
@@ -3923,13 +4079,12 @@ const Dropdown = () => {
|
|
|
3923
4079
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3924
4080
|
m(HelperText, { helperText }),
|
|
3925
4081
|
m('.select-wrapper', {
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
|
|
4082
|
+
onkeydown: disabled ? undefined : (e) => {
|
|
4083
|
+
const selectedValue = handleKeyDown(e, items);
|
|
4084
|
+
if (selectedValue) {
|
|
4085
|
+
handleSelection(selectedValue);
|
|
4086
|
+
}
|
|
4087
|
+
},
|
|
3933
4088
|
tabindex: disabled ? -1 : 0,
|
|
3934
4089
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
3935
4090
|
'aria-haspopup': 'listbox',
|
|
@@ -3946,7 +4101,8 @@ const Dropdown = () => {
|
|
|
3946
4101
|
e.stopPropagation();
|
|
3947
4102
|
if (!disabled) {
|
|
3948
4103
|
state.isOpen = !state.isOpen;
|
|
3949
|
-
|
|
4104
|
+
// Reset focus index when opening/closing
|
|
4105
|
+
state.focusedIndex = -1;
|
|
3950
4106
|
}
|
|
3951
4107
|
},
|
|
3952
4108
|
}),
|
|
@@ -3962,36 +4118,31 @@ const Dropdown = () => {
|
|
|
3962
4118
|
onremove: () => {
|
|
3963
4119
|
state.dropdownRef = null;
|
|
3964
4120
|
},
|
|
3965
|
-
style: getDropdownStyles(state.inputRef, true, items
|
|
3966
|
-
|
|
3967
|
-
group: undefined }))), true),
|
|
3968
|
-
}, items.map((item, index) => {
|
|
4121
|
+
style: getDropdownStyles(state.inputRef, true, items, true),
|
|
4122
|
+
}, items.map((item) => {
|
|
3969
4123
|
if (item.divider) {
|
|
3970
|
-
return m('li.divider'
|
|
3971
|
-
key: `divider-${index}`,
|
|
3972
|
-
});
|
|
4124
|
+
return m('li.divider');
|
|
3973
4125
|
}
|
|
3974
4126
|
const itemIndex = availableItems.indexOf(item);
|
|
3975
4127
|
const isFocused = itemIndex === state.focusedIndex;
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
4128
|
+
const className = [
|
|
4129
|
+
item.disabled ? 'disabled' : '',
|
|
4130
|
+
isFocused ? 'focused' : '',
|
|
4131
|
+
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
4132
|
+
]
|
|
4133
|
+
.filter(Boolean)
|
|
4134
|
+
.join(' ') || undefined;
|
|
4135
|
+
return m('li', {
|
|
4136
|
+
className,
|
|
4137
|
+
onclick: item.disabled
|
|
4138
|
+
? undefined
|
|
4139
|
+
: () => {
|
|
3987
4140
|
const value = (item.id || item.label);
|
|
3988
|
-
state.initialValue = value;
|
|
3989
4141
|
state.isOpen = false;
|
|
3990
4142
|
state.focusedIndex = -1;
|
|
3991
|
-
|
|
3992
|
-
onchange(value);
|
|
4143
|
+
handleSelection(value);
|
|
3993
4144
|
},
|
|
3994
|
-
|
|
4145
|
+
}, m('span', {
|
|
3995
4146
|
style: {
|
|
3996
4147
|
display: 'flex',
|
|
3997
4148
|
alignItems: 'center',
|
|
@@ -5128,9 +5279,9 @@ const TimePicker = () => {
|
|
|
5128
5279
|
dx: 0,
|
|
5129
5280
|
dy: 0,
|
|
5130
5281
|
};
|
|
5131
|
-
// Handle
|
|
5132
|
-
if (attrs.
|
|
5133
|
-
updateTimeFromInput(attrs.
|
|
5282
|
+
// Handle value after options are set
|
|
5283
|
+
if (attrs.defaultValue) {
|
|
5284
|
+
updateTimeFromInput(attrs.defaultValue);
|
|
5134
5285
|
}
|
|
5135
5286
|
},
|
|
5136
5287
|
onremove: () => {
|
|
@@ -5412,32 +5563,47 @@ const RadioButton = () => ({
|
|
|
5412
5563
|
},
|
|
5413
5564
|
});
|
|
5414
5565
|
/** Component to show a list of radio buttons, from which you can choose one. */
|
|
5415
|
-
// export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
|
|
5416
5566
|
const RadioButtons = () => {
|
|
5417
|
-
const state = {
|
|
5567
|
+
const state = {
|
|
5568
|
+
groupId: uniqueId(),
|
|
5569
|
+
componentId: '',
|
|
5570
|
+
internalCheckedId: undefined,
|
|
5571
|
+
};
|
|
5572
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5418
5573
|
return {
|
|
5419
|
-
oninit: ({ attrs
|
|
5420
|
-
state.
|
|
5421
|
-
state
|
|
5422
|
-
|
|
5574
|
+
oninit: ({ attrs }) => {
|
|
5575
|
+
state.componentId = attrs.id || uniqueId();
|
|
5576
|
+
// Initialize internal state for uncontrolled mode
|
|
5577
|
+
if (!isControlled(attrs)) {
|
|
5578
|
+
state.internalCheckedId = attrs.defaultCheckedId;
|
|
5579
|
+
}
|
|
5423
5580
|
},
|
|
5424
|
-
view: ({ attrs
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
const
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5581
|
+
view: ({ attrs }) => {
|
|
5582
|
+
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange, } = attrs;
|
|
5583
|
+
const { groupId, componentId } = state;
|
|
5584
|
+
const controlled = isControlled(attrs);
|
|
5585
|
+
// Get current checked ID from props or internal state
|
|
5586
|
+
const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
|
|
5587
|
+
const handleChange = (id) => {
|
|
5588
|
+
// Update internal state for uncontrolled mode
|
|
5589
|
+
if (!controlled) {
|
|
5590
|
+
state.internalCheckedId = id;
|
|
5591
|
+
}
|
|
5592
|
+
// Call onchange if provided
|
|
5593
|
+
if (onchange) {
|
|
5594
|
+
onchange(id);
|
|
5433
5595
|
}
|
|
5434
5596
|
};
|
|
5435
5597
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5436
|
-
const
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
:
|
|
5440
|
-
|
|
5598
|
+
const radioItems = options.map((r) => ({
|
|
5599
|
+
component: (RadioButton),
|
|
5600
|
+
props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}` }),
|
|
5601
|
+
key: r.id,
|
|
5602
|
+
}));
|
|
5603
|
+
const optionsContent = m(OptionsList, {
|
|
5604
|
+
options: radioItems,
|
|
5605
|
+
layout,
|
|
5606
|
+
});
|
|
5441
5607
|
return m('div', { id: componentId, className: cn }, [
|
|
5442
5608
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
5443
5609
|
description && m('p.helper-text', m.trust(description)),
|
|
@@ -5452,30 +5618,42 @@ const Select = () => {
|
|
|
5452
5618
|
const state = {
|
|
5453
5619
|
id: '',
|
|
5454
5620
|
isOpen: false,
|
|
5455
|
-
selectedIds: [],
|
|
5456
5621
|
focusedIndex: -1,
|
|
5457
5622
|
inputRef: null,
|
|
5458
5623
|
dropdownRef: null,
|
|
5624
|
+
internalSelectedIds: [],
|
|
5459
5625
|
};
|
|
5626
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
|
|
5460
5627
|
const isSelected = (id, selectedIds) => {
|
|
5461
5628
|
return selectedIds.some((selectedId) => selectedId === id);
|
|
5462
5629
|
};
|
|
5463
5630
|
const toggleOption = (id, multiple, attrs) => {
|
|
5631
|
+
const controlled = isControlled(attrs);
|
|
5632
|
+
// Get current selected IDs from props or internal state
|
|
5633
|
+
const currentSelectedIds = controlled
|
|
5634
|
+
? attrs.checkedId !== undefined
|
|
5635
|
+
? Array.isArray(attrs.checkedId)
|
|
5636
|
+
? attrs.checkedId
|
|
5637
|
+
: [attrs.checkedId]
|
|
5638
|
+
: []
|
|
5639
|
+
: state.internalSelectedIds;
|
|
5640
|
+
let newIds;
|
|
5464
5641
|
if (multiple) {
|
|
5465
|
-
|
|
5466
|
-
?
|
|
5467
|
-
|
|
5468
|
-
: [...state.selectedIds, id];
|
|
5469
|
-
state.selectedIds = newIds;
|
|
5470
|
-
attrs.onchange(newIds);
|
|
5471
|
-
console.log(newIds);
|
|
5472
|
-
// Keep dropdown open for multiple select
|
|
5642
|
+
newIds = currentSelectedIds.includes(id)
|
|
5643
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
5644
|
+
: [...currentSelectedIds, id];
|
|
5473
5645
|
}
|
|
5474
5646
|
else {
|
|
5475
|
-
|
|
5476
|
-
// Close dropdown for single select
|
|
5477
|
-
|
|
5478
|
-
|
|
5647
|
+
newIds = [id];
|
|
5648
|
+
state.isOpen = false; // Close dropdown for single select
|
|
5649
|
+
}
|
|
5650
|
+
// Update internal state for uncontrolled mode
|
|
5651
|
+
if (!controlled) {
|
|
5652
|
+
state.internalSelectedIds = newIds;
|
|
5653
|
+
}
|
|
5654
|
+
// Call onchange if provided
|
|
5655
|
+
if (attrs.onchange) {
|
|
5656
|
+
attrs.onchange(newIds);
|
|
5479
5657
|
}
|
|
5480
5658
|
};
|
|
5481
5659
|
const handleKeyDown = (e, attrs) => {
|
|
@@ -5527,88 +5705,22 @@ const Select = () => {
|
|
|
5527
5705
|
};
|
|
5528
5706
|
const closeDropdown = (e) => {
|
|
5529
5707
|
const target = e.target;
|
|
5530
|
-
if (!target.closest('.select-
|
|
5708
|
+
if (!target.closest('.input-field.select-space')) {
|
|
5531
5709
|
state.isOpen = false;
|
|
5532
5710
|
m.redraw();
|
|
5533
5711
|
}
|
|
5534
5712
|
};
|
|
5535
|
-
const renderGroupedOptions = (options, multiple, attrs) => {
|
|
5536
|
-
const groupedOptions = {};
|
|
5537
|
-
const ungroupedOptions = [];
|
|
5538
|
-
// Group options by their group property
|
|
5539
|
-
options.forEach((option) => {
|
|
5540
|
-
if (option.group) {
|
|
5541
|
-
if (!groupedOptions[option.group]) {
|
|
5542
|
-
groupedOptions[option.group] = [];
|
|
5543
|
-
}
|
|
5544
|
-
groupedOptions[option.group].push(option);
|
|
5545
|
-
}
|
|
5546
|
-
else {
|
|
5547
|
-
ungroupedOptions.push(option);
|
|
5548
|
-
}
|
|
5549
|
-
});
|
|
5550
|
-
const renderElements = [];
|
|
5551
|
-
// Render ungrouped options first
|
|
5552
|
-
ungroupedOptions.forEach((option) => {
|
|
5553
|
-
renderElements.push(m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === options.indexOf(option) ? 'focused' : '' }, (option.disabled
|
|
5554
|
-
? {}
|
|
5555
|
-
: {
|
|
5556
|
-
onclick: (e) => {
|
|
5557
|
-
e.stopPropagation();
|
|
5558
|
-
toggleOption(option.id, multiple, attrs);
|
|
5559
|
-
},
|
|
5560
|
-
})), m('span', multiple
|
|
5561
|
-
? m('label', { for: option.id }, m('input', {
|
|
5562
|
-
id: option.id,
|
|
5563
|
-
type: 'checkbox',
|
|
5564
|
-
checked: state.selectedIds.includes(option.id),
|
|
5565
|
-
disabled: option.disabled ? true : undefined,
|
|
5566
|
-
onclick: (e) => {
|
|
5567
|
-
e.stopPropagation();
|
|
5568
|
-
},
|
|
5569
|
-
}), m('span', option.label))
|
|
5570
|
-
: option.label)));
|
|
5571
|
-
});
|
|
5572
|
-
// Render grouped options
|
|
5573
|
-
Object.keys(groupedOptions).forEach((groupName) => {
|
|
5574
|
-
// Add group header
|
|
5575
|
-
renderElements.push(m('li.optgroup', { tabindex: 0 }, m('span', groupName)));
|
|
5576
|
-
// Add group options
|
|
5577
|
-
groupedOptions[groupName].forEach((option) => {
|
|
5578
|
-
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
|
|
5579
|
-
? {}
|
|
5580
|
-
: {
|
|
5581
|
-
onclick: (e) => {
|
|
5582
|
-
e.stopPropagation();
|
|
5583
|
-
toggleOption(option.id, multiple, attrs);
|
|
5584
|
-
},
|
|
5585
|
-
})), m('span', multiple
|
|
5586
|
-
? m('label', { for: option.id }, m('input', {
|
|
5587
|
-
id: option.id,
|
|
5588
|
-
type: 'checkbox',
|
|
5589
|
-
checked: state.selectedIds.includes(option.id),
|
|
5590
|
-
disabled: option.disabled ? true : undefined,
|
|
5591
|
-
onclick: (e) => {
|
|
5592
|
-
e.stopPropagation();
|
|
5593
|
-
},
|
|
5594
|
-
}), m('span', option.label))
|
|
5595
|
-
: option.label)));
|
|
5596
|
-
});
|
|
5597
|
-
});
|
|
5598
|
-
return renderElements;
|
|
5599
|
-
};
|
|
5600
5713
|
return {
|
|
5601
5714
|
oninit: ({ attrs }) => {
|
|
5602
|
-
|
|
5603
|
-
state
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
}
|
|
5715
|
+
state.id = attrs.id || uniqueId();
|
|
5716
|
+
// Initialize internal state for uncontrolled mode
|
|
5717
|
+
if (!isControlled(attrs)) {
|
|
5718
|
+
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
5719
|
+
? Array.isArray(attrs.defaultCheckedId)
|
|
5720
|
+
? attrs.defaultCheckedId
|
|
5721
|
+
: [attrs.defaultCheckedId]
|
|
5722
|
+
: [];
|
|
5723
|
+
state.internalSelectedIds = defaultIds;
|
|
5612
5724
|
}
|
|
5613
5725
|
// Add global click listener to close dropdown
|
|
5614
5726
|
document.addEventListener('click', closeDropdown);
|
|
@@ -5618,17 +5730,18 @@ const Select = () => {
|
|
|
5618
5730
|
document.removeEventListener('click', closeDropdown);
|
|
5619
5731
|
},
|
|
5620
5732
|
view: ({ attrs }) => {
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5733
|
+
const controlled = isControlled(attrs);
|
|
5734
|
+
// Get selected IDs from props or internal state
|
|
5735
|
+
const selectedIds = controlled
|
|
5736
|
+
? attrs.checkedId !== undefined
|
|
5737
|
+
? Array.isArray(attrs.checkedId)
|
|
5738
|
+
? attrs.checkedId
|
|
5739
|
+
: [attrs.checkedId]
|
|
5740
|
+
: []
|
|
5741
|
+
: state.internalSelectedIds;
|
|
5629
5742
|
const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
|
|
5630
5743
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
5631
|
-
const selectedOptions = options.filter((opt) => isSelected(opt.id,
|
|
5744
|
+
const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
5632
5745
|
return m('.input-field.select-space', {
|
|
5633
5746
|
className: finalClassName,
|
|
5634
5747
|
key,
|
|
@@ -5637,11 +5750,6 @@ const Select = () => {
|
|
|
5637
5750
|
// Icon prefix
|
|
5638
5751
|
iconName && m('i.material-icons.prefix', iconName),
|
|
5639
5752
|
m('.select-wrapper', {
|
|
5640
|
-
onclick: disabled
|
|
5641
|
-
? undefined
|
|
5642
|
-
: () => {
|
|
5643
|
-
state.isOpen = !state.isOpen;
|
|
5644
|
-
},
|
|
5645
5753
|
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
|
|
5646
5754
|
tabindex: disabled ? -1 : 0,
|
|
5647
5755
|
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
@@ -5657,7 +5765,9 @@ const Select = () => {
|
|
|
5657
5765
|
onclick: (e) => {
|
|
5658
5766
|
e.preventDefault();
|
|
5659
5767
|
e.stopPropagation();
|
|
5660
|
-
|
|
5768
|
+
if (!disabled) {
|
|
5769
|
+
state.isOpen = !state.isOpen;
|
|
5770
|
+
}
|
|
5661
5771
|
},
|
|
5662
5772
|
}),
|
|
5663
5773
|
// Dropdown Menu
|
|
@@ -5673,7 +5783,63 @@ const Select = () => {
|
|
|
5673
5783
|
style: getDropdownStyles(state.inputRef, true, options),
|
|
5674
5784
|
}, [
|
|
5675
5785
|
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
5676
|
-
|
|
5786
|
+
// Render ungrouped options first
|
|
5787
|
+
options
|
|
5788
|
+
.filter((option) => !option.group)
|
|
5789
|
+
.map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
|
|
5790
|
+
? 'disabled'
|
|
5791
|
+
: state.focusedIndex === options.indexOf(option)
|
|
5792
|
+
? 'focused'
|
|
5793
|
+
: '' }, (option.disabled
|
|
5794
|
+
? {}
|
|
5795
|
+
: {
|
|
5796
|
+
onclick: (e) => {
|
|
5797
|
+
e.stopPropagation();
|
|
5798
|
+
toggleOption(option.id, multiple, attrs);
|
|
5799
|
+
},
|
|
5800
|
+
})), m('span', multiple
|
|
5801
|
+
? m('label', { for: option.id }, m('input', {
|
|
5802
|
+
id: option.id,
|
|
5803
|
+
type: 'checkbox',
|
|
5804
|
+
checked: selectedIds.includes(option.id),
|
|
5805
|
+
disabled: option.disabled ? true : undefined,
|
|
5806
|
+
onclick: (e) => {
|
|
5807
|
+
e.stopPropagation();
|
|
5808
|
+
},
|
|
5809
|
+
}), m('span', option.label))
|
|
5810
|
+
: option.label))),
|
|
5811
|
+
// Render grouped options
|
|
5812
|
+
Object.entries(options
|
|
5813
|
+
.filter((option) => option.group)
|
|
5814
|
+
.reduce((groups, option) => {
|
|
5815
|
+
const group = option.group;
|
|
5816
|
+
if (!groups[group])
|
|
5817
|
+
groups[group] = [];
|
|
5818
|
+
groups[group].push(option);
|
|
5819
|
+
return groups;
|
|
5820
|
+
}, {}))
|
|
5821
|
+
.map(([groupName, groupOptions]) => [
|
|
5822
|
+
m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
|
|
5823
|
+
...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
5824
|
+
? {}
|
|
5825
|
+
: {
|
|
5826
|
+
onclick: (e) => {
|
|
5827
|
+
e.stopPropagation();
|
|
5828
|
+
toggleOption(option.id, multiple, attrs);
|
|
5829
|
+
},
|
|
5830
|
+
})), m('span', multiple
|
|
5831
|
+
? m('label', { for: option.id }, m('input', {
|
|
5832
|
+
id: option.id,
|
|
5833
|
+
type: 'checkbox',
|
|
5834
|
+
checked: selectedIds.includes(option.id),
|
|
5835
|
+
disabled: option.disabled ? true : undefined,
|
|
5836
|
+
onclick: (e) => {
|
|
5837
|
+
e.stopPropagation();
|
|
5838
|
+
},
|
|
5839
|
+
}), m('span', option.label))
|
|
5840
|
+
: option.label))),
|
|
5841
|
+
])
|
|
5842
|
+
.reduce((acc, val) => acc.concat(val), []),
|
|
5677
5843
|
]),
|
|
5678
5844
|
m(MaterialIcon, {
|
|
5679
5845
|
name: 'caret',
|
|
@@ -5704,25 +5870,22 @@ const Switch = () => {
|
|
|
5704
5870
|
},
|
|
5705
5871
|
view: ({ attrs }) => {
|
|
5706
5872
|
const id = attrs.id || state.id;
|
|
5707
|
-
const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
|
|
5873
|
+
const { checked, label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["checked", "label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
|
|
5708
5874
|
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
5709
5875
|
return m('div', {
|
|
5710
5876
|
className: cn,
|
|
5711
5877
|
onclick: (e) => {
|
|
5712
|
-
|
|
5713
|
-
onchange && onchange(state.checked);
|
|
5878
|
+
onchange && onchange(!checked);
|
|
5714
5879
|
e.preventDefault();
|
|
5715
5880
|
},
|
|
5716
5881
|
}, [
|
|
5717
5882
|
label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
|
|
5718
|
-
m('.switch', params, m('label',
|
|
5719
|
-
style: { cursor: 'pointer' },
|
|
5720
|
-
}, [
|
|
5883
|
+
m('.switch', params, m('label', [
|
|
5721
5884
|
m('span', left || 'Off'),
|
|
5722
5885
|
m('input[type=checkbox]', {
|
|
5723
5886
|
id,
|
|
5724
5887
|
disabled,
|
|
5725
|
-
checked
|
|
5888
|
+
checked,
|
|
5726
5889
|
}),
|
|
5727
5890
|
m('span.lever'),
|
|
5728
5891
|
m('span', right || 'On'),
|
|
@@ -5914,22 +6077,62 @@ const Tabs = () => {
|
|
|
5914
6077
|
};
|
|
5915
6078
|
};
|
|
5916
6079
|
|
|
6080
|
+
// Proper components to avoid anonymous closures
|
|
6081
|
+
const SelectedChip = {
|
|
6082
|
+
view: ({ attrs: { option, onRemove } }) => m('.chip', [
|
|
6083
|
+
option.label || option.id.toString(),
|
|
6084
|
+
m(MaterialIcon, {
|
|
6085
|
+
name: 'close',
|
|
6086
|
+
className: 'close',
|
|
6087
|
+
onclick: (e) => {
|
|
6088
|
+
e.stopPropagation();
|
|
6089
|
+
onRemove(option.id);
|
|
6090
|
+
},
|
|
6091
|
+
}),
|
|
6092
|
+
]),
|
|
6093
|
+
};
|
|
6094
|
+
const DropdownOption = {
|
|
6095
|
+
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
|
|
6096
|
+
const checkboxId = `search-select-option-${option.id}`;
|
|
6097
|
+
const optionLabel = option.label || option.id.toString();
|
|
6098
|
+
return m('li', {
|
|
6099
|
+
key: option.id,
|
|
6100
|
+
onclick: (e) => {
|
|
6101
|
+
e.preventDefault();
|
|
6102
|
+
e.stopPropagation();
|
|
6103
|
+
onToggle(option);
|
|
6104
|
+
},
|
|
6105
|
+
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
6106
|
+
onmouseover: () => {
|
|
6107
|
+
if (!option.disabled) {
|
|
6108
|
+
onMouseOver(index);
|
|
6109
|
+
}
|
|
6110
|
+
},
|
|
6111
|
+
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
6112
|
+
m('input', {
|
|
6113
|
+
type: 'checkbox',
|
|
6114
|
+
id: checkboxId,
|
|
6115
|
+
checked: selectedIds.includes(option.id),
|
|
6116
|
+
}),
|
|
6117
|
+
m('span', optionLabel),
|
|
6118
|
+
]));
|
|
6119
|
+
},
|
|
6120
|
+
};
|
|
5917
6121
|
/**
|
|
5918
6122
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
5919
6123
|
*/
|
|
5920
6124
|
const SearchSelect = () => {
|
|
5921
|
-
// (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
|
|
5922
6125
|
// State initialization
|
|
5923
6126
|
const state = {
|
|
6127
|
+
id: '',
|
|
5924
6128
|
isOpen: false,
|
|
5925
|
-
selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
|
|
5926
6129
|
searchTerm: '',
|
|
5927
|
-
options: [],
|
|
5928
6130
|
inputRef: null,
|
|
5929
6131
|
dropdownRef: null,
|
|
5930
6132
|
focusedIndex: -1,
|
|
5931
|
-
|
|
6133
|
+
internalSelectedIds: [],
|
|
5932
6134
|
};
|
|
6135
|
+
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
5933
6136
|
const componentId = uniqueId();
|
|
5934
6137
|
const searchInputId = `${componentId}-search`;
|
|
5935
6138
|
// Handle click outside
|
|
@@ -5970,9 +6173,7 @@ const SearchSelect = () => {
|
|
|
5970
6173
|
// Handle add new option
|
|
5971
6174
|
return 'addNew';
|
|
5972
6175
|
}
|
|
5973
|
-
else if (state.focusedIndex < filteredOptions.length)
|
|
5974
|
-
toggleOption(filteredOptions[state.focusedIndex]);
|
|
5975
|
-
}
|
|
6176
|
+
else if (state.focusedIndex < filteredOptions.length) ;
|
|
5976
6177
|
}
|
|
5977
6178
|
break;
|
|
5978
6179
|
case 'Escape':
|
|
@@ -5984,26 +6185,65 @@ const SearchSelect = () => {
|
|
|
5984
6185
|
return null;
|
|
5985
6186
|
};
|
|
5986
6187
|
// Toggle option selection
|
|
5987
|
-
const toggleOption = (option) => {
|
|
6188
|
+
const toggleOption = (option, attrs) => {
|
|
5988
6189
|
if (option.disabled)
|
|
5989
6190
|
return;
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
6191
|
+
const controlled = isControlled(attrs);
|
|
6192
|
+
// Get current selected IDs from props or internal state
|
|
6193
|
+
const currentSelectedIds = controlled
|
|
6194
|
+
? attrs.checkedId !== undefined
|
|
6195
|
+
? Array.isArray(attrs.checkedId)
|
|
6196
|
+
? attrs.checkedId
|
|
6197
|
+
: [attrs.checkedId]
|
|
6198
|
+
: []
|
|
6199
|
+
: state.internalSelectedIds;
|
|
6200
|
+
const newIds = currentSelectedIds.includes(option.id)
|
|
6201
|
+
? currentSelectedIds.filter((id) => id !== option.id)
|
|
6202
|
+
: [...currentSelectedIds, option.id];
|
|
6203
|
+
// Update internal state for uncontrolled mode
|
|
6204
|
+
if (!controlled) {
|
|
6205
|
+
state.internalSelectedIds = newIds;
|
|
6206
|
+
}
|
|
5993
6207
|
state.searchTerm = '';
|
|
5994
6208
|
state.focusedIndex = -1;
|
|
5995
|
-
|
|
6209
|
+
// Call onchange if provided
|
|
6210
|
+
if (attrs.onchange) {
|
|
6211
|
+
attrs.onchange(newIds);
|
|
6212
|
+
}
|
|
5996
6213
|
};
|
|
5997
6214
|
// Remove a selected option
|
|
5998
|
-
const removeOption = (
|
|
5999
|
-
|
|
6000
|
-
|
|
6215
|
+
const removeOption = (optionId, attrs) => {
|
|
6216
|
+
const controlled = isControlled(attrs);
|
|
6217
|
+
// Get current selected IDs from props or internal state
|
|
6218
|
+
const currentSelectedIds = controlled
|
|
6219
|
+
? attrs.checkedId !== undefined
|
|
6220
|
+
? Array.isArray(attrs.checkedId)
|
|
6221
|
+
? attrs.checkedId
|
|
6222
|
+
: [attrs.checkedId]
|
|
6223
|
+
: []
|
|
6224
|
+
: state.internalSelectedIds;
|
|
6225
|
+
const newIds = currentSelectedIds.filter((id) => id !== optionId);
|
|
6226
|
+
// Update internal state for uncontrolled mode
|
|
6227
|
+
if (!controlled) {
|
|
6228
|
+
state.internalSelectedIds = newIds;
|
|
6229
|
+
}
|
|
6230
|
+
// Call onchange if provided
|
|
6231
|
+
if (attrs.onchange) {
|
|
6232
|
+
attrs.onchange(newIds);
|
|
6233
|
+
}
|
|
6001
6234
|
};
|
|
6002
6235
|
return {
|
|
6003
|
-
oninit: ({ attrs
|
|
6004
|
-
state.
|
|
6005
|
-
state
|
|
6006
|
-
|
|
6236
|
+
oninit: ({ attrs }) => {
|
|
6237
|
+
state.id = attrs.id || uniqueId();
|
|
6238
|
+
// Initialize internal state for uncontrolled mode
|
|
6239
|
+
if (!isControlled(attrs)) {
|
|
6240
|
+
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
6241
|
+
? Array.isArray(attrs.defaultCheckedId)
|
|
6242
|
+
? attrs.defaultCheckedId
|
|
6243
|
+
: [attrs.defaultCheckedId]
|
|
6244
|
+
: [];
|
|
6245
|
+
state.internalSelectedIds = defaultIds;
|
|
6246
|
+
}
|
|
6007
6247
|
},
|
|
6008
6248
|
oncreate() {
|
|
6009
6249
|
document.addEventListener('click', handleClickOutside);
|
|
@@ -6011,14 +6251,27 @@ const SearchSelect = () => {
|
|
|
6011
6251
|
onremove() {
|
|
6012
6252
|
document.removeEventListener('click', handleClickOutside);
|
|
6013
6253
|
},
|
|
6014
|
-
view({ attrs
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6254
|
+
view({ attrs }) {
|
|
6255
|
+
const controlled = isControlled(attrs);
|
|
6256
|
+
// Get selected IDs from props or internal state
|
|
6257
|
+
const selectedIds = controlled
|
|
6258
|
+
? attrs.checkedId !== undefined
|
|
6259
|
+
? Array.isArray(attrs.checkedId)
|
|
6260
|
+
? attrs.checkedId
|
|
6261
|
+
: [attrs.checkedId]
|
|
6262
|
+
: []
|
|
6263
|
+
: state.internalSelectedIds;
|
|
6264
|
+
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
|
|
6265
|
+
// Use i18n values if provided, otherwise use defaults
|
|
6266
|
+
const texts = {
|
|
6267
|
+
noOptionsFound: i18n.noOptionsFound || noOptionsFound,
|
|
6268
|
+
addNewPrefix: i18n.addNewPrefix || '+',
|
|
6269
|
+
};
|
|
6270
|
+
// Get selected options for display
|
|
6271
|
+
const selectedOptions = options.filter((opt) => selectedIds.includes(opt.id));
|
|
6019
6272
|
// Safely filter options
|
|
6020
|
-
const filteredOptions =
|
|
6021
|
-
!
|
|
6273
|
+
const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
6274
|
+
!selectedIds.includes(option.id));
|
|
6022
6275
|
// Check if we should show the "add new option" element
|
|
6023
6276
|
const showAddNew = oncreateNewOption &&
|
|
6024
6277
|
state.searchTerm &&
|
|
@@ -6036,6 +6289,7 @@ const SearchSelect = () => {
|
|
|
6036
6289
|
state.isOpen = !state.isOpen;
|
|
6037
6290
|
// console.log('SearchSelect state changed to', state.isOpen); // Debug log
|
|
6038
6291
|
},
|
|
6292
|
+
class: 'chips chips-container',
|
|
6039
6293
|
style: {
|
|
6040
6294
|
display: 'flex',
|
|
6041
6295
|
alignItems: 'end',
|
|
@@ -6048,25 +6302,20 @@ const SearchSelect = () => {
|
|
|
6048
6302
|
// Hidden input for label association and accessibility
|
|
6049
6303
|
m('input', {
|
|
6050
6304
|
type: 'text',
|
|
6051
|
-
id:
|
|
6052
|
-
value:
|
|
6305
|
+
id: state.id,
|
|
6306
|
+
value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
|
|
6053
6307
|
readonly: true,
|
|
6308
|
+
class: 'sr-only',
|
|
6054
6309
|
style: { position: 'absolute', left: '-9999px', opacity: 0 },
|
|
6055
6310
|
}),
|
|
6056
6311
|
// Selected Options (chips)
|
|
6057
|
-
...
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
onclick: (e) => {
|
|
6063
|
-
e.stopPropagation();
|
|
6064
|
-
removeOption(option);
|
|
6065
|
-
},
|
|
6066
|
-
}),
|
|
6067
|
-
])),
|
|
6312
|
+
...selectedOptions.map((option) => m(SelectedChip, {
|
|
6313
|
+
// key: option.id,
|
|
6314
|
+
option,
|
|
6315
|
+
onRemove: (id) => removeOption(id, attrs),
|
|
6316
|
+
})),
|
|
6068
6317
|
// Placeholder when no options selected
|
|
6069
|
-
|
|
6318
|
+
selectedOptions.length === 0 &&
|
|
6070
6319
|
placeholder &&
|
|
6071
6320
|
m('span.placeholder', {
|
|
6072
6321
|
style: {
|
|
@@ -6087,8 +6336,8 @@ const SearchSelect = () => {
|
|
|
6087
6336
|
// Label
|
|
6088
6337
|
label &&
|
|
6089
6338
|
m('label', {
|
|
6090
|
-
for:
|
|
6091
|
-
class: placeholder ||
|
|
6339
|
+
for: state.id,
|
|
6340
|
+
class: placeholder || selectedOptions.length > 0 ? 'active' : '',
|
|
6092
6341
|
}, label),
|
|
6093
6342
|
// Dropdown Menu
|
|
6094
6343
|
state.isOpen &&
|
|
@@ -6104,7 +6353,6 @@ const SearchSelect = () => {
|
|
|
6104
6353
|
m('li', // Search Input
|
|
6105
6354
|
{
|
|
6106
6355
|
class: 'search-wrapper',
|
|
6107
|
-
style: { padding: '0 16px', position: 'relative' },
|
|
6108
6356
|
}, [
|
|
6109
6357
|
m('input', {
|
|
6110
6358
|
type: 'text',
|
|
@@ -6123,29 +6371,21 @@ const SearchSelect = () => {
|
|
|
6123
6371
|
const result = handleKeyDown(e, filteredOptions, !!showAddNew);
|
|
6124
6372
|
if (result === 'addNew' && oncreateNewOption) {
|
|
6125
6373
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6126
|
-
toggleOption(option);
|
|
6374
|
+
toggleOption(option, attrs);
|
|
6375
|
+
}
|
|
6376
|
+
else if (e.key === 'Enter' &&
|
|
6377
|
+
state.focusedIndex >= 0 &&
|
|
6378
|
+
state.focusedIndex < filteredOptions.length) {
|
|
6379
|
+
toggleOption(filteredOptions[state.focusedIndex], attrs);
|
|
6127
6380
|
}
|
|
6128
6381
|
},
|
|
6129
|
-
|
|
6130
|
-
width: '100%',
|
|
6131
|
-
outline: 'none',
|
|
6132
|
-
fontSize: '0.875rem',
|
|
6133
|
-
border: 'none',
|
|
6134
|
-
padding: '8px 0',
|
|
6135
|
-
borderBottom: '1px solid var(--mm-input-border, #9e9e9e)',
|
|
6136
|
-
backgroundColor: 'transparent',
|
|
6137
|
-
color: 'var(--mm-text-primary, inherit)',
|
|
6138
|
-
},
|
|
6382
|
+
class: 'search-select-input',
|
|
6139
6383
|
}),
|
|
6140
6384
|
]),
|
|
6141
6385
|
// No options found message or list of options
|
|
6142
6386
|
...(filteredOptions.length === 0 && !showAddNew
|
|
6143
6387
|
? [
|
|
6144
|
-
m('li',
|
|
6145
|
-
// {
|
|
6146
|
-
// style: getNoOptionsStyles(),
|
|
6147
|
-
// },
|
|
6148
|
-
noOptionsFound),
|
|
6388
|
+
m('li.search-select-no-options', texts.noOptionsFound),
|
|
6149
6389
|
]
|
|
6150
6390
|
: []),
|
|
6151
6391
|
// Add new option item
|
|
@@ -6154,35 +6394,27 @@ const SearchSelect = () => {
|
|
|
6154
6394
|
m('li', {
|
|
6155
6395
|
onclick: async () => {
|
|
6156
6396
|
const option = await oncreateNewOption(state.searchTerm);
|
|
6157
|
-
toggleOption(option);
|
|
6397
|
+
toggleOption(option, attrs);
|
|
6158
6398
|
},
|
|
6159
6399
|
class: state.focusedIndex === filteredOptions.length ? 'active' : '',
|
|
6160
6400
|
onmouseover: () => {
|
|
6161
6401
|
state.focusedIndex = filteredOptions.length;
|
|
6162
6402
|
},
|
|
6163
|
-
}, [m('span',
|
|
6403
|
+
}, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
|
|
6164
6404
|
]
|
|
6165
6405
|
: []),
|
|
6166
6406
|
// List of filtered options
|
|
6167
|
-
...filteredOptions.map((option, index) => m(
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
state.focusedIndex = index;
|
|
6177
|
-
}
|
|
6407
|
+
...filteredOptions.map((option, index) => m(DropdownOption, {
|
|
6408
|
+
// key: option.id,
|
|
6409
|
+
option,
|
|
6410
|
+
index,
|
|
6411
|
+
selectedIds,
|
|
6412
|
+
isFocused: state.focusedIndex === index,
|
|
6413
|
+
onToggle: (opt) => toggleOption(opt, attrs),
|
|
6414
|
+
onMouseOver: (idx) => {
|
|
6415
|
+
state.focusedIndex = idx;
|
|
6178
6416
|
},
|
|
6179
|
-
},
|
|
6180
|
-
m('input', {
|
|
6181
|
-
type: 'checkbox',
|
|
6182
|
-
checked: state.selectedOptions.some((selected) => selected.id === option.id),
|
|
6183
|
-
}),
|
|
6184
|
-
option.label || option.id.toString(),
|
|
6185
|
-
]))),
|
|
6417
|
+
})),
|
|
6186
6418
|
]),
|
|
6187
6419
|
]);
|
|
6188
6420
|
},
|
|
@@ -8302,6 +8534,7 @@ exports.MaterialIcon = MaterialIcon;
|
|
|
8302
8534
|
exports.ModalPanel = ModalPanel;
|
|
8303
8535
|
exports.NumberInput = NumberInput;
|
|
8304
8536
|
exports.Options = Options;
|
|
8537
|
+
exports.OptionsList = OptionsList;
|
|
8305
8538
|
exports.Pagination = Pagination;
|
|
8306
8539
|
exports.PaginationControls = PaginationControls;
|
|
8307
8540
|
exports.Parallax = Parallax;
|