@servicetitan/dte-pdf-editor 1.92.3 → 1.93.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 (130) hide show
  1. package/README.md +0 -2
  2. package/dist/components/field-config-panel/dropdown-field-items.d.ts +9 -0
  3. package/dist/components/field-config-panel/dropdown-field-items.d.ts.map +1 -0
  4. package/dist/components/field-config-panel/dropdown-field-items.js +60 -0
  5. package/dist/components/field-config-panel/dropdown-field-items.js.map +1 -0
  6. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts +1 -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.d.ts +1 -1
  9. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  10. package/dist/components/field-config-panel/field-config-panel.js +5 -1
  11. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  12. package/dist/components/field-config-panel/fillable-default-config.d.ts +19 -0
  13. package/dist/components/field-config-panel/fillable-default-config.d.ts.map +1 -0
  14. package/dist/components/field-config-panel/fillable-default-config.js +77 -0
  15. package/dist/components/field-config-panel/fillable-default-config.js.map +1 -0
  16. package/dist/components/pdf-editor/pdf-editor.js +1 -1
  17. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  18. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +1 -1
  19. package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
  20. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts +9 -0
  21. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
  22. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +39 -12
  23. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
  24. package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
  25. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +4 -4
  26. package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
  27. package/dist/components/pdf-view/pdf-view-field-container.d.ts.map +1 -1
  28. package/dist/components/pdf-view/pdf-view-field-container.js +7 -20
  29. package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
  30. package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
  31. package/dist/components/pdf-view/pdf-view-fillable.js +6 -2
  32. package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
  33. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  34. package/dist/components/pdf-view/pdf-view.js +9 -2
  35. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  36. package/dist/constants/field.constants.d.ts +1 -0
  37. package/dist/constants/field.constants.d.ts.map +1 -1
  38. package/dist/constants/field.constants.js +9 -0
  39. package/dist/constants/field.constants.js.map +1 -1
  40. package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
  41. package/dist/hooks/usePdfFieldDnD.js +6 -0
  42. package/dist/hooks/usePdfFieldDnD.js.map +1 -1
  43. package/dist/hooks/usePdfFieldSelection.d.ts +1 -1
  44. package/dist/hooks/usePdfFieldSelection.d.ts.map +1 -1
  45. package/dist/hooks/usePdfFieldSelection.js +3 -1
  46. package/dist/hooks/usePdfFieldSelection.js.map +1 -1
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/interface/types.d.ts +46 -1
  52. package/dist/interface/types.d.ts.map +1 -1
  53. package/dist/interface/types.js.map +1 -1
  54. package/dist/styles/index.css +1 -0
  55. package/dist/styles/inline-editable.css +2 -0
  56. package/dist/styles/pdf-canvas.css +2 -1
  57. package/dist/styles/pdf-field-overlay.css +2 -111
  58. package/dist/styles/pdf-field.css +111 -0
  59. package/dist/styles/variables.css +1 -0
  60. package/dist/utils/data-model/index.d.ts +1 -0
  61. package/dist/utils/data-model/index.d.ts.map +1 -1
  62. package/dist/utils/data-model/index.js +1 -0
  63. package/dist/utils/data-model/index.js.map +1 -1
  64. package/dist/utils/data-model/resolve-fields-default-values.d.ts +3 -0
  65. package/dist/utils/data-model/resolve-fields-default-values.d.ts.map +1 -0
  66. package/dist/utils/data-model/resolve-fields-default-values.js +19 -0
  67. package/dist/utils/data-model/resolve-fields-default-values.js.map +1 -0
  68. package/dist/utils/exposes-utils.d.ts +2 -0
  69. package/dist/utils/exposes-utils.d.ts.map +1 -0
  70. package/dist/utils/exposes-utils.js +2 -0
  71. package/dist/utils/exposes-utils.js.map +1 -0
  72. package/dist/utils/field/field-background-color.utils.d.ts +1 -1
  73. package/dist/utils/field/field-background-color.utils.d.ts.map +1 -1
  74. package/dist/utils/field/field-background-color.utils.js +1 -1
  75. package/dist/utils/field/field-background-color.utils.js.map +1 -1
  76. package/dist/utils/field/get-dropdown-field-items.utils.d.ts +3 -0
  77. package/dist/utils/field/get-dropdown-field-items.utils.d.ts.map +1 -0
  78. package/dist/utils/field/get-dropdown-field-items.utils.js +7 -0
  79. package/dist/utils/field/get-dropdown-field-items.utils.js.map +1 -0
  80. package/dist/utils/field/get-field-bounds-style.utils.d.ts +1 -2
  81. package/dist/utils/field/get-field-bounds-style.utils.d.ts.map +1 -1
  82. package/dist/utils/field/get-field-bounds-style.utils.js +9 -4
  83. package/dist/utils/field/get-field-bounds-style.utils.js.map +1 -1
  84. package/dist/utils/field/get-fillable-default-value.utils.d.ts +29 -0
  85. package/dist/utils/field/get-fillable-default-value.utils.d.ts.map +1 -0
  86. package/dist/utils/field/get-fillable-default-value.utils.js +59 -0
  87. package/dist/utils/field/get-fillable-default-value.utils.js.map +1 -0
  88. package/dist/utils/field/index.d.ts +2 -0
  89. package/dist/utils/field/index.d.ts.map +1 -1
  90. package/dist/utils/field/index.js +2 -0
  91. package/dist/utils/field/index.js.map +1 -1
  92. package/dist/utils/recipients/map-colors.js +23 -23
  93. package/dist/utils/recipients/map-colors.js.map +1 -1
  94. package/package.json +1 -1
  95. package/src/__tests__/field-types/e-sign-fields.test.ts +3 -3
  96. package/src/__tests__/field-types/fillable-fields.test.ts +142 -6
  97. package/src/components/field-config-panel/dropdown-field-items.tsx +138 -0
  98. package/src/components/field-config-panel/field-config-panel-overlay.tsx +1 -1
  99. package/src/components/field-config-panel/field-config-panel.tsx +16 -1
  100. package/src/components/field-config-panel/fillable-default-config.tsx +155 -0
  101. package/src/components/pdf-editor/pdf-editor.tsx +1 -1
  102. package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +1 -1
  103. package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +66 -3
  104. package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +6 -3
  105. package/src/components/pdf-view/pdf-view-field-container.tsx +21 -41
  106. package/src/components/pdf-view/pdf-view-fillable.tsx +25 -2
  107. package/src/components/pdf-view/pdf-view.tsx +11 -3
  108. package/src/constants/field.constants.ts +10 -0
  109. package/src/hooks/usePdfFieldDnD.ts +6 -0
  110. package/src/hooks/usePdfFieldSelection.ts +8 -2
  111. package/src/index.ts +1 -0
  112. package/src/interface/types.ts +36 -1
  113. package/src/styles/index.css +1 -0
  114. package/src/styles/inline-editable.css +2 -0
  115. package/src/styles/pdf-canvas.css +2 -1
  116. package/src/styles/pdf-field-overlay.css +2 -111
  117. package/src/styles/pdf-field.css +111 -0
  118. package/src/styles/variables.css +1 -0
  119. package/src/utils/data-model/index.ts +1 -0
  120. package/src/utils/data-model/resolve-fields-default-values.ts +19 -0
  121. package/src/utils/exposes-utils.ts +1 -0
  122. package/src/utils/field/__tests__/field-background-color.utils.test.ts +3 -3
  123. package/src/utils/field/__tests__/get-field-bounds-style.utils.test.ts +0 -1
  124. package/src/utils/field/__tests__/get-fillable-default-value.utils.test.ts +137 -0
  125. package/src/utils/field/field-background-color.utils.ts +1 -1
  126. package/src/utils/field/get-dropdown-field-items.utils.ts +14 -0
  127. package/src/utils/field/get-field-bounds-style.utils.ts +11 -11
  128. package/src/utils/field/get-fillable-default-value.utils.ts +70 -0
  129. package/src/utils/field/index.ts +2 -0
  130. package/src/utils/recipients/map-colors.ts +23 -23
