@pallasoft/ps-file-components 1.4.0 → 2.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 (194) hide show
  1. package/dist/components/ps-document-scanner-loader.d.ts +1 -1
  2. package/dist/components/ps-document-scanner-sheet.d.ts +1 -1
  3. package/dist/components/ps-document-scanner-sheet.js +64 -86
  4. package/dist/components/ps-document-scanner.d.ts +1 -1
  5. package/dist/components/ps-document-scanner.js +2 -3
  6. package/dist/components/ps-file-picker-actions.d.ts +10 -0
  7. package/dist/components/ps-file-picker-actions.js +21 -0
  8. package/dist/components/ps-file-picker.d.ts +7 -1
  9. package/dist/components/ps-file-picker.js +30 -6
  10. package/dist/components/ps-scan-button.d.ts +13 -0
  11. package/dist/components/ps-scan-button.js +9 -0
  12. package/dist/components/ps-scan-camera-view.d.ts +11 -8
  13. package/dist/components/ps-scan-camera-view.js +14 -16
  14. package/dist/components/ps-scan-crop-editor.d.ts +4 -4
  15. package/dist/components/ps-scan-crop-editor.js +24 -8
  16. package/dist/components/ps-scan-denied-steps.d.ts +6 -0
  17. package/dist/components/ps-scan-denied-steps.js +33 -0
  18. package/dist/components/ps-scan-icon-button.d.ts +4 -3
  19. package/dist/components/ps-scan-icon-button.js +3 -1
  20. package/dist/components/ps-scan-illustration.d.ts +1 -0
  21. package/dist/components/ps-scan-illustration.js +6 -0
  22. package/dist/components/ps-scan-loupe.d.ts +10 -0
  23. package/dist/components/ps-scan-loupe.js +31 -0
  24. package/dist/components/ps-scan-permission-view.d.ts +10 -0
  25. package/dist/components/ps-scan-permission-view.js +48 -0
  26. package/dist/components/ps-scan-photo-button.d.ts +7 -0
  27. package/dist/components/ps-scan-photo-button.js +20 -0
  28. package/dist/components/ps-scan-preview.d.ts +12 -0
  29. package/dist/components/ps-scan-preview.js +10 -0
  30. package/dist/components/ps-scan-shutter.d.ts +6 -0
  31. package/dist/components/ps-scan-shutter.js +6 -0
  32. package/dist/components/ps-scan-viewfinder-overlay.d.ts +8 -0
  33. package/dist/components/ps-scan-viewfinder-overlay.js +28 -0
  34. package/dist/components/ps-upload-row.js +1 -1
  35. package/dist/constants/ps-document-scanner-labels.d.ts +39 -38
  36. package/dist/constants/ps-document-scanner-labels.js +39 -38
  37. package/dist/constants/ps-file-picker-labels.d.ts +5 -3
  38. package/dist/constants/ps-file-picker-labels.js +5 -3
  39. package/dist/constants/ps-scan-camera-status.d.ts +4 -0
  40. package/dist/constants/ps-scan-camera-status.js +4 -0
  41. package/dist/constants/ps-scan-chrome-classes.d.ts +10 -0
  42. package/dist/constants/ps-scan-chrome-classes.js +13 -0
  43. package/dist/constants/ps-scan-config.d.ts +34 -24
  44. package/dist/constants/ps-scan-config.js +34 -24
  45. package/dist/constants/ps-scan-platform.d.ts +6 -0
  46. package/dist/constants/ps-scan-platform.js +8 -0
  47. package/dist/constants/ps-scan-step.d.ts +1 -1
  48. package/dist/constants/ps-scan-step.js +1 -1
  49. package/dist/esm/components/ps-document-scanner-sheet.js +67 -89
  50. package/dist/esm/components/ps-document-scanner.js +2 -3
  51. package/dist/esm/components/ps-file-picker-actions.js +17 -0
  52. package/dist/esm/components/ps-file-picker.js +31 -7
  53. package/dist/esm/components/ps-scan-button.js +5 -0
  54. package/dist/esm/components/ps-scan-camera-view.js +17 -19
  55. package/dist/esm/components/ps-scan-crop-editor.js +26 -10
  56. package/dist/esm/components/ps-scan-denied-steps.js +29 -0
  57. package/dist/esm/components/ps-scan-icon-button.js +3 -1
  58. package/dist/esm/components/ps-scan-illustration.js +2 -0
  59. package/dist/esm/components/ps-scan-loupe.js +27 -0
  60. package/dist/esm/components/ps-scan-permission-view.js +44 -0
  61. package/dist/esm/components/ps-scan-photo-button.js +16 -0
  62. package/dist/esm/components/ps-scan-preview.js +6 -0
  63. package/dist/esm/components/ps-scan-shutter.js +2 -0
  64. package/dist/esm/components/ps-scan-viewfinder-overlay.js +24 -0
  65. package/dist/esm/components/ps-upload-row.js +1 -1
  66. package/dist/esm/constants/ps-document-scanner-labels.js +39 -38
  67. package/dist/esm/constants/ps-file-picker-labels.js +5 -3
  68. package/dist/esm/constants/ps-scan-camera-status.js +4 -0
  69. package/dist/esm/constants/ps-scan-chrome-classes.js +10 -0
  70. package/dist/esm/constants/ps-scan-config.js +34 -24
  71. package/dist/esm/constants/ps-scan-platform.js +5 -0
  72. package/dist/esm/constants/ps-scan-step.js +1 -1
  73. package/dist/esm/hooks/use-ps-scan-camera.js +52 -15
  74. package/dist/esm/hooks/use-ps-scan-live-detection.js +41 -0
  75. package/dist/esm/hooks/use-ps-scan-photo.js +42 -0
  76. package/dist/esm/index.js +1 -2
  77. package/dist/esm/models/ps-scan-region.js +1 -0
  78. package/dist/esm/models/ps-scan-result.js +1 -0
  79. package/dist/esm/models/ps-scan-stability.js +1 -0
  80. package/dist/esm/services/ps-scan-camera-service.js +52 -7
  81. package/dist/esm/services/ps-scan-canny-service.js +93 -0
  82. package/dist/esm/services/ps-scan-canvas-service.js +13 -1
  83. package/dist/esm/services/ps-scan-edge-service.js +73 -137
  84. package/dist/esm/services/ps-scan-file-name-service.js +2 -11
  85. package/dist/esm/services/ps-scan-geometry-service.js +56 -0
  86. package/dist/esm/services/ps-scan-hull-service.js +0 -39
  87. package/dist/esm/services/ps-scan-image-filter-service.js +0 -21
  88. package/dist/esm/services/ps-scan-morphology-service.js +44 -0
  89. package/dist/esm/services/ps-scan-pipeline-service.js +9 -19
  90. package/dist/esm/services/ps-scan-platform-service.js +15 -0
  91. package/dist/esm/services/ps-scan-polygon-service.js +75 -0
  92. package/dist/esm/services/ps-scan-region-service.js +52 -0
  93. package/dist/esm/services/ps-scan-stability-service.js +28 -0
  94. package/dist/esm/services/ps-scan-warp-service.js +8 -2
  95. package/dist/esm/services/ps-scan-yield-service.js +5 -0
  96. package/dist/hooks/use-ps-scan-camera.d.ts +3 -1
  97. package/dist/hooks/use-ps-scan-camera.js +52 -15
  98. package/dist/hooks/use-ps-scan-live-detection.d.ts +3 -0
  99. package/dist/hooks/use-ps-scan-live-detection.js +45 -0
  100. package/dist/hooks/use-ps-scan-photo.d.ts +9 -0
  101. package/dist/hooks/use-ps-scan-photo.js +46 -0
  102. package/dist/index.d.ts +1 -2
  103. package/dist/index.js +1 -2
  104. package/dist/models/ps-document-scanner-props.d.ts +0 -3
  105. package/dist/models/ps-document-scanner-sheet-props.d.ts +0 -3
  106. package/dist/models/ps-file-picker-scan.d.ts +4 -0
  107. package/dist/models/ps-scan-gradient.d.ts +4 -0
  108. package/dist/models/ps-scan-live-detection.d.ts +4 -0
  109. package/dist/models/ps-scan-located.d.ts +5 -0
  110. package/dist/models/ps-scan-photo.d.ts +9 -0
  111. package/dist/models/ps-scan-region.d.ts +5 -0
  112. package/dist/models/ps-scan-region.js +2 -0
  113. package/dist/models/ps-scan-result.d.ts +6 -0
  114. package/dist/models/ps-scan-result.js +2 -0
  115. package/dist/models/ps-scan-stability.d.ts +5 -0
  116. package/dist/models/ps-scan-stability.js +2 -0
  117. package/dist/services/ps-scan-camera-service.d.ts +4 -0
  118. package/dist/services/ps-scan-camera-service.js +52 -7
  119. package/dist/services/ps-scan-canny-service.d.ts +7 -0
  120. package/dist/services/ps-scan-canny-service.js +97 -0
  121. package/dist/services/ps-scan-canvas-service.d.ts +1 -0
  122. package/dist/services/ps-scan-canvas-service.js +13 -1
  123. package/dist/services/ps-scan-edge-service.d.ts +5 -5
  124. package/dist/services/ps-scan-edge-service.js +73 -137
  125. package/dist/services/ps-scan-file-name-service.d.ts +1 -3
  126. package/dist/services/ps-scan-file-name-service.js +2 -11
  127. package/dist/services/ps-scan-geometry-service.d.ts +4 -0
  128. package/dist/services/ps-scan-geometry-service.js +56 -0
  129. package/dist/services/ps-scan-hull-service.d.ts +0 -2
  130. package/dist/services/ps-scan-hull-service.js +0 -39
  131. package/dist/services/ps-scan-image-filter-service.d.ts +0 -1
  132. package/dist/services/ps-scan-image-filter-service.js +0 -21
  133. package/dist/services/ps-scan-morphology-service.d.ts +7 -0
  134. package/dist/services/ps-scan-morphology-service.js +48 -0
  135. package/dist/services/ps-scan-pipeline-service.d.ts +2 -5
  136. package/dist/services/ps-scan-pipeline-service.js +9 -19
  137. package/dist/services/ps-scan-platform-service.d.ts +4 -0
  138. package/dist/services/ps-scan-platform-service.js +19 -0
  139. package/dist/services/ps-scan-polygon-service.d.ts +8 -0
  140. package/dist/services/ps-scan-polygon-service.js +79 -0
  141. package/dist/services/ps-scan-region-service.d.ts +4 -0
  142. package/dist/services/ps-scan-region-service.js +56 -0
  143. package/dist/services/ps-scan-stability-service.d.ts +7 -0
  144. package/dist/services/ps-scan-stability-service.js +32 -0
  145. package/dist/services/ps-scan-warp-service.d.ts +1 -1
  146. package/dist/services/ps-scan-warp-service.js +8 -2
  147. package/dist/services/ps-scan-yield-service.d.ts +3 -0
  148. package/dist/services/ps-scan-yield-service.js +9 -0
  149. package/package.json +3 -3
  150. package/dist/components/ps-scan-page-list.d.ts +0 -12
  151. package/dist/components/ps-scan-page-list.js +0 -23
  152. package/dist/components/ps-scan-review.d.ts +0 -23
  153. package/dist/components/ps-scan-review.js +0 -20
  154. package/dist/constants/ps-scan-filter.d.ts +0 -7
  155. package/dist/constants/ps-scan-filter.js +0 -9
  156. package/dist/constants/ps-scan-output-format.d.ts +0 -5
  157. package/dist/constants/ps-scan-output-format.js +0 -7
  158. package/dist/esm/components/ps-scan-page-list.js +0 -19
  159. package/dist/esm/components/ps-scan-review.js +0 -16
  160. package/dist/esm/constants/ps-scan-filter.js +0 -6
  161. package/dist/esm/constants/ps-scan-output-format.js +0 -4
  162. package/dist/esm/hooks/use-ps-scan-pages.js +0 -62
  163. package/dist/esm/hooks/use-ps-scan-previews.js +0 -57
  164. package/dist/esm/services/ps-scan-enhance-service.js +0 -97
  165. package/dist/esm/services/ps-scan-order-service.js +0 -13
  166. package/dist/esm/services/ps-scan-output-service.js +0 -24
  167. package/dist/esm/services/ps-scan-pdf-service.js +0 -56
  168. package/dist/hooks/use-ps-scan-pages.d.ts +0 -12
  169. package/dist/hooks/use-ps-scan-pages.js +0 -66
  170. package/dist/hooks/use-ps-scan-previews.d.ts +0 -2
  171. package/dist/hooks/use-ps-scan-previews.js +0 -61
  172. package/dist/models/ps-scan-added-page.d.ts +0 -5
  173. package/dist/models/ps-scan-levels.d.ts +0 -4
  174. package/dist/models/ps-scan-output-options.d.ts +0 -7
  175. package/dist/models/ps-scan-page.d.ts +0 -12
  176. package/dist/models/ps-scan-pdf-page.d.ts +0 -5
  177. package/dist/services/ps-scan-enhance-service.d.ts +0 -9
  178. package/dist/services/ps-scan-enhance-service.js +0 -101
  179. package/dist/services/ps-scan-order-service.d.ts +0 -4
  180. package/dist/services/ps-scan-order-service.js +0 -17
  181. package/dist/services/ps-scan-output-service.d.ts +0 -5
  182. package/dist/services/ps-scan-output-service.js +0 -28
  183. package/dist/services/ps-scan-pdf-service.d.ts +0 -5
  184. package/dist/services/ps-scan-pdf-service.js +0 -60
  185. /package/dist/esm/models/{ps-scan-added-page.js → ps-file-picker-scan.js} +0 -0
  186. /package/dist/esm/models/{ps-scan-levels.js → ps-scan-gradient.js} +0 -0
  187. /package/dist/esm/models/{ps-scan-output-options.js → ps-scan-live-detection.js} +0 -0
  188. /package/dist/esm/models/{ps-scan-page.js → ps-scan-located.js} +0 -0
  189. /package/dist/esm/models/{ps-scan-pdf-page.js → ps-scan-photo.js} +0 -0
  190. /package/dist/models/{ps-scan-added-page.js → ps-file-picker-scan.js} +0 -0
  191. /package/dist/models/{ps-scan-levels.js → ps-scan-gradient.js} +0 -0
  192. /package/dist/models/{ps-scan-output-options.js → ps-scan-live-detection.js} +0 -0
  193. /package/dist/models/{ps-scan-page.js → ps-scan-located.js} +0 -0
  194. /package/dist/models/{ps-scan-pdf-page.js → ps-scan-photo.js} +0 -0
