@incodetech/prizma-core-screens 0.2.3 → 0.2.7

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 (116) hide show
  1. package/dist/CurpEnterScreen.d.ts +7 -0
  2. package/dist/CurpEnterScreen.js +17 -3
  3. package/dist/CurpGenerateScreen.d.ts +8 -0
  4. package/dist/CurpGenerateScreen.js +28 -13
  5. package/dist/CurpLayout.css +8 -34
  6. package/dist/CurpStrings.d.ts +16 -2
  7. package/dist/EkybAddressDetailsScreen.d.ts +7 -0
  8. package/dist/EkybAddressDetailsScreen.js +25 -7
  9. package/dist/EkybBusinessDetailsScreen.d.ts +6 -0
  10. package/dist/EkybBusinessDetailsScreen.js +23 -6
  11. package/dist/EkybCountryScreen.d.ts +13 -5
  12. package/dist/EkybCountryScreen.js +27 -9
  13. package/dist/EkybOwnersScreen.d.ts +8 -0
  14. package/dist/EkybOwnersScreen.js +26 -3
  15. package/dist/EkybStrings.d.ts +27 -10
  16. package/dist/EkycCountryCatalog.d.ts +4 -1
  17. package/dist/EkycCountryCatalog.js +5 -3
  18. package/dist/EkycCountryFormScreen.d.ts +12 -3
  19. package/dist/EkycCountryFormScreen.js +28 -10
  20. package/dist/EkycDetailsScreen.d.ts +7 -0
  21. package/dist/EkycDetailsScreen.js +21 -4
  22. package/dist/EkycStrings.d.ts +14 -2
  23. package/dist/ElectronicSignatureLayout.css +6 -0
  24. package/dist/EmailInputStrings.d.ts +11 -1
  25. package/dist/EmailInputVerifyScreen.d.ts +7 -0
  26. package/dist/EmailInputVerifyScreen.js +17 -3
  27. package/dist/FaceMatchComparisonScreen.d.ts +3 -3
  28. package/dist/FaceMatchLayout.css +21 -12
  29. package/dist/FormScreen.css +16 -0
  30. package/dist/FormsLongFormScreen.d.ts +8 -0
  31. package/dist/FormsLongFormScreen.js +34 -13
  32. package/dist/FormsStrings.d.ts +11 -1
  33. package/dist/GeolocationBrowserStepsScreen.d.ts +6 -2
  34. package/dist/GeolocationBrowserStepsScreen.js +7 -3
  35. package/dist/GeolocationStrings.d.ts +3 -0
  36. package/dist/IdCaptureLayout.css +40 -0
  37. package/dist/ListScreen.d.ts +2 -0
  38. package/dist/ListScreen.js +1 -1
  39. package/dist/PhoneInputLayout.css +10 -9
  40. package/dist/PhoneInputStrings.d.ts +11 -1
  41. package/dist/PhoneInputVerifyScreen.d.ts +8 -0
  42. package/dist/PhoneInputVerifyScreen.js +19 -6
  43. package/dist/QualifiedSignatureAcceptScreen.js +6 -1
  44. package/dist/QualifiedSignatureDocumentScreen.d.ts +13 -9
  45. package/dist/QualifiedSignatureDocumentScreen.js +18 -16
  46. package/dist/QualifiedSignatureLayout.css +76 -0
  47. package/dist/QualifiedSignatureStrings.d.ts +10 -0
  48. package/dist/ScreenNav.css +8 -0
  49. package/dist/ScreenNav.d.ts +15 -2
  50. package/dist/ScreenNav.js +10 -3
  51. package/dist/VideoSelfieIdCaptureScreen.d.ts +10 -3
  52. package/dist/VideoSelfieIdCaptureScreen.js +44 -13
  53. package/dist/VideoSelfieLayout.css +164 -33
  54. package/dist/VideoSelfieSelfieCaptureScreen.d.ts +44 -27
  55. package/dist/VideoSelfieSelfieCaptureScreen.js +65 -39
  56. package/dist/VideoSelfieSpeakScreen.js +18 -1
  57. package/dist/VideoSelfieStrings.d.ts +78 -12
  58. package/dist/VideoSelfieTurnHeadScreen.d.ts +51 -0
  59. package/dist/VideoSelfieTurnHeadScreen.js +63 -0
  60. package/dist/VideoSelfieTutorialScreen.js +13 -5
  61. package/dist/VideoSelfieWaveScreen.d.ts +53 -0
  62. package/dist/VideoSelfieWaveScreen.js +62 -0
  63. package/dist/WatchlistBusinessFormScreen.d.ts +11 -7
  64. package/dist/WatchlistBusinessFormScreen.js +27 -13
  65. package/dist/WatchlistBusinessStrings.d.ts +21 -7
  66. package/dist/index.d.ts +3 -1
  67. package/dist/index.js +2 -0
  68. package/dist/types.d.ts +3 -1
  69. package/package.json +14 -3
  70. package/samples/video-selfie-room.png +0 -0
  71. package/src/CurpEnterScreen.tsx +21 -4
  72. package/src/CurpGenerateScreen.tsx +46 -22
  73. package/src/CurpLayout.css +8 -34
  74. package/src/CurpStrings.ts +22 -2
  75. package/src/EkybAddressDetailsScreen.tsx +32 -4
  76. package/src/EkybBusinessDetailsScreen.tsx +23 -5
  77. package/src/EkybCountryScreen.tsx +28 -7
  78. package/src/EkybOwnersScreen.tsx +34 -3
  79. package/src/EkybStrings.ts +29 -10
  80. package/src/EkycCountryCatalog.ts +9 -4
  81. package/src/EkycCountryFormScreen.tsx +51 -12
  82. package/src/EkycDetailsScreen.tsx +22 -1
  83. package/src/EkycStrings.ts +14 -2
  84. package/src/ElectronicSignatureLayout.css +6 -0
  85. package/src/EmailInputStrings.ts +12 -1
  86. package/src/EmailInputVerifyScreen.tsx +21 -4
  87. package/src/FaceMatchComparisonScreen.tsx +3 -3
  88. package/src/FaceMatchLayout.css +21 -12
  89. package/src/FormScreen.css +16 -0
  90. package/src/FormsLongFormScreen.tsx +41 -13
  91. package/src/FormsStrings.ts +12 -1
  92. package/src/GeolocationBrowserStepsScreen.tsx +6 -3
  93. package/src/GeolocationStrings.ts +3 -0
  94. package/src/IdCaptureLayout.css +40 -0
  95. package/src/ListScreen.tsx +10 -1
  96. package/src/PhoneInputLayout.css +10 -9
  97. package/src/PhoneInputStrings.ts +12 -1
  98. package/src/PhoneInputVerifyScreen.tsx +21 -5
  99. package/src/QualifiedSignatureAcceptScreen.tsx +12 -1
  100. package/src/QualifiedSignatureDocumentScreen.tsx +27 -17
  101. package/src/QualifiedSignatureLayout.css +76 -0
  102. package/src/QualifiedSignatureStrings.ts +10 -0
  103. package/src/ScreenNav.css +8 -0
  104. package/src/ScreenNav.tsx +35 -5
  105. package/src/VideoSelfieIdCaptureScreen.tsx +116 -30
  106. package/src/VideoSelfieLayout.css +164 -33
  107. package/src/VideoSelfieSelfieCaptureScreen.tsx +104 -59
  108. package/src/VideoSelfieSpeakScreen.tsx +25 -14
  109. package/src/VideoSelfieStrings.ts +85 -12
  110. package/src/VideoSelfieTurnHeadScreen.tsx +151 -0
  111. package/src/VideoSelfieTutorialScreen.tsx +34 -23
  112. package/src/VideoSelfieWaveScreen.tsx +135 -0
  113. package/src/WatchlistBusinessFormScreen.tsx +35 -16
  114. package/src/WatchlistBusinessStrings.ts +21 -7
  115. package/src/index.ts +14 -0
  116. package/src/types.ts +3 -1
