@servicetitan/dte-pdf-editor 1.91.0 → 1.92.1

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 (140) hide show
  1. package/dist/components/field-sidebar/calculated-field-type-list.d.ts.map +1 -1
  2. package/dist/components/field-sidebar/calculated-field-type-list.js +1 -1
  3. package/dist/components/field-sidebar/calculated-field-type-list.js.map +1 -1
  4. package/dist/components/field-sidebar/data-model-field-type-list.d.ts +1 -12
  5. package/dist/components/field-sidebar/data-model-field-type-list.d.ts.map +1 -1
  6. package/dist/components/field-sidebar/data-model-field-type-list.js +9 -29
  7. package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
  8. package/dist/components/field-sidebar/e-sign-field-type-list.d.ts.map +1 -1
  9. package/dist/components/field-sidebar/e-sign-field-type-list.js +1 -1
  10. package/dist/components/field-sidebar/e-sign-field-type-list.js.map +1 -1
  11. package/dist/components/field-sidebar/field-sidebar.d.ts +0 -5
  12. package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
  13. package/dist/components/field-sidebar/field-sidebar.js +10 -7
  14. package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
  15. package/dist/components/field-sidebar/field-type.d.ts +2 -0
  16. package/dist/components/field-sidebar/field-type.d.ts.map +1 -1
  17. package/dist/components/field-sidebar/field-type.js +2 -2
  18. package/dist/components/field-sidebar/field-type.js.map +1 -1
  19. package/dist/components/field-sidebar/fillable-field-type-list.d.ts.map +1 -1
  20. package/dist/components/field-sidebar/fillable-field-type-list.js +1 -1
  21. package/dist/components/field-sidebar/fillable-field-type-list.js.map +1 -1
  22. package/dist/components/field-sidebar/form-fields-type-list.d.ts +5 -23
  23. package/dist/components/field-sidebar/form-fields-type-list.d.ts.map +1 -1
  24. package/dist/components/field-sidebar/form-fields-type-list.js +5 -20
  25. package/dist/components/field-sidebar/form-fields-type-list.js.map +1 -1
  26. package/dist/components/field-sidebar/generic-field-type-list.d.ts.map +1 -1
  27. package/dist/components/field-sidebar/generic-field-type-list.js +1 -1
  28. package/dist/components/field-sidebar/generic-field-type-list.js.map +1 -1
  29. package/dist/components/pdf-canvas/canvas-controls.d.ts +9 -15
  30. package/dist/components/pdf-canvas/canvas-controls.d.ts.map +1 -1
  31. package/dist/components/pdf-canvas/canvas-controls.js +4 -14
  32. package/dist/components/pdf-canvas/canvas-controls.js.map +1 -1
  33. package/dist/components/pdf-canvas/pdf-canvas.d.ts +6 -2
  34. package/dist/components/pdf-canvas/pdf-canvas.d.ts.map +1 -1
  35. package/dist/components/pdf-canvas/pdf-canvas.js +11 -5
  36. package/dist/components/pdf-canvas/pdf-canvas.js.map +1 -1
  37. package/dist/components/pdf-editor/pdf-editor.d.ts +2 -0
  38. package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
  39. package/dist/components/pdf-editor/pdf-editor.js +2 -2
  40. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  41. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +4 -1
  42. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
  43. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.d.ts.map +1 -1
  44. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js +3 -2
  45. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js.map +1 -1
  46. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  47. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +4 -2
  48. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  49. package/dist/components/pdf-view/pdf-view-e-sign.d.ts.map +1 -1
  50. package/dist/components/pdf-view/pdf-view-e-sign.js +3 -2
  51. package/dist/components/pdf-view/pdf-view-e-sign.js.map +1 -1
  52. package/dist/components/pdf-view/pdf-view-field-container.d.ts.map +1 -1
  53. package/dist/components/pdf-view/pdf-view-field-container.js +10 -3
  54. package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
  55. package/dist/components/pdf-view/pdf-view-fillable.js +1 -1
  56. package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
  57. package/dist/components/pdf-view/pdf-view.d.ts +5 -0
  58. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  59. package/dist/components/pdf-view/pdf-view.js +51 -24
  60. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  61. package/dist/constants/field.constants.d.ts.map +1 -1
  62. package/dist/constants/field.constants.js +27 -6
  63. package/dist/constants/field.constants.js.map +1 -1
  64. package/dist/constants/menu-group.d.ts +0 -5
  65. package/dist/constants/menu-group.d.ts.map +1 -1
  66. package/dist/constants/menu-group.js +2 -5
  67. package/dist/constants/menu-group.js.map +1 -1
  68. package/dist/hooks/index.d.ts +1 -0
  69. package/dist/hooks/index.d.ts.map +1 -1
  70. package/dist/hooks/index.js +1 -0
  71. package/dist/hooks/index.js.map +1 -1
  72. package/dist/hooks/usePinchZoom.d.ts +14 -0
  73. package/dist/hooks/usePinchZoom.d.ts.map +1 -0
  74. package/dist/hooks/usePinchZoom.js +58 -0
  75. package/dist/hooks/usePinchZoom.js.map +1 -0
  76. package/dist/hooks/useZoom.d.ts +2 -0
  77. package/dist/hooks/useZoom.d.ts.map +1 -1
  78. package/dist/hooks/useZoom.js +18 -6
  79. package/dist/hooks/useZoom.js.map +1 -1
  80. package/dist/interface/types.d.ts +2 -0
  81. package/dist/interface/types.d.ts.map +1 -1
  82. package/dist/interface/types.js.map +1 -1
  83. package/dist/styles/pdf-canvas.css +91 -15
  84. package/dist/styles/pdf-editor.css +1 -0
  85. package/dist/styles/pdf-field-overlay.css +109 -78
  86. package/dist/styles/variables.css +3 -0
  87. package/dist/utils/field/get-field-type-option.utils.d.ts +3 -0
  88. package/dist/utils/field/get-field-type-option.utils.d.ts.map +1 -0
  89. package/dist/utils/field/get-field-type-option.utils.js +10 -0
  90. package/dist/utils/field/get-field-type-option.utils.js.map +1 -0
  91. package/dist/utils/field/get-navigable-fields.utils.js +1 -1
  92. package/dist/utils/field/get-navigable-fields.utils.js.map +1 -1
  93. package/dist/utils/field/index.d.ts +1 -0
  94. package/dist/utils/field/index.d.ts.map +1 -1
  95. package/dist/utils/field/index.js +1 -0
  96. package/dist/utils/field/index.js.map +1 -1
  97. package/dist/utils/pdf/index.d.ts +1 -0
  98. package/dist/utils/pdf/index.d.ts.map +1 -1
  99. package/dist/utils/pdf/index.js +1 -0
  100. package/dist/utils/pdf/index.js.map +1 -1
  101. package/dist/utils/pdf/pinch-zoom.utils.d.ts +6 -0
  102. package/dist/utils/pdf/pinch-zoom.utils.d.ts.map +1 -0
  103. package/dist/utils/pdf/pinch-zoom.utils.js +18 -0
  104. package/dist/utils/pdf/pinch-zoom.utils.js.map +1 -0
  105. package/package.json +1 -1
  106. package/src/components/field-sidebar/calculated-field-type-list.tsx +1 -0
  107. package/src/components/field-sidebar/data-model-field-type-list.tsx +28 -103
  108. package/src/components/field-sidebar/e-sign-field-type-list.tsx +1 -0
  109. package/src/components/field-sidebar/field-sidebar.tsx +18 -15
  110. package/src/components/field-sidebar/field-type.tsx +13 -3
  111. package/src/components/field-sidebar/fillable-field-type-list.tsx +1 -0
  112. package/src/components/field-sidebar/form-fields-type-list.tsx +94 -109
  113. package/src/components/field-sidebar/generic-field-type-list.tsx +1 -0
  114. package/src/components/pdf-canvas/canvas-controls.tsx +30 -128
  115. package/src/components/pdf-canvas/pdf-canvas.tsx +18 -8
  116. package/src/components/pdf-editor/pdf-editor.tsx +4 -1
  117. package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +1 -1
  118. package/src/components/pdf-fields-overlay/pdf-overlay-field-e-sign.tsx +8 -6
  119. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +17 -1
  120. package/src/components/pdf-view/pdf-view-e-sign.tsx +8 -6
  121. package/src/components/pdf-view/pdf-view-field-container.tsx +21 -1
  122. package/src/components/pdf-view/pdf-view-fillable.tsx +1 -1
  123. package/src/components/pdf-view/pdf-view.tsx +156 -95
  124. package/src/constants/field.constants.ts +28 -6
  125. package/src/constants/menu-group.ts +2 -5
  126. package/src/hooks/index.ts +1 -0
  127. package/src/hooks/usePinchZoom.ts +86 -0
  128. package/src/hooks/useZoom.ts +20 -8
  129. package/src/interface/types.ts +3 -0
  130. package/src/styles/pdf-canvas.css +91 -15
  131. package/src/styles/pdf-editor.css +1 -0
  132. package/src/styles/pdf-field-overlay.css +109 -78
  133. package/src/styles/variables.css +3 -0
  134. package/src/utils/field/__tests__/get-field-type-option.utils.test.ts +53 -0
  135. package/src/utils/field/get-field-type-option.utils.ts +16 -0
  136. package/src/utils/field/get-navigable-fields.utils.ts +1 -1
  137. package/src/utils/field/index.ts +1 -0
  138. package/src/utils/pdf/__tests__/pinch-zoom.utils.test.ts +58 -0
  139. package/src/utils/pdf/index.ts +1 -0
  140. package/src/utils/pdf/pinch-zoom.utils.ts +30 -0
