mithril-materialized 3.2.0 → 3.2.2

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/core.css CHANGED
@@ -2673,6 +2673,9 @@ textarea.materialize-textarea {
2673
2673
  /* Character Counter */
2674
2674
  .character-counter {
2675
2675
  min-height: 18px;
2676
+ font-size: 12px;
2677
+ display: block;
2678
+ text-align: right;
2676
2679
  }
2677
2680
 
2678
2681
  /* Input Clear Button */
package/dist/forms.css CHANGED
@@ -1282,6 +1282,9 @@ textarea.materialize-textarea {
1282
1282
  /* Character Counter */
1283
1283
  .character-counter {
1284
1284
  min-height: 18px;
1285
+ font-size: 12px;
1286
+ display: block;
1287
+ text-align: right;
1285
1288
  }
1286
1289
 
1287
1290
  /* Input Clear Button */
package/dist/index.css CHANGED
@@ -6325,6 +6325,9 @@ textarea.materialize-textarea {
6325
6325
  /* Character Counter */
6326
6326
  .character-counter {
6327
6327
  min-height: 18px;
6328
+ font-size: 12px;
6329
+ display: block;
6330
+ text-align: right;
6328
6331
  }
6329
6332
 
6330
6333
  /* Input Clear Button */
package/dist/index.esm.js CHANGED
@@ -2789,10 +2789,6 @@ const CharacterCounter = () => {
2789
2789
  return m('span.character-counter', {
2790
2790
  style: {
2791
2791
  color: isOverLimit ? '#F44336' : '#9e9e9e',
2792
- fontSize: '12px',
2793
- display: 'block',
2794
- textAlign: 'right',
2795
- marginTop: '8px',
2796
2792
  },
2797
2793
  }, `${currentLength}/${maxLength}`);
2798
2794
  },
@@ -2809,10 +2805,51 @@ const TextArea = () => {
2809
2805
  textarea: undefined,
2810
2806
  internalValue: '',
2811
2807
  };
2812
- const updateHeight = (textarea) => {
2813
- textarea.style.height = 'auto';
2814
- const newHeight = textarea.scrollHeight + 'px';
2815
- state.height = textarea.value.length === 0 ? undefined : newHeight;
2808
+ const updateHeight = (textarea, hiddenDiv) => {
2809
+ if (!textarea || !hiddenDiv)
2810
+ return;
2811
+ // Copy font properties from textarea to hidden div
2812
+ const computedStyle = window.getComputedStyle(textarea);
2813
+ hiddenDiv.style.fontFamily = computedStyle.fontFamily;
2814
+ hiddenDiv.style.fontSize = computedStyle.fontSize;
2815
+ hiddenDiv.style.lineHeight = computedStyle.lineHeight;
2816
+ // Copy padding from textarea (important for accurate measurement)
2817
+ hiddenDiv.style.paddingTop = computedStyle.paddingTop;
2818
+ hiddenDiv.style.paddingRight = computedStyle.paddingRight;
2819
+ hiddenDiv.style.paddingBottom = computedStyle.paddingBottom;
2820
+ hiddenDiv.style.paddingLeft = computedStyle.paddingLeft;
2821
+ // Handle text wrapping
2822
+ if (textarea.getAttribute('wrap') === 'off') {
2823
+ hiddenDiv.style.overflowWrap = 'normal';
2824
+ hiddenDiv.style.whiteSpace = 'pre';
2825
+ }
2826
+ else {
2827
+ hiddenDiv.style.overflowWrap = 'break-word';
2828
+ hiddenDiv.style.whiteSpace = 'pre-wrap';
2829
+ }
2830
+ // Set content with extra newline for measurement
2831
+ hiddenDiv.textContent = textarea.value + '\n';
2832
+ const content = hiddenDiv.innerHTML.replace(/\n/g, '<br>');
2833
+ hiddenDiv.innerHTML = content;
2834
+ // Set width to match textarea
2835
+ if (textarea.offsetWidth > 0) {
2836
+ hiddenDiv.style.width = textarea.offsetWidth + 'px';
2837
+ }
2838
+ else {
2839
+ hiddenDiv.style.width = window.innerWidth / 2 + 'px';
2840
+ }
2841
+ // Get the original/natural height of the textarea
2842
+ const originalHeight = textarea.offsetHeight;
2843
+ const measuredHeight = hiddenDiv.offsetHeight;
2844
+ // Key logic: Only set custom height when content requires MORE space than original height
2845
+ // This matches the Materialize CSS reference behavior
2846
+ if (originalHeight <= measuredHeight) {
2847
+ state.height = measuredHeight + 'px';
2848
+ }
2849
+ else {
2850
+ // Single line content or content that fits in original height - let CSS handle it
2851
+ state.height = undefined;
2852
+ }
2816
2853
  };
2817
2854
  const isControlled = (attrs) => attrs.value !== undefined && attrs.oninput !== undefined;
2818
2855
  return {
@@ -2828,92 +2865,117 @@ const TextArea = () => {
2828
2865
  const { className = 'col s12', helperText, iconName, id = state.id, value, placeholder, isMandatory, label, maxLength, oninput, onchange, onkeydown, onkeypress, onkeyup, onblur, style } = attrs, params = __rest(attrs, ["className", "helperText", "iconName", "id", "value", "placeholder", "isMandatory", "label", "maxLength", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "onblur", "style"]);
2829
2866
  const controlled = isControlled(attrs);
2830
2867
  const currentValue = controlled ? value || '' : state.internalValue;
2831
- return m('.input-field', { className, style }, [
2832
- iconName ? m('i.material-icons.prefix', iconName) : '',
2833
- m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, value: controlled ? currentValue : undefined, style: {
2834
- height: state.height,
2835
- }, oncreate: ({ dom }) => {
2836
- const textarea = (state.textarea = dom);
2837
- // For uncontrolled mode, set initial value only
2838
- if (!controlled && attrs.defaultValue !== undefined) {
2839
- textarea.value = String(attrs.defaultValue);
2840
- }
2841
- updateHeight(textarea);
2842
- // Update character count state for counter component
2843
- if (maxLength) {
2844
- state.currentLength = textarea.value.length;
2845
- m.redraw();
2846
- }
2847
- }, onupdate: ({ dom }) => {
2848
- const textarea = dom;
2849
- if (state.height)
2850
- textarea.style.height = state.height;
2851
- // No need to manually sync in onupdate since value attribute handles it
2852
- }, onfocus: () => {
2853
- state.active = true;
2854
- }, oninput: (e) => {
2855
- state.active = true;
2856
- state.hasInteracted = false;
2857
- const target = e.target;
2858
- // Update height for auto-resize
2859
- updateHeight(target);
2860
- // Update character count
2861
- if (maxLength) {
2862
- state.currentLength = target.value.length;
2863
- state.hasInteracted = target.value.length > 0;
2864
- }
2865
- // Update internal state for uncontrolled mode
2866
- if (!controlled) {
2867
- state.internalValue = target.value;
2868
- }
2869
- // Call oninput handler
2870
- if (oninput) {
2871
- oninput(target.value);
2872
- }
2873
- }, onblur: (e) => {
2874
- state.active = false;
2875
- // const target = e.target as HTMLTextAreaElement;
2876
- state.hasInteracted = true;
2877
- // Call original onblur if provided
2878
- if (onblur) {
2879
- onblur(e);
2880
- }
2881
- if (onchange && state.textarea) {
2882
- onchange(state.textarea.value);
2883
- }
2884
- }, onkeyup: onkeyup
2885
- ? (ev) => {
2886
- onkeyup(ev, ev.target.value);
2887
- }
2888
- : undefined, onkeydown: onkeydown
2889
- ? (ev) => {
2890
- onkeydown(ev, ev.target.value);
2868
+ return [
2869
+ // Hidden div for height measurement - positioned outside the input-field
2870
+ m('.hiddendiv', {
2871
+ style: {
2872
+ visibility: 'hidden',
2873
+ position: 'absolute',
2874
+ top: '0',
2875
+ left: '0',
2876
+ zIndex: '-1',
2877
+ whiteSpace: 'pre-wrap',
2878
+ wordWrap: 'break-word',
2879
+ overflowWrap: 'break-word',
2880
+ },
2881
+ oncreate: ({ dom }) => {
2882
+ const hiddenDiv = dom;
2883
+ if (state.textarea) {
2884
+ updateHeight(state.textarea, hiddenDiv);
2891
2885
  }
2892
- : undefined, onkeypress: onkeypress
2893
- ? (ev) => {
2894
- onkeypress(ev, ev.target.value);
2886
+ },
2887
+ onupdate: ({ dom }) => {
2888
+ const hiddenDiv = dom;
2889
+ if (state.textarea) {
2890
+ updateHeight(state.textarea, hiddenDiv);
2895
2891
  }
2896
- : undefined })),
2897
- m(Label, {
2898
- label,
2899
- id,
2900
- isMandatory,
2901
- isActive: currentValue || placeholder || state.active,
2902
- initialValue: currentValue !== '',
2903
- }),
2904
- m(HelperText, {
2905
- helperText,
2906
- dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
2907
- dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
2892
+ },
2908
2893
  }),
2909
- maxLength
2910
- ? m(CharacterCounter, {
2911
- currentLength: state.currentLength,
2912
- maxLength,
2913
- show: state.currentLength > 0,
2914
- })
2915
- : undefined,
2916
- ]);
2894
+ m('.input-field', { className, style }, [
2895
+ iconName ? m('i.material-icons.prefix', iconName) : '',
2896
+ m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, value: controlled ? currentValue : undefined, style: {
2897
+ height: state.height,
2898
+ }, oncreate: ({ dom }) => {
2899
+ const textarea = (state.textarea = dom);
2900
+ // For uncontrolled mode, set initial value only
2901
+ if (!controlled && attrs.defaultValue !== undefined) {
2902
+ textarea.value = String(attrs.defaultValue);
2903
+ }
2904
+ // Height will be calculated by hidden div
2905
+ // Update character count state for counter component
2906
+ if (maxLength) {
2907
+ state.currentLength = textarea.value.length;
2908
+ }
2909
+ }, onupdate: ({ dom }) => {
2910
+ const textarea = dom;
2911
+ if (state.height)
2912
+ textarea.style.height = state.height;
2913
+ // No need to manually sync in onupdate since value attribute handles it
2914
+ }, onfocus: () => {
2915
+ state.active = true;
2916
+ }, oninput: (e) => {
2917
+ state.active = true;
2918
+ state.hasInteracted = false;
2919
+ const target = e.target;
2920
+ // Height will be recalculated by hidden div on next update
2921
+ // Update character count
2922
+ if (maxLength) {
2923
+ state.currentLength = target.value.length;
2924
+ state.hasInteracted = target.value.length > 0;
2925
+ }
2926
+ // Update internal state for uncontrolled mode
2927
+ if (!controlled) {
2928
+ state.internalValue = target.value;
2929
+ }
2930
+ // Call oninput handler
2931
+ if (oninput) {
2932
+ oninput(target.value);
2933
+ }
2934
+ }, onblur: (e) => {
2935
+ state.active = false;
2936
+ // const target = e.target as HTMLTextAreaElement;
2937
+ state.hasInteracted = true;
2938
+ // Call original onblur if provided
2939
+ if (onblur) {
2940
+ onblur(e);
2941
+ }
2942
+ if (onchange && state.textarea) {
2943
+ onchange(state.textarea.value);
2944
+ }
2945
+ }, onkeyup: onkeyup
2946
+ ? (ev) => {
2947
+ onkeyup(ev, ev.target.value);
2948
+ }
2949
+ : undefined, onkeydown: onkeydown
2950
+ ? (ev) => {
2951
+ onkeydown(ev, ev.target.value);
2952
+ }
2953
+ : undefined, onkeypress: onkeypress
2954
+ ? (ev) => {
2955
+ onkeypress(ev, ev.target.value);
2956
+ }
2957
+ : undefined })),
2958
+ m(Label, {
2959
+ label,
2960
+ id,
2961
+ isMandatory,
2962
+ isActive: currentValue || placeholder || state.active,
2963
+ initialValue: currentValue !== '',
2964
+ }),
2965
+ m(HelperText, {
2966
+ helperText,
2967
+ dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
2968
+ dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
2969
+ }),
2970
+ maxLength
2971
+ ? m(CharacterCounter, {
2972
+ currentLength: state.currentLength,
2973
+ maxLength,
2974
+ show: state.currentLength > 0,
2975
+ })
2976
+ : undefined,
2977
+ ]),
2978
+ ];
2917
2979
  },
2918
2980
  };
2919
2981
  };
@@ -3085,7 +3147,9 @@ const InputField = (type, defaultClass = '') => () => {
3085
3147
  state.isValid = true;
3086
3148
  }
3087
3149
  }