@@ -1,31 +1,41 @@
1
1
  export declare const PsScanConfig: {
2
- readonly adaptiveRatio: 0.92;
3
- readonly adaptiveWindowDivisor: 16;
4
- readonly adaptiveWindowMin: 15;
5
- readonly capturedJpegQuality: 0.92;
2
+ readonly cameraPermission: "camera";
3
+ readonly cannyHighPercentile: 0.9;
4
+ readonly cannyLowRatio: 0.45;
5
+ readonly cannyMinHigh: 12;
6
+ readonly capturedJpegQuality: 0.97;
7
+ readonly closeRadii: readonly [1, 2, 4];
6
8
  readonly cornerNudge: 0.005;
7
9
  readonly cornerNudgeLarge: 0.025;
8
- readonly decodeMaxDimension: 3000;
10
+ readonly decodeMaxPixels: 16000000;
9
11
  readonly defaultFileName: "scan";
10
- readonly defaultJpegQuality: 0.82;
11
- readonly defaultMaxDimension: 2000;
12
- readonly defaultMaxPages: 30;
13
- readonly detectDilations: 2;
14
- readonly detectMaxDimension: 320;
15
- readonly detectMinAreaRatio: 0.12;
16
- readonly detectMaxAreaRatio: 0.995;
17
- readonly detectMinEdgeStrength: 24;
18
- readonly detectMinHullFill: 0.85;
19
- readonly detectPercentile: 0.98;
20
- readonly detectRelativeStrength: 0.3;
21
- readonly hullCandidateLimit: 18;
22
- readonly levelHighPercentile: 0.99;
23
- readonly levelLowPercentile: 0.01;
12
+ readonly defaultJpegQuality: 0.92;
13
+ readonly defaultMaxDimension: 4000;
14
+ readonly detectBorderMargin: 3;
15
+ readonly detectExpand: 0.3;
16
+ readonly detectMaxAngle: 140;
17
+ readonly detectMaxAreaRatio: 0.95;
18
+ readonly detectMaxDimension: 480;
19
+ readonly detectMinAngle: 40;
20
+ readonly detectMinAreaRatio: 0.1;
21
+ readonly detectMinQuadFill: 0.88;
22
+ readonly detectMinSideRatio: 0.06;
23
+ readonly guideArm: 36;
24
+ readonly guideArmRatio: 0.18;
25
+ readonly guideAspect: 0.72;
26
+ readonly guideHeightRatio: 0.58;
27
+ readonly guideLift: 0.03;
28
+ readonly guideWidthRatio: 0.76;
29
+ readonly liveDeadzone: 0.006;
30
+ readonly liveDetectIntervalMs: 200;
31
+ readonly liveDetectMaxDimension: 280;
32
+ readonly liveMissLimit: 3;
33
+ readonly liveSmoothing: 0.45;
34
+ readonly loupeOffset: 72;
35
+ readonly loupeSize: 112;
36
+ readonly loupeZoom: 2.5;
24
37
  readonly minCornerGap: 0.02;
25
- readonly pdfDpi: 150;
26
- readonly pdfPointsPerInch: 72;
27
- readonly previewDebounceMs: 120;
28
- readonly previewMaxDimension: 1400;
29
- readonly thumbnailHeight: 56;
38
+ readonly polygonEpsilons: readonly [0.008, 0.012, 0.018, 0.026, 0.036, 0.05, 0.07];
30
39
  readonly torchConstraint: "torch";
40
+ readonly warpChunkPixels: 400000;
31
41
  };
