Skip to content

Commit fe88de3

Browse files
Make every setting a field type declares reachable from the builder (#2)
A field type says what it supports; the inspector decides what to draw. Nothing joined those two, and the result was not a bug anybody could see: thirty-two supported settings had no control anywhere. The heading level on a heading. The markup in an HTML block. The file types a file field accepts. How many rows a repeater allows. The words on the ends of a scale. The button at the bottom of a page break — which is where this started, because a multi-page form in any language other than English shipped a button saying "Next" and there was nothing to be done about it. Every one of them worked perfectly once set, which is exactly why nobody noticed: the renderer honoured them, the schema stored them, the tests passed, and the only way to set one was to export the form as JSON, edit it by hand, and import it back. The join is now a table — one line per setting — and a test that reads the PHP registrations and fails when a flag has no control and no stated reason to lack one. A hand-kept list here would have agreed with itself forever while disagreeing with the plugin. On the canvas, the settings that are visible parts of the field are edited where they appear: the hint under any control that offers one, the page break's own Next and Back buttons, the repeater's Add button, the name of the step. Both panes stay in step by naming the property they edit rather than by two hand-kept lists of keys, which is one list fewer to forget. Also fixed, all found while doing the above: - `prevLabel` never rendered. A page's Back button read the break that *closes* that page, but the wording belongs to the break that opened it — the same one whose Next brought the visitor there. On a two-page form the setting was unreachable by construction. - The form title sat outside `.atf-form`, where the theme's tokens are scoped, so every `var( --atf-* )` on it was invalid — taking its font, size, colour and bottom margin with them. It had been wearing the site theme's `h2` and sitting flush against the step indicator. The tokens are now scoped to the wrapper, which is what the instance id is on anyway. - A switch rendered as a tick box. It now draws a switch, and the preview tells the two apart the same way the front end does. - A toggle drew its label twice, once as the field label and once beside the control. - The hint sat above the control on the canvas and below it on the page. - `calc`, `confirm`, `counter`, `searchable`, `disabledays`, `defaultcountry`, `explanation` and a rating `icon` were declared and read by nothing. Removed rather than given controls that would do nothing. Likert statements are edited as one per line, and a row's key is what its answers are stored against — so keys survive rewording. Correcting a typo in a statement must not detach every answer already given to it. Claude-Session: https://claude.ai/code/session_0119U4sRRWGcTQdwYreTwpAp Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 0c68b61 commit fe88de3

19 files changed

Lines changed: 4907 additions & 233 deletions

‎assets/css/builder.css‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,6 +2246,70 @@ os-checkbox-label.atfb-check {
22462246
background: var( --atfb-accent-soft );
22472247
}
22482248

2249+
/* A control and the chrome that belongs to it — a repeater and its add button,
2250+
a page break and its navigation — kept together as one block. */
2251+
.atfb-preview__stack {
2252+
display: flex;
2253+
flex-direction: column;
2254+
gap: 6px;
2255+
}
2256+
2257+
/* The hint keeps `.atf-hint` from the front end, so its size and colour are the
2258+
theme's. This only stops an empty one collapsing: an editable with nothing in
2259+
it still has to be a target you can click, and the placeholder inside it is
2260+
what invites the first hint anybody writes. */
2261+
.atfb-preview__hint {
2262+
margin: 4px 0 0;
2263+
}
2264+
2265+
.atfb-preview__hint .atfb-editable {
2266+
display: block;
2267+
min-block-size: 1lh;
2268+
}
2269+
2270+
/* A button whose words are the editable part. `.atf-button` gives it the
2271+
theme's real fill, radius and padding; it is a `<span>` rather than a
2272+
`<button>` because a button element cannot hold a caret. */
2273+
.atfb-preview__button {
2274+
display: inline-flex;
2275+
align-items: center;
2276+
/* The stack is a column flex container, so without this the button grows to
2277+
the full width of the card and stops reading as a button at all. */
2278+
align-self: flex-start;
2279+
cursor: text;
2280+
}
2281+
2282+
/* Inherited rather than themed: the text sits on the button's own colour, and
2283+
the editing outline has to be visible against a filled accent as well as
2284+
against a pale secondary. */
2285+
.atfb-preview__button-text {
2286+
color: inherit;
2287+
font: inherit;
2288+
}
2289+
2290+
.atfb-preview__button-text:focus {
2291+
background: none;
2292+
outline-color: currentcolor;
2293+
}
2294+
2295+
.atfb-preview__button-text:hover {
2296+
outline-color: currentcolor;
2297+
}
2298+
2299+
/* The page break's own navigation, drawn small: it is a preview of the buttons
2300+
that close the page, not a call to action on the canvas. */
2301+
.atfb-preview__nav {
2302+
display: flex;
2303+
gap: 8px;
2304+
margin: 0;
2305+
}
2306+
2307+
/* The step's name, which is what the progress indicator shows. */
2308+
.atfb-progress-name {
2309+
margin: 0;
2310+
font-weight: 600;
2311+
}
2312+
22492313
/* What an elaborate control says instead of pretending to be itself. */
22502314
.atfb-preview__summary {
22512315
margin: 0;

‎assets/css/form.css‎

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,49 @@
923923
justify-content: space-between;
924924
}
925925

926+
/* Every button in a form, reset against the site's theme.
927+
*
928+
* A form is dropped into somebody else's theme, and themes style bare `button`
929+
* elements — Astra's inline CSS sets `padding: 15px 30px`, a box-shadow and a
930+
* background on `.menu-toggle, button, .ast-button, …`. Ours declared `border`
931+
* and `background` and left everything else, so the repeater's 28px × turned
932+
* into a 60×30 slab with a drop shadow hanging over the row's corner.
933+
*
934+
* Declaring only the properties we happen to care about is what loses these
935+
* fights. A form has to look the same on any theme, so every button starts from
936+
* the same place and the rules below paint from there. `.atf-form button` weighs
937+
* (0,1,1), which beats the bare `button` in a theme's selector list.
938+
*
939+
* Wrapped in `:where()`, which contributes nothing to specificity — so this
940+
* weighs (0,1,0): enough to beat a theme's bare `button`, and low enough that
941+
* every `.atf-button` rule below still wins on source order. Written plainly as
942+
* `.atf-form button` it weighed (0,1,1) and beat our own components instead,
943+
* which stripped the submit button and the repeater's "Add another" back to
944+
* bare text — a reset that resets the wrong side of the fight.
945+
*
946+
* Anything a theme commonly sets belongs in this list; if a control starts
947+
* looking wrong on one theme and right on another, this is the first place to
948+
* look. */
949+
.atf-form :where( button ) {
950+
margin: 0;
951+
padding: 0;
952+
border: 0;
953+
border-radius: 0;
954+
background: none;
955+
box-shadow: none;
956+
color: inherit;
957+
font: inherit;
958+
line-height: var( --atf-line-height );
959+
letter-spacing: inherit;
960+
text-transform: none;
961+
text-decoration: none;
962+
min-inline-size: 0;
963+
block-size: auto;
964+
inline-size: auto;
965+
appearance: none;
966+
}
967+
968+
926969
.atf-button {
927970
display: inline-flex;
928971
align-items: center;
@@ -1254,3 +1297,74 @@
12541297
background-color: Highlight;
12551298
}
12561299
}
1300+
1301+
/* --- The Toggle, drawn as a switch -----------------------------------------
1302+
*
1303+
* `switch` and `consent` are the same control underneath — one checkbox — and
1304+
* they should not look the same. A switch says "this setting is on"; consent
1305+
* says "I have agreed", which is a statement a tick box makes and a switch does
1306+
* not. A field type called Toggle that rendered a checkbox was the name lying
1307+
* about the control.
1308+
*
1309+
* Sized in `em` from the field's own font, so it scales with a theme's type
1310+
* rather than needing tokens of its own — a theme that sets a bigger base size
1311+
* gets a bigger switch without touching anything. */
1312+
.atf-form .atf-toggle--switch .atf-toggle__input {
1313+
inline-size: 2.2em;
1314+
block-size: 1.25em;
1315+
border-radius: 1em;
1316+
/* The track, not a box: the switch's own shape overrides the tick box's
1317+
radius token, which is the one place a theme's `radius-check` must not
1318+
apply. */
1319+
padding: 0;
1320+
flex: 0 0 auto;
1321+
position: relative;
1322+
transition: background-color var( --atf-transition-duration ) var( --atf-transition-easing );
1323+
}
1324+
1325+
/* The knob. `::before` is the tick box's mark, reused — so the checked state
1326+
moves the knob rather than drawing a tick. */
1327+
.atf-form .atf-toggle--switch .atf-toggle__input::before {
1328+
content: "";
1329+
position: absolute;
1330+
inset-block-start: 50%;
1331+
inset-inline-start: 0.12em;
1332+
inline-size: 0.95em;
1333+
block-size: 0.95em;
1334+
border-radius: 50%;
1335+
background-color: var( --atf-border );
1336+
transform: translateY( -50% ) scale( 1 );
1337+
clip-path: none;
1338+
transition: inset-inline-start var( --atf-transition-duration ) var( --atf-transition-easing ),
1339+
background-color var( --atf-transition-duration ) var( --atf-transition-easing );
1340+
}
1341+
1342+
.atf-form .atf-toggle--switch .atf-toggle__input:checked::before {
1343+
inset-inline-start: calc( 100% - 1.07em );
1344+
background-color: var( --atf-accent-text );
1345+
transform: translateY( -50% ) scale( 1 );
1346+
}
1347+
1348+
/* The track fills with the accent when on, which is what the tick box does too
1349+
— the state reads the same across every control in the form. */
1350+
.atf-form .atf-toggle--switch .atf-toggle__input:checked {
1351+
background-color: var( --atf-accent );
1352+
border-color: var( --atf-accent );
1353+
}
1354+
1355+
/* A switch is a control, not a word: it sits on the line with its label rather
1356+
than being nudged onto the first line of a paragraph like a tick box. */
1357+
.atf-form .atf-toggle--switch {
1358+
align-items: center;
1359+
}
1360+
1361+
.atf-form .atf-toggle--switch .atf-toggle__label {
1362+
margin-block-start: 0;
1363+
}
1364+
1365+
@media ( prefers-reduced-motion: reduce ) {
1366+
.atf-form .atf-toggle--switch .atf-toggle__input,
1367+
.atf-form .atf-toggle--switch .atf-toggle__input::before {
1368+
transition: none;
1369+
}
1370+
}

0 commit comments

Comments
 (0)