@@ -2,9 +2,12 @@ import { FC, Fragment } from 'react';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import { type PdfField } from '../../interface/types';
4
4
  import {
5
+ getDropdownFieldItems,
5
6
  getFieldPlaceholderText,
6
7
  getFieldTextClipClassName,
7
8
  getFillablePaintedLabel,
9
+ getTodayDateInputValue,
10
+ isRadioDefaultSelected,
8
11
  } from '../../utils';
9
12
  import { AddHandle } from './add-handle';
10
13
 
@@ -13,6 +16,15 @@ interface PdfOverlayFieldFillableProps {
13
16
  handleAddNewField(field: PdfField): void;
14
17
  }
15
18
 
19
+ /**
20
+ * Every case below previews `field.default` through an uncontrolled, read-only DOM element
21
+ * (`defaultValue`/`defaultChecked`, never `value`/`checked` - there's no data model to bind to
22
+ * here, this is a static authoring mock-up). React only applies `defaultValue`/`defaultChecked`
23
+ * once, at mount, and silently ignores it on every later re-render - so each element carries a
24
+ * `key` derived from the same default it previews, forcing React to remount (not patch) it
25
+ * whenever the author changes that default in the config panel, so it doesn't keep showing the
26
+ * value it had when it first mounted.
27
+ */
16
28
  export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
