mithril-materialized 3.2.1 → 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/index.umd.js CHANGED
@@ -2793,10 +2793,6 @@
2793
2793
  return m('span.character-counter', {
2794
2794
  style: {
2795
2795
  color: isOverLimit ? '#F44336' : '#9e9e9e',
2796
- fontSize: '12px',
2797
- display: 'block',
2798
- textAlign: 'right',
2799
- marginTop: '8px',
2800
2796
  },
2801
2797
  }, `${currentLength}/${maxLength}`);
2802
2798
  },
@@ -2813,10 +2809,51 @@
2813
2809
  textarea: undefined,
2814
2810
  internalValue: '',
2815
2811
  };
2816
- const updateHeight = (textarea) => {
2817
- textarea.style.height = 'auto';
2818
- const newHeight = textarea.scrollHeight + 'px';
2819
- state.height = textarea.value.length === 0 ? undefined : newHeight;
2812
+ const updateHeight = (textarea, hiddenDiv) => {
2813
+ if (!textarea || !hiddenDiv)
2814
+ return;
2815
+ // Copy font properties from textarea to hidden div
2816
+ const computedStyle = window.getComputedStyle(textarea);
2817
+ hiddenDiv.style.fontFamily = computedStyle.fontFamily;
2818
+ hiddenDiv.style.fontSize = computedStyle.fontSize;
2819
+ hiddenDiv.style.lineHeight = computedStyle.lineHeight;
2820
+ // Copy padding from textarea (important for accurate measurement)
2821
+ hiddenDiv.style.paddingTop = computedStyle.paddingTop;
2822
+ hiddenDiv.style.paddingRight = computedStyle.paddingRight;
2823
+ hiddenDiv.style.paddingBottom = computedStyle.paddingBottom;
2824
+ hiddenDiv.style.paddingLeft = computedStyle.paddingLeft;
2825
+ // Handle text wrapping
2826
+ if (textarea.getAttribute('wrap') === 'off') {
2827
+ hiddenDiv.style.overflowWrap = 'normal';
2828
+ hiddenDiv.style.whiteSpace = 'pre';
2829
+ }
2830
+ else {
2831
+ hiddenDiv.style.overflowWrap = 'break-word';
2832
+ hiddenDiv.style.whiteSpace = 'pre-wrap';
2833
+ }
2834
+ // Set content with extra newline for measurement
2835
+ hiddenDiv.textContent = textarea.value + '\n';
2836
+ const content = hiddenDiv.innerHTML.replace(/\n/g, '<br>');
2837
+ hiddenDiv.innerHTML = content;
2838
+ // Set width to match textarea
2839
+ if (textarea.offsetWidth > 0) {
2840
+ hiddenDiv.style.width = textarea.offsetWidth + 'px';
2841
+ }
2842
+ else {
2843
+ hiddenDiv.style.width = window.innerWidth / 2 + 'px';
2844
+ }
2845
+ // Get the original/natural height of the textarea
2846
+ const originalHeight = textarea.offsetHeight;
2847
+ const measuredHeight = hiddenDiv.offsetHeight;
2848
+ // Key logic: Only set custom height when content requires MORE space than original height
2849
+ // This matches the Materialize CSS reference behavior
2850
+ if (originalHeight <= measuredHeight) {
2851
+ state.height = measuredHeight + 'px';
2852
+ }
2853
+ else {
2854
+ // Single line content or content that fits in original height - let CSS handle it
2855
+ state.height = undefined;
2856
+ }
2820
2857
  };
2821
2858
  const isControlled = (attrs) => attrs.value !== undefined && attrs.oninput !== undefined;
2822
2859
  return {
@@ -2832,92 +2869,117 @@
2832
2869
  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
2870
  const controlled = isControlled(attrs);
2834
2871
  const currentValue = controlled ? value || '' : state.internalValue;
2835
- return m('.input-field', { className, style }, [
2836
- iconName ? m('i.material-icons.prefix', iconName) : '',
2837
- m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, value: controlled ? currentValue : undefined, style: {
2838
- height: state.height,
2839
- }, oncreate: ({ dom }) => {
2840
- const textarea = (state.textarea = dom);
2841
- // For uncontrolled mode, set initial value only
2842
- if (!controlled && attrs.defaultValue !== undefined) {
2843
- textarea.value = String(attrs.defaultValue);
2844
- }
2845
- updateHeight(textarea);
2846
- // Update character count state for counter component
2847
- if (maxLength) {
2848
- state.currentLength = textarea.value.length;
2849
- m.redraw();
2850
- }
2851
- }, onupdate: ({ dom }) => {
2852
- const textarea = dom;
2853
- if (state.height)
2854
- textarea.style.height = state.height;
2855
- // No need to manually sync in onupdate since value attribute handles it
2856
- }, onfocus: () => {
2857
- state.active = true;
2858
- }, oninput: (e) => {
2859
- state.active = true;
2860
- state.hasInteracted = false;
2861
- const target = e.target;
2862
- // Update height for auto-resize
2863
- updateHeight(target);
2864
- // Update character count
2865
- if (maxLength) {
2866
- state.currentLength = target.value.length;
2867
- state.hasInteracted = target.value.length > 0;
2868
- }
2869
- // Update internal state for uncontrolled mode
2870
- if (!controlled) {
2871
- state.internalValue = target.value;
2872
- }
2873
- // Call oninput handler
2874
- if (oninput) {
2875
- oninput(target.value);
2876
- }
2877
- }, onblur: (e) => {
2878
- state.active = false;
2879
- // const target = e.target as HTMLTextAreaElement;
2880
- state.hasInteracted = true;
2881
- // Call original onblur if provided
2882
- if (onblur) {
2883
- onblur(e);
2884
- }
2885
- if (onchange && state.textarea) {
2886
- onchange(state.textarea.value);
2887
- }
2888
- }, onkeyup: onkeyup
2889
- ? (ev) => {
2890
- onkeyup(ev, ev.target.value);
2891
- }
2892
- : undefined, onkeydown: onkeydown
2893
- ? (ev) => {
2894
- onkeydown(ev, ev.target.value);
2872
+ return [
2873
+ // Hidden div for height measurement - positioned outside the input-field
2874
+ m('.hiddendiv', {
2875
+ style: {
2876
+ visibility: 'hidden',
2877
+ position: 'absolute',
2878
+ top: '0',
2879
+ left: '0',
2880
+ zIndex: '-1',
2881
+ whiteSpace: 'pre-wrap',
2882
+ wordWrap: 'break-word',
2883
+ overflowWrap: 'break-word',
2884
+ },
2885
+ oncreate: ({ dom }) => {
2886
+ const hiddenDiv = dom;
2887
+ if (state.textarea) {
2888
+ updateHeight(state.textarea, hiddenDiv);
2895
2889
  }
2896
- : undefined, onkeypress: onkeypress
2897
- ? (ev) => {
2898
- onkeypress(ev, ev.target.value);
2890
+ },
2891
+ onupdate: ({ dom }) => {
2892
+ const hiddenDiv = dom;
2893
+ if (state.textarea) {
2894
+ updateHeight(state.textarea, hiddenDiv);
2899
2895
  }
2900
- : undefined })),
2901
- m(Label, {
2902
- label,
2903
- id,
2904
- isMandatory,
2905
- isActive: currentValue || placeholder || state.active,
2906
- initialValue: currentValue !== '',
2907
- }),
2908
- m(HelperText, {
2909
- helperText,
2910
- dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
2911
- dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
2896
+ },
2912
2897
  }),
2913
- maxLength
2914
- ? m(CharacterCounter, {
2915
- currentLength: state.currentLength,
2916
- maxLength,
2917
- show: state.currentLength > 0,
2918
- })
2919
- : undefined,
2920
- ]);
2898
+ m('.input-field', { className, style }, [
2899
+ iconName ? m('i.material-icons.prefix', iconName) : '',
2900
+ m('textarea.materialize-textarea', Object.assign(Object.assign({}, params), { id, tabindex: 0, value: controlled ? currentValue : undefined, style: {
2901
+ height: state.height,
2902
+ }, oncreate: ({ dom }) => {
2903
+ const textarea = (state.textarea = dom);
2904
+ // For uncontrolled mode, set initial value only
2905
+ if (!controlled && attrs.defaultValue !== undefined) {
2906
+ textarea.value = String(attrs.defaultValue);
2907
+ }
2908
+ // Height will be calculated by hidden div
2909
+ // Update character count state for counter component
2910
+ if (maxLength) {
2911
+ state.currentLength = textarea.value.length;
2912
+ }
2913
+ }, onupdate: ({ dom }) => {
2914
+ const textarea = dom;
2915
+ if (state.height)
2916
+ textarea.style.height = state.height;
2917
+ // No need to manually sync in onupdate since value attribute handles it
2918
+ }, onfocus: () => {
2919
+ state.active = true;
2920
+ }, oninput: (e) => {
2921
+ state.active = true;
2922
+ state.hasInteracted = false;
2923
+ const target = e.target;
2924
+ // Height will be recalculated by hidden div on next update
2925
+ // Update character count
2926
+ if (maxLength) {
2927
+ state.currentLength = target.value.length;
2928
+ state.hasInteracted = target.value.length > 0;
2929
+ }
2930
+ // Update internal state for uncontrolled mode
2931
+ if (!controlled) {
2932
+ state.internalValue = target.value;
2933
+ }
2934
+ // Call oninput handler
2935
+ if (oninput) {
2936
+ oninput(target.value);
2937
+ }
2938
+ }, onblur: (e) => {
2939
+ state.active = false;
2940
+ // const target = e.target as HTMLTextAreaElement;
2941
+ state.hasInteracted = true;
2942
+ // Call original onblur if provided
2943
+ if (onblur) {
2944
+ onblur(e);
2945
+ }
2946
+ if (onchange && state.textarea) {
2947
+ onchange(state.textarea.value);
2948
+ }
2949
+ }, onkeyup: onkeyup
2950
+ ? (ev) => {
2951
+ onkeyup(ev, ev.target.value);
2952
+ }
2953
+ : undefined, onkeydown: onkeydown
2954
+ ? (ev) => {
2955
+ onkeydown(ev, ev.target.value);
2956
+ }
2957
+ : undefined, onkeypress: onkeypress
2958
+ ? (ev) => {
2959
+ onkeypress(ev, ev.target.value);
2960
+ }
2961
+ : undefined })),
2962
+ m(Label, {
2963
+ label,
2964
+ id,
2965
+ isMandatory,
2966
+ isActive: currentValue || placeholder || state.active,
2967
+ initialValue: currentValue !== '',
2968
+ }),
2969
+ m(HelperText, {
2970
+ helperText,
2971
+ dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
2972
+ dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
2973
+ }),
2974
+ maxLength
2975
+ ? m(CharacterCounter, {
2976
+ currentLength: state.currentLength,
2977
+ maxLength,
2978
+ show: state.currentLength > 0,
2979
+ })
2980
+ : undefined,
2981
+ ]),
2982
+ ];
2921
2983
  },
2922
2984
  };
2923
2985
  };