@@ -7,13 +7,19 @@
7
7
  * - `dropdown` — the same selection with the MENU OPEN over the form: search
8
8
  * bar plus the country rows (Core's documentation-only `forceOpen`, since a
9
9
  * screen is one static spec state and never a live interaction)
10
+ * - `emptyError` — Continue was pressed with no country chosen (GUIDELINES
11
+ * §6-E empty-submit rule, Elena 2026-09-30): the dropdown in Core's error
12
+ * state with `requiredError`, Continue disabled. Not a Figma frame.
10
13
  */
11
- export type EkybCountryState = 'empty' | 'filled' | 'dropdown';
14
+ export type EkybCountryState = 'empty' | 'filled' | 'dropdown' | 'emptyError';
12
15
  /** The two `eKYB.Bdetails` (business identity) sections: every field on its
13
- * resting empty surface, or every field carrying a value. */
14
- export type EkybBusinessState = 'empty' | 'filled';
15
- /** The two address sections (the SECOND `eKYB.Bdetails` parent). Same axis. */
16
- export type EkybAddressState = 'empty' | 'filled';
16
+ * resting empty surface, or every field carrying a value — plus `emptyError`,
17
+ * Continue pressed with the fields empty (empty-submit rule, 2026-09-30):
18
+ * every required field in Core's error state, Continue disabled. */
19
+ export type EkybBusinessState = 'empty' | 'filled' | 'emptyError';
20
+ /** The two address sections (the SECOND `eKYB.Bdetails` parent). Same axis,
21
+ * same `emptyError` (the optional Address line 2 shows no error). */
22
+ export type EkybAddressState = 'empty' | 'filled' | 'emptyError';
17
23
  /**
18
24
  * The `eKYB.UBO.*` and `eKYB.director.*` sections — one screen family, because
19
25
  * measured against the 390 frames the director frames ARE the UBO frames with
@@ -33,8 +39,13 @@ export type EkybAddressState = 'empty' | 'filled';
33
39
  * flight, so EVERY field, both "Add another" links and "Back" go inert and
34
40
  * the spinner rides the CTA, never a field (the Phone Input lesson,
35
41
  * DEVIATIONS #PI-5)
42
+ * - `uboEmptyError` / `directorEmptyError` — `uboEmpty` / `directorEmpty` after
43
+ * Continue was pressed with the fields empty (GUIDELINES §6-E empty-submit
44
+ * rule, Elena 2026-09-30): the two REQUIRED UBO fields in Core's error state
45
+ * with `requiredError`, the optional Director fields unflagged, Continue
46
+ * disabled. Not Figma frames.
36
47
  */
37
- export type EkybOwnersState = 'uboEmpty' | 'uboFilled' | 'directorEmpty' | 'directorFilled' | 'uboAdded' | 'loading';
48
+ export type EkybOwnersState = 'uboEmpty' | 'uboFilled' | 'directorEmpty' | 'directorFilled' | 'uboAdded' | 'loading' | 'uboEmptyError' | 'directorEmptyError';
38
49
  /**
39
50
  * One selectable row in the country / state menus. Runtime data, not copy — the
40
51
  * host owns the catalogue (an ISO country list is ~250 entries and a state list
@@ -47,9 +58,8 @@ export interface EkybOption {
47
58
  /** The row label as drawn in the open menu. */
48
59
  label: string;
49
60
  /** Compact face for the CLOSED field when it differs from the menu row —
50
- * Core's own `PrizmaDropdownOption.fieldLabel`. `eKYB.country.dropdown`
51
- * draws "🇺🇸 +1 United States" in the row and "🇺🇸 United States" in the
52
- * field. */
61
+ * Core's own `PrizmaDropdownOption.fieldLabel`. The eKYB country list
62
+ * does not need it: rows and field both read "🇺🇸 United States". */
53
63
  fieldLabel?: string;
54
64
  }
55
65
  /** The values the business-identity page carries. Runtime data, never copy. */
