Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Commit cc3ffa7

Browse files
committed
fix(vue): handle input without vmodel
Signed-off-by: Shyrro <[email protected]>
1 parent d955fa5 commit cc3ffa7

File tree

3 files changed

+83
-62
lines changed

3 files changed

+83
-62
lines changed

packages/vue/src/polymorphic-factory.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
computed,
1111
} from 'vue'
1212
import type { IntrinsicElementAttributes } from './dom.types'
13-
import { useVModel } from './use-v-model'
13+
import { getAttributes } from './use-v-model'
1414

1515
export type DOMElements = keyof IntrinsicElementAttributes
1616

@@ -20,17 +20,17 @@ export type ComponentWithAs<
2020
Component extends ElementType,
2121
P extends Record<string, unknown> = Record<never, never>,
2222
> = {
23-
new (): {
23+
new(): {
2424
$props: AllowedComponentProps &
25-
ComponentCustomProps &
26-
VNodeProps &
27-
ExtractPropTypes<Component> &
28-
(Component extends keyof IntrinsicElementAttributes
29-
? IntrinsicElementAttributes[Component]
30-
: Record<never, never>) &
31-
P & {
32-
as?: ElementType
33-
}
25+
ComponentCustomProps &
26+
VNodeProps &
27+
ExtractPropTypes<Component> &
28+
(Component extends keyof IntrinsicElementAttributes
29+
? IntrinsicElementAttributes[Component]
30+
: Record<never, never>) &
31+
P & {
32+
as?: ElementType
33+
}
3434
}
3535
}
3636

@@ -59,13 +59,13 @@ function defaultStyled(originalComponent: ElementType) {
5959
emits: ['update:modelValue', 'input', 'change'],
6060
setup(props, { slots, attrs, emit }) {
6161
const Component = props.as || originalComponent
62-
const vmodelAttrs = computed(() =>
63-
useVModel(Component as string, props.modelValue, emit, attrs),
62+
const componentAttrs = computed(() =>
63+
getAttributes(Component as string, props.modelValue, emit, attrs),
6464
)
6565

6666
return () => (
67-
<Component {...vmodelAttrs.value} {...attrs}>
68-
{slots?.default?.()}
67+
<Component {...componentAttrs.value}>
68+
{() => slots?.default?.()}
6969
</Component>
7070
)
7171
},

packages/vue/src/use-v-model.ts

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,80 @@
1-
import { computed } from 'vue'
1+
import { computed, ref } from 'vue'
22

33
const formElements = ['input', 'select', 'textarea', 'fieldset', 'datalist', 'option', 'optgroup']
44

5-
export const useVModel = (
6-
elementTag: string,
7-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8-
modelValue: any,
9-
emit: CallableFunction,
10-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11-
attrs: any,
12-
) => {
13-
let vmodelAttrs = {}
14-
const handleMultipleCheckbox = (value: unknown) => {
15-
const currentModelValue = [...modelValue]
16-
if (currentModelValue.includes(value)) {
17-
currentModelValue.splice(currentModelValue.indexOf(value), 1)
18-
return currentModelValue
19-
} else {
20-
return [...currentModelValue, value]
21-
}
22-
}
23-
const handleInput = (e: Event) => {
24-
emit(
25-
'update:modelValue',
26-
attrs.type === 'checkbox' && Array.isArray(modelValue)
27-
? handleMultipleCheckbox((e?.currentTarget as HTMLInputElement)?.value)
28-
: typeof modelValue === 'boolean'
29-
? (e?.currentTarget as HTMLInputElement)?.checked
30-
: (e?.currentTarget as HTMLInputElement)?.value,
31-
)
32-
emit('input', e, (e?.currentTarget as HTMLInputElement)?.value)
33-
emit(
34-
'change',
35-
e,
36-
typeof modelValue === 'boolean'
37-
? (e?.currentTarget as HTMLInputElement)?.checked
38-
: (e?.currentTarget as HTMLInputElement)?.value,
39-
)
5+
const handleMultipleCheckbox = <TModelValue extends Array<any>>(value: unknown, modelValue: TModelValue) => {
6+
const currentModelValue = [...modelValue]
7+
// If the value is already checked, uncheck it
8+
// else, add it to the checked array.
9+
if (currentModelValue.includes(value)) {
10+
currentModelValue.splice(currentModelValue.indexOf(value), 1)
11+
return currentModelValue
12+
} else {
13+
return [...currentModelValue, value]
4014
}
15+
}
16+
17+
/**
18+
* Function that emits the right events when using v-model.
19+
*/
20+
function handleInput<
21+
TEmit extends CallableFunction,
22+
TModelValue extends Array<any>,
23+
TAttrs extends Record<string, unknown>
24+
>(e: Event, emit: TEmit, modelValue: TModelValue, attrs: TAttrs) {
25+
emit(
26+
'update:modelValue',
27+
attrs.type === 'checkbox' && Array.isArray(modelValue)
28+
? handleMultipleCheckbox((e?.currentTarget as HTMLInputElement)?.value, modelValue)
29+
: typeof modelValue === 'boolean'
30+
? (e?.currentTarget as HTMLInputElement)?.checked
31+
: (e?.currentTarget as HTMLInputElement)?.value,
32+
)
33+
emit('input', e, (e?.currentTarget as HTMLInputElement)?.value)
34+
emit(
35+
'change',
36+
e,
37+
typeof modelValue === 'boolean'
38+
? (e?.currentTarget as HTMLInputElement)?.checked
39+
: (e?.currentTarget as HTMLInputElement)?.value,
40+
)
41+
}
4142

42-
if (formElements.includes(elementTag)) {
43-
let val: Record<string, unknown> = { value: modelValue }
43+
export function getAttributes<
44+
TModelValue extends Array<any>,
45+
TEmit extends CallableFunction,
46+
TAttrs extends Record<string, unknown>>
47+
(
48+
elementTag: string,
49+
modelValue: TModelValue,
50+
emit: TEmit,
51+
attrs: TAttrs,
52+
) {
53+
54+
const val = ref<Record<string, unknown>>({ value: modelValue })
55+
const attributes = ref({ ...attrs })
56+
57+
// Only do this if v-model directive is provided, otherwise return user props
58+
if (formElements.includes(elementTag) && (modelValue !== null || modelValue !== undefined)) {
59+
4460
if (elementTag === 'input' && (attrs.type === 'checkbox' || attrs.type === 'radio')) {
4561
const isChecked = computed(() =>
62+
// If it's a boolean, it's probably a single checkbox or a radio button
63+
// If it's not, it's multiple checkboxes
4664
typeof modelValue === 'boolean' ? modelValue : modelValue.includes(attrs.value),
4765
)
48-
val = {
66+
67+
val.value = {
4968
checked: isChecked.value,
5069
}
5170
}
52-
vmodelAttrs = {
53-
...val,
54-
onInput: handleInput,
71+
72+
attributes.value = {
73+
...val.value,
74+
onInput: (e: Event) => handleInput(e, emit, modelValue, attrs),
75+
...attrs
5576
}
5677
}
57-
58-
return vmodelAttrs
78+
79+
return attributes.value
5980
}

packages/vue/test/poly-test.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ const singleCheckbox = ref(true)
2020
<poly.p>selected : {{ selectValue }}</poly.p>
2121

2222
<poly.div>
23-
<poly.input type="radio" v-model="radioValue" value="vue"></poly.input>
24-
<poly.input type="radio" value="react" v-model="radioValue" data-testid="react-radio"></poly.input>
25-
<poly.input type="radio" value="angular" v-model="radioValue"></poly.input>
23+
<poly.input name="framework" type="radio" v-model="radioValue" value="vue"></poly.input>
24+
<poly.input name="framework" type="radio" value="react" v-model="radioValue" data-testid="react-radio"></poly.input>
25+
<poly.input name="framework" type="radio" value="angular" v-model="radioValue"></poly.input>
2626
</poly.div>
2727
<poly.p>Radio: {{ radioValue }}</poly.p>
2828

0 commit comments

Comments
 (0)