refactor(dynamic-tags): ts

This commit is contained in:
07akioni 2021-01-25 23:30:32 +08:00
parent 09e33ee18d
commit 78773c5701
14 changed files with 252 additions and 193 deletions

View File

@ -9,7 +9,7 @@ export {
render
} from './vue'
export type { MaybeArray } from './vue'
export { warn, warnOnce, throwError } from './naive'
export { warn, warnOnce, throwError, smallerSize, largerSize } from './naive'
export { formatLength } from './css'
export { createKey } from './cssr'
export * from './composable'

View File

@ -1 +1,2 @@
export { warn, warnOnce, throwError } from './warn'
export { smallerSize, largerSize } from './prop'

29
src/_utils/naive/prop.ts Normal file
View File

@ -0,0 +1,29 @@
export function largerSize (
size: 'tiny' | 'small' | 'medium' | 'large'
): 'small' | 'medium' | 'large' | 'huge' {
switch (size) {
case 'tiny':
return 'small'
case 'small':
return 'medium'
case 'medium':
return 'large'
case 'large':
return 'huge'
}
}
export function smallerSize (
size: 'small' | 'medium' | 'large' | 'huge'
): 'tiny' | 'small' | 'medium' | 'large' {
switch (size) {
case 'small':
return 'tiny'
case 'medium':
return 'small'
case 'large':
return 'medium'
case 'huge':
return 'large'
}
}

View File

@ -1,2 +0,0 @@
/* istanbul ignore file */
export { default as NDynamicTags } from './src/DynamicTags.vue'

View File

@ -0,0 +1,2 @@
/* istanbul ignore file */
export { default as NDynamicTags } from './src/DynamicTags'

View File

