@smileid/web-components 2.0.1 → 10.0.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 (125) hide show
  1. package/dist/DocumentCaptureScreens-Dwl7UqVH.js +1534 -0
  2. package/dist/DocumentCaptureScreens-Dwl7UqVH.js.map +1 -0
  3. package/dist/EndUserConsent-C5hZdJzH.js +715 -0
  4. package/dist/EndUserConsent-C5hZdJzH.js.map +1 -0
  5. package/dist/Navigation-juBE4qOw.js +136 -0
  6. package/dist/Navigation-juBE4qOw.js.map +1 -0
  7. package/dist/PoweredBySmileId-CxbaihMu.js +33 -0
  8. package/dist/PoweredBySmileId-CxbaihMu.js.map +1 -0
  9. package/dist/SelfieCaptureScreens-CQc251hz.js +7618 -0
  10. package/dist/SelfieCaptureScreens-CQc251hz.js.map +1 -0
  11. package/dist/SignaturePad-C7MtmT8m.js +324 -0
  12. package/dist/SignaturePad-C7MtmT8m.js.map +1 -0
  13. package/dist/TotpConsent-CQU5jQi4.js +730 -0
  14. package/dist/TotpConsent-CQU5jQi4.js.map +1 -0
  15. package/dist/combobox.js +300 -0
  16. package/dist/combobox.js.map +1 -0
  17. package/dist/document.js +5 -0
  18. package/dist/document.js.map +1 -0
  19. package/dist/end-user-consent.js +5 -0
  20. package/dist/end-user-consent.js.map +1 -0
  21. package/dist/main.js +22 -0
  22. package/dist/main.js.map +1 -0
  23. package/dist/navigation.js +5 -0
  24. package/dist/navigation.js.map +1 -0
  25. package/dist/package-Oi2Yil3b.js +105 -0
  26. package/dist/package-Oi2Yil3b.js.map +1 -0
  27. package/dist/selfie.js +5 -0
  28. package/dist/selfie.js.map +1 -0
  29. package/dist/signature-pad.js +5 -0
  30. package/dist/signature-pad.js.map +1 -0
  31. package/dist/smart-camera-web.js +303 -0
  32. package/dist/smart-camera-web.js.map +1 -0
  33. package/dist/styles-BUWNxWeQ.js +406 -0
  34. package/dist/styles-BUWNxWeQ.js.map +1 -0
  35. package/dist/totp-consent.js +5 -0
  36. package/dist/totp-consent.js.map +1 -0
  37. package/dist/types/combobox.d.ts +21 -0
  38. package/dist/types/document.d.ts +21 -0
  39. package/dist/types/end-user-consent.d.ts +21 -0
  40. package/dist/types/main.d.ts +331 -0
  41. package/dist/types/navigation.d.ts +21 -0
  42. package/dist/types/selfie.d.ts +21 -0
  43. package/dist/types/signature-pad.d.ts +21 -0
  44. package/dist/types/smart-camera-web.d.ts +21 -0
  45. package/dist/types/totp-consent.d.ts +21 -0
  46. package/{src → lib}/components/README.md +14 -14
  47. package/{src → lib}/components/attribution/PoweredBySmileId.js +42 -42
  48. package/{src → lib}/components/camera-permission/CameraPermission.js +140 -140
  49. package/{src → lib}/components/camera-permission/CameraPermission.stories.js +27 -27
  50. package/{src → lib}/components/combobox/src/Combobox.js +589 -589
  51. package/{src → lib}/components/combobox/src/index.js +1 -1
  52. package/{src → lib}/components/document/src/DocumentCaptureScreens.js +409 -409
  53. package/{src → lib}/components/document/src/DocumentCaptureScreens.stories.js +57 -57
  54. package/{src → lib}/components/document/src/README.md +111 -111
  55. package/{src → lib}/components/document/src/document-capture/DocumentCapture.js +760 -760
  56. package/{src → lib}/components/document/src/document-capture/DocumentCapture.stories.js +78 -78
  57. package/{src → lib}/components/document/src/document-capture/README.md +90 -90
  58. package/{src → lib}/components/document/src/document-capture/index.js +3 -3
  59. package/{src → lib}/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +499 -499
  60. package/{src → lib}/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +24 -24
  61. package/{src → lib}/components/document/src/document-capture-instructions/README.md +56 -56
  62. package/{src → lib}/components/document/src/document-capture-instructions/index.js +3 -3
  63. package/{src → lib}/components/document/src/document-capture-review/DocumentCaptureReview.js +362 -362
  64. package/{src → lib}/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +24 -24
  65. package/{src → lib}/components/document/src/document-capture-review/README.md +79 -79
  66. package/{src → lib}/components/document/src/document-capture-review/index.js +3 -3
  67. package/{src → lib}/components/document/src/index.js +3 -3
  68. package/{src → lib}/components/end-user-consent/src/EndUserConsent.js +795 -795
  69. package/{src → lib}/components/end-user-consent/src/EndUserConsent.stories.js +29 -29
  70. package/{src → lib}/components/end-user-consent/src/index.js +4 -4
  71. package/{src → lib}/components/navigation/src/Navigation.js +171 -171
  72. package/{src → lib}/components/navigation/src/Navigation.stories.js +24 -24
  73. package/{src → lib}/components/navigation/src/index.js +3 -3
  74. package/{src → lib}/components/selfie/README.md +225 -225
  75. package/{src → lib}/components/selfie/src/SelfieCaptureScreens.js +433 -282
  76. package/{src → lib}/components/selfie/src/SelfieCaptureScreens.stories.js +29 -29
  77. package/{src → lib}/components/selfie/src/index.js +3 -5
  78. package/{src → lib}/components/selfie/src/selfie-capture/SelfieCapture.js +1041 -1010
  79. package/{src → lib}/components/selfie/src/selfie-capture/SelfieCapture.stories.js +36 -36
  80. package/{src → lib}/components/selfie/src/selfie-capture/index.js +3 -3
  81. package/{src → lib}/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +657 -648
  82. package/{src → lib}/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +23 -23
  83. package/{src → lib}/components/selfie/src/selfie-capture-instructions/index.js +3 -3
  84. package/{src → lib}/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +340 -347
  85. package/{src → lib}/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +24 -24
  86. package/{src → lib}/components/selfie/src/selfie-capture-review/index.js +3 -3
  87. package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +227 -0
  88. package/lib/components/selfie/src/selfie-capture-wrapper/index.ts +1 -0
  89. package/lib/components/selfie/src/smartselfie-capture/OvalProgress.tsx +81 -0
  90. package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +224 -0
  91. package/lib/components/selfie/src/smartselfie-capture/components/AlertDisplay.tsx +34 -0
  92. package/lib/components/selfie/src/smartselfie-capture/components/CameraPreview.tsx +97 -0
  93. package/lib/components/selfie/src/smartselfie-capture/components/CaptureControls.tsx +74 -0
  94. package/lib/components/selfie/src/smartselfie-capture/components/index.ts +3 -0
  95. package/lib/components/selfie/src/smartselfie-capture/constants.ts +23 -0
  96. package/lib/components/selfie/src/smartselfie-capture/hooks/index.ts +2 -0
  97. package/lib/components/selfie/src/smartselfie-capture/hooks/useCamera.ts +94 -0
  98. package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +558 -0
  99. package/lib/components/selfie/src/smartselfie-capture/index.ts +1 -0
  100. package/lib/components/selfie/src/smartselfie-capture/utils/alertMessages.ts +12 -0
  101. package/lib/components/selfie/src/smartselfie-capture/utils/canvas.ts +105 -0
  102. package/lib/components/selfie/src/smartselfie-capture/utils/faceDetection.ts +129 -0
  103. package/lib/components/selfie/src/smartselfie-capture/utils/imageCapture.ts +64 -0
  104. package/lib/components/selfie/src/smartselfie-capture/utils/index.ts +4 -0
  105. package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +60 -0
  106. package/{src → lib}/components/signature-pad/package-lock.json +3009 -3009
  107. package/{src → lib}/components/signature-pad/package.json +30 -30
  108. package/{src → lib}/components/signature-pad/src/SignaturePad.js +484 -484
  109. package/{src → lib}/components/signature-pad/src/SignaturePad.stories.js +32 -32
  110. package/{src → lib}/components/signature-pad/src/index.js +3 -3
  111. package/{src → lib}/components/smart-camera-web/src/README.md +206 -207
  112. package/{src → lib}/components/smart-camera-web/src/SmartCameraWeb.js +299 -299
  113. package/{src → lib}/components/smart-camera-web/src/SmartCameraWeb.stories.js +57 -57
  114. package/{src → lib}/components/totp-consent/src/TotpConsent.js +949 -949
  115. package/{src → lib}/components/totp-consent/src/index.js +4 -4
  116. package/{src → lib}/domain/camera/src/README.md +38 -38
  117. package/{src → lib}/domain/camera/src/SmartCamera.js +109 -109
  118. package/{src → lib}/domain/constants/src/Constants.js +27 -27
  119. package/{src → lib}/domain/file-upload/README.md +35 -35
  120. package/{src → lib}/domain/file-upload/src/SmartFileUpload.js +65 -65
  121. package/{src → lib}/styles/README.md +3 -3
  122. package/{src → lib}/styles/src/styles.js +359 -359
  123. package/{src → lib}/styles/src/typography.js +52 -52
  124. package/package.json +109 -58
  125. package/src/index.js +0 -5
