UNPKG

vuetify

Version:

Vue Material Component Framework

329 lines (328 loc) 11.8 kB
import { createElementVNode as _createElementVNode, createVNode as _createVNode, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, vShow as _vShow, withDirectives as _withDirectives, Fragment as _Fragment, mergeProps as _mergeProps } from "vue"; // Styles import "./VField.css"; // Components import { VFieldLabel } from "./VFieldLabel.js"; import { VExpandXTransition } from "../transitions/index.js"; import { VDefaultsProvider } from "../VDefaultsProvider/index.js"; import { useInputIcon } from "../VInput/InputIcon.js"; // Composables import { useBackgroundColor, useTextColor } from "../../composables/color.js"; import { makeComponentProps } from "../../composables/component.js"; import { makeFocusProps, useFocus } from "../../composables/focus.js"; import { IconValue } from "../../composables/icons.js"; import { LoaderSlot, makeLoaderProps, useLoader } from "../../composables/loader.js"; import { useRtl } from "../../composables/locale.js"; import { makeRoundedProps, useRounded } from "../../composables/rounded.js"; import { makeThemeProps, provideTheme } from "../../composables/theme.js"; // Utilities import { computed, ref, toRef, useId, watch } from 'vue'; import { animate, convertToUnit, EventProp, genericComponent, nullifyTransforms, propsFactory, standardEasing, useRender } from "../../util/index.js"; // Types const allowedVariants = ['underlined', 'outlined', 'filled', 'solo', 'solo-inverted', 'solo-filled', 'plain']; export const makeVFieldProps = propsFactory({ appendInnerIcon: IconValue, bgColor: String, clearable: Boolean, clearIcon: { type: IconValue, default: '$clear' }, active: Boolean, centerAffix: { type: Boolean, default: undefined }, color: String, baseColor: String, dirty: Boolean, disabled: { type: Boolean, default: null }, glow: Boolean, error: Boolean, flat: Boolean, iconColor: [Boolean, String], label: String, persistentClear: Boolean, prependInnerIcon: IconValue, reverse: Boolean, singleLine: Boolean, variant: { type: String, default: 'filled', validator: v => allowedVariants.includes(v) }, 'onClick:clear': EventProp(), 'onClick:appendInner': EventProp(), 'onClick:prependInner': EventProp(), ...makeComponentProps(), ...makeLoaderProps(), ...makeRoundedProps(), ...makeThemeProps() }, 'VField'); export const VField = genericComponent()({ name: 'VField', inheritAttrs: false, props: { id: String, ...makeFocusProps(), ...makeVFieldProps() }, emits: { 'update:focused': focused => true, 'update:modelValue': value => true }, setup(props, _ref) { let { attrs, emit, slots } = _ref; const { themeClasses } = provideTheme(props); const { loaderClasses } = useLoader(props); const { focusClasses, isFocused, focus, blur } = useFocus(props); const { InputIcon } = useInputIcon(props); const { roundedClasses } = useRounded(props); const { rtlClasses } = useRtl(); const isActive = toRef(() => props.dirty || props.active); const hasLabel = toRef(() => !!(props.label || slots.label)); const hasFloatingLabel = toRef(() => !props.singleLine && hasLabel.value); const uid = useId(); const id = computed(() => props.id || `input-${uid}`); const messagesId = toRef(() => `${id.value}-messages`); const labelRef = ref(); const floatingLabelRef = ref(); const controlRef = ref(); const isPlainOrUnderlined = computed(() => ['plain', 'underlined'].includes(props.variant)); const color = computed(() => { return props.error || props.disabled ? undefined : isActive.value && isFocused.value ? props.color : props.baseColor; }); const iconColor = computed(() => { if (!props.iconColor || props.glow && !isFocused.value) return undefined; return props.iconColor === true ? color.value : props.iconColor; }); const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(() => props.bgColor); const { textColorClasses, textColorStyles } = useTextColor(color); watch(isActive, val => { if (hasFloatingLabel.value) { const el = labelRef.value.$el; const targetEl = floatingLabelRef.value.$el; requestAnimationFrame(() => { const rect = nullifyTransforms(el); const targetRect = targetEl.getBoundingClientRect(); const x = targetRect.x - rect.x; const y = targetRect.y - rect.y - (rect.height / 2 - targetRect.height / 2); const targetWidth = targetRect.width / 0.75; const width = Math.abs(targetWidth - rect.width) > 1 ? { maxWidth: convertToUnit(targetWidth) } : undefined; const style = getComputedStyle(el); const targetStyle = getComputedStyle(targetEl); const duration = parseFloat(style.transitionDuration) * 1000 || 150; const scale = parseFloat(targetStyle.getPropertyValue('--v-field-label-scale')); const color = targetStyle.getPropertyValue('color'); el.style.visibility = 'visible'; targetEl.style.visibility = 'hidden'; animate(el, { transform: `translate(${x}px, ${y}px) scale(${scale})`, color, ...width }, { duration, easing: standardEasing, direction: val ? 'normal' : 'reverse' }).finished.then(() => { el.style.removeProperty('visibility'); targetEl.style.removeProperty('visibility'); }); }); } }, { flush: 'post' }); const slotProps = computed(() => ({ isActive, isFocused, controlRef, blur, focus })); function onClick(e) { if (e.target !== document.activeElement) { e.preventDefault(); } } useRender(() => { const isOutlined = props.variant === 'outlined'; const hasPrepend = !!(slots['prepend-inner'] || props.prependInnerIcon); const hasClear = !!(props.clearable || slots.clear) && !props.disabled; const hasAppend = !!(slots['append-inner'] || props.appendInnerIcon || hasClear); const label = () => slots.label ? slots.label({ ...slotProps.value, label: props.label, props: { for: id.value } }) : props.label; return _createElementVNode("div", _mergeProps({ "class": ['v-field', { 'v-field--active': isActive.value, 'v-field--appended': hasAppend, 'v-field--center-affix': props.centerAffix ?? !isPlainOrUnderlined.value, 'v-field--disabled': props.disabled, 'v-field--dirty': props.dirty, 'v-field--error': props.error, 'v-field--glow': props.glow, 'v-field--flat': props.flat, 'v-field--has-background': !!props.bgColor, 'v-field--persistent-clear': props.persistentClear, 'v-field--prepended': hasPrepend, 'v-field--reverse': props.reverse, 'v-field--single-line': props.singleLine, 'v-field--no-label': !label(), [`v-field--variant-${props.variant}`]: true }, themeClasses.value, backgroundColorClasses.value, focusClasses.value, loaderClasses.value, roundedClasses.value, rtlClasses.value, props.class], "style": [backgroundColorStyles.value, props.style], "onClick": onClick }, attrs), [_createElementVNode("div", { "class": "v-field__overlay" }, null), _createVNode(LoaderSlot, { "name": "v-field", "active": !!props.loading, "color": props.error ? 'error' : typeof props.loading === 'string' ? props.loading : props.color }, { default: slots.loader }), hasPrepend && _createElementVNode("div", { "key": "prepend", "class": "v-field__prepend-inner", "onMousedown": e => { e.preventDefault(); e.stopPropagation(); } }, [props.prependInnerIcon && _createVNode(InputIcon, { "key": "prepend-icon", "name": "prependInner", "color": iconColor.value }, null), slots['prepend-inner']?.(slotProps.value)]), _createElementVNode("div", { "class": "v-field__field", "data-no-activator": "" }, [['filled', 'solo', 'solo-inverted', 'solo-filled'].includes(props.variant) && hasFloatingLabel.value && _createVNode(VFieldLabel, { "key": "floating-label", "ref": floatingLabelRef, "class": _normalizeClass([textColorClasses.value]), "floating": true, "for": id.value, "style": _normalizeStyle(textColorStyles.value) }, { default: () => [label()] }), hasLabel.value && _createVNode(VFieldLabel, { "key": "label", "ref": labelRef, "for": id.value }, { default: () => [label()] }), slots.default?.({ ...slotProps.value, props: { id: id.value, class: 'v-field__input', 'aria-describedby': messagesId.value }, focus, blur }) ?? _createElementVNode("div", { "id": id.value, "class": "v-field__input", "aria-describedby": messagesId.value }, null)]), hasClear && _createVNode(VExpandXTransition, { "key": "clear" }, { default: () => [_withDirectives(_createElementVNode("div", { "class": "v-field__clearable", "onMousedown": e => { e.preventDefault(); e.stopPropagation(); } }, [_createVNode(VDefaultsProvider, { "defaults": { VIcon: { icon: props.clearIcon } } }, { default: () => [slots.clear ? slots.clear({ ...slotProps.value, props: { onFocus: focus, onBlur: blur, onClick: props['onClick:clear'], tabindex: -1 } }) : _createVNode(InputIcon, { "name": "clear", "onFocus": focus, "onBlur": blur, "tabindex": -1 }, null)] })]), [[_vShow, props.dirty]])] }), hasAppend && _createElementVNode("div", { "key": "append", "class": "v-field__append-inner", "onMousedown": e => { e.preventDefault(); e.stopPropagation(); } }, [slots['append-inner']?.(slotProps.value), props.appendInnerIcon && _createVNode(InputIcon, { "key": "append-icon", "name": "appendInner", "color": iconColor.value }, null)]), _createElementVNode("div", { "class": _normalizeClass(['v-field__outline', textColorClasses.value]), "style": _normalizeStyle(textColorStyles.value) }, [isOutlined && _createElementVNode(_Fragment, null, [_createElementVNode("div", { "class": "v-field__outline__start" }, null), hasFloatingLabel.value && _createElementVNode("div", { "class": "v-field__outline__notch" }, [_createVNode(VFieldLabel, { "ref": floatingLabelRef, "floating": true, "for": id.value }, { default: () => [label()] })]), _createElementVNode("div", { "class": "v-field__outline__end" }, null)]), isPlainOrUnderlined.value && hasFloatingLabel.value && _createVNode(VFieldLabel, { "ref": floatingLabelRef, "floating": true, "for": id.value }, { default: () => [label()] })])]); }); return { controlRef, fieldIconColor: iconColor }; } }); //# sourceMappingURL=VField.js.map