Skip to content

Commit 6078444

Browse files
committed
2 parents 3a74a3f + b720d15 commit 6078444

File tree

10 files changed

+207
-17
lines changed

10 files changed

+207
-17
lines changed
Lines changed: 165 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,173 @@
11
<script setup lang="ts">
2-
import { getCurrentInstance, onMounted } from '@vue/runtime-core'
2+
import { computed, ref, toRef, watch } from '@vue/runtime-core'
3+
import { onInputData } from '../composables/input'
4+
import { isKeyDataEvent } from '../input/types'
5+
import { useInterval } from '../composables/utils'
6+
import { useFocus } from '../focus/Focusable'
37
4-
const instance = getCurrentInstance()
8+
const emit = defineEmits<{
9+
(event: 'update:modelValue', value: string): void
10+
}>()
511
6-
console.log(!!instance)
7-
debugger
12+
const props = withDefaults(
13+
defineProps<{
14+
modelValue?: string
15+
disabled?: boolean
16+
label?: string
17+
minWidth?: number
18+
type?: 'text' | 'password'
19+
}>(),
20+
{
21+
minWidth: 10,
22+
type: 'text',
23+
}
24+
)
825
9-
onMounted(() => {
10-
console.log(!!instance)
11-
debugger
26+
const { active, disabled } = useFocus({
27+
// @ts-expect-error: vue bug?
28+
disabled: toRef(props, 'disabled'),
29+
})
30+
31+
watch(active, () => {
32+
if (active.value) {
33+
restartBlinking()
34+
}
35+
})
36+
37+
// used for fallback value when no v-model
38+
const localText = ref('')
39+
const text = computed({
40+
get() {
41+
return props.modelValue ?? localText.value
42+
},
43+
set(value) {
44+
if (props.modelValue == null) {
45+
localText.value = value
46+
} else {
47+
emit('update:modelValue', value)
48+
}
49+
},
50+
})
51+
const cursorPosition = ref(text.value.length)
52+
const displayedValue = computed(() => {
53+
const textValue =
54+
props.type === 'text' ? text.value : '*'.repeat(text.value.length)
55+
if (showCursorBlock.value && active.value) {
56+
return (
57+
textValue.slice(0, cursorPosition.value) +
58+
FULL_BLOCK +
59+
textValue.slice(cursorPosition.value + 1) +
60+
(cursorPosition.value >= textValue.length ? '' : ' ')
61+
)
62+
}
63+
return textValue + ' '
64+
})
65+
66+
const FULL_BLOCK = '\u{2588}' // '█'
67+
const showCursorBlock = ref(true)
68+
69+
const { restart } = useInterval(() => {
70+
showCursorBlock.value = !showCursorBlock.value
71+
}, 700)
72+
// allows to always show the cursor while moving it
73+
function restartBlinking() {
74+
restart()
75+
showCursorBlock.value = true
76+
}
77+
78+
onInputData(({ event }) => {
79+
if (active.value && !disabled.value) {
80+
if (isKeyDataEvent(event)) {
81+
switch (event.key) {
82+
// cursor moving
83+
case 'ArrowLeft':
84+
cursorPosition.value = Math.max(0, cursorPosition.value - 1)
85+
// TODO: handle alt, ctrl
86+
restartBlinking()
87+
break
88+
case 'ArrowRight':
89+
cursorPosition.value = Math.min(
90+
text.value.length,
91+
cursorPosition.value + 1
92+
)
93+
restartBlinking()
94+
break
95+
96+
case 'Backspace':
97+
if (cursorPosition.value > 0) {
98+
text.value =
99+
text.value.slice(0, cursorPosition.value - 1) +
100+
text.value.slice(cursorPosition.value)
101+
cursorPosition.value--
102+
}
103+
break
104+
case 'e':
105+
case 'E':
106+
if (event.ctrlKey) {
107+
cursorPosition.value = text.value.length
108+
restartBlinking()
109+
break
110+
}
111+
112+
case 'a':
113+
case 'A':
114+
if (event.ctrlKey) {
115+
cursorPosition.value = 0
116+
restartBlinking()
117+
break
118+
}
119+
120+
case 'u':
121+
case 'U':
122+
if (event.ctrlKey) {
123+
text.value = text.value.slice(cursorPosition.value)
124+
cursorPosition.value = 0
125+
restartBlinking()
126+
break
127+
}
128+
129+
case 'k':
130+
case 'K':
131+
if (event.ctrlKey) {
132+
text.value = text.value.slice(0, cursorPosition.value)
133+
restartBlinking()
134+
break
135+
}
136+
137+
default:
138+
if (
139+
event.key.length === 1 &&
140+
!event.altKey &&
141+
!event.ctrlKey &&
142+
!event.metaKey
143+
) {
144+
text.value =
145+
text.value.slice(0, cursorPosition.value) +
146+
event.key +
147+
text.value.slice(cursorPosition.value)
148+
cursorPosition.value++
149+
}
150+
break
151+
}
152+
}
153+
}
12154
})
13155
</script>
14156

15-
<template>
16-
<TuiText dimmed>Label: </TuiText>
17-
<TuiText>value</TuiText>
157+
<template borderStyle="round">
158+
<Box>
159+
<Box alignItems="center">
160+
<!-- Could also be a title prop in Box -->
161+
<Text dimmed>{{ label }} </Text>
162+
</Box>
163+
<Box
164+
borderStyle="round"
165+
:borderColor="disabled ? 'gray' : active ? 'blue' : undefined"
166+
:backgroundColor="active ? 'blue' : undefined"
167+
:height="3"
168+
:minWidth="minWidth"
169+
>
170+
<Text :dimmed="disabled">{{ displayedValue }}</Text>
171+
</Box>
172+
</Box>
18173
</template>

packages/core/src/components/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ export { TuiBox } from './Box'
66
export { TuiProgressBar } from './ProgressBar'
77

88
export { TuiLink } from './Link'
9-
// export { default as TuiInput } from './Input.vue'
9+
export { default as TuiInput } from './Input.vue'

packages/core/src/composables/utils.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ import { onMounted, onUnmounted, ref } from '@vue/runtime-core'
22

33
export function useInterval(fn: () => void, interval?: number) {
44
let handle: ReturnType<typeof setInterval>
5-
onMounted(() => {
5+
6+
function restart() {
7+
stop()
68
handle = setInterval(fn, interval)
7-
})
8-
onUnmounted(() => {
9+
}
10+
function stop() {
911
clearInterval(handle)
10-
})
12+
}
13+
14+
onMounted(restart)
15+
onUnmounted(stop)
16+
17+
return { restart, stop }
1118
}
1219

1320
export function useTimeout(fn: () => void, delay?: number) {

packages/core/src/focus/Focusable.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
computed,
3-
customRef,
43
onUnmounted,
54
ref,
65
watch,

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
TuiProgressBar,
1112
} from './components'
1213

packages/core/src/shims-vue.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module '*.vue' {
2+
import type { DefineComponent } from '@vue/runtime-core'
3+
const component: DefineComponent<{}, {}, any>
4+
export default component
5+
}

packages/playground/components.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ export {}
77

88
declare module '@vue/runtime-core' {
99
export interface GlobalComponents {
10+
Box: typeof import('vue-termui')['TuiBox']
11+
Br: typeof import('vue-termui')['TuiNewline']
12+
Div: typeof import('vue-termui')['TuiBox']
13+
Input: typeof import('./src/components/Input.vue')['default']
14+
Link: typeof import('vue-termui')['TuiLink']
1015
Newline: typeof import('vue-termui')['TuiNewline']
1116
Progressbar: typeof import('vue-termui')['TuiProgressBar']
1217
Text: typeof import('vue-termui')['TuiText']
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
import { onKeyData, ref, TuiInput as MyInput } from 'vue-termui'
3+
4+
const msg = ref('Hello World')
5+
6+
const disabled = ref(false)
7+
onKeyData(['d', 'D'], () => {
8+
disabled.value = !disabled.value
9+
})
10+
</script>
11+
12+
<template borderStyle="round">
13+
<Text bold>{{ msg }}</Text>
14+
<MyInput label="Enter a message: " v-model="msg" :disabled="disabled" />
15+
<MyInput type="password" />
16+
</template>

packages/playground/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { createApp } from 'vue-termui'
77
// import App from './App.vue'
88
// import App from './Counter.vue'
99
// import App from './Borders.vue'
10-
import App from './ProgressBarDemo.vue'
10+
import App from './InputDemo.vue'
1111

1212
createApp(App, {
1313
// 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)