# Object editor

> A record as labelled fields, each with the control its kind calls for, built from the record's schema.

An object editor shows a record as labelled fields and lets people change them:
the form that edits a customer, the inspector beside a canvas, the detail of a
row. It is built from the record's [schema](https://ui.publictheta.com/foundations/schema.md), so every field
has the control that suits its kind.

## When to use

Use an object editor where people work on one record at a time and need all of
its fields. For many records with the same fields, a table shows more at once.

## Best practices

**Give each kind of value its own control.** Text in a text field, a number
where numbers are read and stepped, a date where dates are understood, a choice
as a select. People should never type what they could choose. Where a text holds
an email address, a telephone number or a name, say so: the right keyboard
appears, and the browser can fill it in.

**Ask a yes-or-no with a checkbox only when it must be answered.** Unticked is
then an answer, so the checkbox is never called required. A yes-or-no that may be
left unanswered is a choice of yes, no and not set: a checkbox cannot be put back
to neither.

**Label every field**, with the words people use for it, beside the field where
the editor has room and above it where the editor is narrow or under a finger. A
long label wraps; it does not take the width from its control. Beside a control
of several lines, the label sits on the first.

**Describe what a label cannot.** A sentence of help belongs under the control,
always visible, and is read with it.

**Show structure.** A record within a record is a group with its own heading. A
list shows its items, each with its own control and named by its place; an item
that is itself a record is a group of its own fields. A list has a way to add an
item and each item a way to remove it. An optional record can be removed
altogether, and none of its fields is then asked for.

**Let people choose among kinds of record.** Where a value is one of several
kinds of record, show the choice of which first, named by the field's label, and
the chosen kind's fields after it, as a group in the same columns. Before a kind
is chosen there is only the choice. Changing it keeps what the two kinds share
and starts the rest blank, as one change; nothing typed into the old kind's
fields, or reported of them, carries over. A choice that may be left unmade can
be put back to none. In a list, each item says which kind it is beside its name.

**Show a record that contains itself only as far as it goes.** A category with a
parent category, a folder of folders: draw what the value holds and, where it
holds nothing yet, a button that adds it. Never draw fields for a record that is
not there.

**Show problems the schema cannot state where they belong.** A key used twice, a
start after an end, a name the server has taken: given with the path to the
field at fault, such a problem appears beside it like any other, and is found
with the rest when people try to finish.

**Keep focus where the work is.** Adding an item moves focus to it. Removing one
moves focus to the next item, else the one before, else the button that adds
one; focus is never lost. In a list of single-line texts, Enter in the last item
adds another and Backspace in an empty one removes it, as in any list being
typed.

**Let items be put in order without dragging.** Each item of a list of several
has a handle. Pressing it lifts the item; the arrow keys, or the two buttons
that appear beside it, move it; pressing it again drops it, and Escape puts it
back where it was. Every move is announced with the item's new place.

**An item is itself, not its place.** What has been typed into an item, and any
problem shown for it, stay with that item when another is removed or it is moved.

**Show what is not yet given as empty.** A field without a value is blank, and a
yes-or-no without one is neither; do not show a zero or a "no" that nobody
entered. A new item starts from the default its schema states, if it states one.

**Report a problem beside its field, once the field has been left.** People who
have not reached a field have not got it wrong yet. Say what the limit is —
"At least 3 characters", "No earlier than Oct 1, 2026" — written as the reader's
locale writes it. Do not let the report move the page under a pointer that is
still pressed. When people try to finish, show every remaining problem at once
and take them to the first.

**Say what is required** to assistive technology as well as to the eye. Mark
only the fewer: the optional fields where most are required, the required ones
where most are optional.

**Show what cannot be changed as it is.** A read-only field keeps its value in
view, can be reached and read, and does not respond to editing; a read-only
editor offers nothing to add, remove or reorder.

