Skip to content

Commit

Permalink
feat: use dynamic prop bindings and (#49)
Browse files Browse the repository at this point in the history
* feat: use dynamic prop bindings

* fix: slider not normalizing value to array if multiple thumbs exist
  • Loading branch information
logaretm authored Aug 17, 2024
1 parent a722f97 commit 50664f5
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 95 deletions.
7 changes: 7 additions & 0 deletions packages/core/src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,6 +44,7 @@ export type PressEvents = {
};

export type InputBaseAttributes = {
name?: string;
readonly?: boolean;
disabled?: boolean;
};
Expand Down
47 changes: 21 additions & 26 deletions packages/core/src/useCheckbox/useCheckbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface CheckboxProps<TValue = string> {

export interface CheckboxDomInputProps extends AriaLabelableProps, InputBaseAttributes {
type: 'checkbox';
name?: string;
indeterminate?: boolean;
}

export interface CheckboxDomProps extends AriaLabelableProps {
Expand Down Expand Up @@ -121,8 +123,8 @@ export function useCheckbox<TValue = string>(
inputRef.value?.focus();
}

function createBindings(isInput: boolean) {
return {
function createBindings(isInput: boolean): CheckboxDomProps | CheckboxDomInputProps {
const base = {
...labelledByProps.value,
...createHandlers(isInput),
id: inputId,
Expand All @@ -131,6 +133,21 @@ export function useCheckbox<TValue = string>(
[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({
Expand All @@ -147,29 +164,8 @@ export function useCheckbox<TValue = string>(
},
});

const inputProps = computed<CheckboxDomInputProps>(() =>
withRefCapture(
{
type: 'checkbox',
name: group?.name || props.name,
indeterminate: toValue(props.indeterminate) || false,
...createBindings(true),
},
inputRef,
elementRef,
),
);

const checkboxProps = computed<CheckboxDomProps>(() =>
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) {
Expand All @@ -196,7 +192,6 @@ export function useCheckbox<TValue = string>(
inputRef,
labelProps,
inputProps,
checkboxProps,
isChecked: checked,
isTouched,
setChecked,
Expand Down
50 changes: 20 additions & 30 deletions packages/core/src/useRadio/useRadio.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<TValue = string>(
Expand Down Expand Up @@ -86,17 +83,30 @@ export function useRadio<TValue = string>(
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({
Expand All @@ -113,34 +123,14 @@ export function useRadio<TValue = string>(
},
});

const inputProps = computed<RadioDomInputProps>(() =>
withRefCapture(
{
type: 'radio',
...createBindings(true),
},
inputRef,
elementRef,
),
);

const radioProps = computed<RadioDomProps>(() =>
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,
};
}
6 changes: 3 additions & 3 deletions packages/core/src/useSlider/slider.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -142,12 +142,12 @@ export function useSlider(_props: Reactivify<SliderProps, 'schema'>) {
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);
}
Expand Down
84 changes: 48 additions & 36 deletions packages/core/src/useSwitch/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Ref, computed, shallowRef, toValue } from 'vue';
import {
AriaDescribableProps,
AriaInputProps,
AriaLabelableProps,
InputBaseAttributes,
InputEvents,
Expand All @@ -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 = {
Expand All @@ -35,10 +48,10 @@ export type SwitchProps = {

export function useSwitch(_props: Reactivify<SwitchProps, 'schema'>, elementRef?: Ref<HTMLInputElement>) {
const props = normalizeProps(_props, ['schema']);
const id = useUniqId(FieldTypePrefixes.Switch);
const inputId = useUniqId(FieldTypePrefixes.Switch);
const inputRef = elementRef || shallowRef<HTMLInputElement>();
const { labelProps, labelledByProps } = useLabel({
for: id,
for: inputId,
label: props.label,
targetRef: inputRef,
});
Expand Down Expand Up @@ -103,39 +116,39 @@ export function useSwitch(_props: Reactivify<SwitchProps, 'schema'>, elementRef?
},
});

/**
* Use this if you are using a native input[type=checkbox] element.
*/
const inputProps = computed<SwitchDOMProps>(() =>
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;
Expand All @@ -147,7 +160,6 @@ export function useSwitch(_props: Reactivify<SwitchProps, 'schema'>, elementRef?
inputRef,
labelProps,
inputProps,
switchProps,
togglePressed,
isTouched,
};
Expand Down

0 comments on commit 50664f5

Please sign in to comment.