ui-ingredients
Version:
Headless component library for Svelte powered by zag
160 lines (159 loc) • 5.26 kB
JavaScript
import { ariaAttr, dataAttr, getDocument, getWindow } from '@zag-js/dom-query';
import { reflect } from '@zag-js/svelte';
import { createUniqueId } from '../create-unique-id.js';
import { getEnvironmentContext } from '../environment-provider/enviroment-provider-context.svelte.js';
import { parts } from './field-anatomy.js';
export function createField(props) {
const environment = getEnvironmentContext();
const id_ = createUniqueId();
const {
/**/
ids, invalid, disabled, required, readOnly, } = $derived.by(() => {
const id = props.id ?? id_;
const ids = {
root: props.ids?.root ?? `field:${id}`,
label: props.ids?.label ?? `field:${id}:label`,
control: props.ids?.control ?? `field:${id}:control`,
errorText: props.ids?.errorText ?? `field:${id}:error-text`,
helperText: props.ids?.helperText ?? `field:${id}:helper-text`,
};
return {
ids,
invalid: props.invalid ?? false,
required: props.required ?? false,
disabled: props.disabled ?? false,
readOnly: props.readOnly ?? false,
};
});
let hasErrorText = $state(false);
let hasHelperText = $state(false);
const ariaDescribedby = $derived.by(() => {
const l = [];
if (hasErrorText) {
l.push(ids.errorText);
}
if (hasHelperText) {
l.push(ids.helperText);
}
return l.join(' ');
});
function getRootProps() {
return {
...parts.root.attrs,
id: ids.root,
role: 'group',
'data-invalid': dataAttr(invalid),
'data-disabled': dataAttr(disabled),
'data-required': dataAttr(required),
'data-readonly': dataAttr(readOnly),
};
}
function getLabelProps() {
return {
...parts.label.attrs,
id: ids.label,
for: ids.control,
'data-invalid': dataAttr(invalid),
'data-disabled': dataAttr(disabled),
'data-required': dataAttr(required),
'data-readonly': dataAttr(readOnly),
};
}
function getRequiredIndicatorProps() {
return {
...parts.requiredIndicator.attrs,
hidden: !required,
'aria-hidden': true,
'data-invalid': dataAttr(invalid),
'data-disabled': dataAttr(disabled),
'data-readonly': dataAttr(readOnly),
};
}
function getErrorTextProps() {
return {
...parts.errorText.attrs,
id: ids.errorText,
hidden: !hasErrorText,
'aria-live': 'polite',
'data-invalid': dataAttr(invalid),
'data-disabled': dataAttr(disabled),
'data-required': dataAttr(required),
'data-readonly': dataAttr(readOnly),
};
}
function getHelperTextProps() {
return {
...parts.helperText.attrs,
id: ids.helperText,
'data-invalid': dataAttr(invalid),
'data-disabled': dataAttr(disabled),
'data-required': dataAttr(required),
'data-readonly': dataAttr(readOnly),
};
}
function getControlProps() {
return {
id: ids.control,
disabled,
required,
'aria-describedby': ariaDescribedby,
'aria-invalid': ariaAttr(invalid),
'aria-disabled': ariaAttr(disabled),
'aria-required': ariaAttr(required),
'aria-readonly': ariaAttr(readOnly),
'data-invalid': dataAttr(invalid),
'data-disabled': dataAttr(disabled),
'data-required': dataAttr(required),
'data-readonly': dataAttr(readOnly),
};
}
function getInputProps() {
return {
readonly: readOnly,
...getControlProps(),
...parts.input.attrs,
};
}
function getTextareaProps() {
return {
readonly: readOnly,
...getControlProps(),
...parts.textarea.attrs,
};
}
function getSelectProps() {
return {
...getControlProps(),
...parts.select.attrs,
};
}
$effect(() => {
const rootNode = environment?.getRootNode() ?? document;
const doc = getDocument(rootNode);
const win = getWindow(rootNode);
function handler() {
hasErrorText = invalid && doc.getElementById(ids.errorText) !== null;
hasHelperText = doc.getElementById(ids.helperText) !== null;
}
handler();
const observer = new win.MutationObserver(handler);
observer.observe(rootNode, { childList: true, subtree: true });
return () => observer.disconnect();
});
return reflect(() => ({
ids,
disabled,
required,
readOnly,
invalid,
'aria-describedby': ariaDescribedby,
getRootProps,
getLabelProps,
getErrorTextProps,
getHelperTextProps,
getInputProps,
getSelectProps,
getTextareaProps,
getRequiredIndicatorProps,
}));
}