3088
- else if ((type === 'email' || type === 'url') && target.classList.contains('invalid') && target.value.length > 0) {
3150
+ else if ((type === 'email' || type === 'url') &&
3151
+ target.classList.contains('invalid') &&
3152
+ target.value.length > 0) {
3089
3153
  // Clear native validation errors if user is typing and input becomes valid
3090
3154
  if (target.validity.valid) {
3091
3155
  target.classList.remove('invalid');
@@ -3998,7 +4062,7 @@ const Dropdown = () => {
3998
4062
  m.redraw();
3999
4063
  }
4000
4064
  };
4001
- const handleKeyDown = (e, items, onchange) => {
4065
+ const handleKeyDown = (e, items) => {
4002
4066
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
4003
4067
  switch (e.key) {
4004
4068
  case 'ArrowDown':
@@ -4008,15 +4072,15 @@ const Dropdown = () => {
4008
4072
  state.focusedIndex = availableItems.length > 0 ? 0 : -1;
4009
4073
  }
4010
4074
  else {
4011
- state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
4075
+ state.focusedIndex = (state.focusedIndex + 1) % availableItems.length;
4012
4076
  }
4013
- break;
4077
+ return undefined;
4014
4078
  case 'ArrowUp':
4015
4079
  e.preventDefault();
4016
4080
  if (state.isOpen) {
4017
- state.focusedIndex = Math.max(state.focusedIndex - 1, 0);
4081
+ state.focusedIndex = state.focusedIndex <= 0 ? availableItems.length - 1 : state.focusedIndex - 1;
4018
4082
  }
4019
- break;
4083
+ return undefined;
4020
4084
  case 'Enter':
4021
4085
  case ' ':
4022
4086
  e.preventDefault();
@@ -4025,18 +4089,20 @@ const Dropdown = () => {
4025
4089
  const value = (selectedItem.id || selectedItem.label);
4026
4090
  state.isOpen = false;
4027
4091
  state.focusedIndex = -1;
4028
- return value; // Return value to be handled in view
4092
+ return value;
4029
4093
  }
4030
4094
  else if (!state.isOpen) {
4031
4095
  state.isOpen = true;
4032
4096
  state.focusedIndex = availableItems.length > 0 ? 0 : -1;
4033
4097
  }
4034
- break;
4098
+ return undefined;
4035
4099
  case 'Escape':
4036
4100
  e.preventDefault();
4037
4101
  state.isOpen = false;
4038
4102
  state.focusedIndex = -1;
4039
- break;
4103
+ return undefined;
4104
+ default:
4105
+ return undefined;
4040
4106
  }
4041
4107
  };
4042
4108
  return {
@@ -4069,7 +4135,9 @@ const Dropdown = () => {
4069
4135
  }
4070
4136
  };
4071
4137
  const selectedItem = currentCheckedId
4072
- ? items.filter((i) => (i.id ? i.id === currentCheckedId : i.label === currentCheckedId)).shift()
4138
+ ? items
4139
+ .filter((i) => (i.id ? i.id === currentCheckedId : i.label === currentCheckedId))
4140
+ .shift()
4073
4141
  : undefined;
4074
4142
  const title = selectedItem ? selectedItem.label : label || 'Select';
4075
4143
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
@@ -4077,12 +4145,14 @@ const Dropdown = () => {
4077
4145
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
4078
4146
  m(HelperText, { helperText }),
4079
4147
  m('.select-wrapper', {
4080
- onkeydown: disabled ? undefined : (e) => {
4081
- const selectedValue = handleKeyDown(e, items);
4082
- if (selectedValue) {
4083
- handleSelection(selectedValue);
4084
- }
4085
- },
4148
+ onkeydown: disabled
4149
+ ? undefined
4150
+ : (e) => {
4151
+ const selectedValue = handleKeyDown(e, items);
4152
+ if (selectedValue) {
4153
+ handleSelection(selectedValue);
4154
+ }
4155
+ },
4086
4156
  tabindex: disabled ? -1 : 0,
4087
4157
  'aria-expanded': state.isOpen ? 'true' : 'false',
4088
4158
  'aria-haspopup': 'listbox',