@object-ui/plugin-form 17.5.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +4841 -0
  2. package/README.md +772 -77
  3. package/dist/DrawerForm.d.ts +30 -2
  4. package/dist/DrawerForm.d.ts.map +1 -1
  5. package/dist/EmbeddableForm.d.ts.map +1 -1
  6. package/dist/FormAnalytics.d.ts.map +1 -1
  7. package/dist/LineItemsPanel.d.ts +6 -3
  8. package/dist/LineItemsPanel.d.ts.map +1 -1
  9. package/dist/MasterDetailForm.d.ts +31 -10
  10. package/dist/MasterDetailForm.d.ts.map +1 -1
  11. package/dist/ModalForm.d.ts +48 -4
  12. package/dist/ModalForm.d.ts.map +1 -1
  13. package/dist/ObjectForm.d.ts +18 -2
  14. package/dist/ObjectForm.d.ts.map +1 -1
  15. package/dist/SplitForm.d.ts +37 -2
  16. package/dist/SplitForm.d.ts.map +1 -1
  17. package/dist/TabbedForm.d.ts +65 -3
  18. package/dist/TabbedForm.d.ts.map +1 -1
  19. package/dist/WizardForm.d.ts +149 -11
  20. package/dist/WizardForm.d.ts.map +1 -1
  21. package/dist/autoLayout.d.ts +12 -3
  22. package/dist/autoLayout.d.ts.map +1 -1
  23. package/dist/closedAffordanceNotice.d.ts +14 -0
  24. package/dist/closedAffordanceNotice.d.ts.map +1 -0
  25. package/dist/customFieldsMerge.d.ts +29 -0
  26. package/dist/customFieldsMerge.d.ts.map +1 -0
  27. package/dist/deriveMasterDetail.d.ts +33 -9
  28. package/dist/deriveMasterDetail.d.ts.map +1 -1
  29. package/dist/fieldGroups.d.ts +176 -0
  30. package/dist/fieldGroups.d.ts.map +1 -1
  31. package/dist/fieldWriteGate.d.ts +116 -0
  32. package/dist/fieldWriteGate.d.ts.map +1 -0
  33. package/dist/flatFields.d.ts +30 -0
  34. package/dist/flatFields.d.ts.map +1 -0
  35. package/dist/formChrome.d.ts +55 -0
  36. package/dist/formChrome.d.ts.map +1 -0
  37. package/dist/index.d.ts +131 -25
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +3676 -1967
  40. package/dist/index.umd.cjs +1 -1
  41. package/dist/initialRecord.d.ts +72 -0
  42. package/dist/initialRecord.d.ts.map +1 -0
  43. package/dist/loadFailure.d.ts +28 -0
  44. package/dist/loadFailure.d.ts.map +1 -0
  45. package/dist/masterDetailTx.d.ts.map +1 -1
  46. package/dist/occSave.d.ts +6 -1
  47. package/dist/occSave.d.ts.map +1 -1
  48. package/dist/recordInvalidation.d.ts +33 -0
  49. package/dist/recordInvalidation.d.ts.map +1 -0
  50. package/dist/recordReadGate.d.ts +34 -0
  51. package/dist/recordReadGate.d.ts.map +1 -0
  52. package/dist/sanitize.d.ts +187 -5
  53. package/dist/sanitize.d.ts.map +1 -1
  54. package/dist/schemaDefaults.d.ts +51 -30
  55. package/dist/schemaDefaults.d.ts.map +1 -1
  56. package/dist/sectionFields.d.ts +65 -2
  57. package/dist/sectionFields.d.ts.map +1 -1
  58. package/dist/sectionGroups.d.ts +62 -0
  59. package/dist/sectionGroups.d.ts.map +1 -0
  60. package/dist/sectionPredicateDiagnostic.d.ts +38 -0
  61. package/dist/sectionPredicateDiagnostic.d.ts.map +1 -0
  62. package/dist/submitRedirect.d.ts +148 -0
  63. package/dist/submitRedirect.d.ts.map +1 -0
  64. package/dist/submitRedirectNavigation.d.ts +82 -0
  65. package/dist/submitRedirectNavigation.d.ts.map +1 -0
  66. package/dist/submitTarget.d.ts +119 -0
  67. package/dist/submitTarget.d.ts.map +1 -0
  68. package/dist/successBehavior.d.ts +44 -8
  69. package/dist/successBehavior.d.ts.map +1 -1
  70. package/dist/thankYouRedirectCountdown.d.ts +17 -0
  71. package/dist/thankYouRedirectCountdown.d.ts.map +1 -0
  72. package/dist/thankYouRedirectNavigation.d.ts +111 -0
  73. package/dist/thankYouRedirectNavigation.d.ts.map +1 -0
  74. package/dist/uploadGate.d.ts +35 -0
  75. package/dist/uploadGate.d.ts.map +1 -0
  76. package/dist/writePayload.d.ts +68 -0
  77. package/dist/writePayload.d.ts.map +1 -0
  78. package/package.json +13 -12
