diff --git a/packages/core/src/types/common.ts b/packages/core/src/types/common.ts index 3cff3715..81359aae 100644 --- a/packages/core/src/types/common.ts +++ b/packages/core/src/types/common.ts @@ -24,6 +24,12 @@ export type AriaValidatableProps = { 'aria-invalid'?: boolean; }; +export type AriaInputProps = { + 'aria-checked'?: boolean; + 'aria-readonly'?: boolean; + 'aria-disabled'?: boolean; +}; + export type InputEvents = { onInput?: (event: InputEvent) => void; onChange?: (event: Event) => void; @@ -38,6 +44,7 @@ export type PressEvents = { }; export type InputBaseAttributes = { + name?: string; readonly?: boolean; disabled?: boolean; }; diff --git a/packages/core/src/useCheckbox/useCheckbox.ts b/packages/core/src/useCheckbox/useCheckbox.ts index f353fe8d..db5b2ee9 100644 --- a/packages/core/src/useCheckbox/useCheckbox.ts +++ b/packages/core/src/useCheckbox/useCheckbox.ts @@ -20,6 +20,8 @@ export interface CheckboxProps { export interface CheckboxDomInputProps extends AriaLabelableProps, InputBaseAttributes { type: 'checkbox'; + name?: string; + indeterminate?: boolean; } export interface CheckboxDomProps extends AriaLabelableProps { @@ -121,8 +123,8 @@ export function useCheckbox( inputRef.value?.focus(); } - function createBindings(isInput: boolean) { - return { + function createBindings(isInput: boolean): CheckboxDomProps | CheckboxDomInputProps { + const base = { ...labelledByProps.value, ...createHandlers(isInput), id: inputId, @@ -131,6 +133,21 @@ export function useCheckbox( [isInput ? 'disabled' : 'aria-disabled']: isDisabled() || undefined, [isInput ? 'required' : 'aria-required']: group?.required, }; + + if (isInput) { + return { + ...base, + type: 'checkbox', + name: group?.name || toValue(props.name), + indeterminate: toValue(props.indeterminate) || false, + }; + } + + return { + ...base, + role: 'checkbox', + tabindex: toValue(props.disabled) ? '-1' : '0', + }; } group?.useCheckboxRegistration({ @@ -147,29 +164,8 @@ export function useCheckbox( }, }); - const inputProps = computed(() => - withRefCapture( - { - type: 'checkbox', - name: group?.name || props.name, - indeterminate: toValue(props.indeterminate) || false, - ...createBindings(true), - }, - inputRef, - elementRef, - ), - ); - - const checkboxProps = computed(() => - withRefCapture( - { - role: 'checkbox', - tabindex: toValue(props.disabled) ? '-1' : '0', - ...createBindings(false), - }, - inputRef, - elementRef, - ), + const inputProps = computed(() => + withRefCapture(createBindings(inputRef.value?.tagName === 'INPUT'), inputRef, elementRef), ); function setChecked(force?: boolean) { @@ -196,7 +192,6 @@ export function useCheckbox( inputRef, labelProps, inputProps, - checkboxProps, isChecked: checked, isTouched, setChecked, diff --git a/packages/core/src/useRadio/useRadio.ts b/packages/core/src/useRadio/useRadio.ts index f8536a5c..691f0d68 100644 --- a/packages/core/src/useRadio/useRadio.ts +++ b/packages/core/src/useRadio/useRadio.ts @@ -1,6 +1,6 @@ import { Ref, computed, inject, nextTick, ref, toValue } from 'vue'; import { isEqual, normalizeProps, useUniqId, withRefCapture } from '../utils/common'; -import { AriaLabelableProps, InputBaseAttributes, Reactivify, RovingTabIndex } from '../types'; +import { AriaInputProps, AriaLabelableProps, InputBaseAttributes, Reactivify, RovingTabIndex } from '../types'; import { useLabel } from '../a11y/useLabel'; import { RadioGroupContext, RadioGroupKey } from './useRadioGroup'; import { FieldTypePrefixes } from '../constants'; @@ -15,13 +15,10 @@ export interface RadioDomInputProps extends AriaLabelableProps, InputBaseAttribu type: 'radio'; } -export interface RadioDomProps extends AriaLabelableProps { +export interface RadioDomProps extends AriaInputProps, AriaLabelableProps { tabindex: RovingTabIndex; role: 'radio'; 'aria-checked'?: boolean; - 'aria-readonly'?: boolean; - 'aria-disabled'?: boolean; - 'aria-required'?: boolean; } export function useRadio( @@ -86,17 +83,30 @@ export function useRadio( inputRef.value?.focus(); } - function createBindings(isInput: boolean) { - return { + function createBindings(isInput: boolean): RadioDomInputProps | RadioDomProps { + const base = { ...labelledByProps.value, ...createHandlers(isInput), id: inputId, - name: group?.name, [isInput ? 'checked' : 'aria-checked']: checked.value || undefined, [isInput ? 'readonly' : 'aria-readonly']: group?.readonly || undefined, [isInput ? 'disabled' : 'aria-disabled']: isDisabled() || undefined, [isInput ? 'required' : 'aria-required']: group?.required, }; + + if (isInput) { + return { + ...base, + name: group?.name, + type: 'radio', + }; + } + + return { + ...base, + role: 'radio', + tabindex: checked.value ? '0' : registration?.canReceiveFocus() ? '0' : '-1', + }; } const registration = group?.useRadioRegistration({ @@ -113,34 +123,14 @@ export function useRadio( }, }); - const inputProps = computed(() => - withRefCapture( - { - type: 'radio', - ...createBindings(true), - }, - inputRef, - elementRef, - ), - ); - - const radioProps = computed(() => - withRefCapture( - { - role: 'radio', - tabindex: checked.value ? '0' : registration?.canReceiveFocus() ? '0' : '-1', - ...createBindings(false), - }, - inputRef, - elementRef, - ), + const inputProps = computed(() => + withRefCapture(createBindings(inputRef.value?.tagName === 'INPUT'), inputRef, elementRef), ); return { inputRef, labelProps, inputProps, - radioProps, isChecked: checked, }; } diff --git a/packages/core/src/useSlider/slider.ts b/packages/core/src/useSlider/slider.ts index aeca76a9..5ab251dd 100644 --- a/packages/core/src/useSlider/slider.ts +++ b/packages/core/src/useSlider/slider.ts @@ -1,7 +1,7 @@ import { InjectionKey, computed, onBeforeUnmount, provide, ref, toValue } from 'vue'; import { useLabel } from '../a11y/useLabel'; import { AriaLabelableProps, Arrayable, Direction, Orientation, Reactivify, TypedSchema } from '../types'; -import { isNullOrUndefined, normalizeProps, useUniqId, withRefCapture } from '../utils/common'; +import { isNullOrUndefined, normalizeArrayable, normalizeProps, useUniqId, withRefCapture } from '../utils/common'; import { toNearestMultipleOf } from '../utils/math'; import { useLocale } from '../i18n/useLocale'; import { useFormField } from '../useFormField'; @@ -142,12 +142,12 @@ export function useSlider(_props: Reactivify) { return; } - if (!Array.isArray(fieldValue.value)) { + if (thumbs.value.length <= 1) { setValue(value); return; } - const nextValue = [...fieldValue.value]; + const nextValue = normalizeArrayable(fieldValue.value).filter(v => !isNullOrUndefined(v)); nextValue[idx] = value; setValue(nextValue); } diff --git a/packages/core/src/useSwitch/index.ts b/packages/core/src/useSwitch/index.ts index b9f90ff5..0b88cb2c 100644 --- a/packages/core/src/useSwitch/index.ts +++ b/packages/core/src/useSwitch/index.ts @@ -1,6 +1,7 @@ import { Ref, computed, shallowRef, toValue } from 'vue'; import { AriaDescribableProps, + AriaInputProps, AriaLabelableProps, InputBaseAttributes, InputEvents, @@ -12,11 +13,23 @@ import { useLabel } from '../a11y/useLabel'; import { useFormField } from '../useFormField'; import { FieldTypePrefixes } from '../constants'; -export interface SwitchDOMProps extends InputBaseAttributes, AriaLabelableProps, AriaDescribableProps, InputEvents { +export interface SwitchDomInputProps + extends InputBaseAttributes, + AriaLabelableProps, + InputBaseAttributes, + AriaDescribableProps, + InputEvents { + type: 'checkbox'; + role: 'switch'; +} + +export interface SwitchDOMProps extends AriaInputProps, AriaLabelableProps, AriaDescribableProps, InputEvents { id: string; - checked: boolean; - name?: string; - role?: string; + tabindex: '0'; + role: 'switch'; + 'aria-checked'?: boolean; + + onClick: (e: Event) => void; } export type SwitchProps = { @@ -35,10 +48,10 @@ export type SwitchProps = { export function useSwitch(_props: Reactivify, elementRef?: Ref) { const props = normalizeProps(_props, ['schema']); - const id = useUniqId(FieldTypePrefixes.Switch); + const inputId = useUniqId(FieldTypePrefixes.Switch); const inputRef = elementRef || shallowRef(); const { labelProps, labelledByProps } = useLabel({ - for: id, + for: inputId, label: props.label, targetRef: inputRef, }); @@ -103,39 +116,39 @@ export function useSwitch(_props: Reactivify, elementRef? }, }); - /** - * Use this if you are using a native input[type=checkbox] element. - */ - const inputProps = computed(() => - withRefCapture( - { - ...labelledByProps.value, - id: id, - name: toValue(props.name), - disabled: toValue(props.disabled), - readonly: toValue(props.readonly), - checked: isPressed.value, - role: 'switch', + function createBindings(isInput: boolean): SwitchDOMProps | SwitchDomInputProps { + const base = { + id: inputId, + ...labelledByProps.value, + [isInput ? 'checked' : 'aria-checked']: isPressed.value || false, + [isInput ? 'readonly' : 'aria-readonly']: toValue(props.readonly) || undefined, + [isInput ? 'disabled' : 'aria-disabled']: toValue(props.disabled) || undefined, + role: 'switch' as const, + }; + + if (isInput) { + return { + ...base, ...handlers, - }, - inputRef, - elementRef, - ), - ); + name: toValue(props.name), + type: 'checkbox', + }; + } + + return { + ...base, + onClick, + tabindex: '0', + onKeydown: handlers.onKeydown, + }; + } /** - * Use this if you are using divs or buttons + * Use this if you are using a native input[type=checkbox] element. */ - const switchProps = computed(() => ({ - ...labelledByProps.value, - role: 'switch', - tabindex: '0', - 'aria-checked': isPressed.value ?? false, - 'aria-readonly': toValue(props.readonly) ?? undefined, - 'aria-disabled': toValue(props.disabled) ?? undefined, - onKeydown: handlers.onKeydown, - onClick, - })); + const inputProps = computed(() => + withRefCapture(createBindings(inputRef.value?.tagName === 'INPUT'), inputRef, elementRef), + ); function togglePressed(force?: boolean) { isPressed.value = force ?? !isPressed.value; @@ -147,7 +160,6 @@ export function useSwitch(_props: Reactivify, elementRef? inputRef, labelProps, inputProps, - switchProps, togglePressed, isTouched, };