**Keep what is computed right, and show that it is.** A field whose
[formula](https://ui.publictheta.com/foundations/formula.md) computes it follows what it reads: an edit
comes with every value it moves, as one change, which is undone as one. It is
marked as computed and says what it is computed as; a formula that cannot be
used is a problem of its field from the start, with the part it is about
marked. Within the records of a list, computed values follow and are not typed
over.

**Let values be [locked](https://ui.publictheta.com/foundations/derived.md), and say which are.** Typing
over a computed field freezes it at what was typed. A computed field has a
menu — and, where the application works with locks, every field of the record
does: _Hold value_; _Freeze_ for a computed one; _Unlock_, or _Unfreeze_ for a
frozen computed one, which computes it again. Elsewhere a field keeps the
browser's own menu, with its cut, copy, paste and spelling. A held field is shown but
not changed, and says why. A change that would move a held value is not made:
the field it was made in keeps what was typed, marked, with the reason —
"Total is held; this would change it." — announced; Escape puts it aside, and
Enter asks for it again once the lock is off.

**Change one thing at a time.** Editing a field changes that field and what it
computes; the rest of the record is untouched, and an edit that changes nothing
reports nothing.

**Start afresh with another record.** What was visited, typed or reported for
one record says nothing about the next.

**Say everything in the reader's language.** Every phrase the editor shows or
announces can be replaced, and a phrase with parts — "Remove Tag 2" — is made
from its parts, so each language can order them its own way.

## Platform considerations

**Keyboard.** Tab moves through the fields in order; each field follows its own
component's keys. On an item's handle, Space or Enter lifts and drops, the up and
down arrows move the lifted item, and Escape returns it. A field's menu opens
with the Menu key or Shift+F10.

**Pointer.** Pressing a handle lifts the item and shows buttons to move it up and
down, which keep it lifted while they are used; pressing the handle again drops
it. Dragging is not yet offered. A secondary click opens a field's menu.

**Touch.** Labels sit above their fields, and every control is at least 44 CSS
pixels tall. Touching and holding a field opens its menu.

**Assistive technology.** Each control is named by its label and described by
its lock, what it is computed as, its help and its problem; a problem marks it
invalid, and is announced politely when it appears, with the name of its field.
A nested record and a list are named groups. A handle says whether its item is
lifted and how it is used; lifting, moving, dropping and removing are each
announced, and so is a change a lock refused.

## Behaviour

Each statement is verified by a test in Chromium, Chromium (touch), Firefox, WebKit, WebKit (touch), unless it names the environments it applies to or is marked open.

### Semantics

- Every field has the control for its kind, named by its label.
- A record within the record is a named group of its own fields.
- A field that must be given says it is required; an optional one does not.
- Where most fields are required the optional ones are marked for the eye, and where most are optional the required ones.
- A yes-or-no that must be answered is a checkbox, and is not said to be required: unticked is an answer.
- An optional yes-or-no is a choice of yes, no and not set, so it can go back to unanswered.
- A value not yet given shows as empty, and a yes-or-no as neither.
- A field's description is shown with it, and described by it along with any problem.
- What a text is said to hold chooses its keyboard and what the browser may fill in.
- A read-only field shows its value and cannot be changed.
- An editor that is read-only is so throughout: nothing steps, ticks, opens, or is added or removed.
- Has no accessibility violations, with and without problems shown.
- Has no accessibility violations with lists of records, an item lifted, and optional parts.

### Editing

- Changing a field changes that field of the record, and nothing else.
- Each kind is edited through its own control.
- A field of a record within the record changes that inner record only.
- Clearing an optional field leaves it absent.
- Typing into an empty text and erasing it again changes nothing.
- A value of the wrong shape is edited as an empty record, and none of it leaks into the fields.
- A field's key may be any text: one with a dot or a slash in it is not taken for a part of another field.
- An optional record can be removed, which asks for none of its fields, and added again as new.
- Choosing what is already chosen changes nothing.

### Lists

- A list shows a control for each item, named by its place.
- An item is added empty, and removed by its own button.
- An added item starts from the default its schema states.
- A list that is full cannot be added to.
- Adding an item moves focus to it.
- Removing an item moves focus to the next item, else the one before, else the button that adds one.
- A removal is announced.
- What was typed into an item, and its problem, stay with that item when another is removed.
- Enter in the last item of a list of single-line texts adds another, keeping what was typed.
- Backspace in an empty item of such a list removes it and moves focus to the one before.
- While an input method is composing, Enter and Backspace are its own: nothing is added or removed.
- Enter that adds an item does not also submit a form the editor is in.
- An item added after another was removed starts with nothing remembered of that one.
- A list's own problem is shown once the list has been added to or removed from.
- In a list of anything else, Enter and Backspace add and remove nothing.
- A list of records shows each as a group of its own fields, named by its place.
- A list of lists shows each inner list as a group of its own items.

### Reordering

- Each item of a list of several has a handle: Space lifts the item, the arrow keys move it, and Space drops it.
- The handle says how it is used.
- The arrow keys do not move an item that has not been lifted, or past either end.
- Escape puts a lifted item back where it was, and goes no further.
- Lifting, each move and the drop are announced, with the item's place among how many.
- A lifted item is dropped where it is when focus leaves its handle.
- A lifted item shows buttons that move it up and down, for a pointer, and stays lifted as they are used.
- What was typed into an item moves with it.
- A list of one item has no handle.
- Items are reordered by dragging. *(open)*

### Variants

- A variant is a choice of which, named by its label, followed by the chosen variant's fields as a group.
- Until one is chosen a variant shows the choice alone, which says it is required.
- Choosing another variant shows its fields, keeps the values of the fields the two share, and starts the rest blank: one change.
- A variant with no fields of its own shows the choice alone.
- A field of the chosen variant is edited like any other, and only it changes.
- An optional variant can be put back to none chosen; one that must be chosen offers no way back.
- A variant's description is shown with its choice, and the fewer of its fields are marked.
- A problem with which variant it is shows beside the choice, once the choice has been left.
- A problem in a variant's field shows beside that field, and is announced with its name.
- What was typed into one variant's field, and its problem, are not another's, nor its own when it is chosen again.
- A read-only variant shows which it is and its fields, and neither can be changed.
- In a list of variants each item shows which it is beside its name, and its own fields.
- An item added to a list of variants starts as the variant its schema states, or as none chosen, with focus on its choice.
- An item of a list of variants changes which it is without disturbing the others.
- Items of a list of variants are reordered and removed like any others, each keeping which it is and what it holds.
- Has no accessibility violations with a variant, a list of variants, and their problems shown.

### References

- A field whose schema is given by reference is edited as what it refers to, under the reference's own label.
- A record that contains itself is shown as far as its value goes: where it has none, a button adds it.
- A record that must hold itself and does not is reported as missing, with nothing to remove; an optional one can be removed again.
- A list of records of the list's own kind nests as deep as its value, and each level is added to on its own.
- Has no accessibility violations with a record within itself, added and not.

### Problems

- A problem is not shown while its field has not been visited.
- A problem is shown beside its field once the field is left, and belongs to it.
- A problem is announced as it appears, with the field it belongs to.
- A problem appears when the pointer is released, not while it is pressed, so nothing moves under it.
- Moving into a choice's own list is not leaving the field.
- Clearing a field that must be given is reported as required, and the record holds no value for it.
- A value that breaks a limit is reported in words, not only marked.
- The words name the limit, written as the editor's locale writes it.
- Text that cannot be read as a value of the field's kind is reported, and the record is unchanged.
- Correcting the value removes the problem.
- Every problem can be shown at once, as after an attempt to submit, and then none is announced.

### Computed and locked values

- A value computed from others follows them: an edit comes with every value it computes, as one change.
- A computed value is marked, and says what it is computed from, which assistive technology hears with it.
- Typing over a computed value freezes it at what was typed: it says so, and no longer follows.
- Where the owner works with locks, each value has a menu, asked for by the keyboard: Hold value for any, and Freeze too for one that is computed.
- Where the owner does not, only a computed value has the menu: the rest keep the browser's own, with its cut, copy, paste and spelling.
- A secondary click on a value opens its menu. *(Chromium, Firefox, WebKit)*
- A finger that rests on a value opens its menu. *(Chromium (touch), WebKit (touch))*
- A held value cannot be changed, is marked, and says why.
- A change that would move a held computed value is not made: the field keeps what was typed, marked, with why, said aloud.
- A change that leaves a held value as it was is made; and a frozen value between stops the change on its way.
- Escape puts a refused change aside and shows the value again; Enter asks for it again, which a lock taken off since lets through.
- Freeze keeps a computed value as it is; Unfreeze computes it again, as one change.
- Unfreezing that would move a held value is refused, and says so.
- A formula that cannot be used is a problem of its field from the start, with the part it is about marked.
- Values computed within the records of a list follow them too, and are not typed over.
- An editor that is read-only offers no menu, and leaves the browser's own.
- A value within a record of the record, or a list, can be locked. *(open)*
- Has no accessibility violations with computed, frozen, held and refused values.

### Language

- The words for a problem can be given in another language.
- Words that name a limit are made from it, in the order the language wants.
- Everything else the editor says can be too, with its parts in that language's order.
- The words for computed and locked values, and for a refusal, can be given in another language.
- A move is announced in the language's own order.

### Layout

- Where the editor is wide, each label sits beside its control. *(Chromium, Firefox, WebKit)*
- Where the editor itself is narrow, each label sits above its control, however wide the screen. *(Chromium, Firefox, WebKit)*
- On a narrow screen, each label sits above its control.
- Above its control, a label stands further from the field before it than from its own.
- Under a finger, each label sits above its control, however wide the editor. *(Chromium (touch), WebKit (touch))*
- A long label wraps, and leaves its control most of the width. *(Chromium, Firefox, WebKit)*
- Beside a control of several lines, the label sits on its first line. *(Chromium, Firefox, WebKit)*

## Reference implementation

React: `npx shadcn@latest add https://ui.publictheta.com/r/object-editor.json`
