@pallasoft/ps-file-components 1.4.0 → 1.5.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 (113) 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 +69 -21
  4. package/dist/components/ps-document-scanner.d.ts +1 -1
  5. package/dist/components/ps-document-scanner.js +2 -2
  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 +17 -7
  13. package/dist/components/ps-scan-camera-view.js +23 -16
  14. package/dist/components/ps-scan-crop-editor.d.ts +2 -2
  15. package/dist/components/ps-scan-crop-editor.js +19 -3
  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-page-list.d.ts +3 -1
  25. package/dist/components/ps-scan-page-list.js +7 -8
  26. package/dist/components/ps-scan-page-stack.d.ts +7 -0
  27. package/dist/components/ps-scan-page-stack.js +6 -0
  28. package/dist/components/ps-scan-permission-view.d.ts +11 -0
  29. package/dist/components/ps-scan-permission-view.js +48 -0
  30. package/dist/components/ps-scan-photo-button.d.ts +8 -0
  31. package/dist/components/ps-scan-photo-button.js +20 -0
  32. package/dist/components/ps-scan-review.d.ts +2 -1
  33. package/dist/components/ps-scan-review.js +21 -4
  34. package/dist/components/ps-scan-shutter.d.ts +7 -0
  35. package/dist/components/ps-scan-shutter.js +6 -0
  36. package/dist/components/ps-scan-viewfinder-overlay.d.ts +8 -0
  37. package/dist/components/ps-scan-viewfinder-overlay.js +28 -0
  38. package/dist/components/ps-upload-row.js +1 -1
  39. package/dist/constants/ps-document-scanner-labels.d.ts +49 -19
  40. package/dist/constants/ps-document-scanner-labels.js +49 -19
  41. package/dist/constants/ps-file-picker-labels.d.ts +5 -3
  42. package/dist/constants/ps-file-picker-labels.js +5 -3
  43. package/dist/constants/ps-scan-camera-status.d.ts +4 -0
  44. package/dist/constants/ps-scan-camera-status.js +4 -0
  45. package/dist/constants/ps-scan-chrome-classes.d.ts +12 -0
  46. package/dist/constants/ps-scan-chrome-classes.js +15 -0
  47. package/dist/constants/ps-scan-config.d.ts +18 -1
  48. package/dist/constants/ps-scan-config.js +18 -1
  49. package/dist/constants/ps-scan-platform.d.ts +6 -0
  50. package/dist/constants/ps-scan-platform.js +8 -0
  51. package/dist/esm/components/ps-document-scanner-sheet.js +71 -23
  52. package/dist/esm/components/ps-document-scanner.js +2 -2
  53. package/dist/esm/components/ps-file-picker-actions.js +17 -0
  54. package/dist/esm/components/ps-file-picker.js +31 -7
  55. package/dist/esm/components/ps-scan-button.js +5 -0
  56. package/dist/esm/components/ps-scan-camera-view.js +26 -19
  57. package/dist/esm/components/ps-scan-crop-editor.js +21 -5
  58. package/dist/esm/components/ps-scan-denied-steps.js +29 -0
  59. package/dist/esm/components/ps-scan-icon-button.js +3 -1
  60. package/dist/esm/components/ps-scan-illustration.js +2 -0
  61. package/dist/esm/components/ps-scan-loupe.js +27 -0
  62. package/dist/esm/components/ps-scan-page-list.js +7 -8
  63. package/dist/esm/components/ps-scan-page-stack.js +2 -0
  64. package/dist/esm/components/ps-scan-permission-view.js +44 -0
  65. package/dist/esm/components/ps-scan-photo-button.js +16 -0
  66. package/dist/esm/components/ps-scan-review.js +21 -4
  67. package/dist/esm/components/ps-scan-shutter.js +2 -0
  68. package/dist/esm/components/ps-scan-viewfinder-overlay.js +24 -0
  69. package/dist/esm/components/ps-upload-row.js +1 -1
  70. package/dist/esm/constants/ps-document-scanner-labels.js +49 -19
  71. package/dist/esm/constants/ps-file-picker-labels.js +5 -3
  72. package/dist/esm/constants/ps-scan-camera-status.js +4 -0
  73. package/dist/esm/constants/ps-scan-chrome-classes.js +12 -0
  74. package/dist/esm/constants/ps-scan-config.js +18 -1
  75. package/dist/esm/constants/ps-scan-platform.js +5 -0
  76. package/dist/esm/hooks/use-ps-scan-camera.js +52 -15
  77. package/dist/esm/hooks/use-ps-scan-live-detection.js +45 -0
  78. package/dist/esm/index.js +1 -0
  79. package/dist/esm/models/ps-file-picker-scan.js +1 -0
  80. package/dist/esm/models/ps-scan-live-detection.js +1 -0
  81. package/dist/esm/models/ps-scan-stability.js +1 -0
  82. package/dist/esm/services/ps-scan-camera-service.js +50 -5
  83. package/dist/esm/services/ps-scan-canvas-service.js +6 -0
  84. package/dist/esm/services/ps-scan-edge-service.js +1 -1
  85. package/dist/esm/services/ps-scan-geometry-service.js +20 -0
  86. package/dist/esm/services/ps-scan-platform-service.js +15 -0
  87. package/dist/esm/services/ps-scan-stability-service.js +41 -0
  88. package/dist/hooks/use-ps-scan-camera.d.ts +3 -1
  89. package/dist/hooks/use-ps-scan-camera.js +52 -15
  90. package/dist/hooks/use-ps-scan-live-detection.d.ts +3 -0
  91. package/dist/hooks/use-ps-scan-live-detection.js +49 -0
  92. package/dist/index.d.ts +1 -0
  93. package/dist/index.js +1 -0
  94. package/dist/models/ps-document-scanner-props.d.ts +1 -0
  95. package/dist/models/ps-document-scanner-sheet-props.d.ts +1 -0
  96. package/dist/models/ps-file-picker-scan.d.ts +4 -0
  97. package/dist/models/ps-file-picker-scan.js +2 -0
  98. package/dist/models/ps-scan-live-detection.d.ts +5 -0
  99. package/dist/models/ps-scan-live-detection.js +2 -0
  100. package/dist/models/ps-scan-stability.d.ts +8 -0
  101. package/dist/models/ps-scan-stability.js +2 -0
  102. package/dist/services/ps-scan-camera-service.d.ts +4 -0
  103. package/dist/services/ps-scan-camera-service.js +50 -5
  104. package/dist/services/ps-scan-canvas-service.d.ts +1 -0
  105. package/dist/services/ps-scan-canvas-service.js +6 -0
  106. package/dist/services/ps-scan-edge-service.js +1 -1
  107. package/dist/services/ps-scan-geometry-service.d.ts +2 -0
  108. package/dist/services/ps-scan-geometry-service.js +20 -0
  109. package/dist/services/ps-scan-platform-service.d.ts +4 -0
  110. package/dist/services/ps-scan-platform-service.js +19 -0
  111. package/dist/services/ps-scan-stability-service.d.ts +9 -0
  112. package/dist/services/ps-scan-stability-service.js +45 -0
  113. package/package.json +3 -3