17
29
  field,
18
30
  handleAddNewField,
@@ -33,9 +45,13 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
33
45
  }}
34
46
  >
35
47
  <input
48
+ key={String(field.default?.type === 'checkbox' && field.default.value)}
36
49
  title={field.label}
37
50
  type="checkbox"
38
51
  readOnly
52
+ defaultChecked={
53
+ field.default?.type === 'checkbox' ? field.default.value : false
54
+ }
39
55
  className="dte-pdf-field-fillable"
40
56
  style={{
41
57
  width: field.width,
@@ -62,15 +78,18 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
62
78
  }}
63
79
  >
64
80
  <input
81
+ key={String(isRadioDefaultSelected(field))}
65
82
  title={field.label}
66
83
  type="radio"
67
84
  readOnly
85
+ defaultChecked={isRadioDefaultSelected(field)}
68
86
  className="dte-pdf-field-fillable"
69
87
  style={{
70
88
  width: field.width,
71
89
  height: field.height,
72
90
  margin: 0,
73
91
  border: 'none',
92
+ pointerEvents: 'none',
74
93
  }}
75
94
  />
76
95
  {getFillablePaintedLabel(field)}
@@ -79,6 +98,8 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
79
98
  onClick={() =>
80
99
  handleAddNewField({
81
100
  ...field,
101
+ default: undefined,
102
+ label: `${field.label} Copy`,
82
103
  x: field.x + field.width + 15,
83
104
  id: uuidv4(),
84
105
  })
@@ -87,31 +108,70 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
87
108
  </Fragment>
88
109
  );
89
110
 
90
- case 'date':
111
+ case 'date': {
112
+ const defaultDateValue =
113
+ field.default?.type === 'date'
114
+ ? field.default.value
115
+ : field.default?.type === 'today'
116
+ ? getTodayDateInputValue()
117
+ : '';
91
118
  return (
92
119
  <input
120
+ key={defaultDateValue}
93
121
  type="date"
94
122
  readOnly
123
+ defaultValue={defaultDateValue}
95
124
  className={`dte-pdf-field-fillable ${clipClassName}`}
96
125
  placeholder={placeholderText}
97
126
  />
98
127
  );
128
+ }
99
129
 
