refactor(input): ts

This commit is contained in:
07akioni 2021-01-17 22:31:57 +08:00
parent e6568da5e4
commit e505f6e1b9
24 changed files with 1120 additions and 1043 deletions

View File

@ -1,4 +1,4 @@
import { h, defineComponent, PropType } from 'vue'
import { h, defineComponent, PropType, renderSlot } from 'vue'
import { useStyle } from '../../../_mixins'
import { DismissCircleIcon } from '../../icons'
import NBaseIcon from '../../icon'
@ -43,11 +43,11 @@ export default defineComponent({
onClick={this.onClear}
onMousedown={this.handleMouseDown}
>
<DismissCircleIcon />
{{ default: () => <DismissCircleIcon /> }}
</NBaseIcon>
) : (
<div key="icon" class="n-base-clear__placeholder">
<slot />
{renderSlot(this.$slots, 'default')}
</div>
)
}

View File

@ -3,10 +3,10 @@ import createIconSwitchTransition from '../../../../_styles/transitions/icon-swi
// vars:
// --bezier
// --color
// --size
// --color-hover
// --color-pressed
// --clear-color
// --clear-size
// --clear-color-hover
// --clear-color-pressed
export default cB(
'base-clear',
{
@ -22,15 +22,15 @@ export default cB(
{
fontSize: 'var(--size)',
cursor: 'pointer',
color: 'var(--color)',
color: 'var(--clear-color)',
transition: 'color .3s var(--bezier)'
},
[
c('&:hover', {
color: 'var(--color-hover)!important'
color: 'var(--clear-color-hover)!important'
}),
c('&:active', {
color: 'var(--color-pressed)!important'
color: 'var(--clear-color-pressed)!important'
})
]
),

View File

@ -14,7 +14,7 @@ export default defineComponent({
},
setup (props) {
useStyle('BaseClose', style)
return (
return () => (
<NBaseIcon
class={[
'n-base-close',
@ -23,7 +23,7 @@ export default defineComponent({
}
]}
>
<CloseIcon />
{{ default: () => <CloseIcon /> }}
</NBaseIcon>
)
}

View File

@ -1,8 +1,10 @@
import { computed, inject, provide, onBeforeUnmount } from 'vue'
import { computed, inject, provide, onBeforeUnmount, ComputedRef } from 'vue'
type FormItemSize = 'small' | 'medium' | 'large'
type AllowedSize = 'tiny' | 'small' | 'medium' | 'large' | 'huge'
interface FormItemInjection {
size: string | undefined
mergedSize: string
mergedSize: FormItemSize
restoreValidation: () => void
handleContentBlur: () => void
handleContentFocus: () => void
@ -10,19 +12,27 @@ interface FormItemInjection {
handleContentChange: () => void
}
interface UseFormItemOptions {
defaultSize?: string
mergedSize?: (formItem: FormItemInjection | null) => string
interface UseFormItemOptions<T> {
defaultSize?: FormItemSize
mergedSize?: (formItem: FormItemInjection | null) => T
}
interface UseFormItemProps {
size?: string
interface UseFormItemProps<T> {
size?: T
}
export default function useFormItem (
props: UseFormItemProps,
{ defaultSize = 'medium', mergedSize }: UseFormItemOptions = {}
) {
interface UseFormItem<T> {
mergedSize: ComputedRef<T>
nTriggerFormBlur: () => void
nTriggerFormChange: () => void
nTriggerFormFocus: () => void
nTriggerFormInput: () => void
}
export default function useFormItem<T extends AllowedSize = FormItemSize> (
props: UseFormItemProps<T>,
{ defaultSize = 'medium', mergedSize }: UseFormItemOptions<T> = {}
): UseFormItem<T> {
const NFormItem = inject<FormItemInjection | null>('NFormItem', null)
provide('NFormItem', null)
const mergedSizeRef = computed(
@ -34,10 +44,10 @@ export default function useFormItem (
if (NFormItem) {
const { mergedSize } = NFormItem
if (mergedSize) {
return mergedSize
return (mergedSize as unknown) as T
}
}
return defaultSize
return (defaultSize as unknown) as T
}
)
onBeforeUnmount(() => {

View File

@ -8,6 +8,7 @@ export {
keysOf,
render
} from './vue'
export type { MaybeArray } from './vue'
export { warn, warnOnce } from './naive'
export { formatLength } from './css'
export { createKey } from './cssr'

View File

@ -1,5 +1,9 @@
export function call<A extends any[]> (funcs: Function[] | Function, ...args: A): void {
export function call<A extends any[]> (
funcs: Function[] | Function,
...args: A
): void {
if (Array.isArray(funcs)) funcs.forEach((func) => call(func, ...args))
else return funcs(...args)
}
export type MaybeArray<T> = T | T[]

View File

@ -6,3 +6,4 @@ export { flatten } from './flatten'
export { call } from './call'
export { keysOf } from './keysOf'
export { render } from './render'
export type { MaybeArray } from './call'

View File

@ -1,4 +0,0 @@
/* istanbul ignore file */
export { default as NInput } from './src/Input.vue'
export { default as NInputGroup } from './src/InputGroup.vue'
export { default as NInputGroupLabel } from './src/InputGroupLabel.vue'

4
src/input/index.ts Normal file
View File

@ -0,0 +1,4 @@
/* istanbul ignore file */
export { default as NInput } from './src/Input'
export { default as NInputGroup } from './src/InputGroup'
export { default as NInputGroupLabel } from './src/InputGroupLabel'

929
src/input/src/Input.tsx Normal file
View File

@ -0,0 +1,929 @@
import {
h,
computed,
defineComponent,
nextTick,
ref,
toRef,
watch,
onMounted,
getCurrentInstance,
renderSlot,
PropType,
CSSProperties
} from 'vue'
import { useMergedState } from 'vooks'
import NIconConfigProvider from '../../icon/src/IconConfigProvider'
import { NBaseClear } from '../../_base'
import { useTheme, useLocale, useFormItem, useConfig } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { call, createKey } from '../../_utils'
import type { MaybeArray } from '../../_utils'
import { inputLight } from '../styles'
import type { InputTheme } from '../styles'
import style from './styles/input.cssr'
export default defineComponent({
name: 'Input',
props: {
...(useTheme.props as ThemeProps<InputTheme>),
bordered: {
type: Boolean,
default: undefined
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: [Array, String] as PropType<undefined | string | [string, string]>,
default: undefined
},
defaultValue: {
type: [String, Array] as PropType<null | string | [string, string]>,
default: null
},
value: {
type: [String, Array] as PropType<
null | undefined | string | [string, string]
>,
default: undefined
},
disabled: {
type: Boolean,
default: false
},
size: {
type: String as PropType<'small' | 'medium' | 'large' | undefined>,
default: undefined
},
rows: {
type: [Number, String] as PropType<number | string>,
default: 3
},
round: {
type: Boolean,
default: false
},
minlength: {
type: [String, Number] as PropType<number | string | undefined>,
default: undefined
},
maxlength: {
type: [String, Number] as PropType<number | string | undefined>,
default: undefined
},
clearable: {
type: Boolean,
default: false
},
autosize: {
type: [Boolean, Object] as PropType<
false | { minRows?: number, maxRows?: number }
>,
default: false
},
showWordLimit: {
type: Boolean,
default: false
},
pair: {
type: Boolean,
default: false
},
separator: {
type: String,
default: undefined
},
readonly: {
type: [String, Boolean],
default: false
},
forceFocus: {
type: Boolean,
default: false
},
passivelyActivated: {
type: Boolean,
default: false
},
stateful: {
type: Boolean,
default: true
},
autofocus: {
type: Boolean,
default: false
},
onInput: {
type: [Function, Array] as PropType<
| undefined
| MaybeArray<(value: string) => void>
| MaybeArray<(value: [string, string]) => void>
>,
default: undefined
},
onFocus: {
type: [Function, Array] as PropType<
undefined | MaybeArray<(e: FocusEvent) => void>
>,
default: undefined
},
onBlur: {
type: [Function, Array] as PropType<
undefined | MaybeArray<(e: FocusEvent) => void>
>,
default: undefined
},
onClick: {
type: [Function, Array] as PropType<
undefined | MaybeArray<(e: MouseEvent) => void>
>,
default: undefined
},
onChange: {
type: [Function, Array] as PropType<
| undefined
| MaybeArray<(value: string) => void>
| MaybeArray<(value: [string, string]) => void>
>,
default: undefined
},
onClear: {
type: [Function, Array] as PropType<
undefined | MaybeArray<(e: MouseEvent) => void>
>,
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': {
type: [Function, Array] as PropType<
undefined | MaybeArray<(value: string) => void>
>,
default: undefined
},
/** private */
textDecoration: {
type: [String, Array],
default: undefined
},
attrSize: {
type: Number,
default: 20
},
onInputBlur: {
type: [Function, Array] as PropType<
undefined | MaybeArray<(e: FocusEvent) => void>
>,
default: undefined
},
onInputFocus: {
type: [Function, Array] as PropType<
undefined | MaybeArray<(e: FocusEvent) => void>
>,
default: undefined
},
onDeactivate: {
type: [Function, Array] as PropType<undefined | MaybeArray<() => void>>,
default: undefined
},
onActivate: {
type: [Function, Array] as PropType<undefined | MaybeArray<() => void>>,
default: undefined
},
onWrapperFocus: {
type: [Function, Array] as PropType<
undefined | MaybeArray<(e: FocusEvent) => void>
>,
default: undefined
},
onWrapperBlur: {
type: [Function, Array] as PropType<
undefined | MaybeArray<(e: FocusEvent) => void>
>,
default: undefined
},
deactivateOnEnter: {
type: Boolean,
default: false
}
},
setup (props) {
const themeRef = useTheme('Input', 'Input', style, inputLight, props)
// dom refs
const wrapperRef = ref<HTMLElement | null>(null)
const textareaRef = ref<HTMLElement | null>(null)
const textareaMirrorRef = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLElement | null>(null)
const input2Ref = ref<HTMLElement | null>(null)
// local
const { locale } = useLocale('Input')
// value
const uncontrolledValueRef = ref(props.defaultValue)
const controlledValueRef = toRef(props, 'value')
const mergedValueRef = useMergedState(
controlledValueRef,
uncontrolledValueRef
)
// form-item
const formItem = useFormItem(props)
const { mergedSize: mergedSizeRef } = formItem
// states
const focusedRef = ref(false)
const hoverRef = ref(false)
const isComposingRef = ref(false)
const activatedRef = ref(false)
// placeholder
const mergedPlaceholderRef = computed<[string, string] | [string]>(() => {
const { placeholder, pair } = props
if (pair) {
if (Array.isArray(placeholder)) {
return placeholder
} else if (placeholder === undefined) {
return ['', '']
}
return [placeholder, placeholder]
} else if (placeholder === undefined) {
return [locale.value.placeholder]
} else {
return [placeholder] as [string]
}
})
const showPlaceholder1Ref = computed(() => {
const { value: isComposing } = isComposingRef
const { value: mergedValue } = mergedValueRef
const { value: mergedPlaceholder } = mergedPlaceholderRef
return (
!isComposing &&
(!mergedValue || (Array.isArray(mergedValue) && !mergedValue[0])) &&
mergedPlaceholder[0]
)
})
const showPlaceholder2Ref = computed(() => {
const { value: isComposing } = isComposingRef
const { value: mergedValue } = mergedValueRef
const { value: mergedPlaceholder } = mergedPlaceholderRef
return (
!isComposing &&
mergedPlaceholder[1] &&
(!mergedValue || (Array.isArray(mergedValue) && !mergedValue[1]))
)
})
const showTextareaPlaceholderRef = computed(() => {
return (
props.type === 'textarea' &&
!isComposingRef.value &&
!mergedValueRef.value &&
mergedPlaceholderRef.value
)
})
// clear
const showClearButton = computed(() => {
if (
props.disabled ||
!props.clearable ||
(!mergedFocusRef.value && !hoverRef.value)
) {
return false
}
const { value: mergedValue } = mergedValueRef
const { value: mergedFocus } = mergedFocusRef
if (props.pair) {
return (
!!(
Array.isArray(mergedValue) &&
(mergedValue[0] || mergedValue[1])
) &&
(hoverRef.value || mergedFocus)
)
} else {
return !!mergedValue && (hoverRef.value || mergedFocus)
}
})
// focus
const mergedFocusRef = computed(() => {
return props.forceFocus || focusedRef.value
})
// text-decoration
const textDecorationStyleRef = computed(() => {
const { textDecoration } = props
if (!textDecoration) return ['', '']
if (Array.isArray(textDecoration)) {
return textDecoration.map((v) => ({
textDecoration: v
}))
}
return [
{
textDecoration
}
]
})
// textarea autosize
const updateTextAreaStyle = (): void => {
if (props.type === 'textarea') {
const { autosize } = props
if (!autosize) return
if (!textareaRef.value) return
const {
paddingTop: stylePaddingTop,
paddingBottom: stylePaddingBottom,
lineHeight: styleLineHeight
} = window.getComputedStyle(textareaRef.value)
const paddingTop = Number(stylePaddingTop.slice(0, -2))
const paddingBottom = Number(stylePaddingBottom.slice(0, -2))
const lineHeight = Number(styleLineHeight.slice(0, -2))
if (!textareaMirrorRef.value) return
if (autosize.minRows) {
const minRows = Math.max(autosize.minRows, 1)
const styleMinHeight = `${
paddingTop + paddingBottom + lineHeight * minRows
}px`
textareaMirrorRef.value.style.minHeight = styleMinHeight
}
if (autosize.maxRows) {
const styleMaxHeight = `${
paddingTop + paddingBottom + lineHeight * autosize.maxRows
}px`
textareaMirrorRef.value.style.maxHeight = styleMaxHeight
}
}
}
watch([mergedValueRef, mergedSizeRef], () => {
void nextTick(updateTextAreaStyle)
})
onMounted(updateTextAreaStyle)
// other methods
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const vm = getCurrentInstance()!.proxy!
function doInput (value: [string, string]): void
function doInput (value: string): void
function doInput (value: string | [string, string]): void {
const { 'onUpdate:value': onUpdateValue, onInput } = props
const { nTriggerFormInput } = formItem
if (onUpdateValue) call(onUpdateValue, value)
if (onInput) call(onInput, value)
uncontrolledValueRef.value = value
nTriggerFormInput()
}
function doChange (value: [string, string]): void
function doChange (value: string): void
function doChange (value: string | [string, string]): void {
const { onChange } = props
const { nTriggerFormChange } = formItem
if (onChange) call(onChange, value)
uncontrolledValueRef.value = value
nTriggerFormChange()
}
function doBlur (e: FocusEvent): void {
const { onBlur } = props
const { nTriggerFormBlur } = formItem
if (onBlur) call(onBlur, e)
nTriggerFormBlur()
}
function doFocus (e: FocusEvent): void {
const { onFocus } = props
const { nTriggerFormFocus } = formItem
if (onFocus) call(onFocus, e)
nTriggerFormFocus()
}
function doClear (e: MouseEvent): void {
const { onClear } = props
if (onClear) call(onClear, e)
}
function doInputBlur (e: FocusEvent): void {
const { onInputBlur } = props
if (onInputBlur) call(onInputBlur, e)
}
function doInputFocus (e: FocusEvent): void {
const { onInputFocus } = props
if (onInputFocus) call(onInputFocus, e)
}
function doDeactivate (): void {
const { onDeactivate } = props
if (onDeactivate) call(onDeactivate)
}
function doActivate (): void {
const { onActivate } = props
if (onActivate) call(onActivate)
}
function doClick (e: MouseEvent): void {
const { onClick } = props
if (onClick) call(onClick, e)
}
function doWrapperFocus (e: FocusEvent): void {
const { onWrapperFocus } = props
if (onWrapperFocus) call(onWrapperFocus, e)
}
function doWrapperBlur (e: FocusEvent): void {
const { onWrapperBlur } = props
if (onWrapperBlur) call(onWrapperBlur, e)
}
// methods
function handleCompositionStart (): void {
isComposingRef.value = true
}
function handleCompositionEnd (e: CompositionEvent): void {
isComposingRef.value = false
if (e.target === input2Ref.value) {
handleInput(e, 1)
} else {
handleInput(e, 0)
}
}
function handleInput (
e: InputEvent | CompositionEvent | Event,
index: 0 | 1 = 0,
event = 'input'
): void {
if (isComposingRef.value) return
const changedValue = (e.target as HTMLInputElement).value
if (!props.pair) {
event === 'input' ? doInput(changedValue) : doChange(changedValue)
} else {
let { value } = mergedValueRef
if (!Array.isArray(value)) {
value = ['', '']
} else {
value = [...value]
}
value[index] = changedValue
event === 'input' ? doInput(value) : doChange(value)
}
// force update to sync input's view with value
// if not set, after input, input value won't sync with dom input value
vm.$forceUpdate()
}
function handleInputBlur (e: FocusEvent): void {
doInputBlur(e)
if (e.relatedTarget === wrapperRef.value) {
doDeactivate()
}
if (
!(
e.relatedTarget !== null &&
(e.relatedTarget === inputRef.value ||
e.relatedTarget === input2Ref.value ||
e.relatedTarget === textareaRef.value)
)
) {
activatedRef.value = false
}
dealWithEvent(e, 'blur')
}
function handleInputFocus (e: FocusEvent): void {
doInputFocus(e)
focusedRef.value = true
activatedRef.value = true
doActivate()
dealWithEvent(e, 'focus')
}
function handleWrapperBlur (e: FocusEvent): void {
if (props.passivelyActivated) {
doWrapperBlur(e)
dealWithEvent(e, 'blur')
}
}
function handleWrapperFocus (e: FocusEvent): void {
if (props.passivelyActivated) {
focusedRef.value = true
doWrapperFocus(e)
dealWithEvent(e, 'focus')
}
}
function dealWithEvent (e: FocusEvent, type: 'focus' | 'blur'): void {
if (
e.relatedTarget !== null &&
(e.relatedTarget === inputRef.value ||
e.relatedTarget === input2Ref.value ||
e.relatedTarget === textareaRef.value ||
e.relatedTarget === wrapperRef.value)
) {
/**
* activeElement transfer inside the input, do nothing
*/
} else {
if (type === 'focus') {
doFocus(e)
focusedRef.value = true
} else if (type === 'blur') {
doBlur(e)
focusedRef.value = false
}
}
}
function handleChange (e: Event, index?: 0 | 1): void {
handleInput(e, index, 'change')
}
function handleClick (e: MouseEvent): void {
doClick(e)
}
function handleClear (e: MouseEvent): void {
doClear(e)
if (props.pair) {
doInput(['', ''])
} else {
doInput('')
}
}
function handleMouseEnter (): void {
hoverRef.value = true
}
function handleMouseLeave (): void {
hoverRef.value = false
}
function handleWrapperKeyDown (e: KeyboardEvent): void {
switch (e.code) {
case 'ESC':
handleWrapperKeyDownEsc()
break
case 'ENTER':
handleWrapperKeyDownEnter(e)
break
}
}
function handleWrapperKeyDownEnter (e: KeyboardEvent): void {
if (props.passivelyActivated) {
const { value: focused } = activatedRef
if (focused) {
if (props.deactivateOnEnter) {
handleWrapperKeyDownEsc()
}
return
}
e.preventDefault()
if (props.type === 'textarea') {
textareaRef.value?.focus()
} else {
inputRef.value?.focus()
}
}
}
function handleWrapperKeyDownEsc (): void {
if (props.passivelyActivated) {
activatedRef.value = false
void nextTick(() => {
wrapperRef.value?.focus()
})
}
}
function focus (): void {
if (props.disabled) return
if (props.passivelyActivated) {
wrapperRef.value?.focus()
} else {
textareaRef.value?.focus()
inputRef.value?.focus()
}
}
function blur (): void {
if (wrapperRef.value?.contains(document.activeElement)) {
;(document.activeElement as HTMLElement).blur()
}
}
function activate (): void {
if (props.disabled) return
if (textareaRef.value) textareaRef.value.focus()
else if (inputRef.value) inputRef.value.focus()
}
function deactivate (): void {
const { value: wrapperEl } = wrapperRef
if (
wrapperEl?.contains(document.activeElement) &&
wrapperEl !== document.activeElement
) {
handleWrapperKeyDownEsc()
}
}
return {
// DOM ref
wrapperRef,
inputRef,
input2Ref,
textareaRef,
textareaMirrorRef,
// value
uncontrolledValue: uncontrolledValueRef,
mergedValue: mergedValueRef,
mergedPlaceholder: mergedPlaceholderRef,
showPlaceholder1: showPlaceholder1Ref,
showPlaceholder2: showPlaceholder2Ref,
showTextareaPlaceholder: showTextareaPlaceholderRef,
mergedFocus: mergedFocusRef,
isComposing: isComposingRef,
activated: activatedRef,
showClearButton,
mergedSize: mergedSizeRef,
textDecorationStyle: textDecorationStyleRef,
// methods
focus,
blur,
deactivate,
activate,
handleCompositionStart,
handleCompositionEnd,
handleInput,
handleInputBlur,
handleInputFocus,
handleWrapperBlur,
handleWrapperFocus,
handleMouseEnter,
handleMouseLeave,
handleChange,
handleClick,
handleClear,
handleWrapperKeyDown,
...useConfig(props),
mergedTheme: themeRef,
cssVars: computed(() => {
const { value: size } = mergedSizeRef
const {
common: { cubicBezierEaseInOut },
self: {
color,
borderRadius,
paddingLeft,
paddingRight,
textColor,
caretColor,
textDecorationColor,
border,
borderDisabled,
borderHover,
borderFocus,
placeholderColor,
placeholderColorDisabled,
lineHeightTextarea,
colorDisabled,
colorFocus,
textColorDisabled,
boxShadowFocus,
iconSize,
colorFocusWarning,
boxShadowFocusWarning,
borderWarning,
borderFocusWarning,
borderHoverWarning,
colorFocusError,
boxShadowFocusError,
borderError,
borderFocusError,
borderHoverError,
clearSize,
clearColor,
clearColorHover,
clearColorPressed,
[createKey('fontSize', size)]: fontSize,
[createKey('height', size)]: height
}
} = themeRef.value
return {
'--bezier': cubicBezierEaseInOut,
'--color': color,
'--font-size': fontSize,
'--border-radius': borderRadius,
'--height': height,
'--padding-left': paddingLeft,
'--padding-right': paddingRight,
'--text-color': textColor,
'--caret-color': caretColor,
'--text-decoration-color': textDecorationColor,
'--border': border,
'--border-disabled': borderDisabled,
'--border-hover': borderHover,
'--border-focus': borderFocus,
'--placeholder-color': placeholderColor,
'--placeholder-color-disabled': placeholderColorDisabled,
'--icon-size': iconSize,
'--line-height-textarea': lineHeightTextarea,
'--color-disabled': colorDisabled,
'--color-focus': colorFocus,
'--text-color-disabled': textColorDisabled,
'--box-shadow-focus': boxShadowFocus,
// form warning
'--color-focus-warning': colorFocusWarning,
'--box-shadow-focus-warning': boxShadowFocusWarning,
'--border-warning': borderWarning,
'--border-focus-warning': borderFocusWarning,
'--border-hover-warning': borderHoverWarning,
// form error
'--color-focus-error': colorFocusError,
'--box-shadow-focus-error': boxShadowFocusError,
'--border-error': borderError,
'--border-focus-error': borderFocusError,
'--border-hover-error': borderHoverError,
// clear-button
'--clear-color': clearColor,
'--clear-size': clearSize,
'--clear-color-hover': clearColorHover,
'--clear-color-pressed': clearColorPressed
}
})
}
},
render () {
return (
<div
ref="wrapperRef"
class={[
'n-input',
{
'n-input--disabled': this.disabled,
'n-input--textarea': this.type === 'textarea',
'n-input--round': this.round && !(this.type === 'textarea'),
'n-input--pair': this.pair,
'n-input--focus': this.mergedFocus,
'n-input--stateful': this.stateful
}
]}
style={this.cssVars as CSSProperties}
tabindex={
!this.disabled && this.passivelyActivated && !this.activated
? 0
: undefined
}
onFocus={this.handleWrapperFocus}
onBlur={this.handleWrapperBlur}
onClick={this.handleClick}
onMouseenter={this.handleMouseEnter}
onMouseleave={this.handleMouseLeave}
onCompositionstart={this.handleCompositionStart}
onCompositionend={this.handleCompositionEnd}
onKeydown={this.handleWrapperKeyDown}
>
{/* textarea mirror */}
{this.type === 'textarea' && this.autosize ? (
<pre ref="textareaMirrorRef" class="n-input__textarea-mirror">
{this.mergedValue}
<br />
</pre>
) : null}
{/* textarea & basic input */}
{this.type === 'textarea' ? (
<textarea
ref="textareaRef"
class={[
'n-input__textarea',
{
'n-input__textarea--autosize': this.autosize
}
]}
autofocus={this.autofocus}
rows={Number(this.rows)}
placeholder={this.placeholder as string | undefined}
value={this.mergedValue as string | undefined}
disabled={this.disabled}
maxlength={this.maxlength as any}
minlength={this.minlength as any}
readonly={this.readonly as any}
tabindex={
this.passivelyActivated && !this.activated ? -1 : undefined
}
style={this.textDecorationStyle[0] as any}
onBlur={this.handleInputBlur}
onFocus={this.handleInputFocus}
onInput={this.handleInput}
onChange={this.handleChange}
/>
) : (
<div class="n-input-wrapper">
{this.$slots.affix || this.$slots.prefix ? (
<NIconConfigProvider
class="n-input__prefix"
depth={this.disabled ? 5 : 4}
>
{{
default: () =>
renderSlot(this.$slots, 'affix', undefined, () => {
return [renderSlot(this.$slots, 'prefix')]
})
}}
</NIconConfigProvider>
) : null}
<div class="n-input__input">
<input
ref="inputRef"
type={this.type}
class="n-input__input-el"
tabindex={
this.passivelyActivated && !this.activated ? -1 : undefined
}
placeholder={this.mergedPlaceholder[0]}
disabled={this.disabled}
maxlength={this.maxlength as any}
minlength={this.minlength as any}
value={
Array.isArray(this.mergedValue)
? this.mergedValue[0]
: (this.mergedValue as any)
}
readonly={this.readonly as any}
autofocus={this.autofocus}
size={this.attrSize}
style={this.textDecorationStyle[0] as any}
onBlur={this.handleInputBlur}
onFocus={this.handleInputFocus}
onInput={(e) => this.handleInput(e, 0)}
onChange={(e) => this.handleChange(e, 0)}
/>
{this.showPlaceholder1 ? (
<div class="n-input__placeholder">
<span>{this.mergedPlaceholder[0]}</span>
</div>
) : null}
</div>
{!this.pair ? (
<NIconConfigProvider
class="n-input__suffix"
depth={this.disabled ? 5 : 4}
>
{{
default: () => [
renderSlot(this.$slots, 'suffix'),
this.clearable || this.$slots.clear ? (
<NBaseClear
show={this.showClearButton}
onClear={this.handleClear}
>
{{ default: () => renderSlot(this.$slots, 'clear') }}
</NBaseClear>
) : null
]
}}
</NIconConfigProvider>
) : null}
</div>
)}
{/* pair input */}
{this.pair ? (
<span class="n-input__separator">
{renderSlot(this.$slots, 'separator', undefined, () => [
this.separator
])}
</span>
) : null}
{this.pair ? (
<div v-if="pair" class="n-input-wrapper">
<div class="n-input__input">
<input
ref="input2Ref"
type={this.type}
class="n-input__input-el"
tabindex={
this.passivelyActivated && !this.activated ? -1 : undefined
}
placeholder={this.mergedPlaceholder[1]}
disabled={this.disabled}
maxlength={this.maxlength as any}
minlength={this.minlength as any}
value={
Array.isArray(this.mergedValue)
? this.mergedValue[1]
: undefined
}
readonly={this.readonly as any}
style={this.textDecorationStyle[1] as any}
onBlur={this.handleInputBlur}
onFocus={this.handleInputFocus}
onInput={(e) => this.handleInput(e, 1)}
onChange={(e) => this.handleChange(e, 1)}
/>
{this.showPlaceholder2 ? (
<div class="n-input__placeholder">
<span>{this.mergedPlaceholder[1]}</span>
</div>
) : null}
</div>
<NIconConfigProvider
class="n-input__suffix"
depth={this.disabled ? 5 : 4}
>
{{
default: () => {
return [
renderSlot(this.$slots, 'suffix'),
this.clearable || this.$slots.clear ? (
<NBaseClear
show={this.showClearButton}
onClear={this.handleClear}
>
{{ default: () => renderSlot(this.$slots, 'cleaar') }}
</NBaseClear>
) : null
]
}
}}
</NIconConfigProvider>
</div>
) : null}
{/* textarea placeholder */}
{this.showTextareaPlaceholder ? (
<div class="n-input__placeholder">{this.placeholder}</div>
) : null}
{/* border */}
{this.mergedBordered ? <div class="n-input__border" /> : null}
{this.mergedBordered ? <div class="n-input__state-border" /> : null}
</div>
)
}
})

View File

@ -1,878 +0,0 @@
<template>
<div
ref="wrapperRef"
class="n-input"
:class="{
'n-input--disabled': disabled,
'n-input--textarea': type === 'textarea',
'n-input--round': round && !(type === 'textarea'),
'n-input--pair': pair,
'n-input--focus': mergedFocus,
'n-input--stateful': stateful
}"
:style="cssVars"
:tabindex="!disabled && passivelyActivated && !activated ? 0 : false"
@focus="handleWrapperFocus"
@blur="handleWrapperBlur"
@keydown.enter="handleWrapperKeyDownEnter"
@keydown.esc="handleWrapperKeyDownEsc"
@click="handleClick"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@compositionstart="handleCompositionStart"
@compositionend="handleCompositionEnd"
>
<pre
v-if="type === 'textarea' && autosize"
ref="textareaMirrorRef"
class="n-input__textarea-mirror">{{ mergedValue }}<br></pre>
<textarea
v-if="type === 'textarea'"
ref="textareaRef"
class="n-input__textarea"
:class="{
'n-input__textarea--autosize': autosize
}"
:autofocus="autofocus"
:rows="rows"
:placeholder="placeholder"
:value="mergedValue"
:disabled="disabled"
:maxlength="maxlength"
:minlength="minlength"
:readonly="readonly"
:tabindex="passivelyActivated && !activated ? -1 : false"
:style="textDecorationStyle[0]"
@blur="handleInputBlur"
@focus="handleInputFocus"
@input="handleInput"
@change="handleChange"
@keyup="handleKeyUp"
/>
<div v-else class="n-input-wrapper">
<n-icon-config-provider
v-if="$slots.affix || $slots.prefix"
class="n-input__prefix"
:depth="disabled ? 5 : 4"
>
<slot name="affix">
<slot name="prefix" />
</slot>
</n-icon-config-provider>
<div class="n-input__input">
<input
ref="inputRef"
:type="type"
class="n-input__input-el"
:tabindex="passivelyActivated && !activated ? -1 : false"
:placeholder="mergedPlaceholder[0]"
:disabled="disabled"
:maxlength="maxlength"
:minlength="minlength"
:value="pair ? mergedValue && mergedValue[0] : mergedValue"
:readonly="readonly"
:autofocus="autofocus"
:size="attrSize"
:style="textDecorationStyle[0]"
@blur="handleInputBlur"
@focus="handleInputFocus"
@input="handleInput($event, 0)"
@change="handleChange($event, 0)"
@keyup="handleKeyUp"
/>
<div v-if="showPlaceholder1" class="n-input__placeholder">
<span>{{ mergedPlaceholder[0] }}</span>
</div>
</div>
<n-icon-config-provider
v-if="!pair"
class="n-input__suffix"
:depth="disabled ? 5 : 4"
>
<slot name="suffix" />
<n-base-clear
v-if="clearable || $slots.clear"
:unstable-theme="mergedTheme.peers.BaseClear"
:unstable-theme-overrides="mergedTheme.overrides.BaseClear"
:show="showClearButton"
@clear="handleClear"
>
<slot name="clear" />
</n-base-clear>
</n-icon-config-provider>
</div>
<span v-if="pair" class="n-input__separator">
<slot name="separator">
{{ separator }}
</slot>
</span>
<div v-if="pair" class="n-input-wrapper">
<div class="n-input__input">
<input
ref="input2Ref"
:type="type"
class="n-input__input-el"
:tabindex="passivelyActivated && !activated ? -1 : false"
:placeholder="mergedPlaceholder[1]"
:disabled="disabled"
:maxlength="maxlength"
:minlength="minlength"
:value="mergedValue && mergedValue[1]"
:readonly="readonly"
:style="textDecorationStyle[1]"
@blur="handleInputBlur"
@focus="handleInputFocus"
@input="handleInput($event, 1)"
@change="handleChange($event, 1)"
@keyup="handleKeyUp"
/>
<div v-if="showPlaceholder2" class="n-input__placeholder">
<span>{{ mergedPlaceholder[1] }}</span>
</div>
</div>
<n-icon-config-provider class="n-input__suffix" :depth="disabled ? 5 : 4">
<slot name="suffix" />
<n-base-clear
v-if="clearable || $slots.clear"
:unstable-theme="mergedTheme.peers.BaseClear"
:unstable-theme-overrides="mergedTheme.overrides.BaseClear"
:show="showClearButton"
@clear="handleClear"
>
<slot name="clear" />
</n-base-clear>
</n-icon-config-provider>
</div>
<div v-if="showTextareaPlaceholder" class="n-input__placeholder">
{{ placeholder }}
</div>
<div v-if="mergedBordered" class="n-input__border" />
<div v-if="mergedBordered" class="n-input__state-border" />
</div>
</template>
<script>
import {
computed,
defineComponent,
nextTick,
ref,
toRef,
watch,
onMounted,
getCurrentInstance
} from 'vue'
import { useMergedState } from 'vooks'
import NIconConfigProvider from '../../icon/src/IconConfigProvider'
import { NBaseClear } from '../../_base'
import { useTheme, useLocale, useFormItem, useConfig } from '../../_mixins'
import { call, createKey } from '../../_utils'
import { inputLight } from '../styles'
import style from './styles/input.cssr.js'
function createMethods (
props,
formItem,
{
mergedValueRef,
uncontrolledValueRef,
isComposingRef,
focusedRef,
hoverRef,
activatedRef,
wrapperRef,
inputRef,
input2Ref,
textareaRef,
textareaMirrorRef
}
) {
const vm = getCurrentInstance().proxy
const doInput = (value) => {
const { 'onUpdate:value': onUpdateValue, onInput } = props
const { nTriggerFormInput } = formItem
if (onUpdateValue) call(onUpdateValue, value)
if (onInput) call(onInput, value)
uncontrolledValueRef.value = value
nTriggerFormInput()
}
const doChange = (value) => {
const { onChange } = props
const { nTriggerFormChange } = formItem
if (onChange) call(onChange, value)
uncontrolledValueRef.value = value
nTriggerFormChange()
}
const doBlur = (e) => {
const { onBlur } = props
const { nTriggerFormBlur } = formItem
if (onBlur) call(onBlur, e)
nTriggerFormBlur()
}
const doFocus = (e) => {
const { onFocus } = props
const { nTriggerFormFocus } = formItem
if (onFocus) call(onFocus, e)
nTriggerFormFocus()
}
const doClear = (...args) => {
const { onClear } = props
if (onClear) call(onClear, ...args)
}
const doInputBlur = (e) => {
const { onInputBlur } = props
if (onInputBlur) call(onInputBlur, e)
}
const doInputFocus = (e) => {
const { onInputFocus } = props
if (onInputFocus) call(onInputFocus, e)
}
const doDeactivate = () => {
const { onDeactivate } = props
if (onDeactivate) call(onDeactivate)
}
const doActivate = () => {
const { onActivate } = props
if (onActivate) call(onActivate)
}
const doKeyUp = (e) => {
const { onKeyUp } = props
if (onKeyUp) call(onKeyUp, e)
}
const doClick = (e) => {
const { onClick } = props
if (onClick) call(onClick, e)
}
const doWrapperFocus = (e) => {
const { onWrapperFocus } = props
if (onWrapperFocus) call(onWrapperFocus, e)
}
const doWrapperBlur = (e) => {
const { onWrapperBlur } = props
if (onWrapperBlur) call(onWrapperBlur, e)
}
// methods
const handleCompositionStart = () => {
isComposingRef.value = true
}
const handleCompositionEnd = (e) => {
isComposingRef.value = false
if (e.target === input2Ref.value) {
handleInput(e, 1)
} else {
handleInput(e, 0)
}
}
const handleInput = (e, index, event = 'input') => {
if (isComposingRef.value) return
const changedValue = e.target.value
if (!props.pair) {
event === 'input' ? doInput(changedValue) : doChange(changedValue)
} else {
let { value } = mergedValueRef
if (!Array.isArray(value)) {
value = [null, null]
} else {
value = [...value]
}
value[index] = changedValue
event === 'input' ? doInput(value) : doChange(value)
}
// force update to sync input's view with value
// if not set, after input, input value won't sync with dom input value
vm.$forceUpdate()
}
const handleInputBlur = (e) => {
doInputBlur(e)
if (e.relatedTarget === wrapperRef.value) {
doDeactivate()
}
if (
!(
e.relatedTarget !== null &&
(e.relatedTarget === inputRef.value ||
e.relatedTarget === input2Ref.value ||
e.relatedTarget === textareaRef.value)
)
) {
activatedRef.value = false
}
dealWithEvent(e, 'blur')
}
const handleInputFocus = (e) => {
doInputFocus(e)
focusedRef.value = true
activatedRef.value = true
doActivate()
dealWithEvent(e, 'focus')
}
const handleWrapperBlur = (e) => {
if (props.passivelyActivated) {
doWrapperBlur(e)
dealWithEvent(e, 'blur')
}
}
const handleWrapperFocus = (e) => {
if (props.passivelyActivated) {
focusedRef.value = true
doWrapperFocus(e)
dealWithEvent(e, 'focus')
}
}
const dealWithEvent = (e, type) => {
if (
e.relatedTarget !== null &&
(e.relatedTarget === inputRef.value ||
e.relatedTarget === input2Ref.value ||
e.relatedTarget === textareaRef.value ||
e.relatedTarget === wrapperRef.value)
) {
/**
* activeElement transfer inside the input, do nothing
*/
} else {
if (type === 'focus') {
doFocus(e)
focusedRef.value = true
} else if (type === 'blur') {
doBlur(e)
focusedRef.value = false
}
}
}
const handleKeyUp = (e) => {
doKeyUp(e)
}
const handleChange = (e, index) => handleInput(e, index, 'change')
const handleClick = (e) => {
doClick(e)
}
const handleClear = (e) => {
doClear(e)
if (props.pair) {
doInput([])
} else {
doInput('')
}
}
const handleMouseEnter = () => {
hoverRef.value = true
}
const handleMouseLeave = () => {
hoverRef.value = false
}
const handleWrapperKeyDownEnter = (e) => {
if (props.passivelyActivated) {
const { value: focused } = activatedRef
if (focused) {
if (props.deactivateOnEnter) {
handleWrapperKeyDownEsc()
}
return
}
e.preventDefault()
if (props.type === 'textarea') {
textareaRef.value.focus()
} else {
inputRef.value.focus()
}
}
}
const handleWrapperKeyDownEsc = () => {
if (props.passivelyActivated) {
activatedRef.value = false
nextTick(() => {
wrapperRef.value.focus()
})
}
}
return {
doInput,
doChange,
doFocus,
doBlur,
doClear,
doWrapperFocus,
doWrapperBlur,
doInputFocus,
doInputBlur,
doActivate,
doDeactivate,
doClick,
doKeyUp,
handleCompositionStart,
handleCompositionEnd,
handleInput,
handleInputBlur,
handleInputFocus,
handleWrapperBlur,
handleWrapperFocus,
handleMouseEnter,
handleMouseLeave,
handleKeyUp,
handleChange,
handleClick,
handleClear,
handleWrapperKeyDownEnter,
handleWrapperKeyDownEsc
}
}
export default defineComponent({
name: 'Input',
components: {
NIconConfigProvider,
NBaseClear
},
props: {
...useTheme.props,
bordered: {
type: Boolean,
default: undefined
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: [Array, String],
default: undefined
},
defaultValue: {
type: [String, Array],
default: null
},
value: {
type: [String, Array],
default: undefined
},
disabled: {
type: Boolean,
default: false
},
size: {
validator (value) {
return ['small', 'medium', 'large'].includes(value)
},
default: undefined
},
rows: {
type: [Number, String],
default: 3
},
round: {
type: Boolean,
default: false
},
minlength: {
type: [String, Number],
default: undefined
},
maxlength: {
type: [String, Number],
default: undefined
},
clearable: {
type: Boolean,
default: false
},
autosize: {
type: [Boolean, Object],
default: false
},
showWordLimit: {
type: Boolean,
default: false
},
pair: {
type: Boolean,
default: false
},
separator: {
type: String,
default: undefined
},
readonly: {
type: [String, Boolean],
default: false
},
forceFocus: {
type: Boolean,
default: false
},
passivelyActivated: {
type: Boolean,
default: false
},
stateful: {
type: Boolean,
default: true
},
autofocus: {
type: Boolean,
default: false
},
onInput: {
type: [Function, Array],
default: undefined
},
onFocus: {
type: [Function, Array],
default: undefined
},
onBlur: {
type: [Function, Array],
default: undefined
},
onClick: {
type: [Function, Array],
default: undefined
},
onChange: {
type: [Function, Array],
default: undefined
},
onKeyUp: {
type: [Function, Array],
default: undefined
},
onClear: {
type: [Function, Array],
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': {
type: [Function, Array],
default: undefined
},
/** private */
textDecoration: {
type: [String, Array],
default: undefined
},
attrSize: {
type: Number,
default: 20
},
onInputBlur: {
type: [Function, Array],
default: undefined
},
onInputFocus: {
type: [Function, Array],
default: undefined
},
onDeactivate: {
type: [Function, Array],
default: undefined
},
onActivate: {
type: [Function, Array],
default: undefined
},
onWrapperFocus: {
type: [Function, Array],
default: undefined
},
onWrapperBlur: {
type: [Function, Array],
default: undefined
},
deactivateOnEnter: {
type: Boolean,
default: false
}
},
setup (props) {
const themeRef = useTheme('Input', 'Input', style, inputLight, props)
// dom refs
const wrapperRef = ref(null)
const textareaRef = ref(null)
const textareaMirrorRef = ref(null)
const inputRef = ref(null)
const input2Ref = ref(null)
// local
const { locale } = useLocale('Input')
// value
const uncontrolledValueRef = ref(props.defaultValue)
const controlledValueRef = toRef(props, 'value')
const mergedValueRef = useMergedState(
controlledValueRef,
uncontrolledValueRef
)
// form-item
const formItem = useFormItem(props)
const { mergedSize: mergedSizeRef } = formItem
// states
const focusedRef = ref(false)
const hoverRef = ref(false)
const isComposingRef = ref(false)
const activatedRef = ref(false)
// placeholder
const mergedPlaceholderRef = computed(() => {
const { placeholder, pair } = props
if (pair) {
if (Array.isArray(placeholder)) {
return placeholder
} else {
return [placeholder, placeholder]
}
} else if (placeholder === undefined) {
return [locale.value.placeholder]
} else {
return [placeholder]
}
})
const showPlaceholder1Ref = computed(() => {
const { value: isComposing } = isComposingRef
const { value: mergedValue } = mergedValueRef
const { value: mergedPlaceholder } = mergedPlaceholderRef
return (
!isComposing &&
(!mergedValue || (Array.isArray(mergedValue) && !mergedValue[0])) &&
mergedPlaceholder[0]
)
})
const showPlaceholder2Ref = computed(() => {
const { value: isComposing } = isComposingRef
const { value: mergedValue } = mergedValueRef
const { value: mergedPlaceholder } = mergedPlaceholderRef
return (
!isComposing &&
mergedPlaceholder[1] &&
(!mergedValue || (Array.isArray(mergedValue) && !mergedValue[1]))
)
})
const showTextareaPlaceholderRef = computed(() => {
return (
props.type === 'textarea' &&
!isComposingRef.value &&
!mergedValueRef.value &&
mergedPlaceholderRef.value
)
})
// clear
const showClearButton = computed(() => {
if (
props.disabled ||
!props.clearable ||
(!mergedFocusRef.value && !hoverRef.value)
) {
return false
}
const { value: mergedValue } = mergedValueRef
const { value: mergedFocus } = mergedFocusRef
if (props.pair) {
return (
!!(
Array.isArray(mergedValue) &&
(mergedValue[0] || mergedValue[1])
) &&
(hoverRef.value || mergedFocus)
)
} else {
return !!mergedValue && (hoverRef.value || mergedFocus)
}
})
// focus
const mergedFocusRef = computed(() => {
return props.forceFocus || focusedRef.value
})
// text-decoration
const textDecorationStyleRef = computed(() => {
const { textDecoration } = props
if (!textDecoration) return ['', '']
if (Array.isArray(textDecoration)) {
return textDecoration.map((v) => ({
textDecoration: v
}))
}
return [
{
textDecoration
}
]
})
// textarea autosize
const updateTextAreaStyle = () => {
if (props.type === 'textarea') {
const { autosize } = props
if (!autosize) return
const {
paddingTop: stylePaddingTop,
paddingBottom: stylePaddingBottom,
lineHeight: styleLineHeight
} = window.getComputedStyle(textareaRef.value)
const paddingTop = Number(stylePaddingTop.slice(0, -2))
const paddingBottom = Number(stylePaddingBottom.slice(0, -2))
const lineHeight = Number(styleLineHeight.slice(0, -2))
if (autosize.minRows) {
const minRows = Math.max(autosize.minRows, 1)
const styleMinHeight =
paddingTop + paddingBottom + lineHeight * minRows + 'px'
textareaMirrorRef.value.style.minHeight = styleMinHeight
}
if (autosize.maxRows) {
const maxRows = Math.max(autosize.maxRows, autosize.minRows, 1)
const styleMaxHeight =
paddingTop + paddingBottom + lineHeight * maxRows + 'px'
textareaMirrorRef.value.style.maxHeight = styleMaxHeight
}
}
}
watch([mergedValueRef, mergedSizeRef], () => {
nextTick(updateTextAreaStyle)
})
onMounted(updateTextAreaStyle)
// other methods
const methods = createMethods(props, formItem, {
mergedValueRef,
uncontrolledValueRef,
isComposingRef,
focusedRef,
hoverRef,
activatedRef,
wrapperRef,
inputRef,
input2Ref,
textareaRef,
textareaMirrorRef
})
const focus = () => {
if (props.disabled) return
if (props.passivelyActivated) {
wrapperRef.value.focus()
} else {
if (textareaRef.value) textareaRef.value.focus()
else if (inputRef.value) inputRef.value.focus()
}
}
const blur = () => {
if (wrapperRef.value.contains(document.activeElement)) {
document.activeElement.blur()
}
}
const activate = () => {
if (props.disabled) return
if (textareaRef.value) textareaRef.value.focus()
else if (inputRef.value) inputRef.value.focus()
}
const deactivate = () => {
if (
wrapperRef.value.contains(document.activeElement) &&
wrapperRef.value !== document.activeElement
) {
methods.handleWrapperKeyDownEsc()
}
}
return {
// DOM ref
wrapperRef,
inputRef,
input2Ref,
textareaRef,
textareaMirrorRef,
// value
uncontrolledValue: uncontrolledValueRef,
mergedValue: mergedValueRef,
mergedPlaceholder: mergedPlaceholderRef,
showPlaceholder1: showPlaceholder1Ref,
showPlaceholder2: showPlaceholder2Ref,
showTextareaPlaceholder: showTextareaPlaceholderRef,
mergedFocus: mergedFocusRef,
isComposing: isComposingRef,
activated: activatedRef,
showClearButton,
mergedSize: mergedSizeRef,
textDecorationStyle: textDecorationStyleRef,
// methods
focus,
blur,
deactivate,
activate,
...methods,
...useConfig(props),
mergedTheme: themeRef,
cssVars: computed(() => {
const { value: size } = mergedSizeRef
const {
common: { cubicBezierEaseInOut },
self: {
color,
borderRadius,
paddingLeft,
paddingRight,
textColor,
caretColor,
textDecorationColor,
border,
borderDisabled,
borderHover,
borderFocus,
placeholderColor,
placeholderColorDisabled,
lineHeightTextarea,
colorDisabled,
colorFocus,
textColorDisabled,
boxShadowFocus,
iconSize,
colorFocusWarning,
boxShadowFocusWarning,
boxShadowHoverWarning,
borderWarning,
borderFocusWarning,
borderHoverWarning,
colorFocusError,
boxShadowFocusError,
boxShadowHoverError,
borderError,
borderFocusError,
borderHoverError,
[createKey('fontSize', size)]: fontSize,
[createKey('height', size)]: height
}
} = themeRef.value
return {
'--bezier': cubicBezierEaseInOut,
'--color': color,
'--font-size': fontSize,
'--border-radius': borderRadius,
'--height': height,
'--padding-left': paddingLeft,
'--padding-right': paddingRight,
'--text-color': textColor,
'--caret-color': caretColor,
'--text-decoration-color': textDecorationColor,
'--border': border,
'--border-disabled': borderDisabled,
'--border-hover': borderHover,
'--border-focus': borderFocus,
'--placeholder-color': placeholderColor,
'--placeholder-color-disabled': placeholderColorDisabled,
'--icon-size': iconSize,
'--line-height-textarea': lineHeightTextarea,
'--color-disabled': colorDisabled,
'--color-focus': colorFocus,
'--text-color-disabled': textColorDisabled,
'--box-shadow-focus': boxShadowFocus,
// form warning
'--color-focus-warning': colorFocusWarning,
'--box-shadow-focus-warning': boxShadowFocusWarning,
'--box-shadow-hover-warning': boxShadowHoverWarning,
'--border-warning': borderWarning,
'--border-focus-warning': borderFocusWarning,
'--border-hover-warning': borderHoverWarning,
// form error
'--color-focus-error': colorFocusError,
'--box-shadow-focus-error': boxShadowFocusError,
'--box-shadow-hover-error': boxShadowHoverError,
'--border-error': borderError,
'--border-focus-error': borderFocusError,
'--border-hover-error': borderHoverError
}
})
}
}
})
</script>

View File

@ -0,0 +1,13 @@
import { h, defineComponent, renderSlot } from 'vue'
import { useStyle } from '../../_mixins'
import style from './styles/input-group.cssr'
export default defineComponent({
name: 'InputGroup',
setup () {
useStyle('InputGroup', style)
},
render () {
return <div class="n-input-group">{renderSlot(this.$slots, 'default')}</div>
}
})

View File

@ -1,18 +0,0 @@
<template>
<div class="n-input-group">
<slot />
</div>
</template>
<script>
import { defineComponent } from 'vue'
import { useStyle } from '../../_mixins'
import style from './styles/input-group.cssr.js'
export default defineComponent({
name: 'InputGroup',
setup (props) {
useStyle('InputGroup', style)
}
})
</script>

View File

@ -1,27 +1,18 @@
<template>
<div
class="n-input-group-label"
:class="`n-input-group-label--${size}-size`"
:style="cssVars"
>
<slot />
<div class="n-input-group-label__border" />
</div>
</template>
<script>
import { computed, defineComponent } from 'vue'
import { computed, defineComponent, renderSlot, h, PropType } from 'vue'
import { useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { createKey } from '../../_utils'
import { inputLight } from '../styles'
import style from './styles/input-group-label.cssr.js'
import type { InputTheme } from '../styles'
import style from './styles/input-group-label.cssr'
import type { Size } from './interface'
export default defineComponent({
name: 'InputGroupLabel',
props: {
...useTheme.props,
...(useTheme.props as ThemeProps<InputTheme>),
size: {
type: String,
type: String as PropType<Size>,
default: 'medium'
}
},
@ -43,8 +34,8 @@ export default defineComponent({
borderRadius,
textColor,
lineHeight,
fontSize,
border,
[createKey('fontSize', size)]: fontSize,
[createKey('height', size)]: height
}
} = themeRef.value
@ -60,6 +51,13 @@ export default defineComponent({
}
})
}
},
render () {
return (
<div class="n-input-group-label" style={this.cssVars as any}>
{renderSlot(this.$slots, 'default')}
<div class="n-input-group-label__border" />
</div>
)
}
})
</script>

View File

@ -0,0 +1 @@
export type Size = 'small' | 'medium' | 'large'

View File

@ -22,7 +22,10 @@ import { cB, c, cE, cM, cNotM, insideFormItem } from '../../../_utils/cssr'
// --color-disabled
// --color-focus
// --box-shadow-focus
// --icon-size
// --clear-color
// --clear-size
// --clear-color-hover
// --clear-color-pressed
export default c([
cB('input', `
line-height: 1.5;
@ -241,7 +244,6 @@ export default c([
}),
c('&:hover', [
cE('state-border', `
box-shadow: var(--box-shadow-hover-${status});
border: var(--border-hover-${status});
`)
]),

View File

@ -1,5 +1,6 @@
export default {
paddingLeft: '14px',
paddingRight: '8px',
paddingIcon: '38px'
paddingIcon: '38px',
clearSize: '16px'
}

View File

@ -1,14 +1,11 @@
import commonVariables from './_common'
import { changeColor } from 'seemly'
import { baseClearDark } from '../../_base/clear/styles'
import { changeColor, scaleColor } from 'seemly'
import { commonDark } from '../../_styles/new-common'
import type { InputTheme } from './light'
export default {
const inputDark: InputTheme = {
name: 'Input',
common: commonDark,
peers: {
BaseClear: baseClearDark
},
self (vars) {
const {
textColor2Overlay,
@ -43,6 +40,7 @@ export default {
fontSizeSmall,
fontSizeMedium,
fontSizeLarge,
lineHeight,
lineHeightTextarea: lineHeight,
borderRadius,
iconSize: '16px',
@ -78,7 +76,12 @@ export default {
boxShadowFocusError: `0 0 8px 0 ${changeColor(errorColor, {
alpha: 0.3
})}`,
caretColorError: errorColor
caretColorError: errorColor,
clearColor: textColor4Overlay,
clearColorHover: scaleColor(textColor4Overlay, { alpha: 1.25 }),
clearColorPressed: scaleColor(textColor4Overlay, { alpha: 0.75 })
}
}
}
export default inputDark

View File

@ -1,2 +0,0 @@
export { default as inputDark } from './dark.js'
export { default as inputLight } from './light.js'

View File

@ -0,0 +1,3 @@
export { default as inputDark } from './dark'
export { default as inputLight } from './light'
export type { InputTheme, InputThemeVars } from './light'

View File

@ -1,86 +0,0 @@
import commonVariables from './_common'
import { changeColor } from 'seemly'
import { baseClearLight } from '../../_base/clear/styles'
import { commonLight } from '../../_styles/new-common'
export default {
name: 'Input',
common: commonLight,
peers: {
BaseClear: baseClearLight
},
self (vars) {
const {
textColor2,
textColor4,
textColor5,
primaryColor,
primaryColorHover,
inputColor,
inputColorDisabled,
borderColor,
warningColor,
warningColorHover,
errorColor,
errorColorHover,
borderRadius,
lineHeight,
fontSizeTiny,
fontSizeSmall,
fontSizeMedium,
fontSizeLarge,
heightTiny,
heightSmall,
heightMedium,
heightLarge,
actionColor
} = vars
return {
...commonVariables,
heightTiny,
heightSmall,
heightMedium,
heightLarge,
fontSizeTiny,
fontSizeSmall,
fontSizeMedium,
fontSizeLarge,
lineHeightTextarea: lineHeight,
borderRadius,
iconSize: '16px',
groupLabelColor: actionColor,
textColor: textColor2,
textColorDisabled: textColor4,
textDecorationColor: textColor2,
caretColor: primaryColor,
placeholderColor: textColor4,
placeholderColorDisabled: textColor5,
color: inputColor,
colorDisabled: inputColorDisabled,
colorFocus: inputColor,
border: `1px solid ${borderColor}`,
borderHover: `1px solid ${primaryColorHover}`,
borderDisabled: `1px solid ${borderColor}`,
borderFocus: `1px solid ${primaryColorHover}`,
boxShadowFocus: `0 0 0 2px ${changeColor(primaryColor, { alpha: 0.2 })}`,
// warning
borderWarning: `1px solid ${warningColor}`,
borderHoverWarning: `1px solid ${warningColorHover}`,
colorFocusWarning: inputColor,
borderFocusWarning: `1px solid ${warningColorHover}`,
boxShadowFocusWarning: `0 0 0 2px ${changeColor(warningColor, {
alpha: 0.2
})}`,
caretColorWarning: warningColor,
// error
borderError: `1px solid ${errorColor}`,
borderHoverError: `1px solid ${errorColorHover}`,
colorFocusError: inputColor,
borderFocusError: `1px solid ${errorColorHover}`,
boxShadowFocusError: `0 0 0 2px ${changeColor(errorColor, {
alpha: 0.2
})}`,
caretColorError: errorColor
}
}
}

95
src/input/styles/light.ts Normal file
View File

@ -0,0 +1,95 @@
import commonVariables from './_common'
import { changeColor, scaleColor } from 'seemly'
import { commonLight } from '../../_styles/new-common'
import type { ThemeCommonVars } from '../../_styles/new-common'
import type { Theme } from '../../_mixins'
const self = (vars: ThemeCommonVars) => {
const {
textColor2,
textColor4,
textColor5,
primaryColor,
primaryColorHover,
inputColor,
inputColorDisabled,
borderColor,
warningColor,
warningColorHover,
errorColor,
errorColorHover,
borderRadius,
lineHeight,
fontSizeTiny,
fontSizeSmall,
fontSizeMedium,
fontSizeLarge,
heightTiny,
heightSmall,
heightMedium,
heightLarge,
actionColor
} = vars
return {
...commonVariables,
heightTiny,
heightSmall,
heightMedium,
heightLarge,
fontSizeTiny,
fontSizeSmall,
fontSizeMedium,
fontSizeLarge,
lineHeight,
lineHeightTextarea: lineHeight,
borderRadius,
iconSize: '16px',
groupLabelColor: actionColor,
textColor: textColor2,
textColorDisabled: textColor4,
textDecorationColor: textColor2,
caretColor: primaryColor,
placeholderColor: textColor4,
placeholderColorDisabled: textColor5,
color: inputColor,
colorDisabled: inputColorDisabled,
colorFocus: inputColor,
border: `1px solid ${borderColor}`,
borderHover: `1px solid ${primaryColorHover}`,
borderDisabled: `1px solid ${borderColor}`,
borderFocus: `1px solid ${primaryColorHover}`,
boxShadowFocus: `0 0 0 2px ${changeColor(primaryColor, { alpha: 0.2 })}`,
// warning
borderWarning: `1px solid ${warningColor}`,
borderHoverWarning: `1px solid ${warningColorHover}`,
colorFocusWarning: inputColor,
borderFocusWarning: `1px solid ${warningColorHover}`,
boxShadowFocusWarning: `0 0 0 2px ${changeColor(warningColor, {
alpha: 0.2
})}`,
caretColorWarning: warningColor,
// error
borderError: `1px solid ${errorColor}`,
borderHoverError: `1px solid ${errorColorHover}`,
colorFocusError: inputColor,
borderFocusError: `1px solid ${errorColorHover}`,
boxShadowFocusError: `0 0 0 2px ${changeColor(errorColor, {
alpha: 0.2
})}`,
caretColorError: errorColor,
clearColor: textColor4,
clearColorHover: scaleColor(textColor4, { lightness: 0.75 }),
clearColorPressed: scaleColor(textColor4, { lightness: 0.9 })
}
}
export type InputThemeVars = ReturnType<typeof self>
const inputLight: Theme<InputThemeVars> = {
name: 'Input',
common: commonLight,
self
}
export default inputLight
export type InputTheme = typeof inputLight