@servicetitan/dte-pdf-editor 1.5.0 → 1.7.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 (77) hide show
  1. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  2. package/dist/components/field-config-panel/field-config-panel.js +7 -2
  3. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  4. package/dist/components/field-sidebar/data-model-field-type-list.js +1 -1
  5. package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
  6. package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
  7. package/dist/components/field-sidebar/field-sidebar.js +9 -2
  8. package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
  9. package/dist/components/field-sidebar/field-type.js +1 -1
  10. package/dist/components/field-sidebar/field-type.js.map +1 -1
  11. package/dist/components/pdf-canvas/pdf-canvas.js +1 -1
  12. package/dist/components/pdf-canvas/pdf-canvas.js.map +1 -1
  13. package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
  14. package/dist/components/pdf-editor/pdf-editor.js +16 -106
  15. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  16. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
  17. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +2 -0
  18. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
  19. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  20. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +1 -1
  21. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  22. package/dist/components/pdf-view/pdf-view-e-sign.d.ts +1 -2
  23. package/dist/components/pdf-view/pdf-view-e-sign.d.ts.map +1 -1
  24. package/dist/components/pdf-view/pdf-view-e-sign.js +2 -4
  25. package/dist/components/pdf-view/pdf-view-e-sign.js.map +1 -1
  26. package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
  27. package/dist/components/pdf-view/pdf-view-fillable.js +9 -0
  28. package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
  29. package/dist/components/pdf-view/pdf-view.js +1 -1
  30. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  31. package/dist/constants/field.constants.d.ts.map +1 -1
  32. package/dist/constants/field.constants.js +1 -0
  33. package/dist/constants/field.constants.js.map +1 -1
  34. package/dist/hooks/index.d.ts +3 -0
  35. package/dist/hooks/index.d.ts.map +1 -1
  36. package/dist/hooks/index.js +3 -0
  37. package/dist/hooks/index.js.map +1 -1
  38. package/dist/hooks/usePdfFieldDnD.d.ts +17 -0
  39. package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -0
  40. package/dist/hooks/usePdfFieldDnD.js +78 -0
  41. package/dist/hooks/usePdfFieldDnD.js.map +1 -0
  42. package/dist/hooks/usePdfFieldSelection.d.ts +13 -0
  43. package/dist/hooks/usePdfFieldSelection.d.ts.map +1 -0
  44. package/dist/hooks/usePdfFieldSelection.js +40 -0
  45. package/dist/hooks/usePdfFieldSelection.js.map +1 -0
  46. package/dist/hooks/useToggle.d.ts +8 -0
  47. package/dist/hooks/useToggle.d.ts.map +1 -0
  48. package/dist/hooks/useToggle.js +15 -0
  49. package/dist/hooks/useToggle.js.map +1 -0
  50. package/dist/interface/types.d.ts +2 -1
  51. package/dist/interface/types.d.ts.map +1 -1
  52. package/package.json +3 -2
  53. package/src/components/field-config-panel/field-config-panel.tsx +11 -1
  54. package/src/components/field-sidebar/data-model-field-type-list.tsx +1 -1
  55. package/src/components/field-sidebar/field-sidebar.tsx +60 -34
  56. package/src/components/field-sidebar/field-type.tsx +1 -1
  57. package/src/components/pdf-canvas/pdf-canvas.tsx +1 -1
  58. package/src/components/pdf-editor/pdf-editor.tsx +54 -171
  59. package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +10 -0
  60. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +1 -0
  61. package/src/components/pdf-view/pdf-view-e-sign.tsx +6 -7
  62. package/src/components/pdf-view/pdf-view-fillable.tsx +23 -0
  63. package/src/components/pdf-view/pdf-view.tsx +1 -1
  64. package/src/constants/field.constants.ts +1 -0
  65. package/src/hooks/index.ts +3 -0
  66. package/src/hooks/usePdfFieldDnD.ts +118 -0
  67. package/src/hooks/usePdfFieldSelection.ts +55 -0
  68. package/src/hooks/useToggle.ts +26 -0
  69. package/src/interface/types.ts +2 -1
  70. package/src/styles/field-config-panel-overlay.css +3 -1
  71. package/src/styles/field-sidebar.css +36 -3
  72. package/src/styles/field-type-list.css +1 -1
  73. package/src/styles/field-type.css +7 -0
  74. package/src/styles/pdf-canvas.css +2 -0
  75. package/src/styles/pdf-editor.css +1 -0
  76. package/src/styles/variables.css +11 -1
  77. package/webpack.config.js +112 -0
