Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use dynamic prop bindings and #49

Merged
merged 2 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
'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 InputBaseAttributes = {
name?: string;
readonly?: boolean;
disabled?: boolean;
};
Expand Down Expand Up @@ -81,7 +88,7 @@

export type Arrayable<T> = T | T[];

export type FormObject = Record<string, any>;

Check warning on line 91 in packages/core/src/types/common.ts

View workflow job for this annotation

GitHub Actions / ts-lint-test

Unexpected any. Specify a different type

Check warning on line 91 in packages/core/src/types/common.ts

View workflow job for this annotation

GitHub Actions / ts-lint-test

Unexpected any. Specify a different type

export type MaybeAsync<T> = T | Promise<T>;

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
Loading