@@ -26,14 +26,77 @@ export interface SectionFieldsContext {
26
26
  * passes it.
27
27
  */
28
28
  fieldLabel: (objectName: string, fieldName: string, fallback: string) => string;
29
+ /**
30
+ * The authored inline members (`schema.customFields`). A member naming the
31
+ * field a section entry names is that entry's BASE definition, in place of
32
+ * the one generated from the object schema — the precedence `ObjectForm`'s
33
+ * default arm resolves its section members with (`findCustomFieldMember`,
34
+ * objectui#10254). Only the base moves: the entry's own overrides still
35
+ * apply on top, by the rules below. Omitted or empty → every base is
36
+ * generated, as before.
37
+ */
38
+ customFields?: readonly FormField[] | null;
39
+ /**
40
+ * The parent field POOL the section resolves its members against, for the
41
+ * one caller that has one: `ObjectForm`'s default arm (`SimpleObjectForm`),
42
+ * whose pool is built from top-level `fields` (or the object) merged with
43
+ * `customFields` (objectui#10475).
44
+ *
45
+ * When set, {@link buildSectionFields} still walks the section's entries in
46
+ * AUTHORED order and applies each entry's overrides by the same rules as
47
+ * every other arm; two things change, both the pool's to decide:
48
+ *
49
+ * - membership — an entry naming a field the pool does not hold is
50
+ * dropped. That is objectui#9884's INTERSECTION of `fields` and
51
+ * `sections`; the caller emits its warning
52
+ * (`warnSectionMemberExcludedByFields`), this module only drops;
53
+ * - the BASE — the pooled field is what a name string draws and what a
54
+ * spec entry's overrides are written onto (a copy), in place of
55
+ * `fromObjectSchema` / the member lookup. The pool already resolved the
56
+ * member-over-generated precedence, and its generated fields carry the
57
+ * default arm's own per-field facts (the lock on a computed `formula` /
58
+ * `summary` / `auto_number` field, the input type, the step), which only
59
+ * LAYOUT and per-entry overrides may move. The managed-object lock is
60
+ * not one of them: every arm applies it after this builder, in
61
+ * `gateFormFields` (objectui#10612).
62
+ *
63
+ * An already-built runtime FormField entry (shape 3) is its own definition
64
+ * with or without a pool; the pool decides only whether it is drawn.
65
+ * Omitted → no pool: every entry is drawn, from the bases above.
66
+ */
67
+ pool?: readonly FormField[] | null;
29
68
  }
69
+ export declare function warnUnresolvedTopLevelField(entry: unknown, objectName: string): void;
70
+ export declare function warnSectionMemberExcludedByFields(memberName: string, objectName: string, sectionLabel?: string): void;
71
+ /**
72
+ * Build a runtime FormField from object-schema metadata for `fieldName`.
73
+ *
74
+ * The ONE object-field-to-FormField mapping in this package. Exported because
75
+ * the flat (no-sections) containers need the identical mapping — see
76
+ * `flatFields.ts`, and objectui#4755 for what a second hand-copied version
77
+ * costs.
78
+ */
79
+ export declare function fromObjectSchema(fieldName: string, ctx: SectionFieldsContext): FormField;
80
+ /**
81
+ * The field name a section entry resolves to — the identity rule of the three
82
+ * shapes above, spelled once: a string is the name; a spec entry's identity is
83
+ * its `field` STRING; an already-built runtime FormField's is its `name`.
84
+ * `undefined` when the entry names nothing.
85
+ */
86
+ export declare function sectionEntryName(fieldDef: unknown): string | undefined;
30
87
  /**
31
88
  * Normalize one section field definition (string | spec object | runtime
32
89
  * FormField) into a runtime FormField with a guaranteed `name`.
33
90
  */