@@ -3089,7 +3151,9 @@
3089
3151
  state.isValid = true;
3090
3152
  }
3091
3153
  }
3092
- else if ((type === 'email' || type === 'url') && target.classList.contains('invalid') && target.value.length > 0) {
3154
+ else if ((type === 'email' || type === 'url') &&
3155
+ target.classList.contains('invalid') &&
3156
+ target.value.length > 0) {
3093
3157
  // Clear native validation errors if user is typing and input becomes valid
3094
3158
  if (target.validity.valid) {
3095
3159
  target.classList.remove('invalid');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mithril-materialized",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "description": "A materialize library for mithril.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -86,16 +86,16 @@
86
86
  "concurrently": "^9.2.1",
87
87
  "express": "^5.1.0",
88
88
  "identity-obj-proxy": "^3.0.0",
89
- "jest": "^30.1.1",
90
- "jest-environment-jsdom": "^30.1.1",
89
+ "jest": "^30.1.2",
90
+ "jest-environment-jsdom": "^30.1.2",
91
91
  "js-yaml": "^4.1.0",
92
92
  "rimraf": "^6.0.1",
93
- "rollup": "^4.49.0",
93
+ "rollup": "^4.50.0",
94
94
  "rollup-plugin-postcss": "^4.0.2",
95
95
  "sass": "^1.91.0",
96
96
  "ts-jest": "^29.4.1",
97
97
  "tslib": "^2.8.1",
98
- "typedoc": "^0.28.11",
98
+ "typedoc": "^0.28.12",
99
99
  "typescript": "^5.9.2"
100
100
  }
101
101
  }
@@ -380,6 +380,9 @@ textarea {
380
380
  /* Character Counter */
381
381
  .character-counter {
382
382
  min-height: 18px;
383
+ font-size: 12px;
384
+ display: block;
385
+ text-align: right;
383
386
  }
384
387
 
385
388
  /* Input Clear Button */