@smileid/web-components 11.4.5 → 11.6.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 (132) hide show
  1. package/dist/esm/DocumentCaptureScreens-DjSTdVP-.js +5398 -0
  2. package/dist/esm/DocumentCaptureScreens-DjSTdVP-.js.map +1 -0
  3. package/dist/esm/{Navigation-Bb7MPLE8.js → Navigation-6DH3vF4-.js} +28 -22
  4. package/dist/esm/Navigation-6DH3vF4-.js.map +1 -0
  5. package/dist/esm/{PoweredBySmileId-CxbaihMu.js → PoweredBySmileId-DoKwoPUd.js} +424 -6
  6. package/dist/esm/PoweredBySmileId-DoKwoPUd.js.map +1 -0
  7. package/dist/esm/SelfieCaptureScreens-CtX-4Tco.js +11470 -0
  8. package/dist/esm/SelfieCaptureScreens-CtX-4Tco.js.map +1 -0
  9. package/dist/esm/combobox.js +1 -1
  10. package/dist/esm/document.js +1 -1
  11. package/dist/esm/end-user-consent.js +713 -2
  12. package/dist/esm/end-user-consent.js.map +1 -1
  13. package/dist/esm/index-BqyuTk9f.js +1366 -0
  14. package/dist/esm/{index-C4RTMbgw.js.map → index-BqyuTk9f.js.map} +1 -1
  15. package/dist/esm/localisation.js +1 -1
  16. package/dist/esm/main.js +14 -14
  17. package/dist/esm/navigation.js +1 -1
  18. package/dist/esm/package-CjZI-cNQ.js +2540 -0
  19. package/dist/esm/package-CjZI-cNQ.js.map +1 -0
  20. package/dist/esm/selfie.js +1 -1
  21. package/dist/esm/smart-camera-web.js +81 -37
  22. package/dist/esm/smart-camera-web.js.map +1 -1
  23. package/dist/esm/totp-consent.js +731 -2
  24. package/dist/esm/totp-consent.js.map +1 -1
  25. package/dist/esm/validate.js +31 -0
  26. package/dist/esm/validate.js.map +1 -0
  27. package/dist/smart-camera-web.js +1513 -383
  28. package/dist/smart-camera-web.js.map +1 -1
  29. package/dist/types/main.d.ts +18 -1
  30. package/dist/types/validate.d.ts +21 -0
  31. package/lib/components/document/src/DocumentCaptureScreens.js +97 -18
  32. package/lib/components/document/src/assets/lottie.d.ts +12 -0
  33. package/lib/components/document/src/assets/svg-inline.d.ts +8 -0
  34. package/lib/components/document/src/document-auto-capture/DocumentAutoCapture.stories.js +75 -0
  35. package/lib/components/document/src/document-auto-capture/DocumentAutoCapture.tsx +1458 -0
  36. package/lib/components/document/src/document-auto-capture/README.md +73 -0
  37. package/lib/components/document/src/document-auto-capture/assets/Greenbook_Shimmer.svg +42 -0
  38. package/lib/components/document/src/document-auto-capture/assets/ID_Back_Shimmer.svg +8 -0
  39. package/lib/components/document/src/document-auto-capture/assets/ID_Front_Shimmer.svg +20 -0
  40. package/lib/components/document/src/document-auto-capture/assets/Passport-Shimmer.svg +143 -0
  41. package/lib/components/document/src/document-auto-capture/assets/shimmers.ts +21 -0
  42. package/lib/components/document/src/document-auto-capture/assets/svg-raw.d.ts +4 -0
  43. package/lib/components/document/src/document-auto-capture/components/CaptureButton.tsx +122 -0
  44. package/lib/components/document/src/document-auto-capture/components/Overlay.tsx +167 -0
  45. package/lib/components/document/src/document-auto-capture/components/TuningPanel.tsx +856 -0
  46. package/lib/components/document/src/document-auto-capture/constants/captureLayout.ts +58 -0
  47. package/lib/components/document/src/document-auto-capture/detection/cvErrorRecovery.ts +40 -0
  48. package/lib/components/document/src/document-auto-capture/detection/documentAspect.ts +20 -0
  49. package/lib/components/document/src/document-auto-capture/detection/qualityScoring.ts +35 -0
  50. package/lib/components/document/src/document-auto-capture/detection/seamRejection.ts +209 -0
  51. package/lib/components/document/src/document-auto-capture/detection/synthesisTiming.ts +10 -0
  52. package/lib/components/document/src/document-auto-capture/hooks/useCamera.ts +117 -0
  53. package/lib/components/document/src/document-auto-capture/hooks/useCardDetection.ts +3059 -0
  54. package/lib/components/document/src/document-auto-capture/index.ts +4 -0
  55. package/lib/components/document/src/document-auto-capture/theme.ts +40 -0
  56. package/lib/components/document/src/document-auto-capture/utils/debug.ts +25 -0
  57. package/lib/components/document/src/document-auto-capture/utils/opencvLoader.ts +86 -0
  58. package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.tsx +327 -244
  59. package/lib/components/document/src/document-capture-review/DocumentCaptureReview.js +153 -189
  60. package/lib/components/document/src/document-capture-submission/DocumentCaptureSubmission.tsx +432 -0
  61. package/lib/components/document/src/document-capture-submission/index.js +3 -0
  62. package/lib/components/navigation/src/Navigation.js +27 -8
  63. package/lib/components/selfie/README.md +13 -0
  64. package/lib/components/selfie/src/SelfieCaptureScreens.js +56 -8
  65. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieCapture.tsx +684 -0
  66. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieConsent.tsx +71 -0
  67. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieSubmission.tsx +181 -0
  68. package/lib/components/selfie/src/enhanced-smartselfie-capture/OvalProgress.tsx +87 -0
  69. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/Icon.svg +8 -0
  70. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/accessories.svg +77 -0
  71. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/active_liveness_animation.lottie +0 -0
  72. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/device.svg +12 -0
  73. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/device_orientation.lottie +0 -0
  74. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/good.svg +52 -0
  75. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/id-card.svg +9 -0
  76. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/illustrations.tsx +852 -0
  77. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/instructions-img.svg +3 -0
  78. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/multiple-faces.svg +69 -0
  79. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/person.svg +6 -0
  80. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/phone.svg +8 -0
  81. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/poor-lighting.svg +53 -0
  82. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/too_dark_animation.lottie +0 -0
  83. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/ActiveLivenessOverlay.tsx +226 -0
  84. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/AlertDisplay.tsx +38 -0
  85. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/BackNavigation.tsx +45 -0
  86. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CameraPreview.tsx +96 -0
  87. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CaptureControls.tsx +97 -0
  88. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CaptureGuidelines.tsx +374 -0
  89. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/ConsentView.tsx +460 -0
  90. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/SubmissionView.tsx +426 -0
  91. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/index.ts +3 -0
  92. package/lib/components/selfie/src/enhanced-smartselfie-capture/constants.ts +23 -0
  93. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/index.ts +2 -0
  94. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/useCamera.ts +238 -0
  95. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/useFaceCapture.ts +1075 -0
  96. package/lib/components/selfie/src/enhanced-smartselfie-capture/index.ts +1 -0
  97. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/alertMessages.ts +20 -0
  98. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/canvas.ts +108 -0
  99. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/faceDetection.ts +545 -0
  100. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/imageCapture.ts +66 -0
  101. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/imageQuality.ts +151 -0
  102. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/index.ts +5 -0
  103. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/mediapipeManager.ts +215 -0
  104. package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +24 -1
  105. package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +2 -2
  106. package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +15 -7
  107. package/lib/components/selfie/src/smartselfie-capture/utils/canvas.ts +4 -6
  108. package/lib/components/signature-pad/package.json +1 -1
  109. package/lib/components/smart-camera-web/src/README.md +11 -0
  110. package/lib/components/smart-camera-web/src/SmartCameraWeb.js +89 -8
  111. package/lib/components/totp-consent/src/TotpConsent.js +1 -1
  112. package/lib/domain/localisation/index.js +2 -2
  113. package/package.json +9 -5
  114. package/dist/esm/DocumentCaptureScreens-D2G0NOQr.js +0 -4147
  115. package/dist/esm/DocumentCaptureScreens-D2G0NOQr.js.map +0 -1
  116. package/dist/esm/EndUserConsent-uHfA3txP.js +0 -717
  117. package/dist/esm/EndUserConsent-uHfA3txP.js.map +0 -1
  118. package/dist/esm/Navigation-Bb7MPLE8.js.map +0 -1
  119. package/dist/esm/PoweredBySmileId-CxbaihMu.js.map +0 -1
  120. package/dist/esm/SelfieCaptureScreens-Dr7VzON7.js +0 -7651
  121. package/dist/esm/SelfieCaptureScreens-Dr7VzON7.js.map +0 -1
  122. package/dist/esm/TotpConsent-Depzg0ti.js +0 -734
  123. package/dist/esm/TotpConsent-Depzg0ti.js.map +0 -1
  124. package/dist/esm/index-C4RTMbgw.js +0 -1360
  125. package/dist/esm/package-D6YrpMcO.js +0 -565
  126. package/dist/esm/package-D6YrpMcO.js.map +0 -1
  127. package/dist/esm/styles-BTEClL7R.js +0 -419
  128. package/dist/esm/styles-BTEClL7R.js.map +0 -1
  129. /package/lib/components/document/src/assets/lottie/{taking photo of green book passport.lottie → greenbook.lottie} +0 -0
  130. /package/lib/components/document/src/assets/lottie/{taking photo of ID FLIP 2D.lottie → id-card-flip.lottie} +0 -0
  131. /package/lib/components/document/src/assets/lottie/{taking photo of ID.lottie → id-card.lottie} +0 -0
  132. /package/lib/components/document/src/assets/lottie/{taking photo of passport 2.lottie → passport.lottie} +0 -0
