mithril-materialized 3.1.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,21 +2947,20 @@ 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;
2922
- }
2923
- };
2950
+ // const lengthUpdateHandler = () => {
2951
+ // const length = state.inputElement?.value.length;
2952
+ // if (length) {
2953
+ // state.currentLength = length;
2954
+ // state.hasInteracted = length > 0;
2955
+ // }
2956
+ // };
2924
2957
  const clearInput = (oninput, onchange) => {
2925
2958
  if (state.inputElement) {
2926
2959
  state.inputElement.value = '';
2927
2960
  state.inputElement.focus();
2928
2961
  state.active = false;
2929
- state.currentLength = 0;
2930
- state.hasInteracted = false;
2962
+ // state.currentLength = 0;
2963
+ // state.hasInteracted = false;
2931
2964
  // Trigger oninput and onchange callbacks
2932
2965
  const value = getValue(state.inputElement);
2933
2966
  if (oninput) {
@@ -2945,9 +2978,26 @@ const InputField = (type, defaultClass = '') => () => {
2945
2978
  // Range slider helper functions
2946
2979
  // Range slider rendering functions are now in separate module
2947
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
+ },
2948
2998
  view: ({ attrs }) => {
2949
2999
  var _a, _b;
2950
- const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, initialValue, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate, canClear } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "initialValue", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate", "canClear"]);
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"]);
2951
3001
  // const attributes = toAttrs(params);
2952
3002
  const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
2953
3003
  const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
@@ -2964,10 +3014,14 @@ const InputField = (type, defaultClass = '') => () => {
2964
3014
  isMandatory,
2965
3015
  helperText }));
2966
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;
2967
3021
  return m('.input-field', { className: cn, style }, [
2968
3022
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
2969
3023
  m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
2970
- 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
2971
3025
  ? {
2972
3026
  height: attrs.height || '200px',
2973
3027
  width: '6px',
@@ -2981,25 +3035,9 @@ const InputField = (type, defaultClass = '') => () => {
2981
3035
  if (focus(attrs)) {
2982
3036
  input.focus();
2983
3037
  }
2984
- // Set initial value if provided
2985
- if (initialValue) {
2986
- input.value = String(initialValue);
2987
- }
2988
- // Update character count state for counter component
2989
- if (maxLength) {
2990
- state.currentLength = input.value.length; // Initial count
2991
- }
2992
- // Range input functionality
2993
- if (type === 'range' && !attrs.minmax) {
2994
- const updateThumb = () => {
2995
- const value = input.value;
2996
- const min = input.min || '0';
2997
- const max = input.max || '100';
2998
- const percentage = ((parseFloat(value) - parseFloat(min)) / (parseFloat(max) - parseFloat(min))) * 100;
2999
- input.style.setProperty('--range-progress', `${percentage}%`);
3000
- };
3001
- input.addEventListener('input', updateThumb);
3002
- updateThumb(); // Initial position
3038
+ // For uncontrolled mode, set initial value only
3039
+ if (!controlled && attrs.defaultValue !== undefined) {
3040
+ input.value = String(attrs.defaultValue);
3003
3041
  }
3004
3042
  }, onkeyup: onkeyup
3005
3043
  ? (ev) => {
@@ -3013,21 +3051,25 @@ const InputField = (type, defaultClass = '') => () => {
3013
3051
  ? (ev) => {
3014
3052
  onkeypress(ev, getValue(ev.target));
3015
3053
  }
3016
- : undefined, onupdate: validate
3017
- ? ({ dom }) => {
3018
- const target = dom;
3019
- setValidity(target, validate(getValue(target), target));
3020
- }
3021
3054
  : undefined, oninput: (e) => {
3022
3055
  state.active = true;
3056
+ state.hasInteracted = false;
3023
3057
  const target = e.target;
3024
3058
  // Handle original oninput logic
3025
- const value = getValue(target);
3059
+ const inputValue = getValue(target);
3060
+ // Update internal state for uncontrolled mode
3061
+ if (!controlled) {
3062
+ state.internalValue = inputValue;
3063
+ }
3026
3064
  if (oninput) {
3027
- oninput(value);
3065
+ oninput(inputValue);
3028
3066
  }
3029
- if (maxLength) {
3030
- 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}%`);
3031
3073
  }
3032
3074
  // Don't validate on input, only clear error states if user is typing
3033
3075
  if (validate && target.classList.contains('invalid') && target.value.length > 0) {
@@ -3043,6 +3085,14 @@ const InputField = (type, defaultClass = '') => () => {
3043
3085
  state.isValid = true;
3044
3086
  }
3045
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
+ }
3046
3096
  }, onfocus: () => {
3047
3097
  state.active = true;
3048
3098
  }, onblur: (e) => {
@@ -3079,6 +3129,48 @@ const InputField = (type, defaultClass = '') => () => {
3079
3129
  state.isValid = true;
3080
3130
  }
3081
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
+ }
3082
3174
  // Also call the original onblur handler if provided
3083
3175
  if (attrs.onblur) {
3084
3176
  attrs.onblur(e);
@@ -3104,18 +3196,18 @@ const InputField = (type, defaultClass = '') => () => {
3104
3196
  id,
3105
3197
  isMandatory,
3106
3198
  isActive,
3107
- initialValue: initialValue !== undefined,
3199
+ initialValue: value !== undefined && value !== '',
3108
3200
  }),
3109
3201
  m(HelperText, {
3110
3202
  helperText,
3111
3203
  dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
3112
3204
  dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
3113
3205
  }),
3114
- maxLength
3206
+ maxLength && typeof value === 'string'
3115
3207
  ? m(CharacterCounter, {
3116
- currentLength: state.currentLength,
3208
+ currentLength: value.length,
3117
3209
  maxLength,
3118
- show: state.currentLength > 0,
3210
+ show: value.length > 0,
3119
3211
  })
3120
3212
  : undefined,
3121
3213
  ]);
@@ -3142,7 +3234,7 @@ const FileInput = () => {
3142
3234
  let i;
3143
3235
  return {
3144
3236
  view: ({ attrs }) => {
3145
- 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;
3146
3238
  const accept = acceptedFiles
3147
3239
  ? acceptedFiles instanceof Array
3148
3240
  ? acceptedFiles.join(', ')
@@ -3173,8 +3265,8 @@ const FileInput = () => {
3173
3265
  placeholder,
3174
3266
  oncreate: ({ dom }) => {
3175
3267
  i = dom;
3176
- if (initialValue)
3177
- i.value = initialValue;
3268
+ if (value)
3269
+ i.value = value;
3178
3270
  },
3179
3271
  })),
3180
3272
  (canClear || (i === null || i === void 0 ? void 0 : i.value)) &&
@@ -3201,11 +3293,14 @@ const FileInput = () => {
3201
3293
 
3202
3294
  /** Component to show a check box */
3203
3295
  const InputCheckbox = () => {
3296
+ let checkboxId;
3204
3297
  return {
3205
3298
  view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
3206
- const checkboxId = inputId || uniqueId();
3299
+ if (!checkboxId)
3300
+ checkboxId = inputId || uniqueId();
3207
3301
  return m(`p`, { className, style }, m('label', { for: checkboxId }, [
3208
3302
  m('input[type=checkbox][tabindex=0]', {
3303
+ className: disabled ? 'disabled' : undefined,
3209
3304
  id: checkboxId,
3210
3305
  checked,
3211
3306
  disabled,
@@ -3222,78 +3317,79 @@ const InputCheckbox = () => {
3222
3317
  },
3223
3318
  };
3224
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
+ };
3225
3329
  /** A list of checkboxes */
3226
3330
  const Options = () => {
3227
- const state = {};
3228
- const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
3229
- const selectAll = (options, callback) => {
3331
+ const state = {
3332
+ componentId: '',
3333
+ };
3334
+ const selectAll = (options, onchange) => {
3230
3335
  const allIds = options.map((option) => option.id);
3231
- state.checkedIds = [...allIds];
3232
- if (callback)
3233
- callback(allIds);
3336
+ onchange && onchange(allIds);
3337
+ };
3338
+ const selectNone = (onchange) => {
3339
+ onchange && onchange([]);
3234
3340
  };
3235
- const selectNone = (callback) => {
3236
- state.checkedIds = [];
3237
- if (callback)
3238
- callback([]);
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);
3239
3347
  };
3240
3348
  return {
3241
- oninit: ({ attrs: { initialValue, checkedId, id } }) => {
3242
- const iv = checkedId || initialValue;
3243
- state.checkedId = checkedId;
3244
- state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
3245
- state.componentId = id || uniqueId();
3349
+ oninit: ({ attrs }) => {
3350
+ state.componentId = attrs.id || uniqueId();
3246
3351
  },
3247
- view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
3248
- const onchange = callback
3249
- ? (propId, checked) => {
3250
- const checkedIds = state.checkedIds.filter((i) => i !== propId);
3251
- if (checked) {
3252
- checkedIds.push(propId);
3253
- }
3254
- state.checkedIds = checkedIds;
3255
- callback(checkedIds);
3256
- }
3257
- : 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);
3258
3356
  const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
3259
- const optionsContent = layout === 'horizontal'
3260
- ? m('div.grid-container', options.map((option) => m(InputCheckbox, {
3261
- disabled: disabled || option.disabled,
3262
- label: option.label,
3263
- onchange: onchange ? (v) => onchange(option.id, v) : undefined,
3264
- className: option.className || checkboxClass,
3265
- checked: isChecked(option.id),
3266
- description: option.description,
3267
- inputId: `${state.componentId}-${option.id}`,
3268
- })))
3269
- : options.map((option) => m(InputCheckbox, {
3357
+ const optionItems = options.map((option) => ({
3358
+ component: InputCheckbox,
3359
+ props: {
3270
3360
  disabled: disabled || option.disabled,
3271
3361
  label: option.label,
3272
- onchange: onchange ? (v) => onchange(option.id, v) : undefined,
3362
+ onchange: onchange ? (v) => handleChange(option.id, v, checkedIds, onchange) : undefined,
3273
3363
  className: option.className || checkboxClass,
3274
3364
  checked: isChecked(option.id),
3275
3365
  description: option.description,
3276
3366
  inputId: `${state.componentId}-${option.id}`,
3277
- }));
3367
+ },
3368
+ key: option.id,
3369
+ }));
3370
+ const optionsContent = m(OptionsList, {
3371
+ options: optionItems,
3372
+ layout,
3373
+ });
3278
3374
  return m('div', { id: state.componentId, className: cn, style }, [
3279
3375
  label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
3280
3376
  showSelectAll &&
3281
- m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
3377
+ m('div.select-all-controls', { style: { marginBottom: '10px' } }, [
3282
3378
  m('a', {
3283
3379
  href: '#',
3284
3380
  onclick: (e) => {
3285
3381
  e.preventDefault();
3286
- selectAll(options, callback);
3382
+ selectAll(options, onchange);
3287
3383
  },
3288
- style: 'margin-right: 15px;',
3289
- }, 'Select All'),
3384
+ style: { marginRight: '15px' },
3385
+ }, selectAllText),
3290
3386
  m('a', {
3291
3387
  href: '#',
3292
3388
  onclick: (e) => {
3293
3389
  e.preventDefault();
3294
- selectNone(callback);
3390
+ selectNone(onchange);
3295
3391
  },
3296
- }, 'Select None'),
3392
+ }, selectNoneText),
3297
3393
  ]),
3298
3394
  description && m(HelperText, { helperText: description }),
3299
3395
  m('form', { action: '#' }, optionsContent),
@@ -3888,65 +3984,96 @@ const DataTable = () => {
3888
3984
  const Dropdown = () => {
3889
3985
  const state = {
3890
3986
  isOpen: false,
3891
- initialValue: undefined,
3892
3987
  id: '',
3893
3988
  focusedIndex: -1,
3894
3989
  inputRef: null,
3895
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
+ }
3896
4000
  };
3897
- const handleKeyDown = (e, items, onchange) => {
4001
+ const handleKeyDown = (e, items) => {
3898
4002
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
3899
4003
  switch (e.key) {
3900
4004
  case 'ArrowDown':
3901
4005
  e.preventDefault();
3902
4006
  if (!state.isOpen) {
3903
4007
  state.isOpen = true;
3904
- state.focusedIndex = 0;
4008
+ state.focusedIndex = availableItems.length > 0 ? 0 : -1;
3905
4009
  }
3906
4010
  else {
3907
- state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
4011
+ state.focusedIndex = (state.focusedIndex + 1) % availableItems.length;
3908
4012
  }
3909
- break;
4013
+ return undefined;
3910
4014
  case 'ArrowUp':
3911
4015
  e.preventDefault();
3912
4016
  if (state.isOpen) {
3913
- state.focusedIndex = Math.max(state.focusedIndex - 1, 0);
4017
+ state.focusedIndex = state.focusedIndex <= 0 ? availableItems.length - 1 : state.focusedIndex - 1;
3914
4018
  }
3915
- break;
4019
+ return undefined;
3916
4020
  case 'Enter':
3917
4021
  case ' ':
3918
4022
  e.preventDefault();
3919
4023
  if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
3920
4024
  const selectedItem = availableItems[state.focusedIndex];
3921
4025
  const value = (selectedItem.id || selectedItem.label);
3922
- state.initialValue = value;
3923
4026
  state.isOpen = false;
3924
4027
  state.focusedIndex = -1;
3925
- if (onchange)
3926
- onchange(value);
4028
+ return value;
3927
4029
  }
3928
4030
  else if (!state.isOpen) {
3929
4031
  state.isOpen = true;
3930
- state.focusedIndex = 0;
4032
+ state.focusedIndex = availableItems.length > 0 ? 0 : -1;
3931
4033
  }
3932
- break;
4034
+ return undefined;
3933
4035
  case 'Escape':
3934
4036
  e.preventDefault();
3935
4037
  state.isOpen = false;
3936
4038
  state.focusedIndex = -1;
3937
- break;
4039
+ return undefined;
4040
+ default:
4041
+ return undefined;
3938
4042
  }
3939
4043
  };
3940
4044
  return {
3941
- oninit: ({ attrs: { id = uniqueId(), initialValue, checkedId } }) => {
3942
- state.id = id;
3943
- state.initialValue = initialValue || checkedId;
3944
- // Mithril will handle click events through the component structure
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);
3945
4054
  },
3946
- view: ({ attrs: { key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12' }, }) => {
3947
- const { initialValue } = state;
3948
- const selectedItem = initialValue
3949
- ? items.filter((i) => (i.id ? i.id === initialValue : i.label === initialValue)).shift()
4055
+ onremove: () => {
4056
+ // Cleanup global listener
4057
+ document.removeEventListener('click', closeDropdown);
4058
+ },
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
4075
+ .filter((i) => (i.id ? i.id === currentCheckedId : i.label === currentCheckedId))
4076
+ .shift()
3950
4077
  : undefined;
3951
4078
  const title = selectedItem ? selectedItem.label : label || 'Select';
3952
4079
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
@@ -3954,13 +4081,14 @@ const Dropdown = () => {
3954
4081
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
3955
4082
  m(HelperText, { helperText }),
3956
4083
  m('.select-wrapper', {
3957
- onclick: disabled
4084
+ onkeydown: disabled
3958
4085
  ? undefined
3959
- : () => {
3960
- state.isOpen = !state.isOpen;
3961
- state.focusedIndex = state.isOpen ? 0 : -1;
4086
+ : (e) => {
4087
+ const selectedValue = handleKeyDown(e, items);
4088
+ if (selectedValue) {
4089
+ handleSelection(selectedValue);
4090
+ }
3962
4091
  },
3963
- onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
3964
4092
  tabindex: disabled ? -1 : 0,
3965
4093
  'aria-expanded': state.isOpen ? 'true' : 'false',
3966
4094
  'aria-haspopup': 'listbox',
@@ -3977,7 +4105,8 @@ const Dropdown = () => {
3977
4105
  e.stopPropagation();
3978
4106
  if (!disabled) {
3979
4107
  state.isOpen = !state.isOpen;
3980
- state.focusedIndex = state.isOpen ? 0 : -1;
4108
+ // Reset focus index when opening/closing
4109
+ state.focusedIndex = -1;
3981
4110
  }
3982
4111
  },
3983
4112
  }),
@@ -3993,36 +4122,31 @@ const Dropdown = () => {
3993
4122
  onremove: () => {
3994
4123
  state.dropdownRef = null;
3995
4124
  },
3996
- style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
3997
- // Convert dropdown items to format expected by getDropdownStyles
3998
- group: undefined }))), true),
3999
- }, items.map((item, index) => {
4125
+ style: getDropdownStyles(state.inputRef, true, items, true),
4126
+ }, items.map((item) => {
4000
4127
  if (item.divider) {
4001
- return m('li.divider', {
4002
- key: `divider-${index}`,
4003
- });
4128
+ return m('li.divider');
4004
4129
  }
4005
4130
  const itemIndex = availableItems.indexOf(item);
4006
4131
  const isFocused = itemIndex === state.focusedIndex;
4007
- return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
4008
- item.disabled ? 'disabled' : '',
4009
- isFocused ? 'focused' : '',
4010
- (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
4011
- ]
4012
- .filter(Boolean)
4013
- .join(' ') }, (item.disabled
4014
- ? {}
4015
- : {
4016
- onclick: (e) => {
4017
- e.stopPropagation();
4132
+ const className = [
4133
+ item.disabled ? 'disabled' : '',
4134
+ isFocused ? 'focused' : '',
4135
+ (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
4136
+ ]
4137
+ .filter(Boolean)
4138
+ .join(' ') || undefined;
4139
+ return m('li', {
4140
+ className,
4141
+ onclick: item.disabled
4142
+ ? undefined
4143
+ : () => {
4018
4144
  const value = (item.id || item.label);
4019
- state.initialValue = value;
4020
4145
  state.isOpen = false;
4021
4146
  state.focusedIndex = -1;
4022
- if (onchange)
4023
- onchange(value);
4147
+ handleSelection(value);
4024
4148
  },
4025
- })), m('span', {
4149
+ }, m('span', {
4026
4150
  style: {
4027
4151
  display: 'flex',
4028
4152
  alignItems: 'center',
@@ -5159,9 +5283,9 @@ const TimePicker = () => {
5159
5283
  dx: 0,
5160
5284
  dy: 0,
5161
5285
  };
5162
- // Handle initial value after options are set
5163
- if (attrs.initialValue) {
5164
- updateTimeFromInput(attrs.initialValue);
5286
+ // Handle value after options are set
5287
+ if (attrs.defaultValue) {
5288
+ updateTimeFromInput(attrs.defaultValue);
5165
5289
  }
5166
5290
  },
5167
5291
  onremove: () => {
@@ -5443,32 +5567,47 @@ const RadioButton = () => ({
5443
5567
  },
5444
5568
  });
5445
5569
  /** Component to show a list of radio buttons, from which you can choose one. */
5446
- // export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
5447
5570
  const RadioButtons = () => {
5448
- const state = { groupId: uniqueId() };
5571
+ const state = {
5572
+ groupId: uniqueId(),
5573
+ componentId: '',
5574
+ internalCheckedId: undefined,
5575
+ };
5576
+ const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
5449
5577
  return {
5450
- oninit: ({ attrs: { checkedId, initialValue, id } }) => {
5451
- state.oldCheckedId = checkedId;
5452
- state.checkedId = checkedId || initialValue;
5453
- state.componentId = id || uniqueId();
5578
+ oninit: ({ attrs }) => {
5579
+ state.componentId = attrs.id || uniqueId();
5580
+ // Initialize internal state for uncontrolled mode
5581
+ if (!isControlled(attrs)) {
5582
+ state.internalCheckedId = attrs.defaultCheckedId;
5583
+ }
5454
5584
  },
5455
- view: ({ attrs: { checkedId: cid, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange: callback, }, }) => {
5456
- if (state.oldCheckedId !== cid) {
5457
- state.oldCheckedId = state.checkedId = cid;
5458
- }
5459
- const { groupId, checkedId, componentId } = state;
5460
- const onchange = (propId) => {
5461
- state.checkedId = propId;
5462
- if (callback) {
5463
- callback(propId);
5585
+ view: ({ attrs }) => {
5586
+ const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange, } = attrs;
5587
+ const { groupId, componentId } = state;
5588
+ const controlled = isControlled(attrs);
5589
+ // Get current checked ID from props or internal state
5590
+ const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
5591
+ const handleChange = (id) => {
5592
+ // Update internal state for uncontrolled mode
5593
+ if (!controlled) {
5594
+ state.internalCheckedId = id;
5595
+ }
5596
+ // Call onchange if provided
5597
+ if (onchange) {
5598
+ onchange(id);
5464
5599
  }
5465
5600
  };
5466
5601
  const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
5467
- const optionsContent = layout === 'horizontal'
5468
- ? m('div.grid-container', options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
5469
- groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` }))))
5470
- : options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
5471
- groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` })));
5602
+ const radioItems = options.map((r) => ({
5603
+ component: (RadioButton),
5604
+ props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}` }),
5605
+ key: r.id,
5606
+ }));
5607
+ const optionsContent = m(OptionsList, {
5608
+ options: radioItems,
5609
+ layout,
5610
+ });
5472
5611
  return m('div', { id: componentId, className: cn }, [
5473
5612
  label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
5474
5613
  description && m('p.helper-text', m.trust(description)),
@@ -5483,30 +5622,42 @@ const Select = () => {
5483
5622
  const state = {
5484
5623
  id: '',
5485
5624
  isOpen: false,
5486
- selectedIds: [],
5487
5625
  focusedIndex: -1,
5488
5626
  inputRef: null,
5489
5627
  dropdownRef: null,
5628
+ internalSelectedIds: [],
5490
5629
  };
5630
+ const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
5491
5631
  const isSelected = (id, selectedIds) => {
5492
5632
  return selectedIds.some((selectedId) => selectedId === id);
5493
5633
  };
5494
5634
  const toggleOption = (id, multiple, attrs) => {
5635
+ const controlled = isControlled(attrs);
5636
+ // Get current selected IDs from props or internal state
5637
+ const currentSelectedIds = controlled
5638
+ ? attrs.checkedId !== undefined
5639
+ ? Array.isArray(attrs.checkedId)
5640
+ ? attrs.checkedId
5641
+ : [attrs.checkedId]
5642
+ : []
5643
+ : state.internalSelectedIds;
5644
+ let newIds;
5495
5645
  if (multiple) {
5496
- const newIds = state.selectedIds.includes(id)
5497
- ? // isSelected(id, state.selectedIds)
5498
- state.selectedIds.filter((selectedId) => selectedId !== id)
5499
- : [...state.selectedIds, id];
5500
- state.selectedIds = newIds;
5501
- attrs.onchange(newIds);
5502
- console.log(newIds);
5503
- // Keep dropdown open for multiple select
5646
+ newIds = currentSelectedIds.includes(id)
5647
+ ? currentSelectedIds.filter((selectedId) => selectedId !== id)
5648
+ : [...currentSelectedIds, id];
5504
5649
  }
5505
5650
  else {
5506
- state.selectedIds = [id];
5507
- // Close dropdown for single select
5508
- state.isOpen = false;
5509
- attrs.onchange([id]);
5651
+ newIds = [id];
5652
+ state.isOpen = false; // Close dropdown for single select
5653
+ }
5654
+ // Update internal state for uncontrolled mode
5655
+ if (!controlled) {
5656
+ state.internalSelectedIds = newIds;
5657
+ }
5658
+ // Call onchange if provided
5659
+ if (attrs.onchange) {
5660
+ attrs.onchange(newIds);
5510
5661
  }
5511
5662
  };
5512
5663
  const handleKeyDown = (e, attrs) => {
@@ -5558,88 +5709,22 @@ const Select = () => {
5558
5709
  };
5559
5710
  const closeDropdown = (e) => {
5560
5711
  const target = e.target;
5561
- if (!target.closest('.select-wrapper-container')) {
5712
+ if (!target.closest('.input-field.select-space')) {
5562
5713
  state.isOpen = false;
5563
5714
  m.redraw();
5564
5715
  }
5565
5716
  };
5566
- const renderGroupedOptions = (options, multiple, attrs) => {
5567
- const groupedOptions = {};
5568
- const ungroupedOptions = [];
5569
- // Group options by their group property
5570
- options.forEach((option) => {
5571
- if (option.group) {
5572
- if (!groupedOptions[option.group]) {
5573
- groupedOptions[option.group] = [];
5574
- }
5575
- groupedOptions[option.group].push(option);
5576
- }
5577
- else {
5578
- ungroupedOptions.push(option);
5579
- }
5580
- });
5581
- const renderElements = [];
5582
- // Render ungrouped options first
5583
- ungroupedOptions.forEach((option) => {
5584
- renderElements.push(m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === options.indexOf(option) ? 'focused' : '' }, (option.disabled
5585
- ? {}
5586
- : {
5587
- onclick: (e) => {
5588
- e.stopPropagation();
5589
- toggleOption(option.id, multiple, attrs);
5590
- },
5591
- })), m('span', multiple
5592
- ? m('label', { for: option.id }, m('input', {
5593
- id: option.id,
5594
- type: 'checkbox',
5595
- checked: state.selectedIds.includes(option.id),
5596
- disabled: option.disabled ? true : undefined,
5597
- onclick: (e) => {
5598
- e.stopPropagation();
5599
- },
5600
- }), m('span', option.label))
5601
- : option.label)));
5602
- });
5603
- // Render grouped options
5604
- Object.keys(groupedOptions).forEach((groupName) => {
5605
- // Add group header
5606
- renderElements.push(m('li.optgroup', { tabindex: 0 }, m('span', groupName)));
5607
- // Add group options
5608
- groupedOptions[groupName].forEach((option) => {
5609
- 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
5610
- ? {}
5611
- : {
5612
- onclick: (e) => {
5613
- e.stopPropagation();
5614
- toggleOption(option.id, multiple, attrs);
5615
- },
5616
- })), m('span', multiple
5617
- ? m('label', { for: option.id }, m('input', {
5618
- id: option.id,
5619
- type: 'checkbox',
5620
- checked: state.selectedIds.includes(option.id),
5621
- disabled: option.disabled ? true : undefined,
5622
- onclick: (e) => {
5623
- e.stopPropagation();
5624
- },
5625
- }), m('span', option.label))
5626
- : option.label)));
5627
- });
5628
- });
5629
- return renderElements;
5630
- };
5631
5717
  return {
5632
5718
  oninit: ({ attrs }) => {
5633
- const { checkedId, initialValue, id } = attrs;
5634
- state.id = id || uniqueId();
5635
- const iv = checkedId || initialValue;
5636
- if (iv !== null && typeof iv !== 'undefined') {
5637
- if (iv instanceof Array) {
5638
- state.selectedIds = [...iv];
5639
- }
5640
- else {
5641
- state.selectedIds = [iv];
5642
- }
5719
+ state.id = attrs.id || uniqueId();
5720
+ // Initialize internal state for uncontrolled mode
5721
+ if (!isControlled(attrs)) {
5722
+ const defaultIds = attrs.defaultCheckedId !== undefined
5723
+ ? Array.isArray(attrs.defaultCheckedId)
5724
+ ? attrs.defaultCheckedId
5725
+ : [attrs.defaultCheckedId]
5726
+ : [];
5727
+ state.internalSelectedIds = defaultIds;
5643
5728
  }
5644
5729
  // Add global click listener to close dropdown
5645
5730
  document.addEventListener('click', closeDropdown);
@@ -5649,17 +5734,18 @@ const Select = () => {
5649
5734
  document.removeEventListener('click', closeDropdown);
5650
5735
  },
5651
5736
  view: ({ attrs }) => {
5652
- // Sync external checkedId prop with internal state - do this in view for immediate response
5653
- const { checkedId } = attrs;
5654
- if (checkedId !== undefined) {
5655
- const newIds = checkedId instanceof Array ? checkedId : [checkedId];
5656
- if (JSON.stringify(newIds) !== JSON.stringify(state.selectedIds)) {
5657
- state.selectedIds = newIds;
5658
- }
5659
- }
5737
+ const controlled = isControlled(attrs);
5738
+ // Get selected IDs from props or internal state
5739
+ const selectedIds = controlled
5740
+ ? attrs.checkedId !== undefined
5741
+ ? Array.isArray(attrs.checkedId)
5742
+ ? attrs.checkedId
5743
+ : [attrs.checkedId]
5744
+ : []
5745
+ : state.internalSelectedIds;
5660
5746
  const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
5661
5747
  const finalClassName = newRow ? `${className} clear` : className;
5662
- const selectedOptions = options.filter((opt) => isSelected(opt.id, state.selectedIds));
5748
+ const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
5663
5749
  return m('.input-field.select-space', {
5664
5750
  className: finalClassName,
5665
5751
  key,
@@ -5668,11 +5754,6 @@ const Select = () => {
5668
5754
  // Icon prefix
5669
5755
  iconName && m('i.material-icons.prefix', iconName),
5670
5756
  m('.select-wrapper', {
5671
- onclick: disabled
5672
- ? undefined
5673
- : () => {
5674
- state.isOpen = !state.isOpen;
5675
- },
5676
5757
  onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
5677
5758
  tabindex: disabled ? -1 : 0,
5678
5759
  'aria-expanded': state.isOpen ? 'true' : 'false',
@@ -5688,7 +5769,9 @@ const Select = () => {
5688
5769
  onclick: (e) => {
5689
5770
  e.preventDefault();
5690
5771
  e.stopPropagation();
5691
- state.isOpen = !state.isOpen;
5772
+ if (!disabled) {
5773
+ state.isOpen = !state.isOpen;
5774
+ }
5692
5775
  },
5693
5776
  }),
5694
5777
  // Dropdown Menu
@@ -5704,7 +5787,63 @@ const Select = () => {
5704
5787
  style: getDropdownStyles(state.inputRef, true, options),
5705
5788
  }, [
5706
5789
  placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
5707
- ...renderGroupedOptions(options, multiple, attrs),
5790
+ // Render ungrouped options first
5791
+ options
5792
+ .filter((option) => !option.group)
5793
+ .map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
5794
+ ? 'disabled'
5795
+ : state.focusedIndex === options.indexOf(option)
5796
+ ? 'focused'
5797
+ : '' }, (option.disabled
5798
+ ? {}
5799
+ : {
5800
+ onclick: (e) => {
5801
+ e.stopPropagation();
5802
+ toggleOption(option.id, multiple, attrs);
5803
+ },
5804
+ })), m('span', multiple
5805
+ ? m('label', { for: option.id }, m('input', {
5806
+ id: option.id,
5807
+ type: 'checkbox',
5808
+ checked: selectedIds.includes(option.id),
5809
+ disabled: option.disabled ? true : undefined,
5810
+ onclick: (e) => {
5811
+ e.stopPropagation();
5812
+ },
5813
+ }), m('span', option.label))
5814
+ : option.label))),
5815
+ // Render grouped options
5816
+ Object.entries(options
5817
+ .filter((option) => option.group)
5818
+ .reduce((groups, option) => {
5819
+ const group = option.group;
5820
+ if (!groups[group])
5821
+ groups[group] = [];
5822
+ groups[group].push(option);
5823
+ return groups;
5824
+ }, {}))
5825
+ .map(([groupName, groupOptions]) => [
5826
+ m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
5827
+ ...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
5828
+ ? {}
5829
+ : {
5830
+ onclick: (e) => {
5831
+ e.stopPropagation();
5832
+ toggleOption(option.id, multiple, attrs);
5833
+ },
5834
+ })), m('span', multiple
5835
+ ? m('label', { for: option.id }, m('input', {
5836
+ id: option.id,
5837
+ type: 'checkbox',
5838
+ checked: selectedIds.includes(option.id),
5839
+ disabled: option.disabled ? true : undefined,
5840
+ onclick: (e) => {
5841
+ e.stopPropagation();
5842
+ },
5843
+ }), m('span', option.label))
5844
+ : option.label))),
5845
+ ])
5846
+ .reduce((acc, val) => acc.concat(val), []),
5708
5847
  ]),