@@ -1,24 +1,24 @@
1
- import './SelfieCaptureReview';
2
-
3
- const meta = {
4
- argTypes: {
5
- 'theme-color': { control: 'color' },
6
- },
7
- component: 'selfie-capture-review',
8
- };
9
-
10
- export default meta;
11
-
12
- export const SelfieCaptureReview = {
13
- args: {
14
- 'theme-color': '#001096',
15
- },
16
- render: (args) => `
17
- <selfie-capture-review
18
- show-navigation
19
- data-image="https://placehold.co/600x400"
20
- theme-color='${args['theme-color']}'
21
- >
22
- </selfie-capture-review>
23
- `,
24
- };
1
+ import './SelfieCaptureReview';
2
+
3
+ const meta = {
4
+ argTypes: {
5
+ 'theme-color': { control: 'color' },
6
+ },
7
+ component: 'selfie-capture-review',
8
+ };
9
+
10
+ export default meta;
11
+
12
+ export const SelfieCaptureReview = {
13
+ args: {
14
+ 'theme-color': '#001096',
15
+ },
16
+ render: (args) => `
17
+ <selfie-capture-review
18
+ show-navigation
19
+ data-image="https://placehold.co/600x400"
20
+ theme-color='${args['theme-color']}'
21
+ >
22
+ </selfie-capture-review>
23
+ `,
24
+ };
@@ -1,3 +1,3 @@
1
- import SelfieCaptureReview from './SelfieCaptureReview';
2
-
3
- export default SelfieCaptureReview;
1
+ import SelfieCaptureReview from './SelfieCaptureReview';
2
+
3
+ export default SelfieCaptureReview;
@@ -0,0 +1,227 @@
1
+ import { useState, useEffect } from 'preact/hooks';
2
+ import { IconLoader2 } from '@tabler/icons-preact';
3
+ import register from 'preact-custom-element';
4
+ import type { FunctionComponent } from 'preact';
5
+
6
+ import { getBoolProp } from '@/utils/props';
7
+ import SmartSelfieCapture from '../smartselfie-capture/SmartSelfieCapture';
8
+ import '../selfie-capture/SelfieCapture';
9
+ import { getMediapipeInstance } from '../smartselfie-capture/utils/mediapipeManager';
10
+
11
+ declare const h: any;
12
+
13
+ interface Props {
14
+ timeout?: number;
15
+ interval?: number;
16
+ duration?: number;
17
+ 'theme-color'?: string;
18
+ 'show-navigation'?: string | boolean;
19
+ 'allow-agent-mode'?: string | boolean;
20
+ 'show-agent-mode-for-tests'?: string | boolean;
21
+ 'hide-attribution'?: string | boolean;
22
+ 'disable-image-tests'?: string | boolean;
23
+ key?: string;
24
+ 'start-countdown'?: string | boolean;
25
+ hidden?: string | boolean;
26
+ }
27
+
28
+ const SelfieCaptureWrapper: FunctionComponent<Props> = ({
29
+ timeout = 10000,
30
+ 'start-countdown': startCountdownProp = false,
31
+ hidden: hiddenProp = false,
32
+ ...props
33
+ }) => {
34
+ const hidden = getBoolProp(hiddenProp);
35
+ const startCountdown = getBoolProp(startCountdownProp);
36
+ const [mediapipeReady, setMediapipeReady] = useState(false);
37
+ const [loadingProgress, setLoadingProgress] = useState(0);
38
+ const [initialSessionCompleted, setInitialSessionCompleted] = useState(false);
39
+ const [mediapipeLoading, setMediapipeLoading] = useState(false);
40
+ const [usingSelfieCapture, setUsingSelfieCapture] = useState(false);
41
+
42
+ useEffect(() => {
43
+ if (mediapipeReady || mediapipeLoading) return;
44
+
45
+ const loadMediapipe = async () => {
46
+ setMediapipeLoading(true);
47
+ try {
48
+ await getMediapipeInstance();
49
+ setMediapipeReady(true);
50
+ } catch (error) {
51
+ console.error('Failed to load Mediapipe:', error);
52
+ }
53
+ setMediapipeLoading(false);
54
+ };
55
+
56
+ loadMediapipe();
57
+ }, [mediapipeReady, mediapipeLoading]);
58
+
59
+ useEffect(() => {
60
+ if (hidden || !startCountdown || mediapipeReady) return undefined;
61
+
62
+ const timer = setInterval(() => {
63
+ setLoadingProgress((prev: number) => Math.min(prev + 1, 100));
64
+ }, timeout / 100);
65
+
66
+ return () => {
67
+ clearInterval(timer);
68
+ };
69
+ }, [hidden, startCountdown, timeout, mediapipeReady]);
70
+
71
+ useEffect(() => {
72
+ if (hidden || mediapipeReady || loadingProgress < 100) return undefined;
73
+
74
+ const setupEventForwarding = () => {
75
+ const selfieCapture = document.querySelector('selfie-capture');
76
+ if (
77
+ selfieCapture &&
78
+ !selfieCapture.hasAttribute('data-backup-events-setup')
79
+ ) {
80
+ selfieCapture.setAttribute('data-backup-events-setup', 'true');
81
+
82
+ const forwardEvent = (event: Event) => {
83
+ const customEvent = event as CustomEvent;
84
+ window.dispatchEvent(
85
+ new CustomEvent(customEvent.type, {
86
+ detail: customEvent.detail,
87
+ bubbles: true,
88
+ }),
89
+ );
90
+ };
91
+
92
+ selfieCapture.addEventListener('selfie-capture.publish', forwardEvent);
93
+ selfieCapture.addEventListener(
94
+ 'selfie-capture.cancelled',
95
+ forwardEvent,
96
+ );
97
+ selfieCapture.addEventListener('selfie-capture.close', forwardEvent);
98
+
99
+ return () => {
100
+ selfieCapture.removeEventListener(
101
+ 'selfie-capture.publish',
102
+ forwardEvent,
103
+ );
104
+ selfieCapture.removeEventListener(
105
+ 'selfie-capture.cancelled',
106
+ forwardEvent,
107
+ );
108
+ selfieCapture.removeEventListener(
109
+ 'selfie-capture.close',
110
+ forwardEvent,
111
+ );
112
+ };
113
+ }
114
+ return undefined;
115
+ };
116
+
117
+ const timeoutId = setTimeout(setupEventForwarding, 200);
118
+ return () => {
119
+ clearTimeout(timeoutId);
120
+ };
121
+ }, [hidden, mediapipeReady, loadingProgress]);
122
+
123
+ if (hidden) {
124
+ return null;
125
+ }
126
+
127
+ // on retakes, prefer SmartSelfieCapture if Mediapipe is ready
128
+ if (initialSessionCompleted && mediapipeReady && !usingSelfieCapture) {
129
+ return <SmartSelfieCapture {...props} />;
130
+ }
131
+
132
+ // use SmartSelfieCapture if mediapipe loads
133
+ if (!initialSessionCompleted && mediapipeReady && !usingSelfieCapture) {
134
+ return <SmartSelfieCapture {...props} />;
135
+ }
136
+
137
+ if (loadingProgress >= 100) {
138
+ if (!usingSelfieCapture) {
139
+ setUsingSelfieCapture(true);
140
+ }
141
+
142
+ const propsWithoutHidden = { ...props };
143
+ delete (propsWithoutHidden as any).hidden;
144
+
145
+ const selfieCapture = h('selfie-capture', {
146
+ ...propsWithoutHidden,
147
+ ref: (el: HTMLElement) => {
148
+ if (el && !el.hasAttribute('data-events-setup')) {
149
+ el.setAttribute('data-events-setup', 'true');
150
+
151
+ const forwardEvent = (event: Event) => {
152
+ const customEvent = event as CustomEvent;
153
+
154
+ if (
155
+ customEvent.type === 'selfie-capture.publish' ||
156
+ customEvent.type === 'selfie-capture.cancelled' ||
157
+ customEvent.type === 'selfie-capture.close'
158
+ ) {
159
+ setInitialSessionCompleted(true);
160
+ }
161
+
162
+ window.dispatchEvent(
163
+ new CustomEvent(customEvent.type, {
164
+ detail: customEvent.detail,
165
+ bubbles: true,
166
+ }),
167
+ );
168
+ };
169
+
170
+ el.addEventListener('selfie-capture.publish', forwardEvent);
171
+ el.addEventListener('selfie-capture.cancelled', forwardEvent);
172
+ el.addEventListener('selfie-capture.close', forwardEvent);
173
+ }
174
+ },
175
+ });
176
+
177
+ return selfieCapture;
178
+ }
179
+
180
+ return (
181
+ <div style={{ textAlign: 'center', marginTop: '20%' }}>
182
+ <div
183
+ style={{
184
+ marginBottom: '16px',
185
+ display: 'flex',
186
+ justifyContent: 'center',
187
+ }}
188
+ >
189
+ <IconLoader2
190
+ size={48}
191
+ style={{ animation: 'spin 1s linear infinite' }}
192
+ />
193
+ </div>
194
+ <p>Loading... {loadingProgress}%</p>
195
+ <style>{`
196
+ @keyframes spin {
197
+ from { transform: rotate(0deg); }
198
+ to { transform: rotate(360deg); }
199
+ }
200
+ `}</style>
201
+ </div>
202
+ );
203
+ };
204
+
205
+ if (!customElements.get('selfie-capture-wrapper')) {
206
+ register(
207
+ SelfieCaptureWrapper,
208
+ 'selfie-capture-wrapper',
209
+ [
210
+ 'timeout',
211
+ 'interval',
212
+ 'duration',
213
+ 'theme-color',
214
+ 'show-navigation',
215
+ 'allow-agent-mode',
216
+ 'show-agent-mode-for-tests',
217
+ 'hide-attribution',
218
+ 'disable-image-tests',
219
+ 'key',
220
+ 'start-countdown',
221
+ 'hidden',
222
+ ],
223
+ { shadow: true },
224
+ );
225
+ }
226
+
227
+ export default SelfieCaptureWrapper;
@@ -0,0 +1 @@
1
+ export { default as SelfieCaptureWrapper } from './SelfieCaptureWrapper';
@@ -0,0 +1,81 @@
1
+ import { useEffect, useRef } from 'preact/hooks';
2
+
3
+ interface OvalProgressProps {
4
+ progress: number;
5
+ duration?: number;
6
+ themeColor?: string;
7
+ }
8
+
9
+ const OvalProgress = ({
10
+ progress,
11
+ duration = 1000,
12
+ themeColor = '#001096',
13
+ }: OvalProgressProps) => {
14
+ const pathRef = useRef<SVGPathElement>(null);
15
+ const prevProgress = useRef(progress);
16
+
17
+ useEffect(() => {
18
+ const path = pathRef.current;
19
+ if (!path) return;
20
+
21
+ const pathLength = path.getTotalLength();
22
+ path.style.opacity = progress > 0 ? '1' : '0';
23
+ const fromOffset = pathLength * (1 - prevProgress.current);
24
+ const toOffset = pathLength * (1 - progress);
25
+
26
+ // If no change, skip
27
+ if (fromOffset === toOffset) return;
28
+
29
+ path.style.transition = 'none';
30
+ path.style.strokeDasharray = `${pathLength} ${pathLength}`;
31
+ path.style.strokeDashoffset = `${fromOffset}`;
32
+
33
+ // Force style flush
34
+ path.getBoundingClientRect();
35
+
36
+ path.style.transition = `stroke-dashoffset ${duration}ms ease-out`;
37
+ path.style.strokeDashoffset = `${toOffset}`;
38
+
39
+ prevProgress.current = progress;
40
+ }, [progress, duration]);
41
+
42
+ return (
43
+ <div
44
+ style={{
45
+ position: 'absolute',
46
+ top: '0',
47
+ left: '0',
48
+ width: '100%',
49
+ height: '100%',
50
+ pointerEvents: 'none',
51
+ }}
52
+ >
53
+ <svg
54
+ viewBox="0 0 285 380"
55
+ preserveAspectRatio="xMidYMid meet"
56
+ style={{ width: '100%', height: '100%' }}
57
+ >
58
+ {/* id is referenced by parent */}
59
+ <clipPath id="selfie-clip-path" clipPathUnits="objectBoundingBox">
60
+ <path d="M0.085 0.382 C0.087 0.357 0.092 0.294 0.131 0.236 C0.200 0.133 0.340 0.063 0.501 0.063 C0.730 0.063 0.915 0.205 0.915 0.382 C0.915 0.424 0.899 0.513 0.891 0.549 C0.882 0.588 0.871 0.626 0.857 0.664 C0.792 0.825 0.639 0.937 0.501 0.937 C0.314 0.937 0.182 0.755 0.144 0.666 C0.126 0.624 0.110 0.557 0.107 0.547 C0.092 0.485 0.081 0.439 0.085 0.382 Z" />
61
+ </clipPath>
62
+ <path
63
+ d="M142.693 24C208.319 24 261 77.97 261 145.008C261 160.97 256.319 194.788 254.129 208.356C251.64 223.188 248.348 237.875 244.27 252.35C225.747 313.203 182.328 356 142.693 356C89.414 356 51.871 286.667 41.016 252.948C35.937 236.987 31.356 211.748 30.559 207.857C26.277 184.114 22.991 166.556 24.285 145.008C24.883 135.631 26.277 111.789 37.431 89.742C57.049 50.636 96.983 24 142.693 24Z"
64
+ stroke={themeColor}
65
+ fill="none"
66
+ style={{ strokeWidth: '8' }}
67
+ />
68
+ <path
69
+ ref={pathRef}
70
+ d="M142.693 24C208.319 24 261 77.97 261 145.008C261 160.97 256.319 194.788 254.129 208.356C251.64 223.188 248.348 237.875 244.27 252.35C225.747 313.203 182.328 356 142.693 356C89.414 356 51.871 286.667 41.016 252.948C35.937 236.987 31.356 211.748 30.559 207.857C26.277 184.114 22.991 166.556 24.285 145.008C24.883 135.631 26.277 111.789 37.431 89.742C57.049 50.636 96.983 24 142.693 24Z"
71
+ stroke="#22c55e"
72
+ style={{ strokeWidth: '8' }}
73
+ fill="none"
74
+ strokeLinecap="round"
75
+ />
76
+ </svg>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export default OvalProgress;
@@ -0,0 +1,224 @@
1
+ import { useRef, useEffect } 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 { useFaceCapture, useCamera } from './hooks';
7
+ import { CameraPreview } from './components/CameraPreview';
8
+ import { AlertDisplay } from './components/AlertDisplay';
9
+ import { CaptureControls } from './components/CaptureControls';
10
+
11
+ import '../../../navigation/src';
12
+ import '../../../attribution/PoweredBySmileId';
13
+
14
+ interface Props {
15
+ interval?: number;
16
+ duration?: number;
17
+ 'theme-color'?: string;
18
+ 'show-navigation'?: string | boolean;
19
+ 'allow-agent-mode'?: string | boolean;
20
+ 'show-agent-mode-for-tests'?: string | boolean;
21
+ 'hide-attribution'?: string | boolean;
22
+ 'disable-image-tests'?: string | boolean;
23
+ }
24
+
25
+ const SmartSelfieCapture: FunctionComponent<Props> = ({
26
+ interval = 350,
27
+ duration = 2800,
28
+ 'theme-color': themeColor = '#001096',
29
+ 'show-navigation': showNavigationProp = false,
30
+ 'allow-agent-mode': allowAgentModeProp = false,
31
+ 'show-agent-mode-for-tests': showAgentModeForTestsProp = false,
32
+ 'hide-attribution': hideAttributionProp = false,
33
+ }) => {
34
+ const canvasRef = useRef<HTMLCanvasElement>(null);
35
+ const navigationRef = useRef<HTMLElement | null>(null);
36
+
37
+ const showNavigation = getBoolProp(showNavigationProp);
38
+ const allowAgentMode = getBoolProp(allowAgentModeProp);
39
+ const showAgentModeForTests = getBoolProp(showAgentModeForTestsProp);
40
+ const hideAttribution = getBoolProp(hideAttributionProp);
41
+
42
+ const smileCooldown = 300;
43
+ const smileThreshold = 0.25;
44
+ const mouthOpenThreshold = 0.05;
45
+ const minFaceSize = 0.35;
46
+ const maxFaceSize = 0.5;
47
+
48
+ const camera = useCamera();
49
+
50
+ const faceCapture = useFaceCapture({
51
+ videoRef: camera.videoRef,
52
+ canvasRef,
53
+ interval,
54
+ duration,
55
+ smileThreshold,
56
+ mouthOpenThreshold,
57
+ minFaceSize,
58
+ maxFaceSize,
59
+ smileCooldown,
60
+ });
61
+
62
+ useEffect(() => {
63
+ const initializeCamera = async () => {
64
+ await camera.startCamera();
65
+ await camera.checkAgentSupport();
66
+ await faceCapture.initializeFaceLandmarker();
67
+
68
+ setTimeout(() => {
69
+ faceCapture.setupCanvas();
70
+ faceCapture.startDetectionLoop();
71
+ }, 500);
72
+ };
73
+
74
+ initializeCamera();
75
+
76
+ return () => {
77
+ faceCapture.stopDetectionLoop();
78
+ camera.stopCamera();
79
+ faceCapture.cleanup();
80
+ };
81
+ }, []);
82
+
83
+ useEffect(() => {
84
+ const navigation = navigationRef.current;
85
+
86
+ if (navigation && showNavigation) {
87
+ const handleNavigationBack = () => {
88
+ faceCapture.handleCancel();
89
+ };
90
+
91
+ const handleNavigationClose = () => {
92
+ faceCapture.handleClose();
93
+ };
94
+
95
+ navigation.addEventListener('navigation.back', handleNavigationBack);
96
+ navigation.addEventListener('navigation.close', handleNavigationClose);
97
+
98
+ return () => {
99
+ navigation.removeEventListener('navigation.back', handleNavigationBack);
100
+ navigation.removeEventListener(
101
+ 'navigation.close',
102
+ handleNavigationClose,
103
+ );
104
+ };
105
+ }
106
+ return undefined;
107
+ }, [showNavigation]);
108
+
109
+ return (
110
+ <div className="smartselfie-capture">
111
+ {showNavigation && (
112
+ // @ts-ignore
113
+ <smileid-navigation ref={navigationRef} theme-color={themeColor} />
114
+ )}
115
+
116
+ <CameraPreview
117
+ videoRef={camera.videoRef}
118
+ canvasRef={canvasRef}
119
+ facingMode={camera.facingMode}
120
+ multipleFaces={faceCapture.multipleFaces.value}
121
+ progress={
122
+ faceCapture.capturesTaken.value > 0
123
+ ? faceCapture.capturesTaken.value / faceCapture.totalCaptures.value
124
+ : 0
125
+ }
126
+ interval={interval}
127
+ themeColor={themeColor}
128
+ />
129
+
130
+ <AlertDisplay alertTitle={faceCapture.alertTitle.value} />
131
+
132
+ {!faceCapture.isCapturing.value &&
133
+ !faceCapture.hasFinishedCapture.value && (
134
+ <CaptureControls
135
+ isCapturing={faceCapture.isCapturing.value}
136
+ hasFinishedCapture={faceCapture.hasFinishedCapture.value}
137
+ isReadyToCapture={faceCapture.isReadyToCapture.value}
138
+ allowAgentMode={allowAgentMode}
139
+ agentSupported={camera.agentSupported}
140
+ showAgentModeForTests={showAgentModeForTests}
141
+ facingMode={camera.facingMode}
142
+ themeColor={themeColor}
143
+ onStartCapture={faceCapture.startCapture}
144
+ onSwitchCamera={camera.switchCamera}
145
+ />
146
+ )}
147
+
148
+ {/* @ts-ignore */}
149
+ {!hideAttribution && <powered-by-smile-id />}
150
+
151
+ <style>{`
152
+ * {
153
+ box-sizing: border-box;
154
+ }
155
+
156
+ button {
157
+ padding: 10px 20px;
158
+ background: ${themeColor || '#001096'};
159
+ color: white;
160
+ border: none;
161
+ border-radius: 4px;
162
+ cursor: pointer;
163
+ font-size: 16px;
164
+ }
165
+
166
+ button:disabled {
167
+ background: #ccc;
168
+ cursor: not-allowed;
169
+ }
170
+
171
+ button.btn-primary {
172
+ background-color: ${themeColor || '#001096'};
173
+ border-radius: 2.5rem;
174
+ color: white;
175
+ border: none;
176
+ height: 3.125rem;
177
+ display: inline-block;
178
+ padding: 0.75rem 1.5rem;
179
+ text-align: center;
180
+ font-size: 1.125rem;
181
+ font-weight: 600;
182
+ font-family: "DM Sans", sans-serif;
183
+ cursor: pointer;
184
+ }
185
+
186
+ button.btn-primary:hover {
187
+ background-color: #2d2b2a;
188
+ }
189
+
190
+ button.btn-primary:disabled {
191
+ background-color: #666;
192
+ cursor: not-allowed;
193
+ }
194
+
195
+ .smartselfie-capture {
196
+ font-family: sans-serif;
197
+ max-width: 356px;
198
+ margin: 0 auto;
199
+ padding-block: 2rem;
200
+ }
201
+ `}</style>
202
+ </div>
203
+ );
204
+ };
205
+
206
+ if (!customElements.get('smartselfie-capture')) {
207
+ register(
208
+ SmartSelfieCapture,
209
+ 'smartselfie-capture',
210
+ [
211
+ 'interval',
212
+ 'duration',
213
+ 'theme-color',
214
+ 'show-navigation',
215
+ 'allow-agent-mode',
216
+ 'show-agent-mode-for-tests',
217
+ 'hide-attribution',
218
+ 'disable-image-tests',
219
+ ],
220
+ { shadow: true },
221
+ );
222
+ }
223
+
224
+ export default SmartSelfieCapture;
@@ -0,0 +1,34 @@
1
+ import type { FunctionComponent } from 'preact';
2
+
3
+ interface AlertDisplayProps {
4
+ alertTitle: string;
5
+ }
6
+
7
+ export const AlertDisplay: FunctionComponent<AlertDisplayProps> = ({
8
+ alertTitle,
9
+ }) =>
10
+ alertTitle ? (
11
+ <>
12
+ <div className="alert-message">
13
+ <div className="alert-title">{alertTitle}</div>
14
+ </div>
15
+
16
+ <style>{`
17
+ .alert-message {
18
+ margin-top: 1.5rem;
19
+ color: #000;
20
+ color: #151F72;
21
+ padding: 0.5rem 1.5rem;
22
+ border-radius: 4px;
23
+ text-align: start;
24
+ width: 100%;
25
+ }
26
+
27
+ .alert-title {
28
+ font-size: 16px;
29
+ font-weight: bold;
30
+ text-align: center;
31
+ }
32
+ `}</style>
33
+ </>
34
+ ) : null;