Packages
corex
0.1.0-beta.3
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/angle-slider.mjs
import {
readNumberControlledZagProps
} from "./chunks/chunk-VKYKN6FK.mjs";
import {
createRect
} from "./chunks/chunk-QB2YSZP6.mjs";
import {
snapValueToStep
} from "./chunks/chunk-PE34YET2.mjs";
import {
createDomEventRegistry,
createHookHandleEventRegistry
} from "./chunks/chunk-77HPO22C.mjs";
import {
emitResponse,
idMatches,
notifyChange,
parseRespondTo,
readPayloadId
} from "./chunks/chunk-LIWT33BG.mjs";
import {
Component,
VanillaMachine,
canPushEvent,
createAnatomy,
createMachine,
dataAttr,
getBoolean,
getDir,
getEventKey,
getEventPoint,
getEventStep,
getNativeEvent,
getString,
isLeftClick,
raf,
setElementValue,
trackPointerMove
} from "./chunks/chunk-LTYT3NRU.mjs";
// ../node_modules/.pnpm/@zag-js+angle-slider@1.40.0/node_modules/@zag-js/angle-slider/dist/angle-slider.anatomy.mjs
var anatomy = createAnatomy("angle-slider").parts(
"root",
"label",
"thumb",
"valueText",
"control",
"track",
"markerGroup",
"marker"
);
var parts = anatomy.build();
// ../node_modules/.pnpm/@zag-js+angle-slider@1.40.0/node_modules/@zag-js/angle-slider/dist/angle-slider.dom.mjs
var getRootId = (ctx) => ctx.ids?.root ?? `angle-slider:${ctx.id}`;
var getThumbId = (ctx) => ctx.ids?.thumb ?? `angle-slider:${ctx.id}:thumb`;
var getHiddenInputId = (ctx) => ctx.ids?.hiddenInput ?? `angle-slider:${ctx.id}:input`;
var getControlId = (ctx) => ctx.ids?.control ?? `angle-slider:${ctx.id}:control`;
var getValueTextId = (ctx) => ctx.ids?.valueText ?? `angle-slider:${ctx.id}:value-text`;
var getLabelId = (ctx) => ctx.ids?.label ?? `angle-slider:${ctx.id}:label`;
var getHiddenInputEl = (ctx) => ctx.getById(getHiddenInputId(ctx));
var getControlEl = (ctx) => ctx.getById(getControlId(ctx));
var getThumbEl = (ctx) => ctx.getById(getThumbId(ctx));
// ../node_modules/.pnpm/@zag-js+rect-utils@1.40.0/node_modules/@zag-js/rect-utils/dist/angle.mjs
function getPointAngle(rect, point, reference = rect.center) {
const x = point.x - reference.x;
const y = point.y - reference.y;
const deg = Math.atan2(x, y) * (180 / Math.PI) + 180;
return 360 - deg;
}
// ../node_modules/.pnpm/@zag-js+angle-slider@1.40.0/node_modules/@zag-js/angle-slider/dist/angle-slider.utils.mjs
var MIN_VALUE = 0;
var MAX_VALUE = 359;
function mirrorAngle(angle) {
return (360 - angle) % 360;
}
function getAngle(controlEl, point, angularOffset, dir) {
const rect = createRect(controlEl.getBoundingClientRect());
let angle = getPointAngle(rect, point);
if (angularOffset != null) {
return angle - angularOffset;
}
if (dir === "rtl") {
angle = mirrorAngle(angle);
}
return angle;
}
function getPointerValue(controlEl, point, angularOffset, value, dir) {
if (angularOffset == null) {
return getAngle(controlEl, point, null, dir);
}
const angle = getAngle(controlEl, point);
const clickAngle = value + angularOffset;
return dir === "rtl" ? value + clickAngle - angle : angle - angularOffset;
}
function getDisplayAngle(value, dir) {
return dir === "rtl" ? mirrorAngle(value) : value;
}
function clampAngle(degree) {
return Math.min(Math.max(degree, MIN_VALUE), MAX_VALUE);
}
function constrainAngle(degree, step) {
const clampedDegree = clampAngle(degree);
const upperStep = Math.ceil(clampedDegree / step);
const nearestStep = Math.round(clampedDegree / step);
return upperStep >= clampedDegree / step ? upperStep * step === MAX_VALUE ? MIN_VALUE : upperStep * step : nearestStep * step;
}
function snapAngleToStep(value, step) {
return snapValueToStep(value, MIN_VALUE, MAX_VALUE, step);
}
// ../node_modules/.pnpm/@zag-js+angle-slider@1.40.0/node_modules/@zag-js/angle-slider/dist/angle-slider.connect.mjs
function connect(service, normalize) {
const { state, send, context, prop, computed, scope } = service;
const dragging = state.matches("dragging");
const value = context.get("value");
const valueAsDegree = computed("valueAsDegree");
const dir = prop("dir");
const displayAngle = getDisplayAngle(value, dir);
const disabled = prop("disabled");
const invalid = prop("invalid");
const readOnly = prop("readOnly");
const interactive = computed("interactive");
const ariaLabel = prop("aria-label");
const ariaLabelledBy = prop("aria-labelledby");
return {
value,
valueAsDegree,
dragging,
setValue(value2) {
send({ type: "VALUE.SET", value: value2 });
},
getRootProps() {
return normalize.element({
...parts.root.attrs,
id: getRootId(scope),
dir: prop("dir"),
"data-disabled": dataAttr(disabled),
"data-invalid": dataAttr(invalid),
"data-readonly": dataAttr(readOnly),
style: {
"--value": value,
"--angle": `${displayAngle}deg`
}
});
},
getLabelProps() {
return normalize.label({
...parts.label.attrs,
id: getLabelId(scope),
htmlFor: getHiddenInputId(scope),
dir: prop("dir"),
"data-disabled": dataAttr(disabled),
"data-invalid": dataAttr(invalid),
"data-readonly": dataAttr(readOnly),
onClick(event) {
if (!interactive) return;
event.preventDefault();
getThumbEl(scope)?.focus();
}
});
},
getHiddenInputProps() {
return normalize.element({
type: "hidden",
value,
name: prop("name"),
id: getHiddenInputId(scope),
dir: prop("dir")
});
},
getControlProps() {
return normalize.element({
...parts.control.attrs,
role: "presentation",
id: getControlId(scope),
dir: prop("dir"),
"data-disabled": dataAttr(disabled),
"data-invalid": dataAttr(invalid),
"data-readonly": dataAttr(readOnly),
onPointerDown(event) {
if (!interactive) return;
if (!isLeftClick(event)) return;
const point = getEventPoint(event);
const controlEl = event.currentTarget;
const thumbEl = getThumbEl(scope);
const composedPath = getNativeEvent(event).composedPath();
const isOverThumb = thumbEl && composedPath.includes(thumbEl);
let angularOffset = null;
if (isOverThumb) {
const clickAngle = getAngle(controlEl, point);
angularOffset = clickAngle - value;
}
send({ type: "CONTROL.POINTER_DOWN", point, angularOffset });
event.stopPropagation();
},
style: {
touchAction: "none",
userSelect: "none",
WebkitUserSelect: "none"
}
});
},
getThumbProps() {
return normalize.element({
...parts.thumb.attrs,
id: getThumbId(scope),
role: "slider",
dir: prop("dir"),
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledBy ?? getLabelId(scope),
"aria-valuemax": 360,
"aria-valuemin": 0,
"aria-valuenow": value,
tabIndex: readOnly || interactive ? 0 : void 0,
"data-disabled": dataAttr(disabled),
"data-invalid": dataAttr(invalid),
"data-readonly": dataAttr(readOnly),
onFocus() {
send({ type: "THUMB.FOCUS" });
},
onBlur() {
send({ type: "THUMB.BLUR" });
},
onKeyDown(event) {
if (!interactive) return;
const step = getEventStep(event) * prop("step");
const keyMap = {
ArrowLeft() {
send({ type: "THUMB.ARROW_DEC", step });
},
ArrowUp() {
send({ type: "THUMB.ARROW_DEC", step });
},
ArrowRight() {
send({ type: "THUMB.ARROW_INC", step });
},
ArrowDown() {
send({ type: "THUMB.ARROW_INC", step });
},
Home() {
send({ type: "THUMB.HOME" });
},
End() {
send({ type: "THUMB.END" });
}
};
const key = getEventKey(event, {
dir: prop("dir"),
orientation: "horizontal"
});
const exec = keyMap[key];
if (exec) {
exec(event);
event.preventDefault();
}
},
style: {
rotate: `var(--angle)`
}
});
},
getValueTextProps() {
return normalize.element({
...parts.valueText.attrs,
id: getValueTextId(scope),
dir: prop("dir")
});
},
getMarkerGroupProps() {
return normalize.element({
...parts.markerGroup.attrs,
dir: prop("dir")
});
},
getMarkerProps(props) {
let markerState;
if (props.value < value) {
markerState = "under-value";
} else if (props.value > value) {
markerState = "over-value";
} else {
markerState = "at-value";
}
const markerDisplayAngle = getDisplayAngle(props.value, dir);
return normalize.element({
...parts.marker.attrs,
dir: prop("dir"),
"data-value": props.value,
"data-state": markerState,
"data-disabled": dataAttr(disabled),
style: {
"--marker-value": props.value,
"--marker-display-value": markerDisplayAngle,
rotate: `calc(var(--marker-display-value) * 1deg)`
}
});
}
};
}
// ../node_modules/.pnpm/@zag-js+angle-slider@1.40.0/node_modules/@zag-js/angle-slider/dist/angle-slider.machine.mjs
var machine = createMachine({
props({ props }) {
return {
step: 1,
defaultValue: 0,
...props
};
},
context({ prop, bindable }) {
return {
value: bindable(() => ({
defaultValue: prop("defaultValue"),
value: prop("value"),
onChange(value) {
prop("onValueChange")?.({ value, valueAsDegree: `${value}deg` });
}
}))
};
},
refs() {
return {
thumbDragOffset: null
};
},
computed: {
interactive: ({ prop }) => !(prop("disabled") || prop("readOnly")),
valueAsDegree: ({ context }) => `${context.get("value")}deg`
},
watch({ track, context, action }) {
track([() => context.get("value")], () => {
action(["syncInputElement"]);
});
},
initialState() {
return "idle";
},
on: {
"VALUE.SET": {
actions: ["setValue"]
}
},
states: {
idle: {
on: {
"CONTROL.POINTER_DOWN": {
target: "dragging",
actions: ["setThumbDragOffset", "setPointerValue", "focusThumb"]
},
"THUMB.FOCUS": {
target: "focused"
}
}
},
focused: {
on: {
"CONTROL.POINTER_DOWN": {
target: "dragging",
actions: ["setThumbDragOffset", "setPointerValue", "focusThumb"]
},
"THUMB.ARROW_DEC": {
actions: ["decrementValue", "invokeOnChangeEnd"]
},
"THUMB.ARROW_INC": {
actions: ["incrementValue", "invokeOnChangeEnd"]
},
"THUMB.HOME": {
actions: ["setValueToMin", "invokeOnChangeEnd"]
},
"THUMB.END": {
actions: ["setValueToMax", "invokeOnChangeEnd"]
},
"THUMB.BLUR": {
target: "idle"
}
}
},
dragging: {
entry: ["focusThumb"],
effects: ["trackPointerMove"],
on: {
"DOC.POINTER_UP": {
target: "focused",
actions: ["invokeOnChangeEnd", "clearThumbDragOffset"]
},
"DOC.POINTER_MOVE": {
actions: ["setPointerValue"]
}
}
}
},
implementations: {
effects: {
trackPointerMove({ scope, send }) {
return trackPointerMove(scope.getDoc(), {
onPointerMove(info) {
send({ type: "DOC.POINTER_MOVE", point: info.point });
},
onPointerUp() {
send({ type: "DOC.POINTER_UP" });
}
});
}
},
actions: {
syncInputElement({ scope, context }) {
const inputEl = getHiddenInputEl(scope);
setElementValue(inputEl, context.get("value").toString());
},
invokeOnChangeEnd({ context, prop, computed }) {
prop("onValueChangeEnd")?.({
value: context.get("value"),
valueAsDegree: computed("valueAsDegree")
});
},
setPointerValue({ scope, event, context, prop, refs }) {
const controlEl = getControlEl(scope);
if (!controlEl) return;
const angularOffset = refs.get("thumbDragOffset");
const value = context.get("value");
const deg = getPointerValue(controlEl, event.point, angularOffset, value, prop("dir"));
context.set("value", constrainAngle(deg, prop("step")));
},
setValueToMin({ context }) {
context.set("value", MIN_VALUE);
},
setValueToMax({ context }) {
context.set("value", MAX_VALUE);
},
setValue({ context, event }) {
context.set("value", clampAngle(event.value));
},
decrementValue({ context, event, prop }) {
const value = snapAngleToStep(context.get("value") - event.step, event.step ?? prop("step"));
context.set("value", value);
},
incrementValue({ context, event, prop }) {
const value = snapAngleToStep(context.get("value") + event.step, event.step ?? prop("step"));
context.set("value", value);
},
focusThumb({ scope }) {
raf(() => {
getThumbEl(scope)?.focus({ preventScroll: true });
});
},
setThumbDragOffset({ refs, event }) {
refs.set("thumbDragOffset", event.angularOffset ?? null);
},
clearThumbDragOffset({ refs }) {
refs.set("thumbDragOffset", null);
}
}
}
});
// components/angle-slider.ts
var AngleSlider = 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="angle-slider"][data-part="root"]') ?? this.el;
this.spreadProps(rootEl, this.api.getRootProps());
const labelEl = this.el.querySelector(
'[data-scope="angle-slider"][data-part="label"]'
);
if (labelEl) this.spreadProps(labelEl, this.api.getLabelProps());
const hiddenInputEl = this.el.querySelector(
'[data-scope="angle-slider"][data-part="hidden-input"]'
);
if (hiddenInputEl) this.spreadProps(hiddenInputEl, this.api.getHiddenInputProps());
const controlEl = this.el.querySelector(
'[data-scope="angle-slider"][data-part="control"]'
);
if (controlEl) this.spreadProps(controlEl, this.api.getControlProps());
const thumbEl = this.el.querySelector(
'[data-scope="angle-slider"][data-part="thumb"]'
);
if (thumbEl) this.spreadProps(thumbEl, this.api.getThumbProps());
const valueTextEl = this.el.querySelector(
'[data-scope="angle-slider"][data-part="value-text"]'
);
if (valueTextEl) {
this.spreadProps(valueTextEl, this.api.getValueTextProps());
const valueSpan = valueTextEl.querySelector(
'[data-scope="angle-slider"][data-part="value"]'
);
const format = this.el.dataset.valueTextAs;
const nextValue = format === "raw" ? String(this.api.value) : String(this.api.valueAsDegree ?? this.api.value);
if (valueSpan && valueSpan.textContent !== nextValue) valueSpan.textContent = nextValue;
}
const markerGroupEl = this.el.querySelector(
'[data-scope="angle-slider"][data-part="marker-group"]'
);
if (markerGroupEl) this.spreadProps(markerGroupEl, this.api.getMarkerGroupProps());
this.el.querySelectorAll('[data-scope="angle-slider"][data-part="marker"]').forEach((markerEl) => {
const valueStr = markerEl.dataset.value;
if (valueStr == null) return;
const value = Number(valueStr);
if (Number.isNaN(value)) return;
this.spreadProps(markerEl, this.api.getMarkerProps({ value }));
});
}
};
// hooks/angle-slider.ts
function valueChangePayload(el, details) {
return {
id: el.id,
value: details.value,
valueAsDegree: details.valueAsDegree
};
}
function queueFormBubblingInputForPhoenix(el, getZag) {
queueMicrotask(() => {
const zag = getZag();
const input = el.querySelector(
'[data-scope="angle-slider"][data-part="hidden-input"]'
);
if (!input) return;
const v = zag.api.value;
if (String(input.value) !== String(v)) {
input.value = String(v);
}
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));
});
}
var AngleSliderHook = {
mounted() {
const el = this.el;
const pushEvent = this.pushEvent.bind(this);
const canPush = () => canPushEvent(this.liveSocket);
const zag = new AngleSlider(el, {
id: el.id,
...readNumberControlledZagProps(el),
disabled: getBoolean(el, "disabled"),
readOnly: getBoolean(el, "readOnly"),
invalid: getBoolean(el, "invalid"),
name: getString(el, "name"),
dir: getDir(el),
"aria-label": getString(el, "aria-label"),
"aria-labelledby": getString(el, "aria-labelledby"),
onValueChange: (details) => {
notifyChange({
el,
canPushServer: canPush(),
pushEvent,
payload: valueChangePayload(el, details),
serverEventName: getString(el, "onValueChange"),
clientEventName: getString(el, "onValueChangeClient")
});
},
onValueChangeEnd: (details) => {
notifyChange({
el,
canPushServer: canPush(),
pushEvent,
payload: valueChangePayload(el, details),
serverEventName: getString(el, "onValueChangeEnd"),
clientEventName: getString(el, "onValueChangeEndClient")
});
queueFormBubblingInputForPhoenix(el, () => zag);
}
});
zag.init();
this.angleSlider = zag;
const emitValue = (respondTo) => {
emitResponse({
respondTo,
canPushServer: canPush(),
pushEvent,
serverEventName: "angle_slider_value_response",
serverPayload: {
id: el.id,
value: zag.api.value,
valueAsDegree: zag.api.valueAsDegree,
dragging: zag.api.dragging
},
el,
domEventName: "angle-slider-value",
domDetail: {
id: el.id,
value: zag.api.value,
valueAsDegree: zag.api.valueAsDegree,
dragging: zag.api.dragging
}
});
};
const domRegistry = createDomEventRegistry(el);
this.domRegistry = domRegistry;
domRegistry.add("corex:angle-slider:set-value", (event) => {
zag.api.setValue(event.detail.value);
queueFormBubblingInputForPhoenix(el, () => zag);
});
domRegistry.add("corex:angle-slider:value", (event) => {
emitValue(parseRespondTo(event.detail));
});
const registry = createHookHandleEventRegistry(this);
this.handleRegistry = registry;
registry.add("angle_slider_set_value", (payload) => {
if (!idMatches(el.id, readPayloadId(payload))) return;
zag.api.setValue(payload.value);
queueFormBubblingInputForPhoenix(el, () => zag);
});
registry.add("angle_slider_value", (payload) => {
if (!idMatches(el.id, readPayloadId(payload))) return;
emitValue(parseRespondTo(payload));
});
},
updated() {
this.angleSlider?.updateProps({
id: this.el.id,
...readNumberControlledZagProps(this.el),
disabled: getBoolean(this.el, "disabled"),
readOnly: getBoolean(this.el, "readOnly"),
invalid: getBoolean(this.el, "invalid"),
name: getString(this.el, "name"),
dir: getDir(this.el)
});
},
destroyed() {
this.domRegistry?.teardown();
this.handleRegistry?.teardown();
this.angleSlider?.destroy();
}
};
export {
AngleSliderHook as AngleSlider
};