@@ -1,30 +1,14 @@
1
1
  import { Flex } from '@servicetitan/anvil2';
2
- import { DragEvent, FC, MouseEvent, ReactNode, useMemo, useRef, useState } from 'react';
3
- import { v4 as uuidv4 } from 'uuid';
4
- import { FIELD_CONSTANTS, FILLABLE_FIELD_DEFAULT_SIZES } from '../../constants';
5
- import { useInitializePdfJsWorker } from '../../hooks';
6
-
7
- import {
8
- ESignFieldType,
9
- FieldTypeEnum,
10
- FieldTypeOption,
11
- PdfField,
12
- RecipientInfo,
13
- SchemaObject,
14
- } from '../../interface/types';
15
- import '../../styles/index.css';
16
- import {
17
- calculateDropCoordinates,
18
- extractGroupedFieldsFromDataModel,
19
- generateESignPath,
20
- generateFillablePath,
21
- isDragOverCanvas,
22
- mapColorsToRecipients,
23
- } from '../../utils';
2
+ import { FC, ReactNode, useCallback, useMemo, useRef } from 'react';
3
+ import { useInitializePdfJsWorker, usePdfFieldDnD, usePdfFieldSelection } from '../../hooks';
4
+ import { PdfField, RecipientInfo, SchemaObject } from '../../interface/types';
5
+ import { extractGroupedFieldsFromDataModel, mapColorsToRecipients } from '../../utils';
24
6
  import { FieldConfigPanelOverlay } from '../field-config-panel/field-config-panel-overlay';
25
7
  import { FieldSidebar } from '../field-sidebar/field-sidebar';
26
8
  import { PdfCanvas } from '../pdf-canvas/pdf-canvas';
27
9
 