@ -0,0 +1,202 @@
import {
h,
defineComponent,
ref,
PropType,
CSSProperties,
computed,
nextTick
} from 'vue'
import commonProps from '../../tag/src/common-props'
import { AddIcon } from '../../_base/icons'
import { NButton } from '../../button'
import { InputRef, NInput } from '../../input'
import { NTag } from '../../tag'
import { NBaseIcon } from '../../_base'
import { useTheme, useFormItem, useLocale } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { warn, call, MaybeArray, smallerSize } from '../../_utils'
import { dynamicTagsLight } from '../styles'
import type { DynamicTagsTheme } from '../styles'
import type { OnUpdateValue } from './interface'
import style from './styles/index.cssr'
export default defineComponent({
name: 'DynamicTags',
props: {
...(useTheme.props as ThemeProps<DynamicTagsTheme>),
...commonProps,
closable: {
type: Boolean,
default: true
},
value: {
type: Array as PropType<string[]>,
default: () => {
return []
}
},
tagStyle: {
type: Object as PropType<CSSProperties>,
default: () => {
return {
marginRight: '6px'
}
}
},
inputStyle: {
type: Object as PropType<CSSProperties>,
default: () => {
return {
width: '64px'
}
}
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
// deprecated
onChange: {
type: [Function, Array] as PropType<
MaybeArray<OnUpdateValue> | undefined
>,
validator: () => {
if (__DEV__) {
warn(
'dynamic-tags',
'`on-change` is deprecated, please use `on-update:value` instead.'
)
}
return true
},
default: undefined
}
},
setup (props) {
const { locale } = useLocale('DynamicTags')
const formItem = useFormItem(props)
const inputValueRef = ref('')
const showInputRef = ref(false)
const inputForceFocusedRef = ref(true)
const inputInstRef = ref<InputRef | null>(null)
const themeRef = useTheme(
'DynamicTags',
'DynamicTags',
style,
dynamicTagsLight,
props
)
const localizedAddRef = computed(() => {
return locale.value.add
})
const inputSizeRef = computed(() => {
return smallerSize(props.size)
})
function doChange (value: string[]): void {
const { onChange, 'onUpdate:value': onUpdateValue } = props
const { nTriggerFormInput, nTriggerFormChange } = formItem
if (onChange) call(onChange, value)
if (onUpdateValue) call(onUpdateValue, value)
nTriggerFormInput()
nTriggerFormChange()
}
function handleCloseClick (index: number): void {
const tags = props.value.slice(0)
tags.splice(index, 1)
doChange(tags)
}
function handleInputKeyUp (e: KeyboardEvent): void {
switch (e.code) {
case 'Enter':
handleInputConfirm()
}
}
function handleInputConfirm (): void {
if (inputValueRef.value) {
const tags = props.value.slice(0)
tags.push(inputValueRef.value)
doChange(tags)
}
showInputRef.value = false
inputForceFocusedRef.value = true
inputValueRef.value = ''
}
function handleInputBlur (): void {
handleInputConfirm()
}
function handleAddClick (): void {
showInputRef.value = true
void nextTick(() => {
inputInstRef.value?.focus()
inputForceFocusedRef.value = false
})
}
return {
inputInstRef,
localizedAdd: localizedAddRef,
inputSize: inputSizeRef,
inputValue: inputValueRef,
showInput: showInputRef,
inputForceFocused: inputForceFocusedRef,
handleInputKeyUp,
handleAddClick,
handleInputBlur,
handleCloseClick,
mergedTheme: themeRef
}
},
render () {
const { mergedTheme } = this
return (
<div class="n-dynamic-tags">
{this.value.map((tag, index) => (
<NTag
key={index}
unstableTheme={mergedTheme.peers.Tag}
unstableThemeOverrides={mergedTheme.overrides.Tag}
style={this.tagStyle}
type={this.type}
round={this.round}
size={this.size}
closable={this.closable}
disabled={this.disabled}
onClose={() => this.handleCloseClick(index)}
>
{{ defualt: () => tag }}
</NTag>
))}
{this.showInput ? (
<NInput
ref="inputInstRef"
value={this.inputValue}
onUpdateValue={(v) => {
this.inputValue = v
}}
forceFocus={this.inputForceFocused}
unstableTheme={mergedTheme.peers.Input}
unstableThemeOverrides={mergedTheme.overrides.Input}
style={this.inputStyle}
size={this.inputSize}
placeholder=""
onKeyup={this.handleInputKeyUp}
onBlur={this.handleInputBlur}
/>
) : (
<NButton
dashed
unstableTheme={this.mergedTheme.peers.Button}
unstableThemeOverrides={this.mergedTheme.overrides.Button}
size={this.inputSize}
onClick={this.handleAddClick}
>
{{
icon: () => (
<NBaseIcon>{{ default: () => <AddIcon /> }}</NBaseIcon>
)
}}
</NButton>
)}
</div>
)
}
})

View File

@ -1,184 +0,0 @@
<template>
<div class="n-dynamic-tags">
<n-tag
v-for="(tag, index) in value"
:key="index"
:unstable-theme="mergedTheme.peers.Tag"
:unstable-theme-overrides="mergedTheme.overrides.Tag"
:style="tagStyle"
:type="type"
:round="round"
:size="size"
:closable="closable"
:disabled="disabled"
@close="handleCloseClick(index)"
>
{{ tag }}
</n-tag>
<n-input
v-if="inputVisible"
ref="tagInput"
v-model:value="inputValue"
:force-focus="inputForceFocused"
:unstable-theme="mergedTheme.peers.Input"
:unstable-theme-overrides="mergedTheme.overrides.Input"
:style="inputStyle"
:size="inputSize"
placeholder=""
@keyup.enter="handleInputConfirm"
@blur="handleInputBlur"
/>
<n-button
v-else
dashed
:unstable-theme="mergedTheme.peers.Button"
:unstable-theme-overrides="mergedTheme.overrides.Button"
:size="inputSize"
@click="handleAddClick"
>
<template #icon>
<n-base-icon>
<add-icon />
</n-base-icon>
</template>
</n-button>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
import commonProps from '../../tag/src/common-props'
import { AddIcon } from '../../_base/icons'
import { NButton } from '../../button'
import { NTag } from '../../tag'
import { NBaseIcon } from '../../_base'
import { useTheme, useFormItem, useLocale } from '../../_mixins'
import { warn, call } from '../../_utils'
import { dynamicTagsLight } from '../styles'
import style from './styles/index.cssr.js'
export default defineComponent({
name: 'DynamicTags',
components: {
NTag,
NButton,
NBaseIcon,
AddIcon
},
props: {
...useTheme.props,
...commonProps,
closable: {
type: Boolean,
default: true
},
value: {
type: Array,
default: () => {
return []
}
},
tagStyle: {
type: Object,
default: () => {
return {
marginRight: '6px'
}
}
},
inputStyle: {
type: Object,
default: () => {
return {
width: '64px'
}
}
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': {
type: [Function, Array],
default: undefined
},
// deprecated
onChange: {
validator () {
if (__DEV__) {
warn(
'dynamic-tags',
'`on-change` is deprecated, please use `on-update:value` instead.'
)
}
return true
},
default: undefined
}
},
setup (props) {
const themeRef = useTheme(
'DynamicTags',
'DynamicTags',
style,
dynamicTagsLight,
props
)
return {
...useLocale('DynamicTags'),
inputValue: ref(''),
inputVisible: ref(false),
inputForceFocused: ref(true),
mergedTheme: themeRef,
...useFormItem(props)
}
},
computed: {
localizedAdd () {
return this.locale.add
},
inputSize () {
const sizes = ['small', 'medium', 'large']
const tagSizeIndex = sizes.findIndex((size) => size === this.size)
const inputSizeIndex = tagSizeIndex - 1 > 0 ? tagSizeIndex - 1 : 0
return sizes[inputSizeIndex]
}
},
methods: {
doChange (value) {
const {
onChange,
'onUpdate:value': onUpdateValue,
nTriggerFormInput,
nTriggerFormChange
} = this
if (onChange) call(onChange, value)
if (onUpdateValue) call(onUpdateValue, value)
nTriggerFormInput()
nTriggerFormChange()
},
handleCloseClick (index) {
const tags = this.value.slice(0)
tags.splice(index, 1)
this.doChange(tags)
},
handleInputConfirm () {
if (this.inputValue) {
const tags = this.value.slice(0)
tags.push(this.inputValue)
this.doChange(tags)
}
this.inputVisible = false
this.inputForceFocused = true
this.inputValue = ''
},
handleInputBlur () {
this.handleInputConfirm()
},
handleAddClick () {
this.inputVisible = true
this.$nextTick(() => {
this.$refs.tagInput.focus()
this.inputForceFocused = false
})
}
}
})
</script>

View File

@ -0,0 +1 @@
export type OnUpdateValue = (value: string[]) => void

View File

@ -2,8 +2,9 @@ import { tagDark } from '../../tag/styles'
import { inputDark } from '../../input/styles'
import { buttonDark } from '../../button/styles'
import { commonDark } from '../../_styles/new-common'
import type { DynamicTagsTheme } from './light'
export default {
const dynamicTagsDark: DynamicTagsTheme = {
name: 'DynamicTags',
common: commonDark,
peers: {
@ -12,3 +13,5 @@ export default {
Tag: tagDark
}
}
export default dynamicTagsDark

View File

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

View File

@ -0,0 +1,3 @@
export { default as dynamicTagsDark } from './dark'
export { default as dynamicTagsLight } from './light'
export type { DynamicTagsTheme, DynamicTagsThemeVars } from './light'

View File

@ -2,8 +2,9 @@ import { tagLight } from '../../tag/styles'
import { inputLight } from '../../input/styles'
import { buttonLight } from '../../button/styles'
import { commonLight } from '../../_styles/new-common'
import { createTheme } from '../../_mixins'
export default {
const dynamicTagsLight = createTheme({
name: 'DynamicTags',
common: commonLight,
peers: {
@ -11,4 +12,9 @@ export default {
Button: buttonLight,
Tag: tagLight
}
}
})
export default dynamicTagsLight
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DynamicTagsThemeVars {}
export type DynamicTagsTheme = typeof dynamicTagsLight

View File

@ -1,4 +1,4 @@
export type Size = 'small' | 'medium' | 'large'
export type Size = 'tiny' | 'small' | 'medium' | 'large'
// null is for clearable
export type OnUpdateValue = <