100
- case 'number':
130
+ case 'number': {
131
+ const defaultNumberValue = field.default?.type === 'number' ? field.default.value : '';
101
132
  return (
102
133
  <input
134
+ key={defaultNumberValue}
103
135
  type="number"
104
136
  readOnly
137
+ defaultValue={defaultNumberValue}
105
138
  className={`dte-pdf-field-fillable ${clipClassName}`}
106
139
  placeholder={placeholderText}
107
140
  />
108
141
  );
142
+ }
143
+
144
+ case 'dropdown': {
145
+ const defaultDropdownValue =
146
+ field.default?.type === 'dropdown' ? field.default.value : '';
147
+ return (
148
+ <select
149
+ key={defaultDropdownValue}
150
+ defaultValue={defaultDropdownValue}
151
+ tabIndex={-1}
152
+ className="dte-pdf-field-fillable"
153
+ style={{ color: 'inherit', pointerEvents: 'none' }}
154
+ >
155
+ <option value="" disabled hidden>
156
+ {placeholderText || 'Select...'}
157
+ </option>
158
+ {getDropdownFieldItems(field).map(item => (
159
+ <option key={item.value} value={item.value}>
160
+ {item.label}
161
+ </option>
162
+ ))}
163
+ </select>
164
+ );
165
+ }
109
166
 
110
- default:
167
+ default: {
168
+ const defaultTextValue = field.default?.type === 'text' ? field.default.value : '';
111
169
  if (field.multiline) {
112
170
  return (
113
171
  <textarea
172
+ key={defaultTextValue}
114
173
  readOnly
174
+ defaultValue={defaultTextValue}
115
175
  style={{ font: 'inherit', lineHeight: 'inherit', outline: 'none' }}
116
176
  placeholder={placeholderText}
117
177
  className={`dte-pdf-field-fillable ${clipClassName}`}
@@ -120,11 +180,14 @@ export const PdfOverlayFieldFillable: FC<PdfOverlayFieldFillableProps> = ({
120
180
  }
121
181
  return (
122
182
  <input
183
+ key={defaultTextValue}
123
184
  type="text"
124
185
  readOnly
186
+ defaultValue={defaultTextValue}
125
187
  placeholder={placeholderText}
126
188
  className={`dte-pdf-field-fillable ${clipClassName}`}
127
189
  />
128
190
  );
191
+ }
129
192
  }
130
193
  };
@@ -4,8 +4,8 @@ import { DISABLE_FIELD_RESIZE } from '../../constants';
4
4
  import { useFieldDrag, useFieldResize } from '../../hooks';
5
5
  import { FieldTypeEnum, type PdfField } from '../../interface/types';
6
6
  import {
7
- getFieldBackgroundColor,
8
7
  getFieldBoundsStyle,
8
+ getFieldColor,
9
9
  getFieldTypeOption,
10
10
  getPagePosition,
11
11
  } from '../../utils';
@@ -74,13 +74,13 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
74
74
  { ...field, page, x, y },
75
75
  {
76
76
  pageOffset: pagePos,
77
- backgroundColor: getFieldBackgroundColor(field.recipient, recipientsColors),
77
+ color: getFieldColor(field.recipient, recipientsColors),
78
78
  zoom,
79
79
  },
80
80
  );
81
81
 
82
82
  const className = [
83
- 'dte-pdf-field-editor',
83
+ 'dte-pdf-field-container',
84
84
  `${field.type}-${field.subType}`,
85
85
  fieldWrapperClassNames,
86
86
  isSelected ? '--selected' : '--unselected',
@@ -103,6 +103,8 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
103
103
  onDragEnd={handleDragEnd}
104
104
  className={className}
105
105
  style={style}
106
+ data-path={field.path}
107
+ data-id={field.id}
106
108
  >
107
109
  {fieldTypeOption && (
108
110
  <div className="dte-pdf-field-label">
@@ -110,6 +112,7 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
110
112
  <Icon svg={fieldTypeOption.icon} size="small" color="inherit" />
111
113
  )}
112
114
  {fieldTypeOption.label}
115
+ {!field.required && <span>(Optional)</span>}
113
116
  </div>
114
117
  )}
