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
17 changes: 12 additions & 5 deletions assets/css/builder.css
Original file line number Diff line number Diff line change
Expand Up @@ -2694,13 +2694,16 @@ os-checkbox-label.atfb-check {
here can resolve against nothing — the content then grows past the
window and the body clips it with no scrollbar, which put the Save
button somewhere nobody could reach. Absolute against the body's
`position: relative` is exact at every window size. */
`position: relative` is exact at every window size.

No bottom padding: the sticky action bar below owns the bottom edge,
and padding under it would be 14px the bar could never cover. */
position: absolute;
inset: 0;
inline-size: auto;
max-block-size: none;
block-size: auto;
padding: 14px 16px;
padding: 14px 16px 0;
overflow-y: auto;
display: block;
}
Expand All @@ -2709,12 +2712,16 @@ os-checkbox-label.atfb-check {
the content: in a window the person sizes themselves, "scroll to find Save"
is a chore the chrome can simply remove. Stretched across the host's padding
and given a top border + opaque background so content sliding under it reads
as under it. */
as under it.

`inset-block-end: 0`, exactly — a negative offset here parks part of the bar
past the scrollport edge, where the window body's `overflow: hidden` cuts
it in half. */
.atfb-valwin--window .atfb-modal__actions {
position: sticky;
inset-block-end: -14px;
inset-block-end: 0;
margin-inline: -16px;
margin-block-end: -14px;
margin-block-start: 12px;
padding: 10px 16px;
background: var( --atfb-bg );
border-block-start: 1px solid var( --atfb-border );
Expand Down
214 changes: 145 additions & 69 deletions assets/js/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3183,16 +3183,12 @@ var allTerrainFormsBuilder = function(exports) {
};
const charBoxes = el("div", {
class: "atfb-valwin__chars",
children: Object.entries(CHAR_GROUPS).map(([key, group]) => {
const box = el("input", {
attrs: { type: "checkbox", checked: recipe.chars.includes(key) }
});
box.addEventListener("change", () => {
recipe.chars = box.checked ? [...recipe.chars, key] : recipe.chars.filter((item) => item !== key);
children: Object.entries(CHAR_GROUPS).map(
([key, group]) => checkbox(group.label, recipe.chars.includes(key), (checked) => {
recipe.chars = checked ? [...recipe.chars, key] : recipe.chars.filter((item) => item !== key);
refresh();
});
return el("label", { class: "atfb-valwin__char", children: [box, el("span", { text: group.label })] });
})
})
)
});
const blocksPane = el("div", {
class: "atfb-valwin__pane",
Expand Down Expand Up @@ -3237,33 +3233,61 @@ var allTerrainFormsBuilder = function(exports) {
)
]
});
const tabs = el("div", { class: "atfb-valwin__tabs", attrs: { role: "tablist" } });
const paintTabs = () => {
tabs.replaceChildren(
...[
["blocks", "Easy blocks"],
["regex", "Expression (advanced)"]
].map(([mode, label]) => {
const active = recipe.mode === mode;
return el("button", {
class: `atfb-valwin__tab${active ? " is-active" : ""}`,
type: "button",
text: label,
attrs: { role: "tab", "aria-selected": active ? "true" : "false" },
on: {
click: () => {
recipe.mode = mode;
blocksPane.hidden = "blocks" !== mode;
regexPane.hidden = "regex" !== mode;
paintTabs();
refresh();
const MODES = [
["blocks", "Easy blocks"],
["regex", "Expression (advanced)"]
];
const setMode = (mode) => {
recipe.mode = mode;
blocksPane.hidden = "blocks" !== mode;
regexPane.hidden = "regex" !== mode;
refresh();
};
const buildTabs = () => {
if (hasComponent("os-segmented") && hasComponent("os-segment")) {
const host2 = document.createElement("os-segmented");
host2.setAttribute("value", recipe.mode);
host2.setAttribute("label", "How to write the rule");
host2.classList.add("atfb-valwin__tabs");
for (const [mode, label] of MODES) {
const segment = document.createElement("os-segment");
segment.setAttribute("value", mode);
segment.textContent = label;
host2.append(segment);
}
host2.addEventListener("os-pick", (event) => {
const mode = event.detail?.value;
if ("blocks" === mode || "regex" === mode) {
host2.setAttribute("value", mode);
setMode(mode);
}
});
return host2;
}
const list = el("div", { class: "atfb-valwin__tabs", attrs: { role: "tablist" } });
const paint = () => {
list.replaceChildren(
...MODES.map(([mode, label]) => {
const active = recipe.mode === mode;
return el("button", {
class: `atfb-valwin__tab${active ? " is-active" : ""}`,
type: "button",
text: label,
attrs: { role: "tab", "aria-selected": active ? "true" : "false" },
on: {
click: () => {
setMode(mode);
paint();
}
}
}
});
})
);
});
})
);
};
paint();
return list;
};
paintTabs();
const tabs = buildTabs();
blocksPane.hidden = "blocks" !== recipe.mode;
regexPane.hidden = "regex" !== recipe.mode;
const messageInput = el("input", {
Expand Down Expand Up @@ -6001,6 +6025,83 @@ var allTerrainFormsBuilder = function(exports) {
}
return this.section(`validation:${field.id}`, "Validation", rows);
}
/**
* The answer-shape dropdown itself.
*
* The shell's `<os-select>` when its components are loaded — the same
* control as every other inspector dropdown. It has no notion of
* `optgroup`, so the group headings ride along as disabled options, which
* its listbox paints muted and unpickable: the same reading an optgroup
* heading gives. The plain admin page gets a native select with real
* optgroups.
*
* @param current The selected value.
* @param onPick Called with the newly picked value.
* @return The control.
*/
buildShapePicker(current, onPick) {
const groups = [
{ heading: null, options: [{ value: "", label: "Anything at all" }] },
...VALIDATION_GROUPS.map((group) => ({
heading: group,
options: VALIDATION_PRESETS.filter((preset) => preset.group === group).map((preset) => ({
value: preset.slug,
label: preset.label
}))
})),
{ heading: "Your own", options: [{ value: "custom", label: "A custom rule…" }] }
];
if (hasComponent("os-select") && hasComponent("os-option")) {
const host = document.createElement("os-select");
host.setAttribute("value", current);
host.setAttribute("aria-label", "What the answer should look like");
host.classList.add("atfb-field");
for (const group of groups) {
if (group.heading) {
const heading = document.createElement("os-option");
heading.setAttribute("value", `__heading:${group.heading}`);
heading.setAttribute("disabled", "");
heading.textContent = group.heading;
host.append(heading);
}
for (const option of group.options) {
const item = document.createElement("os-option");
item.setAttribute("value", option.value);
item.textContent = option.label;
host.append(item);
}
}
host.addEventListener("os-pick", (event) => {
onPick(String(event.detail?.value ?? ""));
});
return host;
}
const picker = el("select", {
class: "atfb-input atfb-select",
attrs: { "aria-label": "What the answer should look like" },
on: {
change: (event) => onPick(event.target.value)
}
});
for (const group of groups) {
const parent = group.heading ? (() => {
const optgroup = document.createElement("optgroup");
optgroup.label = group.heading;
picker.append(optgroup);
return optgroup;
})() : picker;
for (const option of group.options) {
parent.append(
el("option", {
value: option.value,
text: option.label,
attrs: { selected: option.value === current }
})
);
}
}
return picker;
}
/**
* "The answer should look like…" — the validation picker.
*
Expand Down Expand Up @@ -6032,42 +6133,17 @@ var allTerrainFormsBuilder = function(exports) {
},
onCancel: () => this.renderInspector()
});
const picker = el("select", {
class: "atfb-input atfb-select",
attrs: { "aria-label": "What the answer should look like" },
on: {
change: (event) => {
const value = event.target.value;
if ("custom" === value) {
openEditor();
return;
}
update("validation", value);
update("pattern", "");
update("validationRecipe", "");
this.renderInspector();
}
}
});
picker.append(el("option", { value: "", text: "Anything at all", attrs: { selected: "" === current } }));
for (const group of VALIDATION_GROUPS) {
const optgroup = document.createElement("optgroup");
optgroup.label = group;
for (const preset2 of VALIDATION_PRESETS.filter((candidate) => candidate.group === group)) {
optgroup.append(
el("option", {
value: preset2.slug,
text: preset2.label,
attrs: { selected: preset2.slug === current }
})
);
const onPick = (value) => {
if ("custom" === value) {
openEditor();
return;
}
picker.append(optgroup);
}
const yours = document.createElement("optgroup");
yours.label = "Your own";
yours.append(el("option", { value: "custom", text: "A custom rule…", attrs: { selected: "custom" === current } }));
picker.append(yours);
update("validation", value);
update("pattern", "");
update("validationRecipe", "");
this.renderInspector();
};
const picker = this.buildShapePicker(current, onPick);
const preset = validationPreset(current);
const rows = [
row(
Expand Down
6 changes: 3 additions & 3 deletions assets/js/builder.min.js

Large diffs are not rendered by default.

Loading
Loading