34
91
  export declare function normalizeSectionField(fieldDef: string | Record<string, any>, ctx: SectionFieldsContext): FormField;
35
- /** Normalize every field def in a section. */
92
+ /**
93
+ * Normalize every field def in a section, in the section's AUTHORED order.
94
+ *
95
+ * With a {@link SectionFieldsContext.pool}, an entry naming a field the pool
96
+ * does not hold is dropped, and a pooled field is the base the entry starts
97
+ * from; the order stays the section's own either way (objectui#10475).
98
+ */
36
99
  export declare function buildSectionFields(section: {
37
- fields: Array<string | Record<string, any>>;
100
+ fields?: Array<string | Record<string, any>>;
38
101
  }, ctx: SectionFieldsContext): FormField[];
39
102
  //# sourceMappingURL=sectionFields.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sectionFields.d.ts","sourceRoot":"","sources":["../src/sectionFields.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIlD,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,YAAY,EAAE,GAAG,CAAC;IAClB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CACjF;AA8ED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACtC,GAAG,EAAE,oBAAoB,GACxB,SAAS,CA2FX;AAED,8CAA8C;AAC9C,wBAAgB,kBAAkB,CAChC,OAAO,EAAE;IAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;CAAE,EACxD,GAAG,EAAE,oBAAoB,GACxB,SAAS,EAAE,CAEb"}