@@ -97,6 +107,13 @@ export interface EkybStrings {
97
107
  * localizable and never hardcoded in a screen.
98
108
  */
99
109
  requiredHint?: string;
110
+ /**
111
+ * Under every empty REQUIRED field once the primary was pressed with it
112
+ * still empty — the `emptyError` states (GUIDELINES §6-E empty-submit rule,
113
+ * Elena 2026-09-30). Core's own `errorText` slot; SDK key
114
+ * `dynamicForms.fieldRequired`.
115
+ */
116
+ requiredError: string;
100
117
  /** Shared "Back" secondary action drawn under "Continue" on every page except
101
118
  * `country`. Emits `secondary`. */
102
119
  backLabel: string;
@@ -170,7 +187,7 @@ export interface EkybStrings {
170
187
  title: string;
171
188
  subtitle?: string;
172
189
  uboName: EkybFieldStrings;
173
- /** Figma draws NO `*` on the surname — it is optional. */
190
+ /** Required, like the name (Elena, 2026-09-28); the frames drew no `*`. */
174
191
  uboSurname: EkybFieldStrings;
175
192
  /** The tertiary link under the UBO block ("Add another UBO (Optional)"). */
176
193
  addUboLabel: string;
@@ -1,4 +1,7 @@
1
- export type EkycFieldKind = 'text' | 'date' | 'phone' | 'dropdown' | 'radio';
1
+ /** `password`: Core's `PrizmaPassword`, the field with the eye that hides the
2
+ * typed value. For the identifiers a stranger must not read over a shoulder:
3
+ * the SSN / ITIN fields (Elena, 2026-09-28). No requirement checklist. */
4
+ export type EkycFieldKind = 'text' | 'date' | 'phone' | 'dropdown' | 'radio' | 'password';
2
5
  export type EkycFieldKey = 'firstName' | 'middleName' | 'middleNameOptional' | 'lastName' | 'surname' | 'maternalSurname' | 'dateOfBirth' | 'email' | 'phone' | 'ssnLast4' | 'ssnOrItin' | 'nationality' | 'cpf' | 'nationalId' | 'taxId' | 'gender' | 'licenseNumber' | 'licenseState' | 'licenseExpiration' | 'street' | 'houseNumber' | 'city' | 'state' | 'stateCode' | 'zipCode' | 'postalCode';
3
6
  export interface EkycField {
4
7
  key: EkycFieldKey;
@@ -20,6 +20,8 @@
20
20
  // Verification", and no variant draws a close in the nav; steps after the
21
21
  // first get the archetype's Back button instead of a back arrow (#EKC-2).
22
22
  const t = (key, extra = {}) => ({ key, kind: 'text', ...extra });
23
+ /** A hidden-by-default identifier, with the eye to reveal it. */
24
+ const secret = (key, extra = {}) => ({ key, kind: 'password', ...extra });
23
25
  const date = (key = 'dateOfBirth', extra = {}) => ({ key, kind: 'date', ...extra });
24
26
  // Required fields carry Core's asterisk (`required` + `requiredHint`), the
25
27
  // owner's rule (Elena, 2026-09-24, DEVIATIONS #EKC-6): first name, last name or
@@ -54,7 +56,7 @@ export const EKYC_COUNTRY_CATALOG = [
54
56
  {
55
57
  id: 'us-telco-1', country: 'US', label: 'United States', provider: 'Telco 1', stepper: 3,
56
58
  steps: [
57
- { node: '2098:9575', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('US'), t('ssnLast4')] },
59
+ { node: '2098:9575', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('US'), secret('ssnLast4')] },
58
60
  { node: '5854:76222', section: 'license', fields: [t('licenseNumber'), { key: 'licenseState', kind: 'dropdown', placeholder: 'selectState' }] },
59
61
  { node: '5854:76221', section: 'address', fields: US_ADDRESS },
60
62
  ],
@@ -71,7 +73,7 @@ export const EKYC_COUNTRY_CATALOG = [
71
73
  steps: [
72
74
  /* Same First | Last pair as the licence variant; Middle name (Optional)
73
75
  follows it and spans (#EKC-5). */
74
- { node: '2098:9577', fields: [...NAME_PAIR(), t('middleNameOptional'), ...DOB_PHONE('US'), email(), t('ssnOrItin')] },
76
+ { node: '2098:9577', fields: [...NAME_PAIR(), t('middleNameOptional'), ...DOB_PHONE('US'), email(), secret('ssnOrItin')] },
75
77
  { node: '5857:34094', section: 'address', fields: US_ADDRESS },
76
78
  ],
77
79
  },
@@ -90,7 +92,7 @@ export const EKYC_COUNTRY_CATALOG = [
90
92
  {
91
93
  id: 'ca-res-credit', country: 'CA', label: 'Canada', provider: 'Residential credit', stepper: 2,
92
94
  steps: [
93
- { node: '2098:9570', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('CA'), t('ssnOrItin')] },
95
+ { node: '2098:9570', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('CA'), secret('ssnOrItin')] },
94
96
  { node: '5857:36450', section: 'address', fields: CODE_ADDRESS('stateCodeCa') },
95
97
  ],
96
98
  },
@@ -16,11 +16,20 @@ export interface EkycCountryFormScreenProps extends Omit<PrizmaFormScreenProps,
16
16
  values?: EkycCountryValues;
17
17
  /**
18
18
  * The state after Continue was pressed with fields still empty: every empty
19
- * field shows Core's error state and `country.requiredError` under it.
20
- * Continue itself is ALWAYS enabled (Elena, 2026-09-24): validation happens
21
- * on press, not by greying the button.
19
+ * REQUIRED field shows Core's error state and `country.requiredError` under
20
+ * it, and Continue is disabled (GUIDELINES §6-E empty-submit rule, Elena
21
+ * 2026-09-30). Without it Continue stays enabled: validation happens on
22
+ * press, the resting page never greys the button.
22
23
  */
23
24
  showErrors?: boolean;
25
+ /**
26
+ * Field keys whose typed value failed the format check on Continue (an
27
+ * invalid CPF, say). Each keeps its value, shows Core's error state and
28
+ * `country.invalidErrors[key]` under it; Continue stays enabled so the user
29
+ * corrects it and presses again, the CURP precedent (`CurpEnterScreen`'s
30
+ * `invalid`). A key with no message in `invalidErrors` shows no error.
31
+ */
32
+ invalidFields?: string[];
24
33
  /** Dial codes the phone field offers. Runtime data, never copy. */
25
34
  phoneCountries?: PrizmaCountry[];
26
35
  /** Options for the dropdown fields, keyed by field key. Runtime data. */
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { forwardRef } from 'react';
4
4
  import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
5
+ import { PrizmaPassword } from '@incodetech/prizma-core-web/password';
5
6
  import { PrizmaDateField } from '@incodetech/prizma-core-web/date-field';
6
7
  import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
7
8
  import { PrizmaCombined } from '@incodetech/prizma-core-web/combined';
@@ -23,10 +24,13 @@ import './EkycLayout.css';
23
24
  * dropdown, radio pair — so a new country is a catalogue entry, never a file.
24
25
  *
25
26
  * Deliberate, per frame and per owner:
26
- * - Continue is ALWAYS enabled (Elena, 2026-09-24, overriding the grey button
27
- * most frames draw): pressing it with empty fields is the `showErrors` state,
28
- * Core's error ring and message under each empty field. The radio pair has
29
- * no error state in Core, so an empty gender shows none (DEVIATIONS #EKC-4).
27
+ * - Continue is enabled on the resting page (Elena, 2026-09-24, overriding the
28
+ * grey button most frames draw): pressing it with empty fields is the
29
+ * `showErrors` state, Core's error ring and message under each empty
30
+ * required field, and THEN Continue is disabled. The "always enabled" half
31
+ * of DEVIATIONS #EKC-4 is superseded by the 2026-09-30 empty-submit rule
32
+ * (GUIDELINES §6-E). The radio pair has no error state in Core, so an empty
33
+ * gender still shows none.
30
34
  * - ONE title per step (Elena, 2026-09-24): the personal-data step is "Enter
31
35
  * your details" with its subtitle; a licence step is titled "Driver's
32
36
  * License details" and an address step "Address details", with nothing
@@ -53,7 +57,7 @@ const groupRows = (fields) => {
53
57
  }
54
58
  return groups;
55
59
  };
56
- export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, values, showErrors = false, phoneCountries, options, screenId, overrides, onAction, ...shell }, ref) => {
60
+ export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, values, showErrors = false, invalidFields, phoneCountries, options, screenId, overrides, onAction, ...shell }, ref) => {
57
61
  const v = typeof variant === 'string' ? ekycCountryVariant(variant) : variant;
58
62
  if (!v)
59
63
  return null;
@@ -61,14 +65,21 @@ export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, v
61
65
  const c = strings.country;
62
66
  const stepperState = step <= 1 ? 'Start' : Math.min(step, 6);
63
67
  const label = (f) => c.fields[f.key] ?? f.key;
64
- const placeholder = (f) => (f.placeholder ? c.placeholders[f.placeholder] : c.placeholders[f.key]);
68
+ // A lone field in its empty-submit error drops its placeholder, as the
69
+ // single-input screens do (Phone, Email and CURP's `emptyError`).
70
+ const lone = s.fields.length === 1;
71
+ const placeholder = (f) => lone && showErrors && !values?.[f.key] ? undefined : f.placeholder ? c.placeholders[f.placeholder] : c.placeholders[f.key];
65
72
  const renderField = (f) => {
66
73
  // Only a REQUIRED empty field is an error on press (Elena, 2026-09-24,
67
- // DEVIATIONS #EKC-6): an optional field left blank shows nothing.
68
- const missing = showErrors && Boolean(f.required) && !values?.[f.key];
74
+ // DEVIATIONS #EKC-6): an optional field left blank shows nothing. A
75
+ // step's lone field is required by definition, asterisk or not (the
76
+ // single-input rule, as Phone and Email Input: Elena, 2026-10-02).
77
+ const required = Boolean(f.required) || lone;
78
+ const missing = showErrors && required && !values?.[f.key];
79
+ const invalidText = invalidFields?.includes(f.key) ? c.invalidErrors?.[f.key] : undefined;
69
80
  const common = {
70
81
  key: f.key, label: label(f), required: f.required, requiredHint: strings.details.requiredHint,
71
- error: missing || undefined, errorText: missing ? c.requiredError : undefined,
82
+ error: missing || Boolean(invalidText) || undefined, errorText: missing ? c.requiredError : invalidText,
72
83
  };
73
84
  switch (f.kind) {
74
85
  case 'date':
@@ -79,13 +90,20 @@ export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, v
79
90
  }
80
91
  case 'dropdown':
81
92
  return (_jsx(PrizmaDropdown, { ...common, placeholder: placeholder(f), options: options?.[f.key] ?? [], defaultValue: values?.[f.key] ?? null }));
93
+ case 'password':
94
+ /* The SSN / ITIN fields: Core's Password field, hidden by default, the
95
+ eye reveals it. No requirement checklist — this is an identifier,
96
+ not a password to compose (Elena, 2026-09-28). */
97
+ return (_jsx(PrizmaPassword, { ...common, placeholder: placeholder(f), autoComplete: "off", defaultValue: values?.[f.key] ?? '', showPasswordLabel: c.showValueLabel, hidePasswordLabel: c.hideValueLabel, showHelper: false, showRequirements: false }));
82
98
  case 'radio':
83
99
  return (_jsx(PrizmaRadioGroup, { name: f.key, legend: label(f), defaultValue: values?.[f.key], className: "ekyc-country__radios", children: c.genderOptions.map(o => (_jsx(PrizmaRadio, { value: o.value, textSize: "m", showText: true, text: o.label }, o.value))) }, f.key));
84
100
  default:
85
101
  return _jsx(PrizmaTextField, { ...common, placeholder: placeholder(f), autoComplete: "off", defaultValue: values?.[f.key] ?? '' });
86
102
  }
87
103
  };
