@servicetitan/dte-pdf-editor 1.55.0 → 1.57.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 (104) hide show
  1. package/README.md +25 -1
  2. package/dist/components/display-conditions/condition-row.d.ts.map +1 -1
  3. package/dist/components/display-conditions/condition-row.js +5 -1
  4. package/dist/components/display-conditions/condition-row.js.map +1 -1
  5. package/dist/components/display-conditions/condition-value-control.d.ts +14 -2
  6. package/dist/components/display-conditions/condition-value-control.d.ts.map +1 -1
  7. package/dist/components/display-conditions/condition-value-control.js +84 -13
  8. package/dist/components/display-conditions/condition-value-control.js.map +1 -1
  9. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts.map +1 -1
  10. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js +2 -2
  11. package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js.map +1 -1
  12. package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.d.ts.map +1 -1
  13. package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.js +2 -1
  14. package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.js.map +1 -1
  15. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.d.ts.map +1 -1
  16. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js +1 -1
  17. package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js.map +1 -1
  18. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
  19. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +14 -7
  20. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
  21. package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js +9 -1
  22. package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js.map +1 -1
  23. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  24. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +5 -11
  25. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  26. package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -1
  27. package/dist/components/pdf-view/pdf-view-calculated.js +2 -2
  28. package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -1
  29. package/dist/components/pdf-view/pdf-view-data-model.js +2 -2
  30. package/dist/components/pdf-view/pdf-view-data-model.js.map +1 -1
  31. package/dist/components/pdf-view/pdf-view-e-sign.d.ts.map +1 -1
  32. package/dist/components/pdf-view/pdf-view-e-sign.js +1 -1
  33. package/dist/components/pdf-view/pdf-view-e-sign.js.map +1 -1
  34. package/dist/components/pdf-view/pdf-view-field-container.d.ts.map +1 -1
  35. package/dist/components/pdf-view/pdf-view-field-container.js +5 -15
  36. package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
  37. package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
  38. package/dist/components/pdf-view/pdf-view-fillable.js +22 -33
  39. package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
  40. package/dist/components/pdf-view/pdf-view-generic.js +1 -1
  41. package/dist/components/pdf-view/pdf-view-generic.js.map +1 -1
  42. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  43. package/dist/components/pdf-view/pdf-view.js +1 -1
  44. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  45. package/dist/constants/field.constants.d.ts +3 -0
  46. package/dist/constants/field.constants.d.ts.map +1 -1
  47. package/dist/constants/field.constants.js +3 -0
  48. package/dist/constants/field.constants.js.map +1 -1
  49. package/dist/hooks/useLookupOptions.d.ts +24 -8
  50. package/dist/hooks/useLookupOptions.d.ts.map +1 -1
  51. package/dist/hooks/useLookupOptions.js +47 -9
  52. package/dist/hooks/useLookupOptions.js.map +1 -1
  53. package/dist/interface/host-events.d.ts +27 -10
  54. package/dist/interface/host-events.d.ts.map +1 -1
  55. package/dist/interface/host-events.js +10 -10
  56. package/dist/interface/host-events.js.map +1 -1
  57. package/dist/interface/types.d.ts +8 -0
  58. package/dist/interface/types.d.ts.map +1 -1
  59. package/dist/interface/types.js.map +1 -1
  60. package/dist/utils/field/get-field-bounds-style.utils.d.ts +20 -0
  61. package/dist/utils/field/get-field-bounds-style.utils.d.ts.map +1 -0
  62. package/dist/utils/field/get-field-bounds-style.utils.js +53 -0
  63. package/dist/utils/field/get-field-bounds-style.utils.js.map +1 -0
  64. package/dist/utils/field/get-field-text-clip.utils.d.ts +13 -0
  65. package/dist/utils/field/get-field-text-clip.utils.d.ts.map +1 -0
  66. package/dist/utils/field/get-field-text-clip.utils.js +25 -0
  67. package/dist/utils/field/get-field-text-clip.utils.js.map +1 -0
  68. package/dist/utils/field/get-field-typography.utils.d.ts +10 -0
  69. package/dist/utils/field/get-field-typography.utils.d.ts.map +1 -0
  70. package/dist/utils/field/get-field-typography.utils.js +20 -0
  71. package/dist/utils/field/get-field-typography.utils.js.map +1 -0
  72. package/dist/utils/field/index.d.ts +3 -0
  73. package/dist/utils/field/index.d.ts.map +1 -1
  74. package/dist/utils/field/index.js +3 -0
  75. package/dist/utils/field/index.js.map +1 -1
  76. package/package.json +1 -1
  77. package/src/components/display-conditions/condition-row.tsx +8 -1
  78. package/src/components/display-conditions/condition-value-control.tsx +126 -39
  79. package/src/components/pdf-fields-overlay/pdf-overlay-field-calculated.tsx +5 -2
  80. package/src/components/pdf-fields-overlay/pdf-overlay-field-data-model.tsx +2 -1
  81. package/src/components/pdf-fields-overlay/pdf-overlay-field-e-sign.tsx +3 -1
  82. package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +28 -8
  83. package/src/components/pdf-fields-overlay/pdf-overlay-field-generic.tsx +9 -1
  84. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +9 -13
  85. package/src/components/pdf-view/pdf-view-calculated.tsx +9 -2
  86. package/src/components/pdf-view/pdf-view-data-model.tsx +2 -2
  87. package/src/components/pdf-view/pdf-view-e-sign.tsx +3 -4
  88. package/src/components/pdf-view/pdf-view-field-container.tsx +6 -16
  89. package/src/components/pdf-view/pdf-view-fillable.tsx +29 -28
  90. package/src/components/pdf-view/pdf-view-generic.tsx +1 -1
  91. package/src/components/pdf-view/pdf-view.tsx +4 -1
  92. package/src/constants/field.constants.ts +5 -0
  93. package/src/hooks/useLookupOptions.ts +76 -14
  94. package/src/interface/host-events.ts +27 -15
  95. package/src/interface/types.ts +8 -0
  96. package/src/styles/inline-editable.css +1 -0
  97. package/src/styles/pdf-field-overlay.css +80 -27
  98. package/src/utils/field/__tests__/get-field-bounds-style.utils.test.ts +50 -0
  99. package/src/utils/field/__tests__/get-field-text-clip.utils.test.ts +43 -0
  100. package/src/utils/field/__tests__/get-field-typography.utils.test.ts +56 -0
  101. package/src/utils/field/get-field-bounds-style.utils.ts +65 -0
  102. package/src/utils/field/get-field-text-clip.utils.ts +31 -0
  103. package/src/utils/field/get-field-typography.utils.ts +23 -0
  104. package/src/utils/field/index.ts +3 -0
