@servicetitan/dte-pdf-editor 1.2.0 → 1.3.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 (80) hide show
  1. package/dist/components/field-config-panel.d.ts.map +1 -1
  2. package/dist/components/field-config-panel.js +4 -2
  3. package/dist/components/field-config-panel.js.map +1 -1
  4. package/dist/components/fillable-field-type-list.d.ts.map +1 -1
  5. package/dist/components/fillable-field-type-list.js +4 -1
  6. package/dist/components/fillable-field-type-list.js.map +1 -1
  7. package/dist/components/pdf-editor.d.ts.map +1 -1
  8. package/dist/components/pdf-editor.js +9 -3
  9. package/dist/components/pdf-editor.js.map +1 -1
  10. package/dist/components/pdf-field-overlay.d.ts +10 -0
  11. package/dist/components/pdf-field-overlay.d.ts.map +1 -1
  12. package/dist/components/pdf-field-overlay.js +46 -18
  13. package/dist/components/pdf-field-overlay.js.map +1 -1
  14. package/dist/components/pdf-view-fields.d.ts +17 -0
  15. package/dist/components/pdf-view-fields.d.ts.map +1 -0
  16. package/dist/components/pdf-view-fields.js +53 -0
  17. package/dist/components/pdf-view-fields.js.map +1 -0
  18. package/dist/components/pdf-view.d.ts +19 -0
  19. package/dist/components/pdf-view.d.ts.map +1 -0
  20. package/dist/components/pdf-view.js +32 -0
  21. package/dist/components/pdf-view.js.map +1 -0
  22. package/dist/components/resize-handle.d.ts +7 -0
  23. package/dist/components/resize-handle.d.ts.map +1 -0
  24. package/dist/components/resize-handle.js +5 -0
  25. package/dist/components/resize-handle.js.map +1 -0
  26. package/dist/constants/field.constants.d.ts +7 -3
  27. package/dist/constants/field.constants.d.ts.map +1 -1
  28. package/dist/constants/field.constants.js +10 -3
  29. package/dist/constants/field.constants.js.map +1 -1
  30. package/dist/hooks/useFieldResize.d.ts +1 -1
  31. package/dist/hooks/useFieldResize.d.ts.map +1 -1
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/interface/pdf-editor.d.ts +1 -1
  37. package/dist/interface/pdf-editor.d.ts.map +1 -1
  38. package/dist/utils/generate-fillable-path.d.ts +2 -0
  39. package/dist/utils/generate-fillable-path.d.ts.map +1 -0
  40. package/dist/utils/generate-fillable-path.js +4 -0
  41. package/dist/utils/generate-fillable-path.js.map +1 -0
  42. package/dist/utils/get-field-background-color.d.ts +2 -0
  43. package/dist/utils/get-field-background-color.d.ts.map +1 -0
  44. package/dist/utils/get-field-background-color.js +4 -0
  45. package/dist/utils/get-field-background-color.js.map +1 -0
  46. package/dist/utils/get-field-placeholder-text.d.ts +3 -0
  47. package/dist/utils/get-field-placeholder-text.d.ts.map +1 -0
  48. package/dist/utils/get-field-placeholder-text.js +7 -0
  49. package/dist/utils/get-field-placeholder-text.js.map +1 -0
  50. package/dist/utils/index.d.ts +5 -0
  51. package/dist/utils/index.d.ts.map +1 -1
  52. package/dist/utils/index.js +5 -0
  53. package/dist/utils/index.js.map +1 -1
  54. package/dist/utils/parse-fillable-path.d.ts +2 -0
  55. package/dist/utils/parse-fillable-path.d.ts.map +1 -0
  56. package/dist/utils/parse-fillable-path.js +9 -0
  57. package/dist/utils/parse-fillable-path.js.map +1 -0
  58. package/dist/utils/resolve-pdf-data-values.d.ts +2 -0
  59. package/dist/utils/resolve-pdf-data-values.d.ts.map +1 -0
  60. package/dist/utils/resolve-pdf-data-values.js +27 -0
  61. package/dist/utils/resolve-pdf-data-values.js.map +1 -0
  62. package/package.json +1 -1
  63. package/src/components/field-config-panel.tsx +13 -1
  64. package/src/components/fillable-field-type-list.tsx +6 -1
  65. package/src/components/pdf-editor.tsx +8 -1
  66. package/src/components/pdf-field-overlay.tsx +147 -48
  67. package/src/components/pdf-view-fields.tsx +138 -0
  68. package/src/components/pdf-view.tsx +99 -0
  69. package/src/components/resize-handle.tsx +8 -0
  70. package/src/constants/field.constants.ts +16 -3
  71. package/src/hooks/useFieldResize.ts +1 -1
  72. package/src/index.ts +1 -0
  73. package/src/interface/pdf-editor.ts +1 -1
  74. package/src/styles/pdf-field-overlay.css +34 -9
  75. package/src/utils/generate-fillable-path.ts +3 -0
  76. package/src/utils/get-field-background-color.ts +6 -0
  77. package/src/utils/get-field-placeholder-text.ts +8 -0
  78. package/src/utils/index.ts +5 -0
  79. package/src/utils/parse-fillable-path.ts +8 -0
  80. package/src/utils/resolve-pdf-data-values.ts +34 -0