@@ -2,33 +2,43 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsScanConfig = void 0;
4
4
  exports.PsScanConfig = {
5
- adaptiveRatio: 0.92,
6
- adaptiveWindowDivisor: 16,
7
- adaptiveWindowMin: 15,
8
- capturedJpegQuality: 0.92,
5
+ cameraPermission: 'camera',
6
+ cannyHighPercentile: 0.9,
7
+ cannyLowRatio: 0.45,
8
+ cannyMinHigh: 12,
9
+ capturedJpegQuality: 0.97,
10
+ closeRadii: [1, 2, 4],
9
11
  cornerNudge: 0.005,
10
12
  cornerNudgeLarge: 0.025,
11
- decodeMaxDimension: 3000,
13
+ decodeMaxPixels: 16000000,
12
14
  defaultFileName: 'scan',
13
- defaultJpegQuality: 0.82,
14
- defaultMaxDimension: 2000,
15
- defaultMaxPages: 30,
16
- detectDilations: 2,
17
- detectMaxDimension: 320,
18
- detectMinAreaRatio: 0.12,
19
- detectMaxAreaRatio: 0.995,
20
- detectMinEdgeStrength: 24,
21
- detectMinHullFill: 0.85,
22
- detectPercentile: 0.98,
23
- detectRelativeStrength: 0.3,
24
- hullCandidateLimit: 18,
25
- levelHighPercentile: 0.99,
26
- levelLowPercentile: 0.01,
15
+ defaultJpegQuality: 0.92,
16
+ defaultMaxDimension: 4000,
17
+ detectBorderMargin: 3,
18
+ detectExpand: 0.3,
19
+ detectMaxAngle: 140,
20
+ detectMaxAreaRatio: 0.95,
21
+ detectMaxDimension: 480,
22
+ detectMinAngle: 40,
23
+ detectMinAreaRatio: 0.1,
24
+ detectMinQuadFill: 0.88,
25
+ detectMinSideRatio: 0.06,
26
+ guideArm: 36,
27
+ guideArmRatio: 0.18,
28
+ guideAspect: 0.72,
29
+ guideHeightRatio: 0.58,
30
+ guideLift: 0.03,
31
+ guideWidthRatio: 0.76,
32
+ liveDeadzone: 0.006,
33
+ liveDetectIntervalMs: 200,
34
+ liveDetectMaxDimension: 280,
35
+ liveMissLimit: 3,
36
+ liveSmoothing: 0.45,
37
+ loupeOffset: 72,
38
+ loupeSize: 112,
39
+ loupeZoom: 2.5,
27
40
  minCornerGap: 0.02,
28
- pdfDpi: 150,
29
- pdfPointsPerInch: 72,
30
- previewDebounceMs: 120,
31
- previewMaxDimension: 1400,
32
- thumbnailHeight: 56,
41
+ polygonEpsilons: [0.008, 0.012, 0.018, 0.026, 0.036, 0.05, 0.07],
33
42
  torchConstraint: 'torch',
43
+ warpChunkPixels: 400000,
34
44
  };
