@smileid/web-components 10.0.3 → 10.0.5

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 (44) hide show
  1. package/dist/esm/{DocumentCaptureScreens-CkWKSrqy.js → DocumentCaptureScreens-BIJUlWLB.js} +98 -56
  2. package/dist/esm/DocumentCaptureScreens-BIJUlWLB.js.map +1 -0
  3. package/dist/esm/{EndUserConsent-CMHp-34-.js → EndUserConsent-D4fd1ovG.js} +2 -2
  4. package/dist/esm/{EndUserConsent-CMHp-34-.js.map → EndUserConsent-D4fd1ovG.js.map} +1 -1
  5. package/dist/esm/{Navigation-juBE4qOw.js → Navigation-CTjK6tLU.js} +6 -6
  6. package/dist/esm/{Navigation-juBE4qOw.js.map → Navigation-CTjK6tLU.js.map} +1 -1
  7. package/dist/esm/SelfieCaptureScreens-CnMaKUmP.js +11361 -0
  8. package/dist/esm/SelfieCaptureScreens-CnMaKUmP.js.map +1 -0
  9. package/dist/esm/document.js +1 -1
  10. package/dist/esm/end-user-consent.js +1 -1
  11. package/dist/esm/main.js +4 -4
  12. package/dist/esm/navigation.js +1 -1
  13. package/dist/esm/{package-CmYr0HUS.js → package-PZvRbm5J.js} +2 -2
  14. package/dist/esm/{package-CmYr0HUS.js.map → package-PZvRbm5J.js.map} +1 -1
  15. package/dist/esm/selfie.js +1 -1
  16. package/dist/esm/smart-camera-web.js +12 -6
  17. package/dist/esm/smart-camera-web.js.map +1 -1
  18. package/dist/esm/{styles-D2i3GFLK.js → styles-BOEZtbuc.js} +19 -5
  19. package/dist/esm/{styles-D2i3GFLK.js.map → styles-BOEZtbuc.js.map} +1 -1
  20. package/dist/smart-camera-web.js +278 -290
  21. package/dist/smart-camera-web.js.map +1 -1
  22. package/lib/components/document/src/DocumentCaptureScreens.js +1 -1
  23. package/lib/components/document/src/document-capture/DocumentCapture.js +2 -1
  24. package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +54 -8
  25. package/lib/components/document/src/document-capture-review/DocumentCaptureReview.js +2 -3
  26. package/lib/components/navigation/src/Navigation.js +5 -5
  27. package/lib/components/selfie/src/SelfieCaptureScreens.js +116 -118
  28. package/lib/components/selfie/src/selfie-capture/SelfieCapture.js +54 -10
  29. package/lib/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +91 -58
  30. package/lib/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +23 -154
  31. package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +13 -0
  32. package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +66 -6
  33. package/lib/components/selfie/src/smartselfie-capture/components/CaptureControls.tsx +2 -0
  34. package/lib/components/selfie/src/smartselfie-capture/hooks/useCamera.ts +165 -21
  35. package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +82 -23
  36. package/lib/components/selfie/src/smartselfie-capture/utils/alertMessages.ts +2 -1
  37. package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +18 -1
  38. package/lib/components/signature-pad/package.json +1 -1
  39. package/lib/components/smart-camera-web/src/SmartCameraWeb.js +7 -1
  40. package/lib/styles/src/styles.js +18 -4
  41. package/package.json +3 -1
  42. package/dist/esm/DocumentCaptureScreens-CkWKSrqy.js.map +0 -1
  43. package/dist/esm/SelfieCaptureScreens-BF1keQ0h.js +0 -7619
  44. package/dist/esm/SelfieCaptureScreens-BF1keQ0h.js.map +0 -1
@@ -1,12 +1,54 @@
1
- import { useRef, useState } from 'preact/hooks';
1
+ import { useRef, useState, useEffect } from 'preact/hooks';
2
2
 
