@servicetitan/dte-pdf-editor 1.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 (179) hide show
  1. package/README.md +356 -0
  2. package/dist/components/data-model-field-type-list.d.ts +11 -0
  3. package/dist/components/data-model-field-type-list.d.ts.map +1 -0
  4. package/dist/components/data-model-field-type-list.js +23 -0
  5. package/dist/components/data-model-field-type-list.js.map +1 -0
  6. package/dist/components/e-sign-field-type-list.d.ts +9 -0
  7. package/dist/components/e-sign-field-type-list.d.ts.map +1 -0
  8. package/dist/components/e-sign-field-type-list.js +12 -0
  9. package/dist/components/e-sign-field-type-list.js.map +1 -0
  10. package/dist/components/field-config-panel-overlay.d.ts +13 -0
  11. package/dist/components/field-config-panel-overlay.d.ts.map +1 -0
  12. package/dist/components/field-config-panel-overlay.js +8 -0
  13. package/dist/components/field-config-panel-overlay.js.map +1 -0
  14. package/dist/components/field-config-panel.d.ts +12 -0
  15. package/dist/components/field-config-panel.d.ts.map +1 -0
  16. package/dist/components/field-config-panel.js +38 -0
  17. package/dist/components/field-config-panel.js.map +1 -0
  18. package/dist/components/field-sidebar.d.ts +10 -0
  19. package/dist/components/field-sidebar.d.ts.map +1 -0
  20. package/dist/components/field-sidebar.js +25 -0
  21. package/dist/components/field-sidebar.js.map +1 -0
  22. package/dist/components/field-type.d.ts +9 -0
  23. package/dist/components/field-type.d.ts.map +1 -0
  24. package/dist/components/field-type.js +12 -0
  25. package/dist/components/field-type.js.map +1 -0
  26. package/dist/components/fillable-field-type-list.d.ts +10 -0
  27. package/dist/components/fillable-field-type-list.d.ts.map +1 -0
  28. package/dist/components/fillable-field-type-list.js +17 -0
  29. package/dist/components/fillable-field-type-list.js.map +1 -0
  30. package/dist/components/pdf-canvas.d.ts +22 -0
  31. package/dist/components/pdf-canvas.d.ts.map +1 -0
  32. package/dist/components/pdf-canvas.js +14 -0
  33. package/dist/components/pdf-canvas.js.map +1 -0
  34. package/dist/components/pdf-document-renderer.d.ts +16 -0
  35. package/dist/components/pdf-document-renderer.d.ts.map +1 -0
  36. package/dist/components/pdf-document-renderer.js +28 -0
  37. package/dist/components/pdf-document-renderer.js.map +1 -0
  38. package/dist/components/pdf-editor.d.ts +21 -0
  39. package/dist/components/pdf-editor.d.ts.map +1 -0
  40. package/dist/components/pdf-editor.js +108 -0
  41. package/dist/components/pdf-editor.js.map +1 -0
  42. package/dist/components/pdf-field-overlay.d.ts +14 -0
  43. package/dist/components/pdf-field-overlay.d.ts.map +1 -0
  44. package/dist/components/pdf-field-overlay.js +32 -0
  45. package/dist/components/pdf-field-overlay.js.map +1 -0
  46. package/dist/constants/field.constants.d.ts +16 -0
  47. package/dist/constants/field.constants.d.ts.map +1 -0
  48. package/dist/constants/field.constants.js +28 -0
  49. package/dist/constants/field.constants.js.map +1 -0
  50. package/dist/constants/index.d.ts +3 -0
  51. package/dist/constants/index.d.ts.map +1 -0
  52. package/dist/constants/index.js +3 -0
  53. package/dist/constants/index.js.map +1 -0
  54. package/dist/constants/pdf-editor.constants.d.ts +2 -0
  55. package/dist/constants/pdf-editor.constants.d.ts.map +1 -0
  56. package/dist/constants/pdf-editor.constants.js +2 -0
  57. package/dist/constants/pdf-editor.constants.js.map +1 -0
  58. package/dist/hooks/index.d.ts +4 -0
  59. package/dist/hooks/index.d.ts.map +1 -0
  60. package/dist/hooks/index.js +4 -0
  61. package/dist/hooks/index.js.map +1 -0
  62. package/dist/hooks/useFieldDrag.d.ts +24 -0
  63. package/dist/hooks/useFieldDrag.d.ts.map +1 -0
  64. package/dist/hooks/useFieldDrag.js +34 -0
  65. package/dist/hooks/useFieldDrag.js.map +1 -0
  66. package/dist/hooks/useFieldResize.d.ts +17 -0
  67. package/dist/hooks/useFieldResize.d.ts.map +1 -0
  68. package/dist/hooks/useFieldResize.js +58 -0
  69. package/dist/hooks/useFieldResize.js.map +1 -0
  70. package/dist/hooks/usePdfDocumentRenderer.d.ts +9 -0
  71. package/dist/hooks/usePdfDocumentRenderer.d.ts.map +1 -0
  72. package/dist/hooks/usePdfDocumentRenderer.js +13 -0
  73. package/dist/hooks/usePdfDocumentRenderer.js.map +1 -0
  74. package/dist/index.d.ts +3 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +2 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/interface/pdf-editor.d.ts +64 -0
  79. package/dist/interface/pdf-editor.d.ts.map +1 -0
  80. package/dist/interface/pdf-editor.js +14 -0
  81. package/dist/interface/pdf-editor.js.map +1 -0
  82. package/dist/utils/calculate-drop-coordinates.utils.d.ts +15 -0
  83. package/dist/utils/calculate-drop-coordinates.utils.d.ts.map +1 -0
  84. package/dist/utils/calculate-drop-coordinates.utils.js +48 -0
  85. package/dist/utils/calculate-drop-coordinates.utils.js.map +1 -0
  86. package/dist/utils/extract-grouped-fields-from-data-model.utils.d.ts +7 -0
  87. package/dist/utils/extract-grouped-fields-from-data-model.utils.d.ts.map +1 -0
  88. package/dist/utils/extract-grouped-fields-from-data-model.utils.js +57 -0
  89. package/dist/utils/extract-grouped-fields-from-data-model.utils.js.map +1 -0
  90. package/dist/utils/generate-e-sign-path.d.ts +3 -0
  91. package/dist/utils/generate-e-sign-path.d.ts.map +1 -0
  92. package/dist/utils/generate-e-sign-path.js +4 -0
  93. package/dist/utils/generate-e-sign-path.js.map +1 -0
  94. package/dist/utils/get-page-dimensions.utils.d.ts +12 -0
  95. package/dist/utils/get-page-dimensions.utils.d.ts.map +1 -0
  96. package/dist/utils/get-page-dimensions.utils.js +31 -0
  97. package/dist/utils/get-page-dimensions.utils.js.map +1 -0
  98. package/dist/utils/get-page-number-from-client-y.utils.d.ts +9 -0
  99. package/dist/utils/get-page-number-from-client-y.utils.d.ts.map +1 -0
  100. package/dist/utils/get-page-number-from-client-y.utils.js +24 -0
  101. package/dist/utils/get-page-number-from-client-y.utils.js.map +1 -0
  102. package/dist/utils/get-page-position.utils.d.ts +12 -0
  103. package/dist/utils/get-page-position.utils.d.ts.map +1 -0
  104. package/dist/utils/get-page-position.utils.js +22 -0
  105. package/dist/utils/get-page-position.utils.js.map +1 -0
  106. package/dist/utils/handle-field-drag-start.utils.d.ts +16 -0
  107. package/dist/utils/handle-field-drag-start.utils.d.ts.map +1 -0
  108. package/dist/utils/handle-field-drag-start.utils.js +41 -0
  109. package/dist/utils/handle-field-drag-start.utils.js.map +1 -0
  110. package/dist/utils/handle-field-drag.utils.d.ts +19 -0
  111. package/dist/utils/handle-field-drag.utils.d.ts.map +1 -0
  112. package/dist/utils/handle-field-drag.utils.js +36 -0
  113. package/dist/utils/handle-field-drag.utils.js.map +1 -0
  114. package/dist/utils/handle-field-resize.utils.d.ts +35 -0
  115. package/dist/utils/handle-field-resize.utils.d.ts.map +1 -0
  116. package/dist/utils/handle-field-resize.utils.js +66 -0
  117. package/dist/utils/handle-field-resize.utils.js.map +1 -0
  118. package/dist/utils/index.d.ts +13 -0
  119. package/dist/utils/index.d.ts.map +1 -0
  120. package/dist/utils/index.js +13 -0
  121. package/dist/utils/index.js.map +1 -0
  122. package/dist/utils/is-drag-over-canvas.utils.d.ts +9 -0
  123. package/dist/utils/is-drag-over-canvas.utils.d.ts.map +1 -0
  124. package/dist/utils/is-drag-over-canvas.utils.js +26 -0
  125. package/dist/utils/is-drag-over-canvas.utils.js.map +1 -0
  126. package/dist/utils/map-colors-to-recipients.d.ts +3 -0
  127. package/dist/utils/map-colors-to-recipients.d.ts.map +1 -0
  128. package/dist/utils/map-colors-to-recipients.js +35 -0
  129. package/dist/utils/map-colors-to-recipients.js.map +1 -0
  130. package/dist/utils/pdfjs-init.d.ts +6 -0
  131. package/dist/utils/pdfjs-init.d.ts.map +1 -0
  132. package/dist/utils/pdfjs-init.js +25 -0
  133. package/dist/utils/pdfjs-init.js.map +1 -0
  134. package/package.json +28 -0
  135. package/src/components/data-model-field-type-list.tsx +58 -0
  136. package/src/components/e-sign-field-type-list.tsx +27 -0
  137. package/src/components/field-config-panel-overlay.tsx +51 -0
  138. package/src/components/field-config-panel.tsx +142 -0
  139. package/src/components/field-sidebar.tsx +93 -0
  140. package/src/components/field-type.tsx +28 -0
  141. package/src/components/fillable-field-type-list.tsx +42 -0
  142. package/src/components/pdf-canvas.tsx +81 -0
  143. package/src/components/pdf-document-renderer.tsx +78 -0
  144. package/src/components/pdf-editor.tsx +216 -0
  145. package/src/components/pdf-field-overlay.tsx +83 -0
  146. package/src/constants/field.constants.ts +31 -0
  147. package/src/constants/index.ts +2 -0
  148. package/src/constants/pdf-editor.constants.ts +1 -0
  149. package/src/hooks/index.ts +3 -0
  150. package/src/hooks/useFieldDrag.ts +56 -0
  151. package/src/hooks/useFieldResize.ts +95 -0
  152. package/src/hooks/usePdfDocumentRenderer.ts +21 -0
  153. package/src/index.ts +2 -0
  154. package/src/interface/pdf-editor.ts +74 -0
  155. package/src/styles/field-config-panel-overlay.css +33 -0
  156. package/src/styles/field-sidebar.css +31 -0
  157. package/src/styles/field-type-list.css +8 -0
  158. package/src/styles/field-type.css +10 -0
  159. package/src/styles/generic.css +3 -0
  160. package/src/styles/index.css +10 -0
  161. package/src/styles/pdf-canvas.css +9 -0
  162. package/src/styles/pdf-document-renderer.css +4 -0
  163. package/src/styles/pdf-editor.css +14 -0
  164. package/src/styles/pdf-field-overlay.css +54 -0
  165. package/src/styles/variables.css +26 -0
  166. package/src/utils/calculate-drop-coordinates.utils.ts +68 -0
  167. package/src/utils/extract-grouped-fields-from-data-model.utils.ts +73 -0
  168. package/src/utils/generate-e-sign-path.ts +5 -0
  169. package/src/utils/get-page-dimensions.utils.ts +39 -0
  170. package/src/utils/get-page-number-from-client-y.utils.ts +31 -0
  171. package/src/utils/get-page-position.utils.ts +30 -0
  172. package/src/utils/handle-field-drag-start.utils.ts +52 -0
  173. package/src/utils/handle-field-drag.utils.ts +55 -0
  174. package/src/utils/handle-field-resize.utils.ts +102 -0
  175. package/src/utils/index.ts +12 -0
  176. package/src/utils/is-drag-over-canvas.utils.ts +35 -0
  177. package/src/utils/map-colors-to-recipients.ts +37 -0
  178. package/src/utils/pdfjs-init.ts +27 -0
  179. package/src/vite-env.d.ts +16 -0
