mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-02-17 13:20:52 +08:00
feat(input): adds count-graphemes
prop, closes #3967
This commit is contained in:
parent
8884e31093
commit
367ef3df2d
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
35
src/input/demos/enUS/graphemes.demo.vue
Normal file
35
src/input/demos/enUS/graphemes.demo.vue
Normal 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>
|
@ -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). Warning:It won't override internal props with the same name (except `type`). | |
|
||||
|
35
src/input/demos/zhCN/graphemes.demo.vue
Normal file
35
src/input/demos/zhCN/graphemes.demo.vue
Normal 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>
|
@ -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` 会占据空间 | |
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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({
|
||||
|
Loading…
Reference in New Issue
Block a user