@servicetitan/dte-pdf-editor 1.56.0 → 1.58.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 (87) hide show
  1. package/README.md +25 -1
  2. package/dist/components/display-conditions/condition-row.js +2 -2
  3. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts.map +1 -1
  4. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js +2 -2
  5. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js.map +1 -1
  6. package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.d.ts.map +1 -1
  7. package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.js +2 -1
  8. package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.js.map +1 -1
  9. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.d.ts.map +1 -1
  10. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js +1 -1
  11. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js.map +1 -1
  12. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
  13. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +14 -7
  14. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
  15. package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js +9 -1
  16. package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js.map +1 -1
  17. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  18. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +5 -11
  19. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  20. package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -1
  21. package/dist/components/pdf-view/pdf-view-calculated.js +2 -2
  22. package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -1
  23. package/dist/components/pdf-view/pdf-view-data-model.js +2 -2
  24. package/dist/components/pdf-view/pdf-view-data-model.js.map +1 -1
  25. package/dist/components/pdf-view/pdf-view-e-sign.d.ts.map +1 -1
  26. package/dist/components/pdf-view/pdf-view-e-sign.js +1 -1
  27. package/dist/components/pdf-view/pdf-view-e-sign.js.map +1 -1
  28. package/dist/components/pdf-view/pdf-view-field-container.d.ts.map +1 -1
  29. package/dist/components/pdf-view/pdf-view-field-container.js +5 -15
  30. package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
  31. package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
  32. package/dist/components/pdf-view/pdf-view-fillable.js +22 -33
  33. package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
  34. package/dist/components/pdf-view/pdf-view-generic.js +1 -1
  35. package/dist/components/pdf-view/pdf-view-generic.js.map +1 -1
  36. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  37. package/dist/components/pdf-view/pdf-view.js +1 -1
  38. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  39. package/dist/constants/field.constants.d.ts +3 -0
  40. package/dist/constants/field.constants.d.ts.map +1 -1
  41. package/dist/constants/field.constants.js +3 -0
  42. package/dist/constants/field.constants.js.map +1 -1
  43. package/dist/interface/types.d.ts +2 -0
  44. package/dist/interface/types.d.ts.map +1 -1
  45. package/dist/interface/types.js.map +1 -1
  46. package/dist/utils/field/get-field-bounds-style.utils.d.ts +20 -0
  47. package/dist/utils/field/get-field-bounds-style.utils.d.ts.map +1 -0
  48. package/dist/utils/field/get-field-bounds-style.utils.js +53 -0
  49. package/dist/utils/field/get-field-bounds-style.utils.js.map +1 -0
  50. package/dist/utils/field/get-field-text-clip.utils.d.ts +13 -0
  51. package/dist/utils/field/get-field-text-clip.utils.d.ts.map +1 -0
  52. package/dist/utils/field/get-field-text-clip.utils.js +25 -0
  53. package/dist/utils/field/get-field-text-clip.utils.js.map +1 -0
  54. package/dist/utils/field/get-field-typography.utils.d.ts +10 -0
  55. package/dist/utils/field/get-field-typography.utils.d.ts.map +1 -0
  56. package/dist/utils/field/get-field-typography.utils.js +20 -0
  57. package/dist/utils/field/get-field-typography.utils.js.map +1 -0
  58. package/dist/utils/field/index.d.ts +3 -0
  59. package/dist/utils/field/index.d.ts.map +1 -1
  60. package/dist/utils/field/index.js +3 -0
  61. package/dist/utils/field/index.js.map +1 -1
  62. package/package.json +1 -1
  63. package/src/components/display-conditions/condition-row.tsx +2 -2
  64. package/src/components/pdf-fields-overlay/pdf-overlay-field-calculated.tsx +5 -2
  65. package/src/components/pdf-fields-overlay/pdf-overlay-field-data-model.tsx +2 -1
  66. package/src/components/pdf-fields-overlay/pdf-overlay-field-e-sign.tsx +3 -1
  67. package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +28 -8
  68. package/src/components/pdf-fields-overlay/pdf-overlay-field-generic.tsx +9 -1
  69. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +9 -13
  70. package/src/components/pdf-view/pdf-view-calculated.tsx +9 -2
  71. package/src/components/pdf-view/pdf-view-data-model.tsx +2 -2
  72. package/src/components/pdf-view/pdf-view-e-sign.tsx +3 -4
  73. package/src/components/pdf-view/pdf-view-field-container.tsx +6 -16
  74. package/src/components/pdf-view/pdf-view-fillable.tsx +29 -28
  75. package/src/components/pdf-view/pdf-view-generic.tsx +1 -1
  76. package/src/components/pdf-view/pdf-view.tsx +4 -1
  77. package/src/constants/field.constants.ts +5 -0
  78. package/src/interface/types.ts +2 -0
  79. package/src/styles/inline-editable.css +1 -0
  80. package/src/styles/pdf-field-overlay.css +80 -27
  81. package/src/utils/field/__tests__/get-field-bounds-style.utils.test.ts +50 -0
  82. package/src/utils/field/__tests__/get-field-text-clip.utils.test.ts +43 -0
  83. package/src/utils/field/__tests__/get-field-typography.utils.test.ts +56 -0
  84. package/src/utils/field/get-field-bounds-style.utils.ts +65 -0
  85. package/src/utils/field/get-field-text-clip.utils.ts +31 -0
  86. package/src/utils/field/get-field-typography.utils.ts +23 -0
  87. package/src/utils/field/index.ts +3 -0
