Skip to content

fix(k-input): typescript improvements for k-input #2706

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 39 additions & 81 deletions src/components/KInput/KInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,81 +91,39 @@

<script setup lang="ts">
import { computed, ref, watch, useSlots, useAttrs, onMounted, nextTick, useId } from 'vue'
import type { PropType } from 'vue'
import type { LabelAttributes, LimitExceededData } from '@/types'
import type { InputProps, InputEmits, InputSlots } from '@/types'
import useUtilities from '@/composables/useUtilities'
import KLabel from '@/components/KLabel/KLabel.vue'
import { KUI_ICON_SIZE_40 } from '@kong/design-tokens'
import { VisibilityIcon, VisibilityOffIcon } from '@kong/icons'

const props = defineProps({
modelValue: {
type: [String, Number],
default: '',
},
label: {
type: String,
default: '',
},
labelAttributes: {
type: Object as PropType<LabelAttributes>,
default: () => ({}),
validator: (value: LabelAttributes): boolean => {
if (value.help) {
console.warn('KInput: `help` property of `labelAttributes` prop is deprecated. Please use `info` prop instead. See the migration guide for more details: https://kongponents.konghq.com/guide/migrating-to-version-9.html#klabel')
}

return true
},
},
help: {
type: String,
default: '',
},
error: {
type: Boolean,
default: false,
},
errorMessage: {
type: String,
default: '',
},
characterLimit: {
type: Number,
default: null,
// Ensure the characterLimit is greater than zero
validator: (limit: number): boolean => limit > 0,
},
/**
* @deprecated in favor of `error`
*/
hasError: {
type: Boolean,
default: false,
validator: (value: boolean): boolean => {
if (value) {
console.warn('KInput: `hasError` prop is deprecated. Please use `error` prop instead. See the migration guide for more details: https://kongponents.konghq.com/guide/migrating-to-version-9.html#kinput')
}
const {
modelValue = '',
label = '',
labelAttributes = {},
help = '',
error = false,
errorMessage = '',
characterLimit = null,
hasError = false,
type = 'text',
showPasswordMaskToggle = false,
} = defineProps<InputProps>()

watch(() => hasError, (val) => {
if (val) {
console.warn('KInput: `hasError` prop is deprecated. Please use `error` prop instead. See the migration guide for more details: https://kongponents.konghq.com/guide/migrating-to-version-9.html#kinput')
}
}, { immediate: true })

return true
},
},
type: {
type: String,
required: false,
default: 'text',
},
showPasswordMaskToggle: {
type: Boolean,
default: false,
},
watch(() => labelAttributes.help, (help) => {
if (help) {
console.warn('KInput: `help` property of `labelAttributes` prop is deprecated. Please use `info` prop instead. See the migration guide for more details: https://kongponents.konghq.com/guide/migrating-to-version-9.html#klabel')
}
})

const emit = defineEmits<{
(e: 'input', val: string): void
(e: 'update:modelValue', val: string): void
(e: 'char-limit-exceeded', val: LimitExceededData): void
}>()
const emit = defineEmits<InputEmits>()
defineSlots<InputSlots>()

const currValue = ref<string>('') // We need this so that we don't lose the updated value on hover/blur event with label
const modelValueChanged = ref<boolean>(false) // Determine if the original value was modified by the user
Expand All @@ -179,13 +137,13 @@ const isRequired = computed((): boolean => attrs?.required !== undefined && Stri
const defaultId = useId()
const inputId = computed((): string => attrs.id ? String(attrs.id) : defaultId)
const helpTextId = useId()
const strippedLabel = computed((): string => stripRequiredLabel(props.label, isRequired.value))
const hasLabelTooltip = computed((): boolean => !!(props.labelAttributes?.info || slots['label-tooltip']))
const strippedLabel = computed((): string => stripRequiredLabel(label, isRequired.value))
const hasLabelTooltip = computed((): boolean => !!(labelAttributes?.info || slots['label-tooltip']))

// we need this so we can create a watcher for programmatic changes to the modelValue
const value = computed({
get(): string | number {
return props.modelValue
return modelValue
},
set(newValue: string | number): void {
// @ts-ignore: allow typing as Event
Expand All @@ -207,7 +165,7 @@ const modifiedAttrs = computed((): Record<string, any> => {

const charLimitExceeded = computed((): boolean => {
const currValLength = currValue.value?.toString().length || 0
const modelValLength = props.modelValue?.toString().length || 0
const modelValLength = modelValue?.toString().length || 0

// default to length of currVal
let length = currValLength
Expand All @@ -217,7 +175,7 @@ const charLimitExceeded = computed((): boolean => {
length = modelValLength
}

return !!props.characterLimit && length > props.characterLimit
return !!characterLimit && length > characterLimit
})

const charLimitExceededErrorMessage = computed((): string => {
Expand All @@ -226,8 +184,8 @@ const charLimitExceededErrorMessage = computed((): string => {
}

return modelValueChanged.value
? `${currValue.value?.toString().length} / ${props.characterLimit}`
: `${props.modelValue?.toString().length} / ${props.characterLimit}`
? `${currValue.value?.toString().length} / ${characterLimit}`
: `${modelValue?.toString().length} / ${characterLimit}`
})

const helpText = computed((): string => {
Expand All @@ -237,21 +195,21 @@ const helpText = computed((): string => {
}

// if error prop is true and there is an error message, return that
if ((props.error || props.hasError) && props.errorMessage) {
return props.errorMessage
if ((error || hasError) && errorMessage) {
return errorMessage
}

// otherwise return the help text
// if error prop is true it danger styles will be applied
return props.help
return help
})

watch(charLimitExceeded, (newVal, oldVal) => {
if (newVal !== oldVal) {
emit('char-limit-exceeded', {
value: currValue.value,
length: currValue.value.length,
characterLimit: props.characterLimit,
characterLimit: characterLimit!,
limitExceeded: newVal,
})

Expand Down Expand Up @@ -284,10 +242,10 @@ const updateInputValue = (value: string): void => {

const getValue = (): string | number => {
// Use the modelValue only if it was initialized to something and the value hasn't been changed
return currValue.value || modelValueChanged.value ? currValue.value : props.modelValue
return currValue.value || modelValueChanged.value ? currValue.value : modelValue
}

watch(() => props.error, (newVal, oldVal) => {
watch(() => error, (newVal, oldVal) => {
if (newVal !== oldVal) {
// bump the key to trigger the transition
helpTextKey.value += 1
Expand All @@ -301,7 +259,7 @@ const afterSlotElementWidth = ref<string>(KUI_ICON_SIZE_40) // default to slot i

const maskValue = ref<boolean>(false)
const inputType = computed((): string => {
return props.type === 'password' && maskValue.value ? 'text' : props.type
return type === 'password' && maskValue.value ? 'text' : type
})

onMounted(async () => {
Expand Down
98 changes: 98 additions & 0 deletions src/types/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,104 @@ export interface LabelAttributes {
help?: string
}

export interface InputProps {
/**
* To set the value of the input element without using `v-model`.
* @defaults ''
*/
modelValue?: string | number

/**
* String to be used as the input label.
* @defaults ''
*/
label?: string

/**
* KCheckbox has an instance of KLabel for supporting tooltip text.
* Use the labelAttributes prop to configure the KLabel's props.
* @defaults {}
*/
labelAttributes?: LabelAttributes

/**
* String to be displayed as help text.
* @defaults ''
*/
help?: string

/**
* Boolean to indicate whether the element is in an error state and should apply error styling.
* @defaults false
*/
error?: boolean

/**
* String to be displayed as an error message if `error` prop is `true`.
* This prop will supersede the `help` prop if both have a value and `error` is `true`.
* @defaults false
*/
errorMessage?: string

/**
* Use this prop to specify a character limit for the input.
* See the @char-limit-exceeded event for more details.
* @defaults null
*/
characterLimit?: number

/**
* @deprecated in favor of `error`
*/
hasError?: boolean

/**
* HTML Input Element `type` attribute.
* @defaults 'text'
*/
type?: string

/**
* When passing type="password", setting showPasswordMaskToggle to true
* will render an eye icon to the right of input clicking on which
* allows toggling masking the input value on and off.
* @defaults false
*/
showPasswordMaskToggle?: boolean
}

export interface InputEmits {
/**
* To listen for changes to the KInput value.
*/
input: [val: string]

/**
* To listen for changes to the KInput value, automatically set by `v-model`.
*/
'update:modelValue': [val: string]

/**
* Fired when the text starts or stops exceeding the limit, returns an object @LimitExceededData.
*/
'char-limit-exceeded': [val: LimitExceededData]
}

export interface InputSlots {
/**
* Inserting icons before the input field.
*/
before?(): any
/**
* Inserting icons after the input field.
*/
after?(): any
/**
* Slot for customizing the input label's tooltip.
*/
'label-tooltip'?(): any
}

export interface LimitExceededData {
value: string
length: number
Expand Down