5709
5848
  m(MaterialIcon, {
5710
5849
  name: 'caret',
@@ -5735,25 +5874,22 @@ const Switch = () => {
5735
5874
  },
5736
5875
  view: ({ attrs }) => {
5737
5876
  const id = attrs.id || state.id;
5738
- const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
5877
+ 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"]);
5739
5878
  const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
5740
5879
  return m('div', {
5741
5880
  className: cn,
5742
5881
  onclick: (e) => {
5743
- state.checked = !state.checked;
5744
- onchange && onchange(state.checked);
5882
+ onchange && onchange(!checked);
5745
5883
  e.preventDefault();
5746
5884
  },
5747
5885
  }, [
5748
5886
  label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
5749
- m('.switch', params, m('label', {
5750
- style: { cursor: 'pointer' },
5751
- }, [
5887
+ m('.switch', params, m('label', [
5752
5888
  m('span', left || 'Off'),
5753
5889
  m('input[type=checkbox]', {
5754
5890
  id,
5755
5891
  disabled,
5756
- checked: state.checked,
5892
+ checked,
5757
5893
  }),
5758
5894
  m('span.lever'),
5759
5895
  m('span', right || 'On'),
@@ -5945,22 +6081,62 @@ const Tabs = () => {
5945
6081
  };
5946
6082
  };
5947
6083
 
6084
+ // Proper components to avoid anonymous closures
6085
+ const SelectedChip = {
6086
+ view: ({ attrs: { option, onRemove } }) => m('.chip', [
6087
+ option.label || option.id.toString(),
6088
+ m(MaterialIcon, {
6089
+ name: 'close',
6090
+ className: 'close',
6091
+ onclick: (e) => {
6092
+ e.stopPropagation();
6093
+ onRemove(option.id);
6094
+ },
6095
+ }),
6096
+ ]),
6097
+ };
6098
+ const DropdownOption = {
6099
+ view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
6100
+ const checkboxId = `search-select-option-${option.id}`;
6101
+ const optionLabel = option.label || option.id.toString();
6102
+ return m('li', {
6103
+ key: option.id,
6104
+ onclick: (e) => {
6105
+ e.preventDefault();
6106
+ e.stopPropagation();
6107
+ onToggle(option);
6108
+ },
6109
+ class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
6110
+ onmouseover: () => {
6111
+ if (!option.disabled) {
6112
+ onMouseOver(index);
6113
+ }
6114
+ },
6115
+ }, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
6116
+ m('input', {
6117
+ type: 'checkbox',
6118
+ id: checkboxId,
6119
+ checked: selectedIds.includes(option.id),
6120
+ }),
6121
+ m('span', optionLabel),
6122
+ ]));
6123
+ },
6124
+ };
5948
6125
  /**
5949
6126
  * Mithril Factory Component for Multi-Select Dropdown with search
5950
6127
  */
5951
6128
  const SearchSelect = () => {
5952
- // (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
5953
6129
  // State initialization
5954
6130
  const state = {
6131
+ id: '',
5955
6132
  isOpen: false,
5956
- selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
5957
6133
  searchTerm: '',
5958
- options: [],
5959
6134
  inputRef: null,
5960
6135
  dropdownRef: null,
5961
6136
  focusedIndex: -1,
5962
- onchange: null,
6137
+ internalSelectedIds: [],
5963
6138
  };
6139
+ const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
5964
6140
  const componentId = uniqueId();
5965
6141
  const searchInputId = `${componentId}-search`;
5966
6142
  // Handle click outside
@@ -6001,9 +6177,7 @@ const SearchSelect = () => {
6001
6177
  // Handle add new option
6002
6178
  return 'addNew';
6003
6179
  }
6004
- else if (state.focusedIndex < filteredOptions.length) {
6005
- toggleOption(filteredOptions[state.focusedIndex]);
6006
- }
6180
+ else if (state.focusedIndex < filteredOptions.length) ;
6007
6181
  }
6008
6182
  break;
6009
6183
  case 'Escape':
@@ -6015,26 +6189,65 @@ const SearchSelect = () => {
6015
6189
  return null;
6016
6190
  };
6017
6191
  // Toggle option selection
6018
- const toggleOption = (option) => {
6192
+ const toggleOption = (option, attrs) => {
6019
6193
  if (option.disabled)
6020
6194
  return;
6021
- state.selectedOptions = state.selectedOptions.some((item) => item.id === option.id)
6022
- ? state.selectedOptions.filter((item) => item.id !== option.id)
6023
- : [...state.selectedOptions, option];
6195
+ const controlled = isControlled(attrs);
6196
+ // Get current selected IDs from props or internal state
6197
+ const currentSelectedIds = controlled
6198
+ ? attrs.checkedId !== undefined
6199
+ ? Array.isArray(attrs.checkedId)
6200
+ ? attrs.checkedId
6201
+ : [attrs.checkedId]
6202
+ : []
6203
+ : state.internalSelectedIds;
6204
+ const newIds = currentSelectedIds.includes(option.id)
6205
+ ? currentSelectedIds.filter((id) => id !== option.id)
6206
+ : [...currentSelectedIds, option.id];
6207
+ // Update internal state for uncontrolled mode
6208
+ if (!controlled) {
6209
+ state.internalSelectedIds = newIds;
6210
+ }
6024
6211
  state.searchTerm = '';
6025
6212
  state.focusedIndex = -1;
6026
- state.onchange && state.onchange(state.selectedOptions.map((o) => o.id));
6213
+ // Call onchange if provided
6214
+ if (attrs.onchange) {
6215
+ attrs.onchange(newIds);
6216
+ }
6027
6217
  };
6028
6218
  // Remove a selected option
6029
- const removeOption = (option) => {
6030
- state.selectedOptions = state.selectedOptions.filter((item) => item.id !== option.id);
6031
- state.onchange && state.onchange(state.selectedOptions.map((o) => o.id));
6219
+ const removeOption = (optionId, attrs) => {
6220
+ const controlled = isControlled(attrs);
6221
+ // Get current selected IDs from props or internal state
6222
+ const currentSelectedIds = controlled
6223
+ ? attrs.checkedId !== undefined
6224
+ ? Array.isArray(attrs.checkedId)
6225
+ ? attrs.checkedId
6226
+ : [attrs.checkedId]
6227
+ : []
6228
+ : state.internalSelectedIds;
6229
+ const newIds = currentSelectedIds.filter((id) => id !== optionId);
6230
+ // Update internal state for uncontrolled mode
6231
+ if (!controlled) {
6232
+ state.internalSelectedIds = newIds;
6233
+ }
6234
+ // Call onchange if provided
6235
+ if (attrs.onchange) {
6236
+ attrs.onchange(newIds);
6237
+ }
6032
6238
  };
6033
6239
  return {
6034
- oninit: ({ attrs: { options = [], initialValue = [], onchange } }) => {
6035
- state.options = options;
6036
- state.selectedOptions = options.filter((o) => initialValue.includes(o.id));
6037
- state.onchange = onchange;
6240
+ oninit: ({ attrs }) => {
6241
+ state.id = attrs.id || uniqueId();
6242
+ // Initialize internal state for uncontrolled mode
6243
+ if (!isControlled(attrs)) {
6244
+ const defaultIds = attrs.defaultCheckedId !== undefined
6245
+ ? Array.isArray(attrs.defaultCheckedId)
6246
+ ? attrs.defaultCheckedId
6247
+ : [attrs.defaultCheckedId]
6248
+ : [];
6249
+ state.internalSelectedIds = defaultIds;
6250
+ }
6038
6251
  },
6039
6252
  oncreate() {
6040
6253
  document.addEventListener('click', handleClickOutside);
@@ -6042,14 +6255,27 @@ const SearchSelect = () => {
6042
6255
  onremove() {
6043
6256
  document.removeEventListener('click', handleClickOutside);
6044
6257
  },
6045
- view({ attrs: {
6046
- // onchange,
6047
- oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label,
6048
- // maxHeight = '25rem',
6049
- }, }) {
6258
+ view({ attrs }) {
6259
+ const controlled = isControlled(attrs);
6260
+ // Get selected IDs from props or internal state
6261
+ const selectedIds = controlled
6262
+ ? attrs.checkedId !== undefined
6263
+ ? Array.isArray(attrs.checkedId)
6264
+ ? attrs.checkedId
6265
+ : [attrs.checkedId]
6266
+ : []
6267
+ : state.internalSelectedIds;
6268
+ const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
6269
+ // Use i18n values if provided, otherwise use defaults
6270
+ const texts = {
6271
+ noOptionsFound: i18n.noOptionsFound || noOptionsFound,
6272
+ addNewPrefix: i18n.addNewPrefix || '+',
6273
+ };
6274
+ // Get selected options for display
6275
+ const selectedOptions = options.filter((opt) => selectedIds.includes(opt.id));
6050
6276
  // Safely filter options
6051
- const filteredOptions = state.options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
6052
- !state.selectedOptions.some((selected) => selected.id === option.id));
6277
+ const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
6278
+ !selectedIds.includes(option.id));
6053
6279
  // Check if we should show the "add new option" element
6054
6280
  const showAddNew = oncreateNewOption &&
6055
6281
  state.searchTerm &&
@@ -6067,6 +6293,7 @@ const SearchSelect = () => {
6067
6293
  state.isOpen = !state.isOpen;
6068
6294
  // console.log('SearchSelect state changed to', state.isOpen); // Debug log
6069
6295
  },
6296
+ class: 'chips chips-container',
6070
6297
  style: {
6071
6298
  display: 'flex',
6072
6299
  alignItems: 'end',
@@ -6079,25 +6306,20 @@ const SearchSelect = () => {
6079
6306
  // Hidden input for label association and accessibility
6080
6307
  m('input', {
6081
6308
  type: 'text',
6082
- id: componentId,
6083
- value: state.selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
6309
+ id: state.id,
6310
+ value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
6084
6311
  readonly: true,
6312
+ class: 'sr-only',
6085
6313
  style: { position: 'absolute', left: '-9999px', opacity: 0 },
6086
6314
  }),
6087
6315
  // Selected Options (chips)
6088
- ...state.selectedOptions.map((option) => m('.chip', [
6089
- option.label || option.id.toString(),
6090
- m(MaterialIcon, {
6091
- name: 'close',
6092
- className: 'close',
6093
- onclick: (e) => {
6094
- e.stopPropagation();
6095
- removeOption(option);
6096
- },
6097
- }),
6098
- ])),
6316
+ ...selectedOptions.map((option) => m(SelectedChip, {
6317
+ // key: option.id,
6318
+ option,
6319
+ onRemove: (id) => removeOption(id, attrs),
6320
+ })),
6099
6321
  // Placeholder when no options selected
6100
- state.selectedOptions.length === 0 &&
6322
+ selectedOptions.length === 0 &&
6101
6323
  placeholder &&
6102
6324
  m('span.placeholder', {
6103
6325
  style: {
@@ -6118,8 +6340,8 @@ const SearchSelect = () => {
6118
6340
  // Label
6119
6341
  label &&
6120
6342
  m('label', {
6121
- for: componentId,
6122
- class: placeholder || state.selectedOptions.length > 0 ? 'active' : '',
6343
+ for: state.id,
6344
+ class: placeholder || selectedOptions.length > 0 ? 'active' : '',
6123
6345
  }, label),
6124
6346
  // Dropdown Menu
6125
6347
  state.isOpen &&
@@ -6135,7 +6357,6 @@ const SearchSelect = () => {
6135
6357
  m('li', // Search Input
6136
6358
  {
6137
6359
  class: 'search-wrapper',
6138
- style: { padding: '0 16px', position: 'relative' },
6139
6360
  }, [
6140
6361
  m('input', {
6141
6362
  type: 'text',
@@ -6154,29 +6375,21 @@ const SearchSelect = () => {
6154
6375
  const result = handleKeyDown(e, filteredOptions, !!showAddNew);
6155
6376
  if (result === 'addNew' && oncreateNewOption) {
6156
6377
  const option = await oncreateNewOption(state.searchTerm);
6157
- toggleOption(option);
6378
+ toggleOption(option, attrs);
6379
+ }
6380
+ else if (e.key === 'Enter' &&
6381
+ state.focusedIndex >= 0 &&
6382
+ state.focusedIndex < filteredOptions.length) {
6383
+ toggleOption(filteredOptions[state.focusedIndex], attrs);
6158
6384
  }
6159
6385
  },
6160
- style: {
6161
- width: '100%',
6162
- outline: 'none',
6163
- fontSize: '0.875rem',
6164
- border: 'none',
6165
- padding: '8px 0',
6166
- borderBottom: '1px solid var(--mm-input-border, #9e9e9e)',
6167
- backgroundColor: 'transparent',
6168
- color: 'var(--mm-text-primary, inherit)',
6169
- },
6386
+ class: 'search-select-input',
6170
6387
  }),
6171
6388
  ]),
6172
6389
  // No options found message or list of options
6173
6390
  ...(filteredOptions.length === 0 && !showAddNew
6174
6391
  ? [
6175
- m('li',
6176
- // {
6177
- // style: getNoOptionsStyles(),
6178
- // },
6179
- noOptionsFound),
6392
+ m('li.search-select-no-options', texts.noOptionsFound),
6180
6393
  ]
6181
6394
  : []),
6182
6395
  // Add new option item
@@ -6185,35 +6398,27 @@ const SearchSelect = () => {
6185
6398
  m('li', {
6186
6399
  onclick: async () => {
6187
6400
  const option = await oncreateNewOption(state.searchTerm);
6188
- toggleOption(option);
6401
+ toggleOption(option, attrs);
6189
6402
  },
6190
6403
  class: state.focusedIndex === filteredOptions.length ? 'active' : '',
6191
6404
  onmouseover: () => {
6192
6405
  state.focusedIndex = filteredOptions.length;
6193
6406
  },
6194
- }, [m('span', `+ "${state.searchTerm}"`)]),
6407
+ }, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
6195
6408
  ]
6196
6409
  : []),
6197
6410
  // List of filtered options
6198
- ...filteredOptions.map((option, index) => m('li', {
6199
- onclick: (e) => {
6200
- e.preventDefault();
6201
- e.stopPropagation();
6202
- toggleOption(option);
6203
- },
6204
- class: `${option.disabled ? 'disabled' : ''} ${state.focusedIndex === index ? 'active' : ''}`.trim(),
6205
- onmouseover: () => {
6206
- if (!option.disabled) {
6207
- state.focusedIndex = index;
6208
- }
6411
+ ...filteredOptions.map((option, index) => m(DropdownOption, {
6412
+ // key: option.id,
6413
+ option,
6414
+ index,
6415
+ selectedIds,
6416
+ isFocused: state.focusedIndex === index,
6417
+ onToggle: (opt) => toggleOption(opt, attrs),
6418
+ onMouseOver: (idx) => {
6419
+ state.focusedIndex = idx;
6209
6420
  },
6210
- }, m('span', [
6211
- m('input', {
6212
- type: 'checkbox',
6213
- checked: state.selectedOptions.some((selected) => selected.id === option.id),
6214
- }),
6215
- option.label || option.id.toString(),
6216
- ]))),
6421
+ })),
6217
6422
  ]),
6218
6423
  ]);
6219
6424
  },
@@ -8296,4 +8501,4 @@ const isValidationError = (result) => !isValidationSuccess(result);
8296
8501
  // ============================================================================
8297
8502
  // All types are already exported via individual export declarations above
8298
8503
 
8299
- 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 };
8504
+ 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 };