@@ -1,6 +1,11 @@
1
1
  import { FC, useMemo } from 'react';
2
2
  import { DataModelValues, PdfField, PdfViewMode } from '../../interface/types';
3
- import { evaluateFormula, formatCalculatedResult, resolvePdfDataValues } from '../../utils';
3
+ import {
4
+ evaluateFormula,
5
+ formatCalculatedResult,
6
+ getFieldTextClipClassName,
7
+ resolvePdfDataValues,
8
+ } from '../../utils';
4
9
 
5
10
  interface PdfViewCalculatedProps {
6
11
  field: PdfField;
@@ -33,5 +38,7 @@ export const PdfViewCalculated: FC<PdfViewCalculatedProps> = ({
33
38
  const shouldShowPreviewFallback = isPreview && displayValue === '';
34
39
  const content = shouldShowPreviewFallback ? field.label || 'Calculated Field' : displayValue;
35
40
 
36
- return <div className="dte-pdf-field-value">{content}</div>;
41
+ return (
42
+ <div className={`dte-pdf-field-value ${getFieldTextClipClassName(field)}`}>{content}</div>
43
+ );
37
44
  };
@@ -1,6 +1,6 @@
1
1
  import { FC } from 'react';
2
2
  import { DataModelValues, PdfField } from '../../interface/types';
3
- import { nl2br, resolvePdfDataValues } from '../../utils';
3
+ import { getFieldTextClipClassName, nl2br, resolvePdfDataValues } from '../../utils';
4
4
 
5
5
  interface PdfViewDataModelProps {
6
6
  field: PdfField;
@@ -16,7 +16,7 @@ export const PdfViewDataModel: FC<PdfViewDataModelProps> = ({ data, field }) =>
16
16
  */
17
17
  return (
18
18
  <div
19
- className="dte-pdf-field-value"
19
+ className={`dte-pdf-field-value ${getFieldTextClipClassName(field)}`}
20
20
  // eslint-disable-next-line react/no-danger, @typescript-eslint/naming-convention
21
21
  dangerouslySetInnerHTML={{ __html: nl2br(resolvedValue ?? '') }}
22
22
  />
@@ -9,10 +9,9 @@ export const PdfViewESign: FC<PdfViewESignProps> = ({ field }) => {
9
9
  return (
10
10
  <span>
11
11
  {field.label} {field.required ? '*' : ''}
12
- <div
13
- className="dte-pdf-field-value"
14
- style={{ opacity: 0.1, color: '#fff' }}
15
- >{`{{${field.path}}}`}</div>
12
+ <div className="dte-pdf-field-value" style={{ fontSize: '8px' }}>
13
+ {field.path}
14
+ </div>
16
15
  </span>
17
16
  );
18
17
  };
@@ -3,9 +3,8 @@ import { DataModelValues, PdfField } from '../../interface/types';
3
3
  import {
4
4
  evaluateDisplayCondition,
5
5
  getFieldBackgroundColor,
6
- getFieldFontSizeInPixels,
6
+ getFieldBoundsStyle,
7
7
  getPagePosition,
8
- supportsFieldFontSize,
9
8
  } from '../../utils';
10
9
 
11
10
  interface PdfViewFieldContainer {
@@ -35,21 +34,12 @@ export const PdfViewFieldContainer: FC<PropsWithChildren<PdfViewFieldContainer>>
35
34
  const bgColor = getFieldBackgroundColor(field.recipient, recipientsColors);
36
35
  return (
37
36
  <div
38
- style={{
39
- display: 'flex',
40
- alignItems: 'center',
41
- position: 'absolute',
42
- padding: '4px',
43
- background: bgColor,
44
- left: `${pagePos.left + field.x}px`,
45
- top: `${pagePos.top + field.y}px`,
46
- width: `${field.width}px`,
47
- height: `${field.height}px`,
48
- ...(supportsFieldFontSize(field)
49
- ? { fontSize: `${getFieldFontSizeInPixels(field)}px` }
50
- : {}),
37
+ className="dte-pdf-field-view"
38
+ style={getFieldBoundsStyle(field, {
39
+ pageOffset: pagePos,
40
+ backgroundColor: bgColor,
51
41
  borderColor: error ? 'red' : undefined,
52
- }}
42
+ })}
53
43
  >
54
44
  {children}
55
45
  {error && (
@@ -1,6 +1,11 @@
1
1
  import { FC } from 'react';
2
2
  import { DataChangePayload, DataModelValues, PdfField, PdfViewMode } from '../../interface/types';
3
- import { dateToUtcZero, getFieldPlaceholderText, toDateInputValue } from '../../utils';
3
+ import {
4
+ dateToUtcZero,
5
+ getFieldPlaceholderText,
6
+ getFieldTextClipClassName,
7
+ toDateInputValue,
8
+ } from '../../utils';
4
9
 
5
10
  interface PdfViewFillableProps {
6
11
  field: PdfField;
@@ -21,10 +26,13 @@ interface FillableTextInputProps {
21
26
 
22
27
  const fillableTextStyles = {
23
28
  background: 'inherit',
24
- width: 'inherit',
25
- height: 'inherit',
26
- borderColor: 'inherit',
29
+ width: '100%',
30
+ height: '100%',
31
+ border: 'none',
27
32
  fontSize: 'inherit',
33
+ boxSizing: 'border-box' as const,
34
+ padding: 0,
35
+ margin: 0,
28
36
  } as const;
29
37
 
30
38
  export const FillableTextInput: FC<FillableTextInputProps> = ({
@@ -38,6 +46,7 @@ export const FillableTextInput: FC<FillableTextInputProps> = ({
38
46
  <input
39
47
  readOnly={readOnly}
40
48
  type="text"
49
+ className={`dte-pdf-field-fillable ${getFieldTextClipClassName(field)}`}
41
50
  style={fillableTextStyles}
42
51
  disabled={disabled}
43
52
  id={field.path}
@@ -59,6 +68,7 @@ export const FillableTextareaInput: FC<FillableTextInputProps> = ({
59
68
  return (
60
69
  <textarea
61
70
  readOnly={readOnly}
71
+ className={`dte-pdf-field-fillable ${getFieldTextClipClassName(field)}`}
62
72
  style={{ ...fillableTextStyles, resize: 'none' }}
63
73
  disabled={disabled}
64
74
  id={field.path}
@@ -96,20 +106,20 @@ export const PdfViewFillable: FC<PdfViewFillableProps> = ({
96
106
  if (field.subType === 'checkbox') {
97
107
  return (
98
108
  <div
109
+ className="dte-pdf-field-content"
99
110
  style={{
100
111
  display: 'flex',
101
112
  alignItems: 'center',
102
113
  background: 'inherit',
103
- width: 'inherit',
104
- height: 'inherit',
105
114
  }}
106
115
  >
107
116
  <input
117
+ title={field.label}
108
118
  style={{
109
- width: 'inherit',
110
- height: 'inherit',
119
+ width: field.width,
120
+ height: field.height,
111
121
  margin: 0,
112
- borderColor: 'inherit',
122
+ border: 'none',
113
123
  }}
114
124
  type="checkbox"
115
125
  checked={resolvedValue ?? false}
@@ -125,20 +135,21 @@ export const PdfViewFillable: FC<PdfViewFillableProps> = ({
125
135
  if (field.subType === 'radio') {
126
136
  return (
127
137
  <div
138
+ className="dte-pdf-field-content"
128
139
  style={{
129
140
  display: 'flex',
130
141
  alignItems: 'center',
131
142
  background: 'inherit',
132
- width: 'inherit',
133
- height: 'inherit',
143
+ width: '100%',
144
+ height: '100%',
134
145
  }}
135
146
  >
136
147
  <input
137
148
  style={{
138
- width: 'inherit',
139
- height: 'inherit',
149
+ width: field.width,
150
+ height: field.height,
140
151
  margin: 0,
141
- borderColor: 'inherit',
152
+ border: 'none',
142
153
  }}
143
154
  type="radio"
144
155
  value={field.id}
@@ -158,13 +169,8 @@ export const PdfViewFillable: FC<PdfViewFillableProps> = ({
158
169
  <input
159
170
  readOnly={readOnly}
160
171
  type="date"
161
- style={{
162
- background: 'inherit',
163
- width: 'inherit',
164
- height: 'inherit',
165
- borderColor: 'inherit',
166
- fontSize: 'inherit',
167
- }}
172
+ className={`dte-pdf-field-fillable ${getFieldTextClipClassName(field)}`}
173
+ style={fillableTextStyles}
168
174
  disabled={!isFillable}
169
175
  id={field.path}
170
176
  name={field.path}
@@ -183,13 +189,8 @@ export const PdfViewFillable: FC<PdfViewFillableProps> = ({
183
189
  <input
184
190
  readOnly={readOnly}
185
191
  type="number"
186
- style={{
187
- background: 'inherit',
188
- width: 'inherit',
189
- height: 'inherit',
190
- borderColor: 'inherit',
191
- fontSize: 'inherit',
192
- }}
192
+ className={`dte-pdf-field-fillable ${getFieldTextClipClassName(field)}`}
193
+ style={fillableTextStyles}
193
194
  disabled={!isFillable}
194
195
  id={field.path}
195
196
  name={field.path}
@@ -24,7 +24,7 @@ interface GenericFieldTextProps {
24
24
 
25
25
  export const GenericFieldText: FC<GenericFieldTextProps> = ({ data }) => {
26
26
  return (
27
- <div className="dte-pdf-field-value">
27
+ <div className="dte-pdf-field-value dte-pdf-field-text--wrapped">
28
28
  <pre className="inline-editable-html-text">{data?.value ?? ''}</pre>
29
29
  </div>
30
30
  );
@@ -111,7 +111,10 @@ export const PdfView: FC<PdfViewProps> = ({
111
111
  >
112
112
  {(field.type === FieldTypeEnum.dataModel ||
113
113
  field.type === FieldTypeEnum.forms) && (
114
- <PdfViewDataModel field={field} data={data} />
114
+ <PdfViewDataModel
115
+ field={field}
116
+ data={viewMode === 'preview' ? previewData : data}
117
+ />
115
118
  )}
116
119
  {field.type === FieldTypeEnum.eSign && (
117
120
  <PdfViewESign field={field} />
@@ -20,6 +20,11 @@ export const FIELD_CONSTANTS = {
20
20
 
21
21
  export const DEFAULT_FIELD_FONT_SIZE = 8;
22
22
 
23
+ /** Matches the PdfSharp default face used by `/conversions/templatetopdf`. */
24
+ export const DEFAULT_FIELD_FONT_FAMILY = 'Arial, Helvetica, sans-serif';
25
+
26
+ export const DEFAULT_FIELD_FONT_WEIGHT = 'normal' as const;
27
+
23
28
  export const FIELD_FONT_SIZE_OPTIONS = Array.from({ length: 17 }, (_, index) => {
24
29
  return 4 + index;
25
30
  });
@@ -85,6 +85,8 @@ export interface DisplayConditionSingle {
85
85
 
86
86
  export interface PdfFieldStyles {
87
87
  fontSize?: number;
88
+ fontFamily?: string;
89
+ fontWeight?: 'normal' | 'bold';
88
90
  }
89
91
 
90
92
  export interface PdfField {
@@ -18,4 +18,5 @@
18
18
  .inline-editable-html-text {
19
19
  margin: var(--spacing-0, 0);
20
20
  white-space: pre-wrap;
21
+ font-family: 'Arial', sans-serif;
21
22
  }
@@ -11,38 +11,34 @@
11
11
  pointer-events: auto;
12
12
  }
13
13
 
14
- .dte-pdf-field {
15
- position: absolute;
16
- border: 1px dashed #999;
17
- display: flex;
18
- align-items: center;
19
- font-size: var(--typescale-1, 14px);
20
- padding: var(--spacing-half, 4px);
21
- box-sizing: content-box;
14
+ /* Editor-only chrome. Layout metrics come from getFieldBoundsStyle. */
15
+ .dte-pdf-field-editor {
16
+ outline: 1px dashed #999;
17
+ outline-offset: 0;
18
+ border: none;
22
19
  pointer-events: auto;
23
20
  }
24
21
 
25
- .dte-pdf-field.--selected {
26
- border: 2px solid var(--border-color-active);
22
+ .dte-pdf-field-editor.--selected {
23
+ outline: 2px solid var(--border-color-active);
27
24
  cursor: move;
28
25
  }
29
26
 
30
- .dte-pdf-field.--unselected {
27
+ .dte-pdf-field-editor.--unselected {
31
28
  cursor: pointer;
32
29
  }
33
30
 
34
- .dte-pdf-field.--dragging {
31
+ .dte-pdf-field-editor.--dragging {
35
32
  opacity: 0.5;
36
- will-change: left, top;
37
33
  }
38
34
 
39
- .dte-pdf-field.--colored:not(.--selected) {
40
- border-color: var(--border-color-active) !important;
35
+ .dte-pdf-field-editor.--colored:not(.--selected) {
36
+ outline-color: var(--border-color-active) !important;
41
37
  box-shadow: 0 0 2px 0 var(--border-color-active);
42
38
  }
43
39
 
44
- .dte-pdf-field.--error {
45
- border-color: var(--danger) !important;
40
+ .dte-pdf-field-editor.--error {
41
+ outline-color: var(--danger) !important;
46
42
  box-shadow: 0 0 4px 0 var(--danger);
47
43
  }
48
44
 
@@ -82,38 +78,95 @@
82
78
  height: 20px;
83
79
  }
84
80
 
85
- .dte-pdf-field:hover .dte-add-field-button {
81
+ .dte-pdf-field-editor:hover .dte-add-field-button {
86
82
  display: block;
87
83
  }
88
84
 
89
85
  .dte-pdf-field-view {
90
86
  border: none;
87
+ outline: none;
91
88
  cursor: default;
89
+ pointer-events: auto;
90
+ }
91
+
92
+ .dte-pdf-field-content {
93
+ width: 100%;
94
+ min-width: 0;
95
+ min-height: 0;
92
96
  overflow: hidden;
97
+ line-height: 1;
98
+ }
99
+
100
+ /*
101
+ * Printed PDF text modes (no ellipsis — hard clip like PdfSharp).
102
+ */
103
+ .dte-pdf-field-text--single-line {
104
+ width: 100%;
105
+ min-width: 0;
106
+ line-height: 1;
93
107
  white-space: nowrap;
94
- pointer-events: none;
95
- justify-content: left;
96
- text-overflow: ellipsis;
108
+ overflow: hidden;
97
109
  }
98
110
 
99
- .dte-pdf-field-value {
100
- font-weight: normal;
101
- color: #333;
111
+ .dte-pdf-field-text--multiline {
112
+ width: 100%;
113
+ height: 100%;
114
+ min-width: 0;
115
+ min-height: 0;
116
+ line-height: 1;
117
+ white-space: pre-wrap;
118
+ overflow-wrap: break-word;
119
+ overflow: hidden;
120
+ }
121
+
122
+ .dte-pdf-field-text--wrapped {
123
+ width: 100%;
124
+ min-width: 0;
125
+ line-height: 1;
102
126
  white-space: normal;
127
+ overflow-wrap: break-word;
103
128
  overflow: hidden;
104
- text-overflow: ellipsis;
129
+ }
130
+
131
+ .dte-pdf-field-value {
132
+ color: #333;
133
+ font: inherit;
105
134
  }
106
135
 
107
136
  .dte-pdf-field-fillable {
108
137
  width: 100%;
109
138
  height: 100%;
139
+ min-width: 0;
140
+ min-height: 0;
141
+ box-sizing: border-box;
110
142
  background: inherit;
111
143
  border: none;
112
- pointer-events: none;
113
- margin: var(--spacing-0, 0);
144
+ padding: var(--spacing-0);
145
+ margin: var(--spacing-0);
146
+ line-height: 1;
114
147
  font-size: inherit;
115
148
  }
116
149
 
150
+ .dte-pdf-field-editor .dte-pdf-field-fillable {
151
+ pointer-events: none;
152
+ }
153
+
154
+ .dte-pdf-field-view .dte-pdf-field-fillable {
155
+ pointer-events: auto;
156
+ }
157
+
158
+ .dte-pdf-field-fillable.dte-pdf-field-text--single-line {
159
+ white-space: nowrap;
160
+ overflow: hidden;
161
+ }
162
+
163
+ .dte-pdf-field-fillable.dte-pdf-field-text--multiline {
164
+ white-space: pre-wrap;
165
+ overflow-wrap: break-word;
166
+ overflow: hidden;
167
+ resize: none;
168
+ }
169
+
117
170
  .dte-pdf-field-generic-table {
118
171
  padding: var(--spacing-0, 0);
119
172
  margin: var(--spacing-0, 0);
@@ -0,0 +1,50 @@
1
+ import { FieldTypeEnum, PdfField } from '../../../interface/types';
2
+ import { getFieldBoundsStyle } from '../get-field-bounds-style.utils';
3
+
4
+ const buildField = (overrides: Partial<PdfField> = {}): PdfField => ({
5
+ id: 'field-1',
6
+ type: FieldTypeEnum.fillable,
7
+ subType: 'text',
8
+ x: 10,
9
+ y: 20,
10
+ page: 1,
11
+ label: 'Label',
12
+ width: 200,
13
+ height: 25,
14
+ ...overrides,
15
+ });
16
+
17
+ describe('getFieldBoundsStyle', () => {
18
+ const subject = (field: PdfField, options?: Parameters<typeof getFieldBoundsStyle>[1]) =>
19
+ getFieldBoundsStyle(field, options);
20
+
21
+ test('positions the field using page offset and field coordinates', () => {
22
+ expect(subject(buildField(), { pageOffset: { left: 5, top: 15 } })).toMatchObject({
23
+ left: 15,
24
+ top: 35,
25
+ width: 200,
26
+ height: 25,
27
+ });
28
+ });
29
+
30
+ test('matches printed PDF clip rect layout', () => {
31
+ expect(subject(buildField())).toMatchObject({
32
+ boxSizing: 'border-box',
33
+ padding: 0,
34
+ alignItems: 'flex-start',
35
+ justifyContent: 'flex-start',
36
+ overflow: 'hidden',
37
+ lineHeight: 1,
38
+ });
39
+ });
40
+
41
+ test('converts font size from points to editor pixels', () => {
42
+ const style = subject(buildField({ styles: { fontSize: 12 } }));
43
+ expect(style.fontSize).toBe(`${(12 * 980) / 612}px`);
44
+ });
45
+
46
+ test('omits font size for field types that do not support it', () => {
47
+ const style = subject(buildField({ type: FieldTypeEnum.eSign, subType: 'signature' }));
48
+ expect(style.fontSize).toBeUndefined();
49
+ });
50
+ });
@@ -0,0 +1,43 @@
1
+ import { FieldTypeEnum, PdfField } from '../../../interface/types';
2
+ import { getFieldTextClipClassName, getFieldTextClipMode } from '../get-field-text-clip.utils';
3
+
4
+ const buildField = (overrides: Partial<PdfField> = {}): PdfField => ({
5
+ id: 'field-1',
6
+ type: FieldTypeEnum.fillable,
7
+ subType: 'text',
8
+ x: 0,
9
+ y: 0,
10
+ page: 1,
11
+ label: 'Label',
12
+ width: 200,
13
+ height: 25,
14
+ ...overrides,
15
+ });
16
+
17
+ describe('getFieldTextClipMode', () => {
18
+ const subject = (field: PdfField) => getFieldTextClipMode(field);
19
+
20
+ test('uses multiline for multiline fillable fields', () => {
21
+ expect(subject(buildField({ multiline: true }))).toBe('multiline');
22
+ });
23
+
24
+ test('uses single-line for standard fillable fields', () => {
25
+ expect(subject(buildField())).toBe('single-line');
26
+ });
27
+
28
+ test('uses wrapped for data model and generic text fields', () => {
29
+ expect(subject(buildField({ type: FieldTypeEnum.dataModel }))).toBe('wrapped');
30
+ expect(
31
+ subject(buildField({ type: FieldTypeEnum.generic, subType: 'text' })),
32
+ ).toBe('wrapped');
33
+ });
34
+ });
35
+
36
+ describe('getFieldTextClipClassName', () => {
37
+ const subject = (field: PdfField) => getFieldTextClipClassName(field);
38
+
39
+ test('maps mode to css class', () => {
40
+ expect(subject(buildField())).toBe('dte-pdf-field-text--single-line');
41
+ expect(subject(buildField({ multiline: true }))).toBe('dte-pdf-field-text--multiline');
42
+ });
43
+ });
@@ -0,0 +1,56 @@
1
+ import { FieldTypeEnum, PdfField } from '../../../interface/types';
2
+ import {
3
+ DEFAULT_FIELD_FONT_FAMILY,
4
+ DEFAULT_FIELD_FONT_WEIGHT,
5
+ } from '../../../constants/field.constants';
6
+ import { getFieldTypographyStyle } from '../get-field-typography.utils';
7
+
8
+ const buildField = (overrides: Partial<PdfField> = {}): PdfField => ({
9
+ id: 'field-1',
10
+ type: FieldTypeEnum.fillable,
11
+ subType: 'text',
12
+ x: 0,
13
+ y: 0,
14
+ page: 1,
15
+ label: 'Label',
16
+ width: 200,
17
+ height: 25,
18
+ ...overrides,
19
+ });
20
+
21
+ describe('getFieldTypographyStyle', () => {
22
+ const subject = (field: PdfField) => getFieldTypographyStyle(field);
23
+
24
+ test('applies backend default font family and weight', () => {
25
+ expect(subject(buildField())).toEqual({
26
+ fontFamily: DEFAULT_FIELD_FONT_FAMILY,
27
+ fontWeight: DEFAULT_FIELD_FONT_WEIGHT,
28
+ fontSize: `${(8 * 980) / 612}px`,
29
+ });
30
+ });
31
+
32
+ test('uses per-field typography overrides when provided', () => {
33
+ expect(
34
+ subject(
35
+ buildField({
36
+ styles: {
37
+ fontSize: 12,
38
+ fontFamily: 'Times New Roman',
39
+ fontWeight: 'bold',
40
+ },
41
+ }),
42
+ ),
43
+ ).toEqual({
44
+ fontFamily: 'Times New Roman',
45
+ fontWeight: 'bold',
46
+ fontSize: `${(12 * 980) / 612}px`,
47
+ });
48
+ });
49
+
50
+ test('omits font size for unsupported field types', () => {
51
+ expect(subject(buildField({ type: FieldTypeEnum.eSign, subType: 'signature' }))).toEqual({
52
+ fontFamily: DEFAULT_FIELD_FONT_FAMILY,
53
+ fontWeight: DEFAULT_FIELD_FONT_WEIGHT,
54
+ });
55
+ });
56
+ });
@@ -0,0 +1,65 @@
1
+ import { CSSProperties } from 'react';
2
+ import { FieldTypeEnum, PdfField, PdfFieldSubType } from '../../interface/types';
3
+ import { getFieldTypographyStyle } from './get-field-typography.utils';
4
+
5
+ interface FieldBoundsStyleOptions {
6
+ pageOffset?: { left: number; top: number };
7
+ backgroundColor?: string;
8
+ borderColor?: string;
9
+ }
10
+
11
+ const getFieldTypeSpecificStyle = (field: PdfField): CSSProperties => {
12
+ if (!field.subType) {
13
+ return {};
14
+ }
15
+ const styles = {
16
+ [FieldTypeEnum.fillable]: {
17
+ checkbox: {
18
+ width: 'unset',
19
+ height: 'unset',
20
+ },
21
+ radio: {
22
+ width: 'unset',
23
+ height: 'unset',
24
+ },
25
+ },
26
+ } as Record<FieldTypeEnum, Record<PdfFieldSubType, CSSProperties>>;
27
+
28
+ return styles[field.type]?.[field.subType] ?? {};
29
+ };
30
+
31
+ /**
32
+ * Shared absolute-position style for PDF field overlays.
33
+ *
34
+ * Matches the backend `/conversions/templatetopdf` clip rectangle and
35
+ * `PdfView` preview: content-box sizing, top-left alignment,
36
+ * and shared typography (font size / family / weight).
37
+ */
38
+ export const getFieldBoundsStyle = (
39
+ field: PdfField,
40
+ options?: FieldBoundsStyleOptions,
41
+ ): CSSProperties => {
42
+ const { backgroundColor, borderColor, pageOffset = { left: 0, top: 0 } } = options ?? {};
43
+
44
+ return {
45
+ position: 'absolute',
46
+ left: pageOffset.left + field.x,
47
+ top: pageOffset.top + field.y,
48
+ width: field.width,
49
+ height: field.height,
50
+ display: 'flex',
51
+ alignItems: 'flex-start',
52
+ justifyContent: 'flex-start',
53
+ boxSizing: 'content-box',
54
+ padding: 4,
55
+ margin: 0,
56
+ minWidth: 0,
57
+ minHeight: 0,
58
+ overflow: 'hidden',
59
+ lineHeight: 1,
60
+ ...getFieldTypeSpecificStyle(field),
61
+ ...getFieldTypographyStyle(field),
62
+ ...(backgroundColor ? { backgroundColor } : {}),
63
+ ...(borderColor ? { borderColor } : {}),
64
+ };
65
+ };
@@ -0,0 +1,31 @@
1
+ import { FieldTypeEnum, PdfField } from '../../interface/types';
2
+
3
+ export type FieldTextClipMode = 'single-line' | 'multiline' | 'wrapped';
4
+
5
+ /**
6
+ * Text layout mode for a field overlay.
7
+ *
8
+ * Mirrors the backend PDF renderer:
9
+ * - single-line: no wrap, hard clip at the field rect (no ellipsis)
10
+ * - multiline: pre-wrap with word breaks, clip overflow vertically
11
+ * - wrapped: word-wrap for HTML / long prose blocks
12
+ */
13
+ export const getFieldTextClipMode = (field: PdfField): FieldTextClipMode => {
14
+ if (field.type === FieldTypeEnum.fillable && field.multiline) {
15
+ return 'multiline';
16
+ }
17
+
18
+ if (field.type === FieldTypeEnum.generic && field.subType === 'text') {
19
+ return 'wrapped';
20
+ }
21
+
22
+ if (field.type === FieldTypeEnum.dataModel || field.type === FieldTypeEnum.forms) {
23
+ return 'wrapped';
24
+ }
25
+
26
+ return 'single-line';
27
+ };
28
+
29
+ export const getFieldTextClipClassName = (field: PdfField) => {
30
+ return `dte-pdf-field-text--${getFieldTextClipMode(field)}`;
31
+ };