@@ -14,15 +14,11 @@ import { DataModelFieldTypeList } from './data-model-field-type-list';
14
14
  import { ESignFieldTypeList } from './e-sign-field-type-list';
15
15
  import { FieldMenuGroup } from './field-menu-group';
16
16
  import { FillableFieldTypeList } from './fillable-field-type-list';
17
+ import { FormFieldsTypeList } from './form-fields-type-list';
17
18
  import { GenericFieldTypeList } from './generic-field-type-list';
18
19
 
19
20
  interface FieldSidebarProps {
20
21
  dataModelGroups?: DataModelFieldGroup[];
21
- /**
22
- * Forms listed as a group inside the Merge Tags tab (alongside the data model groups).
23
- * Hidden when empty, when `onSelectForm` is missing, or when `hideFields` names
24
- * `FieldTypeEnum.forms`.
25
- */
26
22
  forms?: FormInfo[];
27
23
  formFieldOptions?: FieldTypeOption[];
28
24
  hideFields?: FieldTypeEnum[];
@@ -53,14 +49,15 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
53
49
  };
54
50
 
55
51
  const filteredMenuGroups = useMemo(() => {
56
- if (!hideFields?.length) {
57
- return menuGroups;
52
+ let groups = menuGroups;
53
+ if (hideFields?.length) {
54
+ groups = groups.filter(menuGroup => !hideFields.includes(menuGroup.key));
58
55
  }
59
- return menuGroups.filter(menuGroup => !hideFields.includes(menuGroup.key));
60
- }, [hideFields]);
61
-
62
- // The list itself hides the group when there are no forms or no selection handler.
63
- const visibleForms = hideFields?.includes(FieldTypeEnum.forms) ? undefined : forms;
56
+ if (!forms?.length || !onSelectForm) {
57
+ groups = groups.filter(menuGroup => menuGroup.key !== FieldTypeEnum.forms);
58
+ }
59
+ return groups;
60
+ }, [forms?.length, hideFields, onSelectForm]);
64
61
 
