Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 66 additions & 3 deletions assets/css/builder.css
Original file line number Diff line number Diff line change
Expand Up @@ -655,17 +655,80 @@
margin-block-end: 8px;
}

.atfb-choice-row,
.atfb-rule {
.atfb-choice-row {
display: grid;
grid-template-columns: 1fr 1fr 70px 32px;
gap: 6px;
align-items: center;
margin-block-end: 6px;
}

/* One rule as a card: the question, the comparison and the answer stacked at
full width. Three dropdowns sharing one 250px line truncated every label to
its first two letters — "Op…", "is l…" — which turned the sentence the rule
is into a ransom note. Stacked, each part gets the whole column and reads
as itself. */
.atfb-rules {
display: flex;
flex-direction: column;
gap: 4px;
margin-block-end: 8px;
}

.atfb-rule {
grid-template-columns: 1fr 1fr 1fr 32px;
display: flex;
flex-direction: column;
gap: 6px;
padding: 8px;
background: var( --atfb-well );
border: 1px solid var( --atfb-border );
border-radius: 8px;
}

/* The question line carries the card's remove button, so the card needs no
extra row for its own furniture. */
.atfb-rule__top {
display: flex;
align-items: center;
gap: 6px;
}

.atfb-rule__top > .atfb-field {
flex: 1 1 auto;
min-inline-size: 0;
}

/* "and" / "or" between two cards — the same flippable fact the canvas row
shows, drawn as a small chip on the seam. One setting governs every joint,
so clicking any of them switches all. */
.atfb-rule-join {
display: flex;
justify-content: center;
}

.atfb-rule-join__chip {
padding: 1px 10px;
border: 1px solid var( --atfb-border );
border-radius: 999px;
background: var( --atfb-surface );
font: inherit;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var( --atfb-muted );
cursor: pointer;
}

.atfb-rule-join__chip:hover,
.atfb-rule-join__chip:focus-visible {
border-color: var( --atfb-accent );
color: var( --atfb-accent );
}

.atfb-rule-join__chip:focus-visible {
outline: 2px solid var( --atfb-accent );
outline-offset: 1px;
}

.atfb-rule-head {
Expand Down
213 changes: 133 additions & 80 deletions assets/js/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -4001,7 +4001,7 @@ var allTerrainFormsBuilder = function(exports) {
);
}
this.inspector.append(this.renderValidationSection(field, supports, update));
this.inspector.append(this.renderLogicSection(field, update));
this.inspector.append(this.renderLogicSection(field));
if (supports.includes("prefill")) {
this.inspector.append(this.prefillControl(field, update));
}
Expand Down Expand Up @@ -6178,83 +6178,148 @@ var allTerrainFormsBuilder = function(exports) {
return rows;
}
/** The conditional-logic editor. */
renderLogicSection(field, update) {
renderLogicSection(field) {
const logic = field.logic;
const liveLogic = () => this.liveField(field.id)?.logic;
const write = (mutate, rebuild = false) => {
const live = liveLogic();
if (!live) {
return;
}
mutate(live);
this.markDirty();
this.renderCanvas();
if (rebuild) {
this.renderInspector();
}
};
const others = (this.schema?.fields ?? []).filter(
(candidate) => candidate.id !== field.id && candidate.type !== "page_break"
);
const rules = el("div", { class: "atfb-rules" });
logic.rules.forEach((rule, index) => {
rules.append(
const joiner = () => el("div", {
class: "atfb-rule-join",
children: [
el("button", {
class: "atfb-rule-join__chip",
type: "button",
text: "all" === logic.match ? "and" : "or",
title: "Switch between needing every rule (and) or any one of them (or).",
on: {
click: () => write((live) => {
live.match = "all" === live.match ? "any" : "all";
}, true)
}
})
]
});
const ruleCard = (rule, index) => {
const remove = el("button", {
class: "atfb-card__action",
type: "button",
attrs: { "aria-label": "Remove this rule" },
title: "Remove this rule",
on: {
click: () => write((live) => {
live.rules.splice(index, 1);
}, true)
},
children: [icon("trash")]
});
const children = [
el("div", {
class: "atfb-rule",
class: "atfb-rule__top",
children: [
select(
rule.field,
others.map((candidate) => ({
value: candidate.id,
label: candidate.label || candidate.id
})),
(value) => {
const live = liveLogic()?.rules[index];
if (live) {
live.field = value;
this.markDirty();
}
}
(value) => (
// A new source question invalidates the old
// answer — a value picked from one field's
// choices means nothing against another's.
write((live) => {
const liveRule = live.rules[index];
if (liveRule) {
liveRule.field = value;
liveRule.value = "";
}
}, true)
)
),
remove
]
}),
select(
rule.operator,
Object.entries(this.config?.operators ?? {}).map(([value, label]) => ({ value, label })),
(value) => (
// "is empty" needs no answer and "is" does, so the
// card's own shape depends on this — rebuild.
write((live) => {
const liveRule = live.rules[index];
if (liveRule) {
liveRule.operator = value;
}
}, true)
)
)
];
if (!VALUELESS_OPERATORS.includes(rule.operator)) {
const source = this.schema?.fields.find((candidate) => candidate.id === rule.field);
if (source?.choices?.length) {
const options = source.choices.map((choice) => ({
value: choice.value,
label: choice.label || choice.value
}));
if ("" !== rule.value && !source.choices.some((choice) => choice.value === rule.value)) {
options.unshift({ value: rule.value, label: rule.value });
}
children.push(
select(
rule.operator,
Object.entries(this.config?.operators ?? {}).map(([value, label]) => ({ value, label })),
(value) => {
const live = liveLogic()?.rules[index];
if (live) {
live.operator = value;
this.markDirty();
rule.value,
options,
(value) => write((live) => {
const liveRule = live.rules[index];
if (liveRule) {
liveRule.value = value;
}
}
),
textInput(rule.value, (value) => {
const live = liveLogic()?.rules[index];
if (live) {
live.value = value;
this.markDirty();
}
}),
el("button", {
class: "atfb-card__action",
type: "button",
attrs: { "aria-label": "Remove this rule" },
on: {
click: () => {
const live = liveLogic();
if (!live) {
return;
}
live.rules.splice(index, 1);
update("logic", live);
this.renderInspector();
})
)
);
} else {
children.push(
textInput(
rule.value,
(value) => write((live) => {
const liveRule = live.rules[index];
if (liveRule) {
liveRule.value = value;
}
},
children: [icon("trash")]
})
]
})
);
}),
"The answer to compare against"
)
);
}
}
return el("div", { class: "atfb-rule", children });
};
const rules = el("div", { class: "atfb-rules" });
logic.rules.forEach((rule, index) => {
if (index > 0) {
rules.append(joiner());
}
rules.append(ruleCard(rule, index));
});
return this.section(
`logic:${field.id}`,
"Conditional logic",
[
checkbox("Only show this field sometimes", logic.enabled, (value) => {
const live = liveLogic();
if (!live) {
return;
}
live.enabled = value;
update("logic", live);
this.renderInspector();
write((live) => {
live.enabled = value;
}, true);
}),
logic.enabled ? el("div", {
children: [
Expand All @@ -6267,13 +6332,9 @@ var allTerrainFormsBuilder = function(exports) {
{ value: "show", label: "Show" },
{ value: "hide", label: "Hide" }
],
(value) => {
const live = liveLogic();
if (live) {
live.action = value;
update("logic", live);
}
}
(value) => write((live) => {
live.action = value;
}, true)
),
el("span", { text: "this field when" }),
select(
Expand All @@ -6282,13 +6343,9 @@ var allTerrainFormsBuilder = function(exports) {
{ value: "all", label: "all" },
{ value: "any", label: "any" }
],
(value) => {
const live = liveLogic();
if (live) {
live.match = value;
update("logic", live);
}
}
(value) => write((live) => {
live.match = value;
}, true)
),
el("span", { text: "of these match:" })
]
Expand All @@ -6297,17 +6354,13 @@ var allTerrainFormsBuilder = function(exports) {
button(
"Add rule",
() => {
const live = liveLogic();
if (!live) {
return;
}
live.rules.push({
field: others[0]?.id ?? "",
operator: "is",
value: ""
});
update("logic", live);
this.renderInspector();
write((live) => {
live.rules.push({
field: others[0]?.id ?? "",
operator: "is",
value: ""
});
}, true);
},
"ghost",
"plus-alt2"
Expand Down
6 changes: 3 additions & 3 deletions assets/js/builder.min.js

Large diffs are not rendered by default.

Loading
Loading