Skip to content

Commit

Permalink
feat: Input component
Browse files Browse the repository at this point in the history
  • Loading branch information
posva authored Nov 14, 2022
1 parent 370f62a commit fbaf83c
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 16 deletions.
15 changes: 11 additions & 4 deletions packages/core/src/composables/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import { onMounted, onUnmounted, ref } from '@vue/runtime-core'

export function useInterval(fn: () => void, interval?: number) {
let handle: ReturnType<typeof setInterval>
onMounted(() => {

function restart() {
stop()
handle = setInterval(fn, interval)
})
onUnmounted(() => {
}
function stop() {
clearInterval(handle)
})
}

onMounted(restart)
onUnmounted(stop)

return { restart, stop }
}

export function useTimeout(fn: () => void, delay?: number) {
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ declare module '@vue/runtime-core' {
Box: typeof import('vue-termui')['TuiBox']
Br: typeof import('vue-termui')['TuiNewline']
Div: typeof import('vue-termui')['TuiBox']
Input: typeof import('vue-termui')['TuiInput']
Input: typeof import('./src/components/Input.vue')['default']
Link: typeof import('vue-termui')['TuiLink']
Newline: typeof import('vue-termui')['TuiNewline']
Progressbar: typeof import('vue-termui')['TuiProgressBar']
Expand Down
21 changes: 10 additions & 11 deletions packages/playground/src/InputDemo.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
<script setup lang="ts">
const value = ref('')
const isFocus = ref(true)
const value1 = ref('')
const isFocus1 = ref(false)
import { onKeyData, ref } from 'vue-termui'
import MyInput from './components/Input.vue'
watch(value, (v: string) => {
if (v === 'hello') {
isFocus.value = false
isFocus1.value = true
}
const msg = ref('Hello World')
const disabled = ref(false)
onKeyData(['d', 'D'], () => {
disabled.value = !disabled.value
})
</script>

<template borderStyle="round">
<Input v-model="value" :focus="isFocus" />
<Input v-model="value1" :focus="isFocus1" type="password" />
<Text bold>{{ msg }}</Text>
<MyInput label="Enter a message: " v-model="msg" :disabled="disabled" />
<MyInput type="password" />
</template>
182 changes: 182 additions & 0 deletions packages/playground/src/components/Input.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<script setup lang="ts">
import {
computed,
getCurrentInstance,
isKeyDataEvent,
onInputData,
onKeyData,
onMounted,
ref,
toRef,
toRefs,
useFocus,
useInterval,
watch,
} from 'vue-termui'
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void
}>()
const props = withDefaults(
defineProps<{
modelValue?: string
disabled?: boolean
label?: string
minWidth?: number
type?: 'text' | 'password'
}>(),
{
minWidth: 10,
type: 'text',
}
)
const { active, disabled } = useFocus({
// @ts-expect-error: vue bug?
disabled: toRef(props, 'disabled'),
})
watch(active, () => {
if (active.value) {
restartBlinking()
}
})
// used for fallback value when no v-model
const localText = ref('')
const text = computed({
get() {
return props.modelValue ?? localText.value
},
set(value) {
if (props.modelValue == null) {
localText.value = value
} else {
emit('update:modelValue', value)
}
},
})
const cursorPosition = ref(text.value.length)
const displayedValue = computed(() => {
const textValue =
props.type === 'text' ? text.value : '*'.repeat(text.value.length)
if (showCursorBlock.value && active.value) {
return (
textValue.slice(0, cursorPosition.value) +
FULL_BLOCK +
textValue.slice(cursorPosition.value + 1) +
(cursorPosition.value >= textValue.length ? '' : ' ')
)
}
return textValue + ' '
})
const FULL_BLOCK = '\u{2588}' // '█'
const showCursorBlock = ref(true)
const { restart } = useInterval(() => {
showCursorBlock.value = !showCursorBlock.value
}, 700)
// allows to always show the cursor while moving it
function restartBlinking() {
restart()
showCursorBlock.value = true
}
onInputData(({ event }) => {
if (active.value && !disabled.value) {
if (isKeyDataEvent(event)) {
switch (event.key) {
// cursor moving
case 'ArrowLeft':
cursorPosition.value = Math.max(0, cursorPosition.value - 1)
// TODO: handle alt, ctrl
restartBlinking()
break
case 'ArrowRight':
cursorPosition.value = Math.min(
text.value.length,
cursorPosition.value + 1
)
restartBlinking()
break
case 'Backspace':
if (cursorPosition.value > 0) {
text.value =
text.value.slice(0, cursorPosition.value - 1) +
text.value.slice(cursorPosition.value)
cursorPosition.value--
}
break
case 'e':
case 'E':
if (event.ctrlKey) {
cursorPosition.value = text.value.length
restartBlinking()
break
}
case 'a':
case 'A':
if (event.ctrlKey) {
cursorPosition.value = 0
restartBlinking()
break
}
case 'u':
case 'U':
if (event.ctrlKey) {
text.value = text.value.slice(cursorPosition.value)
cursorPosition.value = 0
restartBlinking()
break
}
case 'k':
case 'K':
if (event.ctrlKey) {
text.value = text.value.slice(0, cursorPosition.value)
restartBlinking()
break
}
default:
if (
event.key.length === 1 &&
!event.altKey &&
!event.ctrlKey &&
!event.metaKey
) {
text.value =
text.value.slice(0, cursorPosition.value) +
event.key +
text.value.slice(cursorPosition.value)
cursorPosition.value++
}
break
}
}
}
})
</script>

<template borderStyle="round">
<Box>
<Box alignItems="center">
<!-- Could also be a title prop in Box -->
<Text dimmed>{{ label }} </Text>
</Box>
<Box
borderStyle="round"
:borderColor="disabled ? 'gray' : active ? 'blue' : undefined"
:backgroundColor="active ? 'blue' : undefined"
:height="3"
:minWidth="minWidth"
>
<Text :dimmed="disabled">{{ displayedValue }}</Text>
</Box>
</Box>
</template>

0 comments on commit fbaf83c

Please sign in to comment.