@@ -0,0 +1,6 @@
1
+ export declare const PsScanPlatform: {
2
+ readonly Android: "android";
3
+ readonly Desktop: "desktop";
4
+ readonly Ios: "ios";
5
+ };
6
+ export type PsScanPlatform = (typeof PsScanPlatform)[keyof typeof PsScanPlatform];
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsScanPlatform = void 0;
4
+ exports.PsScanPlatform = {
5
+ Android: 'android',
6
+ Desktop: 'desktop',
7
+ Ios: 'ios',
8
+ };
@@ -1,6 +1,6 @@
1
1
  export declare const PsScanStep: {
2
2
  readonly Camera: "camera";
3
3
  readonly Crop: "crop";
4
- readonly Review: "review";
4
+ readonly Preview: "preview";
5
5
  };
6
6
  export type PsScanStep = (typeof PsScanStep)[keyof typeof PsScanStep];
@@ -4,5 +4,5 @@ exports.PsScanStep = void 0;
4
4
  exports.PsScanStep = {
5
5
  Camera: 'camera',
6
6
  Crop: 'crop',
7
- Review: 'review',
7
+ Preview: 'preview',
8
8
  };
@@ -1,134 +1,112 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useRef, useState } from 'react';
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
3
  import { PsLayer, usePsLayerZIndex, usePsRestoreFocus, usePsScrollLock, usePsViewport, } from '@pallasoft/ps-core-components';
4
4
  import { PallasoftViewportTier, PallasoftZIndex } from '@pallasoft/ps-design-system';
5
- import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
6
- import { PsModal } from '@pallasoft/ps-popovers';
5
+ import { PsIconKey } from '@pallasoft/ps-icons';
7
6
  import { createPortal } from 'react-dom';
7
+ import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
8
8
  import { PsScanStep } from '../constants/ps-scan-step';
9
9
  import { usePsFocusTrap } from '../hooks/use-ps-focus-trap';
10
10
  import { usePsScanCamera } from '../hooks/use-ps-scan-camera';
11
- import { usePsScanPages } from '../hooks/use-ps-scan-pages';
12
- import { usePsScanPreviews } from '../hooks/use-ps-scan-previews';
13
- import { PsScanOutputService } from '../services/ps-scan-output-service';
11
+ import { usePsScanLiveDetection } from '../hooks/use-ps-scan-live-detection';
12
+ import { usePsScanPhoto } from '../hooks/use-ps-scan-photo';
13
+ import { PsScanFileNameService } from '../services/ps-scan-file-name-service';
14
14
  import { PsScanPipelineService } from '../services/ps-scan-pipeline-service';
15
- import { PsScanTemplateService } from '../services/ps-scan-template-service';
16
15
  import { PsScanCameraView } from './ps-scan-camera-view';
17
16
  import { PsScanCropEditor } from './ps-scan-crop-editor';
18
17
  import { PsScanIconButton } from './ps-scan-icon-button';
