VTextField.mjs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { Fragment as _Fragment, withDirectives as _withDirectives, createVNode as _createVNode, mergeProps as _mergeProps, resolveDirective as _resolveDirective } from "vue";
  2. // Styles
  3. import "./VTextField.css";
  4. // Components
  5. import { VCounter } from "../VCounter/VCounter.mjs";
  6. import { filterFieldProps, makeVFieldProps, VField } from "../VField/VField.mjs";
  7. import { makeVInputProps, VInput } from "../VInput/VInput.mjs"; // Composables
  8. import { useFocus } from "../../composables/focus.mjs";
  9. import { forwardRefs } from "../../composables/forwardRefs.mjs";
  10. import { useProxiedModel } from "../../composables/proxiedModel.mjs"; // Directives
  11. import Intersect from "../../directives/intersect/index.mjs"; // Utilities
  12. import { cloneVNode, computed, nextTick, ref } from 'vue';
  13. import { callEvent, filterInputAttrs, genericComponent, propsFactory, useRender } from "../../util/index.mjs"; // Types
  14. const activeTypes = ['color', 'file', 'time', 'date', 'datetime-local', 'week', 'month'];
  15. export const makeVTextFieldProps = propsFactory({
  16. autofocus: Boolean,
  17. counter: [Boolean, Number, String],
  18. counterValue: [Number, Function],
  19. prefix: String,
  20. placeholder: String,
  21. persistentPlaceholder: Boolean,
  22. persistentCounter: Boolean,
  23. suffix: String,
  24. role: String,
  25. type: {
  26. type: String,
  27. default: 'text'
  28. },
  29. modelModifiers: Object,
  30. ...makeVInputProps(),
  31. ...makeVFieldProps()
  32. }, 'VTextField');
  33. export const VTextField = genericComponent()({
  34. name: 'VTextField',
  35. directives: {
  36. Intersect
  37. },
  38. inheritAttrs: false,
  39. props: makeVTextFieldProps(),
  40. emits: {
  41. 'click:control': e => true,
  42. 'mousedown:control': e => true,
  43. 'update:focused': focused => true,
  44. 'update:modelValue': val => true
  45. },
  46. setup(props, _ref) {
  47. let {
  48. attrs,
  49. emit,
  50. slots
  51. } = _ref;
  52. const model = useProxiedModel(props, 'modelValue');
  53. const {
  54. isFocused,
  55. focus,
  56. blur
  57. } = useFocus(props);
  58. const counterValue = computed(() => {
  59. return typeof props.counterValue === 'function' ? props.counterValue(model.value) : typeof props.counterValue === 'number' ? props.counterValue : (model.value ?? '').toString().length;
  60. });
  61. const max = computed(() => {
  62. if (attrs.maxlength) return attrs.maxlength;
  63. if (!props.counter || typeof props.counter !== 'number' && typeof props.counter !== 'string') return undefined;
  64. return props.counter;
  65. });
  66. const isPlainOrUnderlined = computed(() => ['plain', 'underlined'].includes(props.variant));
  67. function onIntersect(isIntersecting, entries) {
  68. if (!props.autofocus || !isIntersecting) return;
  69. entries[0].target?.focus?.();
  70. }
  71. const vInputRef = ref();
  72. const vFieldRef = ref();
  73. const inputRef = ref();
  74. const isActive = computed(() => activeTypes.includes(props.type) || props.persistentPlaceholder || isFocused.value || props.active);
  75. function onFocus() {
  76. if (inputRef.value !== document.activeElement) {
  77. inputRef.value?.focus();
  78. }
  79. if (!isFocused.value) focus();
  80. }
  81. function onControlMousedown(e) {
  82. emit('mousedown:control', e);
  83. if (e.target === inputRef.value) return;
  84. onFocus();
  85. e.preventDefault();
  86. }
  87. function onControlClick(e) {
  88. onFocus();
  89. emit('click:control', e);
  90. }
  91. function onClear(e) {
  92. e.stopPropagation();
  93. onFocus();
  94. nextTick(() => {
  95. model.value = null;
  96. callEvent(props['onClick:clear'], e);
  97. });
  98. }
  99. function onInput(e) {
  100. const el = e.target;
  101. model.value = el.value;
  102. if (props.modelModifiers?.trim && ['text', 'search', 'password', 'tel', 'url'].includes(props.type)) {
  103. const caretPosition = [el.selectionStart, el.selectionEnd];
  104. nextTick(() => {
  105. el.selectionStart = caretPosition[0];
  106. el.selectionEnd = caretPosition[1];
  107. });
  108. }
  109. }
  110. useRender(() => {
  111. const hasCounter = !!(slots.counter || props.counter !== false && props.counter != null);
  112. const hasDetails = !!(hasCounter || slots.details);
  113. const [rootAttrs, inputAttrs] = filterInputAttrs(attrs);
  114. const {
  115. modelValue: _,
  116. ...inputProps
  117. } = VInput.filterProps(props);
  118. const fieldProps = filterFieldProps(props);
  119. return _createVNode(VInput, _mergeProps({
  120. "ref": vInputRef,
  121. "modelValue": model.value,
  122. "onUpdate:modelValue": $event => model.value = $event,
  123. "class": ['v-text-field', {
  124. 'v-text-field--prefixed': props.prefix,
  125. 'v-text-field--suffixed': props.suffix,
  126. 'v-input--plain-underlined': isPlainOrUnderlined.value
  127. }, props.class],
  128. "style": props.style
  129. }, rootAttrs, inputProps, {
  130. "centerAffix": !isPlainOrUnderlined.value,
  131. "focused": isFocused.value
  132. }), {
  133. ...slots,
  134. default: _ref2 => {
  135. let {
  136. id,
  137. isDisabled,
  138. isDirty,
  139. isReadonly,
  140. isValid
  141. } = _ref2;
  142. return _createVNode(VField, _mergeProps({
  143. "ref": vFieldRef,
  144. "onMousedown": onControlMousedown,
  145. "onClick": onControlClick,
  146. "onClick:clear": onClear,
  147. "onClick:prependInner": props['onClick:prependInner'],
  148. "onClick:appendInner": props['onClick:appendInner'],
  149. "role": props.role
  150. }, fieldProps, {
  151. "id": id.value,
  152. "active": isActive.value || isDirty.value,
  153. "dirty": isDirty.value || props.dirty,
  154. "disabled": isDisabled.value,
  155. "focused": isFocused.value,
  156. "error": isValid.value === false
  157. }), {
  158. ...slots,
  159. default: _ref3 => {
  160. let {
  161. props: {
  162. class: fieldClass,
  163. ...slotProps
  164. }
  165. } = _ref3;
  166. const inputNode = _withDirectives(_createVNode("input", _mergeProps({
  167. "ref": inputRef,
  168. "value": model.value,
  169. "onInput": onInput,
  170. "autofocus": props.autofocus,
  171. "readonly": isReadonly.value,
  172. "disabled": isDisabled.value,
  173. "name": props.name,
  174. "placeholder": props.placeholder,
  175. "size": 1,
  176. "type": props.type,
  177. "onFocus": onFocus,
  178. "onBlur": blur
  179. }, slotProps, inputAttrs), null), [[_resolveDirective("intersect"), {
  180. handler: onIntersect
  181. }, null, {
  182. once: true
  183. }]]);
  184. return _createVNode(_Fragment, null, [props.prefix && _createVNode("span", {
  185. "class": "v-text-field__prefix"
  186. }, [_createVNode("span", {
  187. "class": "v-text-field__prefix__text"
  188. }, [props.prefix])]), slots.default ? _createVNode("div", {
  189. "class": fieldClass,
  190. "data-no-activator": ""
  191. }, [slots.default(), inputNode]) : cloneVNode(inputNode, {
  192. class: fieldClass
  193. }), props.suffix && _createVNode("span", {
  194. "class": "v-text-field__suffix"
  195. }, [_createVNode("span", {
  196. "class": "v-text-field__suffix__text"
  197. }, [props.suffix])])]);
  198. }
  199. });
  200. },
  201. details: hasDetails ? slotProps => _createVNode(_Fragment, null, [slots.details?.(slotProps), hasCounter && _createVNode(_Fragment, null, [_createVNode("span", null, null), _createVNode(VCounter, {
  202. "active": props.persistentCounter || isFocused.value,
  203. "value": counterValue.value,
  204. "max": max.value,
  205. "disabled": props.disabled
  206. }, slots.counter)])]) : undefined
  207. });
  208. });
  209. return forwardRefs({}, vInputRef, vFieldRef, inputRef);
  210. }
  211. });
  212. //# sourceMappingURL=VTextField.mjs.map