@servicetitan/dte-pdf-editor 1.25.0 → 1.27.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 (102) hide show
  1. package/README.md +75 -38
  2. package/dist/components/display-conditions/display-condition.d.ts.map +1 -1
  3. package/dist/components/display-conditions/display-condition.js +0 -2
  4. package/dist/components/display-conditions/display-condition.js.map +1 -1
  5. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  6. package/dist/components/field-config-panel/field-config-panel.js +3 -2
  7. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  8. package/dist/components/field-config-panel/table-configs.d.ts +9 -0
  9. package/dist/components/field-config-panel/table-configs.d.ts.map +1 -0
  10. package/dist/components/field-config-panel/table-configs.js +78 -0
  11. package/dist/components/field-config-panel/table-configs.js.map +1 -0
  12. package/dist/components/field-sidebar/field-menu-group.d.ts +1 -1
  13. package/dist/components/field-sidebar/field-menu-group.d.ts.map +1 -1
  14. package/dist/components/field-sidebar/field-menu-group.js +1 -5
  15. package/dist/components/field-sidebar/field-menu-group.js.map +1 -1
  16. package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
  17. package/dist/components/field-sidebar/field-sidebar.js +3 -2
  18. package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
  19. package/dist/components/field-sidebar/generic-field-type-list.d.ts +9 -0
  20. package/dist/components/field-sidebar/generic-field-type-list.d.ts.map +1 -0
  21. package/dist/components/field-sidebar/generic-field-type-list.js +12 -0
  22. package/dist/components/field-sidebar/generic-field-type-list.js.map +1 -0
  23. package/dist/components/pdf-canvas/pdf-canvas.d.ts +1 -0
  24. package/dist/components/pdf-canvas/pdf-canvas.d.ts.map +1 -1
  25. package/dist/components/pdf-canvas/pdf-canvas.js +2 -2
  26. package/dist/components/pdf-canvas/pdf-canvas.js.map +1 -1
  27. package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
  28. package/dist/components/pdf-editor/pdf-editor.js +1 -1
  29. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  30. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.d.ts +1 -0
  31. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.d.ts.map +1 -1
  32. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +2 -2
  33. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
  34. package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.d.ts +9 -0
  35. package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.d.ts.map +1 -0
  36. package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js +83 -0
  37. package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js.map +1 -0
  38. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts +1 -0
  39. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  40. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +6 -3
  41. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  42. package/dist/components/pdf-view/pdf-view-generic.d.ts +16 -0
  43. package/dist/components/pdf-view/pdf-view-generic.d.ts.map +1 -0
  44. package/dist/components/pdf-view/pdf-view-generic.js +68 -0
  45. package/dist/components/pdf-view/pdf-view-generic.js.map +1 -0
  46. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  47. package/dist/components/pdf-view/pdf-view.js +2 -1
  48. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  49. package/dist/components/shared/index.d.ts +2 -0
  50. package/dist/components/shared/index.d.ts.map +1 -0
  51. package/dist/components/shared/index.js +2 -0
  52. package/dist/components/shared/index.js.map +1 -0
  53. package/dist/components/shared/inline-editable.d.ts +8 -0
  54. package/dist/components/shared/inline-editable.d.ts.map +1 -0
  55. package/dist/components/shared/inline-editable.js +17 -0
  56. package/dist/components/shared/inline-editable.js.map +1 -0
  57. package/dist/constants/field.constants.d.ts +8 -1
  58. package/dist/constants/field.constants.d.ts.map +1 -1
  59. package/dist/constants/field.constants.js +23 -0
  60. package/dist/constants/field.constants.js.map +1 -1
  61. package/dist/constants/menu-group.d.ts.map +1 -1
  62. package/dist/constants/menu-group.js +8 -2
  63. package/dist/constants/menu-group.js.map +1 -1
  64. package/dist/hooks/useFormulaEditor.d.ts.map +1 -1
  65. package/dist/hooks/useFormulaEditor.js +3 -1
  66. package/dist/hooks/useFormulaEditor.js.map +1 -1
  67. package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
  68. package/dist/hooks/usePdfFieldDnD.js +80 -20
  69. package/dist/hooks/usePdfFieldDnD.js.map +1 -1
  70. package/dist/interface/types.d.ts +24 -2
  71. package/dist/interface/types.d.ts.map +1 -1
  72. package/dist/interface/types.js +1 -0
  73. package/dist/interface/types.js.map +1 -1
  74. package/dist/utils/formula/validate-formula.utils.d.ts.map +1 -1
  75. package/dist/utils/formula/validate-formula.utils.js +9 -2
  76. package/dist/utils/formula/validate-formula.utils.js.map +1 -1
  77. package/package.json +1 -1
  78. package/src/components/display-conditions/display-condition.tsx +0 -3
  79. package/src/components/field-config-panel/field-config-panel.tsx +8 -1
  80. package/src/components/field-config-panel/table-configs.tsx +136 -0
  81. package/src/components/field-sidebar/field-menu-group.tsx +16 -27
  82. package/src/components/field-sidebar/field-sidebar.tsx +5 -1
  83. package/src/components/field-sidebar/generic-field-type-list.tsx +27 -0
  84. package/src/components/pdf-canvas/pdf-canvas.tsx +3 -0
  85. package/src/components/pdf-editor/pdf-editor.tsx +1 -0
  86. package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +3 -0
  87. package/src/components/pdf-fields-overlay/pdf-overlay-field-generic.tsx +188 -0
  88. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +11 -1
  89. package/src/components/pdf-view/pdf-view-generic.tsx +129 -0
  90. package/src/components/pdf-view/pdf-view.tsx +4 -0
  91. package/src/components/shared/index.ts +1 -0
  92. package/src/components/shared/inline-editable.tsx +47 -0
  93. package/src/constants/field.constants.ts +27 -0
  94. package/src/constants/menu-group.ts +8 -2
  95. package/src/hooks/useFormulaEditor.ts +4 -1
  96. package/src/hooks/usePdfFieldDnD.ts +97 -27
  97. package/src/interface/types.ts +27 -1
  98. package/src/styles/index.css +1 -0
  99. package/src/styles/inline-editable.css +20 -0
  100. package/src/styles/pdf-field-overlay.css +31 -0
  101. package/src/styles/variables.css +6 -0
  102. package/src/utils/formula/validate-formula.utils.ts +10 -7