@@ -1 +1 @@
1
- export declare const PsDocumentScannerLoader: import("react").LazyExoticComponent<({ fileName, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: import("../models/ps-document-scanner-sheet-props").PsDocumentScannerSheetProps) => import("react").JSX.Element | null>;
1
+ export declare const PsDocumentScannerLoader: import("react").LazyExoticComponent<({ fileName, isAutoCapture, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: import("../models/ps-document-scanner-sheet-props").PsDocumentScannerSheetProps) => import("react").ReactPortal | null>;
@@ -1,2 +1,2 @@
1
1
  import type { PsDocumentScannerSheetProps } from '../models/ps-document-scanner-sheet-props';
2
- export declare const PsDocumentScannerSheet: ({ fileName, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: PsDocumentScannerSheetProps) => import("react").JSX.Element | null;
2
+ export declare const PsDocumentScannerSheet: ({ fileName, isAutoCapture, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: PsDocumentScannerSheetProps) => import("react").ReactPortal | null;
@@ -6,11 +6,12 @@ const react_1 = require("react");
6
6
  const ps_core_components_1 = require("@pallasoft/ps-core-components");
7
7
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
8
8
  const ps_icons_1 = require("@pallasoft/ps-icons");
9
- const ps_popovers_1 = require("@pallasoft/ps-popovers");
10
9
  const react_dom_1 = require("react-dom");
10
+ const ps_scan_camera_status_1 = require("../constants/ps-scan-camera-status");
11
11
  const ps_scan_step_1 = require("../constants/ps-scan-step");
12
12
  const use_ps_focus_trap_1 = require("../hooks/use-ps-focus-trap");
13
13
  const use_ps_scan_camera_1 = require("../hooks/use-ps-scan-camera");
14
+ const use_ps_scan_live_detection_1 = require("../hooks/use-ps-scan-live-detection");
14
15
  const use_ps_scan_pages_1 = require("../hooks/use-ps-scan-pages");
15
16
  const use_ps_scan_previews_1 = require("../hooks/use-ps-scan-previews");
16
17
  const ps_scan_output_service_1 = require("../services/ps-scan-output-service");
@@ -19,27 +20,37 @@ const ps_scan_template_service_1 = require("../services/ps-scan-template-service
19
20
  const ps_scan_camera_view_1 = require("./ps-scan-camera-view");
20
21
  const ps_scan_crop_editor_1 = require("./ps-scan-crop-editor");
21
22
  const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
23
+ const ps_scan_permission_view_1 = require("./ps-scan-permission-view");
22
24
  const ps_scan_review_1 = require("./ps-scan-review");
23
- const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }) => {
25
+ const PsDocumentScannerSheet = ({ fileName, isAutoCapture, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }) => {
24
26
  const tier = (0, ps_core_components_1.usePsViewport)();
25
27
  const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Modal);
26
28
  const content = (0, react_1.useRef)(null);
27
29
  const { add, move, pages, remove, replace, setCorners, update } = (0, use_ps_scan_pages_1.usePsScanPages)();
28
30
  const [step, setStep] = (0, react_1.useState)(ps_scan_step_1.PsScanStep.Camera);
31
+ const [cropReturn, setCropReturn] = (0, react_1.useState)(ps_scan_step_1.PsScanStep.Camera);
29
32
  const [activeId, setActiveId] = (0, react_1.useState)('');
30
33
  const [retakeId, setRetakeId] = (0, react_1.useState)('');
31
34
  const [announcement, setAnnouncement] = (0, react_1.useState)('');
32
35
  const [error, setError] = (0, react_1.useState)('');
33
36
  const [isBusy, setIsBusy] = (0, react_1.useState)(false);
37
+ const [isCapturing, setIsCapturing] = (0, react_1.useState)(false);
38
+ const [isAuto, setIsAuto] = (0, react_1.useState)(isAutoCapture);
39
+ const [flashKey, setFlashKey] = (0, react_1.useState)(0);
34
40
  const camera = (0, use_ps_scan_camera_1.usePsScanCamera)(step === ps_scan_step_1.PsScanStep.Camera);
41
+ const isReady = camera.status === ps_scan_camera_status_1.PsScanCameraStatus.Ready;
42
+ const isFull = pages.length >= maxPages;
35
43
  const previews = (0, use_ps_scan_previews_1.usePsScanPreviews)(pages);
36
44
  const page = pages.find((item) => item.id === activeId);
45
+ const lastPage = pages[pages.length - 1];
37
46
  const isDesktop = tier === ps_design_system_1.PallasoftViewportTier.Desktop;
38
- (0, ps_core_components_1.usePsScrollLock)(!isDesktop);
47
+ const isCameraStep = step === ps_scan_step_1.PsScanStep.Camera;
48
+ const showsCamera = isReady || camera.status === ps_scan_camera_status_1.PsScanCameraStatus.Starting;
49
+ (0, ps_core_components_1.usePsScrollLock)(true);
39
50
  (0, ps_core_components_1.usePsRestoreFocus)(true);
40
51
  (0, use_ps_focus_trap_1.usePsFocusTrap)(content, true, onClose);
41
52
  const edgeMessage = (isDetected) => isDetected ? labels.edgesFound : labels.edgesNotFound;
42
- const addFiles = async (files) => {
53
+ const addFiles = async (files, after) => {
43
54
  setError('');
44
55
  try {
45
56
  const images = files.filter((file) => file.type === '' || file.type.startsWith('image/'));
@@ -57,21 +68,39 @@ const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, m
57
68
  const added = await add(images.slice(0, room));
58
69
  if (added.length === 0)
59
70
  return;
60
- setActiveId(added[0].page.id);
61
- setStep(added.length === 1 ? ps_scan_step_1.PsScanStep.Crop : ps_scan_step_1.PsScanStep.Review);
62
- setAnnouncement(added.length === 1
63
- ? edgeMessage(added[0].isDetected)
64
- : ps_scan_template_service_1.PsScanTemplateService.fill(labels.pageAdded, { position: pages.length + added.length }));
71
+ const position = pages.length + added.length;
72
+ setActiveId(added[added.length - 1].page.id);
73
+ setAnnouncement(ps_scan_template_service_1.PsScanTemplateService.fill(labels.pageAdded, { position }));
74
+ if (after === null)
75
+ return;
76
+ if (added.length === 1) {
77
+ setCropReturn(after);
78
+ setStep(ps_scan_step_1.PsScanStep.Crop);
79
+ setAnnouncement(edgeMessage(added[0].isDetected));
80
+ }
81
+ else
82
+ setStep(ps_scan_step_1.PsScanStep.Review);
65
83
  }
66
84
  catch {
67
85
  setError(labels.processFailed);
68
86
  }
69
87
  };
70
- const capture = async () => {
71
- const blob = await camera.capture();
72
- if (blob !== null)
73
- await addFiles([blob]);
88
+ const capture = async (isAutomatic) => {
89
+ if (isCapturing || isFull)
90
+ return;
91
+ setIsCapturing(true);
92
+ try {
93
+ const blob = await camera.capture();
94
+ if (blob === null)
95
+ return;
96
+ setFlashKey((current) => current + 1);
97
+ await addFiles([blob], isAutomatic && retakeId === '' ? null : ps_scan_step_1.PsScanStep.Camera);
98
+ }
99
+ finally {
100
+ setIsCapturing(false);
101
+ }
74
102
  };
103
+ const detection = (0, use_ps_scan_live_detection_1.usePsScanLiveDetection)(camera.videoRef, isCameraStep && isReady, isAuto, () => capture(true));
75
104
  const autoDetect = async () => {
76
105
  if (page === undefined)
77
106
  return;
@@ -79,12 +108,32 @@ const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, m
79
108
  setCorners(page.id, prepared.corners);
80
109
  setAnnouncement(edgeMessage(prepared.isDetected));
81
110
  };
111
+ const keep = () => setStep(cropReturn === ps_scan_step_1.PsScanStep.Camera && !isFull ? ps_scan_step_1.PsScanStep.Camera : ps_scan_step_1.PsScanStep.Review);
82
112
  const retake = () => {
83
113
  if (page === undefined)
84
114
  return;
115
+ if (step === ps_scan_step_1.PsScanStep.Review)
116
+ setCropReturn(ps_scan_step_1.PsScanStep.Review);
85
117
  setRetakeId(page.id);
86
118
  setStep(ps_scan_step_1.PsScanStep.Camera);
87
119
  };
120
+ const openCamera = () => {
121
+ setRetakeId('');
122
+ setCropReturn(ps_scan_step_1.PsScanStep.Camera);
123
+ setStep(ps_scan_step_1.PsScanStep.Camera);
124
+ };
125
+ const openReview = () => {
126
+ if (pages.length === 0)
127
+ return;
128
+ setRetakeId('');
129
+ if (page === undefined)
130
+ setActiveId(lastPage.id);
131
+ setStep(ps_scan_step_1.PsScanStep.Review);
132
+ };
133
+ const editCrop = () => {
134
+ setCropReturn(ps_scan_step_1.PsScanStep.Review);
135
+ setStep(ps_scan_step_1.PsScanStep.Crop);
136
+ };
88
137
  const deletePage = () => {
89
138
  if (page === undefined)
90
139
  return;
@@ -94,7 +143,7 @@ const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, m
94
143
  setAnnouncement(labels.pageDeleted);
95
144
  if (rest.length === 0) {
96
145
  setActiveId('');
97
- setStep(ps_scan_step_1.PsScanStep.Camera);
146
+ openCamera();
98
147
  }
99
148
  else
100
149
  setActiveId(rest[Math.min(index, rest.length - 1)].id);
@@ -125,14 +174,13 @@ const PsDocumentScannerSheet = ({ fileName, jpegQuality, labels, maxDimension, m
125
174
  position: to + 1,
126
175
  }));
127
176
  }, [move, pages.length, labels]);
128
- const body = ((0, jsx_runtime_1.jsxs)("div", { className: 'relative h-full min-h-0 bg-surface text-ink', ref: content, children: [(0, jsx_runtime_1.jsx)("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), step === ps_scan_step_1.PsScanStep.Camera ? ((0, jsx_runtime_1.jsx)(ps_scan_camera_view_1.PsScanCameraView, { canGoBack: pages.length > 0, canTorch: camera.canTorch, isTorchOn: camera.isTorchOn, labels: labels, onBack: () => {
129
- setRetakeId('');
130
- setStep(ps_scan_step_1.PsScanStep.Review);
131
- }, onCapture: capture, onFiles: addFiles, onToggleTorch: camera.toggleTorch, status: camera.status, videoRef: camera.videoRef })) : null, step === ps_scan_step_1.PsScanStep.Crop && page !== undefined ? ((0, jsx_runtime_1.jsx)(ps_scan_crop_editor_1.PsScanCropEditor, { labels: labels, onApply: () => setStep(ps_scan_step_1.PsScanStep.Review), onAutoDetect: autoDetect, onChange: (corners) => setCorners(page.id, corners), onRetake: retake, page: page })) : null, step === ps_scan_step_1.PsScanStep.Review && page !== undefined ? ((0, jsx_runtime_1.jsx)(ps_scan_review_1.PsScanReview, { canAddPage: pages.length < maxPages, error: error, isBusy: isBusy, labels: labels, onAddPage: () => setStep(ps_scan_step_1.PsScanStep.Camera), onDelete: deletePage, onEditCrop: () => setStep(ps_scan_step_1.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 === ps_scan_step_1.PsScanStep.Camera && error !== '' ? ((0, jsx_runtime_1.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] }));
132
- if (isDesktop)
133
- return ((0, jsx_runtime_1.jsx)(ps_popovers_1.PsModal, { isOpen: true, onClose: onClose, size: 'xl', title: labels.title, children: (0, jsx_runtime_1.jsx)("div", { className: '-m-4 h-[min(70dvh,38rem)] overflow-hidden sm:-m-5', children: body }) }));
177
+ const fromLibrary = (files) => addFiles(files, ps_scan_step_1.PsScanStep.Review);
134
178
  if (typeof document === 'undefined')
135
179
  return null;
136
- return (0, react_dom_1.createPortal)((0, jsx_runtime_1.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: (0, jsx_runtime_1.jsxs)(ps_core_components_1.PsLayer, { zIndex: zIndex, children: [(0, jsx_runtime_1.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: [(0, jsx_runtime_1.jsx)("h2", { className: 'truncate text-base font-semibold', children: labels.title }), (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { label: labels.close, onClick: onClose, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: ps_icons_1.PsIconKey.Close }) })] }), (0, jsx_runtime_1.jsx)("div", { className: 'min-h-0 flex-1', children: body })] }) }), document.body);
180
+ return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { "aria-label": labels.title, "aria-modal": 'true', className: isDesktop
181
+ ? 'fixed inset-0 flex items-center justify-center bg-black/70 p-6 backdrop-blur-sm animate-ps-fade-in'
182
+ : 'fixed inset-0 bg-black animate-ps-fade-in', role: 'dialog', style: { zIndex }, children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsLayer, { zIndex: zIndex, children: (0, jsx_runtime_1.jsxs)("div", { className: isDesktop
183
+ ? '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'
184
+ : 'relative h-full overflow-hidden bg-black text-white', ref: content, children: [(0, jsx_runtime_1.jsx)("p", { "aria-live": 'polite', className: 'sr-only', role: 'status', children: announcement }), isCameraStep && showsCamera ? ((0, jsx_runtime_1.jsx)(ps_scan_camera_view_1.PsScanCameraView, { canTorch: camera.canTorch, detection: detection, flashKey: flashKey, isAuto: isAuto, isBusy: isCapturing, isFull: isFull, isMultiple: maxPages > 1, isReady: isReady, isTorchOn: camera.isTorchOn, labels: labels, lastPreview: lastPage === undefined ? undefined : previews[lastPage.id], onCapture: () => capture(false), onClose: onClose, onDone: openReview, onFiles: fromLibrary, onToggleAuto: () => setIsAuto((current) => !current), onToggleTorch: camera.toggleTorch, pageCount: pages.length, videoRef: camera.videoRef, videoTarget: camera.attachVideo })) : null, isCameraStep && !showsCamera ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ps_scan_permission_view_1.PsScanPermissionView, { isMultiple: maxPages > 1, labels: labels, onAllow: camera.allow, onFiles: fromLibrary, status: camera.status }), (0, jsx_runtime_1.jsx)("div", { className: 'absolute left-4 top-[max(1rem,env(safe-area-inset-top))] flex gap-2', children: (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { iconKey: ps_icons_1.PsIconKey.Close, label: labels.close, onClick: onClose }) }), pages.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: 'absolute right-4 top-[max(1rem,env(safe-area-inset-top))]', children: (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { iconKey: ps_icons_1.PsIconKey.Check, label: ps_scan_template_service_1.PsScanTemplateService.fill(labels.viewPages, { count: pages.length }), onClick: openReview }) })) : null] })) : null, step === ps_scan_step_1.PsScanStep.Crop && page !== undefined ? ((0, jsx_runtime_1.jsx)(ps_scan_crop_editor_1.PsScanCropEditor, { labels: labels, onAutoDetect: autoDetect, onChange: (corners) => setCorners(page.id, corners), onKeep: keep, onRetake: retake, page: page })) : null, step === ps_scan_step_1.PsScanStep.Review && page !== undefined ? ((0, jsx_runtime_1.jsx)(ps_scan_review_1.PsScanReview, { canAddPage: !isFull, error: error, isBusy: isBusy, labels: labels, onAddPage: openCamera, onClose: onClose, onDelete: deletePage, onEditCrop: editCrop, 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, isCameraStep && error !== '' ? ((0, jsx_runtime_1.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);
137
185
  };
138
186
  exports.PsDocumentScannerSheet = PsDocumentScannerSheet;
@@ -1,2 +1,2 @@
1
1
  import type { PsDocumentScannerProps } from '../models/ps-document-scanner-props';
2
- export declare const PsDocumentScanner: ({ fileName, isOpen, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: PsDocumentScannerProps) => import("react").JSX.Element | null;
2
+ export declare const PsDocumentScanner: ({ fileName, isAutoCapture, isOpen, jpegQuality, labels, maxDimension, maxPages, onClose, onComplete, outputFormat, }: PsDocumentScannerProps) => import("react").JSX.Element | null;
@@ -8,10 +8,10 @@ const ps_document_scanner_labels_1 = require("../constants/ps-document-scanner-l
8
8
  const ps_scan_config_1 = require("../constants/ps-scan-config");
9
9
  const ps_scan_output_format_1 = require("../constants/ps-scan-output-format");
10
10
  const ps_document_scanner_loader_1 = require("./ps-document-scanner-loader");
11
- const PsDocumentScanner = ({ fileName = ps_scan_config_1.PsScanConfig.defaultFileName, isOpen, jpegQuality = ps_scan_config_1.PsScanConfig.defaultJpegQuality, labels, maxDimension = ps_scan_config_1.PsScanConfig.defaultMaxDimension, maxPages = ps_scan_config_1.PsScanConfig.defaultMaxPages, onClose, onComplete, outputFormat = ps_scan_output_format_1.PsScanOutputFormat.Pdf, }) => {
11
+ const PsDocumentScanner = ({ fileName = ps_scan_config_1.PsScanConfig.defaultFileName, isAutoCapture = false, isOpen, jpegQuality = ps_scan_config_1.PsScanConfig.defaultJpegQuality, labels, maxDimension = ps_scan_config_1.PsScanConfig.defaultMaxDimension, maxPages = ps_scan_config_1.PsScanConfig.defaultMaxPages, onClose, onComplete, outputFormat = ps_scan_output_format_1.PsScanOutputFormat.Pdf, }) => {
12
12
  const isHydrated = (0, ps_core_components_1.usePsHydrated)();
13
13
  if (!isOpen || !isHydrated)
14
14
  return null;
15
- return ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: null, children: (0, jsx_runtime_1.jsx)(ps_document_scanner_loader_1.PsDocumentScannerLoader, { fileName: fileName, jpegQuality: jpegQuality, labels: { ...ps_document_scanner_labels_1.PsDocumentScannerLabels, ...labels }, maxDimension: maxDimension, maxPages: maxPages, onClose: onClose, onComplete: onComplete, outputFormat: outputFormat }) }));
15
+ return ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: null, children: (0, jsx_runtime_1.jsx)(ps_document_scanner_loader_1.PsDocumentScannerLoader, { fileName: fileName, isAutoCapture: isAutoCapture, jpegQuality: jpegQuality, labels: { ...ps_document_scanner_labels_1.PsDocumentScannerLabels, ...labels }, maxDimension: maxDimension, maxPages: maxPages, onClose: onClose, onComplete: onComplete, outputFormat: outputFormat }) }));
16
16
  };
