@servicetitan/dte-pdf-editor 1.17.0 → 1.19.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 (203) hide show
  1. package/README.md +35 -7
  2. package/dist/components/field-config-panel/advanced-settings.d.ts +9 -0
  3. package/dist/components/field-config-panel/advanced-settings.d.ts.map +1 -0
  4. package/dist/components/field-config-panel/advanced-settings.js +17 -0
  5. package/dist/components/field-config-panel/advanced-settings.js.map +1 -0
  6. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts +4 -1
  7. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts.map +1 -1
  8. package/dist/components/field-config-panel/field-config-panel-overlay.js +2 -2
  9. package/dist/components/field-config-panel/field-config-panel-overlay.js.map +1 -1
  10. package/dist/components/field-config-panel/field-config-panel.d.ts +4 -1
  11. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  12. package/dist/components/field-config-panel/field-config-panel.js +11 -5
  13. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  14. package/dist/components/field-config-panel/field-sidebar.d.ts +13 -0
  15. package/dist/components/field-config-panel/field-sidebar.d.ts.map +1 -0
  16. package/dist/components/field-config-panel/field-sidebar.js +20 -0
  17. package/dist/components/field-config-panel/field-sidebar.js.map +1 -0
  18. package/dist/components/field-config-panel/formula-generator.d.ts +11 -0
  19. package/dist/components/field-config-panel/formula-generator.d.ts.map +1 -0
  20. package/dist/components/field-config-panel/formula-generator.js +51 -0
  21. package/dist/components/field-config-panel/formula-generator.js.map +1 -0
  22. package/dist/components/field-config-panel/formula-modal.d.ts +12 -0
  23. package/dist/components/field-config-panel/formula-modal.d.ts.map +1 -0
  24. package/dist/components/field-config-panel/formula-modal.js +99 -0
  25. package/dist/components/field-config-panel/formula-modal.js.map +1 -0
  26. package/dist/components/field-config-panel/formula-workspace.d.ts +23 -0
  27. package/dist/components/field-config-panel/formula-workspace.d.ts.map +1 -0
  28. package/dist/components/field-config-panel/formula-workspace.js +28 -0
  29. package/dist/components/field-config-panel/formula-workspace.js.map +1 -0
  30. package/dist/components/field-config-panel/result-type-selector.d.ts +9 -0
  31. package/dist/components/field-config-panel/result-type-selector.d.ts.map +1 -0
  32. package/dist/components/field-config-panel/result-type-selector.js +10 -0
  33. package/dist/components/field-config-panel/result-type-selector.js.map +1 -0
  34. package/dist/components/field-sidebar/calculated-field-type-list.d.ts +9 -0
  35. package/dist/components/field-sidebar/calculated-field-type-list.d.ts.map +1 -0
  36. package/dist/components/field-sidebar/calculated-field-type-list.js +12 -0
  37. package/dist/components/field-sidebar/calculated-field-type-list.js.map +1 -0
  38. package/dist/components/field-sidebar/data-model-field-type-list.d.ts +0 -1
  39. package/dist/components/field-sidebar/data-model-field-type-list.d.ts.map +1 -1
  40. package/dist/components/field-sidebar/data-model-field-type-list.js +8 -7
  41. package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
  42. package/dist/components/field-sidebar/field-menu-group.d.ts +11 -0
  43. package/dist/components/field-sidebar/field-menu-group.d.ts.map +1 -0
  44. package/dist/components/field-sidebar/field-menu-group.js +6 -0
  45. package/dist/components/field-sidebar/field-menu-group.js.map +1 -0
  46. package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
  47. package/dist/components/field-sidebar/field-sidebar.js +6 -15
  48. package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
  49. package/dist/components/field-sidebar/fillable-field-type-list.d.ts +0 -1
  50. package/dist/components/field-sidebar/fillable-field-type-list.d.ts.map +1 -1
  51. package/dist/components/field-sidebar/fillable-field-type-list.js +8 -9
  52. package/dist/components/field-sidebar/fillable-field-type-list.js.map +1 -1
  53. package/dist/components/pdf-canvas/pdf-canvas.d.ts +1 -0
  54. package/dist/components/pdf-canvas/pdf-canvas.d.ts.map +1 -1
  55. package/dist/components/pdf-canvas/pdf-canvas.js +2 -2
  56. package/dist/components/pdf-canvas/pdf-canvas.js.map +1 -1
  57. package/dist/components/pdf-editor/pdf-editor.d.ts +1 -0
  58. package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
  59. package/dist/components/pdf-editor/pdf-editor.js +2 -2
  60. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  61. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.d.ts +1 -0
  62. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.d.ts.map +1 -1
  63. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +2 -2
  64. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
  65. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts +8 -0
  66. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts.map +1 -0
  67. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js +5 -0
  68. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js.map +1 -0
  69. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts +1 -0
  70. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  71. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +3 -2
  72. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  73. package/dist/components/pdf-view/pdf-view-calculated.d.ts +9 -0
  74. package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -0
  75. package/dist/components/pdf-view/pdf-view-calculated.js +18 -0
  76. package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -0
  77. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  78. package/dist/components/pdf-view/pdf-view.js +2 -1
  79. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  80. package/dist/constants/field.constants.d.ts +3 -2
  81. package/dist/constants/field.constants.d.ts.map +1 -1
  82. package/dist/constants/field.constants.js +6 -0
  83. package/dist/constants/field.constants.js.map +1 -1
  84. package/dist/constants/menu-group.d.ts +8 -0
  85. package/dist/constants/menu-group.d.ts.map +1 -0
  86. package/dist/constants/menu-group.js +20 -0
  87. package/dist/constants/menu-group.js.map +1 -0
  88. package/dist/hooks/index.d.ts +1 -0
  89. package/dist/hooks/index.d.ts.map +1 -1
  90. package/dist/hooks/index.js +1 -0
  91. package/dist/hooks/index.js.map +1 -1
  92. package/dist/hooks/useFormulaEditor.d.ts +22 -0
  93. package/dist/hooks/useFormulaEditor.d.ts.map +1 -0
  94. package/dist/hooks/useFormulaEditor.js +290 -0
  95. package/dist/hooks/useFormulaEditor.js.map +1 -0
  96. package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
  97. package/dist/hooks/usePdfFieldDnD.js +3 -0
  98. package/dist/hooks/usePdfFieldDnD.js.map +1 -1
  99. package/dist/index.d.ts +1 -1
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +1 -0
  102. package/dist/index.js.map +1 -1
  103. package/dist/interface/types.d.ts +45 -3
  104. package/dist/interface/types.d.ts.map +1 -1
  105. package/dist/interface/types.js +3 -0
  106. package/dist/interface/types.js.map +1 -1
  107. package/dist/utils/data-model/extract-fields.utils.d.ts +5 -5
  108. package/dist/utils/data-model/extract-fields.utils.d.ts.map +1 -1
  109. package/dist/utils/data-model/extract-fields.utils.js +42 -8
  110. package/dist/utils/data-model/extract-fields.utils.js.map +1 -1
  111. package/dist/utils/formula/caret.utils.d.ts +3 -0
  112. package/dist/utils/formula/caret.utils.d.ts.map +1 -0
  113. package/dist/utils/formula/caret.utils.js +123 -0
  114. package/dist/utils/formula/caret.utils.js.map +1 -0
  115. package/dist/utils/formula/dom.utils.d.ts +4 -0
  116. package/dist/utils/formula/dom.utils.d.ts.map +1 -0
  117. package/dist/utils/formula/dom.utils.js +34 -0
  118. package/dist/utils/formula/dom.utils.js.map +1 -0
  119. package/dist/utils/formula/evaluate-formula.utils.d.ts +13 -0
  120. package/dist/utils/formula/evaluate-formula.utils.d.ts.map +1 -0
  121. package/dist/utils/formula/evaluate-formula.utils.js +134 -0
  122. package/dist/utils/formula/evaluate-formula.utils.js.map +1 -0
  123. package/dist/utils/formula/expression.utils.d.ts +18 -0
  124. package/dist/utils/formula/expression.utils.d.ts.map +1 -0
  125. package/dist/utils/formula/expression.utils.js +84 -0
  126. package/dist/utils/formula/expression.utils.js.map +1 -0
  127. package/dist/utils/formula/format-calculated-result.utils.d.ts +7 -0
  128. package/dist/utils/formula/format-calculated-result.utils.d.ts.map +1 -0
  129. package/dist/utils/formula/format-calculated-result.utils.js +50 -0
  130. package/dist/utils/formula/format-calculated-result.utils.js.map +1 -0
  131. package/dist/utils/formula/formula-types.d.ts +3 -0
  132. package/dist/utils/formula/formula-types.d.ts.map +1 -0
  133. package/dist/utils/formula/formula-types.js +2 -0
  134. package/dist/utils/formula/formula-types.js.map +1 -0
  135. package/dist/utils/formula/index.d.ts +11 -0
  136. package/dist/utils/formula/index.d.ts.map +1 -0
  137. package/dist/utils/formula/index.js +11 -0
  138. package/dist/utils/formula/index.js.map +1 -0
  139. package/dist/utils/formula/referenced-paths.utils.d.ts +7 -0
  140. package/dist/utils/formula/referenced-paths.utils.d.ts.map +1 -0
  141. package/dist/utils/formula/referenced-paths.utils.js +18 -0
  142. package/dist/utils/formula/referenced-paths.utils.js.map +1 -0
  143. package/dist/utils/formula/render-formula.utils.d.ts +8 -0
  144. package/dist/utils/formula/render-formula.utils.d.ts.map +1 -0
  145. package/dist/utils/formula/render-formula.utils.js +39 -0
  146. package/dist/utils/formula/render-formula.utils.js.map +1 -0
  147. package/dist/utils/formula/serialize-formula.utils.d.ts +14 -0
  148. package/dist/utils/formula/serialize-formula.utils.d.ts.map +1 -0
  149. package/dist/utils/formula/serialize-formula.utils.js +33 -0
  150. package/dist/utils/formula/serialize-formula.utils.js.map +1 -0
  151. package/dist/utils/formula/validate-formula.utils.d.ts +11 -0
  152. package/dist/utils/formula/validate-formula.utils.d.ts.map +1 -0
  153. package/dist/utils/formula/validate-formula.utils.js +79 -0
  154. package/dist/utils/formula/validate-formula.utils.js.map +1 -0
  155. package/dist/utils/index.d.ts +1 -0
  156. package/dist/utils/index.d.ts.map +1 -1
  157. package/dist/utils/index.js +1 -0
  158. package/dist/utils/index.js.map +1 -1
  159. package/package.json +2 -2
  160. package/src/components/field-config-panel/advanced-settings.tsx +113 -0
  161. package/src/components/field-config-panel/field-config-panel-overlay.tsx +8 -1
  162. package/src/components/field-config-panel/field-config-panel.tsx +43 -15
  163. package/src/components/field-config-panel/field-sidebar.tsx +91 -0
  164. package/src/components/field-config-panel/formula-generator.tsx +122 -0
  165. package/src/components/field-config-panel/formula-modal.tsx +229 -0
  166. package/src/components/field-config-panel/formula-workspace.tsx +116 -0
  167. package/src/components/field-config-panel/result-type-selector.tsx +34 -0
  168. package/src/components/field-sidebar/calculated-field-type-list.tsx +29 -0
  169. package/src/components/field-sidebar/data-model-field-type-list.tsx +10 -4
  170. package/src/components/field-sidebar/field-menu-group.tsx +36 -0
  171. package/src/components/field-sidebar/field-sidebar.tsx +14 -55
  172. package/src/components/field-sidebar/fillable-field-type-list.tsx +11 -9
  173. package/src/components/pdf-canvas/pdf-canvas.tsx +3 -0
  174. package/src/components/pdf-editor/pdf-editor.tsx +5 -0
  175. package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +3 -0
  176. package/src/components/pdf-fields-overlay/pdf-overlay-field-calculated.tsx +15 -0
  177. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +6 -1
  178. package/src/components/pdf-view/pdf-view-calculated.tsx +23 -0
  179. package/src/components/pdf-view/pdf-view.tsx +7 -1
  180. package/src/constants/field.constants.ts +9 -2
  181. package/src/constants/menu-group.ts +26 -0
  182. package/src/hooks/index.ts +1 -0
  183. package/src/hooks/useFormulaEditor.ts +336 -0
  184. package/src/hooks/usePdfFieldDnD.ts +4 -0
  185. package/src/index.ts +1 -1
  186. package/src/interface/types.ts +38 -2
  187. package/src/styles/formula-modal.css +307 -0
  188. package/src/styles/index.css +1 -0
  189. package/src/styles/pdf-field-overlay.css +14 -0
  190. package/src/styles/variables.css +1 -0
  191. package/src/utils/data-model/extract-fields.utils.ts +65 -7
  192. package/src/utils/formula/caret.utils.ts +125 -0
  193. package/src/utils/formula/dom.utils.ts +35 -0
  194. package/src/utils/formula/evaluate-formula.utils.ts +159 -0
  195. package/src/utils/formula/expression.utils.ts +99 -0
  196. package/src/utils/formula/format-calculated-result.utils.ts +79 -0
  197. package/src/utils/formula/formula-types.ts +2 -0
  198. package/src/utils/formula/index.ts +10 -0
  199. package/src/utils/formula/referenced-paths.utils.ts +18 -0
  200. package/src/utils/formula/render-formula.utils.ts +40 -0
  201. package/src/utils/formula/serialize-formula.utils.ts +40 -0
  202. package/src/utils/formula/validate-formula.utils.ts +94 -0
  203. package/src/utils/index.ts +1 -0