@@ -0,0 +1,27 @@
1
+ import { FC, Fragment } from 'react';
2
+ import { GENERIC_FIELD_TYPES } from '../../constants';
3
+ import { FieldTypeOption } from '../../interface/types';
4
+ import { FieldType } from './field-type';
5
+
6
+ interface GenericFieldTypeListProps {
7
+ onDragStart(fieldOption: FieldTypeOption): void;
8
+ onDragEnd(): void;
9
+ }
10
+
11
+ export const GenericFieldTypeList: FC<GenericFieldTypeListProps> = ({ onDragEnd, onDragStart }) => {
12
+ return (
13
+ <Fragment>
14
+ {GENERIC_FIELD_TYPES.map(fieldOption => {
15
+ const key = `${fieldOption.type}-${fieldOption.path ?? fieldOption.label}`;
16
+ return (
17
+ <FieldType
18
+ key={key}
19
+ label={fieldOption.label}
20
+ onDragEnd={onDragEnd}
21
+ onDragStart={() => onDragStart(fieldOption)}
22
+ />
23
+ );
24
+ })}
25
+ </Fragment>
26
+ );
27
+ };
@@ -24,6 +24,7 @@ interface PdfCanvasProps {
24
24
  onFieldMove(fieldId: string, newX: number, newY: number, pageNumber: number): void;
25
25
  onFieldResize(fieldId: string, newWidth: number, newHeight: number, pageNumber: number): void;
26
26
  onDeselectField(): void;
27
+ onFieldConfigChange(updates: Partial<PdfField>): void;
27
28
  }