17
17
  exports.PsDocumentScanner = PsDocumentScanner;
@@ -0,0 +1,10 @@
1
+ export interface PsFilePickerActionsProps {
2
+ chooseLabel: string;
3
+ className?: string;
4
+ disabled: boolean;
5
+ isTouch: boolean;
6
+ onChoose: () => void;
7
+ onScan: () => void;
8
+ scanLabel?: string;
9
+ }
10
+ export declare const PsFilePickerActions: ({ chooseLabel, className, disabled, isTouch, onChoose, onScan, scanLabel, }: PsFilePickerActionsProps) => import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsFilePickerActions = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_core_components_1 = require("@pallasoft/ps-core-components");
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_icons_1 = require("@pallasoft/ps-icons");
8
+ const PsFilePickerActions = ({ chooseLabel, className, disabled, isTouch, onChoose, onScan, scanLabel, }) => {
9
+ const hasScan = scanLabel !== undefined;
10
+ const choose = ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { className: isTouch ? 'w-full' : '', disabled: disabled, leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Upload }), onClick: (event) => {
11
+ event.stopPropagation();
12
+ onChoose();
13
+ }, size: isTouch ? 'large' : 'default', variant: hasScan && isTouch ? 'surface' : hasScan || isTouch ? 'primary' : 'surface', children: chooseLabel }));
14
+ return ((0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge(isTouch
15
+ ? 'w-full max-w-xs flex-col gap-2'
16
+ : 'flex-row flex-wrap items-center justify-center gap-2', className), children: hasScan ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [isTouch ? null : choose, (0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { className: isTouch ? 'w-full' : '', disabled: disabled, leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Camera }), onClick: (event) => {
17
+ event.stopPropagation();
18
+ onScan();
19
+ }, size: isTouch ? 'large' : 'default', variant: isTouch ? 'primary' : 'surface', children: scanLabel }), isTouch ? choose : null] })) : (choose) }));
20
+ };
21
+ exports.PsFilePickerActions = PsFilePickerActions;
@@ -1,5 +1,8 @@
1
1
  import { PsFilePickerLabels } from '../constants/ps-file-picker-labels';
