@cosmicdrift/kumiko-types 0.384.0 → 0.386.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db-connection.d.ts +2 -2
- package/dist/handlers.d.ts +3 -1
- package/dist/handlers.js +13 -1
- package/dist/screen-action-form.d.ts +302 -0
- package/dist/screen-action-form.js +1 -0
- package/dist/screen-custom-config.d.ts +122 -0
- package/dist/screen-custom-config.js +1 -0
- package/dist/screen-dashboard.d.ts +348 -0
- package/dist/screen-dashboard.js +10 -0
- package/dist/screen-entity-edit.d.ts +493 -0
- package/dist/screen-entity-edit.js +8 -0
- package/dist/screen-entity-list.d.ts +427 -0
- package/dist/screen-entity-list.js +1 -0
- package/dist/screen-projection.d.ts +217 -0
- package/dist/screen-projection.js +1 -0
- package/dist/screen.d.ts +15 -1877
- package/dist/screen.js +2 -18
- package/dist/where-clause-types.d.ts +1 -0
- package/package.json +1 -1
- package/src/changes.json +12 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import type { FieldIconKey } from "./field-icon.ts";
|
|
2
|
+
import type { FieldCondition, FieldDefinition, SelectOptionTone } from "./fields.ts";
|
|
3
|
+
import type { AccessRule, AgentScreenHints } from "./handlers.ts";
|
|
4
|
+
import type { IconKey } from "./nav-icon.ts";
|
|
5
|
+
import type { FieldRenderer, PlatformComponent, ScreenNavSugar, ScreenSlots } from "./screen.ts";
|
|
6
|
+
import type { ActionFormRedirect } from "./screen-action-form.ts";
|
|
7
|
+
import type { DashboardPanelVisibility } from "./screen-dashboard.ts";
|
|
8
|
+
import type { ListColumnSpec, ListSortSpec, RelatedListToolbarAction, RowAction, RowActionMode, RowFieldExtractor } from "./screen-entity-list.ts";
|
|
9
|
+
import type { ListFacetSpec } from "./screen-projection.ts";
|
|
10
|
+
export type EditFieldSpec = string | {
|
|
11
|
+
readonly field: string;
|
|
12
|
+
readonly span?: number;
|
|
13
|
+
readonly visible?: FieldCondition;
|
|
14
|
+
readonly readOnly?: FieldCondition;
|
|
15
|
+
readonly required?: FieldCondition;
|
|
16
|
+
readonly renderer?: FieldRenderer;
|
|
17
|
+
/** `false` keeps the field out of the submitted payload — for values
|
|
18
|
+
* that are shown or prefilled for display only (a label, a counter)
|
|
19
|
+
* and that the write handler's schema does not accept. Validation and
|
|
20
|
+
* rendering are unchanged. */
|
|
21
|
+
readonly submit?: false;
|
|
22
|
+
/** Prefix icon on the input — closed FieldIconKey vocabulary into
|
|
23
|
+
* the FIELD_ICONS registry (renderer/web), analogous to
|
|
24
|
+
* `ScreenNavSugar.icon` / NavIconKey. Only takes effect for
|
|
25
|
+
* single-line `type: "text"` and `"number"` — a `text` field with
|
|
26
|
+
* `multiline` routes to a textarea (no icon slot), and other input
|
|
27
|
+
* kinds (select, combobox, date, …) silently ignore the key. */
|
|
28
|
+
readonly icon?: FieldIconKey;
|
|
29
|
+
/** Input placeholder (i18n key, falls back to the literal). Only for
|
|
30
|
+
* single-line text, number and textarea inputs; other kinds ignore it. */
|
|
31
|
+
readonly placeholder?: string;
|
|
32
|
+
/** Static text shown before the input (i18n key, falls back to the
|
|
33
|
+
* literal), or `{ field }` for the current form value of another field
|
|
34
|
+
* (a hidden field carrying e.g. a base domain). Only for single-line
|
|
35
|
+
* text and number inputs; other kinds ignore it. */
|
|
36
|
+
readonly prefix?: FieldAffix;
|
|
37
|
+
/** Same as `prefix`, shown after the input. */
|
|
38
|
+
readonly suffix?: FieldAffix;
|
|
39
|
+
/** Button beside the input that runs a query and writes one scalar of
|
|
40
|
+
* its result into this field. Also offered on a readOnly field, where
|
|
41
|
+
* the value can only come from the suggestion. Only for single-line
|
|
42
|
+
* text and number inputs; other kinds ignore it. */
|
|
43
|
+
readonly fillAction?: FieldFillAction;
|
|
44
|
+
/** Marks this field as a reference lookup instead of a plain value —
|
|
45
|
+
* for projectionDetail fields, which have no `EntityDefinition` to
|
|
46
|
+
* carry a real `reference` field type. Same target convention as
|
|
47
|
+
* `parseRefTarget` (and `ListColumnSpec.refEntity`): an entity name
|
|
48
|
+
* (same feature) or `feature:entity` (cross-feature). Ignored on
|
|
49
|
+
* `entityEdit` fields, where the field's own declared type always
|
|
50
|
+
* wins. */
|
|
51
|
+
readonly refEntity?: string;
|
|
52
|
+
/** Row field on the referenced entity shown as the label (default
|
|
53
|
+
* "id"). Only meaningful together with `refEntity`. */
|
|
54
|
+
readonly refLabelField?: string;
|
|
55
|
+
/** Embedded-list fields only (`createEmbeddedListField`). */
|
|
56
|
+
readonly embeddedList?: EmbeddedListEditSpec;
|
|
57
|
+
/** Fills an empty value when the create form opens; the update form and
|
|
58
|
+
* values from URL params or a handoff are never touched. Unlike a
|
|
59
|
+
* field `default`, it is not applied server-side. */
|
|
60
|
+
readonly prefill?: EditFieldPrefill;
|
|
61
|
+
};
|
|
62
|
+
export type FieldAffix = string | {
|
|
63
|
+
readonly field: string;
|
|
64
|
+
};
|
|
65
|
+
export type FieldFillAction = {
|
|
66
|
+
/** i18n key (falls back to the literal) of the button label. */
|
|
67
|
+
readonly label: string;
|
|
68
|
+
/** Qualified query name, run with the current form values picked by `params`. */
|
|
69
|
+
readonly query: string;
|
|
70
|
+
/** Key in the query result written into the field; the value must be a
|
|
71
|
+
* string or a number, anything else counts as a failed fill. */
|
|
72
|
+
readonly field: string;
|
|
73
|
+
/** Form fields sent as the query payload (none: an empty payload). */
|
|
74
|
+
readonly params?: RowFieldExtractor;
|
|
75
|
+
};
|
|
76
|
+
export type EmbeddedListEditSpec = {
|
|
77
|
+
/** Sub-fields shown as read-only cells; every other cell stays editable. */
|
|
78
|
+
readonly readOnlyCells?: readonly string[];
|
|
79
|
+
/** `true` keeps the rows as they are: no add, remove, duplicate, move or
|
|
80
|
+
* paste-appended rows. Cells stay editable (unless in `readOnlyCells`). */
|
|
81
|
+
readonly fixedRows?: boolean;
|
|
82
|
+
};
|
|
83
|
+
export type EditFieldPrefill = {
|
|
84
|
+
readonly kind: "today";
|
|
85
|
+
} | {
|
|
86
|
+
readonly kind: "currentUser";
|
|
87
|
+
readonly property: "displayName" | "email" | "id";
|
|
88
|
+
};
|
|
89
|
+
export type EditSectionSpec = EditFieldsSection | EditExtensionSection | EditRelatedListSection | EditWriteFormSection;
|
|
90
|
+
type EditFieldsSectionBase = {
|
|
91
|
+
/** Kebab-case tab id, used as the `?tab=` value. Required by the
|
|
92
|
+
* boot-validator when the enclosing `EditLayout.mode` is "tabs". */
|
|
93
|
+
readonly id?: string;
|
|
94
|
+
readonly kind?: "fields";
|
|
95
|
+
/** Optional. Without a title the section only renders its fields (no h3
|
|
96
|
+
* heading) — for flat forms (card title + fields directly, a single
|
|
97
|
+
* section) as in most shadcn form patterns. */
|
|
98
|
+
readonly title?: string;
|
|
99
|
+
/** Optional help text under the block heading (i18n key or raw string,
|
|
100
|
+
* like `title`) — makes a form usable without training instead of
|
|
101
|
+
* falling back to custom JSX for help text. Renders through the same
|
|
102
|
+
* `subtitle` slot as `FormProps.subtitle`. Works without `title` too
|
|
103
|
+
* (subtitle-only section). */
|
|
104
|
+
readonly description?: string;
|
|
105
|
+
readonly columns?: number;
|
|
106
|
+
/** Rendered left of the section title, `text-muted-foreground` — closed
|
|
107
|
+
* IconKey vocabulary into the ICONS registry (renderer/web), analogous
|
|
108
|
+
* to EditFieldSpec.icon. No title → no icon, and no heuristic derives
|
|
109
|
+
* one from the title (titles are free i18n strings). */
|
|
110
|
+
readonly icon?: IconKey;
|
|
111
|
+
/** Record field rendered as a count badge in the tab label when the
|
|
112
|
+
* enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
|
|
113
|
+
* Ignored outside tabs mode or when the field's value is not a finite
|
|
114
|
+
* number. */
|
|
115
|
+
readonly countField?: string;
|
|
116
|
+
/** Short line (i18n key) under this step's title in the wizard step rail
|
|
117
|
+
* and in the "up next" box. Wizard layouts only; unlike `description` it
|
|
118
|
+
* is not shown inside the step. */
|
|
119
|
+
readonly subtitle?: string;
|
|
120
|
+
/** Rendered top-right in the section's Card title row — same
|
|
121
|
+
* navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
|
|
122
|
+
* evaluated against the same record. Every action must resolve an icon
|
|
123
|
+
* (declared or id-derived); the boot-validator rejects one that doesn't. */
|
|
124
|
+
readonly actions?: readonly RowAction[];
|
|
125
|
+
};
|
|
126
|
+
export type EditFieldGroup = {
|
|
127
|
+
readonly title: string;
|
|
128
|
+
readonly fields: readonly EditFieldSpec[];
|
|
129
|
+
/** Default 2. */
|
|
130
|
+
readonly columns?: number;
|
|
131
|
+
};
|
|
132
|
+
/** Either a flat `fields` grid or titled `groups` cards, never both: the groups branch has no
|
|
133
|
+
* `fields` key, so a raw `.fields` read on a section does not compile — use `sectionFieldSpecs`. */
|
|
134
|
+
export type EditFieldsSection = EditFieldsSectionBase & ({
|
|
135
|
+
readonly fields: readonly EditFieldSpec[];
|
|
136
|
+
} | {
|
|
137
|
+
/** Splits the section into multiple titled cards instead of one flat grid; fields named
|
|
138
|
+
* here still need to exist. */
|
|
139
|
+
readonly groups: readonly EditFieldGroup[];
|
|
140
|
+
});
|
|
141
|
+
export type EditExtensionSection = {
|
|
142
|
+
/** Kebab-case tab id, used as the `?tab=` value. Required by the
|
|
143
|
+
* boot-validator when the enclosing `EditLayout.mode` is "tabs". */
|
|
144
|
+
readonly id?: string;
|
|
145
|
+
readonly kind: "extension";
|
|
146
|
+
readonly title: string;
|
|
147
|
+
readonly component: PlatformComponent;
|
|
148
|
+
/** When true, the extension registers via `useExtensionFormSubmit` and the
|
|
149
|
+
* surrounding form's Save button should activate for its dirty state.
|
|
150
|
+
* Omit/false for extensions that persist through their own dispatcher
|
|
151
|
+
* writes (e.g. notes-history NotesSection) — otherwise a read-only form
|
|
152
|
+
* shows a Save button that does nothing (fw#2359). */
|
|
153
|
+
readonly contributesToFormSubmit?: boolean;
|
|
154
|
+
/** Overrides the host-derived entity name passed to the mounted component.
|
|
155
|
+
* Required on `projectionDetail` (which has no real entity — the host
|
|
156
|
+
* value there is an internal placeholder) so a self-persisting extension
|
|
157
|
+
* like NotesSection filters/writes against the right domain entity. Takes
|
|
158
|
+
* precedence over the host value on every screen type. */
|
|
159
|
+
readonly entityName?: string;
|
|
160
|
+
/** Record field rendered as a count badge in the tab label when the
|
|
161
|
+
* enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
|
|
162
|
+
* Ignored outside tabs mode or when the field's value is not a finite
|
|
163
|
+
* number. */
|
|
164
|
+
readonly countField?: string;
|
|
165
|
+
/** Rendered top-right in the section's Card title row — same
|
|
166
|
+
* navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
|
|
167
|
+
* evaluated against the same record. Every action must resolve an icon
|
|
168
|
+
* (declared or id-derived); the boot-validator rejects one that doesn't. */
|
|
169
|
+
readonly actions?: readonly RowAction[];
|
|
170
|
+
};
|
|
171
|
+
/** Rows are grouped by `field` in order of first appearance. Every group gets
|
|
172
|
+
* a collapsible header. */
|
|
173
|
+
export type RelatedListGroupBy = {
|
|
174
|
+
readonly field: string;
|
|
175
|
+
/** Groups whose field value equals this start collapsed; the others start open. */
|
|
176
|
+
readonly collapsedWhen?: string | number | boolean | null;
|
|
177
|
+
/** i18n key of the group header. Placeholders: `{count}` rows in the group,
|
|
178
|
+
* `{value}` the group's field value, `{lastDate}` the group's latest
|
|
179
|
+
* `dateField` value as a locale date. A group with neither `label` nor an
|
|
180
|
+
* entry in `labels` renders its rows without a header and never collapses. */
|
|
181
|
+
readonly label?: string;
|
|
182
|
+
/** Header i18n key per group value (as string), overriding `label`. */
|
|
183
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
184
|
+
/** Row field (ISO date or timestamp) behind `{lastDate}`. */
|
|
185
|
+
readonly dateField?: string;
|
|
186
|
+
};
|
|
187
|
+
/** Group key of a field value; `collapsedWhen` and row values both go through it. */
|
|
188
|
+
export declare function relatedListGroupKey(value: unknown): string;
|
|
189
|
+
export declare function relatedListGroupHeaderLabel(groupBy: RelatedListGroupBy, groupKey: string): string | undefined;
|
|
190
|
+
/** Row tint rule: rows where the `FieldCondition` (`{ field, eq }`, `{ field,
|
|
191
|
+
* in }` …) holds get the tone. `bad` renders red. */
|
|
192
|
+
export type RelatedListRowTone = Exclude<FieldCondition, boolean> & {
|
|
193
|
+
readonly tone: SelectOptionTone;
|
|
194
|
+
};
|
|
195
|
+
/** Replacement for the default "no rows" box of a list. `title`/`description`
|
|
196
|
+
* are i18n keys. `action` is a record action evaluated against an empty
|
|
197
|
+
* record on list screens (the parent record on a relatedList); its icon must
|
|
198
|
+
* resolve, same as `ProjectionDetailScreenDefinition.actions`. A narrowed list
|
|
199
|
+
* (search term or changed filters) shows the neutral "no matches" text instead. */
|
|
200
|
+
export type ListEmptyStateSpec = {
|
|
201
|
+
readonly title: string;
|
|
202
|
+
readonly description?: string;
|
|
203
|
+
readonly action?: RowAction;
|
|
204
|
+
};
|
|
205
|
+
export type EditRelatedListSection = {
|
|
206
|
+
/** Kebab-case tab id, used as the `?tab=` value. Required by the
|
|
207
|
+
* boot-validator when the enclosing `EditLayout.mode` is "tabs". */
|
|
208
|
+
readonly id?: string;
|
|
209
|
+
readonly kind: "relatedList";
|
|
210
|
+
readonly title: string;
|
|
211
|
+
/** Fully qualified query QN. Same paged envelope as projectionList.query:
|
|
212
|
+
* `{ rows, nextCursor, total? }`. */
|
|
213
|
+
readonly query: string;
|
|
214
|
+
/** Query-payload key the parent record's id is passed under. Default "id". */
|
|
215
|
+
readonly parentParam?: string;
|
|
216
|
+
/** Server-side WHERE clause pinning this section to the parent record —
|
|
217
|
+
* sent as `payload.filter: { field, op: "eq", value: parentId }`, kept
|
|
218
|
+
* out of the user-facet `filters` array so it can't be cleared by facet
|
|
219
|
+
* interaction. Lets a tab reuse the generic `<entity>:list` query instead
|
|
220
|
+
* of a bespoke child-rows handler. Mutually exclusive with `parentParam`
|
|
221
|
+
* (the boot-validator rejects both). `field` must be a real field on the
|
|
222
|
+
* entity behind `query`, and that query's Zod schema must accept
|
|
223
|
+
* `filter` (same requirement `filter`/`facets` already have). The filter is
|
|
224
|
+
* client-supplied: any caller may omit it, so the bound query must authorize
|
|
225
|
+
* the ENTIRE row set it can return, not just the parent's children. */
|
|
226
|
+
readonly parentFilter?: {
|
|
227
|
+
readonly field: string;
|
|
228
|
+
};
|
|
229
|
+
readonly columns: readonly ListColumnSpec[];
|
|
230
|
+
/** Entity the query's rows belong to: an entity name (same feature) or
|
|
231
|
+
* `feature:entity`. Columns that name one of its fields render like an
|
|
232
|
+
* entityList column of that field (select as a status badge with the
|
|
233
|
+
* translated option label, dates locale-formatted, default header from the
|
|
234
|
+
* field's label key). The column's own `sortable` still decides the
|
|
235
|
+
* header sort. Without it every column renders as plain text. */
|
|
236
|
+
readonly entity?: string;
|
|
237
|
+
/** i18n key for the one-line hint at the left of the tab toolbar. */
|
|
238
|
+
readonly description?: string;
|
|
239
|
+
/** i18n key holding plural forms for the footer count ("1 Position",
|
|
240
|
+
* "3 Positionen"). Omitted: generic "entries" wording. */
|
|
241
|
+
readonly itemNoun?: string;
|
|
242
|
+
readonly pageSize?: number;
|
|
243
|
+
/** Initial sort on mount, applied client-side over the already-loaded rows
|
|
244
|
+
* (this section has no pager; if the fetch is truncated, `nextCursor !== null`,
|
|
245
|
+
* the section shows a hint banner that the sort covers only the loaded rows).
|
|
246
|
+
* `field` must name a column with `sortable: true` — boot-validator pins
|
|
247
|
+
* that, same as `entityList.defaultSort`. */
|
|
248
|
+
readonly defaultSort?: ListSortSpec;
|
|
249
|
+
/** Renders a search box whose debounced term rides along as
|
|
250
|
+
* `payload.search` on this section's own query. Opt-in, unlike
|
|
251
|
+
* `projectionList.searchable` (derived from the bound query's schema): a
|
|
252
|
+
* relatedList query is an ordinary child-rows handler, so a `search`
|
|
253
|
+
* parameter in its schema is not by itself a statement that this section
|
|
254
|
+
* should offer search. The boot-validator rejects `true` when the query's
|
|
255
|
+
* Zod schema has no `search` parameter. */
|
|
256
|
+
readonly searchable?: boolean;
|
|
257
|
+
/** User-toggleable facet dropdowns — same explicit-label `ListFacetSpec` as
|
|
258
|
+
* `projectionList.facets` (a relatedList has no entity to derive filterable
|
|
259
|
+
* fields from). `field` must be a declared column, except for
|
|
260
|
+
* `type: "reference"`: it filters by id through the bound query's
|
|
261
|
+
* `filters` and needs a registered `entity` instead. The bound query
|
|
262
|
+
* handler must accept `filters` in its Zod schema — the boot-validator
|
|
263
|
+
* checks both. Selected values ride along as `payload.filters`. */
|
|
264
|
+
readonly facets?: readonly ListFacetSpec[];
|
|
265
|
+
/** Row click opens the target entity's detail screen via ObjectTarget —
|
|
266
|
+
* the `detailFor` lookup owns the entity→screen mapping, so no screenId
|
|
267
|
+
* is named here. `idColumn` names the row key holding that id (default
|
|
268
|
+
* "id"). Omit `rowClick` for a non-interactive list. */
|
|
269
|
+
readonly rowClick?: {
|
|
270
|
+
readonly entity: string;
|
|
271
|
+
readonly idColumn?: string;
|
|
272
|
+
};
|
|
273
|
+
/** Per-row actions — same type and dispatch semantics as `entityList`/
|
|
274
|
+
* `projectionList`'s `rowActions` (writeHandler dispatch with a
|
|
275
|
+
* declarative `payload` extractor, or navigate). A successful
|
|
276
|
+
* writeHandler action re-runs this section's own query, same as a
|
|
277
|
+
* projectionList row action re-running its list query. */
|
|
278
|
+
readonly rowActions?: readonly RowAction[];
|
|
279
|
+
/** Layout of the row-action column — see `RowActionMode`. */
|
|
280
|
+
readonly rowActionMode?: RowActionMode;
|
|
281
|
+
/** Groups rows by the value of a field under collapsible group headers. */
|
|
282
|
+
readonly groupBy?: RelatedListGroupBy;
|
|
283
|
+
/** Tints rows whose field matches — a declarative rule, not a function, so
|
|
284
|
+
* the screen definition stays serializable. */
|
|
285
|
+
readonly rowTone?: RelatedListRowTone;
|
|
286
|
+
/** Toolbar actions above the table — same type and dispatch semantics as
|
|
287
|
+
* `entityList`/`projectionList`'s `toolbarActions` ("+ Anlegen" etc.),
|
|
288
|
+
* plus `visible`/`params` evaluated against the parent record (see
|
|
289
|
+
* RelatedListToolbarAction). */
|
|
290
|
+
readonly toolbarActions?: readonly RelatedListToolbarAction[];
|
|
291
|
+
/** Record field rendered as a count badge in the tab label when the
|
|
292
|
+
* enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
|
|
293
|
+
* Ignored outside tabs mode or when the field's value is not a finite
|
|
294
|
+
* number. */
|
|
295
|
+
readonly countField?: string;
|
|
296
|
+
/** Rendered top-right in the section's Card title row — same
|
|
297
|
+
* navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
|
|
298
|
+
* evaluated against the parent record. Every action must resolve an icon
|
|
299
|
+
* (declared or id-derived); the boot-validator rejects one that doesn't.
|
|
300
|
+
* Distinct from `toolbarActions` (rendered inside the DataTable toolbar) —
|
|
301
|
+
* this is the Card-level title-row action. */
|
|
302
|
+
readonly actions?: readonly RowAction[];
|
|
303
|
+
/** Rendered inside the section's Card in place of the table when this
|
|
304
|
+
* section's query returns zero rows. Omitted: the DataTable default
|
|
305
|
+
* ("kumiko.list.no-entries") applies. `action`'s icon must resolve, same
|
|
306
|
+
* as `actions` above. */
|
|
307
|
+
readonly emptyState?: ListEmptyStateSpec;
|
|
308
|
+
};
|
|
309
|
+
export type EditWriteFormSection = {
|
|
310
|
+
/** Kebab-case tab id, used as the `?tab=` value. Required by the
|
|
311
|
+
* boot-validator when the enclosing `EditLayout.mode` is "tabs". */
|
|
312
|
+
readonly id?: string;
|
|
313
|
+
readonly kind: "writeForm";
|
|
314
|
+
readonly title?: string;
|
|
315
|
+
readonly description?: string;
|
|
316
|
+
readonly columns?: number;
|
|
317
|
+
readonly icon?: IconKey;
|
|
318
|
+
/** Field-type map for this section's own form — same shape as
|
|
319
|
+
* `ActionFormScreenDefinition.fields`. */
|
|
320
|
+
readonly fieldDefs: Readonly<Record<string, FieldDefinition>>;
|
|
321
|
+
/** Rendered fields, in order — same shape as `EditFieldsSection.fields`.
|
|
322
|
+
* Every referenced field must have an entry in `fieldDefs`. */
|
|
323
|
+
readonly fields: readonly EditFieldSpec[];
|
|
324
|
+
/** Write-handler QN dispatched on submit. The submitted values (not a
|
|
325
|
+
* diff) land 1:1 as payload, same as `ActionFormScreenDefinition.handler`.
|
|
326
|
+
* The payload carries only this section's own fields — no parent/record
|
|
327
|
+
* id is injected automatically. To thread the host record's id through,
|
|
328
|
+
* declare it in `fieldDefs` with `visible: false` on its `fields` entry:
|
|
329
|
+
* it gets prefilled from the loaded record like any other field and rides
|
|
330
|
+
* along in the submit payload without being shown in the form. */
|
|
331
|
+
readonly handler: string;
|
|
332
|
+
/** i18n-key for the submit button. Default: "kumiko.actions.save". */
|
|
333
|
+
readonly submitLabel?: string;
|
|
334
|
+
/** Rendered top-right in the section's Card title row — same
|
|
335
|
+
* navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
|
|
336
|
+
* evaluated against the same record. Every action must resolve an icon
|
|
337
|
+
* (declared or id-derived); the boot-validator rejects one that doesn't. */
|
|
338
|
+
readonly actions?: readonly RowAction[];
|
|
339
|
+
};
|
|
340
|
+
export type FormWidth = "3xl" | "4xl" | "full";
|
|
341
|
+
export type EditLayout = {
|
|
342
|
+
readonly sections: readonly EditSectionSpec[];
|
|
343
|
+
/** Default "4xl" (a centered column, narrower than list chrome), unless
|
|
344
|
+
* the app overrides it via `createKumikoApp({ screenWidth })`. Override
|
|
345
|
+
* per-screen to "3xl" for a narrower column or "full" to match list
|
|
346
|
+
* chrome. */
|
|
347
|
+
readonly width?: FormWidth;
|
|
348
|
+
/** Default "single". "wizard" renders one section per step (with
|
|
349
|
+
* progress + per-step validation) instead of all sections at once —
|
|
350
|
+
* boot-validator requires >= 2 sections, each with a title, when set.
|
|
351
|
+
* "tabs" renders one section per tab (projectionDetail only — the
|
|
352
|
+
* boot-validator rejects it on entityEdit, see the check for the reason);
|
|
353
|
+
* requires >= 2 sections, each with a title and an `id`. */
|
|
354
|
+
readonly mode?: "single" | "wizard" | "tabs";
|
|
355
|
+
/** "settings-list" lays each section out as a band with a title column on
|
|
356
|
+
* the left and one hairline-separated row per field (label + help left,
|
|
357
|
+
* control right) — the familiar settings pattern, for screens that are a
|
|
358
|
+
* list of independent preferences rather than one record form. Default
|
|
359
|
+
* "form" keeps the grid of cells. */
|
|
360
|
+
readonly variant?: "form" | "settings-list";
|
|
361
|
+
/** Persists in-progress wizard state as a resumable draft instead of
|
|
362
|
+
* discarding it on navigation away. */
|
|
363
|
+
readonly draft?: boolean;
|
|
364
|
+
/** Wizard layouts only. `upNext: true` adds an "up next" box to the step
|
|
365
|
+
* rail with the following step's title and `subtitle`; the last step
|
|
366
|
+
* shows none. */
|
|
367
|
+
readonly wizard?: {
|
|
368
|
+
readonly aside?: {
|
|
369
|
+
readonly upNext: true;
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
export type EntityWriteExcludedFields = {
|
|
374
|
+
readonly create?: readonly string[];
|
|
375
|
+
readonly update?: readonly string[];
|
|
376
|
+
};
|
|
377
|
+
export type EntityEditScreenDefinition = {
|
|
378
|
+
readonly id: string;
|
|
379
|
+
readonly type: "entityEdit";
|
|
380
|
+
/** Default true: the screen fills the shell content height, its body scrolls
|
|
381
|
+
* inside and the pager / action bar stays pinned. `false` restores page
|
|
382
|
+
* scroll (table grows with its rows, pager below the last row, form footer
|
|
383
|
+
* in the flow). */
|
|
384
|
+
readonly fillHeight?: boolean;
|
|
385
|
+
readonly nav?: ScreenNavSugar;
|
|
386
|
+
readonly detailFor?: string;
|
|
387
|
+
readonly description?: string;
|
|
388
|
+
readonly agent?: AgentScreenHints;
|
|
389
|
+
/** Screen has no nav entry by design (opened via link or navved by the
|
|
390
|
+
* app); exempts it from the nav-area boot check. */
|
|
391
|
+
readonly dormant?: boolean;
|
|
392
|
+
/** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
|
|
393
|
+
readonly visibleWhen?: DashboardPanelVisibility;
|
|
394
|
+
/** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
|
|
395
|
+
readonly fallback?: string;
|
|
396
|
+
/** Derived by buildAppSchema from the navigate `params` targeting this
|
|
397
|
+
* screen — the only URL query keys the create form prefills. An authored
|
|
398
|
+
* value is overwritten. */
|
|
399
|
+
readonly urlPrefillFields?: readonly string[];
|
|
400
|
+
/** Derived by buildAppSchema from the entity's create/update write handlers
|
|
401
|
+
* (`excludeFields`): create hides these fields, update shows them read-only.
|
|
402
|
+
* An authored value is overwritten. */
|
|
403
|
+
readonly writeExcludedFields?: EntityWriteExcludedFields;
|
|
404
|
+
readonly entity: string;
|
|
405
|
+
/** Entity field whose value names the loaded record in the header breadcrumb (edit mode only). */
|
|
406
|
+
readonly recordTitleField?: string;
|
|
407
|
+
/** Screen title (i18n key) with `{field}` placeholders filled from the
|
|
408
|
+
* current form values, so the title follows what the user types (create
|
|
409
|
+
* wizard: "Add {make} {model}"). Falls back to the plain title while the
|
|
410
|
+
* placeholders are still empty. */
|
|
411
|
+
readonly titleTemplate?: string;
|
|
412
|
+
/** Shows a saved record read-only while the condition holds for the loaded
|
|
413
|
+
* record (not for unsaved form values): all fields disabled, no save, discard
|
|
414
|
+
* or delete button; record `actions` stay. The title uses
|
|
415
|
+
* `screen:<id>.view.title`, then `screen:<id>.title`. Never applies to create.
|
|
416
|
+
* The entity's `lockedWhen` always applies as well; set this only to add a
|
|
417
|
+
* UI-only condition (either one shows the view).
|
|
418
|
+
* UI only: the update handler still has to enforce access. */
|
|
419
|
+
readonly readOnly?: FieldCondition;
|
|
420
|
+
readonly layout: EditLayout;
|
|
421
|
+
/** Optional i18n key (or raw string) for the submit button. Default
|
|
422
|
+
* `kumiko.actions.save`. Lets the auto-edit screen show domain-specific
|
|
423
|
+
* CTAs ("Save Address", "Create item") instead of a generic "Save". */
|
|
424
|
+
readonly submitLabel?: string;
|
|
425
|
+
/** Default true. `false` for entities whose create runs through its own
|
|
426
|
+
* lifecycle write (e.g. incident:open with event stream + joins) instead
|
|
427
|
+
* of `<entity>:create`: suppresses the automatic "+ New" button on
|
|
428
|
+
* entityList screens of this entity and renders the create branch (call
|
|
429
|
+
* without entityId) as an error instead of a form whose submit would hit
|
|
430
|
+
* an unregistered handler. */
|
|
431
|
+
readonly allowCreate?: boolean;
|
|
432
|
+
/** Default true. `false` when no `<entity>:delete` handler exists
|
|
433
|
+
* (history/audit retention): suppresses the delete button in the
|
|
434
|
+
* update form. */
|
|
435
|
+
readonly allowDelete?: boolean;
|
|
436
|
+
/** Default false. `true` for singleton entities (exactly one record per
|
|
437
|
+
* tenant: organization, settings, tenant profile). A call without an
|
|
438
|
+
* `entityId` first resolves the existing record via `<entity>:list`
|
|
439
|
+
* (limit 1) and renders the update branch with prefill on a hit,
|
|
440
|
+
* instead of blindly creating another one. The create branch only
|
|
441
|
+
* fires when the table is empty (and `allowCreate: false` still
|
|
442
|
+
* applies there). */
|
|
443
|
+
readonly singleton?: boolean;
|
|
444
|
+
/** Navigate to this screen ID after a successful save (create or update).
|
|
445
|
+
* Either a same-feature short screen-ID, or a fully-qualified
|
|
446
|
+
* cross-feature screen QN (`<feature>:screen:<id>`) — the nav-router
|
|
447
|
+
* resolves both against the schema; boot-validator checks the target
|
|
448
|
+
* resolves to a registered screen. Overrides the default post-save
|
|
449
|
+
* target (the entity's list screen); delete is unaffected and still
|
|
450
|
+
* always navigates to the list. A cross-feature QN hard-couples this
|
|
451
|
+
* feature to the target at boot time — an app that doesn't mount the
|
|
452
|
+
* target feature fails the boot-validator; only use this form in a
|
|
453
|
+
* reusable feature when the target feature is guaranteed to be
|
|
454
|
+
* mounted alongside it.
|
|
455
|
+
*
|
|
456
|
+
* The object form additionally names the success-payload field the
|
|
457
|
+
* navigation id comes from (`ActionFormRedirect.idFrom`) — needed when
|
|
458
|
+
* the edited record is a child and the target screen is the parent's
|
|
459
|
+
* detail screen. Update falls back to the loaded record when the
|
|
460
|
+
* success payload doesn't carry that field flatly; create falls back
|
|
461
|
+
* to the submitted form values. */
|
|
462
|
+
readonly redirect?: string | ActionFormRedirect;
|
|
463
|
+
/** Parent list screen (short id) for breadcrumb + nav highlighting when
|
|
464
|
+
* this screen has no nav entry of its own. Same field/semantics as on
|
|
465
|
+
* `custom`/`projectionDetail`; an explicit value here wins over the
|
|
466
|
+
* entity-list heuristic (rowAction target / same-entity entityList). */
|
|
467
|
+
readonly listScreenId?: string;
|
|
468
|
+
/** Optional per-field label i18n key (field name → key), overrides the
|
|
469
|
+
* default convention `<feature>:entity:<entity>:field:<name>`.
|
|
470
|
+
* Primarily for configEdit: its pseudo entity `__config-edit__` has no
|
|
471
|
+
* natural field namespace — the settings hub injects the config key's
|
|
472
|
+
* `mask.title` label here. A missing entry falls back to the
|
|
473
|
+
* convention. */
|
|
474
|
+
readonly fieldLabels?: Readonly<Record<string, string>>;
|
|
475
|
+
/** Record actions. Reuses `RowAction` — analogous to
|
|
476
|
+
* `ProjectionDetailScreenDefinition.actions` (the loaded record stands
|
|
477
|
+
* in for the "row"). `rowClick` has no target here and is rejected by
|
|
478
|
+
* the boot-validator, same as on projectionDetail. Not rendered in
|
|
479
|
+
* create mode — every action targets an existing record, which the
|
|
480
|
+
* create branch doesn't have yet. By default an action renders with the
|
|
481
|
+
* secondary form actions (footer, next to copy-link/delete/cancel);
|
|
482
|
+
* `placement: "menu"` moves it into the header "…" menu, ahead of
|
|
483
|
+
* copy-link/delete. Without a header menu (drawer, card form, shell
|
|
484
|
+
* without a header slot) a "menu" action falls back to inline. */
|
|
485
|
+
readonly actions?: readonly EntityEditAction[];
|
|
486
|
+
readonly slots?: ScreenSlots;
|
|
487
|
+
readonly access?: AccessRule;
|
|
488
|
+
};
|
|
489
|
+
export type EntityEditActionPlacement = "inline" | "menu";
|
|
490
|
+
export type EntityEditAction = RowAction & {
|
|
491
|
+
readonly placement?: EntityEditActionPlacement;
|
|
492
|
+
};
|
|
493
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Group key of a field value; `collapsedWhen` and row values both go through it. */
|
|
2
|
+
export function relatedListGroupKey(value) {
|
|
3
|
+
return value === undefined || value === null ? "" : String(value);
|
|
4
|
+
}
|
|
5
|
+
export function relatedListGroupHeaderLabel(groupBy, groupKey) {
|
|
6
|
+
const labels = groupBy.labels;
|
|
7
|
+
return labels !== undefined && Object.hasOwn(labels, groupKey) ? labels[groupKey] : groupBy.label;
|
|
8
|
+
}
|