Build CultUI slider anatomy in web reference

This commit is contained in:
Meta Vens 2026-06-05 18:22:59 +01:00
commit 0fb9660e8f
7 changed files with 714 additions and 126 deletions

View file

@ -2,8 +2,8 @@ export function compileEveDsl(source) {
const mesh = createCultMeshStore();
const root = {
id: "root",
kind: "grid",
props: { columns: "repeat(auto-fit, minmax(260px, 1fr))" },
kind: "surface",
props: {},
children: [],
};
const state = {
@ -23,127 +23,59 @@ export function compileEveDsl(source) {
colorMuted: "#8ba7b8",
colorLink: "#8efcff",
},
controlSkins: {},
},
},
commands: [],
mesh,
};
let currentComponent;
for (const rawLine of source.split(/\r?\n/)) {
const line = stripComment(rawLine).trim();
if (!line) continue;
const tokens = tokenize(line);
const [head, ...rest] = tokens;
const stack = [{ indent: -1, node: root }];
let activeSkin;
if (head === "surface") {
state.providerId = rest[0] || state.providerId;
state.title = rest[1] || state.title;
for (const entry of readDslLines(source)) {
while (entry.indent <= stack[stack.length - 1].indent) stack.pop();
const parent = stack[stack.length - 1].node;
const [head, ...rest] = entry.tokens;
if (applyStateCommand(state, mesh, head, rest)) {
activeSkin = undefined;
continue;
}
if (head === "version") {
state.version = Number(rest[0]) || state.version;
continue;
}
if (head === "var") {
mesh.var(rest[0]).set(parseDslValue(rest.slice(1).join(" ")));
continue;
}
if (head === "collection") {
mesh.collection(rest[0]);
continue;
}
if (head === "item") {
mesh.collection(rest[0]).append(parseDslValue(rest.slice(1).join(" ")));
continue;
}
if (head === "derive") {
const [target, operator, sourcePath] = rest;
mesh.derive(target, operator, sourcePath);
continue;
}
if (head === "card") {
currentComponent = {
id: rest[0] || `card-${root.children.length + 1}`,
kind: "card",
props: { title: rest[1] || rest[0] || "Card" },
if (head === "skin") {
activeSkin = {
target: readKeyword(rest, "for") || rest[2] || "control",
children: [],
};
root.children.push(currentComponent);
state.surface.styles.controlSkins[rest[0]] = activeSkin;
stack.push({ indent: entry.indent, node: activeSkin });
continue;
}
if (head === "end") {
currentComponent = undefined;
if (isPropertyLine(head, parent)) {
applyProperty(parent.props || (parent.props = {}), head, rest);
continue;
}
if (!currentComponent) continue;
const node = createComponent(entry, state);
if (!node) continue;
if (head === "title") {
currentComponent.children.push({
id: `${currentComponent.id}.title.${currentComponent.children.length}`,
kind: "text.title",
props: parseTextProps(rest),
children: [],
});
if (head === "fieldRow") {
parent.children.push(node.row);
stack.push({ indent: entry.indent, node: node.field });
continue;
}
if (head === "text") {
currentComponent.children.push({
id: `${currentComponent.id}.text.${currentComponent.children.length}`,
kind: "text",
props: parseTextProps(rest),
children: [],
});
if (activeSkin && parent === activeSkin) {
activeSkin.children.push(node);
stack.push({ indent: entry.indent, node });
continue;
}
if (head === "metric") {
currentComponent.children.push({
id: `${currentComponent.id}.metric.${currentComponent.children.length}`,
kind: "metric",
props: {
label: rest[0] || "Metric",
bind: readKeyword(rest, "bind"),
format: rest.includes("percent") ? "percent" : "value",
},
children: [],
});
continue;
}
if (head === "list") {
currentComponent.children.push({
id: `${currentComponent.id}.list.${currentComponent.children.length}`,
kind: "list",
props: {
title: rest[0] || "List",
bind: readKeyword(rest, "bind"),
},
children: [],
});
continue;
}
if (head === "button") {
currentComponent.children.push({
id: `${currentComponent.id}.button.${currentComponent.children.length}`,
kind: "control.button",
props: {
label: rest[0] || "Action",
action: parseButtonAction(rest),
},
children: [],
});
}
parent.children.push(node);
stack.push({ indent: entry.indent, node });
}
return state;
@ -215,12 +147,248 @@ export function createCultMeshStore() {
if (action.type === "append") {
this.collection(action.target).append(renderTemplate(action.value));
} else if (action.type === "set") {
this.var(action.target).set(renderTemplate(action.value));
this.var(action.target).set(parseDslValue(renderTemplate(action.value)));
}
},
};
}
function readDslLines(source) {
const lines = [];
let indentMode;
let spaceUnit;
for (const rawLine of source.split(/\r?\n/)) {
const withoutComment = stripComment(rawLine);
if (!withoutComment.trim()) continue;
const leading = withoutComment.match(/^[\t ]*/)?.[0] || "";
if (leading.includes("\t") && leading.includes(" ")) {
throw new Error("CultUI indentation may use tabs or spaces, not both on one line.");
}
if (leading) {
const mode = leading.includes("\t") ? "tabs" : "spaces";
indentMode ||= mode;
if (indentMode !== mode) throw new Error("CultUI indentation must be consistent within a file.");
if (mode === "spaces") spaceUnit ||= leading.length;
}
const indent = leading.includes("\t")
? leading.length
: leading.length ? leading.length / (spaceUnit || leading.length) : 0;
const line = withoutComment.trim();
lines.push({ indent, tokens: tokenize(line), line });
}
return lines;
}
function applyStateCommand(state, mesh, head, rest) {
if (head === "surface") {
state.providerId = rest[0] || state.providerId;
state.surface.root.id = rest[0] || state.surface.root.id;
state.title = rest[1] || state.title;
return true;
}
if (head === "version") {
state.version = Number(rest[0]) || state.version;
return true;
}
if (head === "var") {
mesh.var(rest[0]).set(parseDslValue(rest.slice(1).join(" ")));
return true;
}
if (head === "collection") {
mesh.collection(rest[0]);
return true;
}
if (head === "item") {
mesh.collection(rest[0]).append(parseDslValue(rest.slice(1).join(" ")));
return true;
}
if (head === "derive") {
const [target, operator, sourcePath] = rest;
mesh.derive(target, operator, sourcePath);
return true;
}
return false;
}
function createComponent(entry, state) {
const [head, ...rest] = entry.tokens;
if (head === "fieldRow") {
const label = rest[0] || "Field";
const id = slug(label);
const field = {
id: `${id}.field`,
kind: "partition",
props: { size: "1fr", role: "inspector.field" },
children: [],
};
return {
field,
row: {
id: `${id}.row`,
kind: "partition",
props: { split: "x", gap: 8, padding: 4, align: "center", role: "inspector.row" },
children: [
{
id: `${id}.label`,
kind: "partition",
props: { size: "12rem", role: "inspector.label" },
children: [{ id: `${id}.label.text`, kind: "label", props: { text: label }, children: [] }],
},
field,
],
},
};
}
if (head === "partition") {
return {
id: rest[0] || `partition.${entry.line.length}`,
kind: "partition",
props: parsePairs(rest.slice(1)),
children: [],
};
}
if (head === "pane") {
return {
id: rest[0] || `pane.${entry.line.length}`,
kind: "pane",
props: parsePairs(rest.slice(1), { title: rest[0] }),
children: [],
};
}
if (head === "card") {
return {
id: rest[0] || `card.${entry.line.length}`,
kind: "card",
props: { title: rest[1] || rest[0] || "Card", ...parsePairs(rest.slice(2)) },
children: [],
};
}
if (head === "title") {
return {
id: scopedId("title", entry),
kind: "text.title",
props: parseTextProps(rest),
children: [],
};
}
if (head === "text") {
return {
id: scopedId("text", entry),
kind: "text",
props: parseTextProps(rest),
children: [],
};
}
if (head === "label") {
return {
id: scopedId("label", entry),
kind: "label",
props: parseTextProps(rest),
children: [],
};
}
if (head === "metric") {
return {
id: scopedId("metric", entry),
kind: "metric",
props: {
label: rest[0] || "Metric",
bind: readKeyword(rest, "bind"),
format: rest.includes("percent") ? "percent" : "value",
},
children: [],
};
}
if (head === "list") {
return {
id: scopedId("list", entry),
kind: "list",
props: {
title: rest[0] || "List",
bind: readKeyword(rest, "bind"),
},
children: [],
};
}
if (head === "button") {
return {
id: scopedId("button", entry),
kind: "control.button",
props: {
label: rest[0] || "Action",
action: parseButtonAction(rest),
},
children: [],
};
}
if (head === "slider") {
return {
id: scopedId("slider", entry),
kind: "control.slider",
props: parsePairs(rest),
children: [],
};
}
if (head === "box") {
return {
id: scopedId("box", entry),
kind: "control.box",
props: parsePairs(rest),
children: [],
};
}
if (head === "part") {
return {
id: scopedId(`part.${rest[0] || "unnamed"}`, entry),
kind: "control.part",
props: { name: rest[0] || "part", ...parsePairs(rest.slice(1)) },
children: [],
};
}
if (head === "hitArea") {
return {
id: scopedId("hitArea", entry),
kind: "control.hitArea",
props: parsePairs(rest),
children: [],
};
}
if (head === "skin") return undefined;
return {
id: scopedId(head, entry),
kind: head,
props: parsePairs(rest),
children: [],
};
}
function parseTextProps(tokens) {
const bind = readKeyword(tokens, "bind");
return {
@ -251,12 +419,63 @@ function parseButtonAction(tokens) {
return undefined;
}
function parsePairs(tokens, initial = {}) {
const props = { ...initial };
for (let index = 0; index < tokens.length; index += 1) {
const key = tokens[index];
if (key === "bind") {
props.bind = tokens[index + 1];
index += 1;
} else if (key === "split") {
props.split = tokens[index + 1];
index += 1;
} else if (key === "skin") {
props.skin = tokens[index + 1];
index += 1;
} else if (PROPERTY_NAMES.has(key)) {
const value = readPropertyValue(tokens, index + 1);
props[key] = value.value;
index = value.nextIndex - 1;
} else if (index + 1 < tokens.length && !PROPERTY_NAMES.has(tokens[index + 1])) {
props[key] = parseDslValue(tokens[index + 1]);
index += 1;
} else {
props[key] = true;
}
}
return props;
}
function isPropertyLine(head, parent) {
return Boolean(parent?.props && PROPERTY_NAMES.has(head));
}
function applyProperty(props, head, rest) {
props[head] = normalizePropertyValues(rest.map(parseDslValue));
}
function readPropertyValue(tokens, start) {
let nextIndex = start;
while (nextIndex < tokens.length && !PROPERTY_NAMES.has(tokens[nextIndex])) {
nextIndex += 1;
}
return {
value: normalizePropertyValues(tokens.slice(start, nextIndex).map(parseDslValue)),
nextIndex,
};
}
function normalizePropertyValues(values) {
return values.length <= 1 ? values[0] : values;
}
function readKeyword(tokens, keyword) {
const index = tokens.indexOf(keyword);
return index >= 0 ? tokens[index + 1] : undefined;
}
function parseDslValue(value) {
if (typeof value !== "string") return value;
const trimmed = value.trim();
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed);
if (trimmed === "true") return true;
@ -269,8 +488,15 @@ function renderTemplate(value) {
}
function stripComment(line) {
const index = line.indexOf("#");
return index >= 0 ? line.slice(0, index) : line;
let quote;
for (let index = 0; index < line.length; index += 1) {
const char = line[index];
if ((char === "\"" || char === "'") && line[index - 1] !== "\\") {
quote = quote === char ? undefined : quote || char;
}
if (char === "#" && !quote) return line.slice(0, index);
}
return line;
}
function tokenize(line) {
@ -282,3 +508,34 @@ function tokenize(line) {
}
return tokens;
}
function slug(value) {
return String(value || "field")
.toLowerCase()
.replace(/[^a-z0-9]+/g, ".")
.replace(/^\.+|\.+$/g, "") || "field";
}
function scopedId(prefix, entry) {
return `${prefix}.${slug(entry.line).slice(0, 48)}`;
}
const PROPERTY_NAMES = new Set([
"align",
"anchor",
"bleed",
"clip",
"fill",
"gap",
"height",
"max",
"min",
"overflow",
"padding",
"radius",
"scroll",
"shadow",
"size",
"step",
"width",
]);

42
web/eve-dsl.test.mjs Normal file
View file

@ -0,0 +1,42 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { compileEveDsl } from "./eve-dsl.js";
test("compiles indentation-authored cards without end blocks", () => {
const state = compileEveDsl(readFileSync(new URL("./fixtures/reactive-composition.eve", import.meta.url), "utf8"));
assert.equal(state.providerId, "gamecult.eve.reactive-demo");
assert.equal(state.surface.root.kind, "surface");
assert.equal(state.surface.root.children.length, 3);
assert.equal(state.surface.root.children[0].children.length, 3);
});
test("lowers fieldRow sugar into explicit label and field partitions", () => {
const state = compileEveDsl(readFileSync(new URL("./fixtures/cultui-slider-inspector.eve", import.meta.url), "utf8"));
const pane = state.surface.root.children[0].children[0];
const fieldList = pane.children[0];
const row = fieldList.children[0];
assert.equal(row.kind, "partition");
assert.equal(row.props.role, "inspector.row");
assert.equal(row.children[0].props.role, "inspector.label");
assert.equal(row.children[1].props.role, "inspector.field");
assert.equal(row.children[1].children[0].kind, "control.slider");
});
test("preserves slider skin anatomy as retained control parts", () => {
const state = compileEveDsl(readFileSync(new URL("./fixtures/cultui-slider-inspector.eve", import.meta.url), "utf8"));
const skin = state.surface.styles.controlSkins["inspector.orangeSlider"];
assert.equal(skin.target, "slider");
assert.deepEqual(skin.children.map(child => child.kind), [
"control.box",
"control.part",
"control.part",
"control.part",
"control.hitArea",
]);
assert.equal(skin.children[3].props.name, "thumb");
assert.equal(skin.children[3].props.bleed, 3);
});

View file

@ -0,0 +1,28 @@
# CultUI partition and element-anatomy fixture.
# The slider face is specified here; the browser only lowers it.
surface eve.cultui.inspector "CultUI Inspector"
version 1
var tester.fov 38
var tester.throttleDecay 5
var tester.flowSpeed 2
skin inspector.orangeSlider for slider
box height 18 overflow visible
part track anchor center size 100% 6 radius 2 fill color.panelInset
part fill anchor left center size value% 6 radius 2 fill color.accent
part thumb anchor value center size 12 12 bleed 3 radius 999 fill color.accent shadow glow color.accent 0.45 radius 7
hitArea size 100% 22
partition inspector split y gap 6 padding 10
pane field-tester title "Aetheric Field Tester" padding 10
partition field-list split y gap 6
fieldRow "FOV"
slider bind tester.fov min 0 max 120 step 1 skin inspector.orangeSlider
fieldRow "Throttle Decay"
slider bind tester.throttleDecay min 0 max 20 step 0.5 skin inspector.orangeSlider
fieldRow "Flow Speed"
slider bind tester.flowSpeed min 0 max 10 step 0.25 skin inspector.orangeSlider

View file

@ -13,18 +13,15 @@ item huginn.records.entries "cursor:moderation | voidbot.moderation_cursor | 202
item huginn.records.entries "affect:face | voidbot.face_affect | 2026-05-31T22:13:44.000Z | 768 bytes | decoded"
card source "Source Witness"
text "file: E:\\Projects\\CultCacheTS\\.voidbot\\state\\huginn.cc"
text "format: cultcache.store.v1"
text "bytes: 3456"
text ".cc bytes are authority; Huginn only emits Eve DSL."
end
text "file: E:\\Projects\\CultCacheTS\\.voidbot\\state\\huginn.cc"
text "format: cultcache.store.v1"
text "bytes: 3456"
text ".cc bytes are authority; Huginn only emits Eve DSL."
card catalog "Schema Catalog"
text "schemas: 3"
list "Catalog Entries" bind huginn.catalog.entries
end
text "schemas: 3"
list "Catalog Entries" bind huginn.catalog.entries
card records "Persisted Records"
text "records: 3"
list "Record Entries" bind huginn.records.entries
end
text "records: 3"
list "Record Entries" bind huginn.records.entries

View file

@ -17,22 +17,19 @@ derive swarm.eventCount count swarm.events
derive swarm.latestEvent latest swarm.events
card identity "Identity Binding"
title bind face.name prefix "Renderer: "
text bind face.body prefix "Body: "
text "Eve owns rendering; providers and CultMesh own truth."
end
title bind face.name prefix "Renderer: "
text bind face.body prefix "Body: "
text "Eve owns rendering; providers and CultMesh own truth."
card pulse "Reactive Pulse"
metric "Swarm Heat" bind swarm.heat percent
metric "Event Count" bind swarm.eventCount
text bind swarm.latestEvent prefix "Latest: "
button "Append Event" append swarm.events "operator touched the stream at {{now}}"
end
metric "Swarm Heat" bind swarm.heat percent
metric "Event Count" bind swarm.eventCount
text bind swarm.latestEvent prefix "Latest: "
button "Append Event" append swarm.events "operator touched the stream at {{now}}"
card doctrine "Binding Contract"
text "Vars are single live values with subscribers."
text "Collections are event-shaped surfaces."
text "Derived fields update from their sources."
text "Actions mutate through the mesh boundary, not the DOM."
list "Event Stream" bind swarm.events
end
text "Vars are single live values with subscribers."
text "Collections are event-shaped surfaces."
text "Derived fields update from their sources."
text "Actions mutate through the mesh boundary, not the DOM."
list "Event Stream" bind swarm.events

View file

@ -289,6 +289,109 @@ p {
border-bottom-width: var(--border-bottom-width);
}
.cultui-partition {
min-width: 0;
min-height: 0;
display: flex;
gap: 10px;
}
.cultui-partition.split-x {
flex-direction: row;
}
.cultui-partition.split-y,
.cultui-partition.split-none {
flex-direction: column;
}
.cultui-partition.split-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.cultui-partition[data-role="inspector.row"] {
min-height: 34px;
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 4px;
background: linear-gradient(180deg, rgba(32, 31, 43, 0.96), rgba(19, 20, 29, 0.96));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 2px 8px rgba(0, 0, 0, 0.4);
}
.cultui-partition[data-role="inspector.label"] {
justify-content: center;
}
.cultui-label {
color: var(--accent);
font: 700 13px var(--font-mono);
line-height: 1.15;
text-transform: uppercase;
}
.cultui-slider {
--cultui-slider-value: 0%;
--part-bleed: 0px;
position: relative;
display: grid;
align-items: center;
width: 100%;
min-height: 18px;
overflow: visible;
}
.cultui-slider-visual {
position: absolute;
inset: 0;
overflow: visible;
pointer-events: none;
}
.cultui-slider-input {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
}
.cultui-slider-part {
position: absolute;
top: 50%;
display: block;
transform: translateY(-50%);
}
.cultui-slider-part.track {
right: 0;
left: 0;
height: 6px;
border-radius: 2px;
background: rgba(0, 0, 0, 0.34);
}
.cultui-slider-part.fill {
left: 0;
width: var(--cultui-slider-value);
height: 6px;
border-radius: 2px;
background: var(--accent);
}
.cultui-slider-part.thumb {
left: var(--cultui-slider-value);
width: 12px;
height: 12px;
border-radius: 999px;
background: var(--accent);
transform: translate(-50%, -50%);
box-shadow: 0 0 7px color-mix(in srgb, var(--accent) 45%, transparent);
}
.cultui-slider-part[data-anchor-value="true"] {
left: var(--cultui-slider-value);
transform: translate(-50%, -50%);
}
body[data-provider="repixelizer"] .surface {
position: relative;
}

View file

@ -10,6 +10,7 @@ const providerMeta = document.querySelector("#provider-meta");
let socket;
let providers = [];
let currentProvider;
let currentSurfaceStyles = {};
const defaultStyleTokens = {
"--bg": "#020909",
@ -82,6 +83,13 @@ const localProviders = [
freshness: { state: "fixture" },
surfaces: [{ transport: "local-eve-dsl", surfaceId: "eve.reactive.dsl", url: "./fixtures/reactive-composition.eve" }],
},
{
providerId: "eve.cultui.inspector",
title: "CultUI Inspector",
kind: "surface.fixture",
freshness: { state: "fixture" },
surfaces: [{ transport: "local-eve-dsl", surfaceId: "eve.cultui.inspector", url: "./fixtures/cultui-slider-inspector.eve" }],
},
];
providerSelect.addEventListener("change", () => {
@ -182,6 +190,7 @@ function closeSocket() {
function renderSurface(state, source) {
window.__eveCurrentMesh = state.mesh;
currentSurfaceStyles = state.surface?.styles || {};
surfaceId.textContent = state.providerId || "surface unknown";
surfaceVersion.textContent = `v${state.version ?? "?"}`;
statusEl.textContent = `${state.title || "surface"} (${source})`;
@ -255,6 +264,14 @@ function renderCultComponent(node) {
return grid;
}
if (kind === "partition") {
const partition = el("section", `cultui-partition split-${props.split || "none"}`);
applyBoxProps(partition, props);
if (props.role) partition.dataset.role = props.role;
for (const child of children) partition.append(renderCultComponent(child));
return partition;
}
if (kind === "image.background") {
const view = el("div", "cultui-background");
if (props.src) view.style.backgroundImage = `url("${props.src}")`;
@ -347,7 +364,7 @@ function renderCultComponent(node) {
return image;
}
if (kind === "text.dialogue" || kind === "text" || kind === "text.title") {
if (kind === "text.dialogue" || kind === "text" || kind === "text.title" || kind === "label") {
const text = el("div", textClassName(kind, props), props.text || node.text || "");
bindText(text, props);
return text;
@ -373,6 +390,10 @@ function renderCultComponent(node) {
return button;
}
if (kind === "control.slider") {
return renderCultSlider(props, children);
}
if (kind === "inspector.kv") {
const panel = el("section", "pane");
panel.append(el("h2", "", props.title || "Inspector"));
@ -459,12 +480,155 @@ function renderCultList(props) {
return panel;
}
function renderCultSlider(props, children) {
const anatomy = resolveSliderAnatomy(props, children);
const box = anatomy.find(part => part.kind === "control.box")?.props || {};
const parts = anatomy.filter(part => part.kind === "control.part");
const hitArea = anatomy.find(part => part.kind === "control.hitArea")?.props || {};
const min = Number(props.min ?? 0);
const max = Number(props.max ?? 1);
const step = Number(props.step ?? 0.01);
const slider = el("div", "cultui-slider");
slider.dataset.skin = props.skin || "default";
applyControlBoxProps(slider, box);
const visual = el("div", "cultui-slider-visual");
const input = el("input", "cultui-slider-input");
input.type = "range";
input.min = String(min);
input.max = String(max);
input.step = String(step);
input.setAttribute("aria-label", props.bind || "slider");
for (const part of parts) {
const partEl = el("span", `cultui-slider-part ${part.props?.name || "part"}`);
applySliderPartProps(partEl, part.props || {});
visual.append(partEl);
}
if (!parts.some(part => part.props?.name === "track")) visual.append(el("span", "cultui-slider-part track"));
if (!parts.some(part => part.props?.name === "fill")) visual.append(el("span", "cultui-slider-part fill"));
if (!parts.some(part => part.props?.name === "thumb")) visual.append(el("span", "cultui-slider-part thumb"));
applyHitAreaProps(input, hitArea);
slider.append(visual, input);
const setVisualValue = (value) => {
const number = Number(value ?? min);
const bounded = Math.max(min, Math.min(max, Number.isFinite(number) ? number : min));
const percent = max === min ? 0 : ((bounded - min) / (max - min)) * 100;
input.value = String(bounded);
slider.style.setProperty("--cultui-slider-value", `${percent}%`);
slider.dataset.value = String(bounded);
};
if (props.bind) {
currentMesh()?.var(props.bind).subscribe(setVisualValue);
input.addEventListener("input", () => currentMesh()?.var(props.bind).set(Number(input.value)));
} else {
setVisualValue(props.value ?? min);
}
return slider;
}
function resolveSliderAnatomy(props, children) {
const skin = props.skin ? currentSurfaceStyles.controlSkins?.[props.skin] : undefined;
const skinChildren = skin?.children || [];
const anatomy = [...skinChildren, ...children];
if (anatomy.length) return anatomy;
return [
{ kind: "control.box", props: { height: 18, overflow: "visible" } },
{ kind: "control.part", props: { name: "track", anchor: "center", size: ["100%", 6], radius: 2, fill: "color.panelInset" } },
{ kind: "control.part", props: { name: "fill", anchor: ["left", "center"], size: ["value%", 6], radius: 2, fill: "color.accent" } },
{ kind: "control.part", props: { name: "thumb", anchor: ["value", "center"], size: [12, 12], bleed: 3, radius: 999, fill: "color.accent" } },
{ kind: "control.hitArea", props: { size: ["100%", 18] } },
];
}
function applyControlBoxProps(element, props) {
if (props.height !== undefined) element.style.minHeight = cssSize(props.height);
if (props.width !== undefined) element.style.width = cssSize(props.width);
if (props.overflow) element.style.overflow = props.overflow;
}
function applySliderPartProps(element, props) {
const [width, height] = Array.isArray(props.size) ? props.size : [props.size, undefined];
if (width !== undefined && width !== "value%") element.style.width = cssSize(width);
if (height !== undefined) element.style.height = cssSize(height);
if (props.radius !== undefined) element.style.borderRadius = cssSize(props.radius);
if (props.fill !== undefined) element.style.background = tokenColor(props.fill);
if (props.bleed !== undefined) element.style.setProperty("--part-bleed", cssSize(props.bleed));
if (props.shadow) element.style.boxShadow = sliderShadow(props.shadow);
const anchor = Array.isArray(props.anchor) ? props.anchor : [props.anchor];
if (anchor.includes("value")) element.dataset.anchorValue = "true";
}
function applyHitAreaProps(element, props) {
const [width, height] = Array.isArray(props.size) ? props.size : [props.size, undefined];
if (width !== undefined) element.style.width = cssSize(width);
if (height !== undefined) element.style.height = cssSize(height);
}
function applyBoxProps(element, props) {
if (props.gap !== undefined) element.style.gap = cssSize(props.gap);
if (props.padding !== undefined) element.style.padding = cssSize(props.padding);
if (props.size !== undefined) element.style.flex = flexSize(props.size);
if (props.min !== undefined) element.style.minWidth = cssSize(props.min);
if (props.max !== undefined) element.style.maxWidth = cssSize(props.max);
if (props.align !== undefined) element.style.alignItems = alignValue(props.align);
if (props.clip === "true" || props.clip === true) element.style.overflow = "hidden";
if (props.scroll === "y") element.style.overflowY = "auto";
if (props.scroll === "x") element.style.overflowX = "auto";
}
function flexSize(value) {
if (typeof value === "string" && value.endsWith("fr")) {
return `${Number(value.slice(0, -2)) || 1} 1 0`;
}
if (value === "auto" || value === "content") return "0 0 auto";
return `0 0 ${cssSize(value)}`;
}
function cssSize(value) {
if (Array.isArray(value)) return value.map(cssSize).join(" ");
if (typeof value === "number") return `${value}px`;
if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) return `${value}px`;
return String(value ?? "");
}
function alignValue(value) {
if (value === "center") return "center";
if (value === "end") return "flex-end";
return String(value);
}
function tokenColor(value) {
const map = {
"color.accent": "var(--accent)",
"color.panelInset": "rgba(0, 0, 0, 0.34)",
"color.panel": "var(--panel)",
"color.text": "var(--text)",
};
return map[value] || value;
}
function sliderShadow(value) {
const parts = Array.isArray(value) ? value : [value];
if (parts[0] !== "glow") return parts.join(" ");
const color = tokenColor(parts[1] || "color.accent");
const alpha = Number(parts[2] ?? 0.35);
const radius = cssSize(parts[4] ?? parts[3] ?? 6);
return `0 0 ${radius} color-mix(in srgb, ${color} ${Math.round(alpha * 100)}%, transparent)`;
}
function currentMesh() {
return window.__eveCurrentMesh;
}
function textClassName(kind, props) {
if (kind === "text.title" || props.role === "title") return "cultui-title";
if (kind === "label") return "cultui-label";
if (props.role === "mono") return "detail mono";
return "detail";
}