mithril-materialized 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,20 +2951,57 @@
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;
2954
+ // const lengthUpdateHandler = () => {
2955
+ // const length = state.inputElement?.value.length;
2956
+ // if (length) {
2957
+ // state.currentLength = length;
2958
+ // state.hasInteracted = length > 0;
2959
+ // }
2960
+ // };
2961
+ const clearInput = (oninput, onchange) => {
2962
+ if (state.inputElement) {
2963
+ state.inputElement.value = '';
2964
+ state.inputElement.focus();
2965
+ state.active = false;
2966
+ // state.currentLength = 0;
2967
+ // state.hasInteracted = false;
2968
+ // Trigger oninput and onchange callbacks
2969
+ const value = getValue(state.inputElement);
2970
+ if (oninput) {
2971
+ oninput(value);
2972
+ }
2973
+ if (onchange) {
2974
+ onchange(value);
2975
+ }
2976
+ // Update validation state
2977
+ state.inputElement.classList.remove('valid', 'invalid');
2978
+ state.isValid = true;
2979
+ m.redraw();
2926
2980
  }
2927
2981
  };
2928
2982
  // Range slider helper functions
2929
2983
  // Range slider rendering functions are now in separate module
2930
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
+ },
2931
3002
  view: ({ attrs }) => {
2932
- var _a;
2933
- const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, initialValue, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "initialValue", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate"]);
3003
+ var _a, _b;
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"]);
2934
3005
  // const attributes = toAttrs(params);
2935
3006
  const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
2936
3007
  const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
@@ -2947,10 +3018,14 @@
2947
3018
  isMandatory,
2948
3019
  helperText }));