2
+ import { PsUploadLabels } from '../constants/ps-upload-labels';
3
+ import type { PsFilePickerScan } from '../models/ps-file-picker-scan';
2
4
  import type { PsLabelSet } from '../models/ps-label-set';
5
+ import type { PsUploadQueueState } from '../models/ps-upload-queue-state';
3
6
  export interface PsFilePickerProps {
4
7
  accept: string[];
5
8
  acceptLabel: string;
@@ -11,9 +14,12 @@ export interface PsFilePickerProps {
11
14
  maxSizeBytes?: number;
12
15
  maxSizeLabel?: string;
13
16
  multiple?: boolean;
17
+ queue?: PsUploadQueueState;
18
+ scan?: PsFilePickerScan;
14
19
  touchLabel?: string;
20
+ uploadLabels?: Partial<PsLabelSet<typeof PsUploadLabels>>;
15
21
  onError?: (message: string) => void;
16
22
  onFileLoad?: (file: File) => void;
17
23
  onFilesSelect?: (files: File[]) => void;
18
24
  }
19
- export declare const PsFilePicker: ({ accept, acceptLabel, className, disabled, dropLabel, isLoading, labels, maxSizeBytes, maxSizeLabel, multiple, onError, onFileLoad, onFilesSelect, touchLabel, }: PsFilePickerProps) => import("react").JSX.Element;
25
+ export declare const PsFilePicker: ({ accept, acceptLabel, className, disabled, dropLabel, isLoading, labels, maxSizeBytes, maxSizeLabel, multiple, onError, onFileLoad, onFilesSelect, queue, scan, touchLabel, uploadLabels, }: PsFilePickerProps) => import("react").JSX.Element;
@@ -6,13 +6,24 @@ const react_1 = require("react");
6
6
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
7
  const ps_icons_1 = require("@pallasoft/ps-icons");
8
8
  const ps_file_picker_labels_1 = require("../constants/ps-file-picker-labels");
9
+ const ps_scan_config_1 = require("../constants/ps-scan-config");
10
+ const ps_upload_labels_1 = require("../constants/ps-upload-labels");
9
11
  const ps_file_selection_service_1 = require("../services/ps-file-selection-service");
10
- const PsFilePicker = ({ accept, acceptLabel, className, disabled = false, dropLabel, isLoading = false, labels, maxSizeBytes, maxSizeLabel, multiple = false, onError, onFileLoad, onFilesSelect, touchLabel, }) => {
12
+ const ps_document_scanner_1 = require("./ps-document-scanner");
13
+ const ps_file_picker_actions_1 = require("./ps-file-picker-actions");
14
+ const ps_upload_row_1 = require("./ps-upload-row");
15
+ const PsFilePicker = ({ accept, acceptLabel, className, disabled = false, dropLabel, isLoading = false, labels, maxSizeBytes, maxSizeLabel, multiple = false, onError, onFileLoad, onFilesSelect, queue, scan, touchLabel, uploadLabels, }) => {
11
16
  const [isDragging, setIsDragging] = (0, react_1.useState)(false);
17
+ const [scanName, setScanName] = (0, react_1.useState)(null);
18
+ const input = (0, react_1.useRef)(null);
12
19
  const acceptAttr = (0, react_1.useMemo)(() => accept.join(','), [accept]);
13
20
  const text = { ...ps_file_picker_labels_1.PsFilePickerLabels, ...labels };
21
+ const rowLabels = { ...ps_upload_labels_1.PsUploadLabels, ...uploadLabels };
22
+ const details = [acceptLabel, maxSizeLabel].filter((value) => value !== undefined && value !== '');
23
+ const chooseLabel = multiple ? text.choose : text.chooseOne;
24
+ const scanLabel = scan === undefined ? undefined : text.scan;
14
25
  if (isLoading)
15
- return ((0, jsx_runtime_1.jsx)("span", { "aria-busy": 'true', className: ps_design_system_1.PallasoftClassNameService.merge('ps-skeleton block min-h-32 w-full rounded-ps-panel', className), role: 'status' }));
26
+ return ((0, jsx_runtime_1.jsx)("span", { "aria-busy": 'true', className: ps_design_system_1.PallasoftClassNameService.merge('ps-skeleton block min-h-48 w-full rounded-ps-control', className), role: 'status' }));
16
27
  const select = (list) => {
17
28
  if (list === null || disabled)
18
29
  return;
@@ -31,6 +42,15 @@ const PsFilePicker = ({ accept, acceptLabel, className, disabled = false, dropLa
31
42
  else
32
43
  onFileLoad?.(accepted[0]);
33
44
  };
45
+ const browse = () => {
46
+ if (!disabled)
47
+ input.current?.click();
48
+ };
49
+ const openScan = () => {
50
+ if (disabled || scan === undefined)
51
+ return;
52
+ setScanName(scan.getFileName === undefined ? ps_scan_config_1.PsScanConfig.defaultFileName : scan.getFileName());
53
+ };
34
54
  const onChange = (event) => {
35
55
  select(event.target.files);
36
56
  event.target.value = '';
@@ -40,9 +60,13 @@ const PsFilePicker = ({ accept, acceptLabel, className, disabled = false, dropLa
40
60
  if (!disabled)
41
61
  setIsDragging(isOver);
42
62
  };
43
- return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.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) => {
44
- onDrag(event, false);
45
- select(event.dataTransfer.files);
46
- }, children: [(0, jsx_runtime_1.jsxs)("div", { className: 'pointer-events-none flex flex-col items-center gap-3 text-center', children: [(0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { className: 'h-8 w-8 text-primary', iconKey: ps_icons_1.PsIconKey.Upload }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-body-2 font-medium text-ink sm:text-body-1', children: isDragging ? (text.dropActive) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: touchLabel === undefined ? '' : '[@media(pointer:coarse)]:hidden', children: dropLabel }), touchLabel === undefined ? null : ((0, jsx_runtime_1.jsx)("span", { className: 'hidden [@media(pointer:coarse)]:inline', children: touchLabel }))] })) }), (0, jsx_runtime_1.jsxs)("p", { className: 'mt-1 text-body-3 text-ink/60 sm:text-body-2', children: [(0, jsx_runtime_1.jsx)("span", { className: '[@media(pointer:coarse)]:hidden', children: text.browse }), (0, jsx_runtime_1.jsx)("span", { className: 'hidden [@media(pointer:coarse)]:inline', children: text.browseTouch })] }), maxSizeLabel ? (0, jsx_runtime_1.jsx)("p", { className: 'mt-3 text-body-3 text-ink/60', children: maxSizeLabel }) : null, (0, jsx_runtime_1.jsxs)("p", { className: 'text-body-3 text-ink/60', children: [text.formats, " ", acceptLabel] })] })] }), (0, jsx_runtime_1.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' })] }));
63
+ return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('flex w-full flex-col gap-3', className), children: [(0, jsx_runtime_1.jsxs)("div", { "aria-disabled": disabled || undefined, className: ps_design_system_1.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
64
+ ? 'border-primary bg-primary/5'
65
+ : '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) => {
66
+ onDrag(event, false);
67
+ select(event.dataTransfer.files);
68
+ }, children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": 'true', className: ps_design_system_1.PallasoftClassNameService.merge('flex size-12 items-center justify-center rounded-full transition-colors duration-200', isDragging
69
+ ? 'bg-primary text-[color:var(--ps-color-primary-contrast)]'
70
+ : 'bg-primary-soft text-primary'), children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { className: 'size-6', iconKey: ps_icons_1.PsIconKey.Upload }) }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-1', children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-base font-semibold text-ink', children: isDragging ? (text.dropActive) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: touchLabel === undefined ? '' : '[@media(pointer:coarse)]:hidden', children: dropLabel }), touchLabel === undefined ? null : ((0, jsx_runtime_1.jsx)("span", { className: 'hidden [@media(pointer:coarse)]:inline', children: touchLabel }))] })) }), (0, jsx_runtime_1.jsx)("p", { className: 'text-sm text-ink/60 [@media(pointer:coarse)]:hidden', children: multiple ? text.dragHint : text.dragHintOne })] }), (0, jsx_runtime_1.jsx)(ps_file_picker_actions_1.PsFilePickerActions, { chooseLabel: chooseLabel, className: 'flex [@media(pointer:coarse)]:hidden', disabled: disabled, isTouch: false, onChoose: browse, onScan: openScan, scanLabel: scanLabel }), (0, jsx_runtime_1.jsx)(ps_file_picker_actions_1.PsFilePickerActions, { chooseLabel: chooseLabel, className: 'hidden [@media(pointer:coarse)]:flex', disabled: disabled, isTouch: true, onChoose: browse, onScan: openScan, scanLabel: scanLabel }), details.length > 0 ? (0, jsx_runtime_1.jsx)("p", { className: 'text-xs text-ink/50', children: details.join(' · ') }) : null, (0, jsx_runtime_1.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 : ((0, jsx_runtime_1.jsx)("ul", { "aria-label": rowLabels.title, className: 'flex flex-col gap-2', children: queue.items.map((item) => ((0, jsx_runtime_1.jsx)(ps_upload_row_1.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 : ((0, jsx_runtime_1.jsx)(ps_document_scanner_1.PsDocumentScanner, { ...scan, fileName: scanName === null ? ps_scan_config_1.PsScanConfig.defaultFileName : scanName, isOpen: scanName !== null, onClose: () => setScanName(null), onComplete: select }))] }));
47
71
  };
48
72
  exports.PsFilePicker = PsFilePicker;
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface PsScanButtonProps {
3
+ children: ReactNode;
4
+ className?: string;
5
+ disabled?: boolean;
6
+ isLarge?: boolean;
7
+ isLoading?: boolean;
8
+ isPrimary?: boolean;
9
+ leadingIcon?: ReactNode;
10
+ loadingLabel?: string;
11
+ onClick: () => void;
12
+ }
13
+ export declare const PsScanButton: ({ children, className, disabled, isLarge, isLoading, isPrimary, leadingIcon, loadingLabel, onClick, }: PsScanButtonProps) => import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsScanButton = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_core_components_1 = require("@pallasoft/ps-core-components");
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_scan_chrome_classes_1 = require("../constants/ps-scan-chrome-classes");
8
+ const PsScanButton = ({ children, className, disabled = false, isLarge = false, isLoading = false, isPrimary = false, leadingIcon, loadingLabel, onClick, }) => ((0, jsx_runtime_1.jsxs)("button", { "aria-busy": isLoading || undefined, className: ps_design_system_1.PallasoftClassNameService.merge(ps_scan_chrome_classes_1.PsScanChromeClasses.button, isLarge ? ps_scan_chrome_classes_1.PsScanChromeClasses.buttonLarge : '', isPrimary ? ps_scan_chrome_classes_1.PsScanChromeClasses.buttonPrimary : ps_scan_chrome_classes_1.PsScanChromeClasses.buttonQuiet, 'relative', className), disabled: disabled || isLoading, onClick: onClick, type: 'button', children: [isLoading ? ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsSpinner, { className: 'absolute inset-0 m-auto', color: 'current', label: loadingLabel === undefined ? '' : loadingLabel, size: 'small' })) : null, (0, jsx_runtime_1.jsxs)("span", { "aria-hidden": isLoading || undefined, className: `inline-flex min-w-0 items-center gap-2 ${isLoading ? 'invisible' : ''}`, children: [leadingIcon === undefined ? null : (0, jsx_runtime_1.jsx)("span", { className: 'shrink-0', children: leadingIcon }), (0, jsx_runtime_1.jsx)("span", { className: 'truncate', children: children })] })] }));
9
+ exports.PsScanButton = PsScanButton;
@@ -1,17 +1,27 @@
1
- import { type RefObject } from 'react';
1
+ import { type RefCallback, type RefObject } from 'react';
2
2
  import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
