@rebasepro/cms-types 0.17.0-canary.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/LICENSE +21 -0
- package/dist/admin_collection.d.ts +650 -0
- package/dist/augment.d.ts +81 -0
- package/dist/collections.d.ts +282 -0
- package/dist/controllers/analytics_controller.d.ts +7 -0
- package/dist/controllers/auth.d.ts +111 -0
- package/dist/controllers/customization_controller.d.ts +69 -0
- package/dist/controllers/dialogs_controller.d.ts +36 -0
- package/dist/controllers/index.d.ts +10 -0
- package/dist/controllers/local_config_persistence.d.ts +20 -0
- package/dist/controllers/navigation.d.ts +248 -0
- package/dist/controllers/registry.d.ts +96 -0
- package/dist/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/controllers/side_panel_controller.d.ts +97 -0
- package/dist/controllers/snackbar.d.ts +45 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.es.js +154 -0
- package/dist/index.es.js.map +1 -0
- package/dist/react_component_ref.d.ts +43 -0
- package/dist/rebase_context.d.ts +68 -0
- package/dist/types/breadcrumbs.d.ts +15 -0
- package/dist/types/builders.d.ts +15 -0
- package/dist/types/collection_views.d.ts +105 -0
- package/dist/types/component_overrides.d.ts +196 -0
- package/dist/types/entity_actions.d.ts +112 -0
- package/dist/types/entity_display.d.ts +148 -0
- package/dist/types/entity_link_builder.d.ts +7 -0
- package/dist/types/entity_views.d.ts +115 -0
- package/dist/types/export_import.d.ts +21 -0
- package/dist/types/form_layout.d.ts +126 -0
- package/dist/types/formex.d.ts +40 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/locales.d.ts +4 -0
- package/dist/types/modify_collections.d.ts +5 -0
- package/dist/types/plugins.d.ts +277 -0
- package/dist/types/property_config.d.ts +74 -0
- package/dist/types/property_options.d.ts +255 -0
- package/dist/types/slots.d.ts +279 -0
- package/dist/types/translations.d.ts +989 -0
- package/dist/types/user_management_delegate.d.ts +22 -0
- package/package.json +103 -0
- package/src/admin_collection.ts +775 -0
- package/src/augment.ts +79 -0
- package/src/collections.ts +312 -0
- package/src/controllers/analytics_controller.tsx +57 -0
- package/src/controllers/auth.ts +122 -0
- package/src/controllers/customization_controller.tsx +81 -0
- package/src/controllers/dialogs_controller.tsx +37 -0
- package/src/controllers/index.ts +10 -0
- package/src/controllers/local_config_persistence.tsx +22 -0
- package/src/controllers/navigation.ts +288 -0
- package/src/controllers/registry.ts +114 -0
- package/src/controllers/side_dialogs_controller.tsx +82 -0
- package/src/controllers/side_panel_controller.tsx +112 -0
- package/src/controllers/snackbar.ts +51 -0
- package/src/index.ts +20 -0
- package/src/react_component_ref.ts +52 -0
- package/src/rebase_context.ts +81 -0
- package/src/types/breadcrumbs.ts +16 -0
- package/src/types/builders.ts +18 -0
- package/src/types/collection_views.tsx +125 -0
- package/src/types/component_overrides.ts +244 -0
- package/src/types/entity_actions.tsx +134 -0
- package/src/types/entity_display.ts +182 -0
- package/src/types/entity_link_builder.ts +8 -0
- package/src/types/entity_views.tsx +135 -0
- package/src/types/export_import.ts +26 -0
- package/src/types/form_layout.ts +137 -0
- package/src/types/formex.ts +45 -0
- package/src/types/index.ts +18 -0
- package/src/types/locales.ts +81 -0
- package/src/types/modify_collections.tsx +6 -0
- package/src/types/plugins.tsx +346 -0
- package/src/types/property_config.tsx +97 -0
- package/src/types/property_options.ts +300 -0
- package/src/types/slots.tsx +334 -0
- package/src/types/translations.ts +1104 -0
- package/src/types/user_management_delegate.ts +23 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a record shows up: its title, image, subtitle, status, date and tags.
|
|
3
|
+
*
|
|
4
|
+
* Every surface that draws a record draws some subset of these six roles. A list
|
|
5
|
+
* row is image + title + subtitle + status + date; a card is the same with the
|
|
6
|
+
* image on top; a board card drops the image; a reference picker is title +
|
|
7
|
+
* subtitle; a page heading is the title alone. The roles are stable — what fills
|
|
8
|
+
* them is not.
|
|
9
|
+
*
|
|
10
|
+
* Before this block, the roles were derived and only derived. `titleProperty`
|
|
11
|
+
* was the single exception, and it could only ever name a property of the
|
|
12
|
+
* collection: seven separate implementations read that key, disagreed about the
|
|
13
|
+
* fallback, and none of them could await. (It is gone now — `display.title`
|
|
14
|
+
* replaced it outright.) The other five roles could not be
|
|
15
|
+
* stated at all — the image was whichever storage property came first, the
|
|
16
|
+
* status whichever enum, the date whichever timestamp. Right often enough to
|
|
17
|
+
* feel automatic, and wrong with no way to say so.
|
|
18
|
+
*
|
|
19
|
+
* So: one mechanism, six roles, two forms each.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* admin: {
|
|
23
|
+
* display: {
|
|
24
|
+
* title: "name", // a property path
|
|
25
|
+
* image: "photos.0", // a dotted path
|
|
26
|
+
* subtitle: ({ entity }) => // computed
|
|
27
|
+
* `${entity.values.city}, ${entity.values.country}`,
|
|
28
|
+
* status: async ({ entity, context }) => { // and may be async
|
|
29
|
+
* const latest = await context.data.audits.get(`${entity.id}/latest`);
|
|
30
|
+
* return latest?.state;
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Anything left out is derived exactly as it is today, so an existing collection
|
|
37
|
+
* renders identically, and a collection that needs one role fixed states that
|
|
38
|
+
* one role.
|
|
39
|
+
*/
|
|
40
|
+
import type { Entity, User } from "@rebasepro/types";
|
|
41
|
+
import type { RebaseContext } from "../rebase_context";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What a resolver is handed.
|
|
45
|
+
*
|
|
46
|
+
* The whole {@link RebaseContext}, matching `AdditionalFieldDelegate.value` and
|
|
47
|
+
* `EntityAction.onClick` — so a resolver can reach `context.data` and
|
|
48
|
+
* `context.client` and read anything the panel can read, including a document in
|
|
49
|
+
* a subcollection that the entity itself never loads.
|
|
50
|
+
*/
|
|
51
|
+
export type EntityDisplayResolverParams<
|
|
52
|
+
M extends Record<string, unknown> = Record<string, unknown>,
|
|
53
|
+
USER extends User = User
|
|
54
|
+
> = {
|
|
55
|
+
entity: Entity<M>;
|
|
56
|
+
context: RebaseContext<USER>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Computes what fills one display role for one record.
|
|
61
|
+
*
|
|
62
|
+
* May be async. While a promise is in flight the surface shows the derived value
|
|
63
|
+
* and swaps the resolved one in when it lands — a title is never a spinner.
|
|
64
|
+
* Results are cached per record and per role, and concurrent calls for the same
|
|
65
|
+
* pair share one execution, so a list of fifty rows resolves each row once
|
|
66
|
+
* rather than once per render.
|
|
67
|
+
*
|
|
68
|
+
* Return `undefined` when this record has nothing for this role. Do not return a
|
|
69
|
+
* placeholder: the surface's own fallback is better informed than the resolver
|
|
70
|
+
* is about what belongs there instead — a heading wants the collection's
|
|
71
|
+
* singular name, a link wants the id.
|
|
72
|
+
*
|
|
73
|
+
* A resolver that throws is treated as `undefined` and logged once. A title that
|
|
74
|
+
* cannot be fetched must not take down the row that shows it.
|
|
75
|
+
*/
|
|
76
|
+
export type EntityDisplayResolver<
|
|
77
|
+
M extends Record<string, unknown> = Record<string, unknown>,
|
|
78
|
+
T = unknown,
|
|
79
|
+
USER extends User = User
|
|
80
|
+
> = {
|
|
81
|
+
/**
|
|
82
|
+
* Declared as a *method* and then indexed back out, which is the only way to
|
|
83
|
+
* write a standalone function type whose parameters stay bivariant.
|
|
84
|
+
*
|
|
85
|
+
* Not a style choice. `AdminCollectionOptions<M>` has to remain assignable
|
|
86
|
+
* to `AdminCollectionOptions<Record<string, unknown>>` — every consumer that
|
|
87
|
+
* takes a collection it did not author depends on it, and losing it breaks
|
|
88
|
+
* `defineCollection`'s own overloads. A resolver takes `Entity<M>` in
|
|
89
|
+
* parameter position, so written as `(params) => …` it makes the entire
|
|
90
|
+
* admin block invariant in `M`, and a typed collection stops being usable as
|
|
91
|
+
* a collection. Method syntax is bivariant under `strictFunctionTypes`; the
|
|
92
|
+
* sibling callbacks (`EntityAction.onClick`, `AdditionalFieldDelegate.value`)
|
|
93
|
+
* are all written this way, and `packages/types/__tests__/bivariance` is the
|
|
94
|
+
* record of finding it out the hard way.
|
|
95
|
+
*/
|
|
96
|
+
resolve(params: EntityDisplayResolverParams<M, USER>): T | undefined | Promise<T | undefined>;
|
|
97
|
+
}["resolve"];
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Where one display role gets its value: a property path on this collection, or
|
|
101
|
+
* a resolver that computes it.
|
|
102
|
+
*
|
|
103
|
+
* The path arm is checked against `M` and read with `getValueInPath`, so
|
|
104
|
+
* `"profile.displayName"` is as valid as `"title"`. It also keeps the property's
|
|
105
|
+
* own rendering — an enum status stays a coloured chip, a date stays formatted,
|
|
106
|
+
* a storage path stays a thumbnail — which a resolver returning a bare string
|
|
107
|
+
* cannot. Prefer the path whenever the value is on the record.
|
|
108
|
+
*
|
|
109
|
+
* `Path` is a type parameter rather than `PropertyPath<M>` directly, so this
|
|
110
|
+
* module does not import from `admin_collection`, which imports it.
|
|
111
|
+
*/
|
|
112
|
+
export type EntityDisplaySource<
|
|
113
|
+
Path extends string,
|
|
114
|
+
M extends Record<string, unknown> = Record<string, unknown>,
|
|
115
|
+
T = unknown,
|
|
116
|
+
USER extends User = User
|
|
117
|
+
> = Path | EntityDisplayResolver<M, T, USER>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The six roles, and what may fill each.
|
|
121
|
+
*
|
|
122
|
+
* The value types describe what the renderers accept, not what a resolver must
|
|
123
|
+
* produce exactly: a `date` resolver may return a `Date`, an ISO string or an
|
|
124
|
+
* epoch number, and `tags` takes a single string as shorthand for one tag. A
|
|
125
|
+
* property path is not constrained by them at all — the property's own type
|
|
126
|
+
* decides how it renders.
|
|
127
|
+
*/
|
|
128
|
+
export type EntityDisplay<
|
|
129
|
+
Path extends string = string,
|
|
130
|
+
M extends Record<string, unknown> = Record<string, unknown>,
|
|
131
|
+
USER extends User = User
|
|
132
|
+
> = {
|
|
133
|
+
/**
|
|
134
|
+
* What the record is called: the heading, the breadcrumb, the row, and the
|
|
135
|
+
* label of every relation chip and reference that points at it.
|
|
136
|
+
*/
|
|
137
|
+
title?: EntityDisplaySource<Path, M, string, USER>;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The line under the title — a short description, a location, a summary.
|
|
141
|
+
*/
|
|
142
|
+
subtitle?: EntityDisplaySource<Path, M, string, USER>;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The record's picture. A storage path or a URL: the same two things a
|
|
146
|
+
* `storage` property holds, so a resolver may return either.
|
|
147
|
+
*/
|
|
148
|
+
image?: EntityDisplaySource<Path, M, string, USER>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The state chip — published, archived, paid. Rendered with the enum's own
|
|
152
|
+
* colour when it comes from an enum property.
|
|
153
|
+
*/
|
|
154
|
+
status?: EntityDisplaySource<Path, M, string, USER>;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The timestamp a row is stamped with, usually when it last changed.
|
|
158
|
+
*/
|
|
159
|
+
date?: EntityDisplaySource<Path, M, Date | string | number, USER>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Free chips beside the status: labels, categories, topics. A single string
|
|
163
|
+
* is accepted as shorthand for one tag.
|
|
164
|
+
*/
|
|
165
|
+
tags?: EntityDisplaySource<Path, M, string[] | string, USER>;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The roles as data, so every consumer iterates the same list instead of
|
|
170
|
+
* repeating it — the mistake that let `titleProperty` grow seven readers.
|
|
171
|
+
*/
|
|
172
|
+
export const ENTITY_DISPLAY_ROLES = [
|
|
173
|
+
"title",
|
|
174
|
+
"subtitle",
|
|
175
|
+
"image",
|
|
176
|
+
"status",
|
|
177
|
+
"date",
|
|
178
|
+
"tags"
|
|
179
|
+
] as const;
|
|
180
|
+
|
|
181
|
+
/** One of the six display roles. */
|
|
182
|
+
export type EntityDisplayRole = typeof ENTITY_DISPLAY_ROLES[number];
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Entity, EntityValues } from "@rebasepro/types";
|
|
3
|
+
|
|
4
|
+
import type { FormexController } from "./formex";
|
|
5
|
+
import type { ComponentRef } from "@rebasepro/types";
|
|
6
|
+
import type { AdminCollection } from "@rebasepro/cms-types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Context passed to custom fields and entity views.
|
|
10
|
+
* @group Form custom fields
|
|
11
|
+
*/
|
|
12
|
+
export interface FormContext<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Current values of the entity
|
|
16
|
+
*/
|
|
17
|
+
values: M;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Update the value of a field
|
|
21
|
+
*/
|
|
22
|
+
setFieldValue: (key: string, value: unknown, shouldValidate?: boolean) => void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Quietly persist the entity to the database without any UI feedback
|
|
26
|
+
* (no validation, no snackbar, no form reset).
|
|
27
|
+
* Use this for programmatic/background saves from custom views.
|
|
28
|
+
*/
|
|
29
|
+
save: (values: M) => void;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Submit the form — validates, saves, resets the form, and shows
|
|
33
|
+
* a success snackbar. This is what the Save button calls.
|
|
34
|
+
* Use this from custom views when you want the full "user saved" experience.
|
|
35
|
+
*/
|
|
36
|
+
submit: () => void;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Collection of the entity being modified
|
|
40
|
+
*/
|
|
41
|
+
collection?: AdminCollection<M>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Entity id, it can be undefined if it's a new entity
|
|
45
|
+
*/
|
|
46
|
+
entityId?: string | number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Path this entity is located at
|
|
50
|
+
*/
|
|
51
|
+
path?: string;
|
|
52
|
+
|
|
53
|
+
status: "new" | "existing" | "copy";
|
|
54
|
+
|
|
55
|
+
entity?: Entity<M>;
|
|
56
|
+
|
|
57
|
+
savingError?: Error;
|
|
58
|
+
|
|
59
|
+
openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The underlying formex controller that powers the form.
|
|
63
|
+
*/
|
|
64
|
+
formex: FormexController<M>;
|
|
65
|
+
|
|
66
|
+
disabled: boolean;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A save is in flight. Covers the autosave debounce too, which `formex`
|
|
70
|
+
* cannot report because it never goes through `handleSubmit`.
|
|
71
|
+
*
|
|
72
|
+
* The identity bar reads this to label its Save button, which is what
|
|
73
|
+
* replaced the unlabelled floating status circle in the form.
|
|
74
|
+
*/
|
|
75
|
+
isSaving?: boolean;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether the form context is in read-only detail view mode.
|
|
79
|
+
* Custom entity views can use this to adjust their rendering.
|
|
80
|
+
*/
|
|
81
|
+
readOnly?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type EntityCustomView<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
85
|
+
key: string;
|
|
86
|
+
name: string;
|
|
87
|
+
icon?: string | React.ReactNode;
|
|
88
|
+
tabComponent?: React.ReactNode;
|
|
89
|
+
includeActions?: boolean | "bottom";
|
|
90
|
+
Builder?: ComponentRef<EntityCustomViewParams<M>>;
|
|
91
|
+
/**
|
|
92
|
+
* Which side of the record's own tab this one sits on.
|
|
93
|
+
*
|
|
94
|
+
* `"end"` (the default) puts it after the record and before the
|
|
95
|
+
* subcollections, which is where an extra reading of a row belongs.
|
|
96
|
+
*
|
|
97
|
+
* `"start"` puts it *before* the record — the cover of an entity: a
|
|
98
|
+
* read-only summary an operator opens the row to see, with the form that
|
|
99
|
+
* edits it one tab to the right. Pair it with
|
|
100
|
+
* {@link AdminCollectionOptions.defaultSelectedView}, or the strip will open
|
|
101
|
+
* on a tab that is not the first one.
|
|
102
|
+
*/
|
|
103
|
+
position?: "start" | "end";
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Configuration to replace the default entity form with a custom component.
|
|
108
|
+
* The Builder receives the same props as entity view tabs (entity, formContext, etc.)
|
|
109
|
+
* and has full control over the UI.
|
|
110
|
+
*
|
|
111
|
+
* The form tab still appears in the tab bar but renders your Builder
|
|
112
|
+
* instead of the auto-generated field form.
|
|
113
|
+
*
|
|
114
|
+
* @group Models
|
|
115
|
+
*/
|
|
116
|
+
export type FormViewConfig<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
117
|
+
/**
|
|
118
|
+
* Custom component that replaces the default form.
|
|
119
|
+
*/
|
|
120
|
+
Builder: ComponentRef<EntityCustomViewParams<M>>;
|
|
121
|
+
/**
|
|
122
|
+
* If true, the save/delete action bar is rendered alongside the custom view.
|
|
123
|
+
* Defaults to true.
|
|
124
|
+
*/
|
|
125
|
+
includeActions?: boolean;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export interface EntityCustomViewParams<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
129
|
+
collection: AdminCollection<M>;
|
|
130
|
+
entity?: Entity<M>;
|
|
131
|
+
modifiedValues?: EntityValues<M>;
|
|
132
|
+
formContext: FormContext<M>;
|
|
133
|
+
parentCollectionSlugs?: string[];
|
|
134
|
+
parentEntityIds?: string[];
|
|
135
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Entity } from "@rebasepro/types";
|
|
2
|
+
import type { User } from "@rebasepro/types";
|
|
3
|
+
import type { RebaseContext } from "../rebase_context";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* You can use this configuration to add additional fields to the data
|
|
7
|
+
* exports
|
|
8
|
+
* @group Models
|
|
9
|
+
*/
|
|
10
|
+
export interface ExportConfig<USER extends User = User> {
|
|
11
|
+
additionalFields: ExportMappingFunction<USER>[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @group Models
|
|
16
|
+
*/
|
|
17
|
+
export interface ExportMappingFunction<USER extends User = User> {
|
|
18
|
+
key: string;
|
|
19
|
+
builder: ({
|
|
20
|
+
entity,
|
|
21
|
+
context
|
|
22
|
+
}: {
|
|
23
|
+
entity: Entity,
|
|
24
|
+
context: RebaseContext<USER>
|
|
25
|
+
}) => Promise<string> | string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { ColumnKey } from "../admin_collection";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The number of columns the form grid is divided into. A field's
|
|
5
|
+
* {@link AdminPropertyOptions.span} is expressed against this.
|
|
6
|
+
*
|
|
7
|
+
* Fixed rather than configurable on purpose: the whole point of a span is that
|
|
8
|
+
* two fields written by two different people line up, and they only do that if
|
|
9
|
+
* everyone is counting against the same grid.
|
|
10
|
+
*
|
|
11
|
+
* @group Models
|
|
12
|
+
*/
|
|
13
|
+
export const FORM_GRID_COLUMNS = 4;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* How wide a field sits on the {@link FORM_GRID_COLUMNS}-column form grid.
|
|
17
|
+
*
|
|
18
|
+
* `4` is the full width of the main column. A field always takes at least a
|
|
19
|
+
* whole row on narrow layouts (the side panel, the split pane, mobile), where
|
|
20
|
+
* the grid collapses to one column and spans are ignored.
|
|
21
|
+
*
|
|
22
|
+
* @group Entity properties
|
|
23
|
+
*/
|
|
24
|
+
export type PropertySpan = 1 | 2 | 3 | 4;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A titled group of fields in the main column of the form.
|
|
28
|
+
*
|
|
29
|
+
* @group Models
|
|
30
|
+
*/
|
|
31
|
+
export interface FormSection<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
32
|
+
/**
|
|
33
|
+
* Stable identity for this section. Used as the React key and to remember
|
|
34
|
+
* the collapsed state across visits, so renaming `title` does not lose it.
|
|
35
|
+
*/
|
|
36
|
+
key: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Shown above the group. A section with no title renders its fields with no
|
|
40
|
+
* heading and no rule — useful for the first group, which rarely needs one.
|
|
41
|
+
*/
|
|
42
|
+
title?: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Property and additional-field keys in this section, in render order.
|
|
46
|
+
*
|
|
47
|
+
* Keys naming a property that does not exist, is hidden, or has been routed
|
|
48
|
+
* to {@link FormLayoutConfig.sidebar} are skipped. Any property *not* named
|
|
49
|
+
* by a section lands in the last section that has no explicit title, or in
|
|
50
|
+
* an untitled trailing group if there is none — a new column is never
|
|
51
|
+
* silently dropped from the form.
|
|
52
|
+
*/
|
|
53
|
+
properties: ColumnKey<M>[];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Start collapsed. Defaults to `false`.
|
|
57
|
+
*
|
|
58
|
+
* Only meaningful when the section can be collapsed at all; a section with
|
|
59
|
+
* no `title` has nothing to click, so this is ignored there.
|
|
60
|
+
*/
|
|
61
|
+
collapsed?: boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Can the user collapse this section. Defaults to `true` for a titled
|
|
65
|
+
* section, `false` for an untitled one.
|
|
66
|
+
*
|
|
67
|
+
* A section holding a required field is still collapsible — but a
|
|
68
|
+
* validation error inside a collapsed section expands it, so an error can
|
|
69
|
+
* never hide.
|
|
70
|
+
*/
|
|
71
|
+
collapsible?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* How this section arranges itself in the **read-only** view of a record.
|
|
75
|
+
* Defaults to `"grid"` — the same grid the form uses.
|
|
76
|
+
*
|
|
77
|
+
* `"summary"` stacks the fields as right-aligned label/value rows with the
|
|
78
|
+
* last one emphasised, which is what a run of related figures wants: a
|
|
79
|
+
* subtotal, a tax, a discount and a total are one calculation, and four
|
|
80
|
+
* equal cells on a four-column grid is the one arrangement that says they
|
|
81
|
+
* are unrelated. Opt in per section — nothing about a group of numbers tells
|
|
82
|
+
* us it adds up, so this is never derived.
|
|
83
|
+
*
|
|
84
|
+
* Read-only only, and named for it. The form goes on rendering the grid:
|
|
85
|
+
* a summary row is a reading arrangement, and shrinking a control to fit one
|
|
86
|
+
* would make the fields harder to edit to make them prettier to skim.
|
|
87
|
+
*/
|
|
88
|
+
readVariant?: "grid" | "summary";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* How the generated form is laid out.
|
|
93
|
+
*
|
|
94
|
+
* Everything here is optional, and the defaults are the point: with no config
|
|
95
|
+
* at all the layout is derived from the properties themselves —
|
|
96
|
+
*
|
|
97
|
+
* - the id and the `createdAt`/`updatedAt` timestamps go to the rail, read-only
|
|
98
|
+
* - short enums, booleans, dates and numbers take a narrow span
|
|
99
|
+
* - long text, markdown, arrays, maps and storage fields take the full width
|
|
100
|
+
* - everything else takes half
|
|
101
|
+
*
|
|
102
|
+
* so a collection that never mentions `form` still gets a two-column layout
|
|
103
|
+
* rather than one flat run of full-width fields. Use this block when the
|
|
104
|
+
* derived answer is wrong for your domain.
|
|
105
|
+
*
|
|
106
|
+
* @group Models
|
|
107
|
+
*/
|
|
108
|
+
export interface FormLayoutConfig<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
109
|
+
/**
|
|
110
|
+
* Property keys shown in the metadata rail beside the main column instead
|
|
111
|
+
* of in it — status, ownership, publication dates, flags.
|
|
112
|
+
*
|
|
113
|
+
* The rail is narrow and does not use the grid, so `span` is ignored for
|
|
114
|
+
* these. On layouts too narrow for a rail (the side panel, the split pane,
|
|
115
|
+
* mobile) they render as an ordinary leading section, so nothing is lost.
|
|
116
|
+
*
|
|
117
|
+
* Set to `[]` to suppress the derived rail entirely and keep every field in
|
|
118
|
+
* the main column.
|
|
119
|
+
*/
|
|
120
|
+
sidebar?: ColumnKey<M>[];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Groups for the main column. When omitted, every field lands in a single
|
|
124
|
+
* untitled group, which is the pre-existing behaviour.
|
|
125
|
+
*/
|
|
126
|
+
sections?: FormSection<M>[];
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Show the read-only record block (id, created, updated) at the foot of the
|
|
130
|
+
* rail. Defaults to `true` when a rail is shown.
|
|
131
|
+
*
|
|
132
|
+
* This is what replaces `hideIdFromForm` for most collections: the id stops
|
|
133
|
+
* being a field in the middle of the form and becomes a copyable line of
|
|
134
|
+
* metadata.
|
|
135
|
+
*/
|
|
136
|
+
showRecordMeta?: boolean;
|
|
137
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { FormEvent } from "react";
|
|
2
|
+
|
|
3
|
+
export type FormexController<T = unknown> = {
|
|
4
|
+
values: T;
|
|
5
|
+
initialValues: T;
|
|
6
|
+
setValues: (values: T) => void;
|
|
7
|
+
setFieldValue: (key: string, value: unknown, shouldValidate?: boolean) => void;
|
|
8
|
+
touched: Record<string, boolean>;
|
|
9
|
+
setFieldTouched: (key: string, touched: boolean, shouldValidate?: boolean) => void;
|
|
10
|
+
setTouched: (touched: Record<string, boolean>) => void;
|
|
11
|
+
dirty: boolean;
|
|
12
|
+
setDirty: (dirty: boolean) => void;
|
|
13
|
+
setSubmitCount: (submitCount: number) => void;
|
|
14
|
+
errors: Record<string, string>;
|
|
15
|
+
setFieldError: (key: string, error?: string) => void;
|
|
16
|
+
handleChange: (event: React.SyntheticEvent) => void,
|
|
17
|
+
handleBlur: (event: React.FocusEvent) => void,
|
|
18
|
+
handleSubmit: (event?: FormEvent<HTMLFormElement>) => void;
|
|
19
|
+
validate: () => void;
|
|
20
|
+
resetForm: (props?: FormexResetProps<T>) => void;
|
|
21
|
+
submitCount: number;
|
|
22
|
+
isSubmitting: boolean;
|
|
23
|
+
setSubmitting: (isSubmitting: boolean) => void;
|
|
24
|
+
isValidating: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The version of the form. This is incremented every time the form is reset
|
|
27
|
+
* or the form is submitted.
|
|
28
|
+
*/
|
|
29
|
+
version: number;
|
|
30
|
+
|
|
31
|
+
debugId?: string;
|
|
32
|
+
|
|
33
|
+
undo: () => void;
|
|
34
|
+
redo: () => void;
|
|
35
|
+
|
|
36
|
+
canUndo: boolean;
|
|
37
|
+
canRedo: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type FormexResetProps<T = unknown> = {
|
|
41
|
+
values?: T;
|
|
42
|
+
submitCount?: number;
|
|
43
|
+
errors?: Record<string, string>;
|
|
44
|
+
touched?: Record<string, boolean>;
|
|
45
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from "./breadcrumbs";
|
|
2
|
+
export * from "./builders";
|
|
3
|
+
export * from "./collection_views";
|
|
4
|
+
export * from "./component_overrides";
|
|
5
|
+
export * from "./entity_actions";
|
|
6
|
+
export * from "./entity_link_builder";
|
|
7
|
+
export * from "./entity_display";
|
|
8
|
+
export * from "./entity_views";
|
|
9
|
+
export * from "./export_import";
|
|
10
|
+
export * from "./form_layout";
|
|
11
|
+
export * from "./formex";
|
|
12
|
+
export * from "./locales";
|
|
13
|
+
export * from "./modify_collections";
|
|
14
|
+
export * from "./plugins";
|
|
15
|
+
export * from "./property_config";
|
|
16
|
+
export * from "./slots";
|
|
17
|
+
export * from "./translations";
|
|
18
|
+
export * from "./user_management_delegate";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @group Models
|
|
3
|
+
*/
|
|
4
|
+
export type Locale =
|
|
5
|
+
"af" |
|
|
6
|
+
"ar" |
|
|
7
|
+
"arDZ" |
|
|
8
|
+
"arMA" |
|
|
9
|
+
"arSA" |
|
|
10
|
+
"az" |
|
|
11
|
+
"be" |
|
|
12
|
+
"bg" |
|
|
13
|
+
"bn" |
|
|
14
|
+
"ca" |
|
|
15
|
+
"cs" |
|
|
16
|
+
"cy" |
|
|
17
|
+
"da" |
|
|
18
|
+
"de" |
|
|
19
|
+
"el" |
|
|
20
|
+
"enAU" |
|
|
21
|
+
"enCA" |
|
|
22
|
+
"enGB" |
|
|
23
|
+
"enIN" |
|
|
24
|
+
"enNZ" |
|
|
25
|
+
"enUS" |
|
|
26
|
+
"eo" |
|
|
27
|
+
"es" |
|
|
28
|
+
"et" |
|
|
29
|
+
"eu" |
|
|
30
|
+
"faIR" |
|
|
31
|
+
"fi" |
|
|
32
|
+
"fil" |
|
|
33
|
+
"fr" |
|
|
34
|
+
"frCA" |
|
|
35
|
+
"frCH" |
|
|
36
|
+
"gd" |
|
|
37
|
+
"gl" |
|
|
38
|
+
"gu" |
|
|
39
|
+
"he" |
|
|
40
|
+
"hi" |
|
|
41
|
+
"hr" |
|
|
42
|
+
"hu" |
|
|
43
|
+
"hy" |
|
|
44
|
+
"id" |
|
|
45
|
+
"is" |
|
|
46
|
+
"it" |
|
|
47
|
+
"ja" |
|
|
48
|
+
"ka" |
|
|
49
|
+
"kk" |
|
|
50
|
+
"kn" |
|
|
51
|
+
"ko" |
|
|
52
|
+
"lb" |
|
|
53
|
+
"lt" |
|
|
54
|
+
"lv" |
|
|
55
|
+
"mk" |
|
|
56
|
+
"ms" |
|
|
57
|
+
"mt" |
|
|
58
|
+
"nb" |
|
|
59
|
+
"nl" |
|
|
60
|
+
"nlBE" |
|
|
61
|
+
"nn" |
|
|
62
|
+
"pl" |
|
|
63
|
+
"pt" |
|
|
64
|
+
"ptBR" |
|
|
65
|
+
"ro" |
|
|
66
|
+
"ru" |
|
|
67
|
+
"sk" |
|
|
68
|
+
"sl" |
|
|
69
|
+
"sr" |
|
|
70
|
+
"srLatn" |
|
|
71
|
+
"sv" |
|
|
72
|
+
"ta" |
|
|
73
|
+
"te" |
|
|
74
|
+
"th" |
|
|
75
|
+
"tr" |
|
|
76
|
+
"ug" |
|
|
77
|
+
"uk" |
|
|
78
|
+
"uz" |
|
|
79
|
+
"vi" |
|
|
80
|
+
"zhCN" |
|
|
81
|
+
"zhTW";
|