@object-ui/plugin-form 17.6.0 → 17.7.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/CHANGELOG.md +4187 -0
- package/README.md +376 -44
- package/dist/DrawerForm.d.ts +30 -2
- package/dist/DrawerForm.d.ts.map +1 -1
- package/dist/EmbeddableForm.d.ts.map +1 -1
- package/dist/FormAnalytics.d.ts.map +1 -1
- package/dist/LineItemsPanel.d.ts +6 -3
- package/dist/LineItemsPanel.d.ts.map +1 -1
- package/dist/MasterDetailForm.d.ts +31 -10
- package/dist/MasterDetailForm.d.ts.map +1 -1
- package/dist/ModalForm.d.ts +48 -4
- package/dist/ModalForm.d.ts.map +1 -1
- package/dist/ObjectForm.d.ts.map +1 -1
- package/dist/SplitForm.d.ts +37 -2
- package/dist/SplitForm.d.ts.map +1 -1
- package/dist/TabbedForm.d.ts +65 -3
- package/dist/TabbedForm.d.ts.map +1 -1
- package/dist/WizardForm.d.ts +143 -38
- package/dist/WizardForm.d.ts.map +1 -1
- package/dist/autoLayout.d.ts +12 -3
- package/dist/autoLayout.d.ts.map +1 -1
- package/dist/closedAffordanceNotice.d.ts +14 -0
- package/dist/closedAffordanceNotice.d.ts.map +1 -0
- package/dist/customFieldsMerge.d.ts +29 -0
- package/dist/customFieldsMerge.d.ts.map +1 -0
- package/dist/deriveMasterDetail.d.ts +32 -8
- package/dist/deriveMasterDetail.d.ts.map +1 -1
- package/dist/fieldGroups.d.ts +176 -0
- package/dist/fieldGroups.d.ts.map +1 -1
- package/dist/fieldWriteGate.d.ts +116 -0
- package/dist/fieldWriteGate.d.ts.map +1 -0
- package/dist/flatFields.d.ts +11 -2
- package/dist/flatFields.d.ts.map +1 -1
- package/dist/formChrome.d.ts +55 -0
- package/dist/formChrome.d.ts.map +1 -0
- package/dist/index.d.ts +124 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3435 -2093
- package/dist/index.umd.cjs +1 -1
- package/dist/initialRecord.d.ts +72 -0
- package/dist/initialRecord.d.ts.map +1 -0
- package/dist/loadFailure.d.ts +28 -0
- package/dist/loadFailure.d.ts.map +1 -0
- package/dist/masterDetailTx.d.ts.map +1 -1
- package/dist/occSave.d.ts +6 -1
- package/dist/occSave.d.ts.map +1 -1
- package/dist/recordInvalidation.d.ts +33 -0
- package/dist/recordInvalidation.d.ts.map +1 -0
- package/dist/recordReadGate.d.ts +34 -0
- package/dist/recordReadGate.d.ts.map +1 -0
- package/dist/sanitize.d.ts +187 -5
- package/dist/sanitize.d.ts.map +1 -1
- package/dist/schemaDefaults.d.ts +27 -6
- package/dist/schemaDefaults.d.ts.map +1 -1
- package/dist/sectionFields.d.ts +56 -2
- package/dist/sectionFields.d.ts.map +1 -1
- package/dist/sectionGroups.d.ts +62 -0
- package/dist/sectionGroups.d.ts.map +1 -0
- package/dist/sectionPredicateDiagnostic.d.ts +38 -0
- package/dist/sectionPredicateDiagnostic.d.ts.map +1 -0
- package/dist/submitTarget.d.ts +119 -0
- package/dist/submitTarget.d.ts.map +1 -0
- package/dist/successBehavior.d.ts +44 -25
- package/dist/successBehavior.d.ts.map +1 -1
- package/dist/thankYouRedirectCountdown.d.ts +1 -1
- package/dist/uploadGate.d.ts +35 -0
- package/dist/uploadGate.d.ts.map +1 -0
- package/dist/writePayload.d.ts +68 -0
- package/dist/writePayload.d.ts.map +1 -0
- package/package.json +12 -11
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ObjectFormSection } from '@object-ui/types';
|
|
2
|
+
/**
|
|
3
|
+
* The keys `@objectstack/spec` REFUSES beside `group`, measured against
|
|
4
|
+
* `FormSectionSchema` in `@objectstack/spec` 17.3.0 and pinned in
|
|
5
|
+
* `__tests__/formSectionGroupReference-7051.test.tsx`.
|
|
6
|
+
*
|
|
7
|
+
* There are no override semantics: each of these is something the object's
|
|
8
|
+
* `fieldGroups` entry declares, so restating one on the referencing section
|
|
9
|
+
* would be a second writable spelling of one fact. Authored metadata carrying
|
|
10
|
+
* one never reaches this renderer (the spec door rejects it); a programmatic
|
|
11
|
+
* SDUI caller, which does not pass that door, is reported instead of quietly
|
|
12
|
+
* having its key honoured or quietly having it dropped.
|
|
13
|
+
*/
|
|
14
|
+
export declare const GROUP_OWNED_SECTION_KEYS: readonly ["name", "label", "description", "collapsible", "collapsed", "visibleWhen"];
|
|
15
|
+
/**
|
|
16
|
+
* The keys the spec PERMITS beside `group`. They describe how THIS form lays
|
|
17
|
+
* the section out, which is the form's business and not the group's, so the
|
|
18
|
+
* authored value wins over the derived section.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SECTION_LAYOUT_KEYS: readonly ["columns", "pane"];
|
|
21
|
+
/** Test seam — forget which section-group diagnostics have been reported. */
|
|
22
|
+
export declare function resetSectionGroupReports(): void;
|
|
23
|
+
/** Does any section in this list use the reference form? */
|
|
24
|
+
export declare function hasSectionGroupReference(sections: unknown): boolean;
|
|
25
|
+
export interface ResolveSectionGroupsOptions {
|
|
26
|
+
/** Object name — diagnostics say WHICH object is missing the group. */
|
|
27
|
+
objectName: string;
|
|
28
|
+
/** The form's layout. `wizard` is the one the spec bans `group` on. */
|
|
29
|
+
formType?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The object definition (`{ fields, fieldGroups }`), or `null`/`undefined`
|
|
32
|
+
* while it is still loading. A `group` section is rendered EMPTY until it
|
|
33
|
+
* arrives — never dropped, see the note on the return value.
|
|
34
|
+
*/
|
|
35
|
+
objectDef: any;
|
|
36
|
+
/**
|
|
37
|
+
* `false` when this form can never load an object definition (no
|
|
38
|
+
* DataSource), so an unresolvable reference is reported as such instead of
|
|
39
|
+
* being left to look like a load that never finished.
|
|
40
|
+
*/
|
|
41
|
+
resolvable?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Replace every `{ group }` section with the section that group declares.
|
|
45
|
+
*
|
|
46
|
+
* Returns the SAME array reference when nothing uses the reference form, so a
|
|
47
|
+
* caller can keep this in a `useMemo` without perturbing any existing form.
|
|
48
|
+
*
|
|
49
|
+
* ⚠️ A reference that cannot be resolved yields `{ fields: [] }` — an empty
|
|
50
|
+
* section — and NOT a dropped one. Both render nothing, which is what the spec
|
|
51
|
+
* assigns to an unresolvable reference (`deriveFieldGroupLayout` already drops
|
|
52
|
+
* a declared group nothing references, and `@objectstack/lint`'s
|
|
53
|
+
* `form-section-group-unknown` is the declared reporter of a dangling key).
|
|
54
|
+
* The choice between them is forced by THIS surface and was measured: a form
|
|
55
|
+
* whose `sections` array empties out stops being a sectioned form at all and
|
|
56
|
+
* falls back to the flat every-field layout, so dropping would make a single
|
|
57
|
+
* mistyped key render MORE than the author asked for. Emptying renders
|
|
58
|
+
* exactly nothing, and leaves the array length — which is the wizard's step
|
|
59
|
+
* count and the container routing's condition — untouched.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveSectionGroupReferences(sections: ObjectFormSection[] | undefined, opts: ResolveSectionGroupsOptions): ObjectFormSection[] | undefined;
|
|
62
|
+
//# sourceMappingURL=sectionGroups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sectionGroups.d.ts","sourceRoot":"","sources":["../src/sectionGroups.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,sFAO3B,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,8BAA+B,CAAC;AAWhE,6EAA6E;AAC7E,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED,4DAA4D;AAC5D,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAKnE;AAcD,MAAM,WAAW,2BAA2B;IAC1C,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,EAAE,GAAG,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,iBAAiB,EAAE,GAAG,SAAS,EACzC,IAAI,EAAE,2BAA2B,GAChC,iBAAiB,EAAE,GAAG,SAAS,CAyEjC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The one layout arm that still DROPS an authored `FormSection.visibleWhen`
|
|
10
|
+
* (objectui#6237).
|
|
11
|
+
*
|
|
12
|
+
* Of the six layout routes in `ObjectForm`, five honour the predicate:
|
|
13
|
+
* `split` / `drawer` / `modal` rebuild each section key by key and copy it
|
|
14
|
+
* (#6111), the flat arm carries it on the `section-divider` pseudo-field
|
|
15
|
+
* (#6236), and `tabbed` joined them by copying it onto the tab `TabbedForm`
|
|
16
|
+
* synthesises, where the renderer evaluates it (#6237). `wizard` is the
|
|
17
|
+
* remainder.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ The wizard's gap is a DESIGN boundary, not an oversight, and it is not one
|
|
20
|
+
* copy line away. A step predicate would be step-boundary reactive against the
|
|
21
|
+
* ruled live-record reactivity, and it needs navigation, indicator, final-gate
|
|
22
|
+
* and re-selection semantics that the tab arm's machinery does not supply —
|
|
23
|
+
* `WizardStepConfig` carries the full measurement. Until that contract is ruled,
|
|
24
|
+
* an author who writes the key on a wizard is told so rather than watching it do
|
|
25
|
+
* nothing.
|
|
26
|
+
*
|
|
27
|
+
* Since objectstack#13622 (ruled 2026-08-31) the boundary is ALSO enforced at
|
|
28
|
+
* the author door: `@objectstack/spec` refuses `visibleWhen` on a wizard step
|
|
29
|
+
* at parse, so authored form-view metadata can no longer arrive here carrying
|
|
30
|
+
* it. This report is deliberately KEPT (Card R of that ruling): programmatic
|
|
31
|
+
* SDUI callers do not pass the spec door, and metadata published before the
|
|
32
|
+
* tightening still round-trips — for both, this warning is the only signal.
|
|
33
|
+
*
|
|
34
|
+
* Single-sourced so the runtime report and the pin that holds its wording cannot
|
|
35
|
+
* drift apart.
|
|
36
|
+
*/
|
|
37
|
+
export declare function sectionPredicateUnsupportedWarning(layout: 'wizard', sectionNames: string): string;
|
|
38
|
+
//# sourceMappingURL=sectionPredicateDiagnostic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sectionPredicateDiagnostic.d.ts","sourceRoot":"","sources":["../src/sectionPredicateDiagnostic.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,QAAQ,EAChB,YAAY,EAAE,MAAM,GACnB,MAAM,CASR"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Does a form have anything to submit TO? — the ONE answer for every renderer
|
|
10
|
+
* `ObjectForm` routes to (objectui#6300).
|
|
11
|
+
*
|
|
12
|
+
* ## What went wrong
|
|
13
|
+
*
|
|
14
|
+
* Each of the five variant containers opened `handleSubmit` with
|
|
15
|
+
*
|
|
16
|
+
* if (!dataSource) { await schema.onSuccess?.(data); return data; }
|
|
17
|
+
*
|
|
18
|
+
* — a SUCCESS signal emitted without consulting `submitHandler` and without
|
|
19
|
+
* persisting anything. Measured on `main` with a `submitHandler` supplied and no
|
|
20
|
+
* `dataSource`, all five read `onSuccess 1 / submitHandler 0`. Through
|
|
21
|
+
* `MasterDetailForm` — whose parent schema declares BOTH
|
|
22
|
+
* `submitHandler: submitViaBatch` and `onSuccess: handleSaved` — that early
|
|
23
|
+
* return reached `handleSaved` directly: a success toast (measured:
|
|
24
|
+
* `toast.success("Created")`) and, in create mode, a form reset clearing the
|
|
25
|
+
* line items nobody wrote. Silent data loss in the worst direction, because the
|
|
26
|
+
* submitter is told it worked.
|
|
27
|
+
*
|
|
28
|
+
* ## The rule this module carries
|
|
29
|
+
*
|
|
30
|
+
* A form has a submit target when it has EITHER a `dataSource` (it writes) or a
|
|
31
|
+
* declared `submitHandler` (the host writes — objectui#6176's seam, which needs
|
|
32
|
+
* no adapter of its own). With neither, exactly one shape is still legitimate:
|
|
33
|
+
* fields authored INLINE, where the author's `onSuccess` IS the write. Anything
|
|
34
|
+
* else must refuse, loudly, instead of confirming.
|
|
35
|
+
*
|
|
36
|
+
* ## What counts as inline, and why it has two limbs
|
|
37
|
+
*
|
|
38
|
+
* Neither limb is a new predicate. Both name a shape this package already
|
|
39
|
+
* documents as working with no adapter, and the test refuses everything else.
|
|
40
|
+
*
|
|
41
|
+
* **(a) `customFields` is non-empty.** `SimpleObjectForm` (`ObjectForm.tsx`)
|
|
42
|
+
* gates its own carve-out on exactly this, the `object-form` element gate
|
|
43
|
+
* (`index.tsx`) declares `requiresDataSource={!(schema?.customFields?.length > 0)
|
|
44
|
+
* && …}` and says so in as many words — *"The one escape hatch is inline
|
|
45
|
+
* `customFields`, which is exactly what `hasInlineFields` gates on inside the
|
|
46
|
+
* component, so the two stay in step"* — and `ObjectFormSchema` glosses the key
|
|
47
|
+
* as *"when used with inline field definitions (without dataSource), this
|
|
48
|
+
* becomes the primary field source"*.
|
|
49
|
+
*
|
|
50
|
+
* **(b) every section field is an inline runtime `FormField`.** This limb is not
|
|
51
|
+
* optional and not an invention: it is how a sectioned form expresses the same
|
|
52
|
+
* thing with no `customFields` at all — its sections carry the definitions.
|
|
53
|
+
* This package's README documents the shape and ships an example of
|
|
54
|
+
* it, `<WizardForm schema={wizard} />` under the comment *"dataSource omitted:
|
|
55
|
+
* every step lists inline fields"*, introduced by *"The inline shape is what
|
|
56
|
+
* lets a wizard run with no data source at all"*. Measured before this module
|
|
57
|
+
* existed: that example renders and submits. A `customFields`-only test would
|
|
58
|
+
* have made the repo's own published example throw — which is why the limb is
|
|
59
|
+
* here.
|
|
60
|
+
*
|
|
61
|
+
* The limb is deliberately ALL-or-nothing, and it is the taxonomy
|
|
62
|
+
* `normalizeSectionField` already keeps: a section field is a name string
|
|
63
|
+
* (shape 1), a spec `FormFieldSchema` whose identity key `field` is a STRING
|
|
64
|
+
* (shape 2), or an inline runtime `FormField` carrying its own `name` (shape 3).
|
|
65
|
+
* Only shape 3 is self-describing; shapes 1 and 2 name fields that only an
|
|
66
|
+
* object schema — and therefore only an adapter — can resolve. So one bare name
|
|
67
|
+
* anywhere in the sections means the form DID need metadata it could not get,
|
|
68
|
+
* and it refuses. Erring that way is the loud direction; the silent one is the
|
|
69
|
+
* defect.
|
|
70
|
+
*
|
|
71
|
+
* `TabbedForm` / `SplitForm` / `WizardForm` receive `customFields` through
|
|
72
|
+
* `ObjectForm`'s `{...schema}` spread. Until objectui#10254 they neither
|
|
73
|
+
* declared nor rendered it, and limb (a) read it there only as a SIGNAL; they
|
|
74
|
+
* now declare it and draw a member a section names, as the drawer and modal do.
|
|
75
|
+
*/
|
|
76
|
+
/** The shape this module reads — deliberately narrower than any form schema. */
|
|
77
|
+
export interface InlineFieldSource {
|
|
78
|
+
customFields?: unknown;
|
|
79
|
+
sections?: unknown;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether the author declared the form's fields inline, i.e. the form is a
|
|
83
|
+
* self-contained collector whose `onSuccess` is the write.
|
|
84
|
+
*
|
|
85
|
+
* Literally `SimpleObjectForm`'s test, kept in one place so the six renderers
|
|
86
|
+
* cannot answer it six ways.
|
|
87
|
+
*
|
|
88
|
+
* The parameter is `object`, not `InlineFieldSource`: TypeScript's weak-type
|
|
89
|
+
* check rejects an argument sharing no property with a wholly-optional
|
|
90
|
+
* interface, so a caller whose schema type declares neither key could not pass
|
|
91
|
+
* it. `TabbedFormSchema` / `SplitFormSchema` / `WizardFormSchema` declared no
|
|
92
|
+
* `customFields` while they did not render it, since declaring a field source a
|
|
93
|
+
* renderer ignores is the declared-≠-enforced shape objectui#6300 set out to
|
|
94
|
+
* undo; they declare it since objectui#10254, which made them render it.
|
|
95
|
+
*/
|
|
96
|
+
export declare function hasInlineFieldSource(schema: object | null | undefined): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The refusal message. `SimpleObjectForm`'s own wording, verbatim, so a host
|
|
99
|
+
* that already matches on it sees one string from all six renderers.
|
|
100
|
+
*/
|
|
101
|
+
export declare const NO_SUBMIT_TARGET_MESSAGE = "DataSource is required for form submission (inline mode not configured)";
|
|
102
|
+
/**
|
|
103
|
+
* The refusal itself.
|
|
104
|
+
*
|
|
105
|
+
* Thrown from INSIDE each container's persistence chain — as the branch taken
|
|
106
|
+
* when none of the real routes (host seam / create / OCC-guarded edit) applies
|
|
107
|
+
* — rather than from a pre-`try` guard, for two reasons:
|
|
108
|
+
*
|
|
109
|
+
* 1. it is where the fact belongs: "there is no route to persist by" is a
|
|
110
|
+
* verdict about the persistence chain, and expressing it there is also what
|
|
111
|
+
* lets TypeScript narrow `dataSource` for the routes that use it, with no
|
|
112
|
+
* assertion;
|
|
113
|
+
* 2. the container's `catch` runs `schema.onError?.(err)` and RETHROWS, so the
|
|
114
|
+
* host learns why. That is what turns the `MasterDetailForm` reading above
|
|
115
|
+
* from `toast.success("Created")` into `toast.error(<this message>)` with
|
|
116
|
+
* the form left intact — the visible half of the fix.
|
|
117
|
+
*/
|
|
118
|
+
export declare function noSubmitTargetError(): Error;
|
|
119
|
+
//# sourceMappingURL=submitTarget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submitTarget.d.ts","sourceRoot":"","sources":["../src/submitTarget.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAEH,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAkCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAI/E;AAED;;;GAGG;AACH,eAAO,MAAM,wBAAwB,4EACsC,CAAC;AAE5E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,IAAI,KAAK,CAE3C"}
|
|
@@ -9,34 +9,53 @@
|
|
|
9
9
|
* for another entry (`resetOnSuccess`).
|
|
10
10
|
*
|
|
11
11
|
* Kept dependency-free on purpose: importing the redirect guard from
|
|
12
|
-
* EmbeddableForm would create a cycle (EmbeddableForm
|
|
13
|
-
*
|
|
12
|
+
* EmbeddableForm would create a cycle (EmbeddableForm -> ObjectForm ->
|
|
13
|
+
* WizardForm -> here), so the destination test below is local to this module.
|
|
14
14
|
*/
|
|
15
15
|
export type { SubmitBehavior } from '@object-ui/types';
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
17
|
+
* Resolve a `navigateOnSuccess` template into the relative destination to
|
|
18
|
+
* navigate to, or refuse it. Returns null when there is no template, no usable
|
|
19
|
+
* id, or the resolved value is out of contract — callers then show the success
|
|
20
|
+
* toast carrying `NAVIGATE_ON_SUCCESS_REFUSED_NOTE`.
|
|
21
|
+
*
|
|
22
|
+
* ## The contract (maintainer ruling, 2026-08-17, objectui#5034)
|
|
23
|
+
*
|
|
24
|
+
* `navigateOnSuccess` is the pre-ruling ancestor of the `submitBehavior` family,
|
|
25
|
+
* not a second dialect, and it is **deprecated in favour of `submitBehavior`**
|
|
26
|
+
* (which already takes precedence over it, pinned). As a compat alias it runs
|
|
27
|
+
* under the semantics objectstack#7496 ruled for that family:
|
|
28
|
+
*
|
|
29
|
+
* 1. **relative paths only.** A same-origin ABSOLUTE value is refused like any
|
|
30
|
+
* other out-of-contract value — not routed differently, refused. The
|
|
31
|
+
* destination is authored metadata, which is exactly where an address
|
|
32
|
+
* somebody else chose gets copied in.
|
|
33
|
+
* 2. **the interpolated value is URL-escaped** when the destination is built.
|
|
34
|
+
* 3. the `{id}` / `{recordId}` dialect **stays** for existing authors of this
|
|
35
|
+
* key. It is the one part deliberately not converged: the ruling keeps the
|
|
36
|
+
* spelling working and points new authoring at `submitBehavior` instead.
|
|
37
|
+
*
|
|
38
|
+
* ## Why both halves are needed, and neither substitutes for the other
|
|
39
|
+
*
|
|
40
|
+
* The escape is what stops a token from adding path STRUCTURE. Interpolated raw,
|
|
41
|
+
* `{id}` with an address in it becomes the whole destination, and `/r/{id}` with
|
|
42
|
+
* `a/b` in it silently grows a path segment. Relative-only is a rule about where
|
|
43
|
+
* a destination STARTS, so it has nothing to say about structure injected
|
|
44
|
+
* further along — the argument `submitRedirect.ts` records for the ruled sibling,
|
|
45
|
+
* which holds here verbatim.
|
|
46
|
+
*
|
|
47
|
+
* Conversely the escape alone would not refuse an authored absolute, because the
|
|
48
|
+
* template is the AUTHOR's and only the substituted value passes through
|
|
49
|
+
* `encodeURIComponent`. So: escape the data, judge the result.
|
|
50
|
+
*
|
|
51
|
+
* ## This can only narrow what is reachable
|
|
52
|
+
*
|
|
53
|
+
* Every destination this returns is a relative reference, and every relative
|
|
54
|
+
* reference was already accepted by the same-origin guard it replaces (a
|
|
55
|
+
* relative reference cannot carry an authority, RFC 3986, so it always resolves
|
|
56
|
+
* to the current origin). Escaping only ever maps an id onto one opaque segment.
|
|
57
|
+
* So the set of addresses reachable through this key is a strict subset of the
|
|
58
|
+
* set reachable before — there is no value that was refused and is now followed.
|
|
40
59
|
*/
|
|
41
60
|
export declare function resolveSuccessNavigate(template: string | undefined, record: any): string | null;
|
|
42
61
|
//# sourceMappingURL=successBehavior.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"successBehavior.d.ts","sourceRoot":"","sources":["../src/successBehavior.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"successBehavior.d.ts","sourceRoot":"","sources":["../src/successBehavior.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA0DvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,GAAG,GACV,MAAM,GAAG,IAAI,CAWf"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PendingThankYouRedirect } from './thankYouRedirectNavigation';
|
|
1
|
+
import { PendingThankYouRedirect } from './thankYouRedirectNavigation.js';
|
|
2
2
|
/** One tick per displayed second — matches the `{{seconds}}` unit the copy promises. */
|
|
3
3
|
export declare const REDIRECT_COUNTDOWN_TICK_MS = 1000;
|
|
4
4
|
/**
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { useUploadingScope } from '@object-ui/fields';
|
|
3
|
+
export interface UploadGate {
|
|
4
|
+
/** True while at least one upload inside this form is still in flight. */
|
|
5
|
+
uploading: boolean;
|
|
6
|
+
/** Mount around the form body so its upload widgets report in. */
|
|
7
|
+
scope: ReturnType<typeof useUploadingScope>;
|
|
8
|
+
/** The Save button's label while `uploading`. */
|
|
9
|
+
busyLabel: string;
|
|
10
|
+
/** Why Save is unavailable — shown, and used as the refusal message. */
|
|
11
|
+
reason: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Own the gate for one record form. The host renders its form body inside
|
|
15
|
+
* {@link UploadGateProvider} with the returned `scope`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useUploadGate(): UploadGate;
|
|
18
|
+
/** Re-exported under this package's own name so hosts import one module. */
|
|
19
|
+
export declare function UploadGateProvider({ gate, children, }: {
|
|
20
|
+
gate: UploadGate;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
}): React.ReactElement;
|
|
23
|
+
/**
|
|
24
|
+
* The visible reason. Rendered beside the action row, not as a toast: the
|
|
25
|
+
* explanation has to be readable at the moment the user looks at the control
|
|
26
|
+
* that will not respond, and a toast is gone by then.
|
|
27
|
+
*
|
|
28
|
+
* `role="status"` (polite) rather than `alert`: nothing has gone wrong, an
|
|
29
|
+
* upload is simply still running, and an assertive live region would interrupt
|
|
30
|
+
* a screen-reader user mid-sentence every time a file is picked.
|
|
31
|
+
*/
|
|
32
|
+
export declare function UploadInFlightNotice({ gate }: {
|
|
33
|
+
gate: UploadGate;
|
|
34
|
+
}): React.ReactElement | null;
|
|
35
|
+
//# sourceMappingURL=uploadGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadGate.d.ts","sourceRoot":"","sources":["../src/uploadGate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,iBAAiB,EAA0B,MAAM,mBAAmB,CAAC;AAgD9E,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,KAAK,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IAC5C,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,UAAU,CAS1C;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,GAAG,KAAK,CAAC,YAAY,CAErB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAO9F"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LoadedRecordSnapshot } from './sanitize.js';
|
|
2
|
+
import { FieldWriteGate } from './fieldWriteGate.js';
|
|
3
|
+
/** The facts of a form schema the outbound sequence reads. */
|
|
4
|
+
export interface FormWriteTarget {
|
|
5
|
+
mode?: string;
|
|
6
|
+
objectName: string;
|
|
7
|
+
recordId?: string | number | null;
|
|
8
|
+
/** Inline field definitions (`object-form.customFields`). */
|
|
9
|
+
customFields?: readonly unknown[] | null;
|
|
10
|
+
}
|
|
11
|
+
/** Where the outbound sequence gets its answers from. */
|
|
12
|
+
export interface FormWriteSources {
|
|
13
|
+
/** The object definition the form loaded, or `null` when it has none. */
|
|
14
|
+
objectSchema: {
|
|
15
|
+
fields?: Record<string, Record<string, unknown> | undefined>;
|
|
16
|
+
} | null | undefined;
|
|
17
|
+
/** The caller's field-level write verdict — {@link FieldWriteGate}. */
|
|
18
|
+
canEdit: FieldWriteGate | undefined;
|
|
19
|
+
/** The record this form read for the record it edits (objectui#10156). */
|
|
20
|
+
snapshot: LoadedRecordSnapshot | null | undefined;
|
|
21
|
+
}
|
|
22
|
+
export interface FormWritePayloads {
|
|
23
|
+
/**
|
|
24
|
+
* Every value the form may write, as the form now holds it: stripped of what
|
|
25
|
+
* a form never writes and, on a create, of the fields the producer owns.
|
|
26
|
+
*/
|
|
27
|
+
payload: Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* What the save sends — on BOTH routes, the host `submitHandler` and the
|
|
30
|
+
* form's own create / OCC-guarded update. On an edit, the fields of
|
|
31
|
+
* {@link FormWritePayloads.payload} that differ from the record the form
|
|
32
|
+
* read; otherwise `payload` itself.
|
|
33
|
+
*/
|
|
34
|
+
writePayload: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The ONE outbound sequence of a record form: what a save writes, built from
|
|
38
|
+
* the values the form collected (objectui#10563).
|
|
39
|
+
*
|
|
40
|
+
* 1. `sanitizeFormData` drops what a form never writes — server-owned columns
|
|
41
|
+
* (objectui#10108), computed / formula / read-only columns, keys the object
|
|
42
|
+
* does not declare, and every field the caller's field-level security
|
|
43
|
+
* refuses (`canEdit`, `80c54122e`).
|
|
44
|
+
* 2. On a create, `omitServerResolvedDefaults` drops an empty field whose
|
|
45
|
+
* runtime default the producer resolves (#4069).
|
|
46
|
+
* 3. On an edit, `dirtyEditPayload` keeps only the fields that differ from the
|
|
47
|
+
* record the form read (objectui#10156), and sends the whole of step 1's
|
|
48
|
+
* result when it cannot settle that.
|
|
49
|
+
*
|
|
50
|
+
* ## Why it is one function
|
|
51
|
+
*
|
|
52
|
+
* `ObjectForm` routes a record save through several layouts, and each layout
|
|
53
|
+
* owns its save handler. When the sequence was spelled inline in each handler,
|
|
54
|
+
* three of them — `TabbedForm`, `SplitForm` and `WizardForm` (which also renders
|
|
55
|
+
* a simple form's mobile `stepper`) — carried none of it and wrote every value
|
|
56
|
+
* they held, `id`, `owner_id`, the formula columns and the field-level-refused
|
|
57
|
+
* ones included. ⛔ Do not spell any step of it beside a call to this function.
|
|
58
|
+
*
|
|
59
|
+
* ## The inline-members rule
|
|
60
|
+
*
|
|
61
|
+
* With a non-empty `customFields` the object definition is NOT handed to steps
|
|
62
|
+
* 1 and 2: an inline member may name a field the object never declares (it is
|
|
63
|
+
* appended after the generated set, objectui#9778), and the unknown-key strip
|
|
64
|
+
* would drop the value the author asked for. The server-owned roster and the
|
|
65
|
+
* field-level verdict still apply, because neither needs a definition.
|
|
66
|
+
*/
|
|
67
|
+
export declare function formWritePayload(values: Record<string, unknown>, form: FormWriteTarget, { objectSchema, canEdit, snapshot }: FormWriteSources): FormWritePayloads;
|
|
68
|
+
//# sourceMappingURL=writePayload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writePayload.d.ts","sourceRoot":"","sources":["../src/writePayload.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAAC;CAC1C;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,YAAY,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAClG,uEAAuE;IACvE,OAAO,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,0EAA0E;IAC1E,QAAQ,EAAE,oBAAoB,GAAG,IAAI,GAAG,SAAS,CAAC;CACnD;AAED,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,eAAe,EACrB,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,gBAAgB,GACpD,iBAAiB,CAanB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/plugin-form",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Form plugin for Object UI",
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@objectstack/spec": "^17.0.0",
|
|
19
|
-
"lucide-react": "^1.
|
|
20
|
-
"@object-ui/components": "17.
|
|
21
|
-
"@object-ui/core": "17.
|
|
22
|
-
"@object-ui/fields": "17.
|
|
23
|
-
"@object-ui/i18n": "17.
|
|
24
|
-
"@object-ui/permissions": "17.
|
|
25
|
-
"@object-ui/react": "17.
|
|
26
|
-
"@object-ui/types": "17.
|
|
19
|
+
"lucide-react": "^1.43.0",
|
|
20
|
+
"@object-ui/components": "17.7.0",
|
|
21
|
+
"@object-ui/core": "17.7.0",
|
|
22
|
+
"@object-ui/fields": "17.7.0",
|
|
23
|
+
"@object-ui/i18n": "17.7.0",
|
|
24
|
+
"@object-ui/permissions": "17.7.0",
|
|
25
|
+
"@object-ui/react": "17.7.0",
|
|
26
|
+
"@object-ui/types": "17.7.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"typescript": "^6.0.3",
|
|
36
36
|
"vite": "^8.2.1",
|
|
37
37
|
"vite-plugin-dts": "^5.0.3",
|
|
38
|
-
"@object-ui/data-objectstack": "17.
|
|
38
|
+
"@object-ui/data-objectstack": "17.7.0",
|
|
39
|
+
"@object-ui/test-support": "17.4.0"
|
|
39
40
|
},
|
|
40
41
|
"keywords": [
|
|
41
42
|
"objectui",
|
|
@@ -71,7 +72,7 @@
|
|
|
71
72
|
],
|
|
72
73
|
"scripts": {
|
|
73
74
|
"build": "vite build",
|
|
74
|
-
"test": "vitest run",
|
|
75
|
+
"test": "vitest run --root ../.. packages/plugin-form/",
|
|
75
76
|
"type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
76
77
|
"lint": "eslint ."
|
|
77
78
|
}
|