2949
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;
2950
3025
  return m('.input-field', { className: cn, style }, [
2951
3026
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
2952
3027
  m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
2953
- 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
2954
3029
  ? {
2955
3030
  height: attrs.height || '200px',
2956
3031
  width: '6px',
@@ -2964,25 +3039,9 @@
2964
3039
  if (focus(attrs)) {
2965
3040
  input.focus();
2966
3041
  }
2967
- // Set initial value if provided
2968
- if (initialValue) {
2969
- input.value = String(initialValue);
2970
- }
2971
- // Update character count state for counter component
2972
- if (maxLength) {
2973
- state.currentLength = input.value.length; // Initial count
2974
- }
2975
- // Range input functionality
2976
- if (type === 'range' && !attrs.minmax) {
2977
- const updateThumb = () => {
2978
- const value = input.value;
2979
- const min = input.min || '0';
2980
- const max = input.max || '100';
2981
- const percentage = ((parseFloat(value) - parseFloat(min)) / (parseFloat(max) - parseFloat(min))) * 100;
2982
- input.style.setProperty('--range-progress', `${percentage}%`);
2983
- };
2984
- input.addEventListener('input', updateThumb);
2985
- updateThumb(); // Initial position
3042
+ // For uncontrolled mode, set initial value only
3043
+ if (!controlled && attrs.defaultValue !== undefined) {
3044
+ input.value = String(attrs.defaultValue);
2986
3045
  }
2987
3046
  }, onkeyup: onkeyup
2988
3047
  ? (ev) => {
@@ -2996,21 +3055,25 @@
2996
3055
  ? (ev) => {
2997
3056
  onkeypress(ev, getValue(ev.target));
2998
3057
  }
2999
- : undefined, onupdate: validate
3000
- ? ({ dom }) => {
3001
- const target = dom;
3002
- setValidity(target, validate(getValue(target), target));
3003
- }
3004
3058
  : undefined, oninput: (e) => {
3005
3059
  state.active = true;
3060
+ state.hasInteracted = false;
3006
3061
  const target = e.target;
3007
3062
  // Handle original oninput logic
3008
- const value = getValue(target);
3063
+ const inputValue = getValue(target);
3064
+ // Update internal state for uncontrolled mode
3065
+ if (!controlled) {
3066
+ state.internalValue = inputValue;
3067
+ }
3009
3068
  if (oninput) {
3010
- oninput(value);
3069
+ oninput(inputValue);
3011
3070
  }
3012
- if (maxLength) {
3013
- 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}%`);
3014
3077
  }
3015
3078
  // Don't validate on input, only clear error states if user is typing
3016
3079
  if (validate && target.classList.contains('invalid') && target.value.length > 0) {
@@ -3026,6 +3089,14 @@
3026
3089
  state.isValid = true;
3027
3090
  }
3028
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
+ }
3029
3100
  }, onfocus: () => {
3030
3101
  state.active = true;
3031
3102
  }, onblur: (e) => {
@@ -3062,6 +3133,48 @@
3062
3133
  state.isValid = true;
3063
3134
  }
3064
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
+ }
3065
3178
  // Also call the original onblur handler if provided
3066
3179
  if (attrs.onblur) {
3067
3180
  attrs.onblur(e);
@@ -3070,23 +3183,35 @@
3070
3183
  onchange(getValue(state.inputElement));
3071
3184
  }
3072
3185
  } })),
3186
+ // Clear button - only for text inputs with canClear enabled and has content
3187
+ canClear && type === 'text' && ((_b = state.inputElement) === null || _b === void 0 ? void 0 : _b.value)
3188
+ ? m(MaterialIcon, {
3189
+ name: 'close',
3190
+ className: 'input-clear-btn',
3191
+ onclick: (e) => {
3192
+ e.preventDefault();
3193
+ e.stopPropagation();
3194
+ clearInput(oninput, onchange);
3195
+ },
3196
+ })
3197
+ : undefined,
3073
3198
  m(Label, {
3074
3199
  label,
3075
3200
  id,
3076
3201
  isMandatory,
3077
3202
  isActive,
3078
- initialValue: initialValue !== undefined,
3203
+ initialValue: value !== undefined && value !== '',
3079
3204
  }),
3080
3205
  m(HelperText, {
3081
3206
  helperText,
3082
3207
  dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
3083
3208
  dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
3084
3209
  }),
3085
- maxLength
3210
+ maxLength && typeof value === 'string'
3086
3211
  ? m(CharacterCounter, {
3087
- currentLength: state.currentLength,
3212
+ currentLength: value.length,
3088
3213
  maxLength,
3089
- show: state.currentLength > 0,
3214
+ show: value.length > 0,
3090
3215
  })
3091
3216
  : undefined,
3092
3217
  ]);
@@ -3113,7 +3238,7 @@
3113
3238
  let i;
3114
3239
  return {
3115
3240
  view: ({ attrs }) => {
3116
- 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;
3117
3242
  const accept = acceptedFiles
3118
3243
  ? acceptedFiles instanceof Array
3119
3244
  ? acceptedFiles.join(', ')
@@ -3144,8 +3269,8 @@
3144
3269
  placeholder,
3145
3270
  oncreate: ({ dom }) => {
3146
3271
  i = dom;
3147
- if (initialValue)
3148
- i.value = initialValue;
3272
+ if (value)
3273
+ i.value = value;
3149
3274
  },
3150
3275
  })),
3151
3276
  (canClear || (i === null || i === void 0 ? void 0 : i.value)) &&
@@ -3172,11 +3297,14 @@
3172
3297
 
3173
3298
  /** Component to show a check box */
3174
3299
  const InputCheckbox = () => {
3300
+ let checkboxId;
3175
3301
  return {
3176
3302
  view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
3177
- const checkboxId = inputId || uniqueId();
3303
+ if (!checkboxId)
3304
+ checkboxId = inputId || uniqueId();
3178
3305
  return m(`p`, { className, style }, m('label', { for: checkboxId }, [
3179
3306
  m('input[type=checkbox][tabindex=0]', {
3307
+ className: disabled ? 'disabled' : undefined,
3180
3308
  id: checkboxId,
3181
3309
  checked,
3182
3310
  disabled,
@@ -3193,78 +3321,79 @@
3193
3321
  },
3194
3322
  };
3195
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
+ };
3196
3333
  /** A list of checkboxes */
3197
3334
  const Options = () => {
3198
- const state = {};
3199
- const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
3200
- const selectAll = (options, callback) => {
3335
+ const state = {
3336
+ componentId: '',
3337
+ };
3338
+ const selectAll = (options, onchange) => {
3201
3339
  const allIds = options.map((option) => option.id);
3202
- state.checkedIds = [...allIds];
3203
- if (callback)
3204
- callback(allIds);
3340
+ onchange && onchange(allIds);
3205
3341
  };
3206
- const selectNone = (callback) => {
3207
- state.checkedIds = [];
3208
- if (callback)
3209
- callback([]);
3342
+ const selectNone = (onchange) => {
3343
+ onchange && onchange([]);
3344
+ };
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);
3210
3351
  };
3211
3352
  return {
3212
- oninit: ({ attrs: { initialValue, checkedId, id } }) => {
3213
- const iv = checkedId || initialValue;
3214
- state.checkedId = checkedId;
3215
- state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
3216
- state.componentId = id || uniqueId();
3353
+ oninit: ({ attrs }) => {
3354
+ state.componentId = attrs.id || uniqueId();
3217
3355
  },
3218
- view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
3219
- const onchange = callback
3220
- ? (propId, checked) => {
3221
- const checkedIds = state.checkedIds.filter((i) => i !== propId);
3222
- if (checked) {
3223
- checkedIds.push(propId);
3224
- }
3225
- state.checkedIds = checkedIds;
3226
- callback(checkedIds);
3227
- }
3228
- : 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);
3229
3360
  const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
3230
- const optionsContent = layout === 'horizontal'
3231
- ? m('div.grid-container', options.map((option) => m(InputCheckbox, {
3361
+ const optionItems = options.map((option) => ({
3362
+ component: InputCheckbox,
3363
+ props: {
3232
3364
  disabled: disabled || option.disabled,
3233
3365
  label: option.label,
3234
- onchange: onchange ? (v) => onchange(option.id, v) : undefined,
3366
+ onchange: onchange ? (v) => handleChange(option.id, v, checkedIds, onchange) : undefined,
3235
3367
  className: option.className || checkboxClass,
3236
3368
  checked: isChecked(option.id),
3237
3369
  description: option.description,
3238
3370
  inputId: `${state.componentId}-${option.id}`,
3239
- })))
3240
- : options.map((option) => m(InputCheckbox, {
3241
- disabled: disabled || option.disabled,
3242
- label: option.label,
3243
- onchange: onchange ? (v) => onchange(option.id, v) : undefined,
3244
- className: option.className || checkboxClass,
3245
- checked: isChecked(option.id),
3246
- description: option.description,
3247
- inputId: `${state.componentId}-${option.id}`,
3248
- }));
3371
+ },
3372
+ key: option.id,
3373
+ }));
3374
+ const optionsContent = m(OptionsList, {
3375
+ options: optionItems,
3376
+ layout,
3377
+ });
3249
3378
  return m('div', { id: state.componentId, className: cn, style }, [
3250
3379
  label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
3251
3380
  showSelectAll &&
3252
- m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
3381
+ m('div.select-all-controls', { style: { marginBottom: '10px' } }, [
3253
3382
  m('a', {
3254
3383
  href: '#',
3255
3384
  onclick: (e) => {
3256
3385
  e.preventDefault();
3257
- selectAll(options, callback);
3386
+ selectAll(options, onchange);
3258
3387
  },
3259
- style: 'margin-right: 15px;',
3260
- }, 'Select All'),
3388
+ style: { marginRight: '15px' },
3389
+ }, selectAllText),
3261
3390
  m('a', {
3262
3391
  href: '#',
3263
3392
  onclick: (e) => {
3264
3393
  e.preventDefault();
3265
- selectNone(callback);
3394
+ selectNone(onchange);
3266
3395
  },
3267
- }, 'Select None'),
3396
+ }, selectNoneText),
3268
3397
  ]),
3269
3398
  description && m(HelperText, { helperText: description }),
3270
3399
  m('form', { action: '#' }, optionsContent),
@@ -3859,11 +3988,19 @@
3859
3988
  const Dropdown = () => {
3860
3989
  const state = {
3861
3990
  isOpen: false,
3862
- initialValue: undefined,
3863
3991
  id: '',
3864
3992
  focusedIndex: -1,
3865
3993
  inputRef: null,
3866
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
+ }
3867
4004
  };
3868
4005
  const handleKeyDown = (e, items, onchange) => {
3869
4006
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
@@ -3872,7 +4009,7 @@
3872
4009
  e.preventDefault();
3873
4010
  if (!state.isOpen) {
3874
4011
  state.isOpen = true;
3875
- state.focusedIndex = 0;
4012
+ state.focusedIndex = availableItems.length > 0 ? 0 : -1;
3876
4013
  }
3877
4014
  else {
3878
4015
  state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
@@ -3890,15 +4027,13 @@
3890
4027
  if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
3891
4028
  const selectedItem = availableItems[state.focusedIndex];
3892
4029
  const value = (selectedItem.id || selectedItem.label);
3893
- state.initialValue = value;
3894
4030
  state.isOpen = false;
3895
4031
  state.focusedIndex = -1;
3896
- if (onchange)
3897
- onchange(value);
4032
+ return value; // Return value to be handled in view
3898
4033
  }
3899
4034
  else if (!state.isOpen) {
3900
4035
  state.isOpen = true;
3901
- state.focusedIndex = 0;
4036
+ state.focusedIndex = availableItems.length > 0 ? 0 : -1;
3902
4037
  }
3903
4038
  break;
3904
4039
  case 'Escape':
@@ -3909,15 +4044,36 @@
3909
4044
  }
3910
4045
  };
3911
4046
  return {
3912
- oninit: ({ attrs: { id = uniqueId(), initialValue, checkedId } }) => {
3913
- state.id = id;
3914
- state.initialValue = initialValue || checkedId;
3915
- // Mithril will handle click events through the component structure
4047
+ oninit: ({ attrs }) => {
4048
+ var _a;
4049
+ state.id = ((_a = attrs.id) === null || _a === void 0 ? void 0 : _a.toString()) || uniqueId();
4050
+ // Initialize internal state for uncontrolled mode
4051
+ if (!isControlled(attrs)) {
4052
+ state.internalCheckedId = attrs.defaultCheckedId;
4053
+ }
4054
+ // Add global click listener to close dropdown
4055
+ document.addEventListener('click', closeDropdown);
4056
+ },
4057
+ onremove: () => {
4058
+ // Cleanup global listener
4059
+ document.removeEventListener('click', closeDropdown);
3916
4060
  },
3917
- view: ({ attrs: { key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12' }, }) => {
3918
- const { initialValue } = state;
3919
- const selectedItem = initialValue
3920
- ? items.filter((i) => (i.id ? i.id === initialValue : i.label === initialValue)).shift()
4061
+ view: ({ attrs }) => {
4062
+ const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
4063
+ const controlled = isControlled(attrs);
4064
+ const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
4065
+ const handleSelection = (value) => {
4066
+ // Update internal state for uncontrolled mode
4067
+ if (!controlled) {
4068
+ state.internalCheckedId = value;
4069
+ }
4070
+ // Call onchange if provided
4071
+ if (onchange) {
4072
+ onchange(value);
4073
+ }
4074
+ };
4075
+ const selectedItem = currentCheckedId
4076
+ ? items.filter((i) => (i.id ? i.id === currentCheckedId : i.label === currentCheckedId)).shift()
3921
4077
  : undefined;
3922
4078
  const title = selectedItem ? selectedItem.label : label || 'Select';
3923
4079
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
@@ -3925,13 +4081,12 @@
3925
4081
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
3926
4082
  m(HelperText, { helperText }),
3927
4083
  m('.select-wrapper', {
3928
- onclick: disabled
3929
- ? undefined
3930
- : () => {
3931
- state.isOpen = !state.isOpen;
3932
- state.focusedIndex = state.isOpen ? 0 : -1;
3933
- },
3934
- onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
4084
+ onkeydown: disabled ? undefined : (e) => {
4085
+ const selectedValue = handleKeyDown(e, items);
4086
+ if (selectedValue) {
4087
+ handleSelection(selectedValue);
4088
+ }
4089
+ },
3935
4090
  tabindex: disabled ? -1 : 0,
3936
4091
  'aria-expanded': state.isOpen ? 'true' : 'false',
3937
4092
  'aria-haspopup': 'listbox',
@@ -3948,7 +4103,8 @@
3948
4103
  e.stopPropagation();
3949
4104
  if (!disabled) {
3950
4105
  state.isOpen = !state.isOpen;
3951
- state.focusedIndex = state.isOpen ? 0 : -1;
4106
+ // Reset focus index when opening/closing
4107
+ state.focusedIndex = -1;
3952
4108
  }
3953
4109
  },
3954
4110
  }),
@@ -3964,36 +4120,31 @@
3964
4120
  onremove: () => {
3965
4121
  state.dropdownRef = null;
3966
4122
  },
3967
- style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
3968
- // Convert dropdown items to format expected by getDropdownStyles
3969
- group: undefined }))), true),
3970
- }, items.map((item, index) => {
4123
+ style: getDropdownStyles(state.inputRef, true, items, true),
4124
+ }, items.map((item) => {
3971
4125
  if (item.divider) {
3972
- return m('li.divider', {
3973
- key: `divider-${index}`,
3974
- });
4126
+ return m('li.divider');
3975
4127
  }
3976
4128
  const itemIndex = availableItems.indexOf(item);
3977
4129
  const isFocused = itemIndex === state.focusedIndex;
3978
- return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
3979
- item.disabled ? 'disabled' : '',
3980
- isFocused ? 'focused' : '',
3981
- (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
3982
- ]
3983
- .filter(Boolean)
3984
- .join(' ') }, (item.disabled
3985
- ? {}
3986
- : {
3987
- onclick: (e) => {
3988
- e.stopPropagation();
4130
+ const className = [
4131
+ item.disabled ? 'disabled' : '',
4132
+ isFocused ? 'focused' : '',
4133
+ (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
4134
+ ]
4135
+ .filter(Boolean)
4136
+ .join(' ') || undefined;
4137
+ return m('li', {
4138
+ className,
4139
+ onclick: item.disabled
4140
+ ? undefined
4141
+ : () => {
3989
4142
  const value = (item.id || item.label);
3990
- state.initialValue = value;
3991
4143
  state.isOpen = false;
3992
4144
  state.focusedIndex = -1;
3993
- if (onchange)
3994
- onchange(value);
4145
+ handleSelection(value);
3995
4146
  },
3996
- })), m('span', {
4147
+ }, m('span', {
3997
4148
  style: {
3998
4149
  display: 'flex',
3999
4150
  alignItems: 'center',
@@ -5130,9 +5281,9 @@
5130
5281
  dx: 0,
5131
5282
  dy: 0,
5132
5283
  };
5133
- // Handle initial value after options are set
5134
- if (attrs.initialValue) {
5135
- updateTimeFromInput(attrs.initialValue);
5284
+ // Handle value after options are set
5285
+ if (attrs.defaultValue) {
5286
+ updateTimeFromInput(attrs.defaultValue);
5136
5287
  }
5137
5288
  },
5138
5289
  onremove: () => {
@@ -5414,32 +5565,47 @@
5414
5565
  },
5415
5566
  });
5416
5567
  /** Component to show a list of radio buttons, from which you can choose one. */
5417
- // export const RadioButtons: FactoryComponent<IRadioButtons<T>> = () => {
5418
5568
  const RadioButtons = () => {
5419
- const state = { groupId: uniqueId() };
5569
+ const state = {
5570
+ groupId: uniqueId(),
5571
+ componentId: '',
5572
+ internalCheckedId: undefined,
5573
+ };
5574
+ const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
5420
5575
  return {
5421
- oninit: ({ attrs: { checkedId, initialValue, id } }) => {
5422
- state.oldCheckedId = checkedId;
5423
- state.checkedId = checkedId || initialValue;
5424
- state.componentId = id || uniqueId();
5576
+ oninit: ({ attrs }) => {
5577
+ state.componentId = attrs.id || uniqueId();
5578
+ // Initialize internal state for uncontrolled mode
5579
+ if (!isControlled(attrs)) {
5580
+ state.internalCheckedId = attrs.defaultCheckedId;
5581
+ }
5425
5582
  },
5426
- view: ({ attrs: { checkedId: cid, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange: callback, }, }) => {
5427
- if (state.oldCheckedId !== cid) {
5428
- state.oldCheckedId = state.checkedId = cid;
5429
- }
5430
- const { groupId, checkedId, componentId } = state;
5431
- const onchange = (propId) => {
5432
- state.checkedId = propId;
5433
- if (callback) {
5434
- callback(propId);
5583
+ view: ({ attrs }) => {
5584
+ const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout = 'vertical', onchange, } = attrs;
5585
+ const { groupId, componentId } = state;
5586
+ const controlled = isControlled(attrs);
5587
+ // Get current checked ID from props or internal state
5588
+ const currentCheckedId = controlled ? checkedId : state.internalCheckedId;
5589
+ const handleChange = (id) => {
5590
+ // Update internal state for uncontrolled mode
5591
+ if (!controlled) {
5592
+ state.internalCheckedId = id;
5593
+ }
5594
+ // Call onchange if provided
5595
+ if (onchange) {
5596
+ onchange(id);
5435
5597
  }
5436
5598
  };
5437
5599
  const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
5438
- const optionsContent = layout === 'horizontal'
5439
- ? m('div.grid-container', options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
5440
- groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` }))))
5441
- : options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
5442
- groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` })));
5600
+ const radioItems = options.map((r) => ({
5601
+ component: (RadioButton),
5602
+ props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}` }),
5603
+ key: r.id,
5604
+ }));
5605
+ const optionsContent = m(OptionsList, {
5606
+ options: radioItems,
5607
+ layout,
5608
+ });
5443
5609
  return m('div', { id: componentId, className: cn }, [
5444
5610
  label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
5445
5611
  description && m('p.helper-text', m.trust(description)),
@@ -5454,30 +5620,42 @@
5454
5620
  const state = {
5455
5621
  id: '',
5456
5622
  isOpen: false,
5457
- selectedIds: [],
5458
5623
  focusedIndex: -1,
5459
5624
  inputRef: null,
5460
5625
  dropdownRef: null,
5626
+ internalSelectedIds: [],
5461
5627
  };
5628
+ const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
5462
5629
  const isSelected = (id, selectedIds) => {
5463
5630
  return selectedIds.some((selectedId) => selectedId === id);
5464
5631
  };
5465
5632
  const toggleOption = (id, multiple, attrs) => {
5633
+ const controlled = isControlled(attrs);
5634
+ // Get current selected IDs from props or internal state
5635
+ const currentSelectedIds = controlled
5636
+ ? attrs.checkedId !== undefined
5637
+ ? Array.isArray(attrs.checkedId)
5638
+ ? attrs.checkedId
5639
+ : [attrs.checkedId]
5640
+ : []
5641
+ : state.internalSelectedIds;
5642
+ let newIds;
5466
5643
  if (multiple) {
5467
- const newIds = state.selectedIds.includes(id)
5468
- ? // isSelected(id, state.selectedIds)
5469
- state.selectedIds.filter((selectedId) => selectedId !== id)
5470
- : [...state.selectedIds, id];
5471
- state.selectedIds = newIds;
5472
- attrs.onchange(newIds);
5473
- console.log(newIds);
5474
- // Keep dropdown open for multiple select
5644
+ newIds = currentSelectedIds.includes(id)
5645
+ ? currentSelectedIds.filter((selectedId) => selectedId !== id)
5646
+ : [...currentSelectedIds, id];
5475
5647
  }
5476
5648
  else {
5477
- state.selectedIds = [id];
5478
- // Close dropdown for single select
5479
- state.isOpen = false;
5480
- attrs.onchange([id]);
5649
+ newIds = [id];
5650
+ state.isOpen = false; // Close dropdown for single select
5651
+ }
5652
+ // Update internal state for uncontrolled mode
5653
+ if (!controlled) {
5654
+ state.internalSelectedIds = newIds;
5655
+ }
5656
+ // Call onchange if provided
5657
+ if (attrs.onchange) {
5658
+ attrs.onchange(newIds);
5481
5659
  }
5482
5660
  };
5483
5661
  const handleKeyDown = (e, attrs) => {
@@ -5529,88 +5707,22 @@
5529
5707
  };
5530
5708
  const closeDropdown = (e) => {
5531
5709
  const target = e.target;
5532
- if (!target.closest('.select-wrapper-container')) {
5710
+ if (!target.closest('.input-field.select-space')) {
5533
5711
  state.isOpen = false;
5534
5712
  m.redraw();
5535
5713
  }
5536
5714
  };
5537
- const renderGroupedOptions = (options, multiple, attrs) => {
5538
- const groupedOptions = {};
5539
- const ungroupedOptions = [];
5540
- // Group options by their group property
5541
- options.forEach((option) => {
5542
- if (option.group) {
5543
- if (!groupedOptions[option.group]) {
5544
- groupedOptions[option.group] = [];
5545
- }
5546
- groupedOptions[option.group].push(option);
5547
- }
5548
- else {
5549
- ungroupedOptions.push(option);
5550
- }
5551
- });
5552
- const renderElements = [];
5553
- // Render ungrouped options first
5554
- ungroupedOptions.forEach((option) => {
5555
- renderElements.push(m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === options.indexOf(option) ? 'focused' : '' }, (option.disabled
5556
- ? {}
5557
- : {
5558
- onclick: (e) => {
5559
- e.stopPropagation();
5560
- toggleOption(option.id, multiple, attrs);
5561
- },
5562
- })), m('span', multiple
5563
- ? m('label', { for: option.id }, m('input', {
5564
- id: option.id,
5565
- type: 'checkbox',
5566
- checked: state.selectedIds.includes(option.id),
5567
- disabled: option.disabled ? true : undefined,
5568
- onclick: (e) => {
5569
- e.stopPropagation();
5570
- },
5571
- }), m('span', option.label))
5572
- : option.label)));
5573
- });
5574
- // Render grouped options
5575
- Object.keys(groupedOptions).forEach((groupName) => {
5576
- // Add group header
5577
- renderElements.push(m('li.optgroup', { tabindex: 0 }, m('span', groupName)));
5578
- // Add group options
5579
- groupedOptions[groupName].forEach((option) => {
5580
- 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
5581
- ? {}
5582
- : {
5583
- onclick: (e) => {
5584
- e.stopPropagation();
5585
- toggleOption(option.id, multiple, attrs);
5586
- },
5587
- })), m('span', multiple
5588
- ? m('label', { for: option.id }, m('input', {
5589
- id: option.id,
5590
- type: 'checkbox',
5591
- checked: state.selectedIds.includes(option.id),
5592
- disabled: option.disabled ? true : undefined,
5593
- onclick: (e) => {
5594
- e.stopPropagation();
5595
- },
5596
- }), m('span', option.label))
5597
- : option.label)));
5598
- });
5599
- });
5600
- return renderElements;
5601
- };
5602
5715
  return {
5603
5716
  oninit: ({ attrs }) => {
5604
- const { checkedId, initialValue, id } = attrs;
5605
- state.id = id || uniqueId();
5606
- const iv = checkedId || initialValue;
5607
- if (iv !== null && typeof iv !== 'undefined') {
5608
- if (iv instanceof Array) {
5609
- state.selectedIds = [...iv];
5610
- }
5611
- else {
5612
- state.selectedIds = [iv];
5613
- }
5717
+ state.id = attrs.id || uniqueId();
5718
+ // Initialize internal state for uncontrolled mode
5719
+ if (!isControlled(attrs)) {
5720
+ const defaultIds = attrs.defaultCheckedId !== undefined
5721
+ ? Array.isArray(attrs.defaultCheckedId)
5722
+ ? attrs.defaultCheckedId
5723
+ : [attrs.defaultCheckedId]
5724
+ : [];
5725
+ state.internalSelectedIds = defaultIds;
5614
5726
  }
5615
5727
  // Add global click listener to close dropdown
5616
5728
  document.addEventListener('click', closeDropdown);
@@ -5620,17 +5732,18 @@
5620
5732
  document.removeEventListener('click', closeDropdown);
5621
5733
  },
5622
5734
  view: ({ attrs }) => {
5623
- // Sync external checkedId prop with internal state - do this in view for immediate response
5624
- const { checkedId } = attrs;
5625
- if (checkedId !== undefined) {
5626
- const newIds = checkedId instanceof Array ? checkedId : [checkedId];
5627
- if (JSON.stringify(newIds) !== JSON.stringify(state.selectedIds)) {
5628
- state.selectedIds = newIds;
5629
- }
5630
- }
5735
+ const controlled = isControlled(attrs);
5736
+ // Get selected IDs from props or internal state
5737
+ const selectedIds = controlled
5738
+ ? attrs.checkedId !== undefined
5739
+ ? Array.isArray(attrs.checkedId)
5740
+ ? attrs.checkedId
5741
+ : [attrs.checkedId]
5742
+ : []
5743
+ : state.internalSelectedIds;
5631
5744
  const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, disabled, style, } = attrs;
5632
5745
  const finalClassName = newRow ? `${className} clear` : className;
5633
- const selectedOptions = options.filter((opt) => isSelected(opt.id, state.selectedIds));
5746
+ const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
5634
5747
  return m('.input-field.select-space', {
5635
5748
  className: finalClassName,
5636
5749
  key,
@@ -5639,11 +5752,6 @@
5639
5752
  // Icon prefix
5640
5753
  iconName && m('i.material-icons.prefix', iconName),
5641
5754
  m('.select-wrapper', {
5642
- onclick: disabled
5643
- ? undefined
5644
- : () => {
5645
- state.isOpen = !state.isOpen;
5646
- },
5647
5755
  onkeydown: disabled ? undefined : (e) => handleKeyDown(e, attrs),
5648
5756
  tabindex: disabled ? -1 : 0,
5649
5757
  'aria-expanded': state.isOpen ? 'true' : 'false',
@@ -5659,7 +5767,9 @@
5659
5767
  onclick: (e) => {
5660
5768
  e.preventDefault();
5661
5769
  e.stopPropagation();
5662
- state.isOpen = !state.isOpen;
5770
+ if (!disabled) {
5771
+ state.isOpen = !state.isOpen;
5772
+ }
5663
5773
  },
5664
5774
  }),
5665
5775
  // Dropdown Menu
@@ -5675,7 +5785,63 @@
5675
5785
  style: getDropdownStyles(state.inputRef, true, options),
5676
5786
  }, [
5677
5787
  placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
5678
- ...renderGroupedOptions(options, multiple, attrs),
5788
+ // Render ungrouped options first
5789
+ options
5790
+ .filter((option) => !option.group)
5791
+ .map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
5792
+ ? 'disabled'
5793
+ : state.focusedIndex === options.indexOf(option)
5794
+ ? 'focused'
5795
+ : '' }, (option.disabled
5796
+ ? {}
5797
+ : {
5798
+ onclick: (e) => {
5799
+ e.stopPropagation();
5800
+ toggleOption(option.id, multiple, attrs);
5801
+ },
5802
+ })), m('span', multiple
5803
+ ? m('label', { for: option.id }, m('input', {
5804
+ id: option.id,
5805
+ type: 'checkbox',
5806
+ checked: selectedIds.includes(option.id),
5807
+ disabled: option.disabled ? true : undefined,
5808
+ onclick: (e) => {
5809
+ e.stopPropagation();
5810
+ },
5811
+ }), m('span', option.label))
5812
+ : option.label))),
5813
+ // Render grouped options
5814
+ Object.entries(options
5815
+ .filter((option) => option.group)
5816
+ .reduce((groups, option) => {
5817
+ const group = option.group;
5818
+ if (!groups[group])
5819
+ groups[group] = [];
5820
+ groups[group].push(option);
5821
+ return groups;
5822
+ }, {}))
5823
+ .map(([groupName, groupOptions]) => [
5824
+ m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
5825
+ ...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
5826
+ ? {}
5827
+ : {
5828
+ onclick: (e) => {
5829
+ e.stopPropagation();
5830
+ toggleOption(option.id, multiple, attrs);
5831
+ },
5832
+ })), m('span', multiple
5833
+ ? m('label', { for: option.id }, m('input', {
5834
+ id: option.id,
5835
+ type: 'checkbox',
5836
+ checked: selectedIds.includes(option.id),
5837
+ disabled: option.disabled ? true : undefined,
5838
+ onclick: (e) => {
5839
+ e.stopPropagation();
5840
+ },
5841
+ }), m('span', option.label))
5842
+ : option.label))),
5843
+ ])
5844
+ .reduce((acc, val) => acc.concat(val), []),
5679
5845
  ]),