88
- return (_jsx(PrizmaFormScreen, { ref: ref, scrollFields: true, progress: v.stepper > 0 ? (_jsx(PrizmaStepper, { steps: v.stepper, currentState: stepperState, showIcons: false, "aria-label": strings.stepperLabel })) : undefined, title: s.section === 'address' ? c.addressSection : s.section === 'license' ? c.licenseSection : strings.details.title, subtitle: s.section ? undefined : strings.details.subtitle, linkContent: step > 1 ? (_jsx(PrizmaButton, { variant: "secondary", size: "L", onClick: () => onAction?.({ type: 'secondary', screenId }), children: c.backLabel })) : undefined, ctaLabel: c.ctaLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
104
+ return (_jsx(PrizmaFormScreen, { ref: ref, scrollFields: true, progress: v.stepper > 0 ? (_jsx(PrizmaStepper, { steps: v.stepper, currentState: stepperState, showIcons: false, "aria-label": strings.stepperLabel })) : undefined, title: s.section === 'address' ? c.addressSection : s.section === 'license' ? c.licenseSection : strings.details.title, subtitle: s.section ? undefined : strings.details.subtitle, linkContent: step > 1 ? (_jsx(PrizmaButton, { variant: "secondary", size: "L", onClick: () => onAction?.({ type: 'secondary', screenId }), children: c.backLabel })) : undefined, ctaLabel: c.ctaLabel,
105
+ /* Greyed only after the empty submit (2026-09-30 empty-submit rule). */
106
+ ctaDisabled: showErrors, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
89
107
  /* The eKYB desktop shape: catalogue pairs become two lanes at 420 and
90
108
  up, Back | Continue share a row. The phone stack is untouched. */
91
109
  responsive: true, ...shell, children: groupRows(s.fields).map(group => group.length > 1 ? (_jsx(PrizmaFormRow, { children: group.map(renderField) }, group.map(f => f.key).join('+'))) : (renderField(group[0]))) }));
@@ -52,6 +52,13 @@ export interface EkycDetailsScreenProps extends Omit<PrizmaFormScreenProps, 'tit
52
52
  * the label row Figma draws as "First name *" — so Middle name, the one
53
53
  * optional field, correctly renders none.
54
54
  *
55
+ * `emptyError` is the state after Continue was pressed with the fields still
56
+ * empty (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; drawn precedent
57
+ * `FormsFieldScreen`'s `emptyError`): every field renders empty, the three
58
+ * REQUIRED ones carry Core's `error` + `country.requiredError`, Middle name
59
+ * stays unflagged (DEVIATIONS #EKC-6), and Continue is disabled. The resting
60
+ * `default` keeps Continue enabled so the press can happen at all.
61
+ *
55
62
  * Figma draws a two-segment journey bar between the nav and the title on every
56
63
  * page-1 frame, and it is the only chrome that separates page 1 from page 2.
57
64
  * Archetype E had no slot above the title; the gap is now CLOSED in the
@@ -44,6 +44,13 @@ import './EkycLayout.css';
44
44
  * the label row Figma draws as "First name *" — so Middle name, the one
45
45
  * optional field, correctly renders none.
46
46
  *
47
+ * `emptyError` is the state after Continue was pressed with the fields still
48
+ * empty (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; drawn precedent
49
+ * `FormsFieldScreen`'s `emptyError`): every field renders empty, the three
50
+ * REQUIRED ones carry Core's `error` + `country.requiredError`, Middle name
51
+ * stays unflagged (DEVIATIONS #EKC-6), and Continue is disabled. The resting
52
+ * `default` keeps Continue enabled so the press can happen at all.
53
+ *
47
54
  * Figma draws a two-segment journey bar between the nav and the title on every
48
55
  * page-1 frame, and it is the only chrome that separates page 1 from page 2.
49
56
  * Archetype E had no slot above the title; the gap is now CLOSED in the
@@ -53,11 +60,18 @@ import './EkycLayout.css';
53
60
  * solid brand blue and segment 2 grey, i.e. `steps={2} currentState="Start"`,
54
61
  * with the icons row off (the frames draw the bar alone). DEVIATIONS #EK-1.
55
62
  */
56
- export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', values, screenId = `ekyc-details-${state}`, className, overrides, onAction, ...shell }, ref) => {
63
+ export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', values: valuesProp, screenId = `ekyc-details-${state}`, ctaDisabled, className, overrides, onAction, ...shell }, ref) => {
57
64
  // The one field treatment that varies across the three sections: 2101:8159
58
65
  // rings First name blue and shows its clear ✕; the other two draw every
59
66
  // field resting.
60
67
  const focused = state === 'focused';
68
+ // The empty-submit state: every field empty, required ones in error.
69
+ const emptyError = state === 'emptyError';
70
+ const values = emptyError ? undefined : valuesProp;
71
+ // Core's own error treatment, spread onto the REQUIRED fields only.
72
+ const missing = emptyError
73
+ ? { error: true, errorText: strings.country.requiredError }
74
+ : {};
61
75
  return (_jsxs(PrizmaFormScreen, { ref: ref,
62
76
  /* Scopes the single rule that lets the focused field keep the clear ✕
63
77
  Figma draws inside it. See EkycLayout.css. */
@@ -68,7 +82,10 @@ export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', value
68
82
  `Start` state at `steps={2}`. `showIcons={false}` because the frames
69
83
  draw the bar with no glyph row (and it keeps the icon lookup, which
70
84
  has no id+2 sequence, out of the render entirely). */
71
- progress: _jsx(PrizmaStepper, { steps: 2, currentState: "Start", showIcons: false, "aria-label": strings.stepperLabel }), title: strings.details.title, subtitle: strings.details.subtitle, ctaLabel: strings.details.ctaLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId,
85
+ progress: _jsx(PrizmaStepper, { steps: 2, currentState: "Start", showIcons: false, "aria-label": strings.stepperLabel }), title: strings.details.title, subtitle: strings.details.subtitle, ctaLabel: strings.details.ctaLabel,
86
+ /* Disabled only after the empty submit; the resting page keeps it
87
+ enabled so the press can happen. */
88
+ ctaDisabled: emptyError || ctaDisabled, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId,
72
89
  /* Every eKYC form frame LEFT-aligns its title block, while the archetype
73
90
  defaults to centred. The module supplies its designed alignment through
74
91
  the sanctioned override rather than declaring any layout, and a host
@@ -80,9 +97,9 @@ export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', value
80
97
  now FOLLOWS Last name, on the phone too (First, Last, Middle, Date of
81
98
  birth), the one order change the pairing costs: the frames drew
82
99
  First, Middle, Last. DEVIATIONS #EKC-5. */
83
- responsive: true, ...shell, children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { className: focused ? 'prizma-input-textfield--focused' : undefined, label: strings.details.firstNameLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.firstName ?? '', autoComplete: "off",
100
+ responsive: true, ...shell, children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { className: focused ? 'prizma-input-textfield--focused' : undefined, label: strings.details.firstNameLabel, required: true, requiredHint: strings.details.requiredHint, ...missing, defaultValue: values?.firstName ?? '', autoComplete: "off",
84
101
  /* Only 2101:8159 draws the ✕, and only in this field. */
85
- clearable: focused, clearLabel: strings.details.clearLabel }), _jsx(PrizmaTextField, { label: strings.details.lastNameLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.lastName ?? '', autoComplete: "off", clearable: false })] }), _jsx(PrizmaTextField, { label: strings.details.middleNameLabel, defaultValue: values?.middleName ?? '', autoComplete: "off", clearable: false }), _jsx(PrizmaDateField, { label: strings.details.dateOfBirthLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.dateOfBirth ?? '', autoComplete: "off" })] }));
102
+ clearable: focused, clearLabel: strings.details.clearLabel }), _jsx(PrizmaTextField, { label: strings.details.lastNameLabel, required: true, requiredHint: strings.details.requiredHint, ...missing, defaultValue: values?.lastName ?? '', autoComplete: "off", clearable: false })] }), _jsx(PrizmaTextField, { label: strings.details.middleNameLabel, defaultValue: values?.middleName ?? '', autoComplete: "off", clearable: false }), _jsx(PrizmaDateField, { label: strings.details.dateOfBirthLabel, required: true, requiredHint: strings.details.requiredHint, ...missing, defaultValue: values?.dateOfBirth ?? '', autoComplete: "off" })] }));
86
103
  });
