feat(12.2-04): edit related records in modals and defer relation work on new records

- RelationManager renders create/link/delete/unlink in declared order with
  one primary, opens the child modal (update or view form) or the pivot
  modal on row click, and deletes selected children behind a busy confirm
- RelationChildModal creates and edits children with its own session key
  (X-Child-Session-Key) so uploads and dates work inside it
- RelationPivotModal edits link details; the picker links one record with
  its pivot values when the relation has a pivot form
- deferrable managers render on the create screen with owner id 0, the
  form's X-Session-Key and the pending note, and mark the form dirty
- the registry resolves RelationManager lazily (child forms close an
  import cycle); DataTable gains openable rows and a trailing cell
- relation lang keys in en and pl; rebuilt boardwalk dist
This commit is contained in:
Jakub Zych
2026-10-02 19:56:10 +02:00
parent a0c182745e
commit 69a37456f6
20 changed files with 1698 additions and 112 deletions

View File

@@ -34,6 +34,12 @@ export type { FieldControlProps } from './control'
/** The relation manager's field type (design screen 5). */
export const RELATION_MANAGER = 'relation-manager'
// The relation manager renders child forms (FormGrid -> FormField -> this
// registry), so it sits on an import cycle with this module. It is looked up
// when a field renders, never while this module evaluates: whichever module
// of the cycle loads first, the binding is complete by then.
const lazyRenderers = new Map<string, () => Component>([[RELATION_MANAGER, () => RelationManager]])
const renderers = new Map<string, Component>([
['text', TextField],
['textarea', TextareaField],
@@ -42,7 +48,6 @@ const renderers = new Map<string, Component>([
['switch', SwitchField],
['checkbox', CheckboxField],
['relation', RelationField],
[RELATION_MANAGER, RelationManager],
['widget', WidgetField],
['partial', PartialField],
['fileupload', FileuploadField],
@@ -54,7 +59,8 @@ const selfLabelled = new Set<string>(['switch', 'checkbox', RELATION_MANAGER])
/**
* Types that need a saved record: never rendered on create (D-05, design
* screen 5).
* screen 5), except a relation manager whose field is deferrable (Phase
* 12.2, D-03): FormView keeps it and it works on owner id 0.
*/
const recordBound = new Set<string>([RELATION_MANAGER])
@@ -68,12 +74,12 @@ const valueless = new Set<string>([RELATION_MANAGER, 'widget', 'partial', 'fileu
const groupLabelledTypes = new Set<string>(['widget', 'partial', 'fileupload'])
export function rendererFor(type: string): Component {
return renderers.get(type) ?? UnsupportedField
return renderers.get(type) ?? lazyRenderers.get(type)?.() ?? UnsupportedField
}
/** Whether the SPA can edit values of this field type. */
export function isRegistered(type: string): boolean {
return renderers.has(type) && !valueless.has(type)
return (renderers.has(type) || lazyRenderers.has(type)) && !valueless.has(type)
}
/** Whether a field type renders only on an existing record (relation manager). */