@@ -0,0 +1,432 @@
1
+ import { useState, useEffect, useRef } from 'preact/hooks';
2
+ import register from 'preact-custom-element';
3
+ import type { FunctionComponent } from 'preact';
4
+
5
+ import { getBoolProp } from '../../../../utils/props';
6
+ import { t } from '../../../../domain/localisation';
7
+
8
+ import '../../../attribution/PoweredBySmileId';
9
+ import '../../../navigation/src';
10
+
11
+ export type SubmissionState = 'submitting' | 'success' | 'error';
12
+
13
+ interface Props {
14
+ 'theme-color'?: string;
15
+ 'hide-attribution'?: string | boolean;
16
+ /** When set, render the close affordance (smileid-navigation). */
17
+ 'show-navigation'?: string | boolean;
18
+ /** Captured document data URI shown behind the status card. */
19
+ 'image-src'?: string;
20
+ /** Initial submission state. Can be overridden at runtime via attribute or event. */
21
+ 'submission-state'?: SubmissionState;
22
+ /** Optional supporting copy under the title (e.g. failure reason). */
23
+ 'submission-message'?: string;
24
+ }
25
+
26
+ // ── Status overlays (match the Enhanced SmartSelfie visual language) ─────────
27
+
28
+ function Spinner() {
29
+ return (
30
+ <div class="doc-submit-overlay" aria-hidden="true">
31
+ <svg
32
+ class="doc-submit-spinner-svg"
33
+ xmlns="http://www.w3.org/2000/svg"
34
+ width="64"
35
+ height="64"
36
+ viewBox="0 0 64 64"
37
+ fill="none"
38
+ >
39
+ <g clip-path="url(#doc-submit-loader-clip)">
40
+ <foreignObject
41
+ x="-1031.25"
42
+ y="-1031.25"
43
+ width="2062.5"
44
+ height="2062.5"
45
+ transform="matrix(0.032 0 0 0.032 32 32)"
46
+ >
47
+ <div
48
+ {...{ xmlns: 'http://www.w3.org/1999/xhtml' }}
49
+ style="background:conic-gradient(from 90deg,rgba(39,174,96,0) 0deg,rgba(58,225,128,0) 0.036deg,rgba(58,225,128,1) 360deg);height:100%;width:100%;opacity:1"
50
+ />
51
+ </foreignObject>
52
+ </g>
53
+ <path
54
+ fill-rule="evenodd"
55
+ clip-rule="evenodd"
56
+ d="M60.751 25.6018C62.2117 25.4134 63.5486 26.4447 63.737 27.9053C63.9122 29.2632 64 30.6309 64 31.9999C64 33.4727 62.8061 34.6666 61.3334 34.6666C59.8606 34.6666 58.6667 33.4727 58.6667 31.9999C58.6667 30.859 58.5935 29.7193 58.4475 28.5878C58.2591 27.1271 59.2904 25.7903 60.751 25.6018Z"
57
+ fill="#2CC05C"
58
+ />
59
+ <defs>
60
+ <clipPath id="doc-submit-loader-clip">
61
+ <path
62
+ fill-rule="evenodd"
63
+ clip-rule="evenodd"
64
+ d="M32 64C49.6731 64 64 49.6731 64 32C64 14.3269 49.6731 0 32 0C14.3269 0 0 14.3269 0 32C0 49.6731 14.3269 64 32 64ZM32 58.6667C46.7276 58.6667 58.6667 46.7276 58.6667 32C58.6667 17.2724 46.7276 5.33333 32 5.33333C17.2724 5.33333 5.33333 17.2724 5.33333 32C5.33333 46.7276 17.2724 58.6667 32 58.6667Z"
65
+ />
66
+ </clipPath>
67
+ </defs>
68
+ </svg>
69
+ </div>
70
+ );
71
+ }
72
+
73
+ function TickBadge() {
74
+ return (
75
+ <div class="doc-submit-overlay" aria-hidden="true">
76
+ <svg
77
+ xmlns="http://www.w3.org/2000/svg"
78
+ width="64"
79
+ height="64"
80
+ viewBox="0 0 64 64"
81
+ fill="none"
82
+ >
83
+ <rect width="64" height="64" rx="32" fill="#2CC05C" />
84
+ <path
85
+ d="M27.1566 42.6663C26.4724 42.6663 25.7882 42.4088 25.2481 41.8568L19.4503 35.9324C18.9481 35.4131 18.6664 34.7123 18.6664 33.9821C18.6664 33.252 18.9481 32.5512 19.4503 32.0319C20.4946 30.9647 22.2232 30.9647 23.2675 32.0319L27.1566 36.006L40.7327 22.1334C41.777 21.0662 43.5055 21.0662 44.5498 22.1334C45.5941 23.2005 45.5941 24.9668 44.5498 26.0339L29.0652 41.8568C28.525 42.4088 27.8408 42.6663 27.1566 42.6663Z"
86
+ fill="white"
87
+ />
88
+ </svg>
89
+ </div>
90
+ );
91
+ }
92
+
93
+ function CrossBadge() {
94
+ return (
95
+ <div class="doc-submit-overlay" aria-hidden="true">
96
+ <svg
97
+ xmlns="http://www.w3.org/2000/svg"
98
+ width="64"
99
+ height="64"
100
+ viewBox="0 0 64 64"
101
+ fill="none"
102
+ >
103
+ <rect width="64" height="64" rx="32" fill="#EC221F" />
104
+ <path
105
+ d="M36.1146 31.9953L45.0425 22.6828C46.1682 21.5086 46.1682 19.5651 45.0425 18.3909C44.4947 17.8261 43.7555 17.5094 42.9852 17.5094C42.215 17.5094 41.4758 17.8261 40.9279 18.3909L32 27.7035L23.0721 18.3909C22.5242 17.8261 21.785 17.5094 21.0148 17.5094C20.2445 17.5094 19.5053 17.8261 18.9575 18.3909C17.8318 19.5651 17.8318 21.5086 18.9575 22.6828L27.8854 31.9953L18.9575 41.3079C17.8318 42.4821 17.8318 44.4256 18.9575 45.5998C19.5397 46.2071 20.2773 46.4906 21.0148 46.4906C21.7523 46.4906 22.4898 46.2071 23.0721 45.5998L32 36.2872L40.9279 45.5998C41.5102 46.2071 42.2477 46.4906 42.9852 46.4906C43.7227 46.4906 44.4603 46.2071 45.0425 45.5998C46.1682 44.4256 46.1682 42.4821 45.0425 41.3079L36.1146 31.9953Z"
106
+ fill="white"
107
+ />
108
+ </svg>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ /**
114
+ * `<document-capture-submission>` — standalone post-capture submission UI for
115
+ * document flows where the host takes the captured image, uploads it, and
116
+ * wants to show submitting / success / failure cards in our visual language.
117
+ *
118
+ * Mirrors `<enhanced-smart-selfie-submission>`: the host knows the upload
119
+ * outcome and drives this element accordingly.
120
+ *
121
+ * Drive it via either:
122
+ * - attribute updates: `submission-state="submitting|success|error"`,
123
+ * `submission-message`
124
+ * - or window events: `document-capture-submission.set-state`
125
+ * detail: { state, message? }
126
+ *
127
+ * Emits:
128
+ * - `document-capture-submission.continue` (detail: { success }) when the
129
+ * submission resolves (success or error). Hosts can listen to navigate on.
130
+ */
131
+ const DocumentCaptureSubmission: FunctionComponent<Props> = ({
132
+ 'theme-color': themeColor = '#001096',
133
+ 'hide-attribution': hideAttributionProp = false,
134
+ 'show-navigation': showNavigationProp = false,
135
+ 'image-src': imageSrc = '',
136
+ 'submission-state': submissionStateProp = 'submitting',
137
+ 'submission-message': submissionMessage,
138
+ }) => {
139
+ const hideAttribution = getBoolProp(hideAttributionProp);
140
+ const showNavigation = getBoolProp(showNavigationProp);
141
+ const navRef = useRef<HTMLElement | null>(null);
142
+
143
+ const [state, setState] = useState<SubmissionState>(submissionStateProp);
144
+ const [message, setMessage] = useState<string | undefined>(submissionMessage);
145
+
146
+ // Bridge the navigation web component's `navigation.close` (which fires on
147
+ // the element, not bubbling) out to a window event the host can act on.
148
+ useEffect(() => {
149
+ const el = navRef.current;
150
+ if (!el) return undefined;
151
+ const onClose = () => {
152
+ window.dispatchEvent(
153
+ new CustomEvent('document-capture-submission.close'),
154
+ );
155
+ };
156
+ el.addEventListener('navigation.close', onClose);
157
+ return () => el.removeEventListener('navigation.close', onClose);
158
+ }, [showNavigation, state]);
159
+
160
+ // Sync attribute changes from the host through to local state so partners
161
+ // can update the card by setAttribute alone (no event required).
162
+ useEffect(() => {
163
+ setState(submissionStateProp);
164
+ }, [submissionStateProp]);
165
+ useEffect(() => {
166
+ setMessage(submissionMessage);
167
+ }, [submissionMessage]);
168
+
169
+ // Event-driven path: avoids partners having to reach into the shadow DOM
170
+ // to flip attributes. Detail mirrors the attribute names.
171
+ useEffect(() => {
172
+ const handler = (e: Event) => {
173
+ const ce = e as CustomEvent<{
174
+ state?: SubmissionState;
175
+ message?: string;
176
+ }>;
177
+ if (ce.detail?.state) setState(ce.detail.state);
178
+ if (ce.detail?.message !== undefined) setMessage(ce.detail.message);
179
+ };
180
+ window.addEventListener('document-capture-submission.set-state', handler);
181
+ return () => {
182
+ window.removeEventListener(
183
+ 'document-capture-submission.set-state',
184
+ handler,
185
+ );
186
+ };
187
+ }, []);
188
+
189
+ // Emit a resolution event when submission settles so hosts can navigate on.
190
+ // Only fire on a transition INTO a resolved state — never on initial mount
191
+ // (a host may render the element already `success`/`error`) and never on a
192
+ // re-render where the state is unchanged — so the host gets exactly one
193
+ // `.continue` per submission.
194
+ const isResolved = state === 'success' || state === 'error';
195
+ const prevStateRef = useRef<SubmissionState>(submissionStateProp);
196
+ useEffect(() => {
197
+ const prevResolved =
198
+ prevStateRef.current === 'success' || prevStateRef.current === 'error';
199
+ prevStateRef.current = state;
200
+ if (!isResolved || prevResolved) return;
201
+ window.dispatchEvent(
202
+ new CustomEvent('document-capture-submission.continue', {
203
+ detail: { success: state === 'success' },
204
+ }),
205
+ );
206
+ }, [isResolved, state]);
207
+
208
+ const isSubmitting = state === 'submitting';
209
+ const isSuccess = state === 'success';
210
+ const isError = state === 'error';
211
+
212
+ let title = '';
213
+ let body = '';
214
+ if (isSubmitting) {
215
+ title = t('document.submission.submitting.title');
216
+ body = t('document.submission.submitting.body');
217
+ } else if (isSuccess) {
218
+ // Success shows only the title (matches the design). Never surface a
219
+ // leftover `submission-message` from a prior error state.
220
+ title = t('document.submission.success.title');
221
+ body = '';
222
+ } else {
223
+ title = t('document.submission.error.title');
224
+ body = message || '';
225
+ }
226
+
227
+ return (
228
+ <div class="doc-submit-root">
229
+ {showNavigation && (
230
+ <div class="doc-submit-nav">
231
+ {/* @ts-expect-error preact-custom-element types */}
232
+ <smileid-navigation
233
+ ref={navRef}
234
+ theme-color={themeColor}
235
+ show-navigation
236
+ hide-back
237
+ />
238
+ </div>
239
+ )}
240
+ <div class="doc-submit-image-area">
241
+ <div class="doc-submit-image-wrap">
242
+ {imageSrc && (
243
+ <img
244
+ class={`doc-submit-image ${isSubmitting ? 'is-dimmed' : ''}`}
245
+ src={imageSrc}
246
+ alt={t('document.submission.imageAlt')}
247
+ />
248
+ )}
249
+ {isSubmitting && <Spinner />}
250
+ {isSuccess && <TickBadge />}
251
+ {isError && <CrossBadge />}
252
+ </div>
253
+ </div>
254
+
255
+ <div class="doc-submit-footer">
256
+ <div class="doc-submit-card">
257
+ <h1 class="doc-submit-title" style={{ color: themeColor }}>
258
+ {title}
259
+ </h1>
260
+ {isSubmitting ? (
261
+ <p class="doc-submit-body">
262
+ {body.split('\n').map((line, i, arr) => (
263
+ <>
264
+ {line}
265
+ {i < arr.length - 1 && <br />}
266
+ </>
267
+ ))}
268
+ </p>
269
+ ) : (
270
+ body && <p class="doc-submit-body">{body}</p>
271
+ )}
272
+ </div>
273
+
274
+ {!hideAttribution && (
275
+ <div class="doc-submit-attribution">
276
+ {/* @ts-expect-error preact-custom-element types */}
277
+ <powered-by-smile-id />
278
+ </div>
279
+ )}
280
+ </div>
281
+
282
+ <style>{`
283
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
284
+ :host { display: block; width: 100%; height: 100%; }
285
+
286
+ .doc-submit-root {
287
+ font-family: 'DM Sans', system-ui, -apple-system, sans-serif;
288
+ background: #f8fafc;
289
+ display: flex;
290
+ flex-direction: column;
291
+ width: 100%;
292
+ height: 100%;
293
+ min-height: 100%;
294
+ position: relative;
295
+ overflow: hidden;
296
+ }
297
+
298
+ .doc-submit-nav {
299
+ position: absolute;
300
+ top: 16px;
301
+ left: 16px;
302
+ right: 16px;
303
+ z-index: 10;
304
+ }
305
+
306
+ .doc-submit-image-area {
307
+ flex: 1;
308
+ display: flex;
309
+ align-items: center;
310
+ justify-content: center;
311
+ padding: 72px 24px 16px;
312
+ min-height: 0;
313
+ }
314
+
315
+ .doc-submit-image-wrap {
316
+ position: relative;
317
+ display: flex;
318
+ align-items: center;
319
+ justify-content: center;
320
+ max-width: 100%;
321
+ max-height: 100%;
322
+ }
323
+
324
+ .doc-submit-image {
325
+ max-width: 100%;
326
+ max-height: 100%;
327
+ width: auto;
328
+ height: auto;
329
+ border-radius: 12px;
330
+ display: block;
331
+ object-fit: contain;
332
+ transition: filter 0.2s ease;
333
+ }
334
+
335
+ .doc-submit-image.is-dimmed {
336
+ filter: brightness(0.55);
337
+ }
338
+
339
+ /* Centered status overlay (spinner / tick / cross) */
340
+ .doc-submit-overlay {
341
+ position: absolute;
342
+ top: 50%;
343
+ left: 50%;
344
+ transform: translate(-50%, -50%);
345
+ width: 64px;
346
+ height: 64px;
347
+ display: flex;
348
+ align-items: center;
349
+ justify-content: center;
350
+ }
351
+
352
+ .doc-submit-spinner-svg {
353
+ animation: doc-submit-spin 1s linear infinite;
354
+ transform-origin: center;
355
+ }
356
+
357
+ @keyframes doc-submit-spin {
358
+ to { transform: rotate(360deg); }
359
+ }
360
+
361
+ .doc-submit-footer {
362
+ padding: 0 20px 16px;
363
+ display: flex;
364
+ flex-direction: column;
365
+ align-items: center;
366
+ gap: 16px;
367
+ flex-shrink: 0;
368
+ }
369
+
370
+ .doc-submit-card {
371
+ width: 100%;
372
+ max-width: 420px;
373
+ background: #ffffff;
374
+ border-radius: 16px;
375
+ box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08),
376
+ 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
377
+ padding: 28px 20px;
378
+ display: flex;
379
+ flex-direction: column;
380
+ align-items: center;
381
+ gap: 8px;
382
+ text-align: center;
383
+ }
384
+
385
+ .doc-submit-title {
386
+ margin: 0;
387
+ font-size: 1.125rem;
388
+ font-weight: 700;
389
+ }
390
+
391
+ .doc-submit-body {
392
+ margin: 0;
393
+ font-size: 0.8125rem;
394
+ font-weight: 400;
395
+ line-height: 1.35;
396
+ color: #5b6b7b;
397
+ }
398
+
399
+ .doc-submit-attribution {
400
+ display: flex;
401
+ align-items: center;
402
+ justify-content: center;
403
+ }
404
+
405
+ @media (min-width: 640px) {
406
+ .doc-submit-image-area { padding-top: 80px; }
407
+ }
408
+ `}</style>
409
+ </div>
410
+ );
411
+ };
412
+
413
+ if (
414
+ typeof customElements !== 'undefined' &&
415
+ !customElements.get('document-capture-submission')
416
+ ) {
417
+ register(
418
+ DocumentCaptureSubmission,
419
+ 'document-capture-submission',
420
+ [
421
+ 'theme-color',
422
+ 'hide-attribution',
423
+ 'show-navigation',
424
+ 'image-src',
425
+ 'submission-state',
426
+ 'submission-message',
427
+ ],
428
+ { shadow: true },
429
+ );
430
+ }
431
+
432
+ export default DocumentCaptureSubmission;
@@ -0,0 +1,3 @@
1
+ import DocumentCaptureSubmission from './DocumentCaptureSubmission.tsx';
2
+
3
+ export default DocumentCaptureSubmission;
@@ -12,12 +12,19 @@ class Navigation extends HTMLElement {
12
12
  const iconColor = this.hasThemeColor ? this.themeColor : '#FFFFFF';
13
13
  const focusColor = '#FFFFFF';
14
14
 
15
+ let justifyContent = 'flex-end';
16
+ if (this.showBackButton && this.showCloseButton) {
17
+ justifyContent = 'space-between';
18
+ } else if (this.showBackButton) {
19
+ justifyContent = 'flex-start';
20
+ }
21
+
15
22
  const style = document.createElement('style');
16
23
  style.textContent = `
17
24
  :host {
18
25
  display: flex;
19
26
  max-inline-size: 100%;
20
- justify-content: ${this.showBackButton ? 'space-between' : 'flex-end'};
27
+ justify-content: ${justifyContent};
21
28
  direction: ${direction};
22
29
  padding: var(--smileid-navigation-padding, ${hostPadding});
23
30
  gap: 1rem;
@@ -135,23 +142,31 @@ button svg {
135
142
 
136
143
  shadow.appendChild(style);
137
144
  if (this.showBackButton) shadow.appendChild(backButton);
138
- shadow.appendChild(closeButton);
145
+ if (this.showCloseButton) shadow.appendChild(closeButton);
139
146
 
140
147
  // Set language direction attribute on host for CSS selectors
141
148
  this.setAttribute('dir', direction);
142
149
 
143
150
  // Back Button Controls
144
- this.backButton = backButton;
145
- this.backButton.addEventListener('click', () => this.handleBack());
151
+ if (this.showBackButton) {
152
+ this.backButton = backButton;
153
+ this.backButton.addEventListener('click', () => this.handleBack());
154
+ }
146
155
 
147
156
  // Close Button Controls
148
- this.closeButton = closeButton;
149
- this.closeButton.addEventListener('click', () => this.handleClose());
157
+ if (this.showCloseButton) {
158
+ this.closeButton = closeButton;
159
+ this.closeButton.addEventListener('click', () => this.handleClose());
160
+ }
150
161
  }
151
162
 
152
163
  disconnectedCallback() {
153
- this.backButton.removeEventListener('click', () => this.handleBack());
154
- this.closeButton.removeEventListener('click', () => this.handleClose());
164
+ if (this.backButton) {
165
+ this.backButton.removeEventListener('click', () => this.handleBack());
166
+ }
167
+ if (this.closeButton) {
168
+ this.closeButton.removeEventListener('click', () => this.handleClose());
169
+ }
155
170
  }
156
171
 
157
172
  handleBack() {
@@ -166,6 +181,10 @@ button svg {
166
181
  return !this.hasAttribute('hide-back');
167
182
  }
168
183
 
184
+ get showCloseButton() {
185
+ return !this.hasAttribute('hide-close');
186
+ }
187
+
169
188
  get themeColor() {
170
189
  return this.getAttribute('theme-color') || '#001096';
171
190
  }
@@ -44,6 +44,19 @@ Usage:
44
44
  <selfie-capture-screens show-navigation></selfie-capture-screens>
45
45
  ```
46
46
 
47
+ #### use-strict-mode
48
+
49
+ This attribute enables Enhanced SmartSelfie (strict-mode capture flow).
50
+
51
+ Usage:
52
+
53
+ ```html
54
+ <selfie-capture-screens use-strict-mode="true"></selfie-capture-screens>
55
+ ```
56
+
57
+ `allow-legacy-selfie-fallback` is also available as an optional compatibility
58
+ attribute.
59
+
47
60
  ### Permissions
48
61
 
49
62
  The `SelfieCaptureScreens` component requires camera permissions to function. It will automatically request these permissions when used. If the permissions are granted, it will remove the `data-camera-error` attribute from the capture screen and set the `data-camera-ready` attribute to true. If the permissions are denied, it will remove the `data-camera-ready` attribute and set the `data-camera-error` attribute with the error message.
@@ -11,6 +11,15 @@ const COMPONENTS_VERSION = packageJson.version;
11
11
 
12
12
  const smartCameraWeb = document.querySelector('smart-camera-web');
13
13
 
14
+ /**
15
+ * Minimum-correct HTML attribute escape. `&` must be replaced first so the
16
+ * subsequent `"` -> `&quot;` substitution isn't double-encoded. Used by every
17
+ * getter that interpolates a partner-supplied value into the `innerHTML`
18
+ * template in `connectedCallback` — without this, a value containing `"`
19
+ * (or `&`) would break out of the attribute and inject markup.
20
+ */
21
+ const escAttr = (s) => String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
22
+
14
23
  const cropImageFromDataUri = (dataUri, cropPercentX = 0, cropPercentY = 0) =>
15
24
  new Promise((resolve, reject) => {
16
25
  if (!dataUri || typeof dataUri !== 'string') {
@@ -83,9 +92,9 @@ class SelfieCaptureScreens extends HTMLElement {
83
92
  this.innerHTML = `
84
93
  ${styles(this.themeColor)}
85
94
  <div style="height: 100%;">
86
- <selfie-capture-instructions theme-color='${this.themeColor}' ${this.showNavigation} ${this.hideAttribution} ${this.hideBack} hidden></selfie-capture-instructions>
87
- <selfie-capture-wrapper theme-color='${this.themeColor}' ${this.showNavigation} ${this.allowAgentMode} ${this.allowAgentModeTests} ${this.hideAttribution} ${this.disableImageTests} ${this.allowLegacySelfieFallback} key="${this._remountKey}" start-countdown="false" hidden></selfie-capture-wrapper>
88
- <selfie-capture-review theme-color='${this.themeColor}' ${this.showNavigation} ${this.hideAttribution} hidden></selfie-capture-review>
95
+ <selfie-capture-instructions theme-color="${escAttr(this.themeColor)}" ${this.showNavigation} ${this.hideAttribution} ${this.hideBack} hidden></selfie-capture-instructions>
96
+ <selfie-capture-wrapper theme-color="${escAttr(this.themeColor)}" ${this.showNavigation} ${this.allowAgentMode} ${this.allowAgentModeTests} ${this.hideAttribution} ${this.disableImageTests} ${this.allowLegacySelfieFallback} ${this.useStrictMode} ${this.showBackOnGuidelines} key="${this._remountKey}" start-countdown="false" hidden></selfie-capture-wrapper>
97
+ <selfie-capture-review theme-color="${escAttr(this.themeColor)}" ${this.showNavigation} ${this.hideAttribution} hidden></selfie-capture-review>
89
98
  </div>
90
99
  `;
91
100
 
@@ -101,7 +110,11 @@ class SelfieCaptureScreens extends HTMLElement {
101
110
 
102
111
  if (
103
112
  this.getAttribute('initial-screen') === 'selfie-capture' ||
104
- this.hideInstructions
113
+ this.hideInstructions ||
114
+ // In strict mode the modern `enhanced-smartselfie-capture` element
115
+ // renders its own guidelines screen, so we skip the legacy
116
+ // `selfie-capture-instructions` element entirely.
117
+ this.isStrictMode
105
118
  ) {
106
119
  this.setActiveScreen(this.selfieCapture);
107
120
  } else {
@@ -346,7 +359,7 @@ class SelfieCaptureScreens extends HTMLElement {
346
359
  // Force remount of selfie-capture-wrapper for clean state on next visit
347
360
  await this.forceWrapperRemount();
348
361
 
349
- if (this.hideInstructions) {
362
+ if (this.hideInstructions || this.isStrictMode) {
350
363
  this.handleBackEvents();
351
364
  return;
352
365
  }
@@ -367,6 +380,18 @@ class SelfieCaptureScreens extends HTMLElement {
367
380
  smartCameraWeb?.dispatchEvent(
368
381
  new CustomEvent('metadata.selfie-capture-end'),
369
382
  );
383
+ this._data.images = event.detail.images;
384
+ SmartCamera.stopMedia();
385
+
386
+ // In strict mode (Enhanced SmartSelfie), the ESS component already
387
+ // shows its own review screen and only re-dispatches `publish` after
388
+ // the user confirms. Skip the legacy `selfie-capture-review` step and
389
+ // publish straight up to the host page.
390
+ if (this.isStrictMode) {
391
+ this._publishSelectedImages();
392
+ return;
393
+ }
394
+
370
395
  this.selfieReview.setAttribute(
371
396
  'data-image',
372
397
  await cropImageFromDataUri(event.detail.referenceImage, 20, 20),
@@ -376,8 +401,6 @@ class SelfieCaptureScreens extends HTMLElement {
376
401
  'mirror-image',
377
402
  shouldMirror ? 'true' : 'false',
378
403
  );
379
- this._data.images = event.detail.images;
380
- SmartCamera.stopMedia();
381
404
  this.setActiveScreen(this.selfieReview);
382
405
  };
383
406
 
@@ -450,10 +473,31 @@ class SelfieCaptureScreens extends HTMLElement {
450
473
 
451
474
  get allowLegacySelfieFallback() {
452
475
  return this.hasAttribute('allow-legacy-selfie-fallback')
453
- ? `allow-legacy-selfie-fallback='${this.getAttribute('allow-legacy-selfie-fallback')}'`
476
+ ? `allow-legacy-selfie-fallback="${escAttr(this.getAttribute('allow-legacy-selfie-fallback'))}"`
454
477
  : '';
455
478
  }
456
479
 
480
+ get useStrictMode() {
481
+ return this.hasAttribute('use-strict-mode') &&
482
+ this.getAttribute('use-strict-mode') !== 'false'
483
+ ? 'use-strict-mode="true"'
484
+ : '';
485
+ }
486
+
487
+ get showBackOnGuidelines() {
488
+ return this.hasAttribute('show-back-on-guidelines')
489
+ ? 'show-back-on-guidelines="true"'
490
+ : '';
491
+ }
492
+
493
+ /** Boolean form of `use-strict-mode` for runtime checks. */
494
+ get isStrictMode() {
495
+ return (
496
+ this.hasAttribute('use-strict-mode') &&
497
+ this.getAttribute('use-strict-mode') !== 'false'
498
+ );
499
+ }
500
+
457
501
  get themeColor() {
458
502
  return this.getAttribute('theme-color') || '#001096';
459
503
  }
@@ -477,6 +521,8 @@ class SelfieCaptureScreens extends HTMLElement {
477
521
  'allow-legacy-selfie-fallback',
478
522
  'show-agent-mode-for-tests',
479
523
  'disable-image-tests',
524
+ 'use-strict-mode',
525
+ 'show-back-on-guidelines',
480
526
  ];
481
527
  }
482
528
 
@@ -489,6 +535,8 @@ class SelfieCaptureScreens extends HTMLElement {
489
535
  case 'allow-legacy-selfie-fallback':
490
536
  case 'show-agent-mode-for-tests':
491
537
  case 'disable-image-tests':
538
+ case 'use-strict-mode':
539
+ case 'show-back-on-guidelines':
492
540
  this.connectedCallback();
493
541
  break;
494
542
  default: