Packages
corex
0.1.0-beta.4
0.1.2
0.1.1
0.1.0
0.1.0-rc.1
0.1.0-rc.0
0.1.0-beta.5
0.1.0-beta.4
0.1.0-beta.3
0.1.0-beta.2
0.1.0-beta.1
0.1.0-alpha.33
0.1.0-alpha.32
0.1.0-alpha.31
0.1.0-alpha.30
0.1.0-alpha.29
0.1.0-alpha.28
0.1.0-alpha.27
0.1.0-alpha.26
0.1.0-alpha.25
0.1.0-alpha.24
0.1.0-alpha.23
0.1.0-alpha.22
0.1.0-alpha.21
0.1.0-alpha.20
0.1.0-alpha.19
0.1.0-alpha.18
0.1.0-alpha.17
0.1.0-alpha.16
0.1.0-alpha.15
0.1.0-alpha.14
0.1.0-alpha.13
0.1.0-alpha.12
0.1.0-alpha.11
0.1.0-alpha.10
0.1.0-alpha.9
0.1.0-alpha.8
0.1.0-alpha.7
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
Accessible and unstyled UI components library written in Elixir and TypeScript that integrates Zag.js state machines into the Phoenix Framework.
Current section
Files
Jump to
Current section
Files
priv/static/radio-group.mjs
import {
isFocusVisible,
trackFocusVisible
} from "./chunks/chunk-MG52DTQN.mjs";
import {
readStringControlledZagProps,
readStringControlledZagUpdate
} from "./chunks/chunk-CDKBKUQ4.mjs";
import {
toPx
} from "./chunks/chunk-PE34YET2.mjs";
import {
notifyChange
} from "./chunks/chunk-LIWT33BG.mjs";
import {
Component,
VanillaMachine,
canPushEvent,
createAnatomy,
createGuards,
createMachine,
dataAttr,
dispatchInputCheckedEvent,
getBoolean,
getDir,
getEventTarget,
getString,
isLeftClick,
isSafari,
queryAll,
resizeObserverBorderBox,
trackFormControl,
visuallyHiddenStyle
} from "./chunks/chunk-LTYT3NRU.mjs";
// ../node_modules/.pnpm/@zag-js+radio-group@1.40.0/node_modules/@zag-js/radio-group/dist/radio-group.anatomy.mjs
var anatomy = createAnatomy("radio-group").parts(
"root",
"label",
"item",
"itemText",
"itemControl",
"indicator"
);
var parts = anatomy.build();
// ../node_modules/.pnpm/@zag-js+radio-group@1.40.0/node_modules/@zag-js/radio-group/dist/radio-group.dom.mjs
var getRootId = (ctx) => ctx.ids?.root ?? `radio-group:${ctx.id}`;
var getLabelId = (ctx) => ctx.ids?.label ?? `radio-group:${ctx.id}:label`;
var getItemId = (ctx, value) => ctx.ids?.item?.(value) ?? `radio-group:${ctx.id}:radio:${value}`;
var getItemHiddenInputId = (ctx, value) => ctx.ids?.itemHiddenInput?.(value) ?? `radio-group:${ctx.id}:radio:input:${value}`;
var getItemControlId = (ctx, value) => ctx.ids?.itemControl?.(value) ?? `radio-group:${ctx.id}:radio:control:${value}`;
var getItemLabelId = (ctx, value) => ctx.ids?.itemLabel?.(value) ?? `radio-group:${ctx.id}:radio:label:${value}`;
var getIndicatorId = (ctx) => ctx.ids?.indicator ?? `radio-group:${ctx.id}:indicator`;
var getRootEl = (ctx) => ctx.getById(getRootId(ctx));
var getItemHiddenInputEl = (ctx, value) => ctx.getById(getItemHiddenInputId(ctx, value));
var getIndicatorEl = (ctx) => ctx.getById(getIndicatorId(ctx));
var getFirstEnabledInputEl = (ctx) => getRootEl(ctx)?.querySelector("input:not(:disabled)");
var getFirstEnabledAndCheckedInputEl = (ctx) => getRootEl(ctx)?.querySelector("input:not(:disabled):checked");
var getInputEls = (ctx) => {
const ownerId = CSS.escape(getRootId(ctx));
const selector = `input[type=radio][data-ownedby='${ownerId}']:not([disabled])`;
return queryAll(getRootEl(ctx), selector);
};
var getRadioEl = (ctx, value) => {
if (!value) return;
return ctx.getById(getItemId(ctx, value));
};
var getOffsetRect = (el) => ({
x: el?.offsetLeft ?? 0,
y: el?.offsetTop ?? 0,
width: el?.offsetWidth ?? 0,
height: el?.offsetHeight ?? 0
});
// ../node_modules/.pnpm/@zag-js+radio-group@1.40.0/node_modules/@zag-js/radio-group/dist/radio-group.connect.mjs
function connect(service, normalize) {
const { context, send, computed, prop, scope } = service;
const groupDisabled = computed("isDisabled");
const groupInvalid = prop("invalid");
const readOnly = prop("readOnly");
function getItemState(props) {
return {
value: props.value,
invalid: !!props.invalid || !!groupInvalid,
disabled: !!props.disabled || groupDisabled,
checked: context.get("value") === props.value,
focused: context.get("focusedValue") === props.value,
focusVisible: context.get("focusVisibleValue") === props.value,
hovered: context.get("hoveredValue") === props.value,
active: context.get("activeValue") === props.value
};
}
function getItemDataAttrs(props) {
const itemState = getItemState(props);
return {
"data-focus": dataAttr(itemState.focused),
"data-focus-visible": dataAttr(itemState.focusVisible),
"data-disabled": dataAttr(itemState.disabled),
"data-readonly": dataAttr(readOnly),
"data-state": itemState.checked ? "checked" : "unchecked",
"data-hover": dataAttr(itemState.hovered),
"data-invalid": dataAttr(itemState.invalid),
"data-orientation": prop("orientation"),
"data-ssr": dataAttr(context.get("ssr"))
};
}
const focus = () => {
const nodeToFocus = getFirstEnabledAndCheckedInputEl(scope) ?? getFirstEnabledInputEl(scope);
nodeToFocus?.focus();
};
return {
focus,
value: context.get("value"),
setValue(value) {
send({ type: "SET_VALUE", value, isTrusted: false });
},
clearValue() {
send({ type: "SET_VALUE", value: null, isTrusted: false });
},
getRootProps() {
return normalize.element({
...parts.root.attrs,
role: "radiogroup",
id: getRootId(scope),
"aria-labelledby": getLabelId(scope),
"aria-required": prop("required") || void 0,
"aria-disabled": groupDisabled || void 0,
"aria-readonly": readOnly || void 0,
"data-orientation": prop("orientation"),
"data-disabled": dataAttr(groupDisabled),
"data-invalid": dataAttr(groupInvalid),
"data-required": dataAttr(prop("required")),
"aria-orientation": prop("orientation"),
dir: prop("dir"),
style: {
position: "relative"
}
});
},
getLabelProps() {
return normalize.element({
...parts.label.attrs,
dir: prop("dir"),
"data-orientation": prop("orientation"),
"data-disabled": dataAttr(groupDisabled),
"data-invalid": dataAttr(groupInvalid),
"data-required": dataAttr(prop("required")),
id: getLabelId(scope),
onClick: focus
});
},
getItemState,
getItemProps(props) {
const itemState = getItemState(props);
return normalize.label({
...parts.item.attrs,
dir: prop("dir"),
id: getItemId(scope, props.value),
htmlFor: getItemHiddenInputId(scope, props.value),
...getItemDataAttrs(props),
onPointerMove() {
if (itemState.disabled) return;
if (itemState.hovered) return;
send({ type: "SET_HOVERED", value: props.value, hovered: true });
},
onPointerLeave() {
if (itemState.disabled) return;
send({ type: "SET_HOVERED", value: null });
},
onPointerDown(event) {
if (itemState.disabled) return;
if (!isLeftClick(event)) return;
if (itemState.focused && event.pointerType === "mouse") {
event.preventDefault();
}
send({ type: "SET_ACTIVE", value: props.value, active: true });
},
onPointerUp() {
if (itemState.disabled) return;
send({ type: "SET_ACTIVE", value: null });
},
onClick() {
if (!itemState.disabled && isSafari()) {
getItemHiddenInputEl(scope, props.value)?.focus();
}
}
});
},
getItemTextProps(props) {
return normalize.element({
...parts.itemText.attrs,
dir: prop("dir"),
id: getItemLabelId(scope, props.value),
...getItemDataAttrs(props)
});
},
getItemControlProps(props) {
const itemState = getItemState(props);
return normalize.element({
...parts.itemControl.attrs,
dir: prop("dir"),
id: getItemControlId(scope, props.value),
"data-active": dataAttr(itemState.active),
"aria-hidden": true,
...getItemDataAttrs(props)
});
},
getItemHiddenInputProps(props) {
const itemState = getItemState(props);
return normalize.input({
"data-ownedby": getRootId(scope),
id: getItemHiddenInputId(scope, props.value),
type: "radio",
name: prop("name") || prop("id"),
form: prop("form"),
value: props.value,
required: prop("required"),
"aria-labelledby": getItemLabelId(scope, props.value),
"aria-invalid": itemState.invalid || void 0,
onClick(event) {
if (readOnly) {
event.preventDefault();
return;
}
if (event.currentTarget.checked) {
send({ type: "SET_VALUE", value: props.value, isTrusted: true });
}
},
onBlur() {
send({ type: "SET_FOCUSED", value: null, focused: false, focusVisible: false });
},
onFocus() {
const focusVisible = isFocusVisible();
send({ type: "SET_FOCUSED", value: props.value, focused: true, focusVisible });
},
onKeyDown(event) {
if (event.defaultPrevented) return;
if (event.key === " ") {
send({ type: "SET_ACTIVE", value: props.value, active: true });
}
},
onKeyUp(event) {
if (event.defaultPrevented) return;
if (event.key === " ") {
send({ type: "SET_ACTIVE", value: null });
}
},
disabled: itemState.disabled || readOnly,
defaultChecked: itemState.checked,
style: visuallyHiddenStyle
});
},
getIndicatorProps() {
const rect = context.get("indicatorRect");
const animateIndicator = context.get("animateIndicator");
return normalize.element({
id: getIndicatorId(scope),
...parts.indicator.attrs,
dir: prop("dir"),
hidden: context.get("value") == null || isRectEmpty(rect),
"data-disabled": dataAttr(groupDisabled),
"data-orientation": prop("orientation"),
onTransitionEnd(event) {
if (getEventTarget(event) !== event.currentTarget) return;
send({ type: "INDICATOR_TRANSITION_END" });
},
style: {
"--transition-property": "left, top, width, height",
"--left": toPx(rect?.x),
"--top": toPx(rect?.y),
"--width": toPx(rect?.width),
"--height": toPx(rect?.height),
position: "absolute",
willChange: animateIndicator ? "var(--transition-property)" : "auto",
transitionProperty: animateIndicator ? "var(--transition-property)" : "none",
transitionDuration: animateIndicator ? "var(--transition-duration, 150ms)" : "0ms",
transitionTimingFunction: "var(--transition-timing-function)",
[prop("orientation") === "horizontal" ? "left" : "top"]: prop("orientation") === "horizontal" ? "var(--left)" : "var(--top)"
}
});
}
};
}
var isRectEmpty = (rect) => rect == null || rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0;
// ../node_modules/.pnpm/@zag-js+radio-group@1.40.0/node_modules/@zag-js/radio-group/dist/radio-group.machine.mjs
var { not } = createGuards();
var machine = createMachine({
props({ props }) {
return {
orientation: "vertical",
...props
};
},
initialState() {
return "idle";
},
context({ prop, bindable }) {
return {
value: bindable(() => ({
defaultValue: prop("defaultValue"),
value: prop("value"),
onChange(value) {
prop("onValueChange")?.({ value });
}
})),
activeValue: bindable(() => ({
defaultValue: null
})),
focusedValue: bindable(() => ({
defaultValue: null
})),
focusVisibleValue: bindable(() => ({
defaultValue: null
})),
hoveredValue: bindable(() => ({
defaultValue: null
})),
indicatorRect: bindable(() => ({
defaultValue: null
})),
animateIndicator: bindable(() => ({
defaultValue: false
})),
fieldsetDisabled: bindable(() => ({
defaultValue: false
})),
ssr: bindable(() => ({
defaultValue: true
}))
};
},
refs() {
return {
indicatorCleanup: null,
focusVisibleValue: null,
prevValue: null
};
},
computed: {
isDisabled: ({ prop, context }) => !!prop("disabled") || context.get("fieldsetDisabled")
},
entry: ["syncPrevValue", "syncIndicatorRect", "syncSsr"],
exit: ["cleanupObserver"],
effects: ["trackFormControlState", "trackFocusVisible"],
watch({ track, action, context }) {
track([() => context.get("value")], () => {
action(["syncIndicatorAnimation", "syncIndicatorRect", "syncInputElements"]);
});
},
on: {
SET_VALUE: [
{
guard: not("isTrusted"),
actions: ["setValue", "dispatchChangeEvent"]
},
{
actions: ["setValue"]
}
],
SET_HOVERED: {
actions: ["setHovered"]
},
SET_ACTIVE: {
actions: ["setActive"]
},
SET_FOCUSED: {
actions: ["setFocused"]
},
INDICATOR_TRANSITION_END: {
actions: ["clearIndicatorAnimation"]
}
},
states: {
idle: {}
},
implementations: {
guards: {
isTrusted: ({ event }) => !!event.isTrusted
},
effects: {
trackFormControlState({ context, scope }) {
return trackFormControl(getRootEl(scope), {
onFieldsetDisabledChange(disabled) {
context.set("fieldsetDisabled", disabled);
},
onFormReset() {
context.set("value", context.initial("value"));
}
});
},
trackFocusVisible({ scope }) {
return trackFocusVisible({ root: scope.getRootNode?.() });
}
},
actions: {
setValue({ context, event }) {
context.set("value", event.value);
},
setHovered({ context, event }) {
context.set("hoveredValue", event.value);
},
setActive({ context, event }) {
context.set("activeValue", event.value);
},
setFocused({ context, event }) {
context.set("focusedValue", event.value);
const focusVisibleValue = event.value != null && event.focusVisible ? event.value : null;
context.set("focusVisibleValue", focusVisibleValue);
},
syncPrevValue({ context, refs }) {
refs.set("prevValue", context.get("value"));
},
syncIndicatorAnimation({ context, refs }) {
const prevValue = refs.get("prevValue");
const nextValue = context.get("value");
const animate = prevValue != null && nextValue != null && prevValue !== nextValue;
context.set("animateIndicator", animate);
refs.set("prevValue", nextValue);
},
clearIndicatorAnimation({ context }) {
context.set("animateIndicator", false);
},
syncInputElements({ context, scope }) {
const inputs = getInputEls(scope);
inputs.forEach((input) => {
input.checked = input.value === context.get("value");
});
},
cleanupObserver({ refs }) {
refs.get("indicatorCleanup")?.();
},
syncSsr({ context }) {
context.set("ssr", false);
},
syncIndicatorRect({ context, scope, refs }) {
refs.get("indicatorCleanup")?.();
if (!getIndicatorEl(scope)) return;
const value = context.get("value");
const radioEl = getRadioEl(scope, value);
if (value == null || !radioEl) {
context.set("indicatorRect", null);
return;
}
const exec = () => {
context.set("indicatorRect", getOffsetRect(radioEl));
};
exec();
const indicatorCleanup = resizeObserverBorderBox.observe(radioEl, exec);
refs.set("indicatorCleanup", indicatorCleanup);
},
dispatchChangeEvent({ context, scope }) {
const inputEls = getInputEls(scope);
inputEls.forEach((inputEl) => {
const checked = inputEl.value === context.get("value");
if (checked === inputEl.checked) return;
dispatchInputCheckedEvent(inputEl, { checked });
});
}
}
}
});
// components/radio-group.ts
var RadioGroup = class extends Component {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initMachine(props) {
return new VanillaMachine(machine, props);
}
initApi() {
return this.zagConnect(connect);
}
render() {
const rootEl = this.el.querySelector('[data-scope="radio-group"][data-part="root"]') ?? this.el;
this.spreadProps(rootEl, this.api.getRootProps());
const labelEl = this.el.querySelector(
'[data-scope="radio-group"][data-part="label"]'
);
if (labelEl) this.spreadProps(labelEl, this.api.getLabelProps());
const indicatorEl = this.el.querySelector(
'[data-scope="radio-group"][data-part="indicator"]'
);
if (indicatorEl) this.spreadProps(indicatorEl, this.api.getIndicatorProps());
this.el.querySelectorAll('[data-scope="radio-group"][data-part="item"]').forEach((itemEl) => {
const value = itemEl.dataset.value;
if (value == null) return;
const disabled = itemEl.dataset.disabled === "true";
const invalid = itemEl.dataset.invalid === "true";
this.spreadProps(itemEl, this.api.getItemProps({ value, disabled, invalid }));
const textEl = itemEl.querySelector(
'[data-scope="radio-group"][data-part="item-text"]'
);
if (textEl)
this.spreadProps(
textEl,
this.api.getItemTextProps({ value, disabled, invalid })
);
const controlEl = itemEl.querySelector(
'[data-scope="radio-group"][data-part="item-control"]'
);
if (controlEl)
this.spreadProps(
controlEl,
this.api.getItemControlProps({
value,
disabled,
invalid
})
);
const hiddenInputEl = itemEl.querySelector(
'[data-scope="radio-group"][data-part="item-hidden-input"]'
);
if (hiddenInputEl)
this.spreadProps(
hiddenInputEl,
this.api.getItemHiddenInputProps({
value,
disabled,
invalid
})
);
});
}
};
// hooks/radio-group.ts
function valueChangePayload(el, details) {
return {
id: el.id,
value: details.value
};
}
var RadioGroupHook = {
mounted() {
const el = this.el;
const pushEvent = this.pushEvent.bind(this);
const canPush = () => canPushEvent(this.liveSocket);
const zag = new RadioGroup(el, {
id: el.id,
...readStringControlledZagProps(el, "value", "defaultValue"),
name: getString(el, "name"),
form: getString(el, "form"),
disabled: getBoolean(el, "disabled"),
invalid: getBoolean(el, "invalid"),
required: getBoolean(el, "required"),
readOnly: getBoolean(el, "readOnly"),
dir: getDir(el),
orientation: getString(el, "orientation"),
onValueChange: (details) => {
const checked = el.querySelector(
'[data-scope="radio-group"][data-part="item-hidden-input"]:checked'
);
if (checked) {
checked.dispatchEvent(new Event("input", { bubbles: true }));
checked.dispatchEvent(new Event("change", { bubbles: true }));
}
notifyChange({
el,
canPushServer: canPush(),
pushEvent,
payload: valueChangePayload(el, details),
serverEventName: getString(el, "onValueChange"),
clientEventName: getString(el, "onValueChangeClient")
});
}
});
zag.init();
this.radioGroup = zag;
},
updated() {
this.radioGroup?.updateProps({
id: this.el.id,
...readStringControlledZagUpdate(this.el, "value", "defaultValue"),
name: getString(this.el, "name"),
form: getString(this.el, "form"),
disabled: getBoolean(this.el, "disabled"),
invalid: getBoolean(this.el, "invalid"),
required: getBoolean(this.el, "required"),
readOnly: getBoolean(this.el, "readOnly"),
orientation: getString(this.el, "orientation"),
dir: getDir(this.el)
});
},
destroyed() {
this.radioGroup?.destroy();
}
};
export {
RadioGroupHook as RadioGroup
};