87
104
  EkycDetailsScreen.displayName = 'EkycDetailsScreen';
88
105
  export default EkycDetailsScreen;
@@ -9,8 +9,12 @@
9
9
  * the clear ✕ (2101:8159 draws the OS keyboard over the CTA; the
10
10
  * keyboard is OS chrome, never a screen element)
11
11
  * - `filled` — all four fields resting with their values
12
+ * - `emptyError` — Continue was pressed with the fields empty (GUIDELINES §6-E
13
+ * empty-submit rule, Elena 2026-09-30): the three REQUIRED fields
14
+ * in Core's error state with `country.requiredError`, Middle name
15
+ * (optional) unflagged, and Continue disabled. Not a Figma frame.
12
16
  */
13
- export type EkycDetailsState = 'default' | 'focused' | 'filled';
17
+ export type EkycDetailsState = 'default' | 'focused' | 'filled' | 'emptyError';
14
18
  /**
15
19
  * The page-2 sections (`eKYC.default` / `eKYC.filled` / `eKYC.loading`), same
16
20
  * layout with the document field set:
@@ -109,6 +113,10 @@ export interface EkycStrings {
109
113
  licenseSection: string;
110
114
  /** Accessible name of the phone field's dial-code list. */
111
115
  countryListLabel: string;