@@ -9,7 +9,9 @@ export const PdfOverlayFieldESign: FC<PdfOverlayFieldESignProps> = ({ field }) =
9
9
  return (
10
10
  <span>
11
11
  {field.label} {field.required ? '*' : ''}
12
- <div className="dte-pdf-field-value">{field.path}</div>
12
+ <div className="dte-pdf-field-value" style={{ fontSize: '8px' }}>
13
+ {field.path}
14
+ </div>
13
15
  </span>
14
16
  );
15
17
  };
@@ -1,7 +1,7 @@
1
1
  import { FC, Fragment } from 'react';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import { PdfField } from '../../interface/types';
4
- import { getFieldPlaceholderText } from '../../utils';
4
+ import { getFieldPlaceholderText, getFieldTextClipClassName } from '../../utils';
5
5
  import { AddHandle } from './add-handle';
6
6
 
7
7
  interface PdfOverlayFieldFillableProps {
@@ -15,14 +15,34 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
15
15
  }) => {
16
16
  const placeholderText = getFieldPlaceholderText(field);
17
17
 
18
+ const clipClassName = getFieldTextClipClassName(field);
19
+
18
20
  switch (field.subType) {
19
21
  case 'checkbox':
20
- return <input type="checkbox" readOnly className="dte-pdf-field-fillable" />;
22
+ return (
23
+ <input
24
+ type="checkbox"
25
+ readOnly
26
+ className="dte-pdf-field-fillable"
27
+ style={{
28
+ width: field.width,
29
+ height: field.height,
30
+ }}
31
+ />
32
+ );
21
33
 
22
34
  case 'radio':
23
35
  return (
24
36
  <Fragment>
25
- <input type="radio" readOnly className="dte-pdf-field-fillable" />
37
+ <input
38
+ type="radio"
39
+ readOnly
40
+ className="dte-pdf-field-fillable"
41
+ style={{
42
+ width: field.width,
43
+ height: field.height,
44
+ }}
45
+ />
26
46
  <AddHandle
27
47
  onClick={() =>
28
48
  handleAddNewField({
@@ -40,7 +60,7 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
40
60
  <input
41
61
  type="date"
42
62
  readOnly
43
- className="dte-pdf-field-fillable"
63
+ className={`dte-pdf-field-fillable ${clipClassName}`}
44
64
  placeholder={placeholderText}
45
65
  />
46
66
  );
@@ -50,7 +70,7 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
50
70
  <input
51
71
  type="number"
52
72
  readOnly
53
- className="dte-pdf-field-fillable"
73
+ className={`dte-pdf-field-fillable ${clipClassName}`}
54
74
  placeholder={placeholderText}
55
75
  />
56
76
  );
@@ -60,9 +80,9 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
60
80
  return (
61
81
  <textarea
62
82
  readOnly
83
+ style={{ font: 'inherit', lineHeight: 'inherit', outline: 'none' }}
63
84
  placeholder={placeholderText}
64
- className="dte-pdf-field-fillable"
65
- style={{ resize: 'none' }}
85
+ className={`dte-pdf-field-fillable ${clipClassName}`}
66
86
  />
67
87
  );
68
88
  }
@@ -71,7 +91,7 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
71
91
  type="text"
72
92
  readOnly
73
93
  placeholder={placeholderText}
74
- className="dte-pdf-field-fillable"
94
+ className={`dte-pdf-field-fillable ${clipClassName}`}
75
95
  />
76
96
  );
77
97
  }
@@ -62,7 +62,15 @@ const GenericFieldText: FC<GenericFieldTextProps> = ({ data, onChange }) => {
62
62
 
63
63
  return isActive ? (
64
64
  <textarea
65
- style={{ width: '100%', height: '100%' }}
65
+ style={{
66
+ width: '100%',
67
+ height: '100%',
68
+ font: 'inherit',
69
+ lineHeight: 'inherit',
70
+ outline: 'none',
71
+ border: 'none',
72
+ padding: 0,
73
+ }}
66
74
  value={editingValue}
67
75
  placeholder="Type here..."
68
76
  onChange={e => setEditingValue(e.target.value)}
@@ -4,9 +4,8 @@ import { useFieldDrag, useFieldResize } from '../../hooks';
4
4
  import { FieldTypeEnum, PdfField } from '../../interface/types';
5
5
  import {
6
6
  getFieldBackgroundColor,
7
- getFieldFontSizeInPixels,
7
+ getFieldBoundsStyle,
8
8
  getPagePosition,
9
- supportsFieldFontSize,
10
9
  } from '../../utils';
11
10
  import { PdfOverlayFieldCalculated } from './pdf-overlay-field-calculated';
12
11
  import { PdfOverlayFieldDataModel } from './pdf-overlay-field-data-model';
@@ -63,16 +62,13 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
63
62
  const resizeConfig = DISABLE_FIELD_RESIZE[field.type]?.[field.subType!];
64
63
  const hideResizeHandle = resizeConfig?.disableVertical && resizeConfig.disableHorizontal;
65
64
 
66
- const style = {
67
- left: pagePos.left + x,
68
- top: pagePos.top + y,
69
- width: field.width,
70
- height: field.height,
71
- backgroundColor: getFieldBackgroundColor(field.recipient, recipientsColors),
72
- ...(supportsFieldFontSize(field)
73
- ? { fontSize: `${getFieldFontSizeInPixels(field)}px` }
74
- : {}),
75
- };
65
+ const style = getFieldBoundsStyle(
66
+ { ...field, page, x, y },
67
+ {
68
+ pageOffset: pagePos,
69
+ backgroundColor: getFieldBackgroundColor(field.recipient, recipientsColors),
70
+ },
71
+ );
76
72
 
77
73
  return (
78
74
  <div
@@ -82,7 +78,7 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
82
78
  onDragStart={e => handleDragStart(e, field)}
83
79
  onDrag={e => handleDrag(e, field)}
84
80
  onDragEnd={handleDragEnd}
85
- className={`dte-pdf-field ${isSelected ? '--selected' : '--unselected'} ${isDragging ? '--dragging' : ''} ${isSameGroup ? '--colored' : ''} ${error ? '--error' : ''}`}
81
+ className={`dte-pdf-field-editor ${isSelected ? '--selected' : '--unselected'} ${isDragging ? '--dragging' : ''} ${isSameGroup ? '--colored' : ''} ${error ? '--error' : ''}`}
86
82
  style={style}
87
83
  >
88
84
  {field.type === FieldTypeEnum.calculated && (
@@ -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
  });
@@ -1,45 +1,62 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import {
3
3
  createLookupRequestId,
4
4
  emitLookupOptionsRequest,
5
- isSearchableLookupSource,
6
5
  type LookupOption,
7
6
  onLookupOptionsResponse,
8
7
  } from '../interface/host-events';
9
8
 
10
9
  /**
11
- * - `idle` -> no lookup source (or a searchable source we skip for now): use free-text input.
12
- * - `loading` -> request in flight: show a disabled/loading value control.
13
- * - `ready` -> host returned options: render the dropdown.
14
- * - `fallback` -> empty/error/timeout: fall back to free-text so the user is never blocked.
10
+ * - `idle` -> no lookup source: use free-text input.
11
+ * - `loading` -> initial request in flight: show a disabled/loading value control.
12
+ * - `static` -> host returned a full list: render a locally-filtered dropdown.
13
+ * - `searchable` -> host wants per-query search: render an async dropdown driven by `loadOptions`.
14
+ * - `fallback` -> empty/error/timeout: fall back to free-text so the user is never blocked.
15
15
  */
16
- export type LookupOptionsStatus = 'idle' | 'loading' | 'ready' | 'fallback';
16
+ export type LookupOptionsStatus = 'idle' | 'loading' | 'static' | 'searchable' | 'fallback';
17
17
 
18
18
  /** Time to wait for the host before falling back to free-text input. */
19
19
  const LOOKUP_OPTIONS_TIMEOUT_MS = 10_000;
20
20
 
21
21
  interface UseLookupOptionsResult {
22
22
  status: LookupOptionsStatus;
23
+ /** Full option list for `static` sources; empty for `searchable`/`idle`/`fallback`. */
23
24
  options: LookupOption[];
25
+ /**
26
+ * Per-query resolver for `searchable` sources. Emits a request with the typed `query` and
27
+ * resolves with the host's fresh list (or `[]` on timeout). Wire this into an async dropdown
28
+ * (e.g. Anvil2 `SelectField`'s `loadOptions`).
29
+ */
30
+ loadOptions: (query: string) => Promise<LookupOption[]>;
31
+ /**
32
+ * Resolve the option(s) for already-stored keys (exact match, not a text search). Used on open
33
+ * to turn a saved searchable value back into its human-readable label. Resolves with `[]` on
34
+ * timeout.
35
+ */
36
+ resolveOptions: (keys: string[]) => Promise<LookupOption[]>;
24
37
  }
25
38
 
26
39
  /**
27
40
  * Requests host-resolved dropdown options for a field's `fieldLookupSource` and exposes the
28
41
  * resulting render state.
29
42
  *
30
- * Re-runs whenever `sourceKey` changes, ignores stale responses whose `requestId` does not match
31
- * the latest request, and tears down its listener/timeout on unmount or source change. Falls back
32
- * to free text for empty/searchable sources and on error/timeout so the user is never blocked.
43
+ * Fires a single query-less request whenever `sourceKey` changes. The host's response decides the
44
+ * mode: a full list (`static`, filtered locally) or a searchable source (`searchable`, driven by
45
+ * `loadOptions` per keystroke). Ignores stale responses whose `requestId` does not match the latest
46
+ * request, and tears down its listener/timeout on unmount or source change. Falls back to free text
47
+ * on empty/error/timeout so the user is never blocked.
33
48
  */
34
49
  export const useLookupOptions = (sourceKey: string | undefined): UseLookupOptionsResult => {
35
50
  const [status, setStatus] = useState<LookupOptionsStatus>('idle');
36
51
  const [options, setOptions] = useState<LookupOption[]>([]);
37
52
  const latestRequestIdRef = useRef<string | null>(null);
53
+ const sourceKeyRef = useRef<string | undefined>(sourceKey);
38
54
 
39
55
  useEffect(() => {
56
+ sourceKeyRef.current = sourceKey;
40
57
  const normalizedSourceKey = sourceKey?.trim();
41
- // No source, or a searchable source we intentionally skip for now.
42
- if (!normalizedSourceKey || isSearchableLookupSource(normalizedSourceKey)) {
58
+
59
+ if (!normalizedSourceKey) {
43
60
  latestRequestIdRef.current = null;
44
61
  setStatus('idle');
45
62
  setOptions([]);
@@ -65,9 +82,14 @@ export const useLookupOptions = (sourceKey: string | undefined): UseLookupOption
65
82
  return;
66
83
  }
67
84
  clearTimeout(timeoutId);
85
+ if (response.searchable) {
86
+ setOptions([]);
87
+ setStatus('searchable');
88
+ return;
89
+ }
68
90
  if (response.options.length > 0) {
69
91
  setOptions(response.options);
70
- setStatus('ready');
92
+ setStatus('static');
71
93
  } else {
72
94
  setOptions([]);
73
95
  setStatus('fallback');
@@ -82,5 +104,45 @@ export const useLookupOptions = (sourceKey: string | undefined): UseLookupOption
82
104
  };
83
105
  }, [sourceKey]);
84
106
 
85
- return { status, options };
107
+ const requestOptions = useCallback((payload: { query?: string; keys?: string[] }) => {
108
+ const normalizedSourceKey = sourceKeyRef.current?.trim();
109
+ if (!normalizedSourceKey) {
110
+ return Promise.resolve<LookupOption[]>([]);
111
+ }
112
+
113
+ return new Promise<LookupOption[]>(resolve => {
114
+ const requestId = createLookupRequestId();
115
+ let settled = false;
116
+
117
+ const finish = (result: LookupOption[]) => {
118
+ if (settled) {
119
+ return;
120
+ }
121
+ settled = true;
122
+ clearTimeout(timeoutId);
123
+ unsubscribe();
124
+ resolve(result);
125
+ };
126
+
127
+ const timeoutId = setTimeout(() => finish([]), LOOKUP_OPTIONS_TIMEOUT_MS);
128
+
129
+ const unsubscribe = onLookupOptionsResponse(response => {
130
+ if (response.requestId !== requestId) {
131
+ return;
132
+ }
133
+ finish(response.options);
134
+ });
135
+
136
+ emitLookupOptionsRequest({ requestId, sourceKey: normalizedSourceKey, ...payload });
137
+ });
138
+ }, []);
139
+
140
+ const loadOptions = useCallback((query: string) => requestOptions({ query }), [requestOptions]);
141
+
142
+ const resolveOptions = useCallback(
143
+ (keys: string[]) => requestOptions({ keys }),
144
+ [requestOptions],
145
+ );
146
+
147
+ return { status, options, loadOptions, resolveOptions };
86
148
  };
@@ -6,6 +6,15 @@
6
6
  * data — it asks the HOST for it over the EventTarget bus below and renders whatever the host
7
7
  * returns.
8
8
  *
9
+ * Two host-driven modes are supported (the host decides, the editor adapts):
10
+ * 1. Full-list source (e.g. "state", "jobType"): the host returns every option on the first
11
+ * request and omits `searchable`. The editor caches that list and filters it LOCALLY as the
12
+ * user types — no further requests are sent.
13
+ * 2. Searchable source (e.g. "city"): the list is too large to send at once. The host returns
14
+ * `searchable: true` (typically with an empty list on the first, query-less request). The
15
+ * editor then re-requests with the typed `query` on each keystroke and the host returns a
16
+ * fresh, server-filtered list per query.
17
+ *
9
18
  * Transport mirrors the unlayer `displayConditionController` / `lookupOptionsController` pattern: a
10
19
  * module-scoped `EventTarget` shared by every consumer of this package (no global `window`
11
20
  * pollution, no cross-instance leakage). The host imports `onLookupOptionsRequest` /
@@ -16,7 +25,7 @@
16
25
  * - "dte-byod-client-mfe:lookup-options-response" (host -> editor)
17
26
  *
18
27
  * The host-side resolver is implemented separately (it wires these events to the app's data
19
- * providers, e.g. job types / states).
28
+ * providers, e.g. job types / states / cities).
20
29
  */
21
30
 
22
31
  /** A single selectable option for a lookup-backed condition value. */
@@ -33,8 +42,18 @@ export interface LookupOptionsRequest {
33
42
  requestId: string;
34
43
  /** The field's `options.fieldLookupSource` value. */
35
44
  sourceKey: string;
36
- /** Reserved for searchable sources (e.g. "city"); omitted for full-list sources. */
45
+ /**
46
+ * The text the user has typed. Sent on each (debounced) keystroke once the host has declared
47
+ * the source `searchable`. Empty/omitted on the first request, where the host decides whether
48
+ * the source is searchable.
49
+ */
37
50
  query?: string;
51
+ /**
52
+ * Resolve mode: the editor is asking the host to return the option(s) for these EXACT stored
53
+ * keys (not a text search). Used on open to turn a saved searchable value (a bare key) back into
54
+ * a human-readable label. When present the host should match by key and ignore `query`.
55
+ */
56
+ keys?: string[];
38
57
  }
39
58
 
40
59
  /** host -> editor: the resolved options for a previously emitted request. */
@@ -42,6 +61,12 @@ export interface LookupOptionsResponse {
42
61
  /** Echoes {@link LookupOptionsRequest.requestId}; responses with a stale id are ignored. */
43
62
  requestId: string;
44
63
  options: LookupOption[];
64
+ /**
65
+ * Set to `true` by the host when the source must be searched per query (server-side). When
66
+ * omitted/false the editor treats `options` as the complete list and switches to local
67
+ * filtering. Only read from the first (query-less) response for a given source.
68
+ */
69
+ searchable?: boolean;
45
70
  }
46
71
 
47
72
  /** editor -> host event name. */
@@ -49,19 +74,6 @@ export const LOOKUP_OPTIONS_REQUEST_EVENT = 'dte-byod-client-mfe:lookup-options-
49
74
  /** host -> editor event name. */
50
75
  export const LOOKUP_OPTIONS_RESPONSE_EVENT = 'dte-byod-client-mfe:lookup-options-response';
51
76
 
52
- /**
53
- * Lookup sources that require the host to search an API per typed query. These keep the free-text
54
- * input for now (the editor only handles full-list, load-once sources).
55
- */
56
- export const SEARCHABLE_LOOKUP_SOURCES = ['city'] as const;
57
-
58
- export type SearchableLookupSource = (typeof SEARCHABLE_LOOKUP_SOURCES)[number];
59
-
60
- /** True when a lookup source is searchable (host-side per-query search) rather than a full list. */
61
- export const isSearchableLookupSource = (sourceKey: string | undefined): boolean => {
62
- return !!sourceKey && (SEARCHABLE_LOOKUP_SOURCES as readonly string[]).includes(sourceKey);
63
- };
64
-
65
77
  /** Generate a unique request id for correlating request/response pairs. */
66
78
  export const createLookupRequestId = (): string => {
67
79
  return `lookup-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
@@ -71,6 +71,12 @@ export interface DisplayConditionGroup {
71
71
  export interface DisplayConditionSingle {
72
72
  id: string;
73
73
  value: string;
74
+ /**
75
+ * Human-readable label for `value` when it came from a searchable lookup source. Persisted so a
76
+ * saved condition can render the selected option's label on reopen (the searched list is empty
77
+ * until the user types). Display-only — never used in condition evaluation.
78
+ */
79
+ valueLabel?: string;
74
80
  operator: string;
75
81
  dataPointKey: string;
76
82
  logicalOperator?: 'and' | 'or';
@@ -79,6 +85,8 @@ export interface DisplayConditionSingle {
79
85
 
80
86
  export interface PdfFieldStyles {
81
87
  fontSize?: number;
88
+ fontFamily?: string;
89
+ fontWeight?: 'normal' | 'bold';
82
90
  }
83
91
 
84
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
  }