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