@@ -0,0 +1,95 @@
1
+ import { MouseEvent, RefObject, useEffect, useRef, useState } from 'react';
2
+ import { PdfField } from '../interface/pdf-editor';
3
+ import { handleFieldResize, handleFieldResizeStart } from '../utils';
4
+
5
+ interface UseFieldResizeOptions {
6
+ pdfWrapperRef: RefObject<HTMLDivElement>;
7
+ onFieldResize: (
8
+ fieldId: string,
9
+ newWidth: number,
10
+ newHeight: number,
11
+ pageNumber: number,
12
+ ) => void;
13
+ }
14
+
15
+ interface UseFieldResizeReturn {
16
+ isResizing: boolean;
17
+ handleResizeStart: (e: MouseEvent<HTMLDivElement>, field: PdfField) => void;
18
+ }
19
+
20
+ /**
21
+ * Custom hook for handling field resize operations
22
+ * Manages resize state and coordinates field resizing with proper cleanup
23
+ */
24
+ export const useFieldResize = ({
25
+ onFieldResize,
26
+ pdfWrapperRef,
27
+ }: UseFieldResizeOptions): UseFieldResizeReturn => {
28
+ const resizeStartDataRef = useRef<{
29
+ field: PdfField;
30
+ startX: number;
31
+ startY: number;
32
+ startWidth: number;
33
+ startHeight: number;
34
+ page: number;
35
+ } | null>(null);
36
+ const onFieldResizeRef = useRef(onFieldResize);
37
+ const [isResizing, setIsResizing] = useState(false);
38
+
39
+ useEffect(() => {
40
+ onFieldResizeRef.current = onFieldResize;
41
+ }, [onFieldResize]);
42
+
43
+ useEffect(() => {
44
+ if (!isResizing) {
45
+ return;
46
+ }
47
+
48
+ const abortController = new AbortController();
49
+ const { signal } = abortController;
50
+
51
+ const handleMouseMove = (e: globalThis.MouseEvent) => {
52
+ if (!resizeStartDataRef.current || signal.aborted) {
53
+ return;
54
+ }
55
+
56
+ const { field, ...resizeStartData } = resizeStartDataRef.current;
57
+ handleFieldResize(e, field, resizeStartData, pdfWrapperRef, onFieldResizeRef.current);
58
+ };
59
+
60
+ const handleMouseUp = () => {
61
+ if (signal.aborted) {
62
+ return;
63
+ }
64
+ resizeStartDataRef.current = null;
65
+ setIsResizing(false);
66
+ abortController.abort();
67
+ };
68
+
69
+ document.addEventListener('mousemove', handleMouseMove, { signal });
70
+ document.addEventListener('mouseup', handleMouseUp, { signal });
71
+
72
+ return () => {
73
+ abortController.abort();
74
+ };
75
+ }, [isResizing, pdfWrapperRef]);
76
+
77
+ const handleResizeStart = (e: MouseEvent<HTMLDivElement>, field: PdfField) => {
78
+ e.stopPropagation();
79
+ e.preventDefault();
80
+
81
+ const resizeStartData = handleFieldResizeStart(e, field, pdfWrapperRef);
82
+ if (resizeStartData) {
83
+ resizeStartDataRef.current = {
84
+ field,
85
+ ...resizeStartData,
86
+ };
87
+ setIsResizing(true);
88
+ }
89
+ };
90
+
91
+ return {
92
+ isResizing,
93
+ handleResizeStart,
94
+ };
95
+ };
@@ -0,0 +1,21 @@
1
+ import { useMemo, useState } from 'react';
2
+
3
+ interface UsePdfDocumentRendererReturn {
4
+ pages: number[];
5
+ handleDocumentLoadSuccess: (data: { numPages: number }) => void;
6
+ }
7
+
8
+ export const usePdfDocumentRenderer = (): UsePdfDocumentRendererReturn => {
9
+ const [numPages, setNumPages] = useState<number>(0);
10
+
11
+ const handleDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
12
+ setNumPages(numPages);
13
+ };
14
+
15
+ const pages = useMemo(() => Array.from({ length: numPages }, (_, i) => i + 1), [numPages]);
16
+
17
+ return {
18
+ pages,
19
+ handleDocumentLoadSuccess,
20
+ };
21
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type * from './interface/pdf-editor';
2
+ export * from './components/pdf-editor';
@@ -0,0 +1,74 @@
1
+ export enum FieldTypeEnum {
2
+ dataModel = 'data-model',
3
+ eSign = 'e-sign',
4
+ fillable = 'fillable',
5
+ }
6
+
7
+ export enum ESignFieldType {
8
+ signature = 'signature',
9
+ initials = 'initials',
10
+ dateSigned = 'dateSigned',
11
+ fullName = 'fullName',
12
+ }
13
+
14
+ export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'e-sign';
15
+
16
+ export interface PdfField {
17
+ id: string;
18
+ type: FieldTypeEnum;
19
+ subType?: FillableFieldType | ESignFieldType;
20
+ x: number;
21
+ y: number;
22
+ page: number;
23
+ label: string;
24
+ width: number;
25
+ height: number;
26
+ required?: boolean;
27
+ path?: string;
28
+ recipient?: string;
29
+ }
30
+
31
+ export interface FieldTypeOption {
32
+ label: string;
33
+ type: FieldTypeEnum;
34
+ subType?: FillableFieldType | ESignFieldType;
35
+ path?: string;
36
+ }
37
+
38
+ export interface DataModelFieldGroup {
39
+ groupName: string;
40
+ fields: FieldTypeOption[];
41
+ }
42
+
43
+ export interface SchemaFieldBaseOptions {
44
+ placeholder?: any;
45
+ description?: any;
46
+ sampleData?: any;
47
+ showInEditor?: boolean;
48
+ isHighlighted?: boolean;
49
+ }
50
+
51
+ interface SchemaNodeProps {
52
+ title?: string;
53
+ options?: SchemaFieldBaseOptions;
54
+ }
55
+
56
+ export interface SchemaObject extends SchemaNodeProps {
57
+ type: 'object';
58
+ properties: Record<string, SchemaNode>;
59
+ }
60
+
61
+ export type SchemaNode = SchemaObject | SchemaArray | SchemaSimple;
62
+
63
+ export interface SchemaArray extends SchemaNodeProps {
64
+ type: 'array';
65
+ items: SchemaNode;
66
+ }
67
+ export type SchemaNodeStringSubTypes = 'string' | 'text' | 'html' | 'image';
68
+
69
+ export interface SchemaSimpleString extends SchemaNodeProps {
70
+ type: 'string';
71
+ subType?: SchemaNodeStringSubTypes;
72
+ }
73
+
74
+ export type SchemaSimple = SchemaSimpleString;
@@ -0,0 +1,33 @@
1
+ @keyframes dte-slide-in-left {
2
+ from {
3
+ transform: translateX(-100%);
4
+ }
5
+ to {
6
+ transform: translateX(0);
7
+ }
8
+ }
9
+
10
+ .dte-field-config-panel-overlay {
11
+ position: absolute;
12
+ top: 0;
13
+ left: 0;
14
+ bottom: 0;
15
+ width: 100%;
16
+ border-right: 1px solid var(--border-color);
17
+ background-color: var(--white);
18
+ z-index: 1000;
19
+ overflow-y: auto;
20
+ animation: dte-slide-in-left 0.3s ease-out;
21
+ display: flex;
22
+ flex-direction: column;
23
+ }
24
+
25
+ .dte-field-config-panel-header {
26
+ padding: var(--spacing-2);
27
+ border-bottom: 1px solid var(--border-color);
28
+ }
29
+
30
+ .dte-field-config-panel-content {
31
+ padding: var(--spacing-2);
32
+ flex: 1;
33
+ }
@@ -0,0 +1,31 @@
1
+ .dte-field-sidebar-container {
2
+ position: relative;
3
+ }
4
+
5
+ .dte-pdf-editor .dte-field-sidebar-menu {
6
+ width: 85px;
7
+ padding: var(--spacing-2) var(--spacing-half);
8
+ }
9
+
10
+ .dte-field-sidebar-menu-item {
11
+ cursor: pointer;
12
+ }
13
+
14
+ .dte-field-sidebar-menu-item-text {
15
+ text-align: center;
16
+ }
17
+
18
+ .dte-field-sidebar-content {
19
+ border-left: 1px solid var(--border-color);
20
+ border-right: 1px solid var(--border-color);
21
+ padding: var(--spacing-2);
22
+ position: relative;
23
+ display: flex;
24
+ flex-direction: column;
25
+ overflow-y: auto;
26
+ width: 305px;
27
+ }
28
+
29
+ .dte-field-sidebar-search {
30
+ margin-top: 18px;
31
+ }
@@ -0,0 +1,8 @@
1
+ .dte-field-type-group {
2
+ margin-top: 0.25rem;
3
+ }
4
+
5
+ .dte-field-type-group-header {
6
+ cursor: pointer;
7
+ user-select: none;
8
+ }
@@ -0,0 +1,10 @@
1
+ .dte-field-type-item {
2
+ align-items: center;
3
+ display: flex;
4
+ gap: 8px;
5
+ padding: 12px;
6
+ background-color: var(--color-neutral-0);
7
+ border-radius: 0.375rem;
8
+ cursor: grab;
9
+ user-select: none;
10
+ }
@@ -0,0 +1,3 @@
1
+ .full-width {
2
+ width: 100%;
3
+ }
@@ -0,0 +1,10 @@
1
+ @import './variables.css';
2
+ @import './generic.css';
3
+ @import './pdf-canvas.css';
4
+ @import './field-sidebar.css';
5
+ @import './pdf-field-overlay.css';
6
+ @import './pdf-editor.css';
7
+ @import './field-type-list.css';
8
+ @import './field-config-panel-overlay.css';
9
+ @import './field-type.css';
10
+ @import './pdf-document-renderer.css';
@@ -0,0 +1,9 @@
1
+ .dte-pdf-canvas-container {
2
+ overflow: auto;
3
+ position: relative;
4
+ }
5
+
6
+ .dte-pdf-wrapper {
7
+ position: relative;
8
+ width: 100%;
9
+ }
@@ -0,0 +1,4 @@
1
+ .dte-pdf-document-renderer-page {
2
+ margin-bottom: 20px;
3
+ position: relative;
4
+ }
@@ -0,0 +1,14 @@
1
+ .dte-pdf-editor {
2
+ max-width: 100%;
3
+ border: 1px solid var(--border-color);
4
+ }
5
+
6
+ .dte-pdf-editor-sidebar-container {
7
+ position: relative;
8
+ overflow: hidden;
9
+ }
10
+
11
+ .dte-pdf-editor-content-container {
12
+ max-width: 100%;
13
+ overflow: auto;
14
+ }
@@ -0,0 +1,54 @@
1
+ .dte-pdf-field-overlay {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ pointer-events: none;
8
+ }
9
+
10
+ .dte-pdf-field {
11
+ position: absolute;
12
+ border: 1px dashed #999;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ font-size: var(--typescale-1, 14px);
17
+ padding: var(--spacing-half, 4px);
18
+ box-sizing: border-box;
19
+ pointer-events: auto;
20
+ }
21
+
22
+ .dte-pdf-field-selected {
23
+ border: 2px solid var(--border-color-active);
24
+ cursor: move;
25
+ }
26
+
27
+ .dte-pdf-field-unselected {
28
+ cursor: pointer;
29
+ }
30
+
31
+ .dte-pdf-field-dragging {
32
+ opacity: 0.5;
33
+ }
34
+
35
+ .dte-pdf-field-path {
36
+ margin-top: 2px;
37
+ }
38
+
39
+ .dte-pdf-field-resize-handle {
40
+ position: absolute;
41
+ bottom: 0;
42
+ right: 0;
43
+ width: 12px;
44
+ height: 12px;
45
+ background-color: var(--border-color-active);
46
+ border: 1px solid var(--border-color-active);
47
+ cursor: nwse-resize;
48
+ z-index: 10;
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ .dte-pdf-field-resize-handle:hover {
53
+ background-color: var(--border-color-active);
54
+ }
@@ -0,0 +1,26 @@
1
+ @import '@servicetitan/tokens/dist/tokens.css';
2
+
3
+ .dte-pdf-editor {
4
+ /* Typography */
5
+ --typescale-1: var(--typescale-1, 14px);
6
+
7
+ /* Spacing */
8
+ --spacing-0: 0;
9
+ --spacing-half: 4px;
10
+ --spacing-2: 16px;
11
+
12
+ /* Colors - Neutral */
13
+ --color-neutral-0: var(--color-neutral-0, #ffffff);
14
+
15
+ /* Colors - Semantic */
16
+ --success: #28a745;
17
+ --danger: #dc3545;
18
+ --green: #28a745;
19
+ --red: #dc3545;
20
+ --white: var(--color-neutral-0, #ffffff);
21
+
22
+ /* Border colors*/
23
+ --border-color: #e0e0e0;
24
+ --border-color-hover: #bdbdbd;
25
+ --border-color-active: #007bff;
26
+ }
@@ -0,0 +1,68 @@
1
+ import { DragEvent, RefObject } from 'react';
2
+ import { FIELD_CONSTANTS } from '../constants';
3
+
4
+ interface DropCoordinates {
5
+ x: number;
6
+ y: number;
7
+ }
8
+
9
+ /**
10
+ * Calculates field coordinates from a drop event on a PDF page
11
+ * @param e - The drag event
12
+ * @param pageNumber - The page number where the drop occurred
13
+ * @param pdfWrapperRef - Reference to the PDF wrapper element
14
+ * @returns Drop coordinates (x, y) or null if a drop is invalid
15
+ */
16
+ export const calculateDropCoordinates = (
17
+ e: DragEvent<HTMLDivElement>,
18
+ pageNumber: number,
19
+ pdfWrapperRef: RefObject<HTMLDivElement>,
20
+ ): DropCoordinates | null => {
21
+ if (!pdfWrapperRef.current) {
22
+ return null;
23
+ }
24
+
25
+ const pageElement = pdfWrapperRef.current.querySelector(`[data-page-number="${pageNumber}"]`);
26
+
27
+ if (!pageElement) {
28
+ return null;
29
+ }
30
+
31
+ const canvas = pageElement.querySelector('canvas');
32
+ if (!canvas) {
33
+ return null;
34
+ }
35
+
36
+ const canvasRect = canvas.getBoundingClientRect();
37
+ const pageRect = pageElement.getBoundingClientRect();
38
+ const wrapperRect = pdfWrapperRef.current.getBoundingClientRect();
39
+
40
+ /*
41
+ * Strictly check if the drop is within the actual PDF canvas boundaries
42
+ * Only allow drops directly on the canvas, not on the page wrapper outside the canvas
43
+ */
44
+ const isWithinCanvas =
45
+ e.clientX >= canvasRect.left &&
46
+ e.clientX <= canvasRect.right &&
47
+ e.clientY >= canvasRect.top &&
48
+ e.clientY <= canvasRect.bottom;
49
+
50
+ if (!isWithinCanvas) {
51
+ return null;
52
+ }
53
+
54
+ /*
55
+ * Calculate coordinates relative to the page element (matching the overlay's coordinate system).
56
+ * This matches how pdf-field-overlay.tsx calculates positions: pagePos.left + field.x
57
+ */
58
+ const pagePosLeft = pageRect.left - wrapperRect.left;
59
+ const pagePosTop = pageRect.top - wrapperRect.top;
60
+ const x = e.clientX - wrapperRect.left - pagePosLeft;
61
+ const y = e.clientY - wrapperRect.top - pagePosTop;
62
+
63
+ // Center the field on the drop point (half of width/height)
64
+ const fieldX = Math.max(0, x - FIELD_CONSTANTS.dropOffsetX);
65
+ const fieldY = Math.max(0, y - FIELD_CONSTANTS.dropOffsetY);
66
+
67
+ return { x: fieldX, y: fieldY };
68
+ };
@@ -0,0 +1,73 @@
1
+ import {
2
+ DataModelFieldGroup,
3
+ FieldTypeEnum,
4
+ FieldTypeOption,
5
+ SchemaObject,
6
+ } from '../interface/pdf-editor';
7
+
8
+ /**
9
+ * Utility function to extract grouped fields from DataModel structure
10
+ * Filters out array types and only processes object properties, generating correct paths for data replacement
11
+ */
12
+ export const extractGroupedFieldsFromDataModel = (
13
+ dataModel: SchemaObject,
14
+ ): DataModelFieldGroup[] => {
15
+ const groups: DataModelFieldGroup[] = [];
16
+
17
+ if (!dataModel?.properties) {
18
+ return groups;
19
+ }
20
+
21
+ Object.keys(dataModel.properties).forEach(key => {
22
+ const property = dataModel.properties[key];
23
+
24
+ if (property.type === 'object' && property.properties) {
25
+ const fields: FieldTypeOption[] = [];
26
+ extractFieldsRecursive(property.properties, key, fields, key);
27
+
28
+ if (fields.length > 0) {
29
+ groups.push({
30
+ groupName: property.title ?? key,
31
+ fields,
32
+ });
33
+ }
34
+ } else if (property.type === 'array' && property.items) {
35
+ // Skip array types - filter them out from the data model
36
+ }
37
+ });
38
+
39
+ return groups;
40
+ };
41
+
42
+ // Recursive function to extract fields from nested structures
43
+ const extractFieldsRecursive = (
44
+ properties: any,
45
+ basePath: string,
46
+ fields: FieldTypeOption[],
47
+ groupName: string,
48
+ ): void => {
49
+ Object.keys(properties).forEach(fieldKey => {
50
+ const fieldProperty = properties[fieldKey];
51
+ const currentPath = basePath.includes('[]')
52
+ ? `${basePath}.${fieldKey}`
53
+ : `${basePath}.${fieldKey}`;
54
+
55
+ if (
56
+ fieldProperty.type === 'string' ||
57
+ fieldProperty.type === 'number' ||
58
+ fieldProperty.type === 'boolean'
59
+ ) {
60
+ // Leaf property - add as a field
61
+ const label = fieldProperty.title ?? fieldKey;
62
+ fields.push({
63
+ label,
64
+ type: FieldTypeEnum.dataModel,
65
+ path: currentPath,
66
+ });
67
+ } else if (fieldProperty.type === 'object' && fieldProperty.properties) {
68
+ extractFieldsRecursive(fieldProperty.properties, currentPath, fields, groupName);
69
+ } else if (fieldProperty.type === 'array' && fieldProperty.items) {
70
+ // Skip array types - filter them out from the data model
71
+ }
72
+ });
73
+ };
@@ -0,0 +1,5 @@
1
+ import { ESignFieldType } from '../interface/pdf-editor';
2
+
3
+ export const generateESignPath = (recipient: string, subType: ESignFieldType): string => {
4
+ return `esign_${recipient}_${subType}`;
5
+ };
@@ -0,0 +1,39 @@
1
+ import { RefObject } from 'react';
2
+
3
+ /**
4
+ * Gets the dimensions of a PDF page (width and height)
5
+ * @param pageNumber - The page number to get dimensions for
6
+ * @param pdfWrapperRef - Reference to the PDF wrapper element
7
+ * @returns Dimensions object with width and height, or { width: 0, height: 0 } if not found
8
+ */
9
+ export const getPageDimensions = (
10
+ pageNumber: number,
11
+ pdfWrapperRef: RefObject<HTMLDivElement>,
12
+ ): { width: number; height: number } => {
13
+ if (!pdfWrapperRef.current) {
14
+ return { width: 0, height: 0 };
15
+ }
16
+
17
+ const pageElement = pdfWrapperRef.current.querySelector(`[data-page-number="${pageNumber}"]`);
18
+
19
+ if (!pageElement) {
20
+ return { width: 0, height: 0 };
21
+ }
22
+
23
+ // Get the canvas element inside the page to get actual PDF dimensions
24
+ const canvas = pageElement.querySelector('canvas');
25
+ if (canvas) {
26
+ const canvasRect = canvas.getBoundingClientRect();
27
+ return {
28
+ width: canvasRect.width,
29
+ height: canvasRect.height,
30
+ };
31
+ }
32
+
33
+ // Fallback to page element dimensions
34
+ const pageRect = pageElement.getBoundingClientRect();
35
+ return {
36
+ width: pageRect.width,
37
+ height: pageRect.height,
38
+ };
39
+ };
@@ -0,0 +1,31 @@
1
+ import { RefObject } from 'react';
2
+
3
+ /**
4
+ * Determines which page number a client Y coordinate falls within
5
+ * @param clientY - The client Y coordinate to check
6
+ * @param pdfWrapperRef - Reference to the PDF wrapper element
7
+ * @returns The page number (defaults to 1 if not found or wrapper is null)
8
+ */
9
+ export const getPageNumberFromClientY = (
10
+ clientY: number,
11
+ pdfWrapperRef: RefObject<HTMLDivElement>,
12
+ ): number => {
13
+ if (!pdfWrapperRef.current) {
14
+ return 1;
15
+ }
16
+
17
+ const pageElements = pdfWrapperRef.current.querySelectorAll('[data-page-number]');
18
+ let targetPage = 1;
19
+
20
+ pageElements.forEach(pageElement => {
21
+ const rect = pageElement.getBoundingClientRect();
22
+ if (clientY >= rect.top && clientY <= rect.bottom) {
23
+ const pageNumber = pageElement.getAttribute('data-page-number');
24
+ if (pageNumber) {
25
+ targetPage = parseInt(pageNumber, 10);
26
+ }
27
+ }
28
+ });
29
+
30
+ return targetPage;
31
+ };
@@ -0,0 +1,30 @@
1
+ import { RefObject } from 'react';
2
+
3
+ /**
4
+ * Gets the position of a PDF page relative to the wrapper element
5
+ * @param pageNumber - The page number to get the position for
6
+ * @param pdfWrapperRef - Reference to the PDF wrapper element
7
+ * @returns Position object with top and left coordinates, or { top: 0, left: 0 } if not found
8
+ */
9
+ export const getPagePosition = (
10
+ pageNumber: number,
11
+ pdfWrapperRef: RefObject<HTMLDivElement>,
12
+ ): { top: number; left: number } => {
13
+ if (!pdfWrapperRef.current) {
14
+ return { top: 0, left: 0 };
15
+ }
16
+
17
+ const pageElement = pdfWrapperRef.current.querySelector(`[data-page-number="${pageNumber}"]`);
18
+
19
+ if (!pageElement) {
20
+ return { top: 0, left: 0 };
21
+ }
22
+
23
+ const wrapperRect = pdfWrapperRef.current.getBoundingClientRect();
24
+ const pageRect = pageElement.getBoundingClientRect();
25
+
26
+ return {
27
+ top: pageRect.top - wrapperRect.top,
28
+ left: pageRect.left - wrapperRect.left,
29
+ };
30
+ };