feat(input): adds count-graphemes prop, closes #3967

This commit is contained in:
07akioni 2022-10-29 21:52:16 +08:00
parent 8884e31093
commit 367ef3df2d
11 changed files with 124 additions and 10 deletions

View File

@ -15,6 +15,7 @@
- `n-tree-select` adds `blur` method.
- `n-cascader` adds `getCheckedKeys` method.
- `n-cascader` adds `getIndeterminateKeys` method.
- `n-input` adds `count-graphemes` prop, closes [#3967](https://github.com/tusen-ai/naive-ui/issues/3967).
### i18n

View File

@ -15,6 +15,7 @@
- `n-tree-select` 新增 `blur` 方法
- `n-cascader` 新增 `getCheckedKeys` 方法
- `n-cascader` 新增 `getIndeterminateKeys` 方法
- `n-input` 新增 `count-graphemes` 属性,关闭 [#3967](https://github.com/tusen-ai/naive-ui/issues/3967)
### i18n

View File

@ -115,6 +115,7 @@
"express": "^4.17.3",
"fast-glob": "^3.2.11",
"fs-extra": "^10.0.1",
"grapheme-splitter": "^1.0.4",
"husky": "^8.0.1",
"inquirer": "^9.1.0",
"jest": "^28.0.3",

View File

@ -0,0 +1,35 @@
<markdown>
# Count graphemes
Browser's default `maxlength` and `minlength` and naive-ui's builtin character count method can't split all string correctly. You can use `count-graphemes` count to count graphemes correctly.
</markdown>
<template>
<n-form>
<n-form-item label="Default behavior">
<n-input default-value="🌷🏳🌈" show-count :maxlength="12" />
</n-form-item>
<n-form-item label="Correct behavior">
<n-input
default-value="🌷🏳️‍🌈"
show-count
:maxlength="12"
:count-graphemes="countGraphemes"
/>
</n-form-item>
</n-form>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import GraphemeSplitter from 'grapheme-splitter'
export default defineComponent({
setup () {
const splitter = new GraphemeSplitter()
return {
countGraphemes: (value: string) => splitter.countGraphemes(value)
}
}
})
</script>

View File

@ -23,6 +23,7 @@ event.vue
input-props.vue
status.vue
pattern.vue
graphemes.vue
```
## API
@ -35,6 +36,7 @@ pattern.vue
| autofocus | `boolean` | `false` | Whether to autofocus. | |
| autosize | `boolean \| { minRows?: number, maxRows?: number }` | `false` | Sizing property for when the input is of type `textarea`. e.g. `{ minRows: 1, maxRows: 3 }`. | |
| clearable | `boolean` | `false` | Whether the input is clearable. | |
| count-graphemes | `(value: string) => number` | `undefined` | Count graphemes of input value. If it's set, native `maxlength` and `minlength` won't be used. | NEXT_VERSION |
| default-value | `string \| [string, string] \| null` | `null` | Default value when not manually set. | |
| disabled | `boolean` | `false` | Whether to disable the input. | |
| input-props | `HTMLInputAttributes` | `undefined` | The dom props of the input element inside the component. This is disabled if the `pair` property is true. For avaiable attributes, [see here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). WarningIt won't override internal props with the same name (except `type`). | |

View File

@ -0,0 +1,35 @@
<markdown>
# 字素计数
浏览器默认的 `maxlength` `minlength` 以及 naive-ui 自带的字数统计功能并不能准确的拆分所有的字符串你可以使用 `count-graphemes` 属性来精确的测量文字长度
</markdown>
<template>
<n-form>
<n-form-item label="默认表现">
<n-input default-value="🌷🏳🌈" show-count :maxlength="12" />
</n-form-item>
<n-form-item label="正确表现">
<n-input
default-value="🌷🏳️‍🌈"
show-count
:maxlength="12"
:count-graphemes="countGraphemes"
/>
</n-form-item>
</n-form>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import GraphemeSplitter from 'grapheme-splitter'
export default defineComponent({
setup () {
const splitter = new GraphemeSplitter()
return {
countGraphemes: (value: string) => splitter.countGraphemes(value)
}
}
})
</script>

View File

@ -23,6 +23,7 @@ event.vue
input-props.vue
status.vue
pattern.vue
graphemes.vue
rtl-debug.vue
prefix-debug.vue
modal-debug.vue
@ -40,6 +41,7 @@ textarea-resize-debug.vue
| autosize | `boolean \| { minRows?: number, maxRows?: number }` | `false` | 自适应内容高度,只对 `type="textarea"` 有效,可传入对象,如 `{ minRows: 1, maxRows: 3 }` | |
| clearable | `boolean` | `false` | 是否可清空 | |
| default-value | `string \| [string, string] \| null` | `null` | 输入框默认值 | |
| count-graphemes | `(value: string) => number` | `undefined` | 计算输入的字数。如果设定了,那么原生的 `maxlength``minlength` 属性将不再被使用 | NEXT_VERSION |
| disabled | `boolean` | `false` | 是否禁用 | |
| input-props | `HTMLInputAttributes` | `undefined` | Input 组件内部 input 元素的属性,对 `pair` 类型不生效,[在这里查看原生属性](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)。注意input-props 不会覆盖内部 input 元素的已经存在的属性(除了 `type` | |
| loading | `boolean` | `undefined` | 是否展示加载图标,设为非 `undefined` 会占据空间 | |

View File

@ -134,6 +134,7 @@ export const inputProps = {
onClick: [Function, Array] as PropType<MaybeArray<(e: MouseEvent) => void>>,
onChange: [Function, Array] as PropType<OnUpdateValue>,
onClear: [Function, Array] as PropType<MaybeArray<(e: MouseEvent) => void>>,
countGraphemes: Function as PropType<(value: string) => number>,
status: String as PropType<FormValidationStatus>,
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
@ -487,6 +488,22 @@ export default defineComponent({
}
}
function allowInput (value: string): boolean {
const { countGraphemes, maxlength, minlength } = props
if (countGraphemes) {
let graphemesCount: number | undefined
if (maxlength !== undefined) {
if (graphemesCount === undefined) {
graphemesCount = countGraphemes(value)
}
if (graphemesCount > Number(maxlength)) return false
}
if (minlength !== undefined) {
if (graphemesCount === undefined) {
graphemesCount = countGraphemes(value)
}
if (graphemesCount < Number(maxlength)) return false
}
}
const { allowInput } = props
if (typeof allowInput === 'function') {
return allowInput(value)
@ -780,7 +797,8 @@ export default defineComponent({
provide(inputInjectionKey, {
mergedValueRef,
maxlengthRef,
mergedClsPrefixRef
mergedClsPrefixRef,
countGraphemesRef: toRef(props, 'countGraphemes')
})
const exposedProps: InputWrappedRef = {
@ -978,7 +996,14 @@ export default defineComponent({
}
},
render () {
const { mergedClsPrefix, mergedStatus, themeClass, type, onRender } = this
const {
mergedClsPrefix,
mergedStatus,
themeClass,
type,
countGraphemes,
onRender
} = this
const $slots = this.$slots as {
prefix?: () => VNode[]
suffix?: () => VNode[]
@ -1069,8 +1094,8 @@ export default defineComponent({
placeholder={this.placeholder as string | undefined}
value={this.mergedValue as string | undefined}
disabled={this.mergedDisabled}
maxlength={this.maxlength as any}
minlength={this.minlength as any}
maxlength={countGraphemes ? undefined : this.maxlength}
minlength={countGraphemes ? undefined : this.minlength}
readonly={this.readonly as any}
tabindex={
this.passivelyActivated && !this.activated
@ -1145,8 +1170,8 @@ export default defineComponent({
}
placeholder={this.mergedPlaceholder[0]}
disabled={this.mergedDisabled}
maxlength={this.maxlength as any}
minlength={this.minlength as any}
maxlength={countGraphemes ? undefined : this.maxlength}
minlength={countGraphemes ? undefined : this.minlength}
value={
Array.isArray(this.mergedValue)
? this.mergedValue[0]
@ -1267,8 +1292,8 @@ export default defineComponent({
}
placeholder={this.mergedPlaceholder[1]}
disabled={this.mergedDisabled}
maxlength={this.maxlength as any}
minlength={this.minlength as any}
maxlength={countGraphemes ? undefined : this.maxlength}
minlength={countGraphemes ? undefined : this.minlength}
value={
Array.isArray(this.mergedValue)
? this.mergedValue[1]

View File

@ -6,13 +6,18 @@ import { len } from './utils'
export default defineComponent({
name: 'InputWordCount',
setup (_, { slots }) {
const { mergedValueRef, maxlengthRef, mergedClsPrefixRef } =
const {
mergedValueRef,
maxlengthRef,
mergedClsPrefixRef,
countGraphemesRef
} =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
inject(inputInjectionKey)!
const wordCountRef = computed(() => {
const { value: mergedValue } = mergedValueRef
if (mergedValue === null || Array.isArray(mergedValue)) return 0
return len(mergedValue)
return (countGraphemesRef.value || len)(mergedValue)
})
return () => {
const { value: maxlength } = maxlengthRef

View File

@ -22,6 +22,7 @@ export interface InputWrappedRef {
export type InputInst = UnwrapRef<InputWrappedRef>
export const inputInjectionKey = createInjectionKey<{
countGraphemesRef: Ref<((input: string) => number) | undefined>
mergedValueRef: Ref<string | [string, string] | null>
maxlengthRef: Ref<number | undefined>
mergedClsPrefixRef: Ref<string>

View File

@ -48,6 +48,7 @@ module.exports = {
'@css-render/vue3-ssr',
'date-fns-tz',
'codesandbox/lib/api/define',
'grapheme-splitter',
'highlight.js/lib/core',
'highlight.js/lib/languages/javascript',
'highlight.js/lib/languages/python',
@ -82,6 +83,11 @@ module.exports = {
},
build: {
outDir: 'site',
output: {
manualChunks: {
'grapheme-splitter': ['grapheme-splitter']
}
},
rollupOptions: {
plugins: [
babel({