116
+ /** Accessible names for the eye on a `password` field (the SSN / ITIN
117
+ * fields): while the value is hidden, and while it is shown. */
118
+ showValueLabel: string;
119
+ hideValueLabel: string;
112
120
  /** The gender radio pair, Argentina. */
113
121
  genderOptions: {
114
122
  value: string;
@@ -119,8 +127,12 @@ export interface EkycStrings {
119
127
  ctaLabel: string;
120
128
  /** The secondary button on a step after the first. */
121
129
  backLabel: string;
122
- /** Under every empty field once Continue was pressed (`showErrors`). */
130
+ /** Under every empty REQUIRED field once Continue was pressed: the
131
+ * by-country `showErrors` state and page 1's `emptyError` both read it. */
123
132
  requiredError: string;
133
+ /** Under a field whose value failed the format check on Continue, by
134
+ * field key (`invalidFields`). `cpf` is SDK key `dynamicForms.invalidCpf`. */
135
+ invalidErrors?: Record<string, string>;
124
136
  };
125
137
  /** `eKYC.processing` — spinner over the status line, no button. */
126
138
  processing: {
@@ -47,6 +47,12 @@
47
47
  border: var(--prizma-border-width-regular) solid var(--prizma-border-neutral-100);
48
48
  border-radius: var(--prizma-border-radius-small);
49
49
  text-align: start;
50
+ /* Air between the frame and the CTA band. Archetype G's list middle carries
51
+ no bottom gap (its content normally ends well above the band), so a frame
52
+ that fills the middle sat flush against Continue (owner-reported
53
+ 2026-09-29). Spacing/16 is the archetype's own scrolling-middle → band gap
54
+ (`.prizma-screen-layout__list--scroll`, ScreenLayout.css). */
55
+ margin-bottom: var(--prizma-spacing-16);
50
56
  }
51
57
 
52
58
  /* The agreement's own typography — Body/M Regular on Text/Body/500, the pair
@@ -8,8 +8,12 @@
8
8
  * - `filled` — focused field with a complete address
9
9
  * - `error` — negative field + clear ✕ + the invalid-format row
10
10
  * - `loading` — field greyed while the code is requested, spinner in the CTA
11
+ * - `emptyError` — "Continue" pressed with the field still empty: the field
12
+ * stays empty (no placeholder) in Core's error state with `requiredError`,
13
+ * and the CTA is disabled. GUIDELINES §6-E empty-submit rule (Elena,
14
+ * 2026-09-30).
11
15
  */
12
- export type EmailInputVerifyState = 'default' | 'typing' | 'filled' | 'error' | 'loading';
16
+ export type EmailInputVerifyState = 'default' | 'typing' | 'filled' | 'error' | 'loading' | 'emptyError';
13
17
  /**
14
18
  * The `email.OTP.*` sections — same layout, different code-field state:
15
19
  * - `typing` — focused field, partial code, CTA enabled, and the line
@@ -27,6 +31,12 @@ export interface EmailInputStrings {
27
31
  verifiedLabel: string;
28
32
  /** Accessible name for the logo nav row. */
29
33
  navLabel?: string;
34
+ /**
35
+ * Error row under the required field when the primary CTA is pressed with it
36
+ * still empty (the verify screen's `emptyError` state): "This field is
37
+ * required". SDK key `dynamicForms.fieldRequired`.
38
+ */
39
+ requiredError: string;
30
40
  /** email.verifyEmail.* */
31
41
  verify: {
32
42
  /** H2 ("Verify your email"). */
@@ -41,6 +41,13 @@ export interface EmailInputVerifyScreenProps extends Omit<PrizmaFormScreenProps,
41
41
  * paints the DISABLED surface (owner definition 2026-07-12), which would grey
42
42
  * the four resting states Figma draws white. The field is uncontrolled instead
43
43
  * (DEVIATIONS #PI-6).
44
+ *
45
+ * `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
46
+ * GUIDELINES §6-E empty-submit rule): "Continue" was pressed with nothing
47
+ * typed. The field renders empty with no placeholder, in Core's error state
48
+ * with `requiredError`, and the primary CTA is disabled, following the drawn
49
+ * precedent in `FormsFieldScreen`. The resting `default` keeps the CTA enabled
50
+ * so the user can reach this state.
44
51
  */
45
52
  export declare const EmailInputVerifyScreen: React.ForwardRefExoticComponent<EmailInputVerifyScreenProps & React.RefAttributes<HTMLDivElement>>;
46
53
  export default EmailInputVerifyScreen;
@@ -31,23 +31,37 @@ import './ScreenLayout.css';
31
31
  * paints the DISABLED surface (owner definition 2026-07-12), which would grey
32
32
  * the four resting states Figma draws white. The field is uncontrolled instead
33
33
  * (DEVIATIONS #PI-6).
34
+ *
35
+ * `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
36
+ * GUIDELINES §6-E empty-submit rule): "Continue" was pressed with nothing
37
+ * typed. The field renders empty with no placeholder, in Core's error state
38
+ * with `requiredError`, and the primary CTA is disabled, following the drawn
39
+ * precedent in `FormsFieldScreen`. The resting `default` keeps the CTA enabled
40
+ * so the user can reach this state.
34
41
  */
35
42
  export const EmailInputVerifyScreen = forwardRef(({ strings, state = 'default', value, screenId = `email-input-verify-${state}`, overrides, onAction, ...shell }, ref) => {
36
43
  const busy = state === 'loading';
37
44
  const invalid = state === 'error';
45
+ // Empty submit: the field drops its placeholder and takes Core's error
46
+ // state with the shared "required" row; the CTA greys.
47
+ const emptyError = state === 'emptyError';
38
48
  const focused = state === 'typing' || state === 'filled';
39
49
  return (_jsx(PrizmaFormScreen, { ref: ref, title: strings.verify.title, subtitle: strings.verify.subtitle, ctaLabel: strings.verify.ctaLabel,
40
50
  /* Figma's `loading` section swaps the CTA label for Core's spinner. */
41
- ctaLoading: busy, ctaLoadingLabel: strings.verify.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx(PrizmaTextField, { className:
51
+ ctaLoading: busy, ctaDisabled: emptyError, ctaLoadingLabel: strings.verify.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx(PrizmaTextField, { className:
42
52
  /* Core's OWN static-focus modifier — the class its CSS already
43
53
  implements beside `:focus-within`. `PrizmaTextField` has no
44
54
  `forceState` prop the way `PrizmaDropdown` does, so this is how a
45
55
  static spec state draws the focus ring without redeclaring any
46
56
  styling (DEVIATIONS #PI-7). */
47
- focused ? 'prizma-input-textfield--focused' : undefined, type: "email", inputMode: "email", autoComplete: "email", "aria-label": strings.verify.fieldLabel, placeholder: strings.verify.placeholder,
57
+ focused ? 'prizma-input-textfield--focused' : undefined, type: "email", inputMode: "email", autoComplete: "email", "aria-label": strings.verify.fieldLabel, placeholder: emptyError ? undefined : strings.verify.placeholder,
48
58
  /* Uncontrolled: a screen is one static spec state, so no `onChange`
49
59
  is needed and no warning is raised. */
50
- defaultValue: value ?? '', disabled: busy, error: invalid, errorText: invalid ? strings.verify.errorText : undefined,
60
+ defaultValue: emptyError ? '' : value ?? '', disabled: busy, error: invalid || emptyError, errorText: invalid
61
+ ? strings.verify.errorText
62
+ : emptyError
63
+ ? strings.requiredError
64
+ : undefined,
51
65
  /* The clear ✕ is drawn on the `error` section only. */
52
66
  clearable: invalid, clearLabel: strings.verify.clearLabel }) }));
53
67
  });
@@ -8,9 +8,9 @@ import './FaceMatchLayout.css';
8
8
  * contract as `camera` on the capture screens) — Face Match never captures. */
9
9
  export type FaceMatchPhotoRole = 'selfie' | 'id' | 'nfc' | 'secondId';
10
10
  /** Host-supplied portraits, one per role the chosen variant asks for. A role
11
- * left out renders the empty state: a Brand/50 disc, marked with
12
- * `.face-match-cluster__photo--empty`. Interim treatment (2026-09-25) while
13
- * the designed no-photo state is drawn; see FaceMatchLayout.css. */
11
+ * left out renders the empty state (capture missing or not retrievable): a
12
+ * grey Surface/Neutral/100 disc under its usual label tooltip, marked with
13
+ * `.face-match-cluster__photo--empty`. See FaceMatchLayout.css. */
14
14
  export type FaceMatchComparisonPhotos = Partial<Record<FaceMatchPhotoRole, ReactNode>>;
15
15
  /**
16
16
  * Where the comparison is in its own choreography — the module MOVING rather
@@ -57,8 +57,14 @@
57
57
 
58
58
  /* The disc's diameter, named once. `--matching`'s rosette and `--animated`'s
59
59
  * convergence both have to know it, and a second literal is a second thing to
60
- * forget. Still a design literal, still DEVIATIONS #FM-22 — just not repeated. */
61
- --face-match-photo: 160px;
60
+ * forget. Still a design literal, still DEVIATIONS #FM-22 — just not repeated.
61
+ *
62
+ * 160 at most, and smaller when two discs and their gap do not fit the
63
+ * content column: on a 320 phone the column is 272, so each disc takes
64
+ * (272 − 16) / 2 = 128 (Camila, 2026-09-28). `100cqi` is the screen shell's
65
+ * width (ScreenShell.css, `container-type: size`), less its two Spacing/24
66
+ * gutters. At 390 and up this resolves to 160, unchanged. */
67
+ --face-match-photo: min(160px, calc((100cqi - 2 * var(--prizma-spacing-24) - var(--prizma-spacing-16)) / 2));
62
68
  /* Half the distance between two neighbouring discs: what one of them has to
63
69
  * travel to reach the pair's centre line. */
64
70
  --face-match-half-step: calc((var(--face-match-photo, 160px) + var(--prizma-spacing-16)) / 2);
@@ -118,16 +124,16 @@
118
124
  background: var(--prizma-surface-neutral-100);
119
125
  }
120
126
 
121
- /* No photo supplied for this role.
127
+ /* No photo for this role: the capture was never taken, or could not be
128
+ * retrieved.
122
129
  *
123
- * INTERIM (2026-09-25, owner call): colour is the whole state for now, so the
124
- * slot reads as a portrait that belongs here rather than an undesigned hole.
125
- * Brand/50 is the same disc PrizmaIllustrationAvatar sits on, which is where
126
- * the designed state is most likely to land. The real treatment (bust,
127
- * outline, or copy) replaces this rule; the modifier and its class name stay,
128
- * so swapping it in is a one-rule change and nothing downstream moves. */
130
+ * Owner call 2026-09-28 (replaces the 2026-09-25 Brand/50 interim): a plain
131
+ * grey disc, Surface/Neutral/100, the same neutral Core puts behind an image
132
+ * that is not there yet (CaptureDigitalId.css, list.css). The callout above it
133
+ * still renders, so the tooltip names which capture is missing. The modifier
134
+ * stays a named state so a richer treatment is still a one-rule change. */
129
135
  .face-match-cluster__photo--empty {
130
- background: var(--prizma-surface-brand-50);
136
+ background: var(--prizma-surface-neutral-100);
131
137
  }
132
138
 
133
139
  .face-match-cluster__photo > img,
@@ -153,11 +159,14 @@
153
159
  }
154
160
 
155
161
  .face-match-cluster--matching .face-match-cluster__face + .face-match-cluster__face {
156
- margin-left: -44px; /* hardcoded — Figma converged overlap; no token (verified absent) */
162
+ /* Figma converged overlap, 44 on the 160 disc; no token (verified absent).
163
+ * Kept as that ratio so the rosette holds its shape on a smaller disc. */
164
+ margin-left: calc(var(--face-match-photo, 160px) * -44 / 160);
157
165
  }
158
166
 
159
167
  .face-match-cluster--matching .face-match-cluster__row + .face-match-cluster__row {
160
- margin-top: calc(-2 * var(--prizma-spacing-40));
168
+ /* Half a disc: 80 on the 160 disc (`2 × Spacing/40`), proportional below it. */
169
+ margin-top: calc(var(--face-match-photo, 160px) / -2);
161
170
  }
162
171
 
163
172
  /* ── The comparison, moving (`phase` on FaceMatchComparisonScreen) ───────────
@@ -129,6 +129,22 @@
129
129
  flex: none;
130
130
  width: calc(50% - var(--prizma-spacing-16) / 2);
131
131
  min-width: 0;
132
+ position: relative;
133
+ }
134
+ /* An error or helper line under ONE field of a pair made that field taller,
135
+ * and the bottom alignment above lifted its input above its neighbour's
136
+ * (Elena, 2026-10-02: "estos inputs tienen que estar alineados"). The line
137
+ * hangs below its input instead, out of the alignment, and the row reserves
138
+ * its height so the next row does not ride over it. */
139
+ .prizma-form-screen--responsive .prizma-form-screen__row > * > [class$="__error"],
140
+ .prizma-form-screen--responsive .prizma-form-screen__row > * > [class$="__helper"] {
141
+ position: absolute;
142
+ top: 100%;
143
+ left: 0;
144
+ margin-top: var(--prizma-spacing-8);
145
+ }
146
+ .prizma-form-screen--responsive .prizma-form-screen__row:has(> * > [class$="__error"], > * > [class$="__helper"]) {
147
+ padding-bottom: calc(var(--prizma-spacing-8) + var(--prizma-body-m-regular-line-height));
132
148
  }
133
149
 
134
150
  /* Continue and Back share a row: Back on the left, Continue on the right, as
@@ -65,6 +65,14 @@ export interface FormsLongFormScreenProps extends Omit<PrizmaFormScreenProps, 't
65
65
  * into the CTA band — the half a module stylesheet kept losing. It is a no-op
66
66
  * on the short `name` preset, whose rows never overflow. DEVIATIONS #DSC-2,
67
67
  * restating #CURP-1.
68
+ *
69
+ * `emptyError` is the GUIDELINES §6-E empty-submit rule (Elena 2026-09-30):
70
+ * the user pressed the CTA with the form still empty. Every REQUIRED row
71
+ * renders empty, with no placeholder, in Core's own error state carrying
72
+ * `strings.requiredError`; optional and unmarked rows stay on their resting
73
+ * surface with no error. The CTA greys there and ONLY there: the resting
74
+ * `default` keeps it enabled, so the user can press it to reach the error —
75
+ * the `FormsFieldScreen` `emptyError` precedent applied to a stack.
68
76
  */
69
77
  export declare const FormsLongFormScreen: React.ForwardRefExoticComponent<FormsLongFormScreenProps & React.RefAttributes<HTMLDivElement>>;
70
78
  export default FormsLongFormScreen;