Skip to content

Commit 4fb918e

Browse files
committed
feat(Textarea, TextInput): added support for number, trim and lazy v-model modifiers
1 parent a3483f9 commit 4fb918e

File tree

4 files changed

+78
-41
lines changed

4 files changed

+78
-41
lines changed

apps/docs/src/stories/Components/TextInput.story.vue

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,27 @@
88

99
<story-canvas title="Basic">
1010
<pf-text-input v-model="text1" aria-label="text input example" />
11+
<pre>Value: {{ JSON.stringify(text1) }}</pre>
1112
</story-canvas>
1213

1314
<story-canvas title="Numeric">
1415
<pf-text-input v-model="number1" type="number" aria-label="text input example" />
16+
<pre>Value: {{ JSON.stringify(number1) }}</pre>
17+
</story-canvas>
18+
19+
<story-canvas title="Number modifier">
20+
<pf-text-input v-model.number="number2" aria-label="text input example" />
21+
<pre>Value: {{ JSON.stringify(number2) }}</pre>
22+
</story-canvas>
23+
24+
<story-canvas title="Trim modifier">
25+
<pf-text-input v-model.trim="text2" aria-label="text input example" />
26+
<pre>Value: {{ JSON.stringify(text2) }}</pre>
27+
</story-canvas>
28+
29+
<story-canvas title="Lazy modifier">
30+
<pf-text-input v-model.lazy="text3" aria-label="text input example" />
31+
<pre>Value: {{ JSON.stringify(text3) }}</pre>
1532
</story-canvas>
1633

1734
<story-canvas title="Disabled">
@@ -56,7 +73,7 @@
5673
/>
5774
<br>
5875
<pf-text-input
59-
v-model="text2"
76+
v-model="text4"
6077
required
6178
pattern="[0-9a-f]*"
6279
placeholder="hexadecimal value (validates on change)"
@@ -110,5 +127,8 @@ import ClockIcon from '@vue-patternfly/icons/clock-icon';
110127
111128
const text1: Ref<string | null> = ref(null);
112129
const text2 = ref('');
130+
const text3 = ref('');
131+
const text4 = ref('');
113132
const number1 = ref(0);
133+
const number2 = ref(0);
114134
</script>

packages/core/src/components/TextInput.vue

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@
3838
</template>
3939

