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