65
62
  return (
66
63
  <Flex className="dte-field-sidebar-container skeleton-item">
@@ -88,11 +85,17 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
88
85
  {activeFieldType === FieldTypeEnum.dataModel && (
89
86
  <DataModelFieldTypeList
90
87
  dataModelGroups={dataModelGroups}
91
- forms={visibleForms}
92
- formFieldOptions={formFieldOptions}
93
- isLoadingFormFields={isLoadingFormFields}
94
88
  onDragStart={onDragStart}
95
89
  onDragEnd={onDragEnd}
90
+ />
91
+ )}
92
+ {activeFieldType === FieldTypeEnum.forms && forms && onSelectForm && (
93
+ <FormFieldsTypeList
94
+ fieldOptions={formFieldOptions}
95
+ forms={forms}
96
+ isLoadingFormFields={isLoadingFormFields}
97
+ onDragEnd={onDragEnd}
98
+ onDragStart={onDragStart}
96
99
  onSelectForm={onSelectForm}
97
100
  selectedForm={selectedForm}
98
101
  />
@@ -1,15 +1,22 @@
1
- import { Flex, Icon, Text } from '@servicetitan/anvil2';
1
+ import { Flex, Icon, Svg, Text } from '@servicetitan/anvil2';
2
2
  import IconDragIndicator from '@servicetitan/anvil2/assets/icons/material/round/drag_indicator.svg';
3
3
  import { FC } from 'react';
4
4
 
5
5
  interface FieldTypeProps {
6
+ icon?: Svg;
6
7
  label: string;
7
8
  sampleData?: string | number | null;
8
9
  onDragEnd(): void;
9
10
  onDragStart(): void;
10
11
  }
11
12
 
12
- export const FieldType: FC<FieldTypeProps> = ({ label, onDragEnd, onDragStart, sampleData }) => {
13
+ export const FieldType: FC<FieldTypeProps> = ({
14
+ icon,
15
+ label,
16
+ onDragEnd,
17
+ onDragStart,
18
+ sampleData,
19
+ }) => {
13
20
  return (
14
21
  <div
15
22
  draggable
@@ -24,7 +31,10 @@ export const FieldType: FC<FieldTypeProps> = ({ label, onDragEnd, onDragStart, s
24
31
  >
25
32
  <Icon svg={IconDragIndicator} size="medium" />
26
33
  <Flex direction="column" style={{ overflow: 'hidden' }}>
27
- <Text size="small">{label}</Text>
34
+ <Flex alignItems="center" gap="2">
35
+ {icon && <Icon svg={icon} size="medium" />}
36
+ <Text size="small">{label}</Text>
37
+ </Flex>
28
38
  {sampleData && (
29
39
  <Text
30
40
  size="small"
@@ -37,6 +37,7 @@ export const FillableFieldTypeList: FC<FillableFieldTypeListProps> = ({
37
37
  {searchedFieldTypes.map(fieldOption => {
38
38
  return (
39
39
  <FieldType
40
+ icon={fieldOption.icon}
40
41
  key={fieldOption.subType}
41
42
  label={fieldOption.label}
42
43
  onDragEnd={onDragEnd}
@@ -6,127 +6,112 @@ import { type FieldTypeOption, type FormInfo } from '../../interface/types';
6
6
  import { getFieldSampleData } from '../../utils';
7
7
  import { FieldType } from './field-type';
8
8
 
9
- /** Heading of the Forms group inside the Merge Tags tab. */
10
- export const FORMS_GROUP_NAME = 'Forms';
11
-
12
- export interface FormsListProps {
9
+ export interface FormFieldsTypeListProps {
13
10
  forms: FormInfo[];
14
- /** Rendered when the (possibly search-filtered) list is empty. */
15
- emptyText?: string;
16
- onSelectForm(form: FormInfo): void;
17
- }
18
-
19
- /**
20
- * Root level of the Forms group: one clickable row per form. Selecting a form hands off to
21
- * `FormFieldsPanel`, which lists that form's draggable fields.
22
- */
23
- export const FormsList: FC<FormsListProps> = ({
24
- emptyText = 'No forms configured.',
25
- forms,
26
- onSelectForm,
27
- }) => {
28
- return (
29
- <div className="dte-field-type-group">
30
- <Flex alignItems="center" gap={1} className="dte-field-type-group-header">
31
- <Text variant="headline" el="h6" size="small">
32
- {FORMS_GROUP_NAME}
33
- </Text>
34
- </Flex>
35
- {forms.length > 0 ? (
36
- <ul className="dte-formula-field-list" role="listbox" aria-label={FORMS_GROUP_NAME}>
37
- {forms.map(form => (
38
- <li
39
- key={form.id}
40
- role="option"
41
- className="dte-formula-field-list-item"
42
- onMouseDown={e => {
43
- e.preventDefault();
44
- onSelectForm(form);
45
- }}
46
- >
47
- <Flex justifyContent="space-between" alignItems="center">
48
- <span className="dte-text-ellipsis" title={form.name}>
49
- {form.name}
50
- </span>
51
- <IconArrowForward />
52
- </Flex>
53
- </li>
54
- ))}
55
- </ul>
56
- ) : (
57
- <Text size="small" subdued variant="body">
58
- {emptyText}
59
- </Text>
60
- )}
61
- </div>
62
- );
63
- };
64
-
65
- export interface FormFieldsPanelProps {
66
- form: FormInfo;
11
+ selectedForm: FormInfo | null;
67
12
  fieldOptions: FieldTypeOption[];
68
- isLoading?: boolean;
69
- /** Rendered when the form has fields but none match the current search. */
70
- hasHiddenFields?: boolean;
71
- onBack(): void;
13
+ isLoadingFormFields?: boolean;
14
+ onSelectForm(form: FormInfo | null): void;
72
15
  onDragEnd(): void;
73
16
  onDragStart(fieldOption: FieldTypeOption): void;
74
17
  }
75
18
 
76
- /**
77
- * Detail level of the Forms group: a back button naming the form, then its fields as the same
78
- * draggable rows the data model groups use, so a form field drops onto the PDF like any merge tag.
79
- */
80
- export const FormFieldsPanel: FC<FormFieldsPanelProps> = ({
19
+ export const FormFieldsTypeList: FC<FormFieldsTypeListProps> = ({
81
20
  fieldOptions,
82
- form,
83
- hasHiddenFields = false,
84
- isLoading = false,
85
- onBack,
21
+ forms,
22
+ isLoadingFormFields = false,
86
23
  onDragEnd,
87
24
  onDragStart,
25
+ onSelectForm,
26
+ selectedForm,
88
27
  }) => {
89
28
  return (
90
- <Fragment>
91
- <Button
92
- style={{ width: '100%' }}
93
- appearance="ghost"
94
- size="small"
95
- icon={IconArrowBack}
96
- title={form.name}
97
- onClick={onBack}
98
- >
99
- <Text
100
- size="medium"
101
- className="dte-text-ellipsis"
102
- style={{ fontWeight: 'bold', width: '100%' }}
29
+ <Flex direction="column" gap={3} flex={1} style={{ minHeight: 0 }}>
30
+ <div className="dte-formula-sidebar-slide-outer">
31
+ <div
32
+ className={`dte-formula-sidebar-slide-track${selectedForm ? ' --detail' : ''}`}
103
33
  >
104
- {form.name}
105
- </Text>
106
- </Button>
107
- <div className="dte-field-type-group">
108
- {isLoading ? (
109
- <Text size="small" subdued variant="body">
110
- Loading fields…
111
- </Text>
112
- ) : fieldOptions.length === 0 ? (
113
- <Text size="small" subdued variant="body">
114
- {hasHiddenFields
115
- ? 'No fields match the search.'
116
- : 'No fields on this form.'}
117
- </Text>
118
- ) : (
119
- fieldOptions.map(option => (
120
- <FieldType
121
- key={option.path}
122
- label={option.label}
123
- sampleData={getFieldSampleData(option.sampleData, option.fieldType)}
124
- onDragEnd={onDragEnd}
125
- onDragStart={() => onDragStart(option)}
126
- />
127
- ))
128
- )}
34
+ <div className="dte-formula-sidebar-slide-panel">
35
+ {forms.length > 0 ? (
36
+ <ul
37
+ className="dte-formula-field-list"
38
+ role="listbox"
39
+ aria-label="Forms"
40
+ >
41
+ {forms.map(form => (
42
+ <li
43
+ key={form.id}
44
+ role="option"
45
+ className="dte-formula-field-list-item"
46
+ onMouseDown={e => {
47
+ e.preventDefault();
48
+ onSelectForm(form);
49
+ }}
50
+ >
51
+ <Flex justifyContent="space-between" alignItems="center">
52
+ <span className="dte-text-ellipsis" title={form.name}>
53
+ {form.name}
54
+ </span>
55
+ <IconArrowForward />
56
+ </Flex>
57
+ </li>
58
+ ))}
59
+ </ul>
60
+ ) : (
61
+ <Text size="small" subdued variant="body">
62
+ No forms configured.
63
+ </Text>
64
+ )}
65
+ </div>
66
+
67
+ <div className="dte-formula-sidebar-slide-panel --form-fields">
68
+ {selectedForm ? (
69
+ <Fragment>
70
+ <Button
71
+ style={{ width: '100%' }}
72
+ appearance="ghost"
73
+ size="small"
74
+ icon={IconArrowBack}
75
+ title={selectedForm.name}
76
+ onClick={() => onSelectForm(null)}
77
+ >
78
+ <Text
79
+ size="medium"
80
+ className="dte-text-ellipsis"
81
+ style={{ fontWeight: 'bold', width: '100%' }}
82
+ >
83
+ {selectedForm.name}
84
+ </Text>
85
+ </Button>
86
+ <div className="dte-field-type-group">
87
+ {isLoadingFormFields ? (
88
+ <Text size="small" subdued variant="body">
89
+ Loading fields…
90
+ </Text>
91
+ ) : fieldOptions.length === 0 ? (
92
+ <Text size="small" subdued variant="body">
93
+ No fields on this form.
94
+ </Text>
95
+ ) : (
96
+ fieldOptions.map(option => (
97
+ <FieldType
98
+ key={option.path}
99
+ label={option.label}
100
+ sampleData={getFieldSampleData(
101
+ option.sampleData,
102
+ option.fieldType,
103
+ )}
104
+ onDragEnd={onDragEnd}
105
+ onDragStart={() => onDragStart(option)}
106
+ />
107
+ ))
108
+ )}
109
+ </div>
110
+ </Fragment>
111
+ ) : null}
112
+ </div>
113
+ </div>
129
114
  </div>
130
- </Fragment>
115
+ </Flex>
131
116
  );
132
117
  };
@@ -16,6 +16,7 @@ export const GenericFieldTypeList: FC<GenericFieldTypeListProps> = ({ onDragEnd,
16
16
  return (
17
17
  <FieldType
18
18
  key={key}
19
+ icon={fieldOption.icon}
19
20
  label={fieldOption.label}
20
21
  onDragEnd={onDragEnd}
21
22
  onDragStart={() => onDragStart(fieldOption)}
@@ -1,23 +1,32 @@
1
1
  import { Button, Divider, Flex, Icon, Text } from '@servicetitan/anvil2';
2
- import IconNavigateDown from '@servicetitan/anvil2/assets/icons/material/round/arrow_downward.svg';
3
- import IconNavigateUp from '@servicetitan/anvil2/assets/icons/material/round/arrow_upward.svg';
4
- import IconNavigateBefore from '@servicetitan/anvil2/assets/icons/material/round/navigate_before.svg';
5
- import IconNavigateNext from '@servicetitan/anvil2/assets/icons/material/round/navigate_next.svg';
6
2
  import IconZoomIn from '@servicetitan/anvil2/assets/icons/material/round/zoom_in.svg';
7
3
  import IconZoomOut from '@servicetitan/anvil2/assets/icons/material/round/zoom_out.svg';
8
4
  import { FC } from 'react';
9
5
  import { ZOOM_MAX, ZOOM_MIN } from '../../constants';
10
6
 
11
- interface ZoomControlsProps {
7
+ export interface ZoomControlsProps {
12
8
  zoom: number;
9
+ vertical?: boolean;
10
+ position?: 'right-center' | 'right-bottom';
13
11
  onZoomIn(): void;
14
12
  onZoomOut(): void;
15
13
  onResetZoom(): void;
16
14
  }
17
15
 
18
- const ZoomControls: FC<ZoomControlsProps> = ({ onResetZoom, onZoomIn, onZoomOut, zoom }) => {
16
+ export const ZoomControls: FC<ZoomControlsProps> = ({
17
+ onResetZoom,
18
+ onZoomIn,
19
+ onZoomOut,
20
+ position = 'right-center',
21
+ vertical,
22
+ zoom,
23
+ }) => {
19
24
  return (
20
- <Flex alignItems="center" gap={1} className="dte-pdf-zoom-controls">
25
+ <Flex
26
+ alignItems="center"
27
+ gap={1}
28
+ className={`dte-pdf-zoom-controls ${vertical ? '--vertical' : '--horizontal'} --${position}`}
29
+ >
21
30
  <Button
22
31
  size="xsmall"
23
32
  appearance="ghost"
@@ -28,7 +37,7 @@ const ZoomControls: FC<ZoomControlsProps> = ({ onResetZoom, onZoomIn, onZoomOut,
28
37
  >
29
38
  <Icon svg={IconZoomOut} size="medium" />
30
39
  </Button>
31
-
40
+ <Divider vertical={!vertical} style={vertical ? { width: 20 } : { height: 20 }} />
32
41
  <Text
33
42
  variant="body"
34
43
  size="small"
@@ -38,7 +47,7 @@ const ZoomControls: FC<ZoomControlsProps> = ({ onResetZoom, onZoomIn, onZoomOut,
38
47
  >
39
48
  {Math.round(zoom * 100)}%
40
49
  </Text>
41
-
50
+ <Divider vertical={!vertical} style={vertical ? { width: 20 } : { height: 20 }} />
42
51
  <Button
43
52
  size="xsmall"
44
53
  appearance="ghost"
@@ -54,130 +63,23 @@ const ZoomControls: FC<ZoomControlsProps> = ({ onResetZoom, onZoomIn, onZoomOut,
54
63
  };
55
64
 
56
65
  interface FieldNavigationControlsProps {
57
- disabledNext: boolean;
58
- disabledOnFirstField: boolean;
59
- disabledOnLastField: boolean;
60
- disabledPrev: boolean;
66
+ isStarted: boolean;
67
+ disabled?: boolean;
61
68
  onNextField(): void;
62
- onPrevField(): void;
63
- onFirstField(): void;
64
- onLastField(): void;
65
69
  }
66
70
 
67
- const FieldNavigationControls: FC<FieldNavigationControlsProps> = ({
68
- disabledNext,
69
- disabledOnFirstField,
70
- disabledOnLastField,
71
- disabledPrev,
72
- onFirstField,
73
- onLastField,
71
+ export const FieldNavigationControls: FC<FieldNavigationControlsProps> = ({
72
+ disabled,
73
+ isStarted,
74
74
  onNextField,
75
- onPrevField,
76
75
  }) => {
77
76
  return (
78
- <Flex alignItems="center" gap="2" className="dte-pdf-field-navigation-controls">
79
- <Button
80
- size="xsmall"
81
- appearance="ghost"
82
- onClick={onPrevField}
83
- disabled={disabledPrev}
84
- aria-label="Go to previous field"
85
- title="Go to previous field"
86
- >
87
- <Icon svg={IconNavigateBefore} size="medium" />
88
- </Button>
89
- <Button
90
- size="xsmall"
91
- appearance="ghost"
92
- onClick={onNextField}
93
- disabled={disabledNext}
94
- aria-label="Go to next field"
95
- title="Go to next field"
96
- >
97
- <Icon svg={IconNavigateNext} size="medium" />
98
- </Button>
99
- <Divider vertical style={{ height: '100%' }} />
100
- <Button
101
- size="xsmall"
102
- appearance="ghost"
103
- onClick={onFirstField}
104
- disabled={disabledOnFirstField}
105
- aria-label="Go to first field"
106
- title="Go to first field"
107
- >
108
- <Icon svg={IconNavigateUp} size="medium" />
109
- </Button>
110
- <Button
111
- size="xsmall"
112
- appearance="ghost"
113
- onClick={onLastField}
114
- disabled={disabledOnLastField}
115
- aria-label="Go to last field"
116
- title="Go to last field"
117
- >
118
- <Icon svg={IconNavigateDown} size="medium" />
119
- </Button>
120
- </Flex>
121
- );
122
- };
123
-
124
- export interface CanvasControlsProps extends ZoomControlsProps {
125
- /** Show the zoom in/out/reset controls. */
126
- zoomable?: boolean;
127
- /** Show the previous/next fillable-field navigation controls. */
128
- enableInputNavigation?: boolean;
129
- disabledNext?: boolean;
130
- disabledPrev?: boolean;
131
- disabledOnFirstField?: boolean;
132
- disabledOnLastField?: boolean;
133
- onNextField?(): void;
134
- onPrevField?(): void;
135
- onFirstField?(): void;
136
- onLastField?(): void;
137
- }
138
-
139
- export const CanvasControls: FC<CanvasControlsProps> = ({
140
- disabledNext = false,
141
- disabledOnFirstField = false,
142
- disabledOnLastField = false,
143
- disabledPrev = false,
144
- enableInputNavigation = false,
145
- onFirstField = () => {},
146
- onLastField = () => {},
147
- onNextField = () => {},
148
- onPrevField = () => {},
149
- onResetZoom,
150
- onZoomIn,
151
- onZoomOut,
152
- zoom,
153
- zoomable = false,
154
- }) => {
155
- if (!zoomable && !enableInputNavigation) {
156
- return null;
157
- }
158
-
159
- return (
160
- <Flex justifyContent="center" gap={2} className="dte-pdf-canvas-controls">
161
- {zoomable && (
162
- <ZoomControls
163
- zoom={zoom}
164
- onZoomIn={onZoomIn}
165
- onZoomOut={onZoomOut}
166
- onResetZoom={onResetZoom}
167
- />
168
- )}
169
- {enableInputNavigation && (
170
- <FieldNavigationControls
171
- disabledOnFirstField={disabledOnFirstField}
172
- disabledOnLastField={disabledOnLastField}
173
- onFirstField={onFirstField}
174
- onLastField={onLastField}
175
- disabledNext={disabledNext}
176
- disabledPrev={disabledPrev}
177
- onNextField={onNextField}
178
- onPrevField={onPrevField}
179
- />
180
- )}
181
- </Flex>
77
+ <div
78
+ className={`dte-pdf-field-next-controls-button ${disabled && '--disabled'} ${isStarted && '--started'}`}
79
+ role="button"
80
+ onClick={onNextField}
81
+ >
82
+ <span>{isStarted ? 'Next' : 'Start'}</span>
83
+ </div>
182
84
  );
183
85
  };
@@ -10,13 +10,13 @@ import {
10
10
  useState,
11
11
  } from 'react';
12
12
  import { BASE_PAGE_WIDTH } from '../../constants';
13
- import { useZoom } from '../../hooks';
13
+ import { usePinchZoom, useZoom } from '../../hooks';
14
14
  import { type PdfField } from '../../interface/types';
15
15
  import { PdfFieldsOverlay } from '../pdf-fields-overlay/pdf-fields-overlay';
16
- import { CanvasControls } from './canvas-controls';
16
+ import { ZoomControls, ZoomControlsProps } from './canvas-controls';
17
17
  import { PdfDocumentRenderer } from './pdf-document-renderer';
18
18
 
19
- interface PdfCanvasProps {
19
+ export interface PdfCanvasProps {
20
20
  pdfUrl: string;
21
21
  zoomable: boolean;
22
22
  errorPlaceholder?: ReactNode;
@@ -29,6 +29,10 @@ interface PdfCanvasProps {
29
29
  pdfWrapperRef: RefObject<HTMLDivElement>;
30
30
  recipientsColors: Record<string, string>;
31
31
  fieldWrapperClassNames?: string;
32
+ zoomProps?: {
33
+ vertical: ZoomControlsProps['vertical'];
34
+ position: ZoomControlsProps['position'];
35
+ };
32
36
  onLoadSuccess?(pageNums: number): void;
33
37
  handleAddNewField(field: PdfField): void;
34
38
  onDragOver(e: DragEvent<HTMLDivElement>): void;
@@ -61,10 +65,17 @@ export const PdfCanvas: FC<PdfCanvasProps> = ({
61
65
  pdfWrapperRef,
62
66
  recipientsColors,
63
67
  selectedField,
68
+ zoomProps,
64
69
  zoomable,
65
70
  }) => {
66
71
  const [isPdfLoaded, setIsPdfLoaded] = useState<boolean>(false);
67
- const { resetZoom, zoom, zoomIn, zoomOut } = useZoom();
72
+ const { resetZoom, setZoomValue, zoom, zoomByFactor, zoomIn, zoomOut } = useZoom();
73
+ usePinchZoom(pdfContainerRef, {
74
+ enabled: zoomable,
75
+ setZoomValue,
76
+ zoom,
77
+ zoomByFactor,
78
+ });
68
79
 
69
80
  const onDocumentLoadSuccess = (pageNum: number) => {
70
81
  setIsPdfLoaded(true);
@@ -77,10 +88,9 @@ export const PdfCanvas: FC<PdfCanvasProps> = ({
77
88
 
78
89
  return (
79
90
  <Fragment>
80
- {isPdfLoaded && (
81
- <CanvasControls
82
- enableInputNavigation={false}
83
- zoomable={zoomable}
91
+ {isPdfLoaded && zoomable && (
92
+ <ZoomControls
93
+ {...zoomProps}
84
94
  zoom={zoom}
85
95
  onZoomIn={zoomIn}
86
96
  onZoomOut={zoomOut}
@@ -12,7 +12,7 @@ import {
12
12
  import { extractGroupedFieldsFromDataModel, mapColorsToRecipients } from '../../utils';
13
13
  import { FieldConfigPanelOverlay } from '../field-config-panel/field-config-panel-overlay';
14
14
  import { FieldSidebar } from '../field-sidebar/field-sidebar';
15
- import { PdfCanvas } from '../pdf-canvas/pdf-canvas';
15
+ import { PdfCanvas, PdfCanvasProps } from '../pdf-canvas/pdf-canvas';
16
16
 
17
17
  import '../../styles/index.css';
18
18
 
@@ -29,6 +29,7 @@ export interface PdfEditorProps {
29
29
  hideFields?: FieldTypeEnum[];
30
30
  hideConditionalLogic?: boolean;
31
31
  forms?: FormInfo[];
32
+ zoomProps?: PdfCanvasProps['zoomProps'];
32
33
  onFormSelect?: (
33
34
  formIds: number[],
34
35
  sendFormFields: (formFieldsByFormId: FormFieldsByFormIdI) => void,
@@ -52,6 +53,7 @@ export const PdfEditor: FC<PdfEditorProps> = ({
52
53
  onLoadSuccess,
53
54
  pdfUrl,
54
55
  recipients = [],
56
+ zoomProps,
55
57
  zoomable = true,
56
58
  }) => {
57
59
  const pdfContainerRef = useRef<HTMLDivElement>(null);
@@ -121,6 +123,7 @@ export const PdfEditor: FC<PdfEditorProps> = ({
121
123
  className="dte-pdf-editor-content-container skeleton-item"
122
124
  >
123
125
  <PdfCanvas
126
+ zoomProps={zoomProps}
124
127
  zoomable={zoomable}
125
128
  pdfUrl={pdfUrl}
126
129
  fields={fields}
@@ -49,7 +49,7 @@ export const PdfFieldsOverlay: FC<PdfFieldsOverlayProps> = ({
49
49
  field={field}
50
50
  fieldWrapperClassNames={fieldWrapperClassNames}
51
51
  onFieldConfigChange={onFieldConfigChange}
52
- error={errors[field.id]}
52
+ error={errors[field.id] ?? errors[field.path ?? '']}
53
53
  handleAddNewField={handleAddNewField}
54
54
  onFieldMove={onFieldMove}
55
55
  onFieldClick={onFieldClick}
@@ -1,3 +1,4 @@
1
+ import { Flex } from '@servicetitan/anvil2';
1
2
  import { FC } from 'react';
2
3
  import { type PdfField } from '../../interface/types';
3
4
 
@@ -7,11 +8,12 @@ interface PdfOverlayFieldESignProps {
7
8
 
8
9
  export const PdfOverlayFieldESign: FC<PdfOverlayFieldESignProps> = ({ field }) => {
9
10
  return (
10
- <span>
11
- {field.label} {field.required ? '*' : ''}
12
- <div className="dte-pdf-field-value" style={{ fontSize: '8px' }}>
13
- {field.path}
14
- </div>
15
- </span>
11
+ <Flex
12
+ alignItems="center"
13
+ className="dte-pdf-field-value"
14
+ style={{ fontSize: '8px', height: '100%' }}
15
+ >
16
+ {field.path} {field.required ? '*' : ''}
17
+ </Flex>
16
18
  );
17
19
  };