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