115
118
  {field.type === FieldTypeEnum.calculated && (
@@ -1,10 +1,10 @@
1
1
  import { Icon } from '@servicetitan/anvil2';
2
- import { FC, Fragment, PropsWithChildren, RefObject } from 'react';
2
+ import { FC, PropsWithChildren, RefObject } from 'react';
3
3
  import { type DataModelValues, type PdfField } from '../../interface/types';
4
4
  import {
5
5
  evaluateDisplayCondition,
6
- getFieldBackgroundColor,
7
6
  getFieldBoundsStyle,
7
+ getFieldColor,
8
8
  getFieldTypeOption,
9
9
  getPagePosition,
10
10
  } from '../../utils';
@@ -37,54 +37,34 @@ export const PdfViewFieldContainer: FC<PropsWithChildren<PdfViewFieldContainer>>
37
37
  }
38
38
 
39
39
  const pagePos = getPagePosition(field.page, pdfWrapperRef);
40
- const bgColor = getFieldBackgroundColor(field.recipient, recipientsColors);
40
+ const color = getFieldColor(field.recipient, recipientsColors);
41
41
  const fieldTypeOption = getFieldTypeOption(field);
42
42
 
43
43
  return (
44
- <Fragment>
45
- <div
46
- id={`dte-pdf-field-${field.id}`}
47
- className={`dte-pdf-field-view --${field.type}`}
48
- tabIndex={-1}
49
- style={getFieldBoundsStyle(field, {
50
- pageOffset: pagePos,
51
- backgroundColor: bgColor,
52
- borderColor: error ? 'red' : bgColor.replace('1f', '7d'),
53
- zoom,
54
- })}
55
- onFocus={() => onFocus?.(field.id)}
56
- >
57
- {children}
58
- </div>
44
+ <div
45
+ id={`dte-pdf-field-${field.id}`}
46
+ data-path={field.path}
47
+ data-id={field.id}
48
+ className={`dte-pdf-field-container --view --${field.type} ${error && '--error'}`}
49
+ tabIndex={-1}
50
+ style={getFieldBoundsStyle(field, {
51
+ pageOffset: pagePos,
52
+ color,
53
+ zoom,
54
+ })}
55
+ onFocus={() => onFocus?.(field.id)}
56
+ >
59
57
  {fieldTypeOption && (
60
- <div
61
- className="dte-pdf-field-label"
62
- style={{
63
- top: pagePos.top + field.y * zoom - 10,
64
- left: pagePos.left + field.x * zoom + 3,
65
- position: 'absolute',
66
- }}
67
- >
58
+ <div className="dte-pdf-field-label">
68
59
  {fieldTypeOption.icon && (
69
60
  <Icon svg={fieldTypeOption.icon} size="small" color="inherit" />
70
61
  )}
71
62
  {fieldTypeOption.label}
63
+ {!field.required && <span>(Optional)</span>}
72
64
  </div>
73
65
  )}
74
- {error && (
75
- <span
76
- style={{
77
- color: 'red',
78
- top: pagePos.top + (field.y + field.height) * zoom + 8,
79
- left: pagePos.left + field.x * zoom,
80
- minWidth: '100px',
81
- fontSize: '10px',
82
- position: 'absolute',
83
- }}
84
- >
85
- {error}
86
- </span>
87
- )}
88
- </Fragment>
66
+ <div style={{ width: 'inherit', height: 'inherit' }}>{children}</div>
67
+ {error && <span className="dte-pdf-field-error-message">{error}</span>}
68
+ </div>
89
69
  );
90
70
  };
@@ -7,6 +7,7 @@ import {
7
7
  } from '../../interface/types';