3
- import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
4
3
  import type { PsLabelSet } from '../models/ps-label-set';
4
+ import type { PsScanLiveDetection } from '../models/ps-scan-live-detection';
5
5
  export interface PsScanCameraViewProps {
6
- canGoBack: boolean;
7
6
  canTorch: boolean;
7
+ detection: PsScanLiveDetection;
8
+ flashKey: number;
9
+ isAuto: boolean;
10
+ isBusy: boolean;
11
+ isFull: boolean;
12
+ isMultiple: boolean;
13
+ isReady: boolean;
8
14
  isTorchOn: boolean;
9
15
  labels: PsLabelSet<typeof PsDocumentScannerLabels>;
10
- onBack: () => void;
16
+ lastPreview: string | undefined;
11
17
  onCapture: () => void;
18
+ onClose: () => void;
19
+ onDone: () => void;
12
20
  onFiles: (files: File[]) => void;
21
+ onToggleAuto: () => void;
13
22
  onToggleTorch: () => void;
14
- status: PsScanCameraStatus;
15
- videoRef: RefObject<HTMLVideoElement>;
23
+ pageCount: number;
24
+ videoRef: RefObject<HTMLVideoElement | null>;
25
+ videoTarget: RefCallback<HTMLVideoElement>;
16
26
  }