@@ -0,0 +1,29 @@
1
+ import { FC, Fragment } from 'react';
2
+ import { CALCULATED_FIELD_TYPES } from '../../constants';
3
+ import { FieldTypeOption } from '../../interface/types';
4
+ import { FieldType } from './field-type';
5
+
6
+ interface CalculatedFieldTypeListProps {
7
+ onDragStart(fieldOption: FieldTypeOption): void;
8
+ onDragEnd(): void;
9
+ }
10
+ export const CalculatedFieldTypeList: FC<CalculatedFieldTypeListProps> = ({
11
+ onDragEnd,
12
+ onDragStart,
13
+ }) => {
14
+ return (
15
+ <Fragment>
16
+ {CALCULATED_FIELD_TYPES.map(fieldOption => {
17
+ const key = `${fieldOption.type}-${fieldOption.path ?? fieldOption.label}`;
18
+ return (
19
+ <FieldType
20
+ key={key}
21
+ label={fieldOption.label}
22
+ onDragEnd={onDragEnd}
23
+ onDragStart={() => onDragStart(fieldOption)}
24
+ />
25
+ );
26
+ })}
27
+ </Fragment>
28
+ );
29
+ };
@@ -1,10 +1,9 @@
1
- import { Flex, Text } from '@servicetitan/anvil2';
2
- import { FC, Fragment, useMemo } from 'react';
1
+ import { Flex, SearchField, Text } from '@servicetitan/anvil2';
2
+ import { FC, Fragment, useMemo, useState } from 'react';
3
3
  import { DataModelFieldGroup, FieldTypeOption } from '../../interface/types';