5680
5846
  m(MaterialIcon, {
5681
5847
  name: 'caret',
@@ -5706,25 +5872,22 @@
5706
5872
  },
5707
5873
  view: ({ attrs }) => {
5708
5874
  const id = attrs.id || state.id;
5709
- const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
5875
+ 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"]);
5710
5876
  const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
5711
5877
  return m('div', {
5712
5878
  className: cn,
5713
5879
  onclick: (e) => {
5714
- state.checked = !state.checked;
5715
- onchange && onchange(state.checked);
5880
+ onchange && onchange(!checked);
5716
5881
  e.preventDefault();
5717
5882
  },
5718
5883
  }, [
5719
5884
  label && m(Label, { label: label || '', id, isMandatory, className: 'active' }),
5720
- m('.switch', params, m('label', {
5721
- style: { cursor: 'pointer' },
5722
- }, [
5885
+ m('.switch', params, m('label', [
5723
5886
  m('span', left || 'Off'),
5724
5887
  m('input[type=checkbox]', {
5725
5888
  id,
5726
5889
  disabled,
5727
- checked: state.checked,
5890
+ checked,
5728
5891
  }),
5729
5892
  m('span.lever'),
5730
5893
  m('span', right || 'On'),
@@ -5916,22 +6079,62 @@
5916
6079
  };
5917
6080
  };
5918
6081
 
6082
+ // Proper components to avoid anonymous closures
6083
+ const SelectedChip = {
6084
+ view: ({ attrs: { option, onRemove } }) => m('.chip', [
6085
+ option.label || option.id.toString(),
6086
+ m(MaterialIcon, {
6087
+ name: 'close',
6088
+ className: 'close',
6089
+ onclick: (e) => {
6090
+ e.stopPropagation();
6091
+ onRemove(option.id);
6092
+ },
6093
+ }),
6094
+ ]),
6095
+ };
6096
+ const DropdownOption = {
6097
+ view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
6098
+ const checkboxId = `search-select-option-${option.id}`;
6099
+ const optionLabel = option.label || option.id.toString();
6100
+ return m('li', {
6101
+ key: option.id,
6102
+ onclick: (e) => {
6103
+ e.preventDefault();
6104
+ e.stopPropagation();
6105
+ onToggle(option);
6106
+ },
6107
+ class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
6108
+ onmouseover: () => {
6109
+ if (!option.disabled) {
6110
+ onMouseOver(index);
6111
+ }
6112
+ },
6113
+ }, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
6114
+ m('input', {
6115
+ type: 'checkbox',
6116
+ id: checkboxId,
6117
+ checked: selectedIds.includes(option.id),
6118
+ }),
6119
+ m('span', optionLabel),
6120
+ ]));
6121
+ },
6122
+ };
5919
6123
  /**
5920
6124
  * Mithril Factory Component for Multi-Select Dropdown with search
5921
6125
  */
5922
6126
  const SearchSelect = () => {
5923
- // (): <T extends string | number>(): Component<SearchSelectAttrs<T>, SearchSelectState<T>> => {
5924
6127
  // State initialization
5925
6128
  const state = {
6129
+ id: '',
5926
6130
  isOpen: false,
5927
- selectedOptions: [], //options.filter((o) => iv.includes(o.id)),
5928
6131
  searchTerm: '',
5929
- options: [],
5930
6132
  inputRef: null,
5931
6133
  dropdownRef: null,
5932
6134
  focusedIndex: -1,
5933
- onchange: null,
6135
+ internalSelectedIds: [],
5934
6136
  };
6137
+ const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
5935
6138
  const componentId = uniqueId();
5936
6139
  const searchInputId = `${componentId}-search`;
5937
6140
  // Handle click outside
@@ -5972,9 +6175,7 @@
5972
6175
  // Handle add new option
5973
6176
  return 'addNew';
5974
6177
  }
5975
- else if (state.focusedIndex < filteredOptions.length) {
5976
- toggleOption(filteredOptions[state.focusedIndex]);
5977
- }
6178
+ else if (state.focusedIndex < filteredOptions.length) ;
5978
6179
  }
5979
6180
  break;
5980
6181
  case 'Escape':
@@ -5986,26 +6187,65 @@
5986
6187
  return null;
5987
6188
  };
5988
6189
  // Toggle option selection
5989
- const toggleOption = (option) => {
6190
+ const toggleOption = (option, attrs) => {
5990
6191
  if (option.disabled)
5991
6192
  return;
5992
- state.selectedOptions = state.selectedOptions.some((item) => item.id === option.id)
5993
- ? state.selectedOptions.filter((item) => item.id !== option.id)
5994
- : [...state.selectedOptions, option];
6193
+ const controlled = isControlled(attrs);
6194
+ // Get current selected IDs from props or internal state
6195
+ const currentSelectedIds = controlled
6196
+ ? attrs.checkedId !== undefined
6197
+ ? Array.isArray(attrs.checkedId)
6198
+ ? attrs.checkedId
6199
+ : [attrs.checkedId]
6200
+ : []
6201
+ : state.internalSelectedIds;
6202
+ const newIds = currentSelectedIds.includes(option.id)
6203
+ ? currentSelectedIds.filter((id) => id !== option.id)
6204
+ : [...currentSelectedIds, option.id];
6205
+ // Update internal state for uncontrolled mode
6206
+ if (!controlled) {
6207
+ state.internalSelectedIds = newIds;
6208
+ }
5995
6209
  state.searchTerm = '';
5996
6210
  state.focusedIndex = -1;
5997
- state.onchange && state.onchange(state.selectedOptions.map((o) => o.id));
6211
+ // Call onchange if provided
6212
+ if (attrs.onchange) {
6213
+ attrs.onchange(newIds);
6214
+ }
5998
6215
  };
5999
6216
  // Remove a selected option
6000
- const removeOption = (option) => {
6001
- state.selectedOptions = state.selectedOptions.filter((item) => item.id !== option.id);
6002
- state.onchange && state.onchange(state.selectedOptions.map((o) => o.id));
6217
+ const removeOption = (optionId, attrs) => {
6218
+ const controlled = isControlled(attrs);
6219
+ // Get current selected IDs from props or internal state
6220
+ const currentSelectedIds = controlled
6221
+ ? attrs.checkedId !== undefined
6222
+ ? Array.isArray(attrs.checkedId)
6223
+ ? attrs.checkedId
6224
+ : [attrs.checkedId]
6225
+ : []
6226
+ : state.internalSelectedIds;
6227
+ const newIds = currentSelectedIds.filter((id) => id !== optionId);
6228
+ // Update internal state for uncontrolled mode
6229
+ if (!controlled) {
6230
+ state.internalSelectedIds = newIds;
6231
+ }
6232
+ // Call onchange if provided
6233
+ if (attrs.onchange) {
6234
+ attrs.onchange(newIds);
6235
+ }
6003
6236
  };
6004
6237
  return {
6005
- oninit: ({ attrs: { options = [], initialValue = [], onchange } }) => {
6006
- state.options = options;
6007
- state.selectedOptions = options.filter((o) => initialValue.includes(o.id));
6008
- state.onchange = onchange;
6238
+ oninit: ({ attrs }) => {
6239
+ state.id = attrs.id || uniqueId();
6240
+ // Initialize internal state for uncontrolled mode
6241
+ if (!isControlled(attrs)) {
6242
+ const defaultIds = attrs.defaultCheckedId !== undefined
6243
+ ? Array.isArray(attrs.defaultCheckedId)
6244
+ ? attrs.defaultCheckedId
6245
+ : [attrs.defaultCheckedId]
6246
+ : [];
6247
+ state.internalSelectedIds = defaultIds;
6248
+ }
6009
6249
  },
6010
6250
  oncreate() {
6011
6251
  document.addEventListener('click', handleClickOutside);
@@ -6013,14 +6253,27 @@
6013
6253
  onremove() {
6014
6254
  document.removeEventListener('click', handleClickOutside);
6015
6255
  },
6016
- view({ attrs: {
6017
- // onchange,
6018
- oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label,
6019
- // maxHeight = '25rem',
6020
- }, }) {
6256
+ view({ attrs }) {
6257
+ const controlled = isControlled(attrs);
6258
+ // Get selected IDs from props or internal state
6259
+ const selectedIds = controlled
6260
+ ? attrs.checkedId !== undefined
6261
+ ? Array.isArray(attrs.checkedId)
6262
+ ? attrs.checkedId
6263
+ : [attrs.checkedId]
6264
+ : []
6265
+ : state.internalSelectedIds;
6266
+ const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
6267
+ // Use i18n values if provided, otherwise use defaults
6268
+ const texts = {
6269
+ noOptionsFound: i18n.noOptionsFound || noOptionsFound,
6270
+ addNewPrefix: i18n.addNewPrefix || '+',
6271
+ };
6272
+ // Get selected options for display
6273
+ const selectedOptions = options.filter((opt) => selectedIds.includes(opt.id));
6021
6274
  // Safely filter options
6022
- const filteredOptions = state.options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
6023
- !state.selectedOptions.some((selected) => selected.id === option.id));
6275
+ const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
6276
+ !selectedIds.includes(option.id));
6024
6277
  // Check if we should show the "add new option" element
6025
6278
  const showAddNew = oncreateNewOption &&
6026
6279
  state.searchTerm &&
@@ -6038,6 +6291,7 @@
6038
6291
  state.isOpen = !state.isOpen;
6039
6292
  // console.log('SearchSelect state changed to', state.isOpen); // Debug log
6040
6293
  },
6294
+ class: 'chips chips-container',
6041
6295
  style: {
6042
6296
  display: 'flex',
6043
6297
  alignItems: 'end',
@@ -6050,25 +6304,20 @@
6050
6304
  // Hidden input for label association and accessibility
6051
6305
  m('input', {
6052
6306
  type: 'text',
6053
- id: componentId,
6054
- value: state.selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
6307
+ id: state.id,
6308
+ value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
6055
6309
  readonly: true,
6310
+ class: 'sr-only',
6056
6311
  style: { position: 'absolute', left: '-9999px', opacity: 0 },
6057
6312
  }),
6058
6313
  // Selected Options (chips)
6059
- ...state.selectedOptions.map((option) => m('.chip', [
6060
- option.label || option.id.toString(),
6061
- m(MaterialIcon, {
6062
- name: 'close',
6063
- className: 'close',
6064
- onclick: (e) => {
6065
- e.stopPropagation();
6066
- removeOption(option);
6067
- },
6068
- }),
6069
- ])),
6314
+ ...selectedOptions.map((option) => m(SelectedChip, {
6315
+ // key: option.id,
6316
+ option,
6317
+ onRemove: (id) => removeOption(id, attrs),
6318
+ })),
6070
6319
  // Placeholder when no options selected
6071
- state.selectedOptions.length === 0 &&
6320
+ selectedOptions.length === 0 &&
6072
6321
  placeholder &&
6073
6322
  m('span.placeholder', {
6074
6323
  style: {
@@ -6089,8 +6338,8 @@
6089
6338
  // Label
6090
6339
  label &&
6091
6340
  m('label', {
6092
- for: componentId,
6093
- class: placeholder || state.selectedOptions.length > 0 ? 'active' : '',
6341
+ for: state.id,
6342
+ class: placeholder || selectedOptions.length > 0 ? 'active' : '',
6094
6343
  }, label),
6095
6344
  // Dropdown Menu
6096
6345
  state.isOpen &&
@@ -6106,7 +6355,6 @@
6106
6355
  m('li', // Search Input
6107
6356
  {
6108
6357
  class: 'search-wrapper',
6109
- style: { padding: '0 16px', position: 'relative' },
6110
6358
  }, [
6111
6359
  m('input', {
6112
6360
  type: 'text',
@@ -6125,29 +6373,21 @@
6125
6373
  const result = handleKeyDown(e, filteredOptions, !!showAddNew);
6126
6374
  if (result === 'addNew' && oncreateNewOption) {
6127
6375
  const option = await oncreateNewOption(state.searchTerm);
6128
- toggleOption(option);
6376
+ toggleOption(option, attrs);
6377
+ }
6378
+ else if (e.key === 'Enter' &&
6379
+ state.focusedIndex >= 0 &&
6380
+ state.focusedIndex < filteredOptions.length) {
6381
+ toggleOption(filteredOptions[state.focusedIndex], attrs);
6129
6382
  }
6130
6383
  },
6131
- style: {
6132
- width: '100%',
6133
- outline: 'none',
6134
- fontSize: '0.875rem',
6135
- border: 'none',
6136
- padding: '8px 0',
6137
- borderBottom: '1px solid var(--mm-input-border, #9e9e9e)',
6138
- backgroundColor: 'transparent',
6139
- color: 'var(--mm-text-primary, inherit)',
6140
- },
6384
+ class: 'search-select-input',
6141
6385
  }),
6142
6386
  ]),
6143
6387
  // No options found message or list of options
6144
6388
  ...(filteredOptions.length === 0 && !showAddNew
6145
6389
  ? [
6146
- m('li',
6147
- // {
6148
- // style: getNoOptionsStyles(),
6149
- // },
6150
- noOptionsFound),
6390
+ m('li.search-select-no-options', texts.noOptionsFound),
6151
6391
  ]
6152
6392
  : []),
6153
6393
  // Add new option item
@@ -6156,35 +6396,27 @@
6156
6396
  m('li', {
6157
6397
  onclick: async () => {
6158
6398
  const option = await oncreateNewOption(state.searchTerm);
6159
- toggleOption(option);
6399
+ toggleOption(option, attrs);
6160
6400
  },
6161
6401
  class: state.focusedIndex === filteredOptions.length ? 'active' : '',
6162
6402
  onmouseover: () => {
6163
6403
  state.focusedIndex = filteredOptions.length;
6164
6404
  },
6165
- }, [m('span', `+ "${state.searchTerm}"`)]),
6405
+ }, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
6166
6406
  ]
6167
6407
  : []),
6168
6408
  // List of filtered options
6169
- ...filteredOptions.map((option, index) => m('li', {
6170
- onclick: (e) => {
6171
- e.preventDefault();
6172
- e.stopPropagation();
6173
- toggleOption(option);
6174
- },
6175
- class: `${option.disabled ? 'disabled' : ''} ${state.focusedIndex === index ? 'active' : ''}`.trim(),
6176
- onmouseover: () => {
6177
- if (!option.disabled) {
6178
- state.focusedIndex = index;
6179
- }
6409
+ ...filteredOptions.map((option, index) => m(DropdownOption, {
6410
+ // key: option.id,
6411
+ option,
6412
+ index,
6413
+ selectedIds,
6414
+ isFocused: state.focusedIndex === index,
6415
+ onToggle: (opt) => toggleOption(opt, attrs),
6416
+ onMouseOver: (idx) => {
6417
+ state.focusedIndex = idx;
6180
6418
  },
6181
- }, m('span', [
6182
- m('input', {
6183
- type: 'checkbox',
6184
- checked: state.selectedOptions.some((selected) => selected.id === option.id),
6185
- }),
6186
- option.label || option.id.toString(),
6187
- ]))),
6419
+ })),
6188
6420
  ]),
6189
6421
  ]);
6190
6422
  },
@@ -8304,6 +8536,7 @@
8304
8536
  exports.ModalPanel = ModalPanel;
8305
8537
  exports.NumberInput = NumberInput;
8306
8538
  exports.Options = Options;
8539
+ exports.OptionsList = OptionsList;
8307
8540
  exports.Pagination = Pagination;
8308
8541
  exports.PaginationControls = PaginationControls;
8309
8542
  exports.Parallax = Parallax;