4040
<script lang="ts">
41-
export type InputType = 'text' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' | 'time' | 'url' | 'week';
42-
43-
export interface Props<T extends InputType = 'text'> extends OUIAProps, /* @vue-ignore */ Omit<InputHTMLAttributes, 'value' | 'type' | 'aria-invalid'> {
41+
export interface Props<T extends InputType = 'text', N extends boolean = false> extends OUIAProps, /* @vue-ignore */ Omit<InputHTMLAttributes, 'value' | 'type' | 'aria-invalid'> {
4442
/** Flag to show if the text input is disabled. */
4543
disabled?: boolean;
4644
/** Flag to apply expanded styling */
@@ -57,6 +55,11 @@ export interface Props<T extends InputType = 'text'> extends OUIAProps, /* @vue-
5755
type?: T;
5856
/** Value of the text input. */
5957
modelValue?: string | number | null;
58+
modelModifiers?: {
59+
number?: N;
60+
trim?: boolean;
61+
lazy?: boolean;
62+
};
6063
/** Aria-label. The text input requires an associated id or aria-label. */
6164
ariaLabel?: string;
6265
/** Trim text at start */
@@ -67,11 +70,11 @@ export interface Props<T extends InputType = 'text'> extends OUIAProps, /* @vue-
6770
}
6871
</script>
6972

70-
<script lang="ts" setup generic="T extends InputType = 'text'">
73+
<script lang="ts" setup generic="T extends InputType = 'text', N extends boolean = false">
7174
import { computed, toRefs, type InputHTMLAttributes, getCurrentInstance, useTemplateRef } from 'vue';
7275
import { useChildrenTracker } from '../use';
7376
import styles from '@patternfly/react-styles/css/components/FormControl/form-control';
74-
import { useInputValidation, type InputValidateState } from '../input';
77+
import { useInputValidation, type InputType, type InputValidateState } from '../input';
7578
import { FormGroupInputsKey, FormInputsKey } from './Form/common';
7679
import { useOUIAProps, type OUIAProps } from '../helpers/ouia';
7780
import PfFormControlIcon from './FormControlIcon.vue';
@@ -81,7 +84,7 @@ defineOptions({
8184
inheritAttrs: false,
8285
});
8386
84-
const props = withDefaults(defineProps<Props<T>>(), {
87+
const props = withDefaults(defineProps<Props<T, N>>(), {
8588
autoValidate: true,
8689
modelValue: undefined,
8790
});
@@ -94,7 +97,7 @@ defineEmits<{
9497
(name: 'input', event: Event): void;
9598
(name: 'invalid', event: Event): void;
9699
(name: 'keyup', event: KeyboardEvent): void;
97-
(name: 'update:modelValue', value: T extends 'number' ? number : string): void;
100+
(name: 'update:modelValue', value: N extends true ? number : (T extends 'number' ? number : string)): void;
98101
(name: 'update:validated', value: InputValidateState): void;
99102
}>();
100103
@@ -113,14 +116,15 @@ const {
113116
effectiveValidated,
114117
onBlur,
115118
onChange,
116-
onInput: commonOnInput,
119+
onInput,
117120
onInvalid,
118121
onKeyUp,
119122
...inputValidationData
120123
} = useInputValidation({
121124
inputElement: input,
122125
autoValidate: props.autoValidate,
123126
validated: validated,
127+
type: props.type,
124128
});
125129
126130
useChildrenTracker(FormInputsKey, getCurrentInstance()?.proxy);
@@ -130,18 +134,6 @@ function focus() {
130134
input.value?.focus();
131135
}
132136
133-
function onInput(event: InputEvent) {
134-
if (!input.value) {
135-
return;
136-
}
137-
const value = input.value.value;
138-
if (props.type === 'number') {
139-
commonOnInput(event, value ? parseFloat(value) : null);
140-
} else {
141-
commonOnInput(event, value);
142-
}
143-
}
144-
145137
defineExpose({
146138
...inputValidationData,
147139
input,

packages/core/src/components/Textarea.vue

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,9 @@
3838
</span>
3939
</template>
4040

41-
<script lang="ts" setup>
42-
import styles from '@patternfly/react-styles/css/components/FormControl/form-control';
43-
44-
import { computed, onMounted, toRefs, type TextareaHTMLAttributes, getCurrentInstance, useTemplateRef } from 'vue';
45-
import { useInputValidation } from '../input';
46-
import { useChildrenTracker } from '../use';
47-
import { FormGroupInputsKey, FormInputsKey } from './Form/common';
48-
import { useOUIAProps, type OUIAProps } from '../helpers/ouia';
49-
import PfFormControlIcon from './FormControlIcon.vue';
5041

51-
defineOptions({
52-
name: 'PfTextarea',
53-
inheritAttrs: false,
54-
});
55-
56-
export interface Props extends OUIAProps, /* @vue-ignore */ Omit<TextareaHTMLAttributes, 'value' | 'aria-invalid'> {
42+
<script lang="ts">
43+
export interface Props<N extends boolean = false> extends OUIAProps, /* @vue-ignore */ Omit<TextareaHTMLAttributes, 'value' | 'aria-invalid'> {
5744
/** Flag to show if the text area is disabled. */
5845
disabled?: boolean;
5946
@@ -75,6 +62,11 @@ export interface Props extends OUIAProps, /* @vue-ignore */ Omit<TextareaHTMLAtt
7562
7663
/** Value of the text area. */
7764
modelValue?: string | number | null;
65+
modelModifiers?: {
66+
number?: N;
67+
trim?: boolean;
68+
lazy?: boolean;
69+
};
7870
7971
/** Value to indicate if the text area is modified to show that validation state.
8072
* If set to success, text area will be modified to indicate valid state.
@@ -90,8 +82,24 @@ export interface Props extends OUIAProps, /* @vue-ignore */ Omit<TextareaHTMLAtt
9082
/** Maximum width */
9183
maxWidth?: string;
9284
}
85+
</script>
86+
87+
<script lang="ts" setup generic="N extends boolean = false">
88+
import styles from '@patternfly/react-styles/css/components/FormControl/form-control';
89+
90+
import { computed, onMounted, toRefs, type TextareaHTMLAttributes, getCurrentInstance, useTemplateRef } from 'vue';
91+
import { useInputValidation } from '../input';
92+
import { useChildrenTracker } from '../use';
93+
import { FormGroupInputsKey, FormInputsKey } from './Form/common';
94+
import { useOUIAProps, type OUIAProps } from '../helpers/ouia';
95+
import PfFormControlIcon from './FormControlIcon.vue';
96+
97+
defineOptions({
98+
name: 'PfTextarea',
99+
inheritAttrs: false,
100+
});
93101
94-
const props = withDefaults(defineProps<Props>(), {
102+
const props = withDefaults(defineProps<Props<N>>(), {
95103
resizeOrientation: 'both',
96104
autoValidate: true,
97105
modelValue: undefined,
@@ -105,7 +113,7 @@ defineEmits<{
105113
(name: 'input', event: Event): void;
106114
(name: 'invalid', event: Event): void;
107115
(name: 'keyup', event: KeyboardEvent): void;
108-
(name: 'update:modelValue', value: string): void;
116+
(name: 'update:modelValue', value: N extends true ? number : string): void;
109117
(name: 'update:validated', value: 'success' | 'warning' | 'error' | 'default'): void;
110118
}>();
111119

packages/core/src/input.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ import { onMounted } from "vue";
33

44
export type InputValidateState = 'success' | 'warning' | 'error' | 'default';
55
type InputElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
6+
export type InputType = 'text' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' | 'time' | 'url' | 'week';
67

78
export function useInputValidation({
89
autoValidate,
910
validated,
1011
inputElement,
12+
type,
1113
customCheckValidity,
1214
}: {
1315
autoValidate: '' | 'blur' | 'input' | 'change' | 'enter' | boolean;
1416
validated?: Ref<InputValidateState | undefined>;
1517
inputElement?: MaybeRef<InputElement | null>;
18+
type?: InputType;
1619
customCheckValidity?: () => boolean;
1720
}) {
1821
const instance = getCurrentInstance()?.proxy;
@@ -21,7 +24,16 @@ export function useInputValidation({
2124
const effectiveValidated = computed(() => validated?.value ?? innerValidated.value);
2225
watch(effectiveValidated, () => instance?.$emit('update:validated', effectiveValidated.value));
2326

24-
const value = useModel((instance?.$props ?? {}) as { modelValue?: string | number | null }, 'modelValue');
27+
const [value, modifiers] = useModel((instance?.$props ?? {}) as { modelValue?: string | number | null }, 'modelValue', {
28+
get: (v) => v,
29+
set: (v) => {
30+
// force number cast unlike default number modifier
31+
if (modifiers.number || type === 'number') {
32+
return Number(v) as any;
33+
}
34+
return String(v) as any;
35+
},
36+
});
2537

2638
function getInput() {
2739
return unref(inputElement) ?? (instance?.$el as InputElement | undefined);
@@ -96,9 +108,11 @@ export function useInputValidation({
96108
reportValidity,
97109
setCustomValidity,
98110

99-
onInput(event: InputEvent, value?: any) {
111+
onInput(event: InputEvent) {
100112
instance?.$emit('input', event);
101-
value.value = value ?? (event.target as InputElement).value;
113+
if (!modifiers.lazy) {
114+
value.value = (event.target as InputElement).value;
115+
}
102116
if (autoValidate === 'input') {
103117
reportValidity();
104118
} else {
@@ -117,6 +131,9 @@ export function useInputValidation({
117131

118132
onChange(event: Event) {
119133
instance?.$emit('change', event);
134+
if (modifiers.lazy) {
135+
value.value = (event.target as InputElement).value;
136+
}
120137
if (autoValidate === 'change') {
121138
reportValidity();
122139
}

0 commit comments

Comments
 (0)