Skip to content

Commit 98051af

Browse files
committed
feat: Input component
1 parent 4396572 commit 98051af

File tree

8 files changed

+168
-19
lines changed

8 files changed

+168
-19
lines changed

packages/core/src/components/Input.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { defineComponent, h, ref, computed, PropType } from '@vue/runtime-core'
2+
import chalk from 'chalk'
3+
import { TuiText } from './Text'
4+
import { onInputData } from '../composables/input'
5+
import type { KeyDataEvent } from '../input/types'
6+
7+
const SKIP_EVENT_KEY = ['ArrowUp', 'ArrowDown', 'Ctrl', 'Tab', 'Shift']
8+
const PWD_FIGURE = '*'
9+
10+
export const TuiInput = defineComponent({
11+
props: {
12+
placeholder: {
13+
type: String,
14+
required: false,
15+
default: '',
16+
},
17+
cursor: {
18+
type: Boolean,
19+
required: false,
20+
default: true,
21+
},
22+
modelValue: {
23+
type: String,
24+
required: true,
25+
},
26+
type: {
27+
type: String as PropType<'text' | 'password'>,
28+
required: false,
29+
default: 'text',
30+
},
31+
},
32+
emits: ['update:modelValue', 'change', 'submit'],
33+
setup(props, { emit }) {
34+
const cursorOffset = ref(props.modelValue.length)
35+
const exited = ref(false)
36+
const content = computed(() => {
37+
if (props.cursor && !exited.value) {
38+
if (props.modelValue) {
39+
if (
40+
props.modelValue &&
41+
props.modelValue.length <= cursorOffset.value
42+
) {
43+
return (
44+
(props.type === 'text'
45+
? props.modelValue
46+
: PWD_FIGURE.repeat(props.modelValue.length)) +
47+
chalk.inverse(' ')
48+
)
49+
}
50+
51+
const l = props.modelValue.slice(0, cursorOffset.value)
52+
const m = chalk.inverse(props.modelValue[cursorOffset.value])
53+
const r = props.modelValue.slice(cursorOffset.value + 1)
54+
55+
return props.type === 'text'
56+
? l + m + r
57+
: PWD_FIGURE.repeat(l.length) +
58+
chalk.inverse(PWD_FIGURE) +
59+
PWD_FIGURE.repeat(r.length)
60+
} else {
61+
return props.placeholder ? '' : chalk.inverse(' ')
62+
}
63+
} else {
64+
const value = props.modelValue
65+
return props.type === 'text' ? value : PWD_FIGURE.repeat(value.length)
66+
}
67+
})
68+
69+
function updateCursorOffset(type: '-' | '+') {
70+
if (type === '-') {
71+
cursorOffset.value = Math.max(0, cursorOffset.value - 1)
72+
} else {
73+
cursorOffset.value = Math.min(
74+
cursorOffset.value + 1,
75+
+props.modelValue.length + 1
76+
)
77+
}
78+
}
79+
80+
function updateValue(value: string) {
81+
emit('change', value)
82+
emit('update:modelValue', value)
83+
}
84+
85+
const stop = onInputData(({ data, event }) => {
86+
const eventKey = (<KeyDataEvent>event!).key
87+
if (SKIP_EVENT_KEY.includes(eventKey) || !eventKey) return
88+
89+
// Submit
90+
if (eventKey === 'Enter') {
91+
stop()
92+
exited.value = true
93+
emit('submit', props.modelValue)
94+
return
95+
}
96+
97+
// Move cursor
98+
if (props.cursor && eventKey === 'ArrowLeft') {
99+
updateCursorOffset('-')
100+
} else if (props.cursor && eventKey === 'ArrowRight') {
101+
updateCursorOffset('+')
102+
}
103+
// Delete Content
104+
else if (eventKey === 'Backspace' || eventKey === 'Delete') {
105+
if (!props.cursor) {
106+
updateValue(props.modelValue.slice(0, -1))
107+
} else if (props.cursor && cursorOffset.value > 0) {
108+
updateValue(
109+
props.modelValue.slice(0, cursorOffset.value - 1) +
110+
props.modelValue.slice(cursorOffset.value)
111+
)
112+
updateCursorOffset('-')
113+
}
114+
}
115+
// Typing Content
116+
else {
117+
updateValue(
118+
props.cursor
119+
? props.modelValue.slice(0, cursorOffset.value) +
120+
data +
121+
props.modelValue.slice(cursorOffset.value)
122+
: props.modelValue + data
123+
)
124+
updateCursorOffset('+')
125+
if (props.cursor && cursorOffset.value === props.modelValue.length) {
126+
updateCursorOffset('+')
127+
}
128+
}
129+
})
130+
131+
return () =>
132+
h(TuiText, () =>
133+
props.placeholder && !props.modelValue
134+
? h(
135+
TuiText,
136+
{
137+
dimmed: true,
138+
},
139+
() => props.placeholder
140+
)
141+
: h(TuiText, () => content.value)
142+
)
143+
},
144+
})

packages/core/src/components/Input.vue

Lines changed: 0 additions & 18 deletions
This file was deleted.

packages/core/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { TuiTextTransform } from './TextTransform'
33
export { TuiNewline } from './Newline'
44
export { TuiApp } from './App'
55
export { TuiBox } from './Box'
6+
export { TuiInput } from './Input'
67

78
export { TuiLink } from './Link'
89
// export { default as TuiInput } from './Input.vue'

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {
77
TuiNewline,
88
TuiLink,
99
TuiTextTransform,
10+
TuiInput,
1011
} from './components'
1112

1213
export { render } from './renderer'

packages/playground/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ declare module '@vue/runtime-core' {
1010
Box: typeof import('vue-termui')['TuiBox']
1111
Br: typeof import('vue-termui')['TuiNewline']
1212
Div: typeof import('vue-termui')['TuiBox']
13+
Input: typeof import('vue-termui')['TuiInput']
1314
Link: typeof import('vue-termui')['TuiLink']
1415
Span: typeof import('vue-termui')['TuiText']
1516
Text: typeof import('vue-termui')['TuiText']

packages/playground/src/InputDemo.vue

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script setup lang="ts">
2+
const value = ref('')
3+
const visible = ref(false)
4+
function submit() {
5+
visible.value = true
6+
}
7+
</script>
8+
9+
<template borderStyle="round">
10+
<Input
11+
v-model="value"
12+
placeholder="Hello World"
13+
@submit="submit"
14+
type="password"
15+
/>
16+
<Text v-if="visible">Value:{{ value }}</Text>
17+
</template>

packages/playground/src/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// import devtools from '@vue/devtools'
22
// import devtools from '@vue/devtools/node'
33
import { createApp } from 'vue-termui'
4-
import App from './Focusables.vue'
4+
// import App from './Focusables.vue'
55
// import App from './Fragments.vue'
66
// import App from './CenteredDemo.vue'
77
// import App from './App.vue'
88
// import App from './Counter.vue'
99
// import App from './Borders.vue'
10+
import App from './InputDemo.vue'
1011

1112
createApp(App, {
1213
// swapScreens: true,

packages/vite-plugin-vue-termui/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ export const VueTuiComponents = new Map<string, ModuleExports>([
172172
['div', 'TuiBox'],
173173
['box', 'TuiBox'],
174174

175+
['input', 'TuiInput'],
176+
175177
['transform', 'TuiTextTransform'],
176178
['text-transform', 'TuiTextTransform'],
177179
])

0 commit comments

Comments
 (0)