17
- export declare const PsScanCameraView: ({ canGoBack, canTorch, isTorchOn, labels, onBack, onCapture, onFiles, onToggleTorch, status, videoRef, }: PsScanCameraViewProps) => import("react").JSX.Element;
27
+ export declare const PsScanCameraView: ({ canTorch, detection, flashKey, isAuto, isBusy, isFull, isMultiple, isReady, isTorchOn, labels, lastPreview, onCapture, onClose, onDone, onFiles, onToggleAuto, onToggleTorch, pageCount, videoRef, videoTarget, }: PsScanCameraViewProps) => import("react").JSX.Element;
@@ -4,24 +4,31 @@ exports.PsScanCameraView = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const ps_core_components_1 = require("@pallasoft/ps-core-components");
7
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
8
  const ps_icons_1 = require("@pallasoft/ps-icons");
8
- const ps_scan_camera_status_1 = require("../constants/ps-scan-camera-status");
9
+ const ps_scan_chrome_classes_1 = require("../constants/ps-scan-chrome-classes");
10
+ const ps_scan_template_service_1 = require("../services/ps-scan-template-service");
11
+ const ps_scan_button_1 = require("./ps-scan-button");
9
12
  const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
10
- const PsScanCameraView = ({ canGoBack, canTorch, isTorchOn, labels, onBack, onCapture, onFiles, onToggleTorch, status, videoRef, }) => {
11
- const captureInput = (0, react_1.useRef)(null);
12
- const libraryInput = (0, react_1.useRef)(null);
13
- const isReady = status === ps_scan_camera_status_1.PsScanCameraStatus.Ready;
14
- const message = status === ps_scan_camera_status_1.PsScanCameraStatus.Denied
15
- ? labels.cameraDenied
16
- : status === ps_scan_camera_status_1.PsScanCameraStatus.Unavailable
17
- ? labels.cameraUnavailable
18
- : '';
19
- const onChange = (event) => {
20
- const files = Array.from(event.target.files === null ? [] : event.target.files);
21
- event.target.value = '';
22
- if (files.length > 0)
23
- onFiles(files);
13
+ const ps_scan_page_stack_1 = require("./ps-scan-page-stack");
14
+ const ps_scan_photo_button_1 = require("./ps-scan-photo-button");
15
+ const ps_scan_shutter_1 = require("./ps-scan-shutter");
16
+ const ps_scan_viewfinder_overlay_1 = require("./ps-scan-viewfinder-overlay");
17
+ const PsScanCameraView = ({ canTorch, detection, flashKey, isAuto, isBusy, isFull, isMultiple, isReady, isTorchOn, labels, lastPreview, onCapture, onClose, onDone, onFiles, onToggleAuto, onToggleTorch, pageCount, videoRef, videoTarget, }) => {
18
+ const [areaRef, area] = (0, ps_core_components_1.usePsElementSize)();
19
+ const [video, setVideo] = (0, react_1.useState)({ height: 0, width: 0 });
20
+ const hint = isFull
21
+ ? labels.maxPages
22
+ : detection.quad === null
23
+ ? labels.hintFind
24
+ : isAuto
25
+ ? labels.hintHold
26
+ : labels.hintFound;
27
+ const measure = () => {
28
+ const element = videoRef.current;
29
+ if (element !== null)
30
+ setVideo({ height: element.videoHeight, width: element.videoWidth });
24
31
  };
25
- return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex h-full min-h-0 flex-col bg-slate-950 text-white', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'relative min-h-0 flex-1', children: [(0, jsx_runtime_1.jsx)("video", { "aria-label": labels.title, autoPlay: true, className: `size-full object-contain ${isReady ? '' : 'invisible'}`, muted: true, playsInline: true, ref: videoRef }), status === ps_scan_camera_status_1.PsScanCameraStatus.Starting ? ((0, jsx_runtime_1.jsxs)("div", { className: 'absolute inset-0 flex flex-col items-center justify-center gap-3 text-sm text-white/80', role: 'status', children: [(0, jsx_runtime_1.jsx)(ps_core_components_1.PsSpinner, { color: 'current', label: labels.cameraStarting, size: 'small' }), (0, jsx_runtime_1.jsx)("span", { children: labels.cameraStarting })] })) : null, message === '' ? null : ((0, jsx_runtime_1.jsx)("div", { className: 'absolute inset-0 flex items-center justify-center p-6 text-center text-sm text-white/85', role: 'status', children: (0, jsx_runtime_1.jsx)("p", { className: 'max-w-sm', children: message }) }))] }), (0, jsx_runtime_1.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 ? ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: onBack, variant: 'surface', children: labels.backToPages })) : null, (0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { leadingIcon: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: ps_icons_1.PsIconKey.Image }), onClick: () => libraryInput.current?.click(), variant: 'surface', children: labels.choosePhotos }), isReady ? ((0, jsx_runtime_1.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: (0, jsx_runtime_1.jsx)("span", { className: 'size-12 rounded-full bg-white' }) })) : ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: () => captureInput.current?.click(), variant: 'primary', children: labels.takePhoto })), canTorch && isReady ? ((0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { isPressed: isTorchOn, label: isTorchOn ? labels.torchOff : labels.torchOn, onClick: onToggleTorch, children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'size-5', iconKey: ps_icons_1.PsIconKey.Spark }) })) : null] }), (0, jsx_runtime_1.jsx)("input", { accept: 'image/*', capture: 'environment', className: 'sr-only', onChange: onChange, ref: captureInput, tabIndex: -1, type: 'file' }), (0, jsx_runtime_1.jsx)("input", { accept: 'image/*', className: 'sr-only', multiple: true, onChange: onChange, ref: libraryInput, tabIndex: -1, type: 'file' })] }));
32
+ return ((0, jsx_runtime_1.jsxs)("div", { className: 'relative h-full min-h-0 overflow-hidden bg-black text-white', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'absolute inset-0', ref: areaRef, children: [(0, jsx_runtime_1.jsx)("video", { "aria-hidden": 'true', autoPlay: true, className: ps_design_system_1.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 ? ((0, jsx_runtime_1.jsx)(ps_scan_viewfinder_overlay_1.PsScanViewfinderOverlay, { area: area, quad: detection.quad, video: video })) : null] }), isReady ? null : ((0, jsx_runtime_1.jsxs)("div", { className: 'absolute inset-0 flex flex-col items-center justify-center gap-3 text-sm text-white/75', role: 'status', children: [(0, jsx_runtime_1.jsx)(ps_core_components_1.PsSpinner, { color: 'current', label: labels.cameraStarting, size: 'small' }), (0, jsx_runtime_1.jsx)("span", { children: labels.cameraStarting })] })), flashKey > 0 ? ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: 'pointer-events-none absolute inset-0 bg-white animate-ps-fade-out' }, flashKey)) : null, (0, jsx_runtime_1.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: [(0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { iconKey: ps_icons_1.PsIconKey.Close, label: labels.close, onClick: onClose }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-2', children: [canTorch && isReady ? ((0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { iconKey: ps_icons_1.PsIconKey.Flash, isPressed: isTorchOn, label: isTorchOn ? labels.torchOff : labels.torchOn, onClick: onToggleTorch })) : null, (0, jsx_runtime_1.jsx)("button", { "aria-label": labels.autoCapture, "aria-pressed": isAuto, className: ps_design_system_1.PallasoftClassNameService.merge(ps_scan_chrome_classes_1.PsScanChromeClasses.button, 'border-transparent', isAuto ? ps_scan_chrome_classes_1.PsScanChromeClasses.iconOn : ps_scan_chrome_classes_1.PsScanChromeClasses.iconOff), onClick: onToggleAuto, title: labels.autoCapture, type: 'button', children: labels.auto })] })] }), isReady ? ((0, jsx_runtime_1.jsx)("div", { className: 'absolute inset-x-0 top-[calc(max(1rem,env(safe-area-inset-top))+3.75rem)] flex justify-center px-4', children: (0, jsx_runtime_1.jsx)("p", { "aria-live": 'polite', className: ps_scan_chrome_classes_1.PsScanChromeClasses.pill, children: hint }) })) : null, (0, jsx_runtime_1.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: [(0, jsx_runtime_1.jsx)("div", { className: 'flex justify-start', children: pageCount > 0 ? ((0, jsx_runtime_1.jsx)(ps_scan_page_stack_1.PsScanPageStack, { count: pageCount, label: ps_scan_template_service_1.PsScanTemplateService.fill(labels.viewPages, { count: pageCount }), onOpen: onDone, preview: lastPreview })) : ((0, jsx_runtime_1.jsx)(ps_scan_photo_button_1.PsScanPhotoButton, { isIconOnly: true, isMultiple: isMultiple, label: labels.choosePhotos, onFiles: onFiles })) }), (0, jsx_runtime_1.jsx)(ps_scan_shutter_1.PsScanShutter, { disabled: !isReady || isFull || isBusy, label: labels.capture, onCapture: onCapture, progress: detection.progress }), (0, jsx_runtime_1.jsx)("div", { className: 'flex justify-end', children: pageCount > 0 ? ((0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { isPrimary: true, onClick: onDone, children: labels.done })) : null })] })] }));
26
33
  };
27
34
  exports.PsScanCameraView = PsScanCameraView;
@@ -4,10 +4,10 @@ import type { PsScanPage } from '../models/ps-scan-page';
4
4
  import type { PsScanQuad } from '../models/ps-scan-quad';
5
5
  export interface PsScanCropEditorProps {
6
6
  labels: PsLabelSet<typeof PsDocumentScannerLabels>;
7
- onApply: () => void;
8
7
  onAutoDetect: () => void;
9
8
  onChange: (corners: PsScanQuad) => void;
9
+ onKeep: () => void;
10
10
  onRetake: () => void;
11
11
  page: PsScanPage;
12
12
  }
13
- export declare const PsScanCropEditor: ({ labels, onApply, onAutoDetect, onChange, onRetake, page, }: PsScanCropEditorProps) => import("react").JSX.Element;
13
+ export declare const PsScanCropEditor: ({ labels, onAutoDetect, onChange, onKeep, onRetake, page, }: PsScanCropEditorProps) => import("react").JSX.Element;
@@ -4,12 +4,18 @@ exports.PsScanCropEditor = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const ps_core_components_1 = require("@pallasoft/ps-core-components");
7
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
8
+ const ps_icons_1 = require("@pallasoft/ps-icons");
7
9
  const ps_scan_config_1 = require("../constants/ps-scan-config");
8
10
  const ps_scan_corner_order_1 = require("../constants/ps-scan-corner-order");
9
11
  const ps_scan_geometry_service_1 = require("../services/ps-scan-geometry-service");
10
- const PsScanCropEditor = ({ labels, onApply, onAutoDetect, onChange, onRetake, page, }) => {
12
+ const ps_scan_button_1 = require("./ps-scan-button");
13
+ const ps_scan_icon_button_1 = require("./ps-scan-icon-button");
14
+ const ps_scan_loupe_1 = require("./ps-scan-loupe");
15
+ const PsScanCropEditor = ({ labels, onAutoDetect, onChange, onKeep, onRetake, page, }) => {
11
16
  const [areaRef, area] = (0, ps_core_components_1.usePsElementSize)();
12
17
  const stageRef = (0, react_1.useRef)(null);
18
+ const [activeIndex, setActiveIndex] = (0, react_1.useState)(null);
13
19
  const hintId = 'ps-scan-crop-hint';
14
20
  const size = ps_scan_geometry_service_1.PsScanGeometryService.fit(area, { height: page.height, width: page.width });
15
21
  const cornerLabels = [
@@ -18,6 +24,9 @@ const PsScanCropEditor = ({ labels, onApply, onAutoDetect, onChange, onRetake, p
18
24
  labels.cornerBottomRight,
19
25
  labels.cornerBottomLeft,
20
26
  ];
27
+ const outline = page.corners
28
+ .map((point) => `${point.x * size.width},${point.y * size.height}`)
29
+ .join(' L');
21
30
  const move = (index, event) => {
22
31
  const stage = stageRef.current;
23
32
  if (stage === null || !event.currentTarget.hasPointerCapture(event.pointerId))
@@ -31,6 +40,10 @@ const PsScanCropEditor = ({ labels, onApply, onAutoDetect, onChange, onRetake, p
31
40
  if (ps_scan_geometry_service_1.PsScanGeometryService.isUsable(next))
32
41
  onChange(next);
33
42
  };
43
+ const release = (event) => {
44
+ event.currentTarget.releasePointerCapture(event.pointerId);
45
+ setActiveIndex(null);
46
+ };
34
47
  const nudge = (index, event) => {
35
48
  const step = event.shiftKey ? ps_scan_config_1.PsScanConfig.cornerNudgeLarge : ps_scan_config_1.PsScanConfig.cornerNudge;
36
49
  const deltas = {
@@ -45,9 +58,12 @@ const PsScanCropEditor = ({ labels, onApply, onAutoDetect, onChange, onRetake, p
45
58
  event.preventDefault();
46
59
  onChange(ps_scan_geometry_service_1.PsScanGeometryService.nudge(page.corners, index, delta[0], delta[1]));
47
60
  };
48
- return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex h-full min-h-0 flex-col bg-slate-950 text-white', children: [(0, jsx_runtime_1.jsx)("p", { className: 'shrink-0 px-4 pt-3 text-center text-sm text-white/80', id: hintId, children: labels.cropHint }), (0, jsx_runtime_1.jsx)("div", { className: 'flex min-h-0 flex-1 items-center justify-center p-6', ref: areaRef, children: (0, jsx_runtime_1.jsxs)("div", { className: 'relative touch-none select-none', ref: stageRef, style: { height: size.height, width: size.width }, children: [(0, jsx_runtime_1.jsx)("img", { alt: '', className: 'absolute inset-0 size-full', draggable: false, src: page.sourceUrl }), (0, jsx_runtime_1.jsx)("svg", { "aria-hidden": 'true', className: 'pointer-events-none absolute inset-0 size-full', preserveAspectRatio: 'none', viewBox: '0 0 1 1', children: (0, jsx_runtime_1.jsx)("polygon", { fill: 'rgba(255,255,255,0.12)', points: page.corners.map((point) => `${point.x},${point.y}`).join(' '), stroke: 'white', strokeWidth: 2, vectorEffect: 'non-scaling-stroke' }) }), ps_scan_corner_order_1.PsScanCornerOrder.map((key, index) => ((0, jsx_runtime_1.jsx)("button", { "aria-describedby": hintId, "aria-label": cornerLabels[index], className: 'ps-focus-ring absolute flex size-11 -translate-x-1/2 -translate-y-1/2 cursor-grab touch-none items-center justify-center rounded-full active:cursor-grabbing', onKeyDown: (event) => nudge(index, event), onPointerCancel: (event) => event.currentTarget.releasePointerCapture(event.pointerId), onPointerDown: (event) => event.currentTarget.setPointerCapture(event.pointerId), onPointerMove: (event) => move(index, event), onPointerUp: (event) => event.currentTarget.releasePointerCapture(event.pointerId), style: {
61
+ return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex h-full min-h-0 flex-col bg-black text-white animate-ps-fade-in', children: [(0, jsx_runtime_1.jsxs)("header", { className: 'grid shrink-0 grid-cols-[2.75rem_1fr_2.75rem] items-center gap-3 px-4 pb-1 pt-[max(1rem,env(safe-area-inset-top))]', children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": 'true' }), (0, jsx_runtime_1.jsx)("h2", { className: 'truncate text-center text-base font-semibold', children: labels.cropTitle }), (0, jsx_runtime_1.jsx)(ps_scan_icon_button_1.PsScanIconButton, { iconKey: ps_icons_1.PsIconKey.Spark, label: labels.autoDetect, onClick: onAutoDetect })] }), (0, jsx_runtime_1.jsx)("p", { className: 'shrink-0 px-6 text-center text-sm text-white/65', id: hintId, children: labels.cropHint }), (0, jsx_runtime_1.jsx)("div", { className: 'flex min-h-0 flex-1 items-center justify-center px-8 py-6', ref: areaRef, children: (0, jsx_runtime_1.jsxs)("div", { className: 'relative touch-none select-none', ref: stageRef, style: { height: size.height, width: size.width }, children: [(0, jsx_runtime_1.jsx)("img", { alt: '', className: 'absolute inset-0 size-full rounded-sm', draggable: false, src: page.sourceUrl }), (0, jsx_runtime_1.jsxs)("svg", { "aria-hidden": 'true', className: 'pointer-events-none absolute inset-0 size-full overflow-visible', viewBox: `0 0 ${Math.max(1, size.width)} ${Math.max(1, size.height)}`, children: [(0, jsx_runtime_1.jsx)("path", { className: 'fill-black/55', d: `M0 0H${size.width}V${size.height}H0Z M${outline}Z`, fillRule: 'evenodd' }), (0, jsx_runtime_1.jsx)("path", { className: 'fill-none stroke-primary', d: `M${outline}Z`, strokeLinejoin: 'round', strokeWidth: '2.5' })] }), ps_scan_corner_order_1.PsScanCornerOrder.map((key, index) => ((0, jsx_runtime_1.jsx)("button", { "aria-describedby": hintId, "aria-label": cornerLabels[index], className: 'ps-focus-ring absolute flex size-12 -translate-x-1/2 -translate-y-1/2 cursor-grab touch-none items-center justify-center rounded-full active:cursor-grabbing', onKeyDown: (event) => nudge(index, event), onPointerCancel: release, onPointerDown: (event) => {
62
+ event.currentTarget.setPointerCapture(event.pointerId);
63
+ setActiveIndex(index);
64
+ }, onPointerMove: (event) => move(index, event), onPointerUp: release, style: {
49
65
  left: `${page.corners[index].x * 100}%`,
50
66
  top: `${page.corners[index].y * 100}%`,
51
- }, type: 'button', children: (0, jsx_runtime_1.jsx)("span", { className: 'size-5 rounded-full border-2 border-white bg-primary shadow' }) }, key)))] }) }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex shrink-0 flex-wrap items-center justify-center gap-3 px-4 pt-2 pb-[max(0.75rem,env(safe-area-inset-bottom))]', children: [(0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: onRetake, variant: 'surface', children: labels.retake }), (0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: onAutoDetect, variant: 'surface', children: labels.autoDetect }), (0, jsx_runtime_1.jsx)(ps_core_components_1.PsButton, { onClick: onApply, variant: 'primary', children: labels.cropApply })] })] }));
67
+ }, type: 'button', children: (0, jsx_runtime_1.jsx)("span", { className: ps_design_system_1.PallasoftClassNameService.merge('size-6 rounded-full border-[3px] border-white bg-primary shadow-lg transition-transform duration-150', activeIndex === index ? 'scale-125' : '') }) }, key))), activeIndex === null ? null : ((0, jsx_runtime_1.jsx)(ps_scan_loupe_1.PsScanLoupe, { corners: page.corners, point: page.corners[activeIndex], source: page.sourceUrl, stage: size }))] }) }), (0, jsx_runtime_1.jsxs)("footer", { className: 'mx-auto grid w-full max-w-md shrink-0 grid-cols-2 gap-3 px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-2', children: [(0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { isLarge: true, onClick: onRetake, children: labels.retake }), (0, jsx_runtime_1.jsx)(ps_scan_button_1.PsScanButton, { isLarge: true, isPrimary: true, onClick: onKeep, children: labels.keepScan })] })] }));
52
68
  };
53
69
  exports.PsScanCropEditor = PsScanCropEditor;
@@ -0,0 +1,6 @@
1
+ import { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
2
+ import type { PsLabelSet } from '../models/ps-label-set';
3
+ export interface PsScanDeniedStepsProps {
4
+ labels: PsLabelSet<typeof PsDocumentScannerLabels>;
5
+ }
6
+ export declare const PsScanDeniedSteps: ({ labels }: PsScanDeniedStepsProps) => import("react").JSX.Element;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsScanDeniedSteps = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_scan_platform_1 = require("../constants/ps-scan-platform");
8
+ const ps_scan_platform_service_1 = require("../services/ps-scan-platform-service");
9
+ const PsScanDeniedSteps = ({ labels }) => {
10
+ const [platform, setPlatform] = (0, react_1.useState)(() => ps_scan_platform_service_1.PsScanPlatformService.detect());
11
+ const platforms = [
12
+ {
13
+ key: ps_scan_platform_1.PsScanPlatform.Ios,
14
+ label: labels.platformIos,
15
+ steps: [labels.deniedIos1, labels.deniedIos2, labels.deniedIos3],
16
+ },
17
+ {
18
+ key: ps_scan_platform_1.PsScanPlatform.Android,
19
+ label: labels.platformAndroid,
20
+ steps: [labels.deniedAndroid1, labels.deniedAndroid2, labels.deniedAndroid3],
21
+ },
22
+ {
23
+ key: ps_scan_platform_1.PsScanPlatform.Desktop,
24
+ label: labels.platformDesktop,
25
+ steps: [labels.deniedDesktop1, labels.deniedDesktop2, labels.deniedDesktop3],
26
+ },
27
+ ];
28
+ const active = platforms.find((item) => item.key === platform);
29
+ return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex w-full flex-col gap-4 rounded-ps-control border border-white/10 bg-white/5 p-4 text-left', children: [(0, jsx_runtime_1.jsx)("div", { className: 'grid grid-cols-3 gap-1 rounded-ps-control bg-black/40 p-1', role: 'group', children: platforms.map((item) => ((0, jsx_runtime_1.jsx)("button", { "aria-pressed": item.key === platform, className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring ps-control min-w-0 truncate rounded-ps-control px-2 text-xs font-semibold transition', item.key === platform
30
+ ? 'bg-white text-black'
31
+ : 'text-white/70 hover:bg-white/10 hover:text-white'), onClick: () => setPlatform(item.key), type: 'button', children: item.label }, item.key))) }), (0, jsx_runtime_1.jsx)("ol", { className: 'flex flex-col gap-3', children: (active === undefined ? [] : active.steps).map((step, index) => ((0, jsx_runtime_1.jsxs)("li", { className: 'flex items-start gap-3 text-sm leading-5 text-white/85', children: [(0, jsx_runtime_1.jsx)("span", { className: 'flex size-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-bold text-[color:var(--ps-color-primary-contrast)]', children: index + 1 }), (0, jsx_runtime_1.jsx)("span", { className: 'pt-0.5', children: step })] }, step))) })] }));
32
+ };
33
+ exports.PsScanDeniedSteps = PsScanDeniedSteps;