28
29
 
29
30
  export const PdfCanvas: FC<PdfCanvasProps> = ({
@@ -37,6 +38,7 @@ export const PdfCanvas: FC<PdfCanvasProps> = ({
37
38
  onDragOver,
38
39
  onDrop,
39
40
  onFieldClick,
41
+ onFieldConfigChange,
40
42
  onFieldMove,
41
43
  onFieldResize,
42
44
  onLoadSuccess,
@@ -79,6 +81,7 @@ export const PdfCanvas: FC<PdfCanvasProps> = ({
79
81
  <PdfFieldsOverlay
80
82
  fields={fields}
81
83
  errors={errors}
84
+ onFieldConfigChange={onFieldConfigChange}
82
85
  handleAddNewField={handleAddNewField}
83
86
  pdfWrapperRef={pdfWrapperRef}
84
87
  recipientsColors={recipientsColors}
@@ -109,6 +109,7 @@ export const PdfEditor: FC<PdfEditorProps> = ({
109
109
  onFieldMove={moveField}
110
110
  onFieldResize={resizeField}
111
111
  onDeselectField={deselectField}
112
+ onFieldConfigChange={updateField}
112
113
  />
113
114
  </Flex>
114
115
  </Flex>
@@ -8,6 +8,7 @@ interface PdfFieldsOverlayProps {
8
8
  errors: Record<string, string>;
9
9
  pdfWrapperRef: RefObject<HTMLDivElement>;
10
10
  recipientsColors: Record<string, string>;
11
+ onFieldConfigChange(updates: Partial<PdfField>): void;
11
12
  handleAddNewField(field: PdfField): void;
12
13
  onFieldClick(fieldId: string, e: MouseEvent): void;
13
14
  onFieldMove(fieldId: string, newX: number, newY: number, pageNumber: number): void;
@@ -19,6 +20,7 @@ export const PdfFieldsOverlay: FC<PdfFieldsOverlayProps> = ({
19
20
  fields,
20
21
  handleAddNewField,
21
22
  onFieldClick,
23
+ onFieldConfigChange,
22
24
  onFieldMove,
23
25
  onFieldResize,
24
26
  pdfWrapperRef,
@@ -31,6 +33,7 @@ export const PdfFieldsOverlay: FC<PdfFieldsOverlayProps> = ({
31
33
  <PdfOverlayField
32
34
  key={field.id}
33
35
  field={field}
36
+ onFieldConfigChange={onFieldConfigChange}
34
37
  error={errors[field.id]}
35
38
  handleAddNewField={handleAddNewField}
36
39
  onFieldMove={onFieldMove}
@@ -0,0 +1,188 @@
1
+ import { FC, useMemo, useState } from 'react';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import {
4
+ GenericFieldTableDataType,
5
+ GenericFieldTextDataType,
6
+ PdfField,
7
+ } from '../../interface/types';
8
+ import { InlineEditable } from '../shared';
9
+
10
+ interface PdfOverlayFieldGenericProps {
11
+ field: PdfField;
12
+ onFieldConfigChange(updates: Partial<PdfField>): void;
13
+ }
14
+
15
+ export const PdfOverlayFieldGeneric: FC<PdfOverlayFieldGenericProps> = ({
16
+ field,
17
+ onFieldConfigChange,
18
+ }) => {
19
+ if (field.subType === 'text') {
20
+ return (
21
+ <GenericFieldText
22
+ data={field.data as GenericFieldTextDataType}
23
+ onChange={data => {
24
+ onFieldConfigChange({
25
+ ...field,
26
+ ...data,
27
+ });
28
+ }}
29
+ />
30
+ );
31
+ }
32
+
33
+ return (
34
+ <GenericFieldTable
35
+ tableData={field.data as GenericFieldTableDataType}
36
+ onChange={data =>
37
+ onFieldConfigChange({
38
+ ...field,
39
+ ...data,
40
+ })
41
+ }
42
+ />
43
+ );
44
+ };
45
+
46
+ interface GenericFieldTextProps {
47
+ data: GenericFieldTextDataType;
48
+ onChange(updates: Partial<PdfField>): void;
49
+ }
50
+
51
+ const GenericFieldText: FC<GenericFieldTextProps> = ({ data, onChange }) => {
52
+ const [isActive, setIsActive] = useState(false);
53
+ const [editingValue, setEditingValue] = useState(data.value ?? '');
54
+ const commit = () => {
55
+ onChange({
56
+ data: {
57
+ value: editingValue,
58
+ },
59
+ });
60
+ setIsActive(false);
61
+ };
62
+
63
+ return isActive ? (
64
+ <textarea
65
+ style={{ width: '100%', height: '100%' }}
66
+ value={editingValue}
67
+ placeholder="Type here..."
68
+ onChange={e => setEditingValue(e.target.value)}
69
+ onBlur={commit}
70
+ onClick={e => e.stopPropagation()}
71
+ autoFocus
72
+ />
73
+ ) : (
74
+ <div
75
+ className="inline-editable-button"
76
+ role="button"
77
+ tabIndex={0}
78
+ onClick={() => setIsActive(true)}
79
+ onKeyDown={e => {
80
+ if (e.key === 'Enter' || e.key === ' ') {
81
+ e.preventDefault();
82
+ setIsActive(true);
83
+ }
84
+ }}
85
+ >
86
+ <pre className="inline-editable-html-text">{data.value ?? 'Type here...'}</pre>
87
+ </div>
88
+ );
89
+ };
90
+
91
+ interface GenericFieldTableProps {
92
+ tableData: GenericFieldTableDataType;
93
+ onChange(updates: Partial<PdfField>): void;
94
+ }
95
+
96
+ const GenericFieldTable: FC<GenericFieldTableProps> = ({ onChange, tableData }) => {
97
+ const { body, header, showHeader } = tableData;
98
+
99
+ const headerCellsWithKeys = useMemo(() => {
100
+ return header.cells.map(cell => ({
101
+ cell,
102
+ key: uuidv4(),
103
+ }));
104
+ }, [header.cells]);
105
+
106
+ const bodyRowsWithKeys = useMemo(() => {
107
+ return body.map(row => ({
108
+ key: uuidv4(),
109
+ row,
110
+ }));
111
+ }, [body]);
112
+
113
+ const commitCellValue = (
114
+ section: 'header' | 'body',
115
+ rowIndex: number,
116
+ cellIndex: number,
117
+ value: string,
118
+ ) => {
119
+ if (section === 'header') {
120
+ onChange({
121
+ data: {
122
+ ...tableData,
123
+ header: {
124
+ ...header,
125
+ cells: header.cells.map((c, i) => (i === cellIndex ? { ...c, value } : c)),
126
+ },
127
+ },
128
+ });
129
+ } else {
130
+ onChange({
131
+ data: {
132
+ ...tableData,
133
+ body: body.map((row, ri) =>
134
+ ri === rowIndex
135
+ ? {
136
+ ...row,
137
+ cells: row.cells.map((c, i) =>
138
+ i === cellIndex ? { ...c, value } : c,
139
+ ),
140
+ }
141
+ : row,
142
+ ),
143
+ },
144
+ });
145
+ }
146
+ };
147
+
148
+ return (
149
+ <table className="dte-pdf-field-generic-table">
150
+ {showHeader && (
151
+ <tr
152
+ style={{ height: header.height }}
153
+ className="dte-pdf-field-generic-table-header"
154
+ >
155
+ {headerCellsWithKeys.map(({ cell, key }, cellIndex) => (
156
+ <td key={key}>
157
+ <InlineEditable
158
+ onCommit={value => commitCellValue('header', 0, cellIndex, value)}
159
+ value={cell.value}
160
+ >
161
+ {cell.value}
162
+ </InlineEditable>
163
+ </td>
164
+ ))}
165
+ </tr>
166
+ )}
167
+ {bodyRowsWithKeys.map(({ key: rowKey, row }, rowIndex) => (
168
+ <tr key={rowKey} style={{ height: row.height }}>
169
+ {row.cells.map((cell, cellIndex) => {
170
+ const cellKey = `body-${rowIndex}-${cellIndex}`;
171
+ return (
172
+ <td key={cellKey}>
173
+ <InlineEditable
174
+ onCommit={value =>
175
+ commitCellValue('body', rowIndex, cellIndex, value)
176
+ }
177
+ value={cell.value}
178
+ >
179
+ {cell.value}
180
+ </InlineEditable>
181
+ </td>
182
+ );
183
+ })}
184
+ </tr>
185
+ ))}
186
+ </table>
187
+ );
188
+ };
@@ -1,4 +1,5 @@
1
1
  import { FC, MouseEvent, RefObject } from 'react';
2
+ import { HIDE_FIELD_RESIZE } from '../../constants';
2
3
  import { useFieldDrag, useFieldResize } from '../../hooks';
3
4
  import { FieldTypeEnum, PdfField } from '../../interface/types';
4
5
  import { getFieldBackgroundColor, getPagePosition } from '../../utils';
@@ -6,6 +7,7 @@ import { PdfOverlayFieldCalculated } from './pdf-overlay-field-calculated';
6
7
  import { PdfOverlayFieldDataModel } from './pdf-overlay-field-data-model';
7
8
  import { PdfOverlayFieldESign } from './pdf-overlay-field-e-sign';
8
9
  import { PdfOverlayFieldFillable } from './pdf-overlay-field-fillable';
10
+ import { PdfOverlayFieldGeneric } from './pdf-overlay-field-generic';
9
11
  import { ResizeHandle } from './resize-handle';
10
12
 
11
13
  interface PdfOverlayFieldProps {
@@ -15,6 +17,7 @@ interface PdfOverlayFieldProps {
15
17
  isSameGroup: boolean;
16
18
  recipientsColors: Record<string, string>;
17
19
  pdfWrapperRef: RefObject<HTMLDivElement>;
20
+ onFieldConfigChange(updates: Partial<PdfField>): void;
18
21
  handleAddNewField(field: PdfField): void;
19
22
  onFieldClick(fieldId: string, e: MouseEvent): void;
20
23
  onFieldMove(fieldId: string, newX: number, newY: number, pageNumber: number): void;
@@ -28,6 +31,7 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
28
31
  isSameGroup,
29
32
  isSelected,
30
33
  onFieldClick,
34
+ onFieldConfigChange,
31
35
  onFieldMove,
32
36
  onFieldResize,
33
37
  pdfWrapperRef,
@@ -74,7 +78,13 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
74
78
  {field.type === FieldTypeEnum.fillable && (
75
79
  <PdfOverlayFieldFillable field={field} handleAddNewField={handleAddNewField} />
76
80
  )}
77
- {isSelected && <ResizeHandle handleResizeStart={e => handleResizeStart(e, field)} />}
81
+ {field.type === FieldTypeEnum.generic && (
82
+ <PdfOverlayFieldGeneric field={field} onFieldConfigChange={onFieldConfigChange} />
83
+ )}
84
+ {isSelected &&
85
+ !(field.subType != null && HIDE_FIELD_RESIZE[field.type]?.[field.subType]) && (
86
+ <ResizeHandle handleResizeStart={e => handleResizeStart(e, field)} />
87
+ )}
78
88
  {error && <span className="dte-pdf-field-error-message">{error}</span>}
79
89
  </div>
80
90
  );
@@ -0,0 +1,129 @@
1
+ import { type CSSProperties, FC, useMemo } from 'react';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import {
4
+ GenericFieldTableDataType,
5
+ GenericFieldTextDataType,
6
+ PdfField,
7
+ } from '../../interface/types';
8
+
9
+ interface PdfViewGenericProps {
10
+ field: PdfField;
11
+ }
12
+
13
+ export const PdfViewGeneric: FC<PdfViewGenericProps> = ({ field }) => {
14
+ if (field.subType === 'text') {
15
+ return <GenericFieldText data={field.data as GenericFieldTextDataType} />;
16
+ }
17
+
18
+ return <GenericFieldTable data={field.data as GenericFieldTableDataType} />;
19
+ };
20
+
21
+ interface GenericFieldTextProps {
22
+ data: GenericFieldTextDataType;
23
+ }
24
+
25
+ export const GenericFieldText: FC<GenericFieldTextProps> = ({ data }) => {
26
+ return (
27
+ <div className="dte-pdf-field-value">
28
+ <pre className="inline-editable-html-text">{data?.value ?? ''}</pre>
29
+ </div>
30
+ );
31
+ };
32
+
33
+ interface GenericFieldTableProps {
34
+ data: GenericFieldTableDataType;
35
+ }
36
+
37
+ export const GenericFieldTable: FC<GenericFieldTableProps> = ({ data }) => {
38
+ const { body, header, showHeader } = data;
39
+
40
+ const headerCellsWithKeys = useMemo(() => {
41
+ return header.cells.map(cell => ({
42
+ cell,
43
+ key: uuidv4(),
44
+ }));
45
+ }, [header.cells]);
46
+
47
+ const bodyRowsWithKeys = useMemo(() => {
48
+ return body.map(row => ({
49
+ key: uuidv4(),
50
+ row,
51
+ cellKeys: row.cells.map(() => uuidv4()),
52
+ }));
53
+ }, [body]);
54
+
55
+ const baseCellStyle: CSSProperties = {
56
+ borderTop: '1px solid #e0e0e0',
57
+ borderLeft: '1px solid #e0e0e0',
58
+ paddingInline: '8px',
59
+ };
60
+
61
+ const headerCellStyle: CSSProperties = {
62
+ ...baseCellStyle,
63
+ backgroundColor: '#f5f5f5',
64
+ };
65
+
66
+ const bodyRowCount = bodyRowsWithKeys.length;
67
+ const isLastRow = (rowIndex: number) => rowIndex === bodyRowCount - 1;
68
+ const getBodyCellStyle = (
69
+ rowIndex: number,
70
+ cellIndex: number,
71
+ row: { cells: { value: string }[] },
72
+ ) => {
73
+ const lastInRow = cellIndex === row.cells.length - 1;
74
+ return {
75
+ ...baseCellStyle,
76
+ backgroundColor: '#ffffff',
77
+ ...(isLastRow(rowIndex) ? { borderBottom: '1px solid #e0e0e0' } : {}),
78
+ ...(lastInRow ? { borderRight: '1px solid #e0e0e0' } : {}),
79
+ } as CSSProperties;
80
+ };
81
+
82
+ return (
83
+ <table
84
+ style={{
85
+ padding: 0,
86
+ margin: 0,
87
+ borderSpacing: 0,
88
+ width: '100%',
89
+ tableLayout: 'fixed',
90
+ }}
91
+ >
92
+ {showHeader && header && (
93
+ <tr style={{ height: header.height, fontWeight: 'bold' }}>
94
+ {headerCellsWithKeys.map(({ cell, key }, idx) => (
95
+ <td
96
+ key={key}
97
+ style={{
98
+ ...headerCellStyle,
99
+ ...(headerCellsWithKeys.length === 1 ||
100
+ idx === headerCellsWithKeys.length - 1
101
+ ? { borderRight: '1px solid #e0e0e0' }
102
+ : {}),
103
+ ...(bodyRowsWithKeys.length === 0
104
+ ? { borderBottom: '1px solid #e0e0e0' }
105
+ : {}),
106
+ }}
107
+ >
108
+ {cell.value}
109
+ </td>
110
+ ))}
111
+ </tr>
112
+ )}
113
+ {bodyRowsWithKeys.map(({ cellKeys, key: rowKey, row }, rowIndex) => (
114
+ <tr key={rowKey} style={{ height: row.height }}>
115
+ {row.cells.map((cell, cellIndex) => (
116
+ <td
117
+ key={cellKeys[cellIndex]}
118
+ style={{
119
+ ...getBodyCellStyle(rowIndex, cellIndex, row),
120
+ }}
121
+ >
122
+ {cell.value}
123
+ </td>
124
+ ))}
125
+ </tr>
126
+ ))}
127
+ </table>
128
+ );
129
+ };
@@ -16,6 +16,7 @@ import { PdfViewDataModel } from './pdf-view-data-model';
16
16
  import { PdfViewESign } from './pdf-view-e-sign';
17
17
  import { PdfViewFieldContainer } from './pdf-view-field-container';
18
18
  import { PdfViewFillable } from './pdf-view-fillable';
19
+ import { PdfViewGeneric } from './pdf-view-generic';
19
20
 
20
21
  export interface PdfViewProps {
21
22
  fields: PdfField[];
@@ -116,6 +117,9 @@ export const PdfView: FC<PdfViewProps> = ({
116
117
  onDataChange={handleDataChange}
117
118
  />
118
119
  )}
120
+ {field.type === FieldTypeEnum.generic && (
121
+ <PdfViewGeneric field={field} />
122
+ )}
119
123
  </PdfViewFieldContainer>
120
124
  ))}
121
125
  </div>
@@ -0,0 +1 @@
1
+ export * from './inline-editable';
@@ -0,0 +1,47 @@
1
+ import { FC, PropsWithChildren, useState } from 'react';
2
+
3
+ interface InlineEditableProps {
4
+ value: string;
5
+ onCommit(value: string): void;
6
+ }
7
+
8
+ export const InlineEditable: FC<PropsWithChildren<InlineEditableProps>> = ({
9
+ children,
10
+ onCommit,
11
+ value,
12
+ }) => {
13
+ const [isActive, setIsActive] = useState(false);
14
+ const [editingValue, setEditingValue] = useState(value);
15
+
16
+ const commit = () => {
17
+ onCommit(editingValue);
18
+ setIsActive(false);
19
+ };
20
+
21
+ return isActive ? (
22
+ <input
23
+ type="text"
24
+ className="inline-editable-input"
25
+ value={editingValue}
26
+ onChange={e => setEditingValue(e.target.value)}
27
+ onBlur={commit}
28
+ onClick={e => e.stopPropagation()}
29
+ autoFocus
30
+ />
31
+ ) : (
32
+ <div
33
+ className="inline-editable-button"
34
+ role="button"
35
+ tabIndex={0}
36
+ onClick={() => setIsActive(true)}
37
+ onKeyDown={e => {
38
+ if (e.key === 'Enter' || e.key === ' ') {
39
+ e.preventDefault();
40
+ setIsActive(true);
41
+ }
42
+ }}
43
+ >
44
+ {children ?? value}
45
+ </div>
46
+ );
47
+ };
@@ -4,6 +4,7 @@ import {
4
4
  FieldTypeOption,
5
5
  PdfFieldSubType,
6
6
  } from '../interface/types';
7
+ import { BASE_PAGE_WIDTH } from './pdf-editor.constants';
7
8
 
8
9
  export const FIELD_CONSTANTS = {
9
10
  defaultWidth: 200,
@@ -21,6 +22,13 @@ export const FILLABLE_FIELD_DEFAULT_SIZES = {
21
22
  checkbox: { width: 20, height: 20 },
22
23
  } as Record<PdfFieldSubType, { width: number; height: number }>;
23
24
 
25
+ export const GENERIC_FIELD_TABLE_ROW_HEIGHT = 40;
26
+
27
+ export const GENERIC_FIELD_DEFAULT_SIZES = {
28
+ text: { width: BASE_PAGE_WIDTH, height: 40 },
29
+ table: { width: BASE_PAGE_WIDTH, height: 2 * GENERIC_FIELD_TABLE_ROW_HEIGHT },
30
+ } as Record<PdfFieldSubType, { width: number; height: number }>;
31
+
24
32
  export const E_SIGN_FIELD_TYPE_OPTIONS: { name: string; id: ESignFieldType }[] = [
25
33
  { name: 'Signature', id: ESignFieldType.signature },
26
34
  { name: 'Initials', id: ESignFieldType.initials },
@@ -50,3 +58,22 @@ export const CALCULATED_FIELD_TYPES: FieldTypeOption[] = [
50
58
  type: FieldTypeEnum.calculated,
51
59
  },
52
60
  ];
61
+
62
+ export const GENERIC_FIELD_TYPES: FieldTypeOption[] = [
63
+ {
64
+ label: 'Text',
65
+ type: FieldTypeEnum.generic,
66
+ subType: 'text',
67
+ },
68
+ {
69
+ label: 'Table',
70
+ type: FieldTypeEnum.generic,
71
+ subType: 'table',
72
+ },
73
+ ];
74
+
75
+ export const HIDE_FIELD_RESIZE = {
76
+ [FieldTypeEnum.generic]: {
77
+ table: true,
78
+ },
79
+ } as Record<FieldTypeEnum, Record<PdfFieldSubType, boolean>>;
@@ -1,7 +1,8 @@
1
1
  import IconBorderColor from '@servicetitan/anvil2/assets/icons/material/round/border_color.svg';
2
2
  import IconCalculate from '@servicetitan/anvil2/assets/icons/material/round/calculate.svg';
3
3
  import IconPhotoSizeSelectSmall from '@servicetitan/anvil2/assets/icons/material/round/photo_size_select_small.svg';
4
- import IconDataObject from '@servicetitan/anvil2/assets/icons/material/round/data_object.svg';
4
+ import IconCommercial from '@servicetitan/anvil2/assets/icons/st/commercial.svg';
5
+ import IconEstimate from '@servicetitan/anvil2/assets/icons/st/estimate.svg';
5
6
  import { FieldTypeEnum } from '../interface/types';
6
7
 
7
8
  export interface MenuGroupModel {
@@ -11,7 +12,7 @@ export interface MenuGroupModel {
11
12
  }
12
13
 
13
14
  export const menuGroups: MenuGroupModel[] = [
14
- { svgIcon: IconDataObject, label: 'Merge Tags', key: FieldTypeEnum.dataModel },
15
+ { svgIcon: IconEstimate, label: 'Merge Tags', key: FieldTypeEnum.dataModel },
15
16
  { svgIcon: IconBorderColor, label: 'E-Sign', key: FieldTypeEnum.eSign },
16
17
  {
17
18
  svgIcon: IconPhotoSizeSelectSmall,
@@ -23,4 +24,9 @@ export const menuGroups: MenuGroupModel[] = [
23
24
  label: 'Calculated Fields',
24
25
  key: FieldTypeEnum.calculated,
25
26
  },
27
+ {
28
+ svgIcon: IconCommercial,
29
+ label: 'Generic Fields',
30
+ key: FieldTypeEnum.generic,
31
+ },
26
32
  ];
@@ -127,10 +127,13 @@ export const useFormulaEditor = (params: {
127
127
  if (!editor) {
128
128
  return;
129
129
  }
130
- const inserted = `${path} `;
131
130
  const caretIndex = getCaretIndexForInsert();
132
131
  const before = draftExpression.slice(0, caretIndex);
133
132
  const after = draftExpression.slice(caretIndex);
133
+ const lastCharBefore = before.trimEnd().slice(-1);
134
+ const needsOperatorBefore =
135
+ lastCharBefore.length > 0 && /[A-Za-z0-9_.]/.test(lastCharBefore);
136
+ const inserted = needsOperatorBefore ? ` ${path} ` : `${path} `;
134
137
  const nextValue = `${before}${inserted}${after}`;
135
138
  updateSourceRef.current = 'state';
136
139
  pushHistory(nextValue, caretIndex + inserted.length);