19
- import { PsScanReview } from './ps-scan-review';
20
- export const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }) => {
18
+ import { PsScanPermissionView } from './ps-scan-permission-view';
19
+ import { PsScanPreview } from './ps-scan-preview';
20
+ export const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, onClose, onComplete, }) => {
21
21
  const tier = usePsViewport();
22
22
  const zIndex = usePsLayerZIndex(PallasoftZIndex.Modal);
23
23
  const content = useRef(null);
24
- const { add, move, pages, remove, replace, setCorners, update } = usePsScanPages();
24
+ const { clear, detectAgain, load, photo, setCorners } = usePsScanPhoto();
25
25
  const [step, setStep] = useState(PsScanStep.Camera);
26
- const [activeId, setActiveId] = useState('');
27
- const [retakeId, setRetakeId] = useState('');
26
+ const [result, setResult] = useState(null);
28
27
  const [announcement, setAnnouncement] = useState('');
29
28
  const [error, setError] = useState('');
30
- const [isBusy, setIsBusy] = useState(false);
29
+ const [isCapturing, setIsCapturing] = useState(false);
30
+ const [flashKey, setFlashKey] = useState(0);
31
31
  const camera = usePsScanCamera(step === PsScanStep.Camera);
32
- const previews = usePsScanPreviews(pages);
33
- const page = pages.find((item) => item.id === activeId);
32
+ const isReady = camera.status === PsScanCameraStatus.Ready;
34
33
  const isDesktop = tier === PallasoftViewportTier.Desktop;
35
- usePsScrollLock(!isDesktop);
34
+ const isCameraStep = step === PsScanStep.Camera;
35
+ const showsCamera = isReady || camera.status === PsScanCameraStatus.Starting;
36
+ const detection = usePsScanLiveDetection(camera.videoRef, isCameraStep && isReady);
37
+ usePsScrollLock(true);
36
38
  usePsRestoreFocus(true);
37
39
  usePsFocusTrap(content, true, onClose);
40
+ useEffect(() => () => {
41
+ if (result !== null)
42
+ URL.revokeObjectURL(result.url);
43
+ }, [result]);
38
44
  const edgeMessage = (isDetected) => isDetected ? labels.edgesFound : labels.edgesNotFound;
39
- const addFiles = async (files) => {
45
+ const accept = async (source) => {
40
46
  setError('');
41
47
  try {
42
- const images = files.filter((file) => file.type === '' || file.type.startsWith('image/'));
43
- if (retakeId !== '' && images.length > 0) {
44
- const added = await replace(retakeId, images[0]);
45
- setActiveId(retakeId);
46
- setRetakeId('');
47
- setStep(PsScanStep.Crop);
48
- setAnnouncement(edgeMessage(added.isDetected));
49
- return;
50
- }
51
- const room = Math.max(0, maxPages - pages.length);
52
- if (images.length > room)
53
- setError(labels.maxPages);
54
- const added = await add(images.slice(0, room));
55
- if (added.length === 0)
56
- return;
57
- setActiveId(added[0].page.id);
58
- setStep(added.length === 1 ? PsScanStep.Crop : PsScanStep.Review);
59
- setAnnouncement(added.length === 1
60
- ? edgeMessage(added[0].isDetected)
61
- : PsScanTemplateService.fill(labels.pageAdded, { position: pages.length + added.length }));
48
+ const added = await load(source);
49
+ setAnnouncement(edgeMessage(added.isDetected));
50
+ setStep(PsScanStep.Crop);
62
51
  }
63
52
  catch {
64
53
  setError(labels.processFailed);
65
54
  }
66
55
  };
67
56
  const capture = async () => {
68
- const blob = await camera.capture();
69
- if (blob !== null)
70
- await addFiles([blob]);
71
- };
72
- const autoDetect = async () => {
73
- if (page === undefined)
57
+ if (isCapturing)
74
58
  return;
75
- const prepared = await PsScanPipelineService.prepare(page.source);
76
- setCorners(page.id, prepared.corners);
77
- setAnnouncement(edgeMessage(prepared.isDetected));
59
+ setIsCapturing(true);
60
+ try {
61
+ const blob = await camera.capture();
62
+ if (blob === null)
63
+ return;
64
+ setFlashKey((current) => current + 1);
65
+ await accept(blob);
66
+ }
67
+ finally {
68
+ setIsCapturing(false);
69
+ }
78
70
  };
71
+ const autoDetect = async () => setAnnouncement(edgeMessage(await detectAgain()));
79
72
  const retake = () => {
80
- if (page === undefined)
81
- return;
82
- setRetakeId(page.id);
73
+ clear();
74
+ setResult(null);
75
+ setError('');
83
76
  setStep(PsScanStep.Camera);
84
77
  };
85
- const deletePage = () => {
86
- if (page === undefined)
87
- return;
88
- const index = pages.findIndex((item) => item.id === page.id);
89
- const rest = pages.filter((item) => item.id !== page.id);
90
- remove(page.id);
91
- setAnnouncement(labels.pageDeleted);
92
- if (rest.length === 0) {
93
- setActiveId('');
94
- setStep(PsScanStep.Camera);
95
- }
96
- else
97
- setActiveId(rest[Math.min(index, rest.length - 1)].id);
78
+ const adjust = () => {
79
+ setResult(null);
80
+ setError('');
81
+ setStep(PsScanStep.Crop);
98
82
  };
99
- const finish = async () => {
100
- setIsBusy(true);
83
+ const flatten = async () => {
84
+ if (photo === null)
85
+ return;
86
+ setResult(null);
101
87
  setError('');
88
+ setStep(PsScanStep.Preview);
102
89
  try {
103
- const files = await PsScanOutputService.build(pages, {
104
- fileName,
105
- format: outputFormat,
106
- jpegQuality,
107
- maxDimension,
108
- });
109
- onComplete(files);
110
- onClose();
90
+ const rendered = await PsScanPipelineService.render(photo.source, photo.corners, maxDimension, jpegQuality);
91
+ setResult({ ...rendered, url: URL.createObjectURL(rendered.blob) });
111
92
  }
112
93
  catch {
113
94
  setError(labels.processFailed);
114
- setIsBusy(false);
115
95
  }
116
96
  };
117
- const moveTo = useCallback((from, to) => {
118
- move(from, to);
119
- setAnnouncement(PsScanTemplateService.fill(labels.pageMoved, {
120
- count: pages.length,
121
- page: `${labels.page} ${from + 1}`,
122
- position: to + 1,
123
- }));
124
- }, [move, pages.length, labels]);
125
- const body = (_jsxs("div", { className: 'relative h-full min-h-0 bg-surface text-ink', ref: content, children: [_jsx("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), step === PsScanStep.Camera ? (_jsx(PsScanCameraView, { canGoBack: pages.length > 0, canTorch: camera.canTorch, isTorchOn: camera.isTorchOn, labels: labels, onBack: () => {
126
- setRetakeId('');
127
- setStep(PsScanStep.Review);
128
- }, onCapture: capture, onFiles: addFiles, onToggleTorch: camera.toggleTorch, status: camera.status, videoRef: camera.videoRef })) : null, step === PsScanStep.Crop && page !== undefined ? (_jsx(PsScanCropEditor, { labels: labels, onApply: () => setStep(PsScanStep.Review), onAutoDetect: autoDetect, onChange: (corners) => setCorners(page.id, corners), onRetake: retake, page: page })) : null, step === PsScanStep.Review && page !== undefined ? (_jsx(PsScanReview, { canAddPage: pages.length < maxPages, error: error, isBusy: isBusy, labels: labels, onAddPage: () => setStep(PsScanStep.Camera), onDelete: deletePage, onEditCrop: () => setStep(PsScanStep.Crop), onFilter: (filter) => update(page.id, { filter }), onFinish: finish, onMove: moveTo, onRetake: retake, onRotate: () => update(page.id, { rotation: (page.rotation + 90) % 360 }), onSelect: setActiveId, page: page, pages: pages, previews: previews })) : null, step === PsScanStep.Camera && error !== '' ? (_jsx("p", { className: 'absolute inset-x-4 top-3 rounded-ps-control bg-danger px-3 py-2 text-sm text-white', role: 'alert', children: error })) : null] }));
129
- if (isDesktop)
130
- return (_jsx(PsModal, { isOpen: true, onClose: onClose, size: 'xl', title: labels.title, children: _jsx("div", { className: '-m-4 h-[min(70dvh,38rem)] overflow-hidden sm:-m-5', children: body }) }));
97
+ const use = () => {
98
+ if (result === null)
99
+ return;
100
+ onComplete([
101
+ new File([result.blob], PsScanFileNameService.build(fileName), { type: 'image/jpeg' }),
102
+ ]);
103
+ onClose();
104
+ };
131
105
  if (typeof document === 'undefined')
132
106
  return null;
133
- return createPortal(_jsx("div", { "aria-label": labels.title, "aria-modal": 'true', className: 'fixed inset-0 flex flex-col bg-surface text-ink animate-ps-fade-in', role: 'dialog', style: { zIndex }, children: _jsxs(PsLayer, { zIndex: zIndex, children: [_jsxs("header", { className: 'flex shrink-0 items-center justify-between gap-3 border-b border-line px-4 pt-[max(0.5rem,env(safe-area-inset-top))] pb-2', children: [_jsx("h2", { className: 'truncate text-base font-semibold', children: labels.title }), _jsx(PsScanIconButton, { label: labels.close, onClick: onClose, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: PsIconKey.Close }) })] }), _jsx("div", { className: 'min-h-0 flex-1', children: body })] }) }), document.body);
107
+ return createPortal(_jsx("div", { "aria-label": labels.title, "aria-modal": 'true', className: isDesktop
108
+ ? 'fixed inset-0 flex items-center justify-center bg-black/70 p-6 backdrop-blur-sm animate-ps-fade-in'
109
+ : 'fixed inset-0 bg-black animate-ps-fade-in', role: 'dialog', style: { zIndex }, children: _jsx(PsLayer, { zIndex: zIndex, children: _jsxs("div", { className: isDesktop
110
+ ? 'relative h-[min(52rem,calc(100dvh-3rem))] w-[min(76rem,calc(100vw-3rem))] overflow-hidden rounded-2xl bg-black text-white shadow-2xl ring-1 ring-white/10 animate-ps-scale-in'
111
+ : 'relative h-full overflow-hidden bg-black text-white', ref: content, children: [_jsx("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), isCameraStep && showsCamera ? (_jsx(PsScanCameraView, { canTorch: camera.canTorch, detection: detection, flashKey: flashKey, isBusy: isCapturing, isReady: isReady, isTorchOn: camera.isTorchOn, labels: labels, onCapture: capture, onClose: onClose, onFile: accept, onToggleTorch: camera.toggleTorch, videoRef: camera.videoRef, videoTarget: camera.attachVideo })) : null, isCameraStep && !showsCamera ? (_jsxs(_Fragment, { children: [_jsx(PsScanPermissionView, { labels: labels, onAllow: camera.allow, onFile: accept, status: camera.status }), _jsx("div", { className: 'absolute left-4 top-[max(1rem,env(safe-area-inset-top))] flex gap-2', children: _jsx(PsScanIconButton, { iconKey: PsIconKey.Close, label: labels.close, onClick: onClose }) })] })) : null, step === PsScanStep.Crop && photo !== null ? (_jsx(PsScanCropEditor, { labels: labels, onAutoDetect: autoDetect, onChange: setCorners, onContinue: flatten, onRetake: retake, photo: photo })) : null, step === PsScanStep.Preview ? (_jsx(PsScanPreview, { error: error, labels: labels, onAdjust: adjust, onRetake: retake, onUse: use, result: result })) : null, isCameraStep && error !== '' ? (_jsx("p", { className: 'absolute inset-x-4 bottom-36 mx-auto max-w-sm rounded-ps-control bg-danger px-3 py-2 text-center text-sm text-white', role: 'alert', children: error })) : null] }) }) }), document.body);
134
112
  };
@@ -3,11 +3,10 @@ import { Suspense } from 'react';
3
3
  import { usePsHydrated } from '@pallasoft/ps-core-components';
4
4
  import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
5
5
  import { PsScanConfig } from '../constants/ps-scan-config';
6
- import { PsScanOutputFormat } from '../constants/ps-scan-output-format';
7
6
  import { PsDocumentScannerLoader } from './ps-document-scanner-loader';
8
- export const PsDocumentScanner = ({ fileName = PsScanConfig.defaultFileName, isOpen, jpegQuality = PsScanConfig.defaultJpegQuality, labels, maxDimension = PsScanConfig.defaultMaxDimension, maxPages = PsScanConfig.defaultMaxPages, onClose, onComplete, outputFormat = PsScanOutputFormat.Pdf, }) => {
7
+ export const PsDocumentScanner = ({ fileName = PsScanConfig.defaultFileName, isOpen, jpegQuality = PsScanConfig.defaultJpegQuality, labels, maxDimension = PsScanConfig.defaultMaxDimension, onClose, onComplete, }) => {
9
8
  const isHydrated = usePsHydrated();
10
9
  if (!isOpen || !isHydrated)
11
10
  return null;
12
- return (_jsx(Suspense, { fallback: null, children: _jsx(PsDocumentScannerLoader, { fileName: fileName, jpegQuality: jpegQuality, labels: { ...PsDocumentScannerLabels, ...labels }, maxDimension: maxDimension, maxPages: maxPages, onClose: onClose, onComplete: onComplete, outputFormat: outputFormat }) }));
11
+ return (_jsx(Suspense, { fallback: null, children: _jsx(PsDocumentScannerLoader, { fileName: fileName, jpegQuality: jpegQuality, labels: { ...PsDocumentScannerLabels, ...labels }, maxDimension: maxDimension, onClose: onClose, onComplete: onComplete }) }));
13
12
  };
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PsButton } from '@pallasoft/ps-core-components';
3
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
+ import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
5
+ export const PsFilePickerActions = ({ chooseLabel, className, disabled, isTouch, onChoose, onScan, scanLabel, }) => {
6
+ const hasScan = scanLabel !== undefined;
7
+ const choose = (_jsx(PsButton, { className: isTouch ? 'w-full' : '', disabled: disabled, leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Upload }), onClick: (event) => {
8
+ event.stopPropagation();
9
+ onChoose();
10
+ }, size: isTouch ? 'large' : 'default', variant: hasScan && isTouch ? 'surface' : hasScan || isTouch ? 'primary' : 'surface', children: chooseLabel }));
11
+ return (_jsx("div", { className: PallasoftClassNameService.merge(isTouch
12
+ ? 'w-full max-w-xs flex-col gap-2'
13
+ : 'flex-row flex-wrap items-center justify-center gap-2', className), children: hasScan ? (_jsxs(_Fragment, { children: [isTouch ? null : choose, _jsx(PsButton, { className: isTouch ? 'w-full' : '', disabled: disabled, leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Camera }), onClick: (event) => {
14
+ event.stopPropagation();
15
+ onScan();
16
+ }, size: isTouch ? 'large' : 'default', variant: isTouch ? 'primary' : 'surface', children: scanLabel }), isTouch ? choose : null] })) : (choose) }));
17
+ };
@@ -1,15 +1,26 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo, useState } from 'react';
2
+ import { useMemo, useRef, useState } from 'react';
3
3
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
4
  import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
5
5
  import { PsFilePickerLabels } from '../constants/ps-file-picker-labels';
6
+ import { PsScanConfig } from '../constants/ps-scan-config';
7
+ import { PsUploadLabels } from '../constants/ps-upload-labels';
6
8
  import { PsFileSelectionService } from '../services/ps-file-selection-service';
7
- export const PsFilePicker = ({ accept, acceptLabel, className, disabled = false, dropLabel, isLoading = false, labels, maxSizeBytes, maxSizeLabel, multiple = false, onError, onFileLoad, onFilesSelect, touchLabel, }) => {
9
+ import { PsDocumentScanner } from './ps-document-scanner';
10
+ import { PsFilePickerActions } from './ps-file-picker-actions';
11
+ import { PsUploadRow } from './ps-upload-row';
12
+ export const PsFilePicker = ({ accept, acceptLabel, className, disabled = false, dropLabel, isLoading = false, labels, maxSizeBytes, maxSizeLabel, multiple = false, onError, onFileLoad, onFilesSelect, queue, scan, touchLabel, uploadLabels, }) => {
8
13
  const [isDragging, setIsDragging] = useState(false);
14
+ const [scanName, setScanName] = useState(null);
15
+ const input = useRef(null);
9
16
  const acceptAttr = useMemo(() => accept.join(','), [accept]);
10
17
  const text = { ...PsFilePickerLabels, ...labels };
18
+ const rowLabels = { ...PsUploadLabels, ...uploadLabels };
19
+ const details = [acceptLabel, maxSizeLabel].filter((value) => value !== undefined && value !== '');
20
+ const chooseLabel = multiple ? text.choose : text.chooseOne;
21
+ const scanLabel = scan === undefined ? undefined : text.scan;
11
22
  if (isLoading)
12
- return (_jsx("span", { "aria-busy": 'true', className: PallasoftClassNameService.merge('ps-skeleton block min-h-32 w-full rounded-ps-panel', className), role: 'status' }));
23
+ return (_jsx("span", { "aria-busy": 'true', className: PallasoftClassNameService.merge('ps-skeleton block min-h-48 w-full rounded-ps-control', className), role: 'status' }));
13
24
  const select = (list) => {
14
25
  if (list === null || disabled)
15
26
  return;
@@ -28,6 +39,15 @@ export const PsFilePicker = ({ accept, acceptLabel, className, disabled = false,
28
39
  else
29
40
  onFileLoad?.(accepted[0]);
30
41
  };
42
+ const browse = () => {
43
+ if (!disabled)
44
+ input.current?.click();
45
+ };
46
+ const openScan = () => {
47
+ if (disabled || scan === undefined)
48
+ return;
49
+ setScanName(scan.getFileName === undefined ? PsScanConfig.defaultFileName : scan.getFileName());
50
+ };
31
51
  const onChange = (event) => {
32
52
  select(event.target.files);
33
53
  event.target.value = '';
@@ -37,8 +57,12 @@ export const PsFilePicker = ({ accept, acceptLabel, className, disabled = false,
37
57
  if (!disabled)
38
58
  setIsDragging(isOver);
39
59
  };
40
- return (_jsxs("div", { className: PallasoftClassNameService.merge('relative flex h-48 w-full flex-col items-center justify-center rounded-ps-control border-2 border-dashed border-line p-4 transition-all hover:border-primary/50 hover:bg-primary/5 sm:h-64', isDragging ? 'border-primary bg-primary/10' : '', disabled ? 'pointer-events-none opacity-60' : '', className), onDragEnter: (event) => onDrag(event, true), onDragLeave: (event) => onDrag(event, false), onDragOver: (event) => onDrag(event, true), onDrop: (event) => {
41
- onDrag(event, false);
42
- select(event.dataTransfer.files);
43
- }, children: [_jsxs("div", { className: 'pointer-events-none flex flex-col items-center gap-3 text-center', children: [_jsx(PsIcon, { className: 'h-8 w-8 text-primary', iconKey: PsIconKey.Upload }), _jsxs("div", { children: [_jsx("p", { className: 'text-body-2 font-medium text-ink sm:text-body-1', children: isDragging ? (text.dropActive) : (_jsxs(_Fragment, { children: [_jsx("span", { className: touchLabel === undefined ? '' : '[@media(pointer:coarse)]:hidden', children: dropLabel }), touchLabel === undefined ? null : (_jsx("span", { className: 'hidden [@media(pointer:coarse)]:inline', children: touchLabel }))] })) }), _jsxs("p", { className: 'mt-1 text-body-3 text-ink/60 sm:text-body-2', children: [_jsx("span", { className: '[@media(pointer:coarse)]:hidden', children: text.browse }), _jsx("span", { className: 'hidden [@media(pointer:coarse)]:inline', children: text.browseTouch })] }), maxSizeLabel ? _jsx("p", { className: 'mt-3 text-body-3 text-ink/60', children: maxSizeLabel }) : null, _jsxs("p", { className: 'text-body-3 text-ink/60', children: [text.formats, " ", acceptLabel] })] })] }), _jsx("input", { accept: acceptAttr, "aria-disabled": disabled, "aria-label": dropLabel, className: 'absolute inset-0 h-full w-full cursor-pointer opacity-0', disabled: disabled, multiple: multiple, onChange: onChange, type: 'file' })] }));
60
+ return (_jsxs("div", { className: PallasoftClassNameService.merge('flex w-full flex-col gap-3', className), children: [_jsxs("div", { "aria-disabled": disabled || undefined, className: PallasoftClassNameService.merge('flex w-full cursor-pointer flex-col items-center gap-5 rounded-ps-control border-2 border-dashed px-5 py-8 text-center transition-colors duration-200 sm:px-8', isDragging
61
+ ? 'border-primary bg-primary/5'
62
+ : 'border-line bg-surface/40 hover:border-primary/40 hover:bg-primary/[0.03]', disabled ? 'pointer-events-none opacity-60' : ''), onClick: browse, onDragEnter: (event) => onDrag(event, true), onDragLeave: (event) => onDrag(event, false), onDragOver: (event) => onDrag(event, true), onDrop: (event) => {
63
+ onDrag(event, false);
64
+ select(event.dataTransfer.files);
65
+ }, children: [_jsx("span", { "aria-hidden": 'true', className: PallasoftClassNameService.merge('flex size-12 items-center justify-center rounded-full transition-colors duration-200', isDragging
66
+ ? 'bg-primary text-[color:var(--ps-color-primary-contrast)]'
67
+ : 'bg-primary-soft text-primary'), children: _jsx(PsIcon, { className: 'size-6', iconKey: PsIconKey.Upload }) }), _jsxs("div", { className: 'flex flex-col gap-1', children: [_jsx("p", { className: 'text-base font-semibold text-ink', children: isDragging ? (text.dropActive) : (_jsxs(_Fragment, { children: [_jsx("span", { className: touchLabel === undefined ? '' : '[@media(pointer:coarse)]:hidden', children: dropLabel }), touchLabel === undefined ? null : (_jsx("span", { className: 'hidden [@media(pointer:coarse)]:inline', children: touchLabel }))] })) }), _jsx("p", { className: 'text-sm text-ink/60 [@media(pointer:coarse)]:hidden', children: multiple ? text.dragHint : text.dragHintOne })] }), _jsx(PsFilePickerActions, { chooseLabel: chooseLabel, className: 'flex [@media(pointer:coarse)]:hidden', disabled: disabled, isTouch: false, onChoose: browse, onScan: openScan, scanLabel: scanLabel }), _jsx(PsFilePickerActions, { chooseLabel: chooseLabel, className: 'hidden [@media(pointer:coarse)]:flex', disabled: disabled, isTouch: true, onChoose: browse, onScan: openScan, scanLabel: scanLabel }), details.length > 0 ? _jsx("p", { className: 'text-xs text-ink/50', children: details.join(' · ') }) : null, _jsx("input", { accept: acceptAttr, "aria-hidden": 'true', className: 'sr-only', disabled: disabled, multiple: multiple, onChange: onChange, onClick: (event) => event.stopPropagation(), ref: input, tabIndex: -1, type: 'file' })] }), queue === undefined || queue.items.length === 0 ? null : (_jsx("ul", { "aria-label": rowLabels.title, className: 'flex flex-col gap-2', children: queue.items.map((item) => (_jsx(PsUploadRow, { item: item, labels: rowLabels, onCancel: () => queue.cancel(item.id), onRemove: () => queue.remove(item.id), onRetry: () => queue.retry(item.id) }, item.id))) })), scan === undefined ? null : (_jsx(PsDocumentScanner, { ...scan, fileName: scanName === null ? PsScanConfig.defaultFileName : scanName, isOpen: scanName !== null, onClose: () => setScanName(null), onComplete: select }))] }));
44
68
  };
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PsSpinner } from '@pallasoft/ps-core-components';
3
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
+ import { PsScanChromeClasses } from '../constants/ps-scan-chrome-classes';
5
+ export const PsScanButton = ({ children, className, disabled = false, isLarge = false, isLoading = false, isPrimary = false, leadingIcon, loadingLabel, onClick, }) => (_jsxs("button", { "aria-busy": isLoading || undefined, className: PallasoftClassNameService.merge(PsScanChromeClasses.button, isLarge ? PsScanChromeClasses.buttonLarge : '', isPrimary ? PsScanChromeClasses.buttonPrimary : PsScanChromeClasses.buttonQuiet, 'relative', className), disabled: disabled || isLoading, onClick: onClick, type: 'button', children: [isLoading ? (_jsx(PsSpinner, { className: 'absolute inset-0 m-auto', color: 'current', label: loadingLabel === undefined ? '' : loadingLabel, size: 'small' })) : null, _jsxs("span", { "aria-hidden": isLoading || undefined, className: `inline-flex min-w-0 items-center gap-2 ${isLoading ? 'invisible' : ''}`, children: [leadingIcon === undefined ? null : _jsx("span", { className: 'shrink-0', children: leadingIcon }), _jsx("span", { className: 'truncate', children: children })] })] }));
@@ -1,23 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef } from 'react';
3
- import { PsButton, PsSpinner } from '@pallasoft/ps-core-components';
4
- import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
5
- import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
2
+ import { useState } from 'react';
3
+ import { PsSpinner, usePsElementSize } from '@pallasoft/ps-core-components';
4
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
5
+ import { PsIconKey } from '@pallasoft/ps-icons';
6
+ import { PsScanChromeClasses } from '../constants/ps-scan-chrome-classes';
6
7
  import { PsScanIconButton } from './ps-scan-icon-button';
7
- export const PsScanCameraView = ({ canGoBack, canTorch, isTorchOn, labels, onBack, onCapture, onFiles, onToggleTorch, status, videoRef, }) => {
8
- const captureInput = useRef(null);
9
- const libraryInput = useRef(null);
10
- const isReady = status === PsScanCameraStatus.Ready;
11
- const message = status === PsScanCameraStatus.Denied
12
- ? labels.cameraDenied
13
- : status === PsScanCameraStatus.Unavailable
14
- ? labels.cameraUnavailable
15
- : '';
16
- const onChange = (event) => {
17
- const files = Array.from(event.target.files === null ? [] : event.target.files);
18
- event.target.value = '';
19
- if (files.length > 0)
20
- onFiles(files);
8
+ import { PsScanPhotoButton } from './ps-scan-photo-button';
9
+ import { PsScanShutter } from './ps-scan-shutter';
10
+ import { PsScanViewfinderOverlay } from './ps-scan-viewfinder-overlay';
11
+ export const PsScanCameraView = ({ canTorch, detection, flashKey, isBusy, isReady, isTorchOn, labels, onCapture, onClose, onFile, onToggleTorch, videoRef, videoTarget, }) => {
12
+ const [areaRef, area] = usePsElementSize();
13
+ const [video, setVideo] = useState({ height: 0, width: 0 });
14
+ const hint = detection.quad === null ? labels.hintFind : labels.hintFound;
15
+ const measure = () => {
16
+ const element = videoRef.current;
17
+ if (element !== null)
18
+ setVideo({ height: element.videoHeight, width: element.videoWidth });
21
19
  };
22
- return (_jsxs("div", { className: 'flex h-full min-h-0 flex-col bg-slate-950 text-white', children: [_jsxs("div", { className: 'relative min-h-0 flex-1', children: [_jsx("video", { "aria-label": labels.title, autoPlay: true, className: `size-full object-contain ${isReady ? '' : 'invisible'}`, muted: true, playsInline: true, ref: videoRef }), status === PsScanCameraStatus.Starting ? (_jsxs("div", { className: 'absolute inset-0 flex flex-col items-center justify-center gap-3 text-sm text-white/80', role: 'status', children: [_jsx(PsSpinner, { color: 'current', label: labels.cameraStarting, size: 'small' }), _jsx("span", { children: labels.cameraStarting })] })) : null, message === '' ? null : (_jsx("div", { className: 'absolute inset-0 flex items-center justify-center p-6 text-center text-sm text-white/85', role: 'status', children: _jsx("p", { className: 'max-w-sm', children: message }) }))] }), _jsxs("div", { className: 'flex shrink-0 flex-wrap items-center justify-center gap-3 px-4 pt-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]', children: [canGoBack ? (_jsx(PsButton, { onClick: onBack, variant: 'surface', children: labels.backToPages })) : null, _jsx(PsButton, { leadingIcon: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Image }), onClick: () => libraryInput.current?.click(), variant: 'surface', children: labels.choosePhotos }), isReady ? (_jsx("button", { "aria-label": labels.capture, className: 'ps-focus-ring flex size-[4.5rem] shrink-0 items-center justify-center rounded-full border-4 border-white bg-white/20 transition active:scale-95', onClick: onCapture, title: labels.capture, type: 'button', children: _jsx("span", { className: 'size-12 rounded-full bg-white' }) })) : (_jsx(PsButton, { onClick: () => captureInput.current?.click(), variant: 'primary', children: labels.takePhoto })), canTorch && isReady ? (_jsx(PsScanIconButton, { isPressed: isTorchOn, label: isTorchOn ? labels.torchOff : labels.torchOn, onClick: onToggleTorch, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: PsIconKey.Spark }) })) : null] }), _jsx("input", { accept: 'image/*', capture: 'environment', className: 'sr-only', onChange: onChange, ref: captureInput, tabIndex: -1, type: 'file' }), _jsx("input", { accept: 'image/*', className: 'sr-only', multiple: true, onChange: onChange, ref: libraryInput, tabIndex: -1, type: 'file' })] }));
20
+ return (_jsxs("div", { className: 'relative h-full min-h-0 overflow-hidden bg-black text-white', children: [_jsxs("div", { className: 'absolute inset-0', ref: areaRef, children: [_jsx("video", { "aria-hidden": 'true', autoPlay: true, className: PallasoftClassNameService.merge('size-full object-cover transition-opacity duration-300', isReady ? 'opacity-100' : 'opacity-0'), muted: true, onLoadedMetadata: measure, onResize: measure, playsInline: true, ref: videoTarget }), isReady ? (_jsx(PsScanViewfinderOverlay, { area: area, quad: detection.quad, video: video })) : null] }), isReady ? null : (_jsxs("div", { className: 'absolute inset-0 flex flex-col items-center justify-center gap-3 text-sm text-white/75', role: 'status', children: [_jsx(PsSpinner, { color: 'current', label: labels.cameraStarting, size: 'small' }), _jsx("span", { children: labels.cameraStarting })] })), flashKey > 0 ? (_jsx("div", { "aria-hidden": 'true', className: 'pointer-events-none absolute inset-0 bg-white animate-ps-fade-out' }, flashKey)) : null, _jsxs("div", { className: 'absolute inset-x-0 top-0 flex items-start justify-between gap-3 bg-gradient-to-b from-black/60 to-transparent px-4 pb-10 pt-[max(1rem,env(safe-area-inset-top))]', children: [_jsx(PsScanIconButton, { iconKey: PsIconKey.Close, label: labels.close, onClick: onClose }), _jsx("div", { className: 'flex items-center gap-2', children: canTorch && isReady ? (_jsx(PsScanIconButton, { iconKey: PsIconKey.Flash, isPressed: isTorchOn, label: isTorchOn ? labels.torchOff : labels.torchOn, onClick: onToggleTorch })) : null })] }), isReady ? (_jsx("div", { className: 'absolute inset-x-0 top-[calc(max(1rem,env(safe-area-inset-top))+3.75rem)] flex justify-center px-4', children: _jsx("p", { "aria-live": 'polite', className: PsScanChromeClasses.pill, children: hint }) })) : null, _jsxs("div", { className: 'absolute inset-x-0 bottom-0 grid grid-cols-[1fr_auto_1fr] items-center gap-4 bg-gradient-to-t from-black/80 to-transparent px-5 pb-[max(1.5rem,env(safe-area-inset-bottom))] pt-12', children: [_jsx("div", { className: 'flex justify-start', children: _jsx(PsScanPhotoButton, { isIconOnly: true, label: labels.choosePhoto, onFile: onFile }) }), _jsx(PsScanShutter, { disabled: !isReady || isBusy, label: labels.capture, onCapture: onCapture }), _jsx("span", { "aria-hidden": 'true' })] })] }));
23
21
  };