8
8
  import {
9
9
  dateToUtcZero,
10
+ getDropdownFieldItems,
10
11
  getFieldPlaceholderText,
11
12
  getFieldTextClipClassName,
12
13
  getFillablePaintedLabel,
@@ -36,6 +37,7 @@ const fillableTextStyles = {
36
37
  height: '100%',
37
38
  border: 'none',
38
39
  fontSize: 'inherit',
40
+ color: 'inherit',
39
41
  boxSizing: 'border-box' as const,
40
42
  padding: 0,
41
43
  margin: 0,
@@ -96,8 +98,7 @@ export const PdfViewFillable: FC<PdfViewFillableProps> = ({
96
98
  }) => {
97
99
  const resolvedValue = data?.[field.path!];
98
100
  const isViewByCurrentRecipient = fillingBy?.includes(field.recipient!);
99
- const isFillable =
100
- viewMode === 'preview' ? true : viewMode === 'fill' ? false : isViewByCurrentRecipient;
101
+ const isFillable = viewMode === 'preview' ? true : isViewByCurrentRecipient;
101
102
 
102
103
  const isInputDisabled = !isFillable || !!readOnly;
103
104
 
@@ -208,6 +209,28 @@ export const PdfViewFillable: FC<PdfViewFillableProps> = ({
208
209
  );
209
210
  }
210
211
 
212
+ if (field.subType === 'dropdown') {
213
+ return (
214
+ <select
215
+ style={fillableTextStyles}
216
+ disabled={isInputDisabled}
217
+ id={field.path}
218
+ name={field.path}
219
+ value={resolvedValue ?? ''}
220
+ onChange={e => handleDataChange(e.target.value)}
221
+ >
222
+ <option value="" disabled hidden>
223
+ {getFieldPlaceholderText(field) || 'Select...'}
224
+ </option>
225
+ {getDropdownFieldItems(field).map(item => (
226
+ <option key={item.value} value={item.value}>
227
+ {item.label}
228
+ </option>
229
+ ))}
230
+ </select>
231
+ );
232
+ }
233
+
211
234
  const TextComponent = field.multiline ? FillableTextareaInput : FillableTextInput;
212
235
  return (
213
236
  <TextComponent
@@ -144,7 +144,16 @@ export const PdfView: FC<PdfViewProps> = ({
144
144
  useLayoutEffect(() => {
145
145
  const field = navigableFields[currentFieldIndex];
146
146
  const navControlsEl = navControlsRef.current;
147
- if (!field || !navControlsEl) {
147
+
148
+ if (!navControlsEl) {
149
+ return;
150
+ }
151
+
152
+ if (currentFieldIndex === -1) {
153
+ navControlsEl.style.top = `20px`;
154
+ return;
155
+ }
156
+ if (!field) {
148
157
  return;
149
158
  }
150
159
 
@@ -152,7 +161,6 @@ export const PdfView: FC<PdfViewProps> = ({
152
161
  if (!fieldEl) {
153
162
  return;
154
163
  }
155
-
156
164
  const containerTop = contentContainerRef.current?.getBoundingClientRect().top ?? 0;
157
165
  navControlsEl.style.top = `${fieldEl.getBoundingClientRect().top - containerTop}px`;
158
166
  }, [currentFieldIndex, navigableFields]);
@@ -247,7 +255,7 @@ export const PdfView: FC<PdfViewProps> = ({
247
255
  key={field.id}
248
256
  field={field}
249
257
  data={viewMode === 'preview' ? previewData : data}
250
- error={errors[field.path!] || ''}
258
+ error={errors[field.path!] || errors[field.id] || ''}
251
259
  recipientsColors={recipientsColors}
252
260
  pdfWrapperRef={pdfWrapperRef}
253
261
  zoom={zoom}
@@ -1,5 +1,6 @@
1
1
  import NumbersIcon from '@servicetitan/anvil2/assets/icons/material/round/123.svg';
2
2
  import AbcIcon from '@servicetitan/anvil2/assets/icons/material/round/abc.svg';
3
+ import ArrowDropDownCircleIcon from '@servicetitan/anvil2/assets/icons/material/round/arrow_drop_down_circle.svg';
3
4
  import BadgeIcon from '@servicetitan/anvil2/assets/icons/material/round/badge.svg';
4
5
  import CalculateIcon from '@servicetitan/anvil2/assets/icons/material/round/calculate.svg';
5
6
  import CalendarMonthIcon from '@servicetitan/anvil2/assets/icons/material/round/calendar_month.svg';
@@ -46,10 +47,13 @@ export const FIELD_FONT_SIZE_OPTIONS = Array.from({ length: 17 }, (_, index) =>
46
47
  export const FILLABLE_FIELD_DEFAULT_SIZES = {
47
48
  text: { width: 200, height: 25 },
48
49
  date: { width: 200, height: 25 },
50
+ dropdown: { width: 200, height: 25 },
49
51
  radio: { width: 20, height: 20 },
50
52
  checkbox: { width: 20, height: 20 },
51
53
  } as Record<PdfFieldSubType, { width: number; height: number }>;
52
54
 
55
+ export const DROPDOWN_FIELD_MAX_ITEMS = 50;
56
+
53
57
  export const GENERIC_FIELD_TABLE_ROW_HEIGHT = 40;
54
58
 
55
59
  export const GENERIC_FIELD_DEFAULT_SIZES = {
@@ -63,6 +67,12 @@ export const FILLABLE_FIELD_TYPES: FieldTypeOption[] = [
63
67
  { label: 'Date Field', type: FieldTypeEnum.fillable, subType: 'date', icon: CalendarMonthIcon },
64
68
  { label: 'Checkbox', type: FieldTypeEnum.fillable, subType: 'checkbox', icon: CheckboxIcon },
65
69
  { label: 'Radio', type: FieldTypeEnum.fillable, subType: 'radio', icon: RadioIcon },
70
+ {
71
+ label: 'Dropdown',
72
+ type: FieldTypeEnum.fillable,
73
+ subType: 'dropdown',
74
+ icon: ArrowDropDownCircleIcon,
75
+ },
66
76
  ];
67
77
 
68
78
  export const E_SIGN_FIELD_TYPES: FieldTypeOption[] = [
@@ -61,12 +61,18 @@ const initializeDroppedFieldByType: Record<FieldTypeEnum, InitializeDroppedField
61
61
  ...base,
62
62
  recipient: recipientName,
63
63
  required: false,
64
+ hideLabel: base.subType === 'radio' || base.subType === 'checkbox',
64
65
  path: generateFillablePath(recipientName, uuidv4()),
65
66
  ...(base.subType === 'radio'
66
67
  ? {
67
68
  groupName: `Radio Group ${uuidv4()}`,
68
69
  }
69
70
  : {}),
71
+ ...(base.subType === 'dropdown'
72
+ ? {
73
+ dropdownItems: [{ value: '', label: '' }],
74
+ }
75
+ : {}),
70
76
  ...(defaultSize && {
71
77
  width: defaultSize.width,
72
78
  height: defaultSize.height,
@@ -28,9 +28,15 @@ export const usePdfFieldSelection = (
28
28
 
29
29
  const updateMatchingFields = (
30
30
  predicate: (field: PdfField) => boolean,
31
- updates: Partial<PdfField>,
31
+ updates: Partial<PdfField> | ((field: PdfField) => Partial<PdfField>),
32
32
  ) => {
33
- onFieldsChange(fields.map(f => (predicate(f) ? { ...f, ...updates } : f)));
33
+ onFieldsChange(
34
+ fields.map(f =>
35
+ predicate(f)
36
+ ? { ...f, ...(typeof updates === 'function' ? updates(f) : updates) }
37
+ : f,
38
+ ),
39
+ );
34
40
  };
35
41
 
36
42
  const deleteSelectedField = () => {
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './interface/types';
2
+ export * from './utils/exposes-utils';
2
3
  export * from './interface/host-events';
3
4
  export * from './components';
@@ -17,7 +17,7 @@ export enum ESignFieldType {
17
17
  fullName = 'fullName',
18
18
  }
19
19
 
20
- export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'radio' | 'number';
20
+ export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'radio' | 'number' | 'dropdown';
21
21
 
22
22
  export type GenericFieldType = 'table' | 'text';
23
23
  export type FormFieldType = 'number' | 'text' | 'date';
@@ -88,6 +88,11 @@ export interface ESignDateSignedOffsetSettings {
88
88
  | [...ESignDateSignedOffsetSettingsRule[], ESignDateSignedOffsetSettingsFinalRule];
89
89
  }
90
90
 
91
+ export interface FillableDropdownItem {
92
+ value: string;
93
+ label: string;
94
+ }
95
+
91
96
  export type FieldDataType =
92
97
  | GenericFieldTableDataType
93
98
  | GenericFieldTextDataType
@@ -125,6 +130,33 @@ export interface PdfFieldStyles {
125
130
  fontWeight?: 'normal' | 'bold';
126
131
  }
127
132
 
133
+ /**
134
+ * Optional author-configured starting value for a fillable field. The `text`/`number`/`checkbox`/
135
+ * `date`/`today`/`radio` variants mirror the backend's `FillableDefault` discriminated union
136
+ * (`ByodField.Default`, DTE-4668) one-for-one: same `type` discriminator and `value` shape, so the
137
+ * JSON round-trips as-is with no FE-side translation.
138
+ *
139
+ * `today` and `radio` intentionally carry no `value` - `today` is a sentinel the client resolves
140
+ * to a calendar date in its own local timezone at render time (never frozen at author time), and
141
+ * `radio` just marks *this* option field as the group's pre-selected default (the "value" is which
142
+ * option, i.e. which sibling field carries the flag - there is nothing else to store).
143
+ *
144
+ * `dropdown` has no backend counterpart at all (the backend doesn't recognize a `dropdown`
145
+ * fillable subtype - see `FillableFieldSubTypes`) and is an FE-only extension of this same union:
146
+ * `value` is one of this field's own `dropdownItems[].value` entries. Unlike `radio`, a dropdown's
147
+ * options all live on this one field (`dropdownItems`), not on sibling fields, so - same as
148
+ * text/number/checkbox/date - there is exactly one field to attach the default to, and the value
149
+ * it points at is meaningful to store here directly.
150
+ */
151
+ export type FillableDefault =
152
+ | { type: 'text'; value: string }
153
+ | { type: 'number'; value: number }
154
+ | { type: 'checkbox'; value: boolean }
155
+ | { type: 'date'; value: string }
156
+ | { type: 'today' }
157
+ | { type: 'radio' }
158
+ | { type: 'dropdown'; value: string };
159
+
128
160
  export interface PdfField {
129
161
  id: string;
130
162
  type: FieldTypeEnum;
@@ -154,6 +186,9 @@ export interface PdfField {
154
186
  */
155
187
  hideLabel?: boolean;
156
188
  styles?: PdfFieldStyles;
189
+ default?: FillableDefault;
190
+ /** Manually-authored options for a fillable `dropdown` field. See `FillableDropdownItem`. */
191
+ dropdownItems?: FillableDropdownItem[];
157
192
  }
158
193
 
159
194
  export type SchemaFieldType = 'number' | 'date';
@@ -11,3 +11,4 @@
11
11
  @import './pdf-document-renderer.css';
12
12
  @import './inline-editable.css';
13
13
  @import './field-config-panel.css';
14
+ @import './pdf-field.css';
@@ -13,6 +13,8 @@
13
13
  width: 100%;
14
14
  height: inherit;
15
15
  cursor: pointer;
16
+ outline: none;
17
+ overflow: hidden;
16
18
  }
17
19
 
18
20
  .inline-editable-html-text {
@@ -87,8 +87,9 @@
87
87
  }
88
88
 
89
89
  .dte-pdf-movable-next-button-wrapper {
90
- top: 50px;
90
+ top: 2px;
91
91
  transform: translateX(-100%);
92
+ transition: top 0.3s ease;
92
93
  position: absolute;
93
94
  z-index: 2;
94
95
  }