4
4
  import { FieldType } from './field-type';
5
5
 
6
6
  interface DataModelFieldTypeListProps {
7
- searchText?: string;
8
7
  dataModelGroups?: DataModelFieldGroup[];
9
8
  onDragStart(fieldOption: FieldTypeOption): void;
10
9
  onDragEnd(): void;
@@ -14,8 +13,9 @@ export const DataModelFieldTypeList: FC<DataModelFieldTypeListProps> = ({
14
13
  dataModelGroups = [],
15
14
  onDragEnd,
16
15
  onDragStart,
17
- searchText,
18
16
  }) => {
17
+ const [searchText, setSearchText] = useState('');
18
+
19
19
  const searchedFieldGroups = useMemo(() => {
20
20
  if (!searchText) {
21
21
  return dataModelGroups;
@@ -33,6 +33,12 @@ export const DataModelFieldTypeList: FC<DataModelFieldTypeListProps> = ({
33
33
  }, [dataModelGroups, searchText]);
34
34
  return (
35
35
  <Fragment>
36
+ <SearchField
37
+ placeholder="Search"
38
+ size="small"
39
+ className="dte-field-sidebar-search"
40
+ onChange={e => setSearchText(e.target.value ?? '')}
41
+ />
36
42
  {searchedFieldGroups.map(group => (
37
43
  <div key={group.groupName} className="dte-field-type-group">
38
44
  <Flex alignItems="center" gap={1} className="dte-field-type-group-header">
@@ -0,0 +1,36 @@
1
+ import { Flex, Icon, Text } from '@servicetitan/anvil2';
2
+ import { FC } from 'react';
3
+ import { MenuGroupModel } from '../../constants/menu-group';
4
+ import { FieldTypeEnum } from '../../interface/types';
5
+
6
+ interface FieldMenuGroupProps {
7
+ menuGroups: MenuGroupModel[];
8
+ activeFieldKey: FieldTypeEnum;
9
+ onClickMenu(key: FieldTypeEnum): void;
10
+ }
11
+
12
+ export const FieldMenuGroup: FC<FieldMenuGroupProps> = ({
13
+ activeFieldKey,
14
+ menuGroups,
15
+ onClickMenu,
16
+ }) => {
17
+ return (
18
+ <Flex className="dte-field-sidebar-menu" shrink={0} direction="column" gap="3">
19
+ {menuGroups.map(({ key, label, svgIcon }) => (
20
+ <Flex
21
+ key={key}
22
+ alignItems="center"
23
+ justifyContent="center"
24
+ direction="column"
25
+ className={`dte-field-sidebar-menu-item cursor-pointer ${key === activeFieldKey ? '--active' : ''}`}
26
+ onClick={() => onClickMenu(key)}
27
+ >
28
+ <Icon svg={svgIcon} size="large" className="dte-field-sidebar-menu-item-icon" />
29
+ <Text variant="body" size="small" className="dte-field-sidebar-menu-item-text">
30
+ {label}
31
+ </Text>
32
+ </Flex>
33
+ ))}
34
+ </Flex>
35
+ );
36
+ };
@@ -1,14 +1,14 @@
1
- import { Button, Flex, Icon, SearchField, Text } from '@servicetitan/anvil2';
2
- import IconBorderColor from '@servicetitan/anvil2/assets/icons/material/round/border_color.svg';
1
+ import { Button, Flex, Icon, Text } from '@servicetitan/anvil2';
3
2
  import IconMenuOpen from '@servicetitan/anvil2/assets/icons/material/round/menu_open.svg';
4
- import IconPhotoSizeSelectSmall from '@servicetitan/anvil2/assets/icons/material/round/photo_size_select_small.svg';
5
- import IconEstimate from '@servicetitan/anvil2/assets/icons/st/estimate.svg';
6
3
 
7
4
  import { FC, useState } from 'react';
5
+ import { menuGroups } from '../../constants/menu-group';
8
6
  import { useToggle } from '../../hooks';
9
7
  import { DataModelFieldGroup, FieldTypeEnum, FieldTypeOption } from '../../interface/types';
8
+ import { CalculatedFieldTypeList } from './calculated-field-type-list';
10
9
  import { DataModelFieldTypeList } from './data-model-field-type-list';
11
10
  import { ESignFieldTypeList } from './e-sign-field-type-list';
11
+ import { FieldMenuGroup } from './field-menu-group';
12
12
  import { FillableFieldTypeList } from './fillable-field-type-list';
13
13
 
14
14
  interface FieldSidebarProps {
@@ -17,23 +17,12 @@ interface FieldSidebarProps {
17
17
  onDragStart(fieldOption: FieldTypeOption): void;
18
18
  }
19
19
 
20
- const menuGroups: { svgIcon: any; label: string; key: FieldTypeEnum }[] = [
21
- { svgIcon: IconEstimate, label: 'Merge Tags', key: FieldTypeEnum.dataModel },
22
- { svgIcon: IconBorderColor, label: 'E-Sign', key: FieldTypeEnum.eSign },
23
- {
24
- svgIcon: IconPhotoSizeSelectSmall,
25
- label: 'Fillable Fields',
26
- key: FieldTypeEnum.fillable,
27
- },
28
- ];
29
-
30
20
  export const FieldSidebar: FC<FieldSidebarProps> = ({
31
21
  dataModelGroups = [],
32
22
  onDragEnd,
33
23
  onDragStart,
34
24
  }) => {
35
25
  const [activeFieldType, setActiveFieldType] = useState<FieldTypeEnum>(FieldTypeEnum.dataModel);
36
- const [searchText, setSearchText] = useState('');
37
26
  const { isOpen, open, toggle } = useToggle(true);
38
27
 
39
28
  const handleActiveFieldTypeChange = (activeFieldType: FieldTypeEnum) => {
@@ -43,31 +32,11 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
43
32
 
44
33
  return (
45
34
  <Flex className="dte-field-sidebar-container skeleton-item">
46
- <Flex className="dte-field-sidebar-menu" shrink={0} direction="column" gap="3">
47
- {menuGroups.map(({ key, label, svgIcon }) => (
48
- <Flex
49
- key={key}
50
- alignItems="center"
51
- justifyContent="center"
52
- direction="column"
53
- className={`dte-field-sidebar-menu-item cursor-pointer ${key === activeFieldType ? '--active' : ''}`}
54
- onClick={() => handleActiveFieldTypeChange(key)}
55
- >
56
- <Icon
57
- svg={svgIcon}
58
- size="large"
59
- className="dte-field-sidebar-menu-item-icon"
60
- />
61
- <Text
62
- variant="body"
63
- size="small"
64
- className="dte-field-sidebar-menu-item-text"
65
- >
66
- {label}
67
- </Text>
68
- </Flex>
69
- ))}
70
- </Flex>
35
+ <FieldMenuGroup
36
+ menuGroups={menuGroups}
37
+ onClickMenu={handleActiveFieldTypeChange}
38
+ activeFieldKey={activeFieldType}
39
+ />
71
40
 
72
41
  <Flex
73
42
  className={`dte-field-sidebar-content skeleton-item ${isOpen ? '--open' : ''}`}
@@ -75,35 +44,25 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
75
44
  >
76
45
  <Flex className="dte-field-sidebar-content-inner">
77
46
  <Text variant="headline" el="h1" size="medium">
78
- {menuGroups.find(group => group.key === activeFieldType)!.label}
47
+ {menuGroups.find(group => group.key === activeFieldType)?.label}
79
48
  </Text>
80
49
 
81
- {activeFieldType !== FieldTypeEnum.eSign && (
82
- <SearchField
83
- placeholder="Search"
84
- size="small"
85
- className="dte-field-sidebar-search"
86
- onChange={e => setSearchText(e.target.value ?? '')}
87
- />
88
- )}
89
50
  {activeFieldType === FieldTypeEnum.fillable && (
90
- <FillableFieldTypeList
91
- searchText={searchText}
92
- onDragStart={onDragStart}
93
- onDragEnd={onDragEnd}
94
- />
51
+ <FillableFieldTypeList onDragStart={onDragStart} onDragEnd={onDragEnd} />
95
52
  )}
96
53
  {activeFieldType === FieldTypeEnum.eSign && (
97
54
  <ESignFieldTypeList onDragEnd={onDragEnd} onDragStart={onDragStart} />
98
55
  )}
99
56
  {activeFieldType === FieldTypeEnum.dataModel && (
100
57
  <DataModelFieldTypeList
101
- searchText={searchText}
102
58
  dataModelGroups={dataModelGroups}
103
59
  onDragStart={onDragStart}
104
60
  onDragEnd={onDragEnd}
105
61
  />
106
62
  )}
63
+ {activeFieldType === FieldTypeEnum.calculated && (
64
+ <CalculatedFieldTypeList onDragStart={onDragStart} onDragEnd={onDragEnd} />
65
+ )}
107
66
  </Flex>
108
67
  </Flex>
109
68
  <Button
@@ -1,10 +1,10 @@
1
- import { FC, Fragment, useMemo } from 'react';
1
+ import { SearchField } from '@servicetitan/anvil2';
2
+ import { FC, Fragment, useMemo, useState } from 'react';
2
3
  import { FILLABLE_FIELD_TYPES } from '../../constants';
3
4
  import { FieldTypeOption } from '../../interface/types';
4
5
  import { FieldType } from './field-type';
5
6
 
6
7
  interface FillableFieldTypeListProps {
7
- searchText?: string;
8
8
  onDragStart(fieldOption: FieldTypeOption): void;
9
9
  onDragEnd(): void;
10
10
  }
@@ -12,8 +12,9 @@ interface FillableFieldTypeListProps {
12
12
  export const FillableFieldTypeList: FC<FillableFieldTypeListProps> = ({
13
13
  onDragEnd,
14
14
  onDragStart,
15
- searchText,
16
15
  }) => {
16
+ const [searchText, setSearchText] = useState('');
17
+
17
18
  const searchedFieldTypes = useMemo(() => {
18
19
  if (!searchText) {
19
20
  return FILLABLE_FIELD_TYPES;
@@ -27,18 +28,19 @@ export const FillableFieldTypeList: FC<FillableFieldTypeListProps> = ({
27
28
 
28
29
  return (
29
30
  <Fragment>
31
+ <SearchField
32
+ placeholder="Search"
33
+ size="small"
34
+ className="dte-field-sidebar-search"
35
+ onChange={e => setSearchText(e.target.value ?? '')}
36
+ />
30
37
  {searchedFieldTypes.map(fieldOption => {
31
38
  return (
32
39
  <FieldType
33
40
  key={fieldOption.subType}
34
41
  label={fieldOption.label}
35
42
  onDragEnd={onDragEnd}
36
- onDragStart={() =>
37
- onDragStart({
38
- ...fieldOption,
39
- label: '',
40
- })
41
- }
43
+ onDragStart={() => onDragStart(fieldOption)}
42
44
  />
43
45
  );
44
46
  })}
@@ -10,6 +10,7 @@ interface PdfCanvasProps {
10
10
  errorPlaceholder?: ReactNode;
11
11
  loadingPlaceholder?: ReactNode;
12
12
  fields: PdfField[];
13
+ errors?: Record<string, string>;
13
14
  loading: boolean;
14
15
  selectedField: PdfField | null;
15
16
  pdfContainerRef: RefObject<HTMLDivElement>;
@@ -27,6 +28,7 @@ interface PdfCanvasProps {
27
28
 
28
29
  export const PdfCanvas: FC<PdfCanvasProps> = ({
29
30
  errorPlaceholder,
31
+ errors = {},
30
32
  fields,
31
33
  handleAddNewField,
32
34
  loading,
@@ -76,6 +78,7 @@ export const PdfCanvas: FC<PdfCanvasProps> = ({
76
78
  {isPdfLoaded && (
77
79
  <PdfFieldsOverlay
78
80
  fields={fields}
81
+ errors={errors}
79
82
  handleAddNewField={handleAddNewField}
80
83
  pdfWrapperRef={pdfWrapperRef}
81
84
  recipientsColors={recipientsColors}
@@ -12,6 +12,7 @@ import '../../styles/index.css';
12
12
  export interface PdfEditorProps {
13
13
  pdfUrl: string;
14
14
  loading?: boolean;
15
+ errors?: Record<string, string>;
15
16
  loadingPlaceholder?: ReactNode;
16
17
  errorPlaceholder?: ReactNode;
17
18
  dataModel?: SchemaObject;
@@ -24,6 +25,7 @@ export interface PdfEditorProps {
24
25
  export const PdfEditor: FC<PdfEditorProps> = ({
25
26
  dataModel,
26
27
  errorPlaceholder,
28
+ errors = {},
27
29
  fields = [],
28
30
  loading = false,
29
31
  loadingPlaceholder,
@@ -69,6 +71,8 @@ export const PdfEditor: FC<PdfEditorProps> = ({
69
71
  <Flex flex={1} className={`dte-pdf-editor ${loading ? 'skeleton' : ''}`}>
70
72
  {selectedField && (
71
73
  <FieldConfigPanelOverlay
74
+ dataModel={dataModel}
75
+ documentFields={fields}
72
76
  selectedField={selectedField}
73
77
  onFieldConfigChange={updateField}
74
78
  onDeleteField={deleteSelectedField}
@@ -90,6 +94,7 @@ export const PdfEditor: FC<PdfEditorProps> = ({
90
94
  pdfUrl={pdfUrl}
91
95
  fields={fields}
92
96
  loading={loading}
97
+ errors={errors}
93
98
  onLoadSuccess={onLoadSuccess}
94
99
  recipientsColors={recipientsColors}
95
100
  selectedField={selectedField}
@@ -5,6 +5,7 @@ import { PdfOverlayField } from './pdf-overlay-field';
5
5
  interface PdfFieldsOverlayProps {
6
6
  fields: PdfField[];
7
7
  selectedField: PdfField | null;
8
+ errors: Record<string, string>;
8
9
  pdfWrapperRef: RefObject<HTMLDivElement>;
9
10
  recipientsColors: Record<string, string>;
10
11
  handleAddNewField(field: PdfField): void;
@@ -14,6 +15,7 @@ interface PdfFieldsOverlayProps {
14
15
  }
15
16
 
16
17
  export const PdfFieldsOverlay: FC<PdfFieldsOverlayProps> = ({
18
+ errors,
17
19
  fields,
18
20
  handleAddNewField,
19
21
  onFieldClick,
@@ -29,6 +31,7 @@ export const PdfFieldsOverlay: FC<PdfFieldsOverlayProps> = ({
29
31
  <PdfOverlayField
30
32
  key={field.id}
31
33
  field={field}
34
+ error={errors[field.id]}
32
35
  handleAddNewField={handleAddNewField}
33
36
  onFieldMove={onFieldMove}
34
37
  onFieldClick={onFieldClick}
@@ -0,0 +1,15 @@
1
+ import { FC } from 'react';
2
+ import { PdfField } from '../../interface/types';
3
+
4
+ interface PdfOverlayFieldCalculatedProps {
5
+ field: PdfField;
6
+ }
7
+
8
+ export const PdfOverlayFieldCalculated: FC<PdfOverlayFieldCalculatedProps> = ({ field }) => {
9
+ return (
10
+ <span>
11
+ {field.label ? field.label : field.path}
12
+ {field.required ? '*' : ''}
13
+ </span>
14
+ );
15
+ };
@@ -2,6 +2,7 @@ import { FC, MouseEvent, RefObject } from 'react';
2
2
  import { useFieldDrag, useFieldResize } from '../../hooks';
3
3
  import { FieldTypeEnum, PdfField } from '../../interface/types';
4
4
  import { getFieldBackgroundColor, getPagePosition } from '../../utils';
5
+ import { PdfOverlayFieldCalculated } from './pdf-overlay-field-calculated';
5
6
  import { PdfOverlayFieldDataModel } from './pdf-overlay-field-data-model';
6
7
  import { PdfOverlayFieldESign } from './pdf-overlay-field-e-sign';
7
8
  import { PdfOverlayFieldFillable } from './pdf-overlay-field-fillable';
@@ -9,6 +10,7 @@ import { ResizeHandle } from './resize-handle';
9
10
 
10
11
  interface PdfOverlayFieldProps {
11
12
  field: PdfField;
13
+ error?: string;
12
14
  isSelected: boolean;
13
15
  isSameGroup: boolean;
14
16
  recipientsColors: Record<string, string>;
@@ -20,6 +22,7 @@ interface PdfOverlayFieldProps {
20
22
  }
21
23
 
22
24
  export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
25
+ error,
23
26
  field,
24
27
  handleAddNewField,
25
28
  isSameGroup,
@@ -62,15 +65,17 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
62
65
  onDragStart={e => handleDragStart(e, field)}
63
66
  onDrag={e => handleDrag(e, field)}
64
67
  onDragEnd={handleDragEnd}
65
- className={`dte-pdf-field ${isSelected ? '--selected' : '--unselected'} ${isDragging ? '--dragging' : ''} ${isSameGroup ? '--colored' : ''}`}
68
+ className={`dte-pdf-field ${isSelected ? '--selected' : '--unselected'} ${isDragging ? '--dragging' : ''} ${isSameGroup ? '--colored' : ''} ${error ? '--error' : ''}`}
66
69
  style={style}
67
70
  >
71
+ {field.type === FieldTypeEnum.calculated && <PdfOverlayFieldCalculated field={field} />}
68
72
  {field.type === FieldTypeEnum.eSign && <PdfOverlayFieldESign field={field} />}
69
73
  {field.type === FieldTypeEnum.dataModel && <PdfOverlayFieldDataModel field={field} />}
70
74
  {field.type === FieldTypeEnum.fillable && (
71
75
  <PdfOverlayFieldFillable field={field} handleAddNewField={handleAddNewField} />
72
76
  )}
73
77
  {isSelected && <ResizeHandle handleResizeStart={e => handleResizeStart(e, field)} />}
78
+ {error && <span className="dte-pdf-field-error-message">{error}</span>}
74
79
  </div>
75
80
  );
76
81
  };
@@ -0,0 +1,23 @@
1
+ import { FC, useMemo } from 'react';
2
+ import { DataModelValues, PdfField } from '../../interface/types';
3
+ import { evaluateFormula, formatCalculatedResult, resolvePdfDataValues } from '../../utils';
4
+
5
+ interface PdfViewCalculatedProps {
6
+ field: PdfField;
7
+ data?: DataModelValues;
8
+ }
9
+
10
+ export const PdfViewCalculated: FC<PdfViewCalculatedProps> = ({ data, field }) => {
11
+ const displayValue = useMemo(() => {
12
+ if (field.formula?.tokens?.length) {
13
+ const value = evaluateFormula(field.formula, data);
14
+ return formatCalculatedResult(value, field.formulaFormat);
15
+ }
16
+ if (field.path) {
17
+ return resolvePdfDataValues(data, field.path) ?? '';
18
+ }
19
+ return field.label ?? '';
20
+ }, [data, field.formula, field.formulaFormat, field.label, field.path]);
21
+
22
+ return <div className="dte-pdf-field-value">{displayValue}</div>;
23
+ };
@@ -11,6 +11,7 @@ import {
11
11
  } from '../../interface/types';
12
12
  import { mapColorsToRecipients } from '../../utils';
13
13
  import { PdfDocumentRenderer } from '../pdf-canvas/pdf-document-renderer';
14
+ import { PdfViewCalculated } from './pdf-view-calculated';
14
15
  import { PdfViewDataModel } from './pdf-view-data-model';
15
16
  import { PdfViewESign } from './pdf-view-e-sign';
16
17
  import { PdfViewFieldContainer } from './pdf-view-field-container';
@@ -70,7 +71,6 @@ export const PdfView: FC<PdfViewProps> = ({
70
71
  };
71
72
 
72
73
  const recipientsColors = mapColorsToRecipients(recipients);
73
-
74
74
  return (
75
75
  <Flex flex={1} className="dte-pdf-view-container" style={{ maxWidth: BASE_PAGE_WIDTH }}>
76
76
  <div ref={pdfWrapperRef} className="dte-pdf-wrapper">
@@ -97,6 +97,12 @@ export const PdfView: FC<PdfViewProps> = ({
97
97
  {field.type === FieldTypeEnum.eSign && (
98
98
  <PdfViewESign field={field} />
99
99
  )}
100
+ {field.type === FieldTypeEnum.calculated && (
101
+ <PdfViewCalculated
102
+ field={field}
103
+ data={previewMode === 'fillable' ? previewData : data}
104
+ />
105
+ )}
100
106
  {field.type === FieldTypeEnum.fillable && (
101
107
  <PdfViewFillable
102
108
  data={previewMode === 'fillable' ? previewData : data}
@@ -2,7 +2,7 @@ import {
2
2
  ESignFieldType,
3
3
  FieldTypeEnum,
4
4
  FieldTypeOption,
5
- FillableFieldType,
5
+ PdfFieldSubType,
6
6
  } from '../interface/types';
7
7
 
8
8
  export const FIELD_CONSTANTS = {
@@ -19,7 +19,7 @@ export const FILLABLE_FIELD_DEFAULT_SIZES = {
19
19
  date: { width: 200, height: 25 },
20
20
  radio: { width: 20, height: 20 },
21
21
  checkbox: { width: 20, height: 20 },
22
- } as Record<FillableFieldType | ESignFieldType, { width: number; height: number }>;
22
+ } as Record<PdfFieldSubType, { width: number; height: number }>;
23
23
 
24
24
  export const E_SIGN_FIELD_TYPE_OPTIONS: { name: string; id: ESignFieldType }[] = [
25
25
  { name: 'Signature', id: ESignFieldType.signature },
@@ -43,3 +43,10 @@ export const E_SIGN_FIELD_TYPES: FieldTypeOption[] = [
43
43
  subType: ESignFieldType.signature,
44
44
  },
45
45
  ];
46
+
47
+ export const CALCULATED_FIELD_TYPES: FieldTypeOption[] = [
48
+ {
49
+ label: 'Calculated Field',
50
+ type: FieldTypeEnum.calculated,
51
+ },
52
+ ];
@@ -0,0 +1,26 @@
1
+ import IconBorderColor from '@servicetitan/anvil2/assets/icons/material/round/border_color.svg';
2
+ import IconCalculate from '@servicetitan/anvil2/assets/icons/material/round/calculate.svg';
3
+ import IconPhotoSizeSelectSmall from '@servicetitan/anvil2/assets/icons/material/round/photo_size_select_small.svg';
4
+ import IconEstimate from '@servicetitan/anvil2/assets/icons/st/estimate.svg';
5
+ import { FieldTypeEnum } from '../interface/types';
6
+
7
+ export interface MenuGroupModel {
8
+ svgIcon: any;
9
+ label: string;
10
+ key: FieldTypeEnum;
11
+ }
12
+
13
+ export const menuGroups: MenuGroupModel[] = [
14
+ { svgIcon: IconEstimate, label: 'Merge Tags', key: FieldTypeEnum.dataModel },
15
+ { svgIcon: IconBorderColor, label: 'E-Sign', key: FieldTypeEnum.eSign },
16
+ {
17
+ svgIcon: IconPhotoSizeSelectSmall,
18
+ label: 'Fillable Fields',
19
+ key: FieldTypeEnum.fillable,
20
+ },
21
+ {
22
+ svgIcon: IconCalculate,
23
+ label: 'Calculated Fields',
24
+ key: FieldTypeEnum.calculated,
25
+ },
26
+ ];
@@ -1,5 +1,6 @@
1
1
  export * from './useFieldDrag';
2
2
  export * from './useFieldResize';
3
+ export * from './useFormulaEditor';
3
4
  export * from './usePdfDocumentRenderer';
4
5
  export * from './usePdfFieldDnD';
5
6
  export * from './usePdfFieldSelection';