Skip to content

Commit c66bb10

Browse files
committed
feat: bubble input/change for form component
1 parent 171dd00 commit c66bb10

File tree

4 files changed

+85
-33
lines changed

4 files changed

+85
-33
lines changed

packages/core/src/Select/BubbleSelect.vue

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref } from 'vue'
2+
import { ref, watch } from 'vue'
33
import { VisuallyHidden } from '@/VisuallyHidden'
44
55
interface BubbleSelectProps {
@@ -18,20 +18,19 @@ const props = defineProps<BubbleSelectProps>()
1818
const selectElement = ref<HTMLElement>()
1919
2020
// This would bubble "change" event to form, with the target as Select element.
21-
// We temporary disable this as not sure if it will be needed for Vue
22-
// watch(value, () => {
23-
// const selectProto = window.HTMLSelectElement.prototype;
24-
// const descriptor = Object.getOwnPropertyDescriptor(
25-
// selectProto,
26-
// "value"
27-
// ) as PropertyDescriptor;
28-
// const setValue = descriptor.set;
29-
// if (prevValue.value !== value.value && setValue) {
30-
// const event = new Event("change", { bubbles: true });
31-
// setValue.call(selectElement.value, value.value);
32-
// selectElement.value?.dispatchEvent(event);
33-
// }
34-
// });
21+
watch(() => props.value, (cur, prev) => {
22+
const selectProto = window.HTMLSelectElement.prototype
23+
const descriptor = Object.getOwnPropertyDescriptor(
24+
selectProto,
25+
'value',
26+
) as PropertyDescriptor
27+
const setValue = descriptor.set
28+
if (cur !== prev && setValue) {
29+
const event = new Event('change', { bubbles: true })
30+
setValue.call(selectElement.value, cur)
31+
selectElement.value?.dispatchEvent(event)
32+
}
33+
})
3534
3635
/**
3736
* We purposefully use a `select` here to support form autofill as much

packages/core/src/ToggleGroup/ToggleGroupRoot.vue

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<script lang="ts">
22
import type { ComputedRef, Ref } from 'vue'
33
import type { PrimitiveProps } from '@/Primitive'
4-
import type { AcceptableValue, DataOrientation, Direction, SingleOrMultipleProps, SingleOrMultipleType } from '../shared/types'
5-
import { createContext, useDirection, useForwardExpose } from '@/shared'
4+
import type { AcceptableValue, DataOrientation, Direction, FormFieldProps, SingleOrMultipleProps, SingleOrMultipleType } from '../shared/types'
5+
import { createContext, useDirection, useFormControl, useForwardExpose } from '@/shared'
6+
import VisuallyHiddenInput from '@/VisuallyHidden/VisuallyHiddenInput.vue'
67
78
export interface ToggleGroupRootProps<ValidValue = AcceptableValue | AcceptableValue[], ExplicitType = SingleOrMultipleType>
8-
extends PrimitiveProps, SingleOrMultipleProps<ValidValue, ExplicitType> {
9+
extends PrimitiveProps, FormFieldProps, SingleOrMultipleProps<ValidValue, ExplicitType> {
910
/** When `false`, navigating through the items using arrow keys will be disabled. */
1011
rovingFocus?: boolean
1112
/** When `true`, prevents the user from interacting with the toggle group and all its items. */
@@ -59,9 +60,10 @@ defineSlots<{
5960
6061
const { loop, rovingFocus, disabled, dir: propDir } = toRefs(props)
6162
const dir = useDirection(propDir)
62-
const { forwardRef } = useForwardExpose()
63+
const { forwardRef, currentElement } = useForwardExpose()
6364
6465
const { modelValue, changeModelValue, isSingle } = useSingleOrMultipleValue(props, emits)
66+
const isFormControl = useFormControl(currentElement)
6567
6668
provideToggleGroupRootContext({
6769
isSingle,
@@ -90,6 +92,13 @@ provideToggleGroupRootContext({
9092
:as="as"
9193
>
9294
<slot :model-value="modelValue" />
95+
96+
<VisuallyHiddenInput
97+
v-if="isFormControl && name"
98+
:name="name"
99+
:required="required"
100+
:value="modelValue"
101+
/>
93102
</Primitive>
94103
</component>
95104
</template>

packages/core/src/VisuallyHidden/VisuallyHiddenInput.vue

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
<script setup lang="ts" generic="T">
22
import { computed } from 'vue'
3-
import VisuallyHidden, { type VisuallyHiddenProps } from './VisuallyHidden.vue'
3+
import VisuallyHiddenInputBubble, { type VisuallyHiddenInputBubbleProps } from './VisuallyHiddenInputBubble.vue'
44
55
defineOptions({
66
inheritAttrs: false,
77
})
88
9-
const props = withDefaults(defineProps<{
10-
name: string
11-
value: T
12-
required?: boolean
13-
disabled?: boolean
14-
feature?: VisuallyHiddenProps['feature']
15-
}>(), {
9+
const props = withDefaults(defineProps<VisuallyHiddenInputBubbleProps<T>>(), {
1610
feature: 'fully-hidden',
11+
checked: undefined,
1712
})
1813
1914
const parsedValue = computed(() => {
@@ -44,15 +39,11 @@ const parsedValue = computed(() => {
4439
</script>
4540

4641
<template>
47-
<VisuallyHidden
42+
<VisuallyHiddenInputBubble
4843
v-for="parsed in parsedValue"
4944
:key="parsed.name"
50-
v-bind="$attrs"
51-
as="input"
52-
:feature="feature"
45+
v-bind="{ ...props, ...$attrs }"
5346
:name="parsed.name"
5447
:value="parsed.value"
55-
:required="required"
56-
:disabled="disabled"
5748
/>
5849
</template>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script lang="ts">
2+
export interface VisuallyHiddenInputBubbleProps<T> {
3+
name: string
4+
value: T
5+
checked?: boolean
6+
required?: boolean
7+
disabled?: boolean
8+
feature?: VisuallyHiddenProps['feature']
9+
}
10+
</script>
11+
12+
<script setup lang="ts" generic="T">
13+
import { usePrimitiveElement } from '@/Primitive'
14+
import VisuallyHidden, { type VisuallyHiddenProps } from './VisuallyHidden.vue'
15+
import { computed, watch } from 'vue'
16+
17+
defineOptions({
18+
inheritAttrs: false,
19+
})
20+
21+
const props = withDefaults(defineProps<VisuallyHiddenInputBubbleProps<T>>(), {
22+
feature: 'fully-hidden',
23+
checked: undefined,
24+
})
25+
26+
const { primitiveElement, currentElement } = usePrimitiveElement()
27+
const valueState = computed(() => props.checked ?? props.value)
28+
29+
watch(valueState, (cur, prev) => {
30+
if (!currentElement.value)
31+
return
32+
33+
const input = currentElement.value as HTMLInputElement
34+
const inputProto = window.HTMLInputElement.prototype
35+
const descriptor = Object.getOwnPropertyDescriptor(inputProto, 'value') as PropertyDescriptor
36+
const setValue = descriptor.set
37+
if (setValue && cur !== prev) {
38+
const inputEvent = new Event('input', { bubbles: true })
39+
const changeEvent = new Event('change', { bubbles: true })
40+
setValue.call(input, cur)
41+
input.dispatchEvent(inputEvent)
42+
input.dispatchEvent(changeEvent)
43+
}
44+
})
45+
</script>
46+
47+
<template>
48+
<VisuallyHidden
49+
ref="primitiveElement"
50+
v-bind="{ ...props, ...$attrs }"
51+
as="input"
52+
/>
53+
</template>

0 commit comments

Comments
 (0)