10
+ import '../../styles/index.css';
11
+
28
12
  interface PdfEditorProps {
29
13
  pdfUrl: string;
30
14
  loading?: boolean;
@@ -46,161 +30,60 @@ export const PdfEditor: FC<PdfEditorProps> = ({
46
30
  pdfUrl,
47
31
  recipients = [],
48
32
  }) => {
49
- useInitializePdfJsWorker();
50
-
51
- const dataModelGroups = useMemo(() => {
52
- return dataModel ? extractGroupedFieldsFromDataModel(dataModel) : [];
53
- }, [dataModel]);
54
- const [draggedFieldOption, setDraggedFieldOption] = useState<FieldTypeOption | null>(null);
55
- const [selectedFieldId, setSelectedFieldId] = useState<string | null>(null);
56
33
  const pdfContainerRef = useRef<HTMLDivElement>(null);
57
34
  const pdfWrapperRef = useRef<HTMLDivElement>(null);
35
+ useInitializePdfJsWorker();
36
+ const {
37
+ deleteSelectedField,
38
+ deselectField,
39
+ moveField,
40
+ resizeField,
41
+ selectField,
42
+ selectedField,
43
+ setSelectedFieldId,
44
+ updateField,
45
+ } = usePdfFieldSelection(fields, onFieldsChange);
46
+
47
+ const pdfFieldDnd = usePdfFieldDnD({
48
+ fields,
49
+ recipients,
50
+ onFieldsChange,
51
+ pdfWrapperRef,
52
+ onSelectField: setSelectedFieldId,
53
+ });
54
+
55
+ const dataModelGroups = useMemo(
56
+ () => (dataModel ? extractGroupedFieldsFromDataModel(dataModel) : []),
57
+ [dataModel],
58
+ );
58
59
 
59
- const handleDragStart = (fieldOption: FieldTypeOption) => {
60
- setDraggedFieldOption(fieldOption);
61
- };
62
-
63
- const handleDragEnd = () => {
64
- setDraggedFieldOption(null);
65
- };
66
-
67
- const handleDrop = (e: DragEvent<HTMLDivElement>, pageNumber: number) => {
68
- e.preventDefault();
69
- e.stopPropagation();
70
-
71
- const isExistingField = e.dataTransfer.getData('text/plain') === 'existing-field';
72
- if (isExistingField || !draggedFieldOption) {
73
- return;
74
- }
75
-
76
- const coordinates = calculateDropCoordinates(e, pageNumber, pdfWrapperRef);
77
- if (!coordinates) {
78
- setDraggedFieldOption(null);
79
- return;
80
- }
81
-
82
- const newField: PdfField = {
83
- id: uuidv4(),
84
- type: draggedFieldOption.type,
85
- subType: draggedFieldOption.subType,
86
- x: coordinates.x,
87
- y: coordinates.y,
88
- page: pageNumber,
89
- label: draggedFieldOption.label,
90
- width: FIELD_CONSTANTS.defaultWidth,
91
- height: FIELD_CONSTANTS.defaultHeight,
92
- path: draggedFieldOption.path,
93
- };
94
-
95
- if (draggedFieldOption.type === FieldTypeEnum.eSign) {
96
- newField.recipient = recipients[0]?.name ?? '';
97
- newField.path = generateESignPath(
98
- recipients[0].name!,
99
- draggedFieldOption.subType as ESignFieldType,
100
- );
101
- }
102
- if (draggedFieldOption.type === FieldTypeEnum.fillable) {
103
- const defaultFieldSizes = FILLABLE_FIELD_DEFAULT_SIZES[newField.subType!];
104
- newField.recipient = recipients[0]?.name ?? '';
105
- newField.required = false;
106
- newField.path = generateFillablePath(recipients[0]?.name ?? '', uuidv4());
107
- if (defaultFieldSizes) {
108
- newField.width = defaultFieldSizes.width;
109
- newField.height = defaultFieldSizes.height;
110
- }
111
- }
112
-
113
- onFieldsChange([...fields, newField]);
114
- setSelectedFieldId(newField.id);
115
- setDraggedFieldOption(null);
116
- };
117
-
118
- const handleAddNewField = (newField: PdfField) => {
119
- onFieldsChange([...fields, newField]);
120
- };
121
-
122
- const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
123
- if (!draggedFieldOption) {
124
- e.dataTransfer.dropEffect = 'none';
125
- return;
126
- }
127
-
128
- const isOverCanvas = isDragOverCanvas(e, pdfWrapperRef);
129
-
130
- // Only allow a drop if we're over the actual PDF canvas
131
- if (isOverCanvas) {
132
- e.preventDefault();
133
- e.stopPropagation();
134
- e.dataTransfer.dropEffect = 'copy';
135
- } else {
136
- e.dataTransfer.dropEffect = 'none';
137
- }
138
- };
139
-
140
- const handleFieldClick = (fieldId: string, e: MouseEvent) => {
141
- e.stopPropagation();
142
- setSelectedFieldId(fieldId);
143
- };
144
-
145
- const handleFieldMove = (fieldId: string, newX: number, newY: number, pageNumber: number) => {
146
- onFieldsChange(
147
- fields.map(f => (f.id === fieldId ? { ...f, x: newX, y: newY, page: pageNumber } : f)),
148
- );
149
- };
150
-
151
- const handleFieldResize = (
152
- fieldId: string,
153
- newWidth: number,
154
- newHeight: number,
155
- pageNumber: number,
156
- ) => {
157
- onFieldsChange(
158
- fields.map(f =>
159
- f.id === fieldId
160
- ? { ...f, width: newWidth, height: newHeight, page: pageNumber }
161
- : f,
162
- ),
163
- );
164
- };
165
-
166
- const handleDeleteField = () => {
167
- if (selectedFieldId) {
168
- onFieldsChange(fields.filter(f => f.id !== selectedFieldId));
169
- setSelectedFieldId(null);
170
- }
171
- };
172
-
173
- const handleFieldConfigChange = (updates: Partial<PdfField>) => {
174
- if (selectedFieldId) {
175
- onFieldsChange(fields.map(f => (f.id === selectedFieldId ? { ...f, ...updates } : f)));
176
- }
177
- };
60
+ const handleAddNewField = useCallback(
61
+ (field: PdfField) => onFieldsChange([...fields, field]),
62
+ [fields, onFieldsChange],
63
+ );
178
64
 
179
65
  const recipientsColors = mapColorsToRecipients(recipients);
180
66
 
181
- const selectedField = useMemo(
182
- (): PdfField | null => fields.find(f => f.id === selectedFieldId) ?? null,
183
- [fields, selectedFieldId],
184
- );
185
-
186
67
  return (
187
68
  <Flex flex={1} className={`dte-pdf-editor ${loading ? 'skeleton' : ''}`}>
188
- <Flex className="dte-pdf-editor-sidebar-container ">
69
+ {selectedField && (
70
+ <FieldConfigPanelOverlay
71
+ selectedField={selectedField}
72
+ onFieldConfigChange={updateField}
73
+ onDeleteField={deleteSelectedField}
74
+ recipients={recipients}
75
+ onDeselectField={deselectField}
76
+ />
77
+ )}
78
+
79
+ <Flex className="dte-pdf-editor-sidebar-container">
189
80
  <FieldSidebar
190
81
  dataModelGroups={dataModelGroups}
191
- onDragEnd={handleDragEnd}
192
- onDragStart={handleDragStart}
82
+ onDragStart={pdfFieldDnd.handleDragStart}
83
+ onDragEnd={pdfFieldDnd.handleDragEnd}
193
84
  />
194
- {selectedField && (
195
- <FieldConfigPanelOverlay
196
- selectedField={selectedField}
197
- onFieldConfigChange={handleFieldConfigChange}
198
- onDeleteField={handleDeleteField}
199
- recipients={recipients}
200
- onDeselectField={() => setSelectedFieldId(null)}
201
- />
202
- )}
203
85
  </Flex>
86
+
204
87
  <Flex gap={12} flex={1} className="dte-pdf-editor-content-container skeleton-item">
205
88
  <PdfCanvas
206
89
  pdfUrl={pdfUrl}
@@ -213,12 +96,12 @@ export const PdfEditor: FC<PdfEditorProps> = ({
213
96
  errorPlaceholder={errorPlaceholder}
214
97
  loadingPlaceholder={loadingPlaceholder}
215
98
  handleAddNewField={handleAddNewField}
216
- onDrop={handleDrop}
217
- onDragOver={handleDragOver}
218
- onFieldClick={handleFieldClick}
219
- onFieldMove={handleFieldMove}
220
- onFieldResize={handleFieldResize}
221
- onDeselectField={() => setSelectedFieldId(null)}
99
+ onDrop={pdfFieldDnd.handleDrop}
100
+ onDragOver={pdfFieldDnd.handleDragOver}
101
+ onFieldClick={selectField}
102
+ onFieldMove={moveField}
103
+ onFieldResize={resizeField}
104
+ onDeselectField={deselectField}
222
105
  />
223
106
  </Flex>
224
107
  </Flex>
@@ -45,6 +45,16 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
45
45
  />
46
46
  );
47
47
 
48
+ case 'number':
49
+ return (
50
+ <input
51
+ type="number"
52
+ readOnly
53
+ className="dte-pdf-field-fillable"
54
+ placeholder={placeholderText}
55
+ />
56
+ );
57
+
48
58
  default:
49
59
  return (
50
60
  <input
@@ -67,6 +67,7 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
67
67
 
68
68
  return (
69
69
  <div
70
+ title={field.description}
70
71
  onClick={e => onFieldClick(field.id, e)}
71
72
  draggable
72
73
  onDragStart={e => handleDragStart(e, field)}
@@ -1,19 +1,18 @@
1
1
  import { FC } from 'react';
2
- import { DataModelValues, PdfField } from '../../interface/types';
3
- import { resolvePdfDataValues } from '../../utils';
2
+ import { PdfField } from '../../interface/types';
4
3
 
5
4
  interface PdfViewESignProps {
6
5
  field: PdfField;
7
- data?: DataModelValues;
8
6
  }
9
7
 
10
- export const PdfViewESign: FC<PdfViewESignProps> = ({ data, field }) => {
11
- const resolvedValue = field.path ? resolvePdfDataValues(data, field.path) : '';
12
-
8
+ export const PdfViewESign: FC<PdfViewESignProps> = ({ field }) => {
13
9
  return (
14
10
  <span>
15
11
  {field.label} {field.required ? '*' : ''}
16
- <div className="dte-pdf-field-value">{resolvedValue}</div>
12
+ <div
13
+ className="dte-pdf-field-value"
14
+ style={{ opacity: 0.1, color: '#fff' }}
15
+ >{`{{${field.path}}}`}</div>
17
16
  </span>
18
17
  );
19
18
  };
@@ -97,6 +97,29 @@ export const PdfViewFillable: FC<PdfViewFillableProps> = ({
97
97
  );
98
98
  }
99
99
 
100
+ if (field.subType === 'number') {
101
+ return (
102
+ <input
103
+ type="number"
104
+ style={{
105
+ background: 'inherit',
106
+ width: 'inherit',
107
+ height: 'inherit',
108
+ }}
109
+ disabled={!isViewByCurrentRecipient}
110
+ id={field.path}
111
+ name={field.path}
112
+ value={resolvedValue ?? ''}
113
+ onChange={e =>
114
+ onDataChange?.({
115
+ [field.path!]: e.target.value,
116
+ })
117
+ }
118
+ placeholder={getFieldPlaceholderText(field)}
119
+ />
120
+ );
121
+ }
122
+
100
123
  return (
101
124
  <input
102
125
  type="text"
@@ -79,7 +79,7 @@ export const PdfView: FC<PdfViewProps> = ({
79
79
  <PdfViewDataModel field={field} data={data} />
80
80
  )}
81
81
  {field.type === FieldTypeEnum.eSign && (
82
- <PdfViewESign field={field} data={data} />
82
+ <PdfViewESign field={field} />
83
83
  )}
84
84
  {field.type === FieldTypeEnum.fillable && (
85
85
  <PdfViewFillable
@@ -30,6 +30,7 @@ export const E_SIGN_FIELD_TYPE_OPTIONS: { name: string; id: ESignFieldType }[] =
30
30
 
31
31
  export const FILLABLE_FIELD_TYPES: FieldTypeOption[] = [
32
32
  { label: 'Text Field', type: FieldTypeEnum.fillable, subType: 'text' },
33
+ { label: 'Number Field', type: FieldTypeEnum.fillable, subType: 'number' },
33
34
  { label: 'Date Field', type: FieldTypeEnum.fillable, subType: 'date' },
34
35
  { label: 'Checkbox', type: FieldTypeEnum.fillable, subType: 'checkbox' },
35
36
  { label: 'Radio', type: FieldTypeEnum.fillable, subType: 'radio' },
@@ -2,3 +2,6 @@ export * from './useFieldDrag';
2
2
  export * from './useFieldResize';
3
3
  export * from './useInitializePdfJsWorker';
4
4
  export * from './usePdfDocumentRenderer';
5
+ export * from './usePdfFieldDnD';
6
+ export * from './usePdfFieldSelection';
7
+ export * from './useToggle';
@@ -0,0 +1,118 @@
1
+ import { DragEvent, RefObject, useState } from 'react';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { FIELD_CONSTANTS, FILLABLE_FIELD_DEFAULT_SIZES } from '../constants';
4
+ import {
5
+ ESignFieldType,
6
+ FieldTypeEnum,
7
+ FieldTypeOption,
8
+ PdfField,
9
+ RecipientInfo,
10
+ } from '../interface/types';
11
+ import {
12
+ calculateDropCoordinates,
13
+ generateESignPath,
14
+ generateFillablePath,
15
+ isDragOverCanvas,
16
+ } from '../utils';
17
+
18
+ interface UsePdfFieldDnDProps {
19
+ fields: PdfField[];
20
+ recipients: RecipientInfo[];
21
+ onFieldsChange: (fields: PdfField[]) => void;
22
+ pdfWrapperRef: RefObject<HTMLDivElement>;
23
+ onSelectField: (id: string) => void;
24
+ }
25
+
26
+ export const usePdfFieldDnD = ({
27
+ fields,
28
+ onFieldsChange,
29
+ onSelectField,
30
+ pdfWrapperRef,
31
+ recipients,
32
+ }: UsePdfFieldDnDProps) => {
33
+ const [draggedFieldOption, setDraggedFieldOption] = useState<FieldTypeOption | null>(null);
34
+
35
+ const handleDragStart = (fieldOption: FieldTypeOption) => {
36
+ setDraggedFieldOption(fieldOption);
37
+ };
38
+
39
+ const handleDragEnd = () => {
40
+ setDraggedFieldOption(null);
41
+ };
42
+
43
+ const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
44
+ if (!draggedFieldOption) {
45
+ e.dataTransfer.dropEffect = 'none';
46
+ return;
47
+ }
48
+
49
+ if (isDragOverCanvas(e, pdfWrapperRef)) {
50
+ e.preventDefault();
51
+ e.stopPropagation();
52
+ e.dataTransfer.dropEffect = 'copy';
53
+ } else {
54
+ e.dataTransfer.dropEffect = 'none';
55
+ }
56
+ };
57
+
58
+ const handleDrop = (e: DragEvent<HTMLDivElement>, pageNumber: number) => {
59
+ e.preventDefault();
60
+ e.stopPropagation();
61
+
62
+ if (e.dataTransfer.getData('text/plain') === 'existing-field' || !draggedFieldOption) {
63
+ return;
64
+ }
65
+
66
+ const coordinates = calculateDropCoordinates(e, pageNumber, pdfWrapperRef);
67
+ if (!coordinates) {
68
+ setDraggedFieldOption(null);
69
+ return;
70
+ }
71
+
72
+ const newField: PdfField = {
73
+ id: uuidv4(),
74
+ type: draggedFieldOption.type,
75
+ subType: draggedFieldOption.subType,
76
+ x: coordinates.x,
77
+ y: coordinates.y,
78
+ page: pageNumber,
79
+ label: draggedFieldOption.label,
80
+ width: FIELD_CONSTANTS.defaultWidth,
81
+ height: FIELD_CONSTANTS.defaultHeight,
82
+ path: draggedFieldOption.path,
83
+ };
84
+
85
+ const recipientName = recipients[0]?.name ?? '';
86
+
87
+ if (newField.type === FieldTypeEnum.eSign) {
88
+ newField.recipient = recipientName;
89
+ newField.path = generateESignPath(
90
+ recipientName,
91
+ draggedFieldOption.subType as ESignFieldType,
92
+ );
93
+ }
94
+
95
+ if (newField.type === FieldTypeEnum.fillable) {
96
+ const defaultSize = FILLABLE_FIELD_DEFAULT_SIZES[newField.subType!];
97
+ newField.recipient = recipientName;
98
+ newField.required = false;
99
+ newField.path = generateFillablePath(recipientName, uuidv4());
100
+
101
+ if (defaultSize) {
102
+ newField.width = defaultSize.width;
103
+ newField.height = defaultSize.height;
104
+ }
105
+ }
106
+
107
+ onFieldsChange([...fields, newField]);
108
+ onSelectField(newField.id);
109
+ setDraggedFieldOption(null);
110
+ };
111
+
112
+ return {
113
+ handleDragStart,
114
+ handleDragEnd,
115
+ handleDragOver,
116
+ handleDrop,
117
+ };
118
+ };
@@ -0,0 +1,55 @@
1
+ import { MouseEvent, useMemo, useState } from 'react';
2
+ import { PdfField } from '../interface/types';
3
+
4
+ export const usePdfFieldSelection = (
5
+ fields: PdfField[],
6
+ onFieldsChange: (fields: PdfField[]) => void,
7
+ ) => {
8
+ const [selectedFieldId, setSelectedFieldId] = useState<string | null>(null);
9
+
10
+ const selectedField = useMemo(
11
+ () => fields.find(f => f.id === selectedFieldId) ?? null,
12
+ [fields, selectedFieldId],
13
+ );
14
+
15
+ const selectField = (fieldId: string, e?: MouseEvent) => {
16
+ e?.stopPropagation();
17
+ setSelectedFieldId(fieldId);
18
+ };
19
+
20
+ const deselectField = () => setSelectedFieldId(null);
21
+
22
+ const updateField = (updates: Partial<PdfField>) => {
23
+ if (!selectedFieldId) {
24
+ return;
25
+ }
26
+ onFieldsChange(fields.map(f => (f.id === selectedFieldId ? { ...f, ...updates } : f)));
27
+ };
28
+
29
+ const deleteSelectedField = () => {
30
+ if (!selectedFieldId) {
31
+ return;
32
+ }
33
+ onFieldsChange(fields.filter(f => f.id !== selectedFieldId));
34
+ setSelectedFieldId(null);
35
+ };
36
+
37
+ const moveField = (id: string, x: number, y: number, page: number) => {
38
+ onFieldsChange(fields.map(f => (f.id === id ? { ...f, x, y, page } : f)));
39
+ };
40
+
41
+ const resizeField = (id: string, width: number, height: number, page: number) => {
42
+ onFieldsChange(fields.map(f => (f.id === id ? { ...f, width, height, page } : f)));
43
+ };
44
+
45
+ return {
46
+ selectedField,
47
+ selectField,
48
+ deselectField,
49
+ updateField,
50
+ deleteSelectedField,
51
+ moveField,
52
+ resizeField,
53
+ setSelectedFieldId,
54
+ };
55
+ };
@@ -0,0 +1,26 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ export interface UseToggleReturn {
4
+ isOpen: boolean;
5
+ toggle: () => void;
6
+ open: () => void;
7
+ close: () => void;
8
+ }
9
+
10
+ export const useToggle = (initialValue: boolean = false): UseToggleReturn => {
11
+ const [isOpen, setIsOpen] = useState<boolean>(initialValue);
12
+
13
+ const toggle = useCallback(() => {
14
+ setIsOpen(v => !v);
15
+ }, []);
16
+
17
+ const open = useCallback(() => {
18
+ setIsOpen(true);
19
+ }, []);
20
+
21
+ const close = useCallback(() => {
22
+ setIsOpen(false);
23
+ }, []);
24
+
25
+ return { isOpen, toggle, open, close };
26
+ };
@@ -11,7 +11,7 @@ export enum ESignFieldType {
11
11
  fullName = 'fullName',
12
12
  }
13
13
 
14
- export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'radio';
14
+ export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'radio' | 'number';
15
15
 
16
16
  export interface PdfField {
17
17
  id: string;
@@ -26,6 +26,7 @@ export interface PdfField {
26
26
  required?: boolean;
27
27
  path?: string;
28
28
  recipient?: string;
29
+ description?: string;
29
30
  }
30
31
 
31
32
  export interface FieldTypeOption {
@@ -12,7 +12,9 @@
12
12
  top: 0;
13
13
  left: 0;
14
14
  bottom: 0;
15
- width: 100%;
15
+ width: calc(
16
+ var(--layout-sidebar-content-width, 305px) + var(--layout-sidebar-menu-width, 85px) + 1px
17
+ );
16
18
  border-right: 1px solid var(--border-color);
17
19
  background-color: var(--white);
18
20
  z-index: 1000;
@@ -3,27 +3,60 @@
3
3
  }
4
4
 
5
5
  .dte-pdf-editor .dte-field-sidebar-menu {
6
- width: 85px;
6
+ width: var(--layout-sidebar-menu-width, 85px);
7
7
  padding: var(--spacing-2) var(--spacing-half);
8
8
  }
9
9
 
10
10
  .dte-field-sidebar-menu-item {
11
+ --sub-item-color: unset;
11
12
  cursor: pointer;
12
13
  }
13
14
 
15
+ .dte-field-sidebar-menu-item:hover {
16
+ --sub-item-color: var(--menu-hover-color);
17
+ }
18
+
19
+ .dte-field-sidebar-menu-item.--active {
20
+ --sub-item-color: var(--menu-active-color);
21
+ }
22
+
14
23
  .dte-field-sidebar-menu-item-text {
15
24
  text-align: center;
16
25
  }
17
26
 
27
+ .dte-field-sidebar-menu-item-text,
28
+ .dte-field-sidebar-menu-item-icon {
29
+ transition: color 0.2s ease-out;
30
+ color: var(--sub-item-color);
31
+ }
32
+
33
+ .dte-field-sidebar-toggler {
34
+ position: absolute;
35
+ top: 0;
36
+ right: -5px;
37
+ cursor: pointer;
38
+ background: transparent;
39
+ }
40
+
18
41
  .dte-field-sidebar-content {
19
42
  border-left: 1px solid var(--border-color);
20
43
  border-right: 1px solid var(--border-color);
21
- padding: var(--spacing-2);
22
44
  position: relative;
45
+ width: 0;
46
+ transition: width 0.3s ease-out;
47
+ }
48
+
49
+ .dte-field-sidebar-content-inner {
50
+ width: var(--layout-sidebar-content-width, 305px);
51
+ min-width: var(--layout-sidebar-content-width, 305px);
23
52
  display: flex;
53
+ padding: var(--spacing-2);
24
54
  flex-direction: column;
25
55
  overflow-y: auto;
26
- width: 305px;
56
+ }
57
+
58
+ .dte-field-sidebar-content.--open {
59
+ width: var(--layout-sidebar-content-width, 305px);
27
60
  }
28
61
 
29
62
  .dte-field-sidebar-search {
@@ -1,5 +1,5 @@
1
1
  .dte-field-type-group {
2
- margin-top: 0.25rem;
2
+ margin-top: var(--spacing-1);
3
3
  }
4
4
 
5
5
  .dte-field-type-group-header {
@@ -4,7 +4,14 @@
4
4
  gap: 8px;
5
5
  padding: 12px;
6
6
  background-color: var(--color-neutral-0);
7
+ border: 1px solid var(--border-color);
7
8
  border-radius: 0.375rem;
8
9
  cursor: grab;
9
10
  user-select: none;
11
+ margin-top: var(--spacing-1, 8px);
12
+ transition: box-shadow 0.2s ease-in-out;
13
+ }
14
+
15
+ .dte-field-type-item:hover {
16
+ box-shadow: 0 0 2px 0 var(--border-color-active);
10
17
  }