3
- export const useCamera = () => {
3
+ export const useCamera = (initialFacingMode: CameraFacingMode = 'user') => {
4
4
  const videoRef = useRef<HTMLVideoElement>(null);
5
5
  const streamRef = useRef<MediaStream | null>(null);
6
- const [facingMode, setFacingMode] = useState<'user' | 'environment'>('user');
6
+ const [facingMode, setFacingMode] = useState(initialFacingMode);
7
7
  const [agentSupported, setAgentSupported] = useState(false);
8
+ const onCameraSwitchCallbackRef = useRef<(() => void) | null>(null);
9
+ const isSwitchingCameraRef = useRef(false);
10
+ const timeoutIdsRef = useRef<Set<NodeJS.Timeout>>(new Set());
8
11
 
9
- const startCamera = async () => {
12
+ const registerCameraSwitchCallback = (callback: () => void) => {
13
+ onCameraSwitchCallbackRef.current = callback;
14
+ };
15
+
16
+ useEffect(() => {
17
+ const video = videoRef.current;
18
+ if (!video) return undefined;
19
+
20
+ const handleVideoReady = () => {
21
+ if (isSwitchingCameraRef.current && onCameraSwitchCallbackRef.current) {
22
+ const timeoutId = setTimeout(() => {
23
+ onCameraSwitchCallbackRef.current?.();
24
+ isSwitchingCameraRef.current = false;
25
+ timeoutIdsRef.current.delete(timeoutId);
26
+ }, 100);
27
+ timeoutIdsRef.current.add(timeoutId);
28
+ }
29
+ };
30
+
31
+ video.addEventListener('loadedmetadata', handleVideoReady);
32
+
33
+ return () => {
34
+ video.removeEventListener('loadedmetadata', handleVideoReady);
35
+ timeoutIdsRef.current.forEach((timeoutId) => clearTimeout(timeoutId));
36
+ timeoutIdsRef.current.clear();
37
+ };
38
+ }, [videoRef.current?.src]);
39
+
40
+ useEffect(
41
+ () => () => {
42
+ timeoutIdsRef.current.forEach((timeoutId) => clearTimeout(timeoutId));
43
+ timeoutIdsRef.current.clear();
44
+ },
45
+ [],
46
+ );
47
+
48
+ const startCamera = async (
49
+ targetFacingMode?: CameraFacingMode,
50
+ callback?: (cameraName?: string) => void,
51
+ ) => {
10
52
  try {
11
53
  if (streamRef.current) {
12
54
  streamRef.current.getTracks().forEach((track) => track.stop());
@@ -18,10 +60,24 @@ export const useCamera = () => {
18
60
  }
19
61
 
20
62
  const stream = await navigator.mediaDevices.getUserMedia({
21
- video: { facingMode },
63
+ video: { facingMode: targetFacingMode || facingMode },
22
64
  });
23
65
  streamRef.current = stream;
24
66
 
67
+ const track = stream.getVideoTracks()[0];
68
+ const settings = track.getSettings();
69
+ const actualFacingMode = settings.facingMode as
70
+ | CameraFacingMode
71
+ | undefined;
72
+
73
+ const requestedFacingMode = targetFacingMode || facingMode;
74
+
75
+ if (actualFacingMode && actualFacingMode !== requestedFacingMode) {
76
+ setFacingMode(actualFacingMode);
77
+ } else if (actualFacingMode && actualFacingMode !== facingMode) {
78
+ setFacingMode(actualFacingMode);
79
+ }
80
+
25
81
  const devices = await navigator.mediaDevices.enumerateDevices();
26
82
  const videoDevice = devices.find(
27
83
  (device) =>
@@ -29,16 +85,12 @@ export const useCamera = () => {
29
85
  stream.getVideoTracks()[0].getSettings().deviceId === device.deviceId,
30
86
  );
31
87
 
32
- const smartCameraWeb = document.querySelector('smart-camera-web');
33
- smartCameraWeb?.dispatchEvent(
34
- new CustomEvent('metadata.camera-name', {
35
- detail: { cameraName: videoDevice?.label },
36
- }),
37
- );
88
+ callback?.(videoDevice?.label);
38
89
 
39
90
  if (videoRef.current) {
40
91
  videoRef.current.srcObject = stream;
41
92
  await videoRef.current.play();
93
+ // Video ready callback will be handled by useEffect
42
94
  }
43
95
  } catch (error) {
44
96
  console.error('Failed to start camera:', error);
@@ -47,23 +99,114 @@ export const useCamera = () => {
47
99
 
48
100
  const switchCamera = async () => {
49
101
  const newFacingMode = facingMode === 'user' ? 'environment' : 'user';
50
- setFacingMode(newFacingMode);
102
+ isSwitchingCameraRef.current = true;
51
103
 
52
- if (streamRef.current) {
53
- streamRef.current.getTracks().forEach((track) => track.stop());
54
- streamRef.current = null;
104
+ const previousFacingMode = facingMode;
105
+ try {
106
+ setFacingMode(newFacingMode);
107
+ if (streamRef.current) {
108
+ streamRef.current.getTracks().forEach((track) => track.stop());
109
+ streamRef.current = null;
110
+ }
111
+
112
+ await startCamera(newFacingMode);
113
+ } catch (error) {
114
+ setFacingMode(previousFacingMode);
115
+ isSwitchingCameraRef.current = false;
116
+
117
+ try {
118
+ await startCamera(previousFacingMode);
119
+ } catch (restoreError) {
120
+ console.error('Failed to restore previous camera:', restoreError);
121
+ }
55
122
  }
123
+ };
124
+
125
+ const detectBrowserEngine = () => {
126
+ const userAgent = navigator.userAgent.toLowerCase();
127
+
128
+ const isGecko =
129
+ userAgent.includes('firefox') ||
130
+ (userAgent.includes('gecko') &&
131
+ !userAgent.includes('chrome') &&
132
+ !userAgent.includes('edge'));
56
133
 
57
- await startCamera();
134
+ const hasFirefoxFeatures =
135
+ 'mozInnerScreenX' in window ||
136
+ 'mozInputSource' in window ||
137
+ 'mozPaintCount' in window ||
138
+ typeof (window as any).InstallTrigger !== 'undefined';
139
+
140
+ const supportsMozCSS =
141
+ CSS.supports &&
142
+ (CSS.supports('-moz-appearance', 'none') ||
143
+ CSS.supports('-moz-user-select', 'none'));
144
+
145
+ return {
146
+ isGecko: isGecko || hasFirefoxFeatures || supportsMozCSS,
147
+ isChromium:
148
+ userAgent.includes('chrome') ||
149
+ userAgent.includes('chromium') ||
150
+ userAgent.includes('edge'),
151
+ isWebKit: userAgent.includes('webkit') && !userAgent.includes('chrome'),
152
+ };
58
153
  };
59
154
 
60
155
  const checkAgentSupport = async () => {
61
156
  try {
62
- const devices = await navigator.mediaDevices.enumerateDevices();
63
- const videoDevices = devices.filter(
64
- (device) => device.kind === 'videoinput',
65
- );
66
- setAgentSupported(videoDevices.length > 1);
157
+ const isMobile =
158
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
159
+ navigator.userAgent,
160
+ ) ||
161
+ (navigator.maxTouchPoints && navigator.maxTouchPoints > 1);
162
+
163
+ // mobile devices generally support both cameras
164
+ // also, ios crashes if we try to check for cameras
165
+ if (isMobile) {
166
+ setAgentSupported(true);
167
+ return;
168
+ }
169
+
170
+ const { isGecko } = detectBrowserEngine();
171
+
172
+ let userCameraId: string | null = null;
173
+ let environmentCameraId: string | null = null;
174
+
175
+ // test if we can get a user-facing camera
176
+ try {
177
+ const userStream = await navigator.mediaDevices.getUserMedia({
178
+ video: { facingMode: 'user' },
179
+ });
180
+ userCameraId =
181
+ userStream.getVideoTracks()[0].getSettings().deviceId ?? null;
182
+ userStream.getTracks().forEach((track) => track.stop());
183
+ } catch (error) {
184
+ // no user-facing camera available
185
+ }
186
+
187
+ // test if we can get an environment-facing camera
188
+ try {
189
+ const envStream = await navigator.mediaDevices.getUserMedia({
190
+ video: { facingMode: 'environment' },
191
+ });
192
+ environmentCameraId =
193
+ envStream.getVideoTracks()[0].getSettings().deviceId ?? null;
194
+ envStream.getTracks().forEach((track) => track.stop());
195
+ } catch (error) {
196
+ // no environment-facing camera available
197
+ }
198
+
199
+ const hasBothCameras =
200
+ userCameraId &&
201
+ environmentCameraId &&
202
+ userCameraId !== environmentCameraId;
203
+
204
+ if (!hasBothCameras) {
205
+ setAgentSupported(false);
206
+ return;
207
+ }
208
+
209
+ setAgentSupported(!isGecko);
67
210
  } catch (error) {
68
211
  setAgentSupported(false);
69
212
  }
@@ -90,5 +233,6 @@ export const useCamera = () => {
90
233
  switchCamera,
91
234
  checkAgentSupport,
92
235
  stopCamera,
236
+ registerCameraSwitchCallback,
93
237
  };
94
238
  };
@@ -1,6 +1,7 @@
1
1
  import { useRef } from 'preact/hooks';
2
2
  import { useSignal, useComputed } from '@preact/signals';
3
3
  import { FaceLandmarker } from '@mediapipe/tasks-vision';
4
+ import { throttle } from 'lodash';
4
5
  import {
5
6
  calculateFaceSize,
6
7
  isFaceInBounds,
@@ -29,6 +30,7 @@ interface UseFaceCaptureProps {
29
30
  minFaceSize: number;
30
31
  maxFaceSize: number;
31
32
  smileCooldown: number;
33
+ getFacingMode: () => CameraFacingMode;
32
34
  }
33
35
 
34
36
  export const useFaceCapture = ({
@@ -41,6 +43,7 @@ export const useFaceCapture = ({
41
43
  minFaceSize,
42
44
  maxFaceSize,
43
45
  smileCooldown,
46
+ getFacingMode,
44
47
  }: UseFaceCaptureProps) => {
45
48
  const faceLandmarkerRef = useRef<FaceLandmarker | null>(null);
46
49
  const animationFrameRef = useRef<number | null>(null);
@@ -58,6 +61,7 @@ export const useFaceCapture = ({
58
61
  const currentMouthOpen = useSignal(0);
59
62
  const lastSmileTime = useSignal(0);
60
63
  const alertTitle = useSignal('');
64
+ const isInitializing = useSignal(true);
61
65
 
62
66
  const isCapturing = useSignal(false);
63
67
  const isPaused = useSignal(false);
@@ -81,19 +85,36 @@ export const useFaceCapture = ({
81
85
  !multipleFaces.value,
82
86
  );
83
87
 
88
+ const updateAlertImmediate = (messageKey: MessageKey | null) => {
89
+ if (messageKey && MESSAGES[messageKey]) {
90
+ alertTitle.value = MESSAGES[messageKey];
91
+ } else {
92
+ alertTitle.value = '';
93
+ }
94
+ };
95
+
96
+ const updateAlert = useRef(
97
+ throttle((messageKey: MessageKey | null) => {
98
+ updateAlertImmediate(messageKey);
99
+ }, 300),
100
+ ).current;
101
+
84
102
  const initializeFaceLandmarker = async () => {
85
103
  try {
104
+ const isAlreadyLoaded =
105
+ window.__smileIdentityMediapipe?.loaded &&
106
+ window.__smileIdentityMediapipe?.instance;
107
+
108
+ if (!isAlreadyLoaded) {
109
+ isInitializing.value = true;
110
+ updateAlertImmediate('initializing');
111
+ }
112
+
86
113
  faceLandmarkerRef.current = await getMediapipeInstance();
114
+ isInitializing.value = false;
87
115
  } catch (error) {
88
116
  console.error('Failed to initialize MediaPipe:', error);
89
- }
90
- };
91
-
92
- const updateAlert = (messageKey: MessageKey | null) => {
93
- if (messageKey && MESSAGES[messageKey]) {
94
- alertTitle.value = MESSAGES[messageKey];
95
- } else {
96
- alertTitle.value = '';
117
+ isInitializing.value = false;
97
118
  }
98
119
  };
99
120
 
@@ -110,7 +131,6 @@ export const useFaceCapture = ({
110
131
  if (container) {
111
132
  canvasRef.current.style.left = '50%';
112
133
  canvasRef.current.style.top = '50%';
113
- canvasRef.current.style.transform = 'translate(-50%, -50%) scaleX(-1)';
114
134
  }
115
135
  }
116
136
  };
@@ -119,9 +139,7 @@ export const useFaceCapture = ({
119
139
  const isInNeutralZone = capturesTaken.value < neutralZone.value;
120
140
  const isInSmileZone = capturesTaken.value >= smileCheckpoint.value;
121
141
 
122
- if (isInNeutralZone && currentSmileScore.value >= smileThreshold) {
123
- updateAlert('neutral-expression');
124
- } else if (isInNeutralZone) {
142
+ if (isInNeutralZone) {
125
143
  alertTitle.value = 'Capturing...';
126
144
  } else if (isInSmileZone) {
127
145
  const timeSinceSmile = Date.now() - lastSmileTime.value;
@@ -143,7 +161,9 @@ export const useFaceCapture = ({
143
161
  };
144
162
 
145
163
  const updateAlerts = () => {
146
- if (multipleFaces.value) {
164
+ if (isInitializing.value) {
165
+ updateAlertImmediate('initializing');
166
+ } else if (multipleFaces.value) {
147
167
  updateAlert('multiple-faces');
148
168
  } else if (!faceDetected.value) {
149
169
  updateAlert('no-face');
@@ -173,7 +193,21 @@ export const useFaceCapture = ({
173
193
  return;
174
194
  }
175
195
 
196
+ // ensure video has valid dimensions before processing
197
+ if (
198
+ videoRef.current.videoWidth <= 0 ||
199
+ videoRef.current.videoHeight <= 0 ||
200
+ videoRef.current.readyState < 2
201
+ ) {
202
+ animationFrameRef.current = requestAnimationFrame(detectFace);
203
+ return;
204
+ }
205
+
176
206
  try {
207
+ if (isInitializing.value) {
208
+ isInitializing.value = false;
209
+ }
210
+
177
211
  const croppedCanvas = createCroppedVideoFrame(videoRef.current);
178
212
  const detectionSource = croppedCanvas || videoRef.current;
179
213
 
@@ -359,6 +393,7 @@ export const useFaceCapture = ({
359
393
  images: [...livenessImages, referenceImage],
360
394
  referenceImage: referencePhoto.value,
361
395
  previewImage: referencePhoto.value,
396
+ facingMode: getFacingMode(),
362
397
  meta: { libraryVersion: COMPONENTS_VERSION },
363
398
  };
364
399
 
@@ -368,11 +403,6 @@ export const useFaceCapture = ({
368
403
  }),
369
404
  );
370
405
 
371
- const smartCameraWeb = document.querySelector('smart-camera-web');
372
- smartCameraWeb?.dispatchEvent(
373
- new CustomEvent('metadata.selfie-capture-end'),
374
- );
375
-
376
406
  hasFinishedCapture.value = true;
377
407
  }
378
408
  };
@@ -424,13 +454,8 @@ export const useFaceCapture = ({
424
454
  return;
425
455
  }
426
456
 
427
- const isInNeutralZone = capturesTaken.value < neutralZone.value;
428
457
  const isInSmileZone = capturesTaken.value >= smileCheckpoint.value;
429
458
 
430
- if (isInNeutralZone && currentSmileScore.value >= smileThreshold) {
431
- return;
432
- }
433
-
434
459
  if (isInSmileZone) {
435
460
  const timeSinceSmile = Date.now() - lastSmileTime.value;
436
461
  if (timeSinceSmile > smileCooldown) {
@@ -474,6 +499,21 @@ export const useFaceCapture = ({
474
499
  capturesTaken.value = 0;
475
500
  countdown.value = totalCaptures.value;
476
501
 
502
+ const smartCameraWeb = document.querySelector('smart-camera-web');
503
+ smartCameraWeb?.dispatchEvent(
504
+ new CustomEvent('metadata.selfie-capture-start'),
505
+ );
506
+ smartCameraWeb?.dispatchEvent(
507
+ new CustomEvent('metadata.selfie-origin', {
508
+ detail: {
509
+ imageOrigin: {
510
+ environment: 'back_camera',
511
+ user: 'front_camera',
512
+ }[getFacingMode()],
513
+ },
514
+ }),
515
+ );
516
+
477
517
  startCaptureInterval();
478
518
  };
479
519
 
@@ -515,6 +555,23 @@ export const useFaceCapture = ({
515
555
  clearInterval(captureTimerRef.current);
516
556
  }
517
557
  stopDetectionLoop();
558
+ updateAlert.cancel();
559
+ };
560
+
561
+ const resetFaceDetectionState = () => {
562
+ faceDetected.value = false;
563
+ faceInBounds.value = false;
564
+ faceProximity.value = 'good';
565
+ multipleFaces.value = false;
566
+ faceLandmarks.value = [];
567
+ currentSmileScore.value = 0;
568
+ currentFaceSize.value = 0;
569
+ currentMouthOpen.value = 0;
570
+ lastSmileTime.value = 0;
571
+
572
+ if (canvasRef.current) {
573
+ clearCanvas(canvasRef.current);
574
+ }
518
575
  };
519
576
 
520
577
  return {
@@ -529,6 +586,7 @@ export const useFaceCapture = ({
529
586
  currentMouthOpen,
530
587
  lastSmileTime,
531
588
  alertTitle,
589
+ isInitializing,
532
590
  isReadyToCapture,
533
591
 
534
592
  isCapturing,
@@ -554,5 +612,6 @@ export const useFaceCapture = ({
554
612
  handleCancel,
555
613
  handleClose,
556
614
  cleanup,
615
+ resetFaceDetectionState,
557
616
  };
558
617
  };
@@ -6,7 +6,8 @@ export const MESSAGES = {
6
6
  'too-far': 'Move closer',
7
7
  'neutral-expression': 'Neutral expression',
8
8
  'smile-required': 'Smile!',
9
- 'open-mouth-smile': 'Bigger smile!',
9
+ 'open-mouth-smile': 'Wider smile - teeth visible',
10
+ initializing: 'Initializing...',
10
11
  };
11
12
 
12
13
  export type MessageKey = keyof typeof MESSAGES;
@@ -10,6 +10,23 @@ declare global {
10
10
  }
11
11
  }
12
12
 
13
+ const hasFP16Support = () => {
14
+ const canvas = document.createElement('canvas');
15
+ const gl =
16
+ canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
17
+ if (!gl) return false;
18
+
19
+ const hasHalfFloatExt = (gl as any).getExtension('OES_texture_half_float');
20
+ const hasHalfFloatLinear = (gl as any).getExtension(
21
+ 'OES_texture_half_float_linear',
22
+ );
23
+ const hasColorBufferHalfFloat = (gl as any).getExtension(
24
+ 'EXT_color_buffer_half_float',
25
+ );
26
+
27
+ return !!(hasHalfFloatExt && hasColorBufferHalfFloat && hasHalfFloatLinear);
28
+ };
29
+
13
30
  export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
14
31
  if (!window.__smileIdentityMediapipe) {
15
32
  window.__smileIdentityMediapipe = {
@@ -38,7 +55,7 @@ export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
38
55
  const faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
39
56
  baseOptions: {
40
57
  modelAssetPath: `https://web-models.smileidentity.com/face_landmarker/face_landmarker.task`,
41
- delegate: 'GPU',
58
+ delegate: hasFP16Support() ? 'GPU' : 'CPU',
42
59
  },
43
60
  outputFaceBlendshapes: true,
44
61
  runningMode: 'VIDEO',
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smileid/signature-pad",
3
- "version": "10.0.3",
3
+ "version": "10.0.5",
4
4
  "private": "true",
5
5
  "exports": {
6
6
  ".": "./index.js"
@@ -10,8 +10,14 @@ const COMPONENTS_VERSION = packageJson.version;
10
10
 
11
11
  function scwTemplateString() {
12
12
  return `
13
+ <style>
14
+ :host {
15
+ display: block;
16
+ height: 100%;
17
+ }
18
+ </style>
13
19
  ${styles(this.themeColor)}
14
- <div>
20
+ <div style="height: 100%;">
15
21
  <camera-permission ${this.applyComponentThemeColor} ${this.title} ${this.showNavigation} ${this.hideInstructions ? '' : 'hidden'} ${this.hideAttribution}></camera-permission>
16
22
  <selfie-capture-screens ${this.applyComponentThemeColor} ${this.title} ${this.showNavigation} ${this.disableImageTests} ${this.hideAttribution} ${this.hideInstructions} hidden
17
23
  ${this.hideBackToHost} ${this.allowAgentMode} ${this.allowAgentModeTests}
@@ -260,6 +260,9 @@ ${typography}
260
260
 
261
261
  #document-capture-instructions-screen,
262
262
  #back-of-document-capture-instructions-screen {
263
+ box-sizing: border-box;
264
+ height: 100%;
265
+ padding: 1rem;
263
266
  display: flex;
264
267
  flex-direction: column;
265
268
  max-block-size: 100%;
@@ -287,22 +290,33 @@ ${typography}
287
290
  padding-bottom: 2rem;
288
291
  }
289
292
 
293
+ smart-camera-web, selfie-capture-screens, selfie-capture-instructions, document-capture-screens, document-capture-instructions {
294
+ height: 100%;
295
+ display: block;
296
+ }
297
+
290
298
  .instructions-wrapper {
291
299
  display: inline-flex;
292
300
  flex-direction: column;
293
- gap: 1.5rem;
294
- margin-block-start: 2rem;
295
- margin-block-end: 2rem;
301
+ gap: 1rem;
302
+ }
303
+
304
+ @media (min-width: 40rem) {
305
+ .instructions-wrapper {
306
+ gap: 1.75rem;
307
+ }
296
308
  }
309
+
297
310
  .instructions {
298
311
  display: flex;
299
312
  align-items: center;
300
313
  text-align: initial;
314
+ gap: 0.5rem;
301
315
  }
302
316
 
303
317
  .instructions svg {
304
318
  flex-shrink: 0;
305
- margin-inline-end: 2rem;
319
+ margin-inline-end: 0.25rem;
306
320
  }
307
321
 
308
322
  .instructions p {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smileid/web-components",
3
- "version": "10.0.3",
3
+ "version": "10.0.5",
4
4
  "private": false,
5
5
  "main": "dist/esm/main.js",
6
6
  "module": "dist/esm/main.js",
@@ -76,6 +76,7 @@
76
76
  "@mediapipe/tasks-vision": "^0.10.22-rc.20250304",
77
77
  "@preact/signals": "^2.1.1",
78
78
  "@tabler/icons-preact": "^3.34.0",
79
+ "lodash": "^4.17.21",
79
80
  "preact": "^10.26.9",
80
81
  "preact-custom-element": "^4.3.0",
81
82
  "preact-router": "^4.1.2",
@@ -84,6 +85,7 @@
84
85
  },
85
86
  "devDependencies": {
86
87
  "@preact/preset-vite": "^2.10.2",
88
+ "@types/lodash": "^4.17.20",
87
89
  "@types/node": "^20.11.24",
88
90
  "@types/preact-custom-element": "^4.0.4",
89
91
  "@typescript-eslint/eslint-plugin": "^7.18.0",