1
+ {"version":3,"file":"sectionFields.d.ts","sourceRoot":"","sources":["../src/sectionFields.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAMlD,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,YAAY,EAAE,GAAG,CAAC;IAClB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAChF;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CAAC;IAC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,IAAI,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CAAC;CACpC;AA0FD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAepF;AAkCD,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CAaN;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,GAAG,SAAS,CAgCxF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAMtE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACtC,GAAG,EAAE,oBAAoB,GACxB,SAAS,CAEX;AAwHD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;CAAE,EACzD,GAAG,EAAE,oBAAoB,GACxB,SAAS,EAAE,CAmBb"}
@@ -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,148 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * Where a form's `submitBehavior: { kind: 'redirect' }` sends the submitter
6
+ * (objectui#4989), under the shape objectstack#7496 ruled on 2026-08-11 and
7
+ * objectstack#7657 landed in `@objectstack/spec` (live on the 17.0.0 GA pin
8
+ * this package depends on).
9
+ *
10
+ * The ruling has three points:
11
+ *
12
+ * 1. **relative paths only** — an absolute or protocol-relative URL is
13
+ * refused. This is the open-redirect face: the destination is authored
14
+ * metadata, which is exactly where an address somebody else chose gets
15
+ * copied in.
16
+ * 2. **interpolation only from declared record fields**, spelled
17
+ * `{{record.field_name}}`, with every interpolated value **URL-escaped
18
+ * when the redirect is built**.
19
+ * 3. a **verbatim redirect on the resolved relative path** is the intended
20
+ * consumption — the destination is that string with its tokens
21
+ * substituted, and nothing else.
22
+ *
23
+ * Point 1 is a property of the string, so the spec enforces it at the authoring
24
+ * door and this module ASKS it rather than restating it. Whether
25
+ * `{{record.foo}}` names a field the form's object actually declares needs both
26
+ * the string and the object declaration, so `@objectstack/lint`'s
27
+ * reference-integrity family owns that. Point 2's "when the redirect is built"
28
+ * and point 3's consumption are runtime, and this module is where they happen.
29
+ *
30
+ * ## The shape verdict is the spec's, not a copy of it
31
+ *
32
+ * `@objectstack/spec` does not export its url check as a function, but it does
33
+ * export the schema the check lives in, so the verdict here is produced by
34
+ * PARSING the smallest form view that carries this behavior. That costs one
35
+ * parse per submit and buys the property that matters: there is no second
36
+ * spelling of a security rule in this repo to drift from the first, and when the
37
+ * ruling widens (it says an allowlist of absolute origins "waits for measured
38
+ * demand") this package follows the pin with no edit here.
39
+ *
40
+ * The alternative was a local re-implementation of the seven refusal families
41
+ * kept honest by a parity table. `scripts/check-spec-symbol-derivation.mjs`
42
+ * exists because this repo has paid for that shape four times
43
+ * (objectstack#4115, objectui#4074/#4588/#4592): a hand copy passes every value
44
+ * comparison right up to the release that moves the original.
45
+ *
46
+ * Parsing a MINIMAL view is load-bearing, not laziness. A whole stored
47
+ * `FormView` refuses on any unrelated key the strict schema does not know, and a
48
+ * redirect must not be refused because some other part of the metadata drifted.
49
+ * The question asked here is narrow — "is this url a value the contract
50
+ * allows?" — so only that value is submitted for judgement, and only issues on
51
+ * that value's path are read back.
52
+ *
53
+ * `@objectstack/spec` is already a declared runtime dependency of this package
54
+ * and already imported by its siblings (`sectionFields.ts`, `sanitize.ts`), so
55
+ * the import adds no dependency and no chunk.
56
+ *
57
+ * ## Relationship to `apps/console`'s `submitRedirect.ts`
58
+ *
59
+ * `apps/console` consumes the same ruled key and got the same treatment in
60
+ * objectui#4190 / PR #4992. The two are deliberately SEMANTICALLY aligned and
61
+ * physically separate: a published renderer package cannot import an app's
62
+ * module, and the shared thing — the shape verdict — is not duplicated at all,
63
+ * because both ask the same `FormViewSchema`. What is written twice is the token
64
+ * substitution and the escape, ~20 lines the spec exports no substituter for.
65
+ *
66
+ * Two deliberate divergences, both named so a future consolidation has a map.
67
+ * The accepted value: the console resolves to a `path` it hands to a ROUTER
68
+ * unconditionally, whereas this package resolves to a path it hands to the
69
+ * host's injected navigate when there is one and to a browser-level navigation
70
+ * when there is not — a renderer cannot assume a router, which is the whole
71
+ * subject of `submitRedirectNavigation.ts`. And the split: the console has since
72
+ * separated its shape verdict
73
+ * into `checkSubmitRedirectUrl` because it also owns an authoring door
74
+ * (`PublicFormsPage` asks it before saving), which this package has no
75
+ * counterpart to — a renderer only ever consumes.
76
+ */
77
+ /** Accepted: `url` is the resolved relative destination to navigate to. */
78
+ interface SubmitRedirectAccepted {
79
+ ok: true;
80
+ url: string;
81
+ }
82
+ /** Refused: `refusal` is author-facing prose explaining what to write instead. */
83
+ interface SubmitRedirectRefused {
84
+ ok: false;
85
+ refusal: string;
86
+ }
87
+ export type SubmitRedirectVerdict = SubmitRedirectAccepted | SubmitRedirectRefused;
88
+ /**
89
+ * The token scope for one submit: "the record this submit just wrote".
90
+ *
91
+ * The values as submitted, with whatever the DataSource answered layered over
92
+ * them — defaults, computed columns and the new id are canonical there, and the
93
+ * submitted values are what a `create` that echoes nothing back still has. Both
94
+ * form components call this so a `{{record.id}}` token cannot resolve one way in
95
+ * `ObjectForm` and another in `WizardForm`.
96
+ */
97
+ export declare function submitRedirectScope(submittedValues: unknown, result: unknown): Record<string, unknown>;
98
+ /**
99
+ * Resolve an authored `submitBehavior.url` into the relative destination to
100
+ * navigate to, or refuse it.
101
+ *
102
+ * @param url the authored value, exactly as the metadata carries it
103
+ * @param record the record this submit just wrote — the token scope
104
+ *
105
+ * Refusal is fail-closed and quotable: the message is the spec's own
106
+ * prescription (it names the key, the rule, and what to write instead, including
107
+ * that a deliberately external destination is an app navigation item rather than
108
+ * this key), so the author reads the same sentence here that the authoring door
109
+ * would have told them. Callers must SHOW it — the write already succeeded, and
110
+ * dropping the refusal leaves the submitter looking at a still-filled form with
111
+ * no idea why nothing happened, which is objectui#4989's defect 2 and the one
112
+ * outcome the ruling's consumer half rules out.
113
+ *
114
+ * ## Why substitution cannot widen the destination
115
+ *
116
+ * Every interpolated value goes through `encodeURIComponent`, which escapes the
117
+ * characters that could add structure — `/` and `:`, along with `?`, `#` and the
118
+ * rest. So a field carrying an address, a traversal, or a space becomes one
119
+ * opaque segment or query value: a token is a VALUE in the path, never a way to
120
+ * add path structure.
121
+ *
122
+ * That escape carries the whole weight of the property, and re-parsing the
123
+ * result would NOT be enough on its own. Interpolated raw,
124
+ * `/t/{{record.slug}}` with an address in `slug` becomes
125
+ * `/t/https://evil.example/steal` — which still starts with `/` and carries no
126
+ * leading scheme, i.e. a spec-VALID relative path pointing somewhere the author
127
+ * never wrote. Relative-only is a rule about where a path STARTS, so it has
128
+ * nothing to say about structure injected further along. `submitRedirect.test.ts`
129
+ * therefore pins both halves.
130
+ *
131
+ * ## What this module does not answer: WHO travels (objectui#4989 defect 4)
132
+ *
133
+ * An accepted value is a rooted relative path, and a rooted path resolves
134
+ * against the ORIGIN root when a browser-level navigation performs it — so
135
+ * under a host mounted at a sub-path a ruled in-app `/thanks` used to leave the
136
+ * app. That is defect 4 of the card, and it is not a property of the string:
137
+ * the same accepted path is correct or wrong depending on who travels to it.
138
+ * It is therefore answered one layer out, in `submitRedirectNavigation.ts`,
139
+ * where the host's optionally injected navigate is used when present
140
+ * (maintainer ruling, 2026-08-17) and `window.location.assign` otherwise.
141
+ *
142
+ * Nothing about the verdict changes with the seam: an injected host navigate is
143
+ * never a laundering route for a value this module refuses, because it is only
144
+ * ever handed a path this module already accepted.
145
+ */
146
+ export declare function resolveSubmitRedirect(url: string, record: Record<string, unknown>): SubmitRedirectVerdict;
147
+ export {};
148
+ //# sourceMappingURL=submitRedirect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submitRedirect.d.ts","sourceRoot":"","sources":["../src/submitRedirect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAsBH,2EAA2E;AAC3E,UAAU,sBAAsB;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;CACb;AAED,kFAAkF;AAClF,UAAU,qBAAqB;IAC7B,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG,qBAAqB,CAAC;AAoBnF;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,OAAO,EACxB,MAAM,EAAE,OAAO,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,qBAAqB,CAgDvB"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * How an ACCEPTED `submitBehavior: { kind: 'redirect' }` destination is
6
+ * actually travelled to — objectui#4989 **defect 4, mount-blindness**, the
7
+ * reopened half of that card.
8
+ *
9
+ * `submitRedirect.ts` answers WHETHER a destination may be followed and WHAT
10
+ * the resolved relative path is. This module answers WHO performs the
11
+ * navigation, and it is a separate question because the right answer is not
12
+ * this package's to pick: a rooted path such as `/thanks` handed to
13
+ * `window.location.assign` resolves against the ORIGIN, so under a host mounted
14
+ * at a sub-path (the framework CLI configures one for every embedded
15
+ * deployment) a ruled in-app destination leaves the application. Only the host
16
+ * knows its mount.
17
+ *
18
+ * ## The mechanism, and why it is this one
19
+ *
20
+ * Maintainer ruling, 2026-08-17: **optional injected navigation**. The host
21
+ * offers its own router-aware, basename-aware navigate through
22
+ * `HostNavigationContext` (`@object-ui/react`); this package uses it when it is
23
+ * there and keeps today's `window.location.assign` when it is not. Two other
24
+ * mechanisms were weighed and REJECTED in that ruling, recorded here so the
25
+ * next reader does not re-derive them as improvements:
26
+ *
27
+ * - **reading React Router's context when a router happens to be present** —
28
+ * implicit, and unavailable regardless: a React context is a module-instance
29
+ * object, so reading the host's requires importing `react-router` here.
30
+ * This package declares it in none of its four dependency fields, its Vite
31
+ * build externalises every bare specifier, and two real consumers
32
+ * (`apps/site`, `packages/plugin-view`) have no react-router to resolve —
33
+ * the published `dist` would break their builds. Declaring it as a normal
34
+ * dependency does not help either: that yields this package its OWN
35
+ * react-router instance, whose context object is not the host's, so the
36
+ * read answers null forever.
37
+ * - **a `react-router` peer requirement** — the honest version of the above,
38
+ * and it costs this package the property that it drops into ANY React
39
+ * application.
40
+ *
41
+ * ## Absent seam is not a degraded mode
42
+ *
43
+ * With no provider the behaviour is byte-for-byte what it was: one
44
+ * `window.location.assign` of the resolved path after the declared delay. A
45
+ * host with no router has no basename, so origin-rooted resolution is already
46
+ * correct there — the seam changes what a MOUNTED host gets, and nothing else.
47
+ *
48
+ * ## What the host takes on by supplying it
49
+ *
50
+ * A host navigate is a client-side transition, so the destination must be a
51
+ * path the host's router can match; an in-app path with no route renders the
52
+ * host's not-found instead of a full page load. That is the host's routing
53
+ * table to answer for, which is exactly why the choice is the host's. The
54
+ * verdict is NOT relaxed for injected hosts: a destination the contract refuses
55
+ * is refused identically either way (`submitRedirect.ts`), so a supplied
56
+ * navigate can never become a laundering route for a value the authoring door
57
+ * rejects.
58
+ */
59
+ /**
60
+ * An accepted destination plus the delay declared with it, captured at the
61
+ * moment the write was accepted. Held in component state by each form so the
62
+ * wait belongs to the mounted component (objectui#5033) — see the state's own
63
+ * comment at each call site.
64
+ */
65
+ export interface PendingSubmitRedirect {
66
+ url: string;
67
+ delayMs: number;
68
+ }
69
+ /**
70
+ * Own the delayed leg of an accepted `redirect` submit behaviour: wait the
71
+ * declared `delayMs`, then travel to `pending.url` through the host's navigate
72
+ * if one was injected, else through `window.location.assign`.
73
+ *
74
+ * Unmounting cancels the wait, which is the property objectui#5033 bought and
75
+ * this extraction preserves: the hook's effect is the same effect both forms
76
+ * used to declare inline, now written once so the two cannot disagree about
77
+ * either the wait or the seam.
78
+ *
79
+ * Pass `null` when nothing is pending.
80
+ */
81
+ export declare function useSubmitRedirectNavigation(pending: PendingSubmitRedirect | null): void;
82
+ //# sourceMappingURL=submitRedirectNavigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submitRedirectNavigation.d.ts","sourceRoot":"","sources":["../src/submitRedirectNavigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAKH;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CA8BvF"}
@@ -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,17 +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 → ObjectForm → WizardForm
13
- * → here), so the same-origin check is inlined.
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
- /** Same-origin guard for declarative navigation (relative or absolute URLs). */
17
- export declare function isSameOriginUrl(rawUrl: string): boolean;
18
16
  /**
19
- * Resolve a `navigateOnSuccess` template into a safe URL: interpolate
20
- * `{id}` / `{recordId}` from the created/updated record and same-origin-guard it.
21
- * Returns null when there's no template, no usable id, or the URL fails the
22
- * guard — callers then fall back to a toast.
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.
23
59
  */
24
60
  export declare function resolveSuccessNavigate(template: string | undefined, record: any): string | null;
25
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;AAEvD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAQvD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,GAAG,GACV,MAAM,GAAG,IAAI,CAMf"}
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"}
@@ -0,0 +1,17 @@
1
+ import { PendingThankYouRedirect } from './thankYouRedirectNavigation.js';
2
+ /** One tick per displayed second — matches the `{{seconds}}` unit the copy promises. */
3
+ export declare const REDIRECT_COUNTDOWN_TICK_MS = 1000;
4
+ /**
5
+ * The remaining whole seconds of an accepted thank-you redirect wait, ticking
6
+ * down once per second until it reaches 0. `null` while nothing is pending —
7
+ * the panel keeps deciding on its own whether a countdown renders at all (a
8
+ * refused destination, objectui#5073, never reaches this hook with a
9
+ * non-null `pending`).
10
+ *
11
+ * Pass the same `PendingThankYouRedirect | null` value given to
12
+ * `useThankYouRedirectNavigation`. This hook does not navigate anything and
13
+ * does not judge the destination — it only counts down the delay that was
14
+ * captured alongside it.
15
+ */
16
+ export declare function useRedirectCountdownSeconds(pending: PendingThankYouRedirect | null): number | null;
17
+ //# sourceMappingURL=thankYouRedirectCountdown.d.ts.map