@kobalte/core
Version:
Unstyled components and primitives for building accessible web apps and design systems with SolidJS.
130 lines (127 loc) • 5.18 kB
JavaScript
import { createRegisterId } from './E4R2EMM4.js';
import { createTagName } from './ET5T45DO.js';
import { Polymorphic } from './6Y7B2NEO.js';
import { mergeDefaultProps, access, createGenerateId, mergeRefs } from '@kobalte/utils';
import { createContext, createUniqueId, createSignal, createMemo, useContext, createEffect, onCleanup, splitProps, Show } from 'solid-js';
import { createComponent, mergeProps, memo } from 'solid-js/web';
var FORM_CONTROL_PROP_NAMES = ["id", "name", "validationState", "required", "disabled", "readOnly"];
function createFormControl(props) {
const defaultId = `form-control-${createUniqueId()}`;
const mergedProps = mergeDefaultProps({
id: defaultId
}, props);
const [labelId, setLabelId] = createSignal();
const [fieldId, setFieldId] = createSignal();
const [descriptionId, setDescriptionId] = createSignal();
const [errorMessageId, setErrorMessageId] = createSignal();
const getAriaLabelledBy = (fieldId2, fieldAriaLabel, fieldAriaLabelledBy) => {
const hasAriaLabelledBy = fieldAriaLabelledBy != null || labelId() != null;
return [
fieldAriaLabelledBy,
labelId(),
// If there is both an aria-label and aria-labelledby, add the field itself has an aria-labelledby
hasAriaLabelledBy && fieldAriaLabel != null ? fieldId2 : void 0
].filter(Boolean).join(" ") || void 0;
};
const getAriaDescribedBy = (fieldAriaDescribedBy) => {
return [
descriptionId(),
// Use aria-describedby for error message because aria-errormessage is unsupported using VoiceOver or NVDA.
// See https://github.com/adobe/react-spectrum/issues/1346#issuecomment-740136268
errorMessageId(),
fieldAriaDescribedBy
].filter(Boolean).join(" ") || void 0;
};
const dataset = createMemo(() => ({
"data-valid": access(mergedProps.validationState) === "valid" ? "" : void 0,
"data-invalid": access(mergedProps.validationState) === "invalid" ? "" : void 0,
"data-required": access(mergedProps.required) ? "" : void 0,
"data-disabled": access(mergedProps.disabled) ? "" : void 0,
"data-readonly": access(mergedProps.readOnly) ? "" : void 0
}));
const formControlContext = {
name: () => access(mergedProps.name) ?? access(mergedProps.id),
dataset,
validationState: () => access(mergedProps.validationState),
isRequired: () => access(mergedProps.required),
isDisabled: () => access(mergedProps.disabled),
isReadOnly: () => access(mergedProps.readOnly),
labelId,
fieldId,
descriptionId,
errorMessageId,
getAriaLabelledBy,
getAriaDescribedBy,
generateId: createGenerateId(() => access(mergedProps.id)),
registerLabel: createRegisterId(setLabelId),
registerField: createRegisterId(setFieldId),
registerDescription: createRegisterId(setDescriptionId),
registerErrorMessage: createRegisterId(setErrorMessageId)
};
return {
formControlContext
};
}
var FormControlContext = createContext();
function useFormControlContext() {
const context = useContext(FormControlContext);
if (context === void 0) {
throw new Error("[kobalte]: `useFormControlContext` must be used within a `FormControlContext.Provider` component");
}
return context;
}
function FormControlDescription(props) {
const context = useFormControlContext();
const mergedProps = mergeDefaultProps({
id: context.generateId("description")
}, props);
createEffect(() => onCleanup(context.registerDescription(mergedProps.id)));
return createComponent(Polymorphic, mergeProps({
as: "div"
}, () => context.dataset(), mergedProps));
}
function FormControlErrorMessage(props) {
const context = useFormControlContext();
const mergedProps = mergeDefaultProps({
id: context.generateId("error-message")
}, props);
const [local, others] = splitProps(mergedProps, ["forceMount"]);
const isInvalid = () => context.validationState() === "invalid";
createEffect(() => {
if (!isInvalid()) {
return;
}
onCleanup(context.registerErrorMessage(others.id));
});
return createComponent(Show, {
get when() {
return local.forceMount || isInvalid();
},
get children() {
return createComponent(Polymorphic, mergeProps({
as: "div"
}, () => context.dataset(), others));
}
});
}
function FormControlLabel(props) {
let ref;
const context = useFormControlContext();
const mergedProps = mergeDefaultProps({
id: context.generateId("label")
}, props);
const [local, others] = splitProps(mergedProps, ["ref"]);
const tagName = createTagName(() => ref, () => "label");
createEffect(() => onCleanup(context.registerLabel(others.id)));
return createComponent(Polymorphic, mergeProps({
as: "label",
ref(r$) {
const _ref$ = mergeRefs((el) => ref = el, local.ref);
typeof _ref$ === "function" && _ref$(r$);
},
get ["for"]() {
return memo(() => tagName() === "label")() ? context.fieldId() : void 0;
}
}, () => context.dataset(), others));
}
export { FORM_CONTROL_PROP_NAMES, FormControlContext, FormControlDescription, FormControlErrorMessage, FormControlLabel, createFormControl, useFormControlContext };