@@ -0,0 +1,99 @@
1
+ import { Flex } from '@servicetitan/anvil2';
2
+ import { FC, ReactNode, useRef, useState } from 'react';
3
+ import { BASE_PAGE_WIDTH } from '../constants';
4
+ import { FieldTypeEnum, PdfField } from '../interface/pdf-editor';
5
+ import { getFieldBackgroundColor, getPagePosition, mapColorsToRecipients } from '../utils';
6
+ import { PdfDocumentRenderer } from './pdf-document-renderer';
7
+ import { RecipientInfo } from './pdf-editor';
8
+ import { FillableFieldView, ViewDataModel, ViewEsign } from './pdf-view-fields';
9
+
10
+ interface PdfViewProps {
11
+ fields: PdfField[];
12
+ data?: Record<string, any>;
13
+ pdfUrl: string;
14
+ loading?: boolean;
15
+ fillingBy?: string[];
16
+ loadingPlaceholder?: ReactNode;
17
+ errorPlaceholder?: ReactNode;
18
+ recipients?: RecipientInfo[];
19
+ onDataChange?(changedData: { [path: string]: string | boolean }): void;
20
+ }
21
+
22
+ export const PdfView: FC<PdfViewProps> = ({
23
+ data,
24
+ errorPlaceholder,
25
+ fields,
26
+ fillingBy,
27
+ loading = false,
28
+ loadingPlaceholder,
29
+ onDataChange,
30
+ pdfUrl,
31
+ recipients,
32
+ }) => {
33
+ const [isPdfLoaded, setIsPdfLoaded] = useState<boolean>(false);
34
+ const pdfWrapperRef = useRef<HTMLDivElement>(null);
35
+
36
+ const onDocumentLoadSuccess = (_: number) => {
37
+ setIsPdfLoaded(true);
38
+ };
39
+
40
+ const recipientsColors = mapColorsToRecipients(recipients);
41
+
42
+ return (
43
+ <Flex flex={1} className="dte-pdf-view-container" style={{ maxWidth: BASE_PAGE_WIDTH }}>
44
+ <div ref={pdfWrapperRef} className="dte-pdf-wrapper">
45
+ <PdfDocumentRenderer
46
+ pdfUrl={pdfUrl}
47
+ loading={loading}
48
+ pageWidth={BASE_PAGE_WIDTH}
49
+ errorPlaceholder={errorPlaceholder}
50
+ loadingPlaceholder={loadingPlaceholder}
51
+ onDocumentLoadSuccess={onDocumentLoadSuccess}
52
+ />
53
+ {isPdfLoaded && fields && fields.length > 0 && (
54
+ <div className="dte-pdf-field-overlay --view-mode">
55
+ {fields.map(field => {
56
+ const pagePos = getPagePosition(field.page, pdfWrapperRef);
57
+ const bgColor = getFieldBackgroundColor(
58
+ field.recipient,
59
+ recipientsColors,
60
+ );
61
+
62
+ return (
63
+ <div
64
+ key={field.id}
65
+ style={{
66
+ display: 'flex',
67
+ alignItems: 'center',
68
+ position: 'absolute',
69
+ padding: '4px',
70
+ background: bgColor,
71
+ left: `${pagePos.left + field.x}px`,
72
+ top: `${pagePos.top + field.y}px`,
73
+ width: `${field.width}px`,
74
+ height: `${field.height}px`,
75
+ }}
76
+ >
77
+ {field.type === FieldTypeEnum.dataModel && (
78
+ <ViewDataModel field={field} data={data} />
79
+ )}
80
+ {field.type === FieldTypeEnum.eSign && (
81
+ <ViewEsign field={field} data={data} />
82
+ )}
83
+ {field.type === FieldTypeEnum.fillable && (
84
+ <FillableFieldView
85
+ data={data}
86
+ field={field}
87
+ fillingBy={fillingBy}
88
+ onDataChange={onDataChange}
89
+ />
90
+ )}
91
+ </div>
92
+ );
93
+ })}
94
+ </div>
95
+ )}
96
+ </div>
97
+ </Flex>
98
+ );
99
+ };
@@ -0,0 +1,8 @@
1
+ import { FC, MouseEvent } from 'react';
2
+
3
+ interface ResizeHandleProps {
4
+ handleResizeStart(e: MouseEvent<HTMLDivElement>): void;
5
+ }
6
+ export const ResizeHandle: FC<ResizeHandleProps> = ({ handleResizeStart }) => {
7
+ return <div className="dte-pdf-field-resize-handle" onMouseDown={handleResizeStart} />;
8
+ };
@@ -1,14 +1,26 @@
1
- import { ESignFieldType, FieldTypeEnum, FieldTypeOption } from '../interface/pdf-editor';
1
+ import {
2
+ ESignFieldType,
3
+ FieldTypeEnum,
4
+ FieldTypeOption,
5
+ FillableFieldType,
6
+ } from '../interface/pdf-editor';
2
7
 
