Composition / foldkit-mixins-builder
What the inspector draws
The selected node's props are its Block's settings form, a foldkit-form form
that foldkit-builder makes (see its README), drawn by foldkit-mixins-form's
FormView. So each prop gets the control a form would give it, and its fields
are styled through FieldSlots and FormSlots, as any form's are:
| Prop Schema | Control |
|---|---|
Schema.Boolean | input type="checkbox" |
Schema.Literals([...]) | select of the literals, text or numbers; a number is stored as a number |
Schema.Number | input type="text", so "1." is kept while it is typed |
Schema.String, and a brand of it such as Url | input type="text" |
| anything else | its JSON, shown and not edited |
A control of the application's own, such as a color picker given with
Builder.controls({ tint: Input.bundle(...) }), is drawn with its Bundle's
view, as any form draws it. A field is labelled with its Schema's title,
else its prop key, spaced. A change that decodes is one setProp, and typing
a word is one undo step. One that does not (abc for a number, an address its
brand refuses) shows its error at its field and changes nothing.
Where the Schema alone does not say, the Block asks for a control through
metadata. Input.multiline() draws a textarea, and Input.hidden() leaves
the prop out:
import { Schema } from 'effect'
import { Block, Content } from 'foldkit-composition'
import { Builder } from 'foldkit-builder'
import { Input } from 'foldkit-form'
const Quote = Block.define('Quote', {
Props: Schema.Struct({
text: Schema.String.annotate({ title: 'Quotation' }),
ref: Schema.String,
}),
provides: [Content.Flow],
}).pipe(Block.annotate(Builder.controls({ text: Input.multiline(), ref: Input.hidden() })))The hint is foldkit-builder's, kept on the Block beside any other package's
metadata; foldkit-composition does not read it. A later annotation's prop
replaces an earlier one's.
What the editor calls a Block
A Block's label, description and palette group are its words, from
foldkit-composition, which an agent's tool reads too:
const Described = Quote.pipe(Block.words({ group: 'Text', description: 'Words someone said' }))The label defaults to the Block's name spaced (PostList is "Post list"), and
Blocks given no group share one, "Blocks"; a group is headed only when there is
more than one. The palette shows the label and the description, and a layer row
the label and the node's first text prop, cut to forty characters. Palette
buttons and layer rows carry data-block with the Block's name, so a Style can
give each an icon; a pattern's button carries data-pattern with its name.
A prop that points at the application's things
A prop holding an id, such as the category a list shows, asks for a picker:
Input.relationOne(Category) draws a select, and Input.relationMany(Tag)
a group of checkboxes over a prop that is an array of ids. What they choose
from is the application's rows, loaded by its own query as a form's picker
options are; the page's parent gives them in the Builder's view inputs, keyed
'Block.prop':
const Featured = Block.define('Featured', {
Props: Schema.Struct({ category: Schema.NullOr(Schema.String) }),
provides: [Content.Flow],
}).pipe(Block.annotate(Builder.controls({ category: Input.relationOne(Category) })))
EditorSlot.view(model, h, {
controls: {
document: BuilderView.inputs({
options: { 'Featured.category': categories.map(c => ({ value: c.id, label: c.name })) },
}),
},
})A relationOne always offers "none", stored as null where the Schema admits
it; choosing it for a prop that needs an id says so at the field. A chosen id
the choices lack (a row since deleted, or choices not loaded yet) is shown as
? id; in a relationMany it stays chosen until it is unchecked. The
inspector lists the choices it is given and has no search box.
A node whose Block the Catalog does not know is shown, with its props, but not
edited. A stored value a select does not offer, such as a choice an older
version made, is shown as ? value and chosen, with its error, rather than as
the blank.
The props are under Content, labelled by their Schema's title, else their
key spaced (text is "Text"). Under Style, each appearance axis the Block
offers: one of up to four values is a row of buttons, "Default" and each value,
the chosen one pressed (a stored value the axis lacks is shown pressed as
? value); one with more values is a select, with a blank for the default. A
choice is one setAppearance, and choosing the default for the last one
removes the node's appearance. A responsive token axis is a select per
breakpoint (Space at md), blank for unchanged; with only the base chosen, one
name is stored.
Then, under Visibility, when the Catalog declares a context, one field per
context key says when the node shows: Shown when audience is is a select of
the key's literals (a
flag's is true and false, other keys are typed in), with a blank for
always. A choice is one setWhen holding an eq condition for that key;
conditions of other kinds are kept as they are, and clearing the last removes
the node's when.
Last, under Interactions, each event the Block names says what it runs:
On press is a select
of the Catalog's actions, blank for nothing, and under it the chosen action's
input as a form of its own, drawn and styled as the props are; its ids are
<Block>-<event>-<action>-<key>. Choosing an action starts its input from
each field's empty value as stored ('', null, false), else zero or a
select's first choice (inputOf(block, event, action).seed() in
foldkit-builder); a start the action's Schema still refuses is refused and
shown in the alert. Each change to the input is one
setAction with the changed keys over the rest, and choosing nothing removes
it.
The looks and the conditions stay the Builder's own controls, drawn through
BuilderSlots: each is a choice from a fixed list, which a form would draw no
better.