3
8
  export const FIELD_CONSTANTS = {
4
9
  defaultWidth: 200,
5
- defaultHeight: 30,
6
- minWidth: 50,
10
+ defaultHeight: 25,
11
+ minWidth: 20,
7
12
  minHeight: 20,
8
13
  dropOffsetX: 75,
9
14
  dropOffsetY: 15,
10
15
  } as const;
11
16
 
17
+ export const FILLABLE_FIELD_DEFAULT_SIZES = {
18
+ text: { width: 200, height: 25 },
19
+ date: { width: 200, height: 25 },
20
+ radio: { width: 20, height: 20 },
21
+ checkbox: { width: 20, height: 20 },
22
+ } as Record<FillableFieldType | ESignFieldType, { width: number; height: number }>;
23
+
12
24
  export const E_SIGN_FIELD_TYPE_OPTIONS: { name: string; id: ESignFieldType }[] = [
13
25
  { name: 'Signature', id: ESignFieldType.signature },
14
26
  { name: 'Initials', id: ESignFieldType.initials },
@@ -20,6 +32,7 @@ export const FILLABLE_FIELD_TYPES: FieldTypeOption[] = [
20
32
  { label: 'Text Field', type: FieldTypeEnum.fillable, subType: 'text' },
21
33
  { label: 'Date Field', type: FieldTypeEnum.fillable, subType: 'date' },
22
34
  { label: 'Checkbox', type: FieldTypeEnum.fillable, subType: 'checkbox' },
35
+ { label: 'Radio', type: FieldTypeEnum.fillable, subType: 'radio' },
23
36
  ];
24
37
 
25
38
  export const E_SIGN_FIELD_TYPES: FieldTypeOption[] = [
@@ -12,7 +12,7 @@ interface UseFieldResizeOptions {
12
12
  ) => void;
13
13
  }
14
14
 
15
- interface UseFieldResizeReturn {
15
+ export interface UseFieldResizeReturn {
16
16
  isResizing: boolean;
17
17
  handleResizeStart: (e: MouseEvent<HTMLDivElement>, field: PdfField) => void;
18
18
  }
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export type * from './interface/pdf-editor';
2
2
  export * from './components/pdf-editor';
3
+ export * from './components/pdf-view';
@@ -11,7 +11,7 @@ export enum ESignFieldType {
11
11
  fullName = 'fullName',
12
12
  }
13
13
 
14
- export type FillableFieldType = 'text' | 'date' | 'checkbox';
14
+ export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'radio';
15
15
 
16
16
  export interface PdfField {
17
17
  id: string;
@@ -7,15 +7,18 @@
7
7
  pointer-events: none;
8
8
  }
9
9
 
10
+ .dte-pdf-field-overlay.--view-mode {
11
+ pointer-events: auto;
12
+ }
13
+
10
14
  .dte-pdf-field {
11
15
  position: absolute;
12
16
  border: 1px dashed #999;
13
17
  display: flex;
14
18
  align-items: center;
15
- justify-content: center;
16
19
  font-size: var(--typescale-1, 14px);
17
20
  padding: var(--spacing-half, 4px);
18
- box-sizing: border-box;
21
+ box-sizing: content-box;
19
22
  pointer-events: auto;
20
23
  }
21
24
 
@@ -32,23 +35,45 @@
32
35
  opacity: 0.5;
33
36
  }
34
37
 
35
- .dte-pdf-field-path {
36
- margin-top: 2px;
37
- }
38
-
39
38
  .dte-pdf-field-resize-handle {
40
39
  position: absolute;
41
40
  bottom: 0;
42
41
  right: 0;
43
42
  width: 12px;
44
43
  height: 12px;
45
- background-color: var(--border-color-active);
46
- border: 1px solid var(--border-color-active);
44
+ border: 2px dashed var(--border-color-active);
47
45
  cursor: nwse-resize;
48
46
  z-index: 10;
49
47
  box-sizing: border-box;
50
48
  }
51
49
 
52
50
  .dte-pdf-field-resize-handle:hover {
53
- background-color: var(--border-color-active);
51
+ border-color: var(--border-color-active);
52
+ }
53
+
54
+ .dte-pdf-field-view {
55
+ border: none;
56
+ cursor: default;
57
+ overflow: hidden;
58
+ white-space: nowrap;
59
+ pointer-events: none;
60
+ justify-content: left;
61
+ text-overflow: ellipsis;
62
+ }
63
+
64
+ .dte-pdf-field-value {
65
+ font-weight: normal;
66
+ color: #333;
67
+ white-space: normal;
68
+ overflow: hidden;
69
+ text-overflow: ellipsis;
70
+ }
71
+
72
+ .dte-pdf-field-fillable {
73
+ width: 100%;
74
+ height: 100%;
75
+ background: inherit;
76
+ border: none;
77
+ pointer-events: none;
78
+ margin: var(--spacing-0, 0);
54
79
  }
@@ -0,0 +1,3 @@
1
+ export const generateFillablePath = (recipient: string, name: string): string => {
2
+ return `fillable_${recipient}_${name}`;
3
+ };
@@ -0,0 +1,6 @@
1
+ export const getFieldBackgroundColor = (
2
+ recipient: string | undefined,
3
+ recipientsColors: Record<string, string>,
4
+ ): string => {
5
+ return recipient && recipientsColors[recipient] ? recipientsColors[recipient] : 'none';
6
+ };
@@ -0,0 +1,8 @@
1
+ import { PdfField } from '../interface/pdf-editor';
2
+
3
+ export const getFieldPlaceholderText = (field: PdfField, prefix?: string): string => {
4
+ const requiredIndicator = field.required ? '*' : '';
5
+ const label = field.label || '';
6
+ const parts = [prefix, label, requiredIndicator].filter(Boolean);
7
+ return parts.join(' ');
8
+ };
@@ -10,3 +10,8 @@ export * from './is-drag-over-canvas.utils';
10
10
  export * from './map-colors-to-recipients';
11
11
  export * from './pdfjs-init';
12
12
  export * from './generate-e-sign-path';
13
+ export * from './resolve-pdf-data-values';
14
+ export * from './generate-fillable-path';
15
+ export * from './get-field-background-color';
16
+ export * from './get-field-placeholder-text';
17
+ export * from './parse-fillable-path';
@@ -0,0 +1,8 @@
1
+ export const parseFillablePathName = (path: string | undefined): string => {
2
+ if (!path) {
3
+ return '';
4
+ }
5
+ const parts = path.split('_');
6
+ // Path format: fillable_{recipient}_{name}
7
+ return parts.length >= 3 ? parts[2] : '';
8
+ };
@@ -0,0 +1,34 @@
1
+ export const resolvePdfDataValues = (
2
+ data: Record<string, any> | undefined,
3
+ path: string | undefined,
4
+ ): string => {
5
+ if (!data || !path) {
6
+ return '';
7
+ }
8
+
9
+ const keys = path.split('.');
10
+ let value: any = data;
11
+
12
+ for (const key of keys) {
13
+ if (value === null || value === undefined || typeof value !== 'object') {
14
+ return '';
15
+ }
16
+ value = value[key];
17
+ }
18
+
19
+ if (value === null || value === undefined) {
20
+ return '';
21
+ }
22
+
23
+ if (typeof value === 'object') {
24
+ if ('Amount' in value && 'Currency' in value) {
25
+ return `${value.Currency} ${value.Amount}`;
26
+ }
27
+ if ('Value' in value) {
28
+ return String(value.Value);
29
+ }
30
+ return JSON.stringify